转自:http://blog.chinaunix.net/uid-20196318-id-2420689.html
以前学C++时记的笔记,因最近开发用C++较多,把笔记翻出来复习了一下,跟大家分享一下。
- class、struct、union保留字都可以用来声明和定义类。class中成员默认为private类型,struct、union与C语言兼容,成员默认为public类型。
- 只有当类没有显式的定义构造/析构函数时,C++才会提供默认的构造/析构函数;默认的构造函数只负责创建对象,不做任何初始化工作。
- 程序正常退出时,析构函数会被隐式调用;非正常退出(如abort)则析构函数不会被调用,可能导致系统资源没有及时得到回收。构造函数只能被隐式调用,析构函数可以被显式调用。
- 对象在退出其作用域时自动析构,其对象的析构顺序与其创建的顺序相反;常量对象在创建之后立即析构。
1. private说明的对象成员是完全私有的,即便是派生类的后代对象也不能访问这类成员。
2. protected说明的对象成员是私有的,受保护的,该对象派生的后代可以访问这类成员。
3. public说明的对象成员是完全公开的,任何对象都能访问这类成员。
4. 无论什么类型的的对象成员,都可以被友元函数访问。
5. 类的访问权限只能防止无意识的越权访问,通过强制类型转换,可对类的成员无限制的进行访问。
1. 不管是否出现inline保留字,在类体内定义的任何函数成员都会自动成为内联函数。
2. 在类题外定义内联函数,必须用inline显式的加以说明。
3. 内联函数的定义必须出现在内联函数第一次调用之前,否则以普通形式调用(内联失败)。
1. 对于简单类型以及没有定义构造和析构函数的类,malloc/free及new/delete两种内存分配方式可以混用;若类定义了构造函数和析构函数,则最好使用new和delete来分配和释放内存。
2. 在用new为数组分配空间时,数组的第一维下标可以是动态的,其它维则要求是静态的,即必须为整型常量或常量表达式;如果数组元素的类型为类,且希望用new创建对象数组,则相应的类必须定义无参数的构造函数,如果没有定义任何构造函数则使用C++的无参数构造函数。
3. 在全局空间重载new、delete运算符,则全局空间中的使用new、delete将是重载版本。
4. 在类空间重载new、delete运算符,则对于该类的new、delete将使用类的重载版本。
5. delete不需要测试指针是否为0,因为在delete的实现中已经考虑到了,没有必要进行重复的测试。
1. 类的普通成员函数比静态成员函数多了一个隐含参数this指针,隐含this指针是普通成员函数的第一个参数,该参数的类型为指向此类对象的const指针。
2. this可以用来区别与该函数成员参数同名的数据成员。
3. this可以用来访问调用该函数成员的对象、对象的地址和对象的引用。
4. 在非const成员函数中,const的声明如:C * const this;
在const成员函数中,const的声明如:const C * const this;
- 若类定义了构造函数,则其对象必须用类定义的构造函数进行初始化;当类含有只读和引用类型的非静态数据成员时,类必须为这些成员定义构造函数。
- 假定数据成员类A包含B类的非静态数据成员,如果B类定义了带参数的构造函数,则A类必须定义自己的构造函数,不能使用C++缺省的构造函数。
- 构造函数必须初始化对象的对象成员,只读成员和引用成用,且只能在构造函数的函数体前初始化一次;其他数据成员可以在构造函数的函数体前初始化,也可以在构造函数的函数体内再次初始化。
- 数据成员按其在类中定义的顺序初始化,而与他们出现在构造函数体前的顺序无关。如果简单的类型数据成员没有出现在构造函数的函数体前,则他们的值将被缺省的初始化为0。
- 在定义的时候使用A a = A()时,与A a()一样,只调用构造函数,不调用operator=。
1. namespace保留字用于定义名字空间。名字空间必须在程序的全局作用域内定义,不能在函数内定义,最外层名字空间的名称必须在程序的全局作用域内唯一,名字空间可分多次定义。没有名称的名字空间称为匿名名字空间,每个程序只能有一个匿名名字空间。
2. using保留字用于声明程序要引用的名字空间成员,或者用于指示程序要引用的名字空间。如using A::x,则将x引入到了代码所在的作用域,不能再重复定义x;而如果使用using namespace A,则仍可定义x,但须按照A::X才能访问到A中的x。
3. 名字空间可以定义别名,以代替过长和难懂的名字空间名称,如namespace ABCD=A::B::C::D,则以后直接可以用ABCD来访问多重名字空间。
const、volatile、mutable
1. 使用const声明不可变的数据成员,函数成员的参数和返回值等,包含const成员的类必须定义构造函数。
2. volatile修饰的变量表示其可能被并发访问(修改),该保留字告诉编译器不要对变量的访问做任何访问优化,即不利用寄存器存放中间计算结果,直接访问对象以便获得对象的最新值。
3. 普通函数成员的参数表后可以出现const或volatile,其修饰的是函数成员隐含参数this指针指向的对象。
4. mutable修饰的数据成员是易变的,mutable成员总可以被更新,即使在const类型的成员函数中。
1. 引用时变量的别名,可以通过别名直接访问被引用的变量;而指针变量的值是变量的地址,指针是通过地址间接访问变量的值。
2. 引用必须被初始化,且初始化之后不能再做其他变量的引用(别名),引用参数则在函数调用时进行初始化。
3. 引用可用于做函数的参数或返回值,避免了传递参数时大量数据的拷贝。
4. 在目标代码中,引用是不存在的,需要使用引用的地方,已经尤其引用的对象替代了。
1. 引用变量是对引用变量的别名,对被引用的变量必须进行构造和析构,而引用变量本身没有必要构造和析构。
2. 普通引用变量必须用左值初始化,如果用右值表达式进行初始化,就会生成一个临时的匿名变量,引用参数也一样;如A &q = new A(3),则在使用完q后必须delete &q,以释放临时匿名变量的空间。
3. 非引用类型的形参是作用域限于函数的局部变量,形参对象的析构是在函数调用返回前完成的,至于形参对象的构造则是在调用时由值参数传递的,值参数传递将实参各数据成员的值相应地赋给形参的数据成员,对于指针类型的数据成员则只复制指针的值,而没有复制指针所指向的存储单元,即进行浅拷贝。如果类中含有指针成员,则进行浅拷贝可能导致内存错误,必须定义拷贝构造函数,在函数调用时使用拷贝构造函数进行深拷贝。
1. 静态数据成员用于描述类的总体信息,必须在类的体外进行定义并初始化。
2. 静态数据成员脱离具体对象独立存在,其存储单元不是任何对象存储空间的一部分,但逻辑上所哟对象都共享这一存储单元,在计算对象或类的存储空间时不能包含静态数据成员。
3. 静态数据成员描述类的总体信息,由于全局类作用与所有程序文件,故全局类的静态数据成员也必须作用于所有程序文件,即定义的时候是int P::q = 0,而不是static intP::q = 0,但在类中声明时要加上static。
4. union的数据成员必须共享存储空间,而静态数据成员各自独立分配存储单元,故静态数据成员不能成为union的成员。
5. 静态数据/函数成员可通过三种方式访问:
(1) A::member (2) a.A::member (3) a.member
1. 普通成员函数的第一个参数为隐含this指针,而静态函数成员没有隐含的this参数。
2. 构造函数、析构函数、虚函数等有this指针,若函数成员的参数表后出现const、volatile,则该函数成员的参数表必包含隐含的this指针,这些函数不能定义为静态函数成员。
3. union不能定义静态数据成员,但可以定义静态函数成员。
- 尽量用const 和inline 而不用#define
- 尽量用而不用
- 尽量用new 和delete 而不用malloc 和free
1. 一个对象以值传递的方式传入函数体
2. 一个对象以值传递的方式从函数返回
3. 一个对象需要通过另外一个对象进行初始化
函数重载与默认参数
1. C是弱类型的语言,在调用函数时,实参的类型和数目可以同函数原型不一致;而C++是强类型的语言,调用时实参的个数和类型都必须同函数原型一致。
2. 函数的参数个数或者类型不同,则同名的函数被视为重载函数。
3. 不能同时在函数的原型声明和函数定义中定义缺省参数(声明或定义任一位置指定默认参数即可),在调用函数是,非缺省的参数必须传递实参,所有缺省的参数必须出现在非缺省参数的右部。
4. 编译时通过匹配实参和形参来确定要调用的重载函数,当调用重载函数时,若找不到匹配的函数,或是找到多个匹配的函数(有默认参数的情形),编译程序均将报错。
1. 重载不改变运算符的优先级和结合性,一般情况下,重载也不改变运算符操作数的个数(++/--需要区分前置还是后置运算符,->改变了操作数的个数)。
2. 除sizeof . * :: 和三目运算符?:外所有的运算符都可以重载;运算符= -> () []只能重载为类的普通函数成员,不能重载为静态函数成员或普通函数。
3. 运算符重载是面向单个类对象的,而不是面向简单类型或常量的。如果将运算符重载为普通函数,就必须至少定义一个引用类或者类的参数,且参数不能是对象的指针或者是对象数组类型。
4. 如果重载左值运算符(如+=、=等),则重载后运算符最好返回引用类型,重载为普通函数的运算符通常为了方便数据访问,将其设为类的友元。
5. 为了区分单目的前置运算符与后置运算符(++、--),在重载为后置运算符时,加一个额外的int参数作为标识。
6. 运算符重载可用于强制类型转换,如对某个类实现operator int() const {};(const表示强制类型转换不改变当前对象的值),则当类向int转换时,该函数会被调用。
静态绑定与动态绑定
1. 静态绑定:编译时绑定,通过对象调用;动态绑定:运行时绑定,通过地址实现。
2. 静态多态性:函数多态性——函数重载
模板多态性——C++模板(类模板、函数模板)
3. 动态多态性:虚函数(只有用地址才能实现动态多态性)
4. 只有采用“指针->函数()”或“引用变量.函数()”的方式调用C++类中的虚函数才会执行动态绑定。对于C++中的非虚函数,因为其不具备动态绑定的特征,所以不管采用什么样的方式调用,都不会执行动态绑定。
http://dev.firnow.com/course/3_program/c++/cppxl/2008313/104493.html
5. 执行动态绑定的只有通过地址,即只有通过指针或引用变量才能实现,而且还必须是虚函数。从概念上来说,虚函数机制只有在应用于地址时才有效,因为地址在编译阶段提供的类型信息不完全。
模板与继承1. 当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类,如一个支持泛型的堆栈类。
2. 当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类,如一个拥有不同特性的猫类。
1. 重载函数时一种静态多态函数,其通过静态绑定完成;虚函数是一种动态多态函数,其通过动态绑定完成。动态绑定的效率非常高,仅比静态绑定多了一次指针访问。
2. 虚函数以virtual标示,在基类中定义虚函数,派生类原型相同的函数将自动成为虚函数,不管进行多少级派生,虚函数的特性将一直保持下去。
3. 虚函数具有隐含的this指针,故虚函数不能定义成静态成员函数。构造函数拥有隐含的this指针,但构造函数的对象类型必须是确定的,不需要表现出多态性,故构造函数不能定义为虚函数。
4. 对于基类和派生类的虚函数,其原型必须完全相同,但其访问权限可以不同。
5. 虚函数可以定义为inline,也可以重载,缺省和省略参数。
1. 通过保留字template声明模板,声明中模板的参数表必须用尖括号括起来,每个参数必须在函数参数表中至少出现一次,参数表中的参数可以在实现中并没有使用。在套用函数模板生成模板函数时,函数模板参数表的每个参数都用类型替换。
2. 对于只有一个参数的函数模板,系统能根据参数表自动推到,即调用时可以省略模板特化参数。
3. 模板可以将声明和定义分开,模板中的函数可以定义缺省参数,也可以定义为内联函数。
4. 编译程序根据函数模板生成的函数实例成为模板函数。
5. 根据模板可以显式的生成模板函数,如可在调用时生成并调用模板函数。
6. 可以用特定的类型函数实现覆盖函数模板。
1. 异常是一种意外破坏程序正常处理流程的事件,程序不能通过检测变量的值侦察异常(这种方式称为断言)。
2. 异常既可以被硬件引发,又可以被软件引发。由硬件引发的异常通常由中断服务程序产生,如算术运算溢出和除数为0所产生的异常;由软件引发的异常通常由throw语句产生,操作系统和应用程序都可能引发异常。
3. 当程序引发一个异常时,在引发点建立一个描述异常的对象并抛出(throw)异常对象,之后控制被转移(catch)到该异常的处理过程,在引发点创建的对象用于初始化异常处理过程的参数。
4. 位于try中的语句可以引发多种类型的异常,在try之后可以定义多种类型的异常处理过程。在定义多个类型的异常处理过程之后,程序之多执行其中一个异常处理过程。在相应的异常处理过程执行完后,紧随其后的异常处理过程将会被忽略,程序将继续执行这些异常处理过程之后的语句。
5. 异常处理过程执行完后,还可以执行没有操作数的throw语句,继续传播该类型的异常,没有操作数的throw只能在catch中执行(有操作数的throw只能在try中发出),无论有无操作数,throw之后的所有语句都会被忽略,知道遇到参数类型匹配的异常处理过程。
6. 异常处理过程必须定义一个参数,该参数必须是省略参数(以…标示,能匹配任意异常)或是类型确定的参数。
7. 异常处理按catch块的顺序匹配,基类异常对象能匹配派生类对象,省略参数的catch块能匹配任意异常;故通常将派生类的catch块放到基类之前,省略参数的catch块放到最后。
8. 异常接口声明的异常有该函数引发,而其自身又不想捕获和处理的异常,异常接口定义的异常出现在函数参数表的后面,用throw列出要引发的异常类型。如:
void anyexception(); // 可引发任何异常
void noexception() throw(); // 不引发任何异常
void noexpected() throw(A,B); // 引发异常A、B
声明异常接口时,函数声明与定义的异常声明接口要一致,在函数声明时throw(A),则在函数定义时也许指定throw(A)。
C++标准库
1. 标准化过程中,C++生成新头文件的方法仅仅是将现有C++头文件名中的 .h 去掉,方法本身不重要,正如最后产生的结果不一致也并不重要一样。所以变成了,变成了,等等。对于C 头文件,采用同样的方法,但在每个名字前还要添加一个c。所以C 的变成了,变成了,等等。最后一点是,旧的C++头文件是官方所反对使用的(即,明确列出不再支持),但旧的C 头文件则没有(以保持对C 的兼容性)。实际上,编译器制造商不会停止对客户现有软件提供支持,所以可以预计,旧的C++头文件在未来几年内还是会被支持。
2. 旧的C++头文件名如将会继续被支持,尽管它们不在官方标准中。这些头文件的内容不在名字空间std 中。
3. 新的C++头文件如包含的基本功能和对应的旧头文件相同,但头文件的内容在名字空间std 中。(在标准化的过程中,库中有些部分的细节被修改了,所以旧头文件和新头文件中的实体不一定完全对应。)
4. 标准C 头文件如继续被支持。头文件的内容不在std 中。
5. 具有C 库功能的新C++头文件具有如这样的名字。它们提供的内容和相应的旧C 头文件相同,只是内容在std 中。
派生权限控制
| 私有继承private | 保护继承protected | 公有继承public |
private | 不可访问 | 不可访问 | 不可访问 |
protected | private | protected | protected |
public | private | protected | public |
1. 公有继承被称为“类型继承”,派生类是基类的子类型。
2. 私有继承被称为“实现继承”,派生类不直接支持基类的公有接口,相反,当其提供自己的接口时,它希望重用基类的实现(组合也能达到这个目的,视情况而定)。
3. 保护继承主要用于多层次的继承。
4. 常见的多继承模式为:继承一个类的公有接口和另一个类的实现。
派生类的构造顺序1. 调用虚基类的构造函数,无论虚基类出现在继承层次上的哪个位置,它们都先于非虚基类被构造。
2. 调用基类的构造函数,按继承列表中出现的顺序。
3. 按照数据成员的声明顺序,依次调用数据成员的构造函数或初始化数据成员。
4. 执行派生类构造函数的函数体。
5. 析构顺序与构造过程相反。
1. 通过一个隐式转换,从“派生类的指针或引用”转换到“其共有基类类型的指针或引用”。
2. 通过虚函数机制。
3. 通过dynamic_cast,typeid实现从基类指针向派生类指针的转换。