C++

来源:互联网 发布:基因大数据公司 编辑:程序博客网 时间:2024/06/03 07:55

一、关键字

1.extern

          extern有两个作用:

          (1)声明一个变量而不定义它

注:变量的定义用于为变量分配存储空间,还可以为变量指定初始值,在一个程序中,变量有且仅由一个定义。

       声明用于向程序表明变量的类型和名字,定义也是声明。

            比如在头文件中

extern int i;//声明int i;//定义且声明extern double pi =3.14;//定义//extern double pi;//声明但没有定义//extern double pi = 3.14;//错误,重定义extern void print(int *a, int len);

它的作用就是声明全局变量的作用范围的关键字或者函数,其声明的函数和变量可以在本模块或其他模块中使用。它是一个声明而不是定义!即B模块(编译单元)要是引用模块A(编译单元)中定义的全局变量时,它只要包含A模块的头文件即可,在编辑阶段,模块B虽然找不到该函数或变量,但它不会报错,它会再链接时从模块A生成的目标代码中找到此函数。

//main.cppextern void print();//在print.cpp中定义int main(){print();return 0;}

//print.cpp#include "stdio.h"void print(){printf("!!!!!!!\n");}


          (2)解决名字匹配问题,实现C++和C语言的混合编程。被extern “C” 修饰的变量和函数是按照C语言方式编译和链接的。作为一种面向对象的语言,C++支持函数重载,而过程式C语言不支持。函数被C++编译后在符号库中与C语言的名字不同。例如某个函数的原型为

