1、特点:是一种“类”代表的面向对象语言、C++模板支持的泛型编程 2、C++的二重性:OOP提供了高级抽象;C提供了低级硬件访问 3、C++98/C++2003:异常、运行阶段类型识别(RTTI)、模板和标准模板库(STL) 4、C++尽量是C语言的超集 5、C++对程序进行编译和链接:Compile、Build、Make、Build All、Link、Execute、Run和Debug 6、预处理器:处理#开头的编译指令——在源代码被编译之前,替换或添加文本 7、C++头文件:形式上,取消.h结尾(<iostream>),功能上,没有h 的头文件也可以包含名称空间 8、回车:与空格和制表符作用相同,不能放在元素(比如名称)中间。C++11新增的原始(raw)字符串可包含回车 9、标记(token):一行代码中不可分割的元素 10、空白(white space):空格、制表符、回车 11、C++源代码风格: @ 每条语句占一行; @ 每个函数都有一个开始花括号和一个结尾花括号,这两个花括号各占一行; @ 函数中的语句都相对于花括号进行缩进; @ 与函数名称相关的圆括号周围没有空白。 12、定义声明:需要的内存、内存单元的名称,编译器为变量分配内存空间 13、引用声明:声明引用时,必须进行初始化,则原变量拥有两个名称,且不会占用任何的存储单元 14、C++声明变量的形式:首次使用变量前声明。缺点:不能全局地看到变量使用情况 15、C++中class和struct类的区别:class关键词定义的类,成员默认为私有成员,struct关键词定义的类,成员默认为公有成员(且只有数据,没有函数) 16、函数原型:描述函数接口,即发送给函数的信息和返回的信息;首次使用函数之前提供其原型 17、函数定义:函数头+函数体 18、初始化:创建变量时对它进行赋值;变量声明和赋值分开,可能会带来瞬间悬而未决的问题 19、隐式声明: 20、关键字:计算机语言中的词汇,不能用作变量名和函数名,但main不是关键字,且是一个必不可少的函数的名称 21、访问名称空间:放在函数定义之前;放在特定的函数定义中;使用指定的元素(using std::cout);使用前缀std:: 22、内置的C++类型:基本类型(整型和浮点型)和复合类型(数组、字符串、指针和结构) 23、变量命名规则(补充):以两个下划线或者下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。 24、符号常量:CHAR_BIT、(S/U)CHAR_MAX、(U)INT_MAX、(U)SHRT_MAX、(U)LONG_MAX、(U)LLONG_MAX 25、显示整数的方式:cout<<hex(十六进制);cout<<oct(八进制);cout<<dec(十进制) 26、常量的类型:除非有理由存储为其他类型(如使用特殊后缀、值太大),否则C++将整型常量存储为int类型 27、数字常量后缀:2000ULL(大小写均可) 28、wchar_t:是一种整数类型,具体大小取决于“实现”,它有足够的空间,可以表示系统使用的最大扩展字符集。作用是将每个字符存储在一个两字节的内存单元中。 29、char16_t和char32_t:C++11新增类型,无符号16/32位的整型,使用u/U前缀表示 30、bool类型:字面值true和false可以通过提升转换位int类型,true被转换为1,而false被转换为0;任何数字值或指针值都可以被隐式转换为bool值,任何非零值都被转换为true,零被转换为false 31、const int:限定为只读变量即常量,编译器不允许修改该常量的值;务必声明初始化,否则该常量的值不确定,且无法修改 32、浮点数:可表示整数之间的值;运算速度慢,且精度降低 33、列表初始化:不允许缩窄(narrowing),即变量的类型可能无法表示赋给它的值 34、auto声明:编译器把变量的类型设置成与初始值相同 35、数组声明:注意点:存储在每个元素中的值的类型;数组名;元素数量(整型常数、const值、常量表达式) 36、数组的初始化规则:只有在定义数组时才能进行初始化;不能将一个数组赋给另一个数组 37、C++11数组初始化:可省略等号(int cpp [11] {1});可不在大括号内包含任何东西,所有元素置为0;禁止缩窄转换 38、字符串初始化:char 变量名[可让编译器自动计算/记得计算结尾’\0’] = “字符串字面值”,隐式地包括结尾的空字符 39、结构声明:C++允许在声明结构变量时省略关键字struct 40、C++11结构初始化:支持列表初始化(可省略等号);花括号内为空,各个成员都将被置为0;不允许缩窄转换;可创建没有名称的结构类型(省略名称同时紧跟变量) 41、结构中的位字段:C++允许指定占用特定位数的结构成员;字段类型必须为整型或枚举 - 名称 - 冒号 – 数字(使用的位数);使用没有名称的字段来提供间距 42、共用体:能够存储不同的数据类型,但只能同时存储其中的一种类型;共用体的长度为其最大成员的长度 43、共用体使用环境:共用体常用于(但并非只能用于)节省内存 44、枚举赋值:枚举是创建符号常量的一种方式;把非enum值赋给enum变量视为错误 45、new内存:从堆或自由存储区的内存区域分配内存 46、内存耗尽:计算机可能由于没有足够的内存而无法满足new的请求 47、delete内存: @ 释放指针指向的内存,但不会删除指针本身; @ 不能释放已经释放的内存块,但是对空指针(0或nullptr)执行释放是安全的; @ 不能释放声明变量获得的内存 48、数组的静态联编:编译时给数组分配内存 49、数组的动态联编:程序运行时选择数组的长度 50、数组取地址:对数组名应用地址运算符时,得到的是整个数组的地址;+1会使地址增加一个数组的总长度 51、数组(的)指针:short array[20]; short (*p)[20] = &array; 此时指针p的类型为short (*)[20],*p和array等价,(*p)[0]为array数组的第一个元素 52、自动存储:是一个局部变量,作用域为包含它的代码块,通常存储在栈中 53、静态存储:是整个程序执行期间都存在的存储方式;定义静态变量的两种方式:函数外面定义它,或者声明变量时使用关键字static 54、动态存储: 通过new和delete管理一个内存池,数据的生命周期不完全受程序或函数的生存时间控制;相比于栈内存,可能导致占用内存不连续 55、内存泄漏:内存被分配,但无法回收 56、线程存储: 57、类型别名:#define aliasName typeName和typedef typeName aliasName; 注意:#define……不适合一次性声明一系列变量,例如int* a, b; //b为变量 58、函数返回值 类型限制:直接返回不能是数组,可以作为结构或对象组成部分来返回 59、函数原型的功能:编译器正确处理函数返回值;编译器检查使用的参数数目是否正确;编译器检查使用的参数类型是否正确,如果不正确,则转换为正确的类型 60、数组作为函数形参:实际上不是数组,是一个指针;当且仅当用于函数头或函数原型中,int* arr和int arr[]、int []含义才相同;方括号为空表明,可以将任何长度的数组传递给数组; 61、函数形参注意:使用普通参数时,由于C++的按值传递方式,原参数变量的保护将会自动实现,函数使用的是参数的副本;指针、引用或数组则不同,为防止函数无意中修改数组的内存,可以在声明形参时使用关键字const 62、函数和二维数组形参:实际上是一个指针,当且仅当用于函数头或函数原型中,int (*arr)[4]和int arr[][4]含义才相同; 63、函数指针: @ 获取函数地址(函数名,不跟括号和参数); @ 声明函数指针(例如函数原型:double pam(int);)(函数指针:double (*pf)(int);) 64、函数指针调用函数:double ans = (*pf)(5); 或者 double ans = pf(5);都可 65、指向函数指针数组的指针应用:类的虚方法 66、typedef简化:将别名当作标识符进行声明,并在开头使用关键字typedef(7.10.4,p.248) 67、内联函数:程序无需跳转到另一个位置处执行代码,再跳回来。 68、内联函数声明时定义:开头加关键字inline,函数体积过大或递归都不能将其作为inline函数 69、引用变量:必须在声明引用变量时进行初始化,等同于const地址的指针(int * const p = arr;) 70、按引用传递:引用形参能够接收的实参格式限制极为严格 71、左值:变量、数组元素、结构成员、引用和解除引用的指针 72、普通引用:作为函数参数,禁止创建临时变量接收非左值 73、const引用:如果实参与引用参数不匹配,当且仅当引用参数是const引用时,则编译器将在下面两种情况下生成临时变量:(此时等效于按值传递) @ 实参的类型正确,但不是左值; @ 实参的类型不正确,但可以转换为正确的类型。 74、函数形参应尽可能使用const引用: @ 使用const可以避免无意中修改数据的编程错误; @ 使用const使函数能够处理const和非const实参,否则只能接受非const; @ 使用const引用使函数能够正确生成并使用临时变量 75、函数返回值为引用:函数可以作为左值(应用:重载赋值运算符) 76、返回引用注意问题:避免返回临时变量的引用,避免返回指向临时变量的指针 77、const用于引用返回类型:禁止引用类型的函数作为左值 78、基类引用:可以指向派生类对象,而无需进行强制类型转换:定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。 79、何时使用引用参数: 1)对于使用传递的值而不作修改的函数。 @ 如果数据对象很小,如内置数据类型或小型数据结构,则按值传递 @ 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针 @ 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间 @ 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。因此,传递类对象参数的标准方式是按引用传递。 2)对于修改调用函数中数据的函数。 @ 如果数据对象是内置数据类型,则使用指针。 @ 如果数据对象是数组,则只能使用指针。 @ 如果数据对象是结构,则使用引用或者指针。 @ 如果数据对象是类对象,则使用引用。 80、默认参数:通过函数原型中定义,且必须从右向左添加默认值。 81、使用默认参数带来的好处:可以减少要定义的析构函数、方法以及方法重载的数量。 82、函数重载:多个同名的函数,必须保证特征标(参数数量和或参数类型)不同。但是类型引用(int&)和类型本身(int)视为同一种特征标。除此以外,调用最匹配的重载版本 83、函数模板:是通用的函数描述,使用泛型来定义函数,泛型可用具体的类型替换。 84、模板的局限:编写的模板函数可能无法处理某些类型 85、对于函数重载、函数模板和函数模板重载,编译器选择原则: @ 创建函数候选列表; @ 使用候选函数列表创建可行函数列表; @ 确定是否有最佳的可行函数 86、单独编译:不要将函数定义或变量声明放到头文件中 87、头文件常包含的内容: @ 函数原型; @ 使用#define或const定义的符号常量 @ 结构声明 @ 类声明 @ 模板声明 @ 内联函数 88、#include: @ 尖括号<>:C++编译器将在存储标准头文件的主机系统的文件系统中查找; @ 双引号””:首先查找当前的工作目录或源代码目录,如果没有则按上查找 89、头文件管理:#ifndef和#endif 90、作用域和链接 @ 作用域:描述了名称在文件的多大范围内可见 @ 链接性:描述了名称如何在不同单元间共享 91、auto:主要用途是指出当前变量为局部自动变量 92、静态持续变量:外部链接性(全局,可在其它文件中访问)、内部链接性(全局+static,只能在当前文件中访问)和无链接性(局部+static,只能在当前函数或代码块中访问)。编译器分配固定的内存块来存储所有的静态变量。 93、静态变量的初始化:零初始化(编译)——常量表达式初始化(编译)/动态初始化(链接) 94、全局变量: @ 静态持续性、外部链接性 @ 定义声明:给变量分配存储空间,(需要初始化同时使用extern) @ 引用声明:不给变量分配存储空间,引用已有的变量 @ 引用声明使用关键字extern,且不进行初始化 95、作用域解析符:(::)放在变量名前,该运算符表示使用变量的全局版本 96、全局静态变量: @ 静态持续性、内部链接性 @ 只能在其所属的文件中使用 @ 使用static关键字 97、局部静态变量: @ 静态持续性、无链接性 @ 声明变量只能在代码块中使用,但代码块不活跃时仍然存在 @ 如果初始化了静态局部变量,则程序只在启动时进行一次初始化 98、mutable:即使结构(或类)的变量为const,则被mutable修饰的成员也可以被修改 99、const全局变量: @ 链接性为内部的,和static const相同 @ 每个文件都有自己的一组常量 @ 使用extern定义+初始化,链接性为外部 100、函数: @ 静态持续性、默认外部链接性 @ 可使用关键字static将函数的链接性设置为内部的 101、语言链接性:extern “C” void spiff(int); 102、名称空间特性: @ 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中 @ 除了用户定义的名称空间外,还存在一个全局名称空间 @ 任何名称空间中的名称都不会与其它名称空间中的名称发生冲突。 103、using声明和using编译指令: @ using声明使一个名称可用,using编译指令使所有的名称都可用 @ 如果某个名称已经在函数中声明了,则不能使用using声明导入相同的名称,但是using编译指令可以继续使用,仍然遵循局部名称隐藏名称空间名 104、访问控制: @ public:类的公共接口的类成员(抽象) @ private:只能通过公共成员访问的类成员(数据隐藏) @ protected:继承相关 105、内联方法:定义位于类声明中的函数都将自动成为内联函数 106、默认构造函数:如果没有提供任何构造函数,则C++将自动提供默认构造函数。为类定义了构造函数后,也必须为它提供默认构造函数。 107、定义默认构造函数: @ 给已有构造函数的所有参数提供默认值 @ 通过函数重载来定义另一个构造函数——一个没有参数的构造函数 另外,不能同时采用这两种方法 108、析构函数:类对象 过期时,析构函数将自动被调用。默认地编译器隐式地声明了一个析构函数 109、如果既可以通过初始化,也可以通过赋值来设置对象的值,应采用初始化方式。通常这种方式效率更高。 110、const成员函数:void Stock::show() const @ 保证函数不会修改调用对象; @ 只要类方法不修改调用对象,就应将其声明为const。 @ 可以重载仅函数返回值有区别的成员函数 111、ClassName obj = value; @ 接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值 112、作用域为类的常量: @ 匿名枚举,创建符号常量 @ static const关键字 @ const关键字(C++11已支持) 113、类声明中的static常规变量: @ 不能在类声明中初始化,因为类声明中不分配内存 ; @ 类声明之外使用单独的语句来进行初始化,需指出类型、类作用域、不适用关键字static 114、作用域内枚举:enum class egg {small, medium, large, jumbo}; 使用:egg choice = egg::large 115、重载运算符:operator 符号 (一般为该class的对象引用) 116、运算符表示法中,运算符的左侧的对象是调用对象,运算符右侧的对象是作为参数被传递的对象 117、运算符重载限制: @ 重载后的运算符必须至少有一个是用户定义的类型(避免重载标准运算符,比如减号重载为加号); @ 使用运算符时不能违反运算符原来的句法规则。不能修改运算符的优先级; @ 不能创建新运算符; @ 只能通过成员函数进行重载的运算符:=、()、[]、-> 118、友元有三种:友元函数;友元类;友元成员函数 119、友元函数:通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限 120、为何需要友元:为类重载二元运算符时常常需要友元,如果操作符不是对象,比如ClassobjA = 2 * ClassobjB; //不能够调用成员函数 121、上述与这样的非成员函数调用匹配:ClassobjA = operator*(2, classobjB) 该函数的原型为:ClassName operator*(int, const ClassName& ) @ 非成员的重载运算符函数(有两个参数):运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数 122、创建友元: 1)原型放入类声明中,原型声明前加上关键字friend; friend ClassName operator*(int, const ClassName&); 2)在类中声明,与成员函数的访问权限相同,但不是该类的成员函数; 3)编写定义 不允许添加类限定符,不允许添加friend 4)如果操作数据交换操作顺序后,结果不发生改变,则可以 return Classobj * int_data; //此时原函数不必为友元函数,但写作友元非常好 123、转换函数:是一种特殊的C++运算符函数,由用户定义的强制类型转换。 @ 转换函数必须是类方法; @ 转换函数不能指定返回类型; @ 转换函数不能有参数; @ 成员函数声明开头添加explicit规定只能使用显式转换 124、转换函数原型:operator typeName(); 125、静态数据成员在类声明中声明,在包含类方法的文件中初始化。 int className::static_var = 0; 126、类的特殊成员函数: @ 默认构造函数:如果没有定义构造函数; @ 默认析构函数:如果没有定义; @ 复制构造函数:如果没有定义; @ 赋值运算符:如果没有定义; @ 地址运算符:如果没有定义 @ 移动构造函数 @ 移动赋值运算符 127、复制构造函数:使用一个对象来初始化另一个对象,编译器将自动生成复制构造函数(ClassName(const ClassName & ClassName);) 128、复制构造函数调用时机:(一定) @ Person husband(wife) @ Person husband = wife; //可能使用复制构造函数生成一个临时对象 @ Person husband = Person(wife); //可能使用复制构造函数生成一个临时对象 @ Person *husband = new Person(wife); 129、默认复制构造函数执行功能: @ 逐个复制非静态的数据成员(成员复制也称为浅复制) 130、显示定义复制构造函数原因: @ 浅复制会使新对象的指针与老对象的指针指向同一内存,析构出现问题 @ 试图释放两次内存会导致程序异常终止 131、默认赋值运算符: @ ClassName& ClassName::operator=(const& ClassName); 132、赋值运算调用时机: @ Person husband(“Song”); Person wife; wife = husband; //调用赋值运算符 @ 初始化对象时(不一定)使用 Person husband = wife; //可能仅是复制构造函数,也可能两者都存在 133、默认赋值运算符: @ 成员复制为浅复制 134、重载赋值运算符: @ 赋值对象为自身时,直接返回(return *this;) @ 自身可能在堆中保存了旧数据,需先进行释放操作(delete [] var;) @ 函数返回一个指向调用对象的引用(return *this) 135、声明类常量对象引发问题的解决方式: @ const String str_const(“test”); @ char& operator[](const int i) { return str[i]; } //仅能够作用于普通类对象 @ const char& operator[](const int i) const { return str[i]; } //重载一个const &版 136、静态成员函数: @ 函数声明必须包含关键字static(如果函数是独立的,不能声明为static); @ 不能通过对象调用 静态成员函数; @ 静态成员函数不能使用this指针; @ 如果是在公有部分声明,使用类名和作用域解析运算符调用函数; @ 只能使用静态数据成员。 137、构造函数中使用new注意事项: @ 构造函数中使用new来初始化指针,对应析构函数中应该使用delete; @ new和delete必须相互兼容。new对应于delete,new[]对应于delete[]; @ 多个构造函数必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有构造函数都必须与之兼容。当然,可以将构造函数中的指针初始化为空(0或nullptr); @ 应该定义复制构造函数,通过深度复制将一个对象复制给另一个对象; @ 应该定义赋值运算符,通过深度复制将一个对象复制给另一个对象。 138、返回指向const对象的引用:通过返回引用来提高效率 139、返回指向非const对象的引用:重载赋值运算符、重载与cout一起使用的<<运算符 140、返回对象:一般会调用复制构造函数。被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数返回完毕时,局部对象将调用析构函数。通常,被重载算数运算符时属于这一类 141、返回const对象:为避免出现 obj1 + obj2 = obj3; 这样的公式滥用。 142、定位new运算符:定位new对象的delete不能使用与定位new配合使用,delete原内存不会让对象执行析构,需要为对象显示地调用析构函数。 143、类中嵌套结构或类声明:(结构、类、枚举) @ 作用域为整个类,只能在类中使用; @ 可以使用它来声明类成员; @ 可以将它作为类方法中的类型名称。 144、成员列表初始化: @ 初值可以是成员参数中的任意参数; @ 只有构造函数可以使用这种方法; @ 必须使用这种格式来初始化非静态const数据成员;(至少C++11之前) @ 必须使用这种格式来初始化引用数据成员。 145、C++11支持数据成员在类声明时初始化(包括const和引用数据成员) 146、类继承: @ 派生类继承了基类的特征,包括方法; @ 可以在基类的基础上添加功能; @ 可以添加数据; @ 可以修改类方法的行为。 147、公有基类与公有派生: @ 派生类对象包含基类对象; @ 基类的公有成员将成为派生类的公有成员; @ 基类的私有部分也将成为派生类的一部分,但是不能直接访问它,只能通过基类的公有方法和保护方法间接访问。 148、继承类中添加特性: @ 派生类需要自己的构造函数; @ 派生类可以根据需要添加额外的数据成员和成员函数。 149、构造函数必须给新成员(如果有的话)和继承的成员提供数据 150、构造函数:访问权限的考虑 @ 派生类不能直接访问基类的私有成员,必须通过基类的方法进行访问 @ 派生类构造函数必须使用基类构造函数 @ C++使用成员初始化列表语法来完成 151、派生类构造函数要点: 1)首先创建基类对象; 2)派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数; 3)派生类构造函数应初始化派生类新增的数据成员 152、派生类和基类之间的特殊关系: @ 若基类方法不是私有成员,派生对象可以使用基类的方法; @ 基类(指针/引用)可以在不进行显式类型转换的情况下(指向/引用)派生类对象,基类指针或引用只能用于调用基类方法(如果是虚方法,根据指向对象类型决定调用哪个虚方法); @ 不可以将基类对象和地址赋给派生类引用和指针; @ 可以使用派生类对象初始化基类对象(调用隐式复制构造函数) @ 可以使用派生类对象赋值给基类对象(调用隐式重载赋值运算符) 153、公有继承不能建立的关系: @ 不能建立has-a的关系:午餐有水果,水果Fruit不能派生午餐Lunch; @ 不能建立is-like-a的关系:律师像鲨鱼,鲨鱼Shark不能派生律师Lawyer; @ 不建立is-implemented-as-a的关系:不能从数组Array派生栈Stack; @ 不建立uses-a 的关系:不能从计算机Computer派生出打印机Printer; 154、多态公有继承的两个重要机制: @ 在派生类中重新定义基类的方法;(基类指针/引用指向对象将使用基类的方法) @ 使用虚方法。(基类指针/引用指向对象使用方法取决于指向对象的类型是基类还是派生类) 155、虚方法: @ 方法在基类中被声明为虚方法后,在派生类中将自动成为虚方法,但仍可以在派生类声明中显式地使用virtual关键字指出。 @ 基类指针或引用的对象调用哪个虚方法,取决于指向的是基类对象还是派生类对象;(如果方法不是虚方法,基类指针/引用始终指向/引用基类方法) 156、虚析构函数:确保释放派生类对象时,按正确的顺序调用析构函数。 157、派生类实现中的注意要点: @ 派生类中重新定义基类方法,通常应将基类方法声明为虚的; @ 调用基类中的重写方法时,需添加作用域解析符; 158、联编:将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编 @ 静态联编:编译过程中进行联编;编译器对非虚方法使用静态联编 @ 动态联编:编译器必须生成能够在程序运行时选择正确的虚方法的代码;编译器对虚方法使用动态联编 159、向上强制转换:将派生类引用或指针转换为基类引用或指针;且具有传递性,“孙子”对象仍然可以指向或引用;隐式转换。 160、向下强制转换:将基类指针或引用转换为派生类指针或引用,必须使用显式类型转换 161、为什么默认为静态联编: @ 动态联编进行动态决策时,必须采取一些方法来跟踪基类指针或引用指向的对象类型,增加了额外的处理开销;而静态联编效率更高,设计为确实需要虚函数时,才使用它们较为合理 @ 设计类时,不需要进行重定义的方法,不将其设计为虚方法。效率高,指出了不要重定义的函数 162、虚函数工作原理: 1)给每个对象添加一个隐藏成员,保存了一个指向虚函数地址数组的指针,这样的数组成为虚函数表(virtual function table,vtbl); 2)派生类中重新定义过的虚函数,则更新vtbl中的地址;派生类中新添加的虚函数,则在vtbl中添加地址; 3)调用虚函数时,程序将查看存储在对象中的vtbl地址。 163、虚函数注意事项: @ 构造函数不能是虚函数; @ 析构函数应当是虚函数,除非类不用做基类。基类的析构函数需要显式定义为虚析构函数; @ 友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数; @ 如果派生类没有重新定义的函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外情况是基类版本是隐藏的; @ 如果在派生中直接进行基类虚方法的重载,则派生类对象无法再使用该基类方法(隐藏了同名的基类方法) @ 如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变,因为允许返回类型随类类型的变化而变化;(只适应于返回值,而不适用于参数) @ 如果基类声明被重载了,则应在派生中重新定义所有的基类版本。 164、protected访问控制: @ 派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员; @ 对于外部世界来说,保护成员的行为与私有成员相似;对于派生类来说,保护成员的行为与公有成员相似。 165、纯虚函数:提供未实现的函数;声明纯虚函数时,结尾处加上=0 166、抽象基类:(abstract base class,ABC) @ 至少包含一个纯虚函数; @ 当类声明中包含纯虚函数时,则不能创建该类的对象; @ C++允许纯虚函数有定义。 167、ABC理念: @ 指出编程问题所需的类以及它们之间的关系; @ ABC要求具体派生类覆盖其纯虚函数——迫使派生类遵循ABC设置的接口规则。 168、继承和动态内存分配: @ 派生类不使用new:派生类不需要定义显式析构函数、复制构造函数(默认复制构造函数使用基类的复制构造函数来复制派生类对象的公有部分)和赋值运算符(和复制构造函数类似); @ 派生类使用new:需要显式析构函数(对派生类new内存进行删除)、复制构造函数(对于初始化列表,使用派生类对象调用基类复制构造函数)和赋值运算符(定义内显式调用基类的赋值运算符,baseClass::operator=(hi)); @ 使用动态内存分配和友元的继承:若需要在派生类使用基类的友元,则需要将派生类对象的引用强制转换(例如(const base&) devired_obj)为基类引用 169、什么不能被继承: @ 构造函数不能被继承:创建派生类对象时,必须调用派生类的构造函数; @ 析构函数不能被继承:释放对象时,程序将首先调用派生类的析构函数; @ 赋值运算符是不能被继承的; @ 友元函数不能被继承:因为友元函数不是类成员。 170、包含对象成员的类(has-a):即创建一个包含其他类对象的类 171、C++和约束:使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据 172、对于继承对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。对于成员对象,构造函数则使用成员变量名。 173、初始化顺序:初始化列表包含多个项目时,被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。 174、私有继承:基类的公有成员和保护成员都将成为派生类的私有成员。 @ 使用关键字private(实际上,private是默认值,省略访问限定符也将导致私有继承) 175、私有继承与“包含”的区别与相同: @ 区别:包含将对象作为一个命名的成员对象添加到类中;私有继承将对象作为一个未被命名的继承对象添加到类中。 @ 相同:两者提供的特性相同,都是获得实现,但不获得接口。 176、私有继承的初始化基类组件: @ 隐式地继承组件而不是成员对象; @ 构造函数使用成员初始化列表语法,它使用类名而不是成员名来标识; 177、私有继承的访问基类的方法: @ 使用私有继承时将使用类名和作用域解析运算符来调用方法 178、私有继承的访问基类对象: @ 强制类型转换,指针this 179、私有继承的访问基类的友元函数: @ 可以通过显式地转换为基类来调用正确的函数; @ 在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。 180、保护继承: @ 基类的公有成员和保护成员都将成为派生类的保护成员; @ 和私有继承一样,基类的接口在派生类中也是可用的,在继承层次结构之外是不可用的; @ 从派生类派生新类时,若使用私有继承,则第三代类将不能使用基类的接口(基类的公有方法在派生类中将变为私有方法),若使用保护继承,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。 181、使用using重新定义访问权限: @ 使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。若让基类的方法在派生类外面可用,方法一:定义一个使用该基类方法的派生类方法。 @ 方法二:派生类public部分使用using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。(例如:using std::valarray<int>::min;) @ using声明只适用于继承,而不适用于包含。 182、多重继承: @ 有多个直接基类的类; @ 必须使用关键字public来限定每个基类。否则编译器将认为是私有派生; @ 存在两个主要问题: 1)从两个不同的基类继承同名方法; 2)从两个或更多相关基类那里继承同一个类的多个实例。 例如:通常可以将派生类对象的地址赋给基类指针,但现在会出现二义性(可以使用强制类型转换来指定对象) 183、虚基类: @ 虚基类是从多个类(它们的基类相同)派生出的对象只继承一个基类对象。( class Singer : virtual public BaseWorker {…}; class Waiter : public virtual BaseWorker{…}; class SingingWaiter : public Singer, public Waiter {…}; 虚继承并非作为绝对的MI准则,某些情况下,也可能需要基类的多个拷贝;基类作为虚的,要求程序完成额外的计算。 @ 新的构造函数规则: 假如SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other): Waiter(wk, p), Singer(wk, v) {} 自动传递信息时,将通过2条不同的路径(Waiter和Singer)将wk传递给Worker对象。为避免这种冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。因此,上述构造函数将初始化成员panache和voice,但wk参数中的信息将不会传递给子对象Waiter。然而,编译器必须在构造派生对象之前构造基类对象组件;在上述情况下,编译器将使用Worker的默认构造函数。 如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数: SingingWaiter(const Worker & wk, int p =0, int v= Singer::other): Worker(wk), Waiter(wk, p), Singer(wk, v) {} 对于虚基类,必须这样做;但对于非虚基类,则是非法的。 @ 祖先们同一名字的方法该如何使用: 1)可以使用作用域解析运算符来澄清编程者的意图: SingingWaiter newhire(“Elise Hawks”, 2005, 6, soprano); newhire.Singer::Show(); 2)继承类中重新定义Show(): void SingingWaiter::Show(){ Singer::Show(); } //无效,这种定义方式忽略了Waiter组件,需要加入Waiter::Show() 采用模块化方式,Woker只提供显示Work成员的Woker::DataShow() Waiter和Singer同理,SingingWaiter提供两者一起Show的DataShow() Show()负责组合上述DataShow(); 184、虚基类和支配:派生类中的名称优先于直接或间接祖先类中的相同名称。 185、虚二义性规则与访问规则无关,导致二义性的是相同名称。 186、使用虚基类的目的:如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。通常情况下,多个基类实例都是问题。 187、类模板: @ 不能将模板成员函数放在独立的实现文件中。因为模板不是函数,它们不能单独编译; @ 基本类型、结构和类是成立的,数组不成立; 188、模板适用范围:容器类。主要动机是为容器类提供可重用代码 189、template <class T, int n>: @ T为类型参数,n为非类型或表达式参数 @ 表达式参数有一些限制。可以是整型、枚举、引用或指针。 @ 模板代码不能修改参数的值,也不能使用参数的地址。(n++或&n都是不允许的) 190、表达式参数的优缺点: @ 优点:声明数组时,可以作为数组大小。构造函数方法使用的是通过new和delete管理的堆内存,表达式参数使用的是为自动变量维护的内存栈。 @ 缺点:每种数组大小都将生成自己的模板(生成两个独立的类声明:) array<int, 10> eggs; array<int, 20> odds; 191、模板的多功能性:可用作基类、组件类、其他模板的类型参数等。 192、递归使用模板 193、使用多个类型参数:template<class T1, classT2> 194、默认类型模板参数:template<class T1, classT2 = int> 195、可以为类模板类型参数提供默认值,不能为函数模板参数提供默认值。都可以为非类型参数提供默认值。 196、模板具体化: @ 隐式实例化: @ 显式实例化:使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化(template class Array<string, 100>;) @ 显式具体化:特定类型(用于替换模板中的泛型)的定义 template <> class Classname<specialized-type-name> {…}; @ 部分具体化:部分限制模板的通用性。 template <class T1, classT2> class Pair{…}; // template <class T1 > class Pair<T1, int>{…}; //部分具体化 template <> class Pair<int, int>{…}; //退变为显式具体化(编译器使用具体化程度最高的模板) 也可以通过指针提供特殊版本来部分具体化现有的模板: template<class T> class Base{…}; template<class T*> class Base{…}; 197、成员模板:模板可用作结构、类或模板类的成员。用于实现STL 198、模板用作参数:模板还可以包含本身就是模板的参数。用于实现STL template <template <typename T> class C> class ClassName{ C<T> c1; 199、模板类和友元(3类): @ 非模板友元; @ 约束模板友元,即友元的类型取决于类被实例化的类型; @ 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。 200、模板类的非模板友元函数: friend void counts (); //是模板类所有具体化后的友元(一个) friend void report ( Base<T> & ); //模板类有多少具体化,对应的有多少友元 201、模板类的约束模板友元函数: 1)在类定义的前面声明每个模板函数 template < typename T > void fun1 (); template < typename T > void fun2 (T &); 2)模板类中声明为友元函数: template <typename TT> class Base{ friend void fun1<TT> (); friend void fun2<> (Base<TT> &); // friend void fun2<Base<TT>> (<Base<TT> &); fun2的<>可以为空,因为可以从函数参数推断出注释的内容 3)为友元提供模板定义 202、模板类的非约束模板友元函数: 通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。 template <typename T> class Base{ template <typename C, typename D> friend void fun1 (C&, D&); 203、模板别名(C++11): @ 使用typedef为模板具体化指定别名: typedef std::array<double, 12> arrd; arrd arr; @ 使用模板+using定义别名: template <typename T> using arrtype = std::array<T, 12>; arrtype<int> days; @ C++11允许将语法using= 用于非模板。与常规typedef等价: typedef const char* pc; using pc = const char*; 204、友元类: @ 声明:friend class ClassName; // ClassName can access private of this class @ 友元类的声明可以位于公有、私有或保护部分,所在位置无关紧要。 205、友元成员函数:(往往需要前向声明 class ClasName;) @ 选择让特定的类成员成为另一个类的友元 @ 声明:friend void Brother::give(Myself & m, string fruit); 206、共同的友元:(往往需要前向声明 class ClasName;) 在两个类中声明同名的友函数。类外实现定义 207、嵌套类: 208、异常: @ 调用abort();(默认情况下) @ 返回错误码; @ 异常机制:引发异常;使用处理程序捕获异常;使用try块。 throw:表示引发异常,紧随其后的值(字符串或对象)指出了异常的特征。 try{}:标识其中特定的异常可能被激活的代码块。 catch(){}:可能有多个,表示捕获异常。括号中的类型声明,指出了异常处理程序要响应的异常类型;花括号的代码块,指出要采取的措施。 catch(…){}:不清楚引发异常的类型,使用省略号来表示异常类型 @ 将对象用作异常类型 209、异常规范(C++11已摈弃) 210、栈解退:throw语句处理try块和throw之间整个函数调用序列放在栈中的对象。 211、exception类 212、RTTI:运行阶段类型识别。旨在为程序在运行阶段确定对象的类型提供一种标准方法。 213、RTTI用途:判断基类指针指向的对象是基类对象还是派生类对象。 214、RTTI的工作原理: @ C++有3个支持RTTI的元素: 1)如果可能的话,dynamic_cast运算符使用一个指向基类的指针来生成一个指向派生类的指针;否则,该运算符返回0——空指针。 2)typeid运算符返回一个指出对象的类型的值。 3)type_info结构存储了有关特定类型的信息。 @ 只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针。 @ RTTI只适用于包含虚函数的类 215、dynamic_cast运算符: @ 函数原型:dynamic_cast<Type *>(pt) @ 如果指向的对象(*pt)的类型为Type或从Type直接或间接派生而来的类型,则上面的表达式将指针pt转换为Type类型的指针。 216、typeid运算符: @ 原型:typeid() // 接收类名或者结果为对象的表达式 @ 能够确定两个对象是否为同种类型(通过==或!=)。 @ typeid运算符返回一个对type_info对象的引用 217、类型转换运算符: 选择合适的运算符,而不是使用通用的类型转换。 218、const_cast运算符: @ 用途;将指针、引用安全地转换为同一基础类型的const限定或者常规。