c++总结

经验:
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是程序终止)


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhongjiekangping/archive/2009/12/06/4952730.aspx

你可能感兴趣的:(C++,c,struct,Class,internet,编译器)