您的位置:首页 > 编程语言 > C语言/C++

C/C++ 基础知识学习笔记 (不断更新中)

2009-05-26 17:28 1296 查看
经验:

1、一个函数只实现相应的一个单功能

2、用extern声明外部变量

   (1)在同一个文件内,若定义在文件中间,要在文件前面使用,可以在前面用extern声明

   (2)在相关联的不同的文件内,若文件A定义了变量a,文件B要使用变量a,若都定义变量a会出现重复定义错误,在文件B中只要加上extern
a(不用加类型
如int,但建议加上,否则可能出现不可预期的错误,实际操作中的经验),就可以使用(如果不想要某变量被另一个文件外部引用,可以加上static以防
止外部引用)

3、关于变量的声明和定义

   一般为了叙述方便,把建立存储空间的声明称为定义,而把不需要建立存储空间的声明称为声明。

4、typedef的用法

   typedef int INTEGER 这样在程序中可以用INTEGER代替int来进行定义,如INTERGER a;

   typedef struct

   {

int month;

int day;

int year;

   }DATE;              这样可以用DATE定义变量:DATE birthday;  DATE *p;它代表一个结构体类型

  

   typedef int NUM[100];  这样直接用NUM n; 就可以定义整型数组变量n

   typedef int ARR[10];   这样用ARR a,b,c就等同于int a[10],b[10],c[10];

  

   typedef 与 #define的区别:(如typedef int COUNT与 #define COUNT int)

#define 在预编译时处理,它只能做简单的字符串替换,而typedef是编译时处理的,并非简单替换。typedef有利于程序的通用和移植

  typedef int (*pcb)(int , int) ;

  pcb p1,p2;

  以上等价于: int (*p1)(int,int);

                        int (*p2)(int,int);

5、数组的用法

   char a[10]; 定义一个数组

   char *p;  定义了一个指针

   对指针p进行附初值时可以: p = &a[0]; 或 p = a; 也可以在定义时赋 char *p = a(或者&a[0]);

  

   p+i 和 a+i 就是a[i]的地址值,也就是说p+1或a+1是指向数组中的下一个元素

  

   *(p+i) 和*(a+i)是p+i 和 a+i指向的数组元素,即a[i]

  

   指向数组的指针变量也可以带下标, 如p[i]与*(p+i)等价

   引用一个数组元素有以下方法: a[i]    ;   *(a+i)  ;    *(p+i)

   对一个数组赋值,可以a[10] = {'a','b','c','d','e','f'};

                      a[10] = {"hello!"}; 长度为7,末尾自动加上'/0'结束符

                      a[10] = "hello!";

    关于释放指针

    int *point = new int(10);

    int *pointArray = new int[4];

    释放是, delete point;

                   delete [ ] pointArray; //如果delete pointArray 会造成内存泄漏

6、sizeof

   (1)用于数据类型 : sizeof(type);  如 sizeof(int) 结果为4,表示4个字节

   (2)用于变量: sizeof(var_name);或者sizeof var_name; 带括号的用法更普遍

   (注意:sizeof操作符不能用于函数类型,不完全类型或位字段)

   (3)sizeof 的操作结果:sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型

    I.若操作数具有类型char、unsigned char或signed char,其结果等于1

    II.int、unsigned int 、short int、unsigned short 、long int 、unsigned
long 、float、double、long double 类型的sizeof 在ANSI
C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。

    III.当操作数具有数组类型时,其结果是数组的总字节数

    IIII.联合类型操作数的sizeof是其最大字节成员的字节数

    IIIII.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小

   (4)主要用途

    I.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信

    II.sizeof的另一个的主要用途是计算数组中元素的个数

7、对于位移动"<<" ">>"  左右移动都是补0

   如0x01<<1 得到0x02  0x80右移一位得到0x40;

   移位只针对C标准中的char和int类型,因此不能对float,double等进行位操作

8、选择输出类型

   如果一个数a=10;分别输出它的十进制、八进制、十六进制为:

   cout<<dec<<a<<' '     //输出十进制数

       <<oct<<a<<' '     //输出八进制数

       <<hex<<a<<endl;   //输出十六进制数

9、(一) 无名的函数形参

    声明函数时可以包含一个或多个用不到的形式参数。这种情况多出现在用一个通用的函数指针调用多个函数的场合,其中有些函数不需要函数指针声明中的所有参数。看下面的例子:

       int fun(int x,int y)

       {

           return x*2;

       }

   
尽管这样的用法是正确的,但大多数C和C++的编译器都会给出一个警告,说参数y在程序中没有被用到。为了避免这样的警告,C++允许声明一个无名形参,
以告诉编译器存在该参数,且调用者需要为其传递一个实际参数,但是函数不会用到这个参数。下面给出使用了无名参数的C++函数代码:

       int fun(int x,int)         //注意不同点

       {

           return x*2;

       }

    (二) 函数的默认参数

    C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型:

       #include "iostream.h"

       void show(int=1,float=2.3,long=6);

       int main()

       {

           show();

           show(2);

           show(4,5.6);

           show(8,12.34,50L);

           return 0;

       }

       void show(int first,float second,long third)

       {

           cout<<"first="<<first

               <<"second="<<second

               <<"third="<<third<<endl;

       }