extern "C" {          //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的    /**** some declaration or so *****/      void foo (int x, int y);}

该函数被C编译器编译后在符号库中的名字为 _foo,而C++编译器则会产生像_foo_int_int之类的名字,这样的名字包含了函数名,函数参数数量以及类型信息,C++就是靠这种机制来实现函数重载的。

注:引用一个已经定义过的全局变量可以采用a、引用头文件和b、extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定将那个变量写错了,那么在编译期间会报错;如果用extern方式引用,在编译期间不会报错,而在链接期间报错。

2.static

 (1)全局静态变量:在全局变量前加上关键词static

                 a.内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

                 b.初始化:未经初始化的全局静态变量会被程序自动初始化为0(auto对象的值是任意的,除非被显示初始化)

                 c.作用域:全局静态变量在声明他的文件之外是不可见的。准确地来讲是从定义之处开始到文件结尾

           好处:a.不会被其他文件访问、修改

                      b.其他文件中可以使用相同名字的变量,不会发生冲突。

(2)局部静态变量:在局部变量前加上关键词static

                 a.内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

                 b.初始化:未经初始化的局静态变量会被程序自动初始化为0(auto对象的值是任意的,除非被显示初始化)

                 c.作用域:作用域仍为局部作用域,当定义他的函数或者语句块结束的时候,作用域随之结束。

         注:当static用来修饰局部变量的时候,他就改变了局部变量的存储位置,从原来的栈中改为静态存储区。但是局部变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过不能再对他进行访问。

            

                 局部静态对象在程序执行到该对象的声明处时被首次初始化,其值在下次调用时仍维持上次的值

                 无论全局还是局部静态变量,生存周期都是从声明处到程序结束

(3)静态函数:在函数返回类型前加上关键词static

          函数的定义和声明默认情况下是extern的,静态函数只在声明他的文件中可见,不能被其他文件所用。

(不加static意味着external链接属性,可能在其它编译单元有定义,或者被其它编译单元使用,由于某种需要,一般是有一个跳转表。
而static只在本编译单元用,所以没有跳转表。
也就是说,不加static的会执行一个jmp然后再到函数体代码,而static的会直接执行。
为什么要有一个表呢?正是实现多编译单元的相互作用。比如函数在A中定义,在B中调用,于是调用的代码只需要走到跳转表就行了,而链接的时候,才知道跳转到哪里,这个时候就合起来了。

          好处:

                 a.其他文件中可以定义相同名字的函数,不会发生冲突。

                 b.静态函数不能被其他文件所用。

注:存储说明符auto,register,extern,static对应两种存储期:自动存储期和静态存储期。auto和register对应自动存储期,具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时被撤销。关键字static和extern用来说明具有静态存储期的变量和函数,用static声明的局部变量具有静态存储持续期,或静态范围。虽然其值在函数调用之间保持有效,但是其名字的可视性仍限制在局部域内

总结:static具有如下作用

          (1)设置变量的存储域,static声明的局部变量具有静态存储持续期,或静态范围,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。

          (2)限制变量的作用域,在模块内的static变量可以被模块内的所有函数访问,但不能被模块的其他函数访问;

          (3)限制函数的作用域,在模块内的static函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明他的模块内。

C++内对static的扩展

          (4)在类中static成员变量以为这它为该类的所有实例所共享,也就是说某个类的实例修改了该静态成员变量,其修改值为该类的所有其他实例所见。

          (5)在类中的static成员函数就属于整个类所拥有,这个函数不接受this指针,因而只能访问类的static成员变量。

(4)静态成员:静态成员的的提出是为了解决数据共享问题(还有设置全局性的变量)

    静态成员初始化与一般成员初始化不同,初始化只能在类外进行( static const int 除外),而前面不加static,以免与一般静态变量或对象相混淆,且只能初始化一次,初始化时不加该成员的访问权限控制符,格式如下

<数据类型><类名>::<静态数据成员名>=<值>

class Test  {  private :      int var1;      // int var11= 4; 错误的初始化方法      const int var2 ;      // const int var22 =22222; 错误的初始化方法      static int var3;      // static int var3333=33333; 错误,只有静态常量int成员才能直接赋值来初始化      static const int var4=4444; //正确,静态常量成员可以直接初始化        static const int var44;  public:      Test(void);      ~Test(void);  };  --------------------Test.cpp-----------------------------------  #include ".\test.h"    int Test::var3 = 3333333; //静态成员的正确的初始化方法    // int Test::var1 = 11111;; 错误静态成员才能初始化  // int Test::var2 = 22222; 错误  // int Test::var44 = 44444; // 错误的方法,提示重定义  Test::Test(void):var1(11111),var2(22222)正确的初始化方法//var3(33333)不能在这里初始化  {      var1 =11111; //正确, 普通变量也可以在这里初始化      //var2 = 222222; 错误,因为常量不能赋值,只能在 “constructor initializer (构造函数的初始化列表)” 那里初始化                  var3 =44444; //这个赋值是正确的,不过因为所有对象一个静态成员,所以会影响到其他的,这不能叫做初始化了吧  }  

(1)static成员的名字在类的作用域中,因此可以避免与其他类的成员或成员函数冲突

(2)可以实现封装,static成员可以是私有的,而全局对象不可以

(3)static成员与特定类相关联,并不与类的对象相关联

注:特殊的const static数据成员:只要初始化式是一个常量表达式,整型const static数据成员就可以在类的定义体内进行初始化;

       C++primer注释说该数据成员仍必须在类外进行定义,而不必指定初始值;但实际在VS2008中运行时是不必要在类定义体外进行定义的。

(4)静态成员函数:静态成员函数可以在类内或类外定义,但必须在类内声明

       a、static成员函数没有this指针,所以不能直接引用非static数据成员

       b、staitic成员不是任何对象的组成,所以static成员函数不能被声明为const,因为const只限定该类的对象;

       c、static成员函数不能同时被声明为虚函数(虚函数是动态绑定)

注:1、static成员不能被继承。子类可以访问父类的static变量,但受访问控制(若父类中的static是private就无法访问)子类和父类的static变量是同一变量,共享同一存储空间、而继承关系,子类和父类是分别有自己的存储空间的。

        2、普通成员是给定类的每个对象的组成部分,static成员独立于任何对象而存在,不是类类型对象的组成部分,所以他们的使用方法对于非static数据成员而言是不合法的。

class Bar{public://...private:static Bar mem1;//static数据成员可以是该成员所属的类类型Bar *mem2;      //非static成员被限定声明为其自身类对象的指针或引用};

static 成员可以用作默认实参;非static数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用


3.const

const内容太多了,附链接

http://blog.csdn.net/mishifangxiangdefeng/article/details/7109046

1. const的用法:

(1)定义常量

(2)修饰函数的参数

(3)修饰函数的返回值

         注:pass_by_reference-to-const通常比较高效

                 可以避免切割问题

class Window{...}class WindowWithScollBars:public Window{...}void printNameAndDisplay(Window w){    //....}//pass_by_reference-to-const:参数传进什么类型w就是什么类型,调用WindowWithScrolBas::display//pass by value:参数w会被构造成一个Window对象,所有的特化信息会被切除


(4)修饰函数的定义体


2. 用const 修饰函数的参数

(1)const 只能修饰输入参数
如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针。
    例如:void StringCopy(char *strDestination, const char *strSource);

(2)如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。
    例如:不要将函数void Func1(int x) 写成void Func1(const int x)。

(3)对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。

     例如:将void Func(A a) 改为void Func(const A &a)。

因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间;“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。

(4)对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。

     例如:void Func(int x) 不应该改为void Func(const int &x)。

因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。


3. 用const 修饰函数的返回值

(1)如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如:函数const char * GetString(void);
正确的用法是: const char *str = GetString();  //写为char *str = GetString();
将出现编译错误

(2)如果函数返回值采用“值传递”方式,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
例如:不要把函数int GetInt(void) 写成const int GetInt(void)。

(3)函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数


4.const 成员函数

任何不会修改数据成员的函数都应该声明为const 类型。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。例如:


常量指针和指针常量

const A*:指向的内容的值不能变

A* const:指针不能重新指向别的对象

const实现原理:C对const变量没有做过多处理,编译后const和普通变量没有区别,只是在编译过程中,编译器会检查代码中是否有队const变量进行修改的代码,如果有则向用户报错。在编译过后,const变量就和普通变量相同了。而且如果使用memset去修改const变量的内容,也完全没有问题,这就可以看出const修饰是属于编译层面的限制,一般不会涉及到运行层面。

const int a = 1;    int* b = (int*)&a;    *b = 31; //是可以通过的

const define 区别

http://blog.csdn.net/love_gaohz/article/details/7567856

注:非const对象默认为extern

       const对象默认为文件的局部变量,要使const变量能够在其他的文件中访问,必须显示的指定它为extern

//file1.ccextern const int bufSize = fcn();//file2.ccextern const int bufSize; //使用file1的bufSize


        可基于函数的引用形参是指向const对象还是指向非const对象,实现函数重载。非const对象既可以用于初始化const引用,也可用于初始化非const引用,但是将const引用初始化为非const对象需要通过转换来实现。

Record lookup(Account &);Record lookup(const Account &);
       对指针形参的处理如出一辙

f(int *);f(const int *);
      但不能基于指针本身是否是const来实现函数的重载
f(int *);f(int *const );//重定义



        


注意:const 成员和引用成员只能通过构造函数初始化列表初始化。 

4、sizeof

       sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。作用就是返回一个对象或者类型所占的内存字节数。

sizeof( object ); // sizeof( 对象 );sizeof( type_name ); // sizeof( 类型 );sizeof object; // sizeof 对象;
(1)

int i=2;printf("%d\n", sizeof(i));printf("%d\n", sizeof(i + 3.14));//// 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );

(2)sizeof也可以对一个函数调用或者表达式求值,其结果是函数返回类型的大小,函数并不会被调用

int testSizeOf(){printf("sizeof is called");return 1;}int main(){int i = 2;        printf("sizeof(++i)=%d,i=%d\n", sizeof(++i),i);printf("%d\n", sizeof(testSizeOf()));//testSizeOf没有被调用return 0;}

(3)sizeof的常量性
sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如:

int a[sizeof(i)];


sizeof对对象求内存大小,最终都是转换为对对象的数据类型进行求值。

(4)定义一个空类型,里面没有任何成员变量和成员函数,对该类型求sizeof,得到的结果为1

分析:当声明该类型的实例的时候,他必须在内存中占有一定的空间,否则无法使用这些实例,占用多少内存由编译器决定(Visual Studio中每个空类型占据一字节)


(5)如果在该类型中添加一个构造函数和析构函数之后,再求sizeof

      调用构造函数和析构函数只需要知道函数的地址,函数的地址只和类型相关,而和类型的实例无关,编译器也不会因为这两个函数而在实例内添加任何额外的信息。


(6)将析构函数标记为虚函数

分析:C++编译器一旦发现一个类型中有虚函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针;在32位的机器上,一个指针占4字节空间;在64位机器上,一个指针占8字节空间。


5、inline

      5-1 内联函数和宏定义的区别

     (1)内联函数在编译时展开,可以做一些类型检测处理;宏定义在预编译时展开

     (2)内联函数直接嵌入到目标代码中,宏则是简单的做文本替换。

     (3)C++中引入了类及类的访问控制,在涉及到类的保护成员和私有成员就不能使用宏定义来操作

      5-2 内联函数有哪些优势

     (1)和普通函数相比,内联函数是放在符号表中,使用时像宏一样展开,没有调用的开销,效率很高

     (2)和宏相比,inline函数是真正的函数,所以要进行一系列的数据检查

     (3) 和宏相比,inline函数作为类的成员函数,可以使用类的保护成员和私有成员

      5-3 为什么不能把所有的函数写成inline函数

      内联函数是以代码膨胀(复制)为代价的,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少;另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

      以下情况不宜使用内联

     (1)函数体内的代码比较长

     (2)函数执行的时间比函数调用开销大

注意:a、inline的使用是有所限制的,inline只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。

          b、inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思

          c、 建议:inline函数的定义放在头文件中:因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然就成了非内联函数的调用了。

          d、定义在类中的成员函数缺省都是内联的(也是一种建议),如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上inline,否则就认为不是内联的。


6、类型转换

C风格:TYPE(EXPRESSION)

缺点(1)可以在任意类型之间转换   (2)不容易查找


c++风格

(1)static_case

         用来强迫饮食转换,例如将non-const对象转为const对象,或将int转为doublel;也可以将void*转为typed指针,将pointer to const转为pointer to derived;!!但它无法将const转为non-const——只有const_cast才能办到

         最常用,一般用于基本数据类型之间的转换,no run-time check,编译时判断。 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. float f;  
  3. f=(float)i;  
  4. f=static<float>(i);  

(2)const_cast

去掉变量的const属性,是唯一有此能力的c++style转型操作符

一、常量指针被转化成非常量的指针,并且仍然指向原来的对象;
二、常量引用被转换成非常量的引用,并且仍然指向原来的对象;
三、const_cast一般用于修改底指针。如const char *p形式。

变量本身的const属性是不能去除的,要想修改变量的值,一般是去除指针(或引用)的const属性,再进行间接修改。

通过const_cast运算符,也只能将const type*转换为type*,将const type&转换为type&。

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. const int a=2;  
  2. int b=const_cast<int>(a);  
编译错误



[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. const int a=2;  
  2. int *b=const_cast<int*>(&a);  
  3. *b=1;  
  4. cout<<*b<<endl;  
成功运行,b=1


(3)dynamic_cast

         将基类指针或引用安全的转换为派生类的指针或引用,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理(基类中必须有虚函数)

//type-id必须是累的指针、类的引用或者void*,expression与type-id相应必须是累的指针、类的引用或者void*  dynamic_cast<Type*>(BasePtr)//将基类类型的指针转换为派生类类型的指针  dynamic_cas<Type&>Ref;//引用

指针:在运行时,㘝basePtr实际指向Derived对象,则转换将成功,并且dericedPtr将被初始化为指向basePtr的Derived对象;否则转换的结果是0,意味着DrivedPtr将置为0

引用:只有当Ref实际引用一个Type类型对象,或者Ref是一个Type派生类型的对象的时候,dynamic_cast操作才将操作数val转换为想要的Type&类型,因为不存在空引用,所以当转换失败时,它抛出一个std::bad_cast异常

        dynamic_cast运算符可以在执行期决定真正的类型,如果down cast是安全的,即如果基类指针或引用确实指向一个派生类对象,这个运算符会传回适当转型过的指针;如果down cast不安全,会传回空指针(即基类指针或引用没有指向一个派生类对象)
        在进行类层次见的上行转换时,dynamic_cast和static_cast效果相同;

        在进行下行转换时,dynamic_static具有类型检查的功能,比static_cast更安全。

        如果类型T不是a的某个基类型,该操作将返回一个空指针。

class B{public:virtual void print(){}};class C:public B{};int main(){B *pb1 = new B;B *pb2 = new C;C *c1 = dynamic_cast<C*>(pb1);C *c2 = dynamic_cast<C*>(pb2);if (c1 == NULL)printf("c1 is NULL.\n");//基类指针或引用没有指向一个派生类对象,传回空指针  if (c2 == NULL)printf("c2 is NULL.\n");return 0;}




(4)reinterpret_cast

        reinterpret即为重新解释,此标识符的意思即为数据的二进制形式重新解释,但是不改变其值。(例如将一个pointer to int转型为一个int)

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.        int i;  
  2. char* pstr = "hello world";  
  3. i = reinterpret_cast<int>(pstr);  

7、volatile

它是被设计用来修饰被不同线程访问和修改的变量。

volatile提醒编译器它后面所定义的变量随时都可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据,如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值。

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的

8、overide和final

http://blog.csdn.net/u013470115/article/details/42679911


9、异常

异常机制提供程序中错误检测和错误处理部分之间的通信,c++的异常处理中包括:

       throw表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说throw引发了异常条件

       try块,错误处理部分使用它来处理异常。try语句块以try关键字开始,并以一个或多个catch子句处理,在try块中执行的代码所抛出的异常,通常会被其中一个catch子句处理,因此catch自居也被称为处理代码。

       在复杂的系统之,程序的执行路径也许在遇到抛出的异常代码之前,就已经经过了多个try块。例如,一个try块可能调用了包含另一try块的函数,它的try块又调用了含有try块的另一个函数,如此类推。

       寻找处理代码的过程与函数调用链正好相反。抛出一个异常时,首先要搜索的是抛出异常的函数,如果没有找到匹配的catch,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的catch,如果仍然没有找到相应的处理代码,该函数同样要终止,搜索调用它的函数,如果类推,继续按执行路径回退,直到找到适当类型的catch为止。

       如果不存在处理改异常的catch自居,程序的运行就要跳转到名为terminate的标准库函数,该函数在exception头文件中定义,这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出


http://www.cnblogs.com/ggjucheng/archive/2011/12/18/2292089.html

http://blog.csdn.net/sky453589103/article/details/49862949

http://blog.csdn.net/zhy_cheng/article/details/8217896

http://blog.csdn.net/zhy_cheng/article/details/8220539

http://blog.csdn.net/zzjxiaozi/article/details/6649999

7.typedef:定义别名

http://blog.csdn.net/wangqiulin123456/article/details/8284939


8.explicit

   在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)

http://www.cnblogs.com/ymy124/p/3632634.html


9.异常机制

http://www.cnblogs.com/ggjucheng/archive/2011/12/18/2292089.html


10、命名空间

命名空间污染:在一个给定的作用域中定义的每个名字在该作用域中必须是唯一的,由独立开发的库构成的复杂程序更有可能遇到名字冲突。

命名空间:为防止名字冲突提供了更加可控的机制,命名空间能够划分全局命名空间,这样使用独立开发的库就更加容易了。一个命名空间是一个作用域

namespace cplucplus_primer{     class Sales_item{//...};     Sales_item operator+(const Sales_item&, const Sales_item&);     //....}


二、内存

(1)new、delete、malloc、free

         malloc/free:

void *malloc(long NumBytes);//该函数分配NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针void free(void* FistByte);//该函数是将之前用malloc分配的空间还给程序或是操作系统,也就是释放了这块内存(不是释放指针本身),其中指针必须指向所释放内存的首地址

#include <stdlib.h>#include <iostream>using namespace std;class A{A(){ cout << "construct" << endl; }//没有调用~A(){ cout << "destruct" << endl; }//没有调用};int main(){A* pA = (A*)malloc(sizeof(A));free(pA);pA = NULL;return 0;}


new/free:

         new的时候会有两个时间发生:a.内存被分配(通过operator new函数) b.为被分配的内存调用一个或多个构造函数

         delete的时候也有两件事发生:a.为被释放的内存调用一个或多个析构函数   b.释放内存(通过operator delete函数)

         总结:

                 a.malloc/free是C/C++的标准库函数,new/delete是C++运算符

                 b.new能自动分配空间大小

                 c.对于用户自定义的对象而言,用malloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数不是运算符,不在编译器控制权限之内,不能把执行构造函数和西沟函数的任务强加于malloc/free。因此C++需要一个能对对象完成动态分配和初始化工作的运算符new,以及一个能对对象完成清理与释放内存工作的运算符delete。

(2)delete和delete[]

         delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。当delete[]操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。

         delete和new配套,delete[]和new[]配套

         对于内建简单数据类型,delete和delete[]功能是相同的,对于自定义的复杂数据类型,delete和delete[]不能互用。

3、空指针、悬垂指针/野指针

      空指针:赋值为NULL的指针

      悬垂指针/野指针:指向垃圾内存的指针。成因:a.指针变量没有被初始化。  b.指针被free或delete后,没有置为NULL   c.指针操作超过了变量的作用范围。(使用智能指针可以解决)

//情况bvoid func(){    char *dp = malloc(A_CONST);    free(dp);//此时dp为悬垂指针    dp = NULL;//空指针}//情况cclass A  {  public:      void Func(void)      {          cout << "Func of class A" << endl;      }  };    class B  {  public:      A * p;            void Test(void)      {          A a;          p = &a;     // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B      }        void Test1(void)      {          p->Func();   // p 是“野指针”      }  }; 


      区别:a.空指针可以被多次delete,而悬垂指针的再次删除时程序会变得非常不稳定

                 b.使用空指针和悬垂指针都是非法的,可能造成程序崩溃,但空指针和悬垂指针相比是一种可预料的崩溃。

注:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

4、智能指针

      引入原因:实现类似JAVA中的垃圾回收机制,使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存之后,无需去关注应该何时何地地释放内存,JAVA会帮助回收。

            智能指针的出现时为了满足管理类中指针成员的需要,包含指针成员的类需要特别注意复制控制和复制操作,原因是复制指针时只复制指针的地址,而不会复制指针指向的对象。当类的实例在析构时,可能会导致悬垂指针问题。

       指针管理的两种方法: a.采用值型的方式管理:每次每个类对象都保留一份指针指向的对象的拷贝;  b.智能指针:实现指针指向的对象的共享

       智能指针的实现:智能指针的一种通用实现技术是引用计数,智能指针将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少对象共享同一指针。

              每次创建类的新对象时,初始化指针并将引用计数置为1;

              当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;

              对一个对象进行赋值时,赋值操作符减少左操作数所指向对象的引用计数;

              调用析构函数时,析构函数减少引用计数;如果引用计数减至0,则删除基础对象。

      C++中的智能指针:

智能指针比较

       为了维护独占性,auto_ptr的复制和赋值都是破坏性操作,标准库的容器要求复制和赋值后两个对象相等,因此,STL容器容不得auto_ptr。而C++11新出现的智能指针unique_ptr比auto_ptr更聪明好用,unique_ptr拒绝直接的复制/赋值操作,必须通过reset/release接口来进行对象管理权的转移,这无疑提高了安全性。

       auto_ptr和shared_ptr都在其析构函数内做delete而不是delete[]动作,那意味着在动态分配而得的array身上使用auto_ptr或者shared_ptr是够馊主意。

      auto_ptr的缺陷:

      (1)不要使用auto_ptr对象保存指向静态分配对象的指针,否则当auto_ptr对象本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义行为。

      (2)永远不要使用两个auto_ptr对象指向同一对象,导致这个错误一个明显方式是,使用同一指针来初始化或者reset两个不同的auto_ptr对象;使用同一个auto_ptr的get函数的结果来初始化或者reset另一个auto_ptr对象。

      (3)不用使用auto_ptr对象保存指向动态分配数组的指针,当auto_ptr对象被删除的时候,它只会释放一个对象——它使用普通的delete操作符,而不用数组的delete[]操作符

      (4)不要将auto_ptr对象存储在容器中,容器要求所保存的类型定义复制和赋值操作符,使得、他们表现得类似于内置类型的操作符:在复制(赋值)之后,两个对象必须具有相同值,auto_ptr类不满足这个要求。

unique_ptr

weak_ptr



#include <memory>#include <iostream>#include <string>using namespace std;int main(){    unique_ptr<int> up1(new int(11));    unique_ptr<int> up2 = up1;   //! 编译时会出错 [1]    cout << *up1 << endl;    unique_ptr<int> up3 = move(up1);  //! [2]    cout << *up3 << endl;    if (up1)        cout << *up1 << endl;    up3.reset();  //! [3]    up1.reset();    shared_ptr<string> sp1(make_shared<string>("Hello"));    shared_ptr<string> sp2 = sp1;    cout << "*sp1:" << *sp1 << endl;    cout << "*sp2:" << *sp2 << endl;    sp1.reset();    cout << "*sp2:" << *sp2 << endl;    weak_ptr<string> wp = sp2; //! [4]    cout << "*wp.lock():" << *wp.lock() << endl;    sp2.reset();    cout << "*wp.lock():" << *wp.lock() << endl;  //! 运行时会出错    return 0;}

[1]: unique_ptr 是禁止复制赋值的,始终保持一个 unique_ptr 管理一个对象。
[2]: unique_ptr 虽然不能赋值,但可以通过 move() 函数转移对象的所有权。一旦被 move() 了,原来的 up1 则不再有效了。
[3]: reset() 可以让 unique_ptr 提前释放指针。

[4]: 由 shared_ptr 构造一个 weak_ptr。


5、调用函数时要进行参数压栈,一般情况下顺序是从右往左

6.经常要操作的内存分为那几个类别?

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

  (2)存放动态分配的变量,一般由程序员分配和自动释放,若程序员不释放,程序结束时可能由OS回收;与数据结构中的堆是两回事,分配方式类似于链表

注:内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。

  (3)全局区(静态区):全局变量和静态变量存放在这一块,初始化和未初始化的分开放;程序结束后由系统释放

  (4)文字常量区:常量字符串,程序结束时自动释放

  (5)程序代码区:存放函数体的二进制代码

         一个正常的程序在内存中通常分为程序段、数据段和堆栈三部分。程序段里放着程序的机器码、只读数据,这个段通常是只读,对它的写操作是非法的;数据段存放的是程序中的静态数据;动态数据则通过堆栈来存放。

         在函数体中定义的变量通常是在栈上,用malloc、calloc和realloc等函数分配得到的就是再堆上;在所有函数体外定义的书全局量,加了static修饰符后不管放在哪里都是存放在全局区(静态区);

//main.cpp int a = 0; //全局初始化区char *p1; //全局未初始化区int main(){int b; //栈char s[] = "abc"; //栈char *p2; //栈char *p3 = "123456"; //123456\0在常量区,p3在栈上。static int c = 0; //全局(静态)初始化区p1 = (char *)malloc(10);p2 = (char *)malloc(20);//分配得来得10和20字节的区域就在堆区。strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。}//字符串放在常量区,对其修改其结果是未定义的;如果要修改请使用字符串数组,他放在非常量区域

6.堆和栈的区别?

  (1)申请方式

                  栈:由系统自动分配

                  堆:需要程序员自己申请并指明大小,由程序员进行释放;

  (2)申请大小限制:

                  栈:像低地址方向扩展的数据结构,栈的大小固定,如果申请的空间超过栈的剩余空间,将提示overflow(栈溢出);

                  对:像高地址扩展,是不连续的内存区域,空间相对较大且灵活(因为系统使用链表来存储空闲内存地址,不连续,而链表的遍历方向由低地址想高地址,堆的大小受限于计算机系统中有效的虚拟内存)

 (3)申请效率:

                 栈:有系统自动分配,速度快,但程序员无法控制;

                 堆:由malloc分配的内存,一般速度比较慢且容易产生碎片,不过用起来最方便(申请空间时遍历链表,知找到的对接点大小不一定正好等于申请的大小,系统会自动将多余的部分重新放入链表中)


7、缓冲区溢出攻击

缓冲区溢出攻击


三、面向对象

1、C++面向对象的三个基本特征

         封装、继承、多态

         封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。

                    好处:对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

         继承:可以使用现有类的所有功能,并在无需编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为子类或派生类,被继承的类称为基类或父类。继承的过程就是从一般到特殊的过程。

                    实现继承:直接使用基类的属性和方法,而无需额外编码的能力;

                    接口继承(interface):仅适用属性和方法的名称,但是子类必须提供实现的能力。

         多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中。这个共享接口是以virtual function机制引发的

2、虚函数、纯虚函数

         虚函数:在基类中冠以关键字virtual的成员函数。他提供了一种接口界面,允许在派生类中对基类的虚函数重新定义。

         纯虚函数:在基类中中声明的虚函数,在函数原型后加=0,在基类中没有定义,以便在派生类中根据需要对它进行定义。作为接口而存在,纯虚函数不具备函数的功能,一般不能被直接调用。如果一个类中至少有一个纯虚函数,则这个类被称为抽象类。抽象类必须用作其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时的多态性。

class A{public:virtual void foo1()//虚函数{cout << "A::foo1() is called" << endl;}virtual void foo2() = 0;//纯虚函数};

注:一个父类写了一个virtual函数,如果子类覆盖它的函数,不加virtual也能实现多态。只要基类在定义成员函数时已经声明了virtual关键字,在派生类实现的时候覆盖该函数时,virtual关键字可加可不加,不影响多态的实现;子类的空间里有父类的所有变量(static除外)。


3、虚析构函数

class Base  {      public:          Base(){}          virtual ~Base(){}  };   class Derived: public Base  {      public:          Derived(){};          ~Derived(){};  }   void foo()  {     Base *pb;     pb = new Derived;     delete pb;  }

        编译器总是根据类型来调用类成员函数,如果删除一个指向派生类对象的基类的指针的时候如果析构函数不声明为virtual,所调用的的函数依赖于指向的静态类型,即基类的类型,此时调用基类的析构函数,会发生资源泄漏;但如果把析构函数声明为virtual,会发生动态绑定,会先调用Derived的析构函数。

注意:构造函数不能为虚函数:虚函数对应一个虚函数表,这个虚函数表是存储在对象的内存空间的。如果构造函数是虚的,就需要通过虚函数表来调用,可是对象还有没有实例化,无法找到虚函数表。

4、构造函数可以是内联函数

   

5、重载和覆盖(重写)  

     (1)重载(overload):

              函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

相同作用域中,方法名相同,参数列表不同

     发生重载的条件

                                  a、方法名相同,参数列表不同

                               b、不能通过访问权限,返回类型,抛出的异常进行重载

                               c、方法的异常类型和数目不会对重载造成影响;

                               d、可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数。”

#include<iostream>     class A{   public:      A();      int foo(int *test);      int foo(const int *test);  };  A::A(){  }   int A::foo(int *test){      std::cout << *test << " A::foo(int *test)" <<std::endl;      return 1;  }   int A::foo(const int *test){      std::cout << *test << " A::foo(const int *test)" <<std::endl;      return 1;  }   int main()  {      const int b =5;      int c = 3;      A a;      a.foo(&b);      a.foo(&c);      return 1;  }  
//输出A::foo(const int *test)  A::foo(int *test)  


                  e、const 成员函数重载的解析和const参数重载解析的原理可以说是一样的。之所以这样说是因为const成员函数的解析可被看做是对函数this参数用const来修饰的过程。例如下面代码:

代码  #include<iostream>    class A{  public:      A();      int foo(int *test); //可看做:int foo(A *this,int *test);      int foo(int *test) const;//可看做:int foo(const A *this,int *test);  };  A::A(){  }  int A::foo(int *test){      std::cout << *test << "foo" <<std::endl;      return 1;  }  int A::foo(int *test) const {      std::cout << *test << "foo const" <<std::endl;      return 1;  }  int main()  {      int b = 5;      const A a;      a.foo(&b);      return 1;  } 


     (2)覆盖/重写(override):覆盖了一个方法,以实现不同的功能,一般用于子类继承父类时,重写(重新实现)父类中的方法

     发生覆盖的条件:a、“三同一不低”子类和父类的方法名称、参数列表、返回类型必须完全相同,而且子类方法的访问修饰符的权限不能比父类低。

                                  b、子类方法不能比父类方法抛出更多的异常。即子类方法所抛出的异常必须和父类方法所抛出的异常一致,或者是其子类,或者什么也不抛出。

                                  c、被覆盖的方法不能是final类型的,因为final修饰的方法是无法覆盖的;

                                  d、被覆盖的方法不能为private,否则在其子类中只是定义了一个新方法,并没有对其进行覆盖。

                                  e、被覆盖的方法不能为static,所以如果父类中的方法为静态的,而子类中的方法不是静态的,但是这两个方法除了这一点外其他都满足覆盖条件,那么会发生编译错误;反之亦然,即使父类和子类中的方法都是静态,并且满足覆盖条件,但是仍然不会覆盖,因为静态方法时在编译的时候把静态方法和类的引用类型进行匹配。

a.父子的foo都是static,可以正常运行 

class A{public:static void foo(){cout << " static A" << endl;}};class B :public A{public:static void foo(){cout << " static A" << endl;}};int main(){A::foo();A a;a.foo();B::foo();B b;b.foo();return 0;}

 

 b、父是static,子不是static,可以运行

class A{public:static void foo(){cout << " static A" << endl;}};class B :public A{public:void foo(){cout << " static B" << endl;}};int main(){A::foo();A a;a.foo();B b;b.foo();//B::foo();return 0;}



 c、private函数不能覆盖,否则只是在其子类中只是定义了一个新方法,并没有对其进行覆盖

class A{private:void foo(){cout << " static A" << endl;}};class B :public A{public:void f(){ foo(); }//报错,不能被继承};int main(){B b;b.f();//B::foo();return 0;}


      区别:a、方法覆盖要求参数列表必须一致,而方法重载要求参数列表必须不一致

                 b、方法覆盖要求返回类型必须一致,而方法重载要没有要求

                 c、方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个作用域中的所有方法(同一个勒种的所有方法,包括从父类中继承而来的方法)

                 d、方法覆盖对方法的访问权限和抛出的异常有特殊要求,而方法重载在这方面没有限制

                 e、父类的一个方法只能被子类覆盖一次,而一个方法可以在所有的类中被重载多次

注:参数列表/参数签名,指三样东西:参数的类型,参数的个数,参数的顺序。这三者只要有一个不同就叫做参数列表不同。

 

6、公有成员、受保护成员、私有成员

      (1)公有成员外部可以访问的成员

      (2)私有成员外部不可以访问的成员

      (3)受保护成员外部不可以访问的成员

             

7、公有继承、受保护继承、私有继承

      (1)公有继承:派生类内部可以访问基类中public和protected成员,但是类外只能通过派生类的对象访问基类的public成员。

      (2)私有继承:基类的公有成员和保护成员作为派生类的私有成员,派生类不能访问

                                 注:若果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象(如下所示),private继承意味implemented-in-terms-of(根据某物实现出)。如果你让class D以private的形式继承自class B,用意是为了采用class B内已经备妥的某些特性。

class Person{...};class Student:public Person{...};void eat(const Person& p);void study(const Student& p);Person p;Student s;eat(p);eat(s);//错误


      (3)受保护继承,基类的公有成员和保护成员作为派生类的保护成员,派生类内部可以访问基类中public和protected成员,并且类外也不能通过派生类的对象访问基类的成员(可以在派生类中添加公有成员函数接口间接访问基类中的public和protected成员)。

  http://blog.csdn.net/lqk1985/article/details/4791293

   

  

  此时c相当于A的private成员

 因为c是A的private成员,C不能访问B的private成员


8、虚成员函数、虚指针、虚成员函数列表、多态

      每一个class只会有一个虚成员函数列表,每一个table内含其对应之class object中所有active virtual functions函数实例的地址。编译器另外还为每个类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表。每个virtual function都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关系。

      例:

class Point  {      public:          virtual ~Point();          virtual Point &mult(float)=0;          //....          float x() const{return x;}          virtual float y() const{return 0;}          virtual float z() const{return 0;}          //...  protected:         Point(float x=0.0);         float _x;  }  //virtual ~Point()被赋值为slot 1//mult()被赋值slot 2(纯虚函数,没有定义,如果该函数意外地被调用,通常的操作是结束掉这个程序)//y()被赋值为slot 3//z()被赋值为slot 4//x()不是virtual function,故不再其中

当一个class继承自point时

class Point2d:public Point  {  public:      Point3d(float x=0.0,float y=0.0):_y(y),_z(z){}      ~Point3d();            //改写base class的虚函数      Point3d& mult(float);      float y()const{return _y;}      //...        protected:      float _z;  };  //1、他可以继承baseclass所声明的virtual function函数实例,该函数实例的地址会被拷贝到derived class的virtual table相对应的slot之中//2、他可以使用自己的函数实例,这表示它自己的函数实例地址必须放在对应的slot之中//3、它可以加入一个新的virtual function,这时候virtual table的尺寸会增大一个slot,而新函数的实例地址会被放进该slot之中



类似,对于Point3D

class Point3d:public Point2d  {  public:      Point3d(float x=0.0,float y=0.0,float z=0.0):Point2d(x,y),_z(z){}      ~Point3d();            //改写base class的虚函数      Point3d& mult(float);      float z()const{return _z;}      //...        protected:      float _z;  };  


Point *ptr;ptr = new Point2d;ptr = new Point3d;ptr->z();  

        每次调用时没有足够的信息在编译时期设定virtual function的调用,所以一般而言每次调用z()时,并不知道ptr所指对象的真正类型, 但知道经由ptr可以存取到该对象的virtual table

       虽然不知道哪一个z()函数是咧会被调用,但知道每一个z()函数地址都被放在slot4中,这些信息使得编译器可以将该调用转化为

*ptr->vtpr[4](ptr)


注:虚函数表示在编译器就建立的,各个虚函数这是被组织成了一个虚拟函数的入口地址的数组,而对象的隐藏成员——虚函数指针是在运行期,即构造函数被调用时进行初始化的。

9、私有构造函数

     阻止一个类被实例化:a、将类定义为抽象基类;b、将构造函数声明为private

     private构造函数何时使用:不允许在类外部构建类对象,只能在类内部创建对象。

10、建立一个只能在栈(静态)/堆(动态)上构建的对象

https://www.nowcoder.com/questionTerminal/0a584aa13f804f3ea72b442a065a7618 

     (1)私有构造函数(不可以)

   (2)私有析构函数(可以)

           但是无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。

           类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问,不符合接口习惯)。




(3)protected构造函数



11、只能建立在栈上

        只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。



四、其他

1、ifndef/define/endif

    C++预编译头文件保护符,保证即使头文件被多次包含,也只定义一次

#ifndef GRAPHICS_H // 防止graphics.h被重复引用 (如果不存在)#define GRAPHICS_H (就引入a.h)#include <math.h> // 引用标准库的头文件 … #include “header.h” // 引用非标准库的头文件 … void Function1(…); // 全局函数声明 … class Box // 类结构声明 { … }; #endif


2、#include<file.h> 与 #include "file.h"

      前者从标准库路径寻找和引用file.h,后者从当前径搜寻并引用file.h


3、指针和引用的区别

      相同点:指针和引用都提供了间接操作对象的功能

      区别:(1)指针定义时可以不初始化,而引用在定义时就要初始化,和一个对象绑定,而且一经绑定,只要引用存在,就会一直保持和该对象的绑定

                 (2)赋值行为的差异:指针赋值是将指针重新指向另外一个对象,而引用赋值则是修改对象本身。

                 (3)指针之间存在类型转换,而引用分const引用和非const引用,非const引用只能和同类型的对象绑定,而const引用可绑定到不同但相关类型的对象或右值

int i = 5;       int j= 2;int *pb;//float &ra = i;//错误,非const引用只能同类型的对象绑定 ra=j;//改变了i的值const float &rb = i;//正确,const引用可以和不同但相关类型的对象绑定float *pa = (float*)&i;//指针之间存在类型转换pb = &i;//指针可以后来初始化pa = (float*)&i;

3、指针和数组的区别

     (1)数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块;

     (2)

char a[] = "hello";//字符串数组  a[0] = 'X';char *p = "world"; // 字符串常量  p[0] = 'X'; // 编译器不能发现该错误,运行时错误  

     (3)用运算符sizeof可以计算出数组的容量(字节数),sizeof(指针)得到的是一个指针变量的字节数,而不是p所指的内存容量。C/C++没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注:当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

4、字符数组与字符串的比较:最明显的区别是字符串会在末尾自动添加空字符。

5、函数指针:

http://www.cnblogs.com/hammerc/p/4024015.html

6、sprintf、strcpy 及 memcpy

(1)strcpy函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能

(2)sprintf函数操作的对象不限于字符串;虽然目的对象是字符串,但是源对象可以是字符串,也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。

(3)memcpy是内存拷贝,实现将一个内存块的内容复制到另一个内存,内存块的长度以及长度确定。其操作对象不局限于某一类数据类型。

int sprintf( char *buffer, const char *format, [ argument] … );//参数列表//buffer:char型指针,指向将要写入的字符串的缓冲区。//format:格式化字符串。//[argument]...:可选参数,可以是任何类型的数据char *strcpy(char* dest, const char *src);//功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间//说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。void *memcpy(void *dest, const void *src, size_t n);//功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,可以重叠
• strcpy 无疑是最合适的选择:效率高且调用方便。
• sprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。
• memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。
对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用 。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。

注意:C语言对数组进行操作时并不检测数组的长度,如果str的长度不够,sprintf()很容易造成缓冲区溢出,带来意想不到的后果,黑客经常利用这个弱点攻击看上去安全的系统。栈破坏,是未定义行为。可能正确,可能错误,可能马上崩溃,可能运行到某个毫无关联的地方修改了数据。所有情况都可能发生。而且编译器基本帮不上忙,只能自己小心。

慎用sprintf,易造成缓冲区溢出



7、操作符重载

     重载操作符是具有特殊名称的函数,具有返回类型和形参列表,必须具有一个类类型的操作数

     (1)操作符<<

       接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,IO操作符必须为非成员函数

ostream& operator(ostream& os, const ClassType& object){    os<<   //...元素    return os;}

(2)操作符>>

istream& operator>>(istream& in, Sales_item& s){    double price;    in >> s.isbn >> s.units_sold >> price;    if(in)        s.revenue = s.units * price;    else        s = Sales_item();    return in;}

(3)算数操作符和关系操作符

         算术操作符通常产生一个新值,该值是两个操作数的计算结果,一般返回一个右值而不是引用

Sales_item operator + (const Sales_item& lhs, const Sales_item& rhs){     Sales_item ret(lhs);     ret += rhs;     return ret;}

(4)复制操作符

         一般而言,赋值操作符与复合赋值操作符应返回左操作数的引用,

string& operator=(const string&);string& operator=(const char*);string& operator=(char);Sales_item& Sale_item::operator+=(const& Sale_item& rhs)//形参一般为const引用{     units_sold += rhs.units_sold;     revenue += rhs.revenue;     return *this;}
Test a,b,c;a=b=c

这里存在连续赋值,这是符合C++的语法规范的。如果赋值操作符返回一个引用类型,倒不是说  b=c 返回的引用变量直接赋值给a,毕竟a不是引用类型。

该过程实际上是 b=c 返回一个引用temp,然后 a=temp 再次调用赋值操作符。这里存在两次调用赋值操作符。

如果赋值操作符不是返回的一个引用那么 在b=c调用复制操作符之后就会再次调用拷贝构造函数返回一个临时对象temp 然后 a=temp 再调用赋值操作符。增加了一次拷贝的代价。


(5)下标操作符

          提供读写访问,可以对const和非const对象使用下标,应用于const对象时,返回值应为const引用,因此不能用作赋值的目标

class Foo{    int& operator[] (const size_t);    const int& opeator[](const size_t);private:    vector<int>data;};int& Foo::operator[](const size_t index){    return data[index];}const int& Foo::operator[](const size_t index){     return data[index];}

(6)成员访问操作符

Class ScreenPtr{public:    Screen& operator*{return *ptr->sp};//解引用    Screen* operator->{return ptr->sp};//箭头操作符    const Screen& operator* cosnt{return *ptr->sp};    const Screen* operator-> const{return ptr->sp};    }


(7)自增和自减操作符

class CheckedPtr{   CheckedPtr operator ++();//前缀   CheckedPtr operator ++(int);//后缀};


8、对齐准则

  在结构体中,成员数据对齐满足以下规则:
        a、结构体中的第一个成员的首地址也即是结构体变量的首地址。
        b、结构体中的每一个成员的首地址相对于结构体的首地址的偏移量(offset)是该成员数据类型大小的整数倍。
        c、结构体的总大小是对齐模数(对齐模数等于#pragma pack(n)所指定的n与结构体中最大数据类型的成员大小的最小值)的整数倍。

http://blog.csdn.net/sdwuyulunbi/article/details/8510401

http://www.cppblog.com/snailcong/archive/2009/03/16/76705.html


9、strcpy返回值

char * strcpy(char * strDest,const char * strSrc){          if ((NULL==strDest) || (NULL==strSrc)) //[1]     throw "Invalid argument(s)"; //[2]     char * strDestCopy = strDest; //[3]     while ((*strDest++=*strSrc++)!='\0'); //[4]     return strDestCopy;}

错误的做法:
[1]
  (A)不检查指针的有效性,说明答题者不注重代码的健壮性。
  (B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。
  (C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[2]
  (A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。
   (B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。
[3]
(A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
[4]
(A)循环写成while (*strDestCopy++=*strSrc++);,同[1](B)。
(B)循环写成while (*strSrc!='\0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'\0'。

⒉返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。
链式表达式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是错误的。其一,源字符串肯定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串,形参用const限定strSrc所指的内容,把const char *作为char *返回,类型不符,编译报错。


10、C++11新特性    

       (1)auto

          (2)decltype

          (3)Lambda匿名函数

                    c++11标准:匿名函数(匿名表达式)lambda

          (4)nullptr

          (5)基于范围的for循环

          (6)override和final

          (7)Strongly-typed enums 强类型枚举

          (8)智能指针

                    为什么用unique_ptr代替auto_ptr

                    智能指针boost::weak_ptr

          (9)右值引用和move语义

auto:从变量声明的初始化表达式获得变量的类型

decltype:而以一个普通表达式作为参数,返回该表达式的类型,而且decltype并不会对表达式进行求值。


11、变量声明在头文件中,定义在C文件中

重复定义发生在链接阶段

#ifndef #define #endif防止的是“重复编译”,而不是“重复定义”
重复编译可能造成重复定义,但重复定义的来源不只有重复编译


从代码变成可执行的程序,需要两个步骤
编译和链接
编译开始时,将所有#include头文件的地方替换成该头文件的代码
在编译阶段,编译所有源文件成为模块,各模块中的每个变量与函数都得到了属于自己的空间
在链接阶段,各个模块被组合到一起


#ifndef能够防止在编译阶段,一段代码被重复编译,并且由此可以避免一个变量被重复定义(多次引入相同的头文件)
但它不能防止链接阶段,各模块中都有叫某个名字的变量,于是报链接错误:变量重复定义

C语言程序编译流程

http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html

12、模板

1.函数模板

template<Typename T>int compare(T &a, T &b){      if(a < b)return -1;      if(b < a)return 1;      return 0;}
2.类模板

template <class Type>class Queue{public:    Queue();    Type &front();    const Type &front();    void push(const Type &);    void pop();    bool empty() const;private;    //....}

typename和class具有相同含义

在模板定义内部指定类型,需要在成员名前加上关键字typename作为前缀

template<class Parm, class U>Parm fcn(Parm* array, U value){     typename Parm::size_type *p;}

模板在使用时进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。

13、traits技术

        traits技术:允许你在编译期间取得某些类型信息。traits技术必须能够实行于内置类型如指针上,因此类型的traits信息必须位于类型自身之外(因为无法将信息嵌套于原始指针之内)。标准技术是把它放进一个template及其一或多个特化版本中。

实现一个traits classes的方法

a. 确认若干你希望将来可取得的类型相关信息。(例如对迭代器而言,我们希望将来可取得其分类)

b. 为该信息选择一个名称(例如iterator_catogory)

c. 提供一个template和一组特化版本,内含你希望支持的类型相关信息。

template<typename IterT>struct iterator_traits;//iterator_traits的运作方式是,针对每一个类型IterT,在struct iterator_traits内一定声明某个typedef名为iterator_category。这个typedef用来确认IterT的分类
   iterator_traits以两个部分实现上述所言,首先他要求每个用户自定义的迭代器类型必须嵌套一个typedef,名为iterator_category,用来确认适当的卷标结构。例如deque迭代器可随机访问,所以一个针对deque迭代器而设计的class看起来回事这样

template<...>class deque{public:    class iterator{          public:               typedef random_access_iterator_tag iterator_category;    };    //....}

list迭代器可双向前进

template<...>class deque{public:    class iterator{          public:               typedef bidirectional_access_iterator_tag iterator_category;    };    //....}

iterator_traits只是响应Iterator_class的嵌套typedef

template<typename IterT>struct iterator_traits{      typedef typename IterT::iterator_category iterator_category;     //...}


为了之处指针迭代器,iterator_traits特别针对指针类型提供了以偏特化版本

template<typename IterT>strcut iterator_traits<iterT*>//针对内置指针{    typedef random_access_iterator_tag iterator_category;    //...}

对于函数


template<typename IterT, typename DistT>void advance(IterT& iter, DistT d){       if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))       //...}   
1、编译问题

2、IterT类型在编译期间获知,所以iterator_traits<IterT>::iterator_category也可以在编译期间确定,但if语句确实在运行期间才会核定


改进方法:重载

template<typename IterT, typename DistT>void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag){      //...}
template<typename IterT, typename DistT>void doAdvance(IterT& iter, DistT d, std::bdirectional_iterator_tag){      //...}

template<typename IterT, typename DistT>void doAdvance(IterT& iter, DistT d, std::inupt_iterator_tag){      //...}
advance只需调用doAdvance并传递额外的参数

template<typename IterT, typename DistT>void advance(IterT& iter, DistT d)     doAdvance(IterT& iter,                       DistT d,                      typename std::iterator_traits<iTerT>::iterator_category());}


总结:

建立一组重载函数或函数模板,彼此间的差异只在于各自的traits参数,另每个函数实现码与其接受值traits信息相应和

建立一个控制函数或函数模板,它调用上述函数并传递traits class所提供的信息





1 0