10、static的用法

    (1)表示退出一个块后依然存在的局部变量/*  可以解决频繁调用某函数时某个初始变量不变的问题  */

    (2)表示不能被其他文件访问的全局变量和函数

    (3)属于类且不属于类对象的变量和函数

11、变量作用域

        C++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C语言中,必须要在函数开头部分。而且C++允许重复定义变量,C语言也是做不到这一点的。看下面的程序:

        #include "iostream.h"

       

        int a;

        int main()

        {

            cin>>a;

            for(int i=1;i<=10;i++)        //C语言中,不允许在这里定义变量

            {

                static int a=0;           //C语言中,同一函数块,不允许有同名变量

                a+=i;

                cout<<::a<<"   "<<a<<endl;

            }

            return 0;

        }

12、输出一些预定义的内容

    cerr<<__FILE__    //输出正在被编译的文件名字,包含路径

        <<__LINE__    //记录文件已经编译的行数

        <<__DATE__

        <<__TIME__

13、assert()

   
assert()是c语言标准库中提供的一个通用预处理器宏。在代码中长利用assert()来判断一个必需的前提条件,以便程序能够正确执行。需包含头
文件#include<assert.h> 
assert.h是C库头文件的C名字,C++库头文件的C++名字总是以字母C开头,后面是去掉后缀.h的C名字

使用断言来发现软件问题,提高代码可测性。

说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以
对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。

示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)

#ifdef _EXAM_ASSERT_TEST_  // 若使用断言测试

void exam_assert( char * file_name, unsigned int line_no )

{

    printf( "/n[EXAM]Assert failed: %s, line %u/n",

            file_name, line_no );

    abort( );

}

#define  EXAM_ASSERT( condition )

    if (condition) // 若条件成立,则无动作

        NULL;

    else  // 否则报告

        exam_assert( __FILE__, __LINE__ )

#else  // 若不使用断言测试

#define EXAM_ASSERT(condition)  NULL

#endif  /* end of ASSERT */

14、include 预处理器指示符preprocessor include directive

   #include<>表示这个文件是一个工程或标准头文件,查找过程会检查预定义的目录。可通过设置搜索路径环境变量或命令行选项来修改这些目录。  #include" " 表明该文件是用户提供的头文件,查找该文件时将从当前文件目录开始

15、exit()

    标准库函数exit()终止程序的执行。当函数中出现exit()时,该函数会立即结束全部程序,强制返回操作系统。

    exit()的作用类似于跳出整个程序。

    exit()的一般形式为void exit(int return_code);

    其中返回值return_code将送回调用过程,一般是操作系统。按照管理,0值一般表示正常结束,非0值表示某种错误。

    该函数包含在<stdlib.h>头文件中

16、变量的存储类别(从变量值的存在时间角度来分)

    (1)静态存储变量: 编译时分配存储空间的变量

       定义形式   变量定义前面加static,如static int a = 8;

    (2)动态存储变量: 程序运行期间分配固定的存储空间

       可以是函数的形参、局部变量、函数调用时的现场保护和返回地址。这些变量函数调用时分配存储空间,函数结束时释放存储空间。

       定义形式   变量定义前面加auto ,如auto int a,b,c  可省略不写。

17、 printf格式字符

    

     格式字符                                       说明

     d,i                    以带符号的十进制形式输出整数(整数不输出符号)

     o                      以八进制无符号形式输出整数(不输出前导符o)

     x,X                    以十六进制无符号形式输出整数(不输出前导符0x),用x输出十六进制数的a~f时以小写形式输出,用X时则以大                            写字母输出

     u                      以无符号十进制形式输出整数

     c                      以字符形式输出,只输出一格字符

     s                      输出字符串

     f                      以小数形式输出单、双精度数,隐含输出6位小数。

     e,E                    以指数形式输出实数

     g,G                   选用%f,或%e格式中输出宽度较短的一种格式,不输出无意义的0.

     附加格式

     字母l                  用于长整型整型,可加在格式符d、o、x、u前面

     m(代表一个正整数)      数据最小宽度

     n(代表一个正整数)      对实数,表示输出n位小数;对字符串,表示截取的字符个数

     -                      输出的数字或字符在域内向左靠

    在c语言中 ,printf("%-8d",a);即可在8个宽度内以左靠的形式显示字符。

                printf("%-5.2f",a);可以在以左靠5个宽度内输出两位精度的浮点数

    在c++中 ,cout<<a<<setw(8)<<b<<endl;空8个宽度再输出,需要包含头文件<iomanip>

18、大小写转换函数

    toupper函数,原型int toupper(int ch),作用是在ch为字母时,返回等价的大写字母;否则返回的ch值不变。toupper即to-upper的以此函数和toupper类似,只是返回等价小写字母。

    两个函数包含在<ctype.h>中

19、枚举类型enum

    enum weekday{sun,mon,tue,wed,thu,fri,sta}workday,week_end;

    对枚举变量赋值不能超过它的范围。weekday中的值依次是0,1,2,3,4,5,6

20、结构体struct和联合体union

    struct Student

    {

         int num;

         char name[];

         int  age;

         int  score[3];

    }stu[3];

    也可以使用typedef

    typedef struct

    {

         int num;

         char name[];

         int  age;

         int  score[3];

    }Student;

    定义时直接可以使用 Student stu[3];

    对于联合体

    union Description

    {

         int a;

         char b;

         double c;

    }des;

    可以进行赋值,des.a = 1;或des.b = 'a';或des.c = 30.11;

   

    也可以

    typedef union

    {

         int a;

         char b;

         double c;

    }Description;

    可以定义变量 Description des;

    des.a = 1;或des.b = 'a';或des.c = 30.11;

    该联合体中3个定义共占一个内存,长度为占用长度最长的那个变量长度。这里是double。

    每次只能使用3个中的一个,如des.a =1;然后可以输出值,如果des.a = 1;des.b='a';printf("%d,%s",des.a,des.b);只会产生一个值。

21、引用

    对一个数据的可以使用“引用”(reference),这是C++对C的一个重要扩充。C++之所以增加引用类型,主要是把它作为函数参数,以扩充函数传递数据的功能。

    引用是一种新的变量类型,它的作用是为一个变量起一格别名。(就自己的理解来说,引用操作的是变量的地址,而非变量本身的值)

    声明方法:int a; 

              int &b = a;

    说明:(1)a或b作用相同,都代表同一变量,声明变量b为引用类型并不需要重新开辟地址

          (2)&是引用声明符,并不代表地址

  (3)在声明一个引用类型变量时,必须同时使之初始化,即声明它代表哪一个变量


  (4)在声明变量b是变量a的引用后,在它们所在的函数执行期间,该引用类型变量b始终与其代表的变量a联系,不能再做其他变量的引用。如 int a1,a2;

         int &b = a1;

         int &b = a2;//企图使b又变成a2的引用是不行的

    引用作为函数参数。

    函数参数传递的两种情况。

    (1)将变量名作为实参和形参

    (2)传递变量的指针。

    int main()

    {

        void swap(int ,int);

        int i = 3,j = 5;

        swap(i,j);

        ...

    }

    void swap(int a,int b)//将变量名作为实参和形参

    {

int temp;

        temp = a;

        a = b;

        b = temp;

    }

    运行时输出还是3 5,没有进行交换。将变量作为实参和形参,传给形参的是变量的值,传递是单向的。如果在执行函数期间形参的值发生变化,并不传回给实参。因为在调用函数时,形参和实参不是同一存储单元。

   int main()

   {

void swap(int *,int *);

        int i = 3,i = 5;

        swap(&i,&j);

        cout<<i<<" "<<j<<endl;

        return 0;

   }

   void swap(int *p1,int *p2)

   {

int temp;

        temp = *p1;

        *p1 = *p2;

        *p2 = temp;

   }

   调用函数时把变量i和j的地址传给形参p1和p2(它们是指针变量),因此*p1和i为同一内存单元,*p2和j为同一内存单元。

   这种虚实传递仍然是“值传递”方式,只是实参的值是变量的地址而已。

   int main()

   {

void swap(int &,int &);

        int i=3,j=5;

        swap(i,j);

        ....

   }

   void swap(int &a,int &b)

   {

int temp;

temp = a;

a = b;

b = temp;

   }

  
在swap函数的形参列表中声明a和b是整型变量的引用。注意,此处&a不是a的地址,而是指“a是一个引用型变量”,&是引用型变量的
声明符。此时并没有对它们初始化,即没有指定它们是哪个变量的别名,它们也不占存储单元。当main调用函数时,由实参将变量名传给形参,i的名字传给引
用变量a,这样a就成了i的别名。

   其实虚实结合时是把实参i的地址传到形参a,是形参a的地址取实参i的地址,让a和i共享同一单元。这就是地址传递方式。

   与使用指针变量作形参时有什么不同?

   1、使用引用类型不必在swap函数中申明形参是指针变量。指针变量要另外开辟内存单元,其内容是地址。‘

   2、在main函数中调用swap时,实参不必用变量地址。系统向形参传送的是实参的地址而不是实参的值。

  

22、数值型和字符型数据的字节数和数值范围(依据谭浩强C++)

      类型标志符                字节                      数值范围  

int                      4       32位 (c中是16位)                   

unsigned int             4       32

short 2       16位

        long                     4       32位           -2147483648~+2147483647

        char                     1       8位            -128~127

        unsigned char            1       8              0~255

        float                    4       32             3.4*10e(-38)~3.4*10e(38)

double                   8       64

long double              8       64(c中为80位)

23、指针的一些用法和注意

定义指针   int *p;  //这里的*是指针声明符号

                   int a = 5;

                   p = &a;

                   printf("%d",*p);   //这里的*是指针运算符,它表示“指向”,*p指p所指向的内容

区别 变量的指针 和 指向变量的指针变量;前者是指变量的地址,后者是指针变量,用来指向另一个变量。比如上面p就是指针变量,而&a是a的指针,即p的内容。

关于形参实参的值传递,单向传递的问题:

int main()

        {

    int x=5,y=9;

    int *point1,*point2;

    point1 = &x,point2 = &y;

    swap(...);

    printf("%d,%d",a,b);

}

写一个swap()函数来实现两个数交换。

(1)  void swap(int *p1,int *p2)

(2)  void swap(int a, int b)

void swap(int *p1,int *p2)

{

    int tmp;

    tmp = *p1;             

    *p1 = *p2;                //指针变量所指向的变量值发生改变,在函数调用结束后依然保留

    *p2 = tmp;

}

void swap(int a, int b)

{

    int tmp;

    tmp = a;

    a = b;

            b = tmp;

}

对于当调用函数void swap(int *p1,int *p2)来实现时,输出为9,5,即实现了两个数的交换;而调用void swap(int a, int b)时,输出仍然为5,9;

这里需要注意的问题是: 什么是函数调用时,实参变量的值传给形参变量。这里即调用函数swap(point1,point2)时,实参变量point1、point2的值传给了形参变量p1,p2。

“值传递”是单向的。这句话是说实参将值传给形参以后,形参无论进行什么操作,都无法将其值再回送给实参,即形参无法再改变实参的值。swap(int
a, int
b)就是这种情况。为了使在函数中改变了的变量值能被main函数所使用,不能采取上述把要改变值的变量作为参数传给形参的办法,而应该用指针变量作为函
数参数。在函数执行过程中,指针变量所指向的变量值发生该变,函数调用结束后,变量值依然保存。

24、volatile

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

25、类模板 template<class 类型参数名>

    为了解决类里面的类型参数问题。与typedef int DataType这种感觉差不多。就是为了使同一作用的类对不同数据类型都有效。

    具体使用方法:

    template<class numtype>

    class Compare

    {

private :

numtype x,y;

public:

        Compare(numtype a,numtype b)

               {x = a; y = b;}

        numtype max()

        {return (x>y)?x:y;}

    }

    main()

    {

Compare<int> cmp1(3,7);           //注意这里再Compare后加<数据类型>

        cout<<cmp1.max();

        Compare<float> cmp2(3.1,7.5);

        cout<<cmp2.max();

    }

    注意,类模板的类型参数还可以有多个,每个类型前面都必须加class

    template<class t1,class t2>

    class someclass

    {...};

    在定义时分别代入实际类型名,如  someclass<int,float> obj;

26、文件操作 FILE

   

    文件操作主要涉及到几个函数:fopen,fwrite,fread,fclose。

    1、要创建文件指针:FILE *fp;

    2、可以利用fopen先创建一个没有的文件 fp=fopen("new.txt","w"),也可以利用它打开一个已有文件,但文件内容会被清空,可查询fopen中各打开类型的用法。

    3、利用fwrite函数写数据。size_t fwrite(  void* buffer,   //要写的数据的指针,比如要写int a=10;就给&a

                                             size_t size,          //写的数据类型长度,可用sizeof(int)这种来实现

                                             size_t count,         //要写的数据个数,如果是一个a,就1,如果要写一个数组,就给    //数组长度

        FILE* stream          //给文件指针fp

                                           );

    4、利用fread读数据              size_t fread(

   void* buffer,      //要读的数据的指针,比如要写int a=10;就给&a

  size_t size,       //读的数据类型长度,可用sizeof(int)这种来实现

   size_t count,      //要读的数据个数,如果是一个a,就1,如果要写一个数组,就给            //数组长度

   FILE* stream       //给文件指针fp

);

27、sizeof 和 strlen

    sizeof{variable|type}, strlen(const char[]);

    sizeof可以返回变量类型如int char float等的长度,并且单位是字节,如int 32位的话就返回4;还可返回变量的长度,如int a=10;sizeof(a) 是等于4的(这里是int为32位长);

    strlen用于返回字符数组的实际长度,如char str[20] = "China";  strlen(str) 的值是5,而sizeof(str)为20,要特别注意。

    列举几个例子好理解

    char a;

    int  b;

    char arr[50] = "Prison Break";

    int arr2[50] = "Prison Break";  //这种定义方法是通不过的

   

    sizeof(a) 为 1

    sizeof(b) 为 4

    sizeof(arr) 为 50   

    strlen(arr) 为 12

    strlen(arr2)    //编译不过,strlen的参数要求是 char[] 

    补充:对于一个struct来说,比如struct people

                                  {

int age;  //这里把int当作32位

                                        char sex;

                                        long number;

                                  }

                                  struct people me;

    在8位机上,也就是一些单片机上,sizeof(me)为4+1+4=9Byte,如果题目给出为32位平台下,则sizeof(me) = 4+4+4=12Byte。

    理解32位存储机制 先存32位的age,再存8位的sex,当再存32位的number时,一个单位的32位只剩下24位,不够,所以只能到下一个单位存number,因此sex也相当于占了32位,因此sizeof的结果是12。

    自然对界:

    struct 是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float 等)的变量,也可以是

一些复合数据类型(如array、struct、union 等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,

以提高运算效率。缺省情况下,编译器为结构体的每个成员按其自然对界(natural alignment)条件分配空间。各

个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size 最大的成员对齐。

例如:

struct naturalalign

{

char a;

short b;

char c;

};

在上述结构体中,size 最大的是short,其长度为2 字节,因而结构体中的char 成员a、c 都以2 为单位对齐,

sizeof(naturalalign)的结果等于6;

    指定对界:

一般地,可以通过下面的方法来改变缺省的对界条件:

使用伪指令#pragma pack (n),编译器将按照n 个字节对齐;

使用伪指令#pragma pack (),取消自定义字节对齐方式。

注意:如果#pragma pack (n)中指定的n 大于结构体中最大成员的size,则其不起作用,结构体

仍然按照size 最大的成员进行对界。

例如:

#pragma pack (n)

struct naturalalign

{

char a;

int b;

char c;

};

#pragma pack ()

当n 为4、8、16 时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n 为2时,其发挥了作用,使得sizeof        (naturalalign)的结果为6。

28、大端小端

    嵌入式系统人员应该对Big-endian和Little-endian非常清楚。小端模式是指CPU对操作数的存放方式是从低字节到高字节,而大端模式是指CPU对操作数的存放是从低字节到高字节。

    例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为

                     内存地址  0x4000     0x4001

                     存放内容   0x34       0x12

     而在Big-endian模式CPU内存中的存放方式则为:

                     内存地址  0x4000     0x4001

                     存放内容   0x12       0x34

     编程实现方式:若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

int checkCPU( )

{

    {

           union w

           {  

                  int  a;

                  char b;

           } c;

           c.a = 1;

           return(c.b ==1);

    }

}

联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写

29、运算符重载 (参阅C++  P317)

    在C++中,比如复数的相加,在类中声明一个函数 Complex operator+(Complex &c2);

    并实现它:

    Complex Complex::operator+(Comlex &c2)

    {

return Complex(real+c2.real,imag+c2.imag);

    }

    在主函数中

    int main()

    {

    
   Complex c1(1,2),c2(2,3),c3;

        c3 = c1 + c2;   //即可完成相加的结果

    }

    一般来说,应该令赋值操作符返回一个reference to *this. 即返回应该是一个引用。

    在实现 = 操作符的时候应该注意自我赋值
问题。

    class Bitmap{...};

    class Widget{

            ....

            private:

                Bitmap *pb;

    };

    Widget& Widget::operator=(const Widget& rhs)

    {

        delete pb;                             //如果是自我赋值, 这里不只是销毁当前对象的bitmap, 也是销毁rhs的bigmap,那么将导致错误

        pb = new Bitmap(*rhs.pb);

        return *this;

    }

    自我赋值解决方案:

    1. 证同测试 (identity test)

   Widget& Widget::operator=(const Widget& rhs)

    {

       if(this == &rhs) return *this;

        

        delete pb;

        pb = new Bitmap(*rhs.pb);

        return *this;

    }

   

    具备自我赋值安全性,但是不具备异常安全性(因为无论什么情况导致new Bitmap出现错误, pb的指向都会出错)。

    2. 异常安全性测试

   Widget& Widget::operator=(const Widget& rhs)

    {

        Bitmap *pOrig = pb;

       

        pb = new Bitmap(*rhs.pb);

        delete pOrig;

        return *this;

    }

    通常保证了异常安全性的都能够保证自我赋值安全性。

    这里即便new 操作抛出异常,pb还是指向原位置, 他并不高效,但是行得通。

    3. Copy and swap

    class Widget{

    ...

    void swap(Widget &rhs);    //交换*this 和rhs的数据

    };

    
  Widget& Widget::operator=(const Widget& rhs)

    {

        Widget temp(rhs);

        swap(temp);       

        return *this;

    }

    这种方法好比是值传递方式,直接进行拷贝然后copy,确保不会出现new问题。缺点是为了伶俐巧妙的修补而牺牲了清晰性。

30、头文件重复包含

    为了避免头文件重复包含,需要在头文件开头加 #ifndef _HEARDER_

                                               #define _HEARDER_

                                               ...

                                               #endif

    第一次编译发现没有定义_HEADER_宏,于是定义并编译。若另有文件包含了此头文件,会发现已定义了_DERDER_,于是跳到#endif执行。

31、memset和memcpy

    (1)  void *memset(void *s,int c,size_t n)

         总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

         如:char *p = "My space is TommySpace";

         利用memset(p, 'M', 5);可以将 *p改变成 "MMMMMace is TommySpace"

        

         通常它用作内存初始化

         char str[100];

         memset(str,0,100); //将开辟的内存全部初始化为0值

         一般用在对定义的字符串进行初始化为‘ ’或‘/0’;例:char a[100];memset(a, '/0', sizeof(a));

         还可以方便的清空一个结构类型的变量或数组。

         struct sample_struct

         {

                char csName[16];

int iSeq;

int iType;

};

         对于变量

         struct sample_strcut stTest; 一般的清空方式为:

         stTest.csName[0] = '/0'

         stTest.iSeq = 0;

         stTest.iType = 0;

         利用memset可以一次清空: memset(&stTest, 0, sizeof(stTest));

        

         如果是数组:struct sample_struct Test[10];

         利用memset一次清空:  memset(Test, 0, sizeof(10*(struct sample_struct));

     (2)  extern void *memcpy(void *dest, void *src, unsigned int count);

  功能:由src所指内存区域复制count个字节到dest所指内存区域。

  说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。

32、时钟周期、机器周期、指令周期

   

    时钟周期:也称震荡周期,是时钟脉冲的倒数。比如单片机外接晶振为12M,那么时钟周期为1/12000秒,是计算机中最基本、最小的时间单位。 在一个时钟周期内,CPU仅完成一个最基本的动作。一个时钟周期定义为一个节拍(用P表示)

    机器周期 
在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个
基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成

    指令周期 
指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。对于一些简单的的单字节指令,在取指令周期中,指令
取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。

32、容器类 Vector

   
参考资料 1

参考资料 2




创建、拷贝、销毁操作:

vector<Elem> c

vector<Elem> c1(c2)

vector<Elem> c(n)

c.~vector<Elem>()

vector<Elem> c(n,elem)

vector<Elem> c(beg,end)


非修改操作(包括大小操作和比较操作):

c.size()

c.empty()

c.max_size()

capacity()

reserve()

c1 == c2

c1 != c2

c1 < c2

c1 > c2

c1 <= c2

c1 >= c2

reserve(n) 增加容量至n,提供的n不小于当前大小。


        赋值操作:

c1 = c2

c.assign(n,value)   把n个值为value的元素赋给c

c.assign(beg,end)   把范围[beg,end)内的元素赋给c

c1.swap(c2)

swap(c1,c2)


元素访问操作:

c.at(idx)   返回索引号为idx的元素(如果索引号idx超出范围,抛出range error exception)。

c[idx]      返回索引号为idx的元素(无范围检查)。

c.front()   返回第一个元素(不会检查第一个元素是否存在)。

c.back()    返回最后一个元素(不会检查最后一个元素是否存在)。

对一个空容器调用[],front()和back()经常导致不确定行为,所以我们在调用[]时必须确保索引号是

有效的,在调用front()和back()时容器是非空的。


std::vector<Elem> coll; // empty!

if (coll.size() > 5) {

coll [5] = elem; // OK

}

if (!coll.empty()) {

cout << coll.front(); // OK

}

coll.at(5) = elem; // throws out_of_range exception


迭代器操作:

c.begin()

c.end()

c.rbegin()

c.rend()

c.insert(pos,elem)     在pos迭代器所指的位置处插入一个elem的拷贝,并返回新元素的位置。

c.insert(pos,n,elem)   在pos迭代器所指的位置处插入n个elem的拷贝,没有返回。

c.insert(pos,beg,end) 在pos迭代器所指的位置处插入范围[beg,end)内所有元素的拷贝,没有返回。

c.push_back(elem)      在末尾追加一个elem的拷贝。

c.pop_back()           移除最后一个元素,不会返回这个元素。

c.erase(pos)           移除pos迭代器位置处的元素,并返回下一个元素的位置。

c.erase(beg,end)       移除[beg,end)范围内所有的元素,并返回下一个元素的位置。

c.resize(num)          改变元素的数目为num,如果size()增加,新元素通过默认

函数创建。

c.resize(num,elem)     改变元素的数目为num,如果size()增加,新元素拷贝elem得到。

c.clear()              移除所有元素(使容器变为空)。

将vector用作普通数组:

对于vector容器类有下面的表达式成立:

&v[i] == &v[0] + i

其中v为vector容器类,定义如下:

std::vector<T> v;

下面看一个vector用作普通数组的例子:

std::vector<char> v;

v.resize(41); // 分配能容纳41个字符的空间,包括'/0'在内。

strcpy(&v[0], "hello, world"); // copy a C-string into the vector

printf("%s/n", &v[0]);

注意:不能将一个迭代器当作第一个元素的地址,迭代器是特别实现、有特殊含义的类型。

例如下面这样写是不对的:

printf("%s/n", v.begin()); // ERROR (might work, but not portable)

printf("%s/n", &v[0]); // OK

33.  static_cast

  用法:static_cast < type-id > ( expression )

  该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性

。它主要有如下几种用法:

  ①用于类层次结构中基类和子类之间指针或引用的转换。

  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;

  进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。

  ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

  ③把空指针转换成目标类型的空指针。

  ④把任何类型的表达式转换成void类型。

  注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

  C++中static_cast和reinterpret_cast的区别

  C++primer第五章里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释

  1、C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:

  int i;

  float f = 166.7f;

  i = static_cast<int>(f);

  此时结果,i的值为166。

  2、C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:

  int i;

  char *p = "This is a example.";

  i = reinterpret_cast<int>(p);

  此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,一个明显的现象是在转换前后没有数位损失。

        static_cast 和 reinterpret_cast 以及 c语言中的强制转换的区别:

        

        static_cast 是c++中采用的强制转换方法, 具有一定的安全性,即相关部分类型才能转换。

        reinterpret_cast 是c++中更类似与C的强制转换方法, 它允许更自由的转换, 但是编程人员需要清楚自己在做什么转换。

        c中的强制转换是随便怎么转换都行,不进行限制。

34. final 与static

      final:

      final可修饰类、域(变量和常量)、方法 (而static不修饰类)

1、final修饰类,表示该类不可被继承。

2、final修饰变量
程序中经常需要定义各种类型的常量,如:3.24268,"201"等等。这时候我们就用final来修饰一个类似于标志符名字。

3、修饰方法:
final修饰的方法,称为最终方法。最终方法不可被子类重新定义,即不可被覆盖。

    final 与const 如果是限定一些基本类型,没有太大区别,但是用于类, final则 表示其不可继承, 而const的类对象不可调用非const成员函数。

35. 引用、传值和传地址

    引用vs 传值

    传值操作是一个拷贝操作,能够进行操作的是实参的一个拷贝(形参), 以及返回值的一个拷贝。 如果传值对象代价高,这样的花销是不值得的。

    如果采用引用,即把引用结合const使用, 如 func(const CClassA& a){...} 则可以避免这种开销,因为他不使用拷贝,不会产生新的对象。

    但是对于内建类型,如int,float, char这些传值会比引用的效率高, 非内建类型通常使用引用效率高

    引用vs 传地址

    对于引用和传地址(即指针方式),因为指针需要随时判断是否为空,进行有效性判断,而引用在声明变量是必须初始化,因此无有效性判断,更加安全

    

36. 虚函数的使用

   

(1)当类要用来被继承的时候, 析构函数通常被声明为虚函数

  

        需要说明的是, 如果类有其他虚函数, 那么也应该有相应的虚析构函数。

        为什么这种类的析构函数通常要被声明为虚函数呢?
这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

        参考

        c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的   

      
 

37.  函数指针

    int max(int x,int y);

    int (*pFun)(int x, int y);  //这里pFun就是指向 int max(int x,int y)的函数指针, 其声明方法跟函数基本一样,只是名字有所替换(*pFun)替代了max

    pFun = max ; //给指针赋值, 函数名代表函数入口地址

38. 构造函数

    对于类中有一个成员变量是其他的构造函数带参数的类,如果不定义成指针,而是普通成员变量,其成员变量的构造需要使用构造函数初始化参数列表:

class A

{

    public:

    A(int a){ m_a = a;}

    private: int m_a;

}

class B

{

    public:

    B(int b): m_b(b), m_classA(b)       // 这里这样实现初始化

    {  

        m_pA = new A(b);

    }   

    private:

    int m_b;

    A m_classA;

    A *m_pA;

}

int main()

{

    B b(5);

    

}

    强调, 构造函数是用来进行初始化的,最好使用成员初始化列表来进行成员初始化,那种使用函数调用和成员变量赋值的方式不是真正的初始化,效率也不如初始化列表高。

    构造函数初始化顺序: 父类高于子类构造函数, 成员变量初始化顺序跟申明顺序相同。

39 const 的用法

(1) const 与函数

    void fun(int a) const     //任何不会修改数据成员的函数都因该声明为const类型

    {...}

    这说明函数内部不能改变类的成员变量的值。 如果修改了编译器会报错,大大提高了程序健壮性

   const void fun()    //通常用于运算符的重载

    {...}

    比如运算符重载后, 比如 a*b=c, 表示讲c付给了a*b的结果,这样是不行的,如果使用了const修饰,则编译不通过。

(2) const 与指针

    char greeting[] = "hello";

    char *p = greeting;                            //non-const pointer, non-const data

    const char*p = greeting;                   //non-const pointer, const data

    char * const p = greeting;                 //const pointer, non-const data

    const char* const p = greeting;        //const pointer, const data

    char *p

40 mutable 的用法

    当const用来 这样修饰时  void fun() const {...}, 函数内部的类数据成员是不能被修改的,但是如果定义时声明为 mutable, 则可以再这里被修改。

41 casting 转型

    转型是用来将进行一些类型转换的, 如const 型转为非const型, 非const型转为const型

       

    static_cast<const T&>(*this)[position]  //为*this加上const

    const string str = "hello";

    cosnt_cast<char&> str[3];    //将const转除

    

42 堆栈

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 

4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放

43 virtual

    在基类中,通常会出现virtual 的函数或者析构函数。 virtual 一般用于拿来继承的积累,virtual 函数的目的是允许derived class的实现得以客制化。

    比较特殊的是析构函数是否采用virtual。

    如果一个类是用来作为基类,而且目的是实现多态,那么通常会有一个virtual的析构函数。或者说任何class只要带有virtual函数都几乎确定应该有一个virtual析构函数。

  

    除了客制化,virtual析构函数还可以避免一下情况。

    class base

    {

        base();

        ~base();

        void print();

        。。。

    }

    class deriveClass:public base

    {

        deriveClass();

        ~deriveClass();

        void print();

        。。。

    }

    base *p = new deriveClass;

    delete p;

    当我们delete p 的时候要注意,如果base类的析构函数是non-virtual的, 那么delete只会调用~base(),而不会调用~deriveClass,这样会导致非父类的数据不会被销毁,造成内存泄漏。

如果基类采用了virtual ~base(), 那么这里就~deriveClass() (同时也应该调用了~base()), 达到我们真正的目的。

    另外一种情况就是关于上述print()函数。

    写一个函数   LetPrint(base b){ b.print();}  目的是调用print打印函数

    deriveClass child;

   
LetPrint(child);    这里调用的是base的print()还是deriveClass的print()呢?     
如果print函数在基类不是virtual的,那么一句LetPrint的参数类型,就是调用base的print,如果print是virtual的
呢?是不是应该就是deriveClass

    的print呢? 不然, 这里还是会调用base的print。因为LetPrint采用的是值传递的方式,这样会使继承的对象出现 切割
引用传值
的方式

    LetPrint(const base &b) 这样的话,print就是deriveClass的print了。

    总结起来,如果传入一个deriveClass 对象,会有如下结果

  
                                                                      
const Base &b                                     Base b

   
virtual print                                               
(deriveClass) print                                 (Base) print

   
print                                                         
(Base)print                                        (Base)print

    

    

    

44 explicit

    c++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的。所谓构造函数是显示的还是隐式的说明如下

    

    class Myclass

    {

        public:

        Myclass(int a);

        ...

    };

    int main()

{

    Myclass m = 10;    //默认的构造函数会隐式的把他转化为    Myclass temp(10);     Myclass m = temp;

}

    如果在构造函数前面加上 explicit Myclass(int a);

    则上述 Myclass m = 10 不会通过隐式转换。

45. frient 友元

    友元是为了让其他函数或其他类访问类的私有成员,避免将私有成员申明为public。

    class
 Internet   

{   
public
:   

    Internet(char
 *name,char
 *address)   

    {   

        strcpy(Internet::name,name);   

        strcpy(Internet::address,address);    

    } 
friend
 void
 ShowN(Internet &obj);//友元函数的声明 

friend class
friendClass;

public
:   

    char
 name[20]; 

    char
 address[20]; 

}; 

 

 
void
 ShowN(Internet &obj)//函数定义,不能写成,void Internet::ShowN(Internet &obj) 



    cout
<<obj.name<<endl; 



class
friendclass

{

  public:

    char* subtractfrom()

    {

  
        Internet    in;

           return in.name;

    }

};

void
 main
()   



    Internet a("中国软件开发实验室","www.cndev-lab.com");

    ShowN(a);

    friendclass c;

    cout<< c.substractform<<endl;

    cin
.get(); 

}

   

友元函数并不能看做是类的成员函数,它只是个被声明为类友元的普通函数,所以在类外部函数的定义部分不能够写成void Internet::ShowN(Internet &obj

46. 异常处理 try catch

    某一个可能发生异常的地方使用 throw **(任意类型), 并把这一部分放入try{}里面去, catch紧跟try并进行出错处理

int fun(int a,int b)

{

    int c = a+b;

    if(c<0) throw c;

    return c;

}

int main()

{

    int a = 3, b = -4;

    try

    {

           cout<<fun(a,b)<<endl;

    }

    catch(int)

    {

        cout<<a<<" "<<b<<" "<<"和小于0"<<endl;
    }

    cout<<"end"<<endl;

    return 0;

}

几个注意:

(1) try catch 中间不能夹杂其他语句 如

    try{}

    int a = 0;

    catch(int){}

(2) try 可以配多个catch, 因为可能抛出不同类型的异常

(3) 删节号 ... 表示可以捕捉任何异常    catch(...) { cout<<"OK";}

(4) try -catch 结构可以出现在不同函数中。

    他的找寻顺序是逐次往上层搜索catch (如果找不到catch ,会系统会调用系统terminal是程序终止)

·

(slicing)的问题,即继承的部分丢掉了,只留下了base的部分。解决的方法事使用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息