C++ 笔记

这篇文章是我的c++基础笔记,有些是直接摘抄,有些来自自己的理解,因此可能会有些偏颇和错误,忘有心人斧正;文章是c++的一些基本知识,作为笔记以后可以看看,高手飘过

1.  std::endl的作用有两个:输出换行符;刷新相关联设备的缓冲区;默认情况下cin会刷新cout,当程序正常结束时,cout也会被刷新;默认情况下cerr不缓冲,clog带缓冲。

2.  输入流cin和输出流cout,当进行输入和输出时,返回值为输入或输出流(cin >> arg, cout >> cout都返回的是cin,cout);当istream和ostream流用于作为条件判断时(while(cin>>arg))测试的是流的状态,如果流是有效的(可以继续读入下一个输入),那么测试条件为真,反之亦然;遇到文件结束符或无效输入则流的状态被标记为无效(输入流可以接受换行符的)。

3.  类类型没有未初始化的变量,没有指定初始值的类类型变量由类定义初始化;建议在使用变量之前对变量进行初始化。

4.  将一个超出取值范围的值赋给一个指定类型对象时,会根据对象是signed还是unsigned的不同;对unsigned类型编译器会对unsigned类型可能取值数目求模,(ex: unsigned char ch;ch = 320;最终ch的值为ch = 320/256,即ch为64;ch = -1则ch=-1/256 =255);若为signed,则根据编译器的指定而定。

5.  为了兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字符('\0');两个相邻的仅有空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值(这一点与C语言很类似)。

6.  C/C++都是静态类型语言,在编译时会作类型检查。

7.  C++支持两种初始化变量的形式:复制初始化(用=初始化)和直接初始化(用()初始化);int ival(1024); int inval=1024;直接初始化语法更灵活且效率更高;这两者在类对象的初始化中有区别。

8.  初始化不是赋值,初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

9.  只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern;ex:extern double pi = 3.1416; 这是一个定义。

10.  C++中const对象默认为文件作用域的变量,如果没有特别说明,它仅能在定义它的文件中使用;要想用于其它文件可以用extern定义,extern const...。

11.  引用&必须在定义的时候进行初始化,并且只能用该引用同类型的对象初始化;int &refVal(错误没有初始化),int &refVal = 10(错误必须用同类型的对象进行初始化);不能将引用绑定到另一对象上(即只能进行一次绑定);const引用是指向const的引用,只能用const对象初始化;引用类型不能用于定义数组。

12.  不能改变枚举成员的值。枚举成员本身就是一个常量表达式,所以也可用于需要常量表达式的任何地方。

13.  用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。

14.  string 类型的输入操作符(>>,指string内型内置的输入操作符):读取并忽略开头所有的空白字符(如空格,换行符,制表符);读取字符直至再次遇到空白字符,读取终止(cin>>str)。

15.  当进行 string 对象和字符串字面值混合连接操作时,+ 操作符的左右操作数必须至少有一个是 string 类型的;ex:string s = "hello" + "world";是非法的。

16.  vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector<int> 和vector<string> 都是数据类型;虽然可以对给定元素个数的 vector 对象预先分配内存,但更有效的方法是先初始化一个空 vector 对象,然后再动态地增加元素;

17.  如果没有指定初始化值,标准库将自动提供一个初始值用于初始化,其提供的初始化值与C中基本相同,如果为类类型则用类的默认构造函数进行初始化;ex:vector<string> strvec(10);若没有默认构造函数则必须提供初始化值。

18.  任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。

19.  在定义数组时,C和C++对待const不同,用const定义的变量并且用整数表达式初始化后在C中不能作为数组的维数,而在C++中可以,ex:const int size = 10; int array[size]在C中是不合法的;对于类类型的数组不管定义在什么位置都调用默认构造函数进行初始化,如果没有默认构造函数则必须提供初始化值,类类型的初始化

20.  均是由构造函数完成的;对于内置类型的数组是否初始化与C语言相同,看定义的位置来决定。

21.  void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型;void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用void* 指针操纵它所指向的对象。

22.  不能保证指向 const 的指针所指对象的值一定不可修改,这一例证是当一个指向const对象的指针实际指向的是非const的对象时;ex:int value=3;const int *p =&value; int *p2=&value; *p2=4;此时cout << *p; 输出为4。

23.  动态分配的数组对象在堆内存中没有名字,只能通过其地址访问堆中的对象;如果数组对象为类类型,则使用类的默认构造函数进行初始化,若为内置类型,则无初始化,亦可以在后面跟一对()要求进行初始化(int *p = new int[10](); 内置类型不进行初始化int*p=new int[10];数组维数可以是任意可以计算的表达式);只能初始化为类型的默认值,不能提供初始化值;const对象动态数组必须进行初始化,若为类类型则此类必须有默认构造函数。

24.  通常,由于 C 风格字符串与字符串字面值具有相同的数据类型(都是以null结尾的字符数组,但是string不同),而且都是以空字符 null 结束,因此可以把 C 风格字符串用在任何可以使用字符串字面值的地方;反之则不行,在针对string类型的初始化和赋值时,C风格的字符串可以直接使用。

25.  如果两个操作数为正,除法(/)和求模(%)操作的结果也是正数(或零);如果两个操作数都是负数,除法操作的结果为正数(或零),而求模操作的结果则为负数(或零);如果只有一个操作数为负数,这两种操作的结果取决于机器;求模结果的符号也取决于机器,而除法操作的值则是负数(或零)。

26.  对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量;制定这个规则是为避免出现代码跳过变量的定义和初始化的情况;如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。

27.  在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参,因为函数的参数采用值传递的方式,形参与实参无关,只是其的一个副本;数组形参可声明为数组的引用,如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身;在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配。

28.  对于 C++ 程序,只能将简单数据类型传递给含有省略符形参(可变形参函数)的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制;如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参;既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次;通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中;如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。

29.  内联函数应该在头文件中定义,这一点不同于其他函数,因为inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。

30.  编译器隐式地将在类内定义的成员函数当作内联函数。

31.  const 对象、指向 const 对象的指针或引用只能用于调用其const 成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的;因为对象的地址被隐式地传递给this指针,只有const成员函数(即在成员函数声明和定义的形参列表后加const,这个const改变this的类型,保证this指向对象的内容不被修改)才能保证this内容不被修改。

32.  构造函数通常应确保其每个数据成员都完成了初始化;具有类类型的成员皆被其默认构造函数自动初始化;如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数;利用自动生成的默认构造函数初始化时根据类变量的定义位置进行初始化,如果类(变量)对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则类中的内置数据成员将被初始化为 0;如果对象在局部作用域中定义,则这些成员没有初始化(无论怎样类类型的类成员都将被初始化);因此,合成的默认构造函数一般适用于仅包含类类型成员的类。

33.  任何程序都仅有一个 main 函数的实例;main 函数不能重载。

34.  如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的;函数不能仅仅基于不同的返回类型而实现重载

35.  形参与 const 形参是等价的当且仅当形参是非引用形参, const 引用(指针)形参的函数与非 const 引用(指针)形参的函数是不同的。

36.  在函数(代码块)中局部声明的名字将屏蔽在全局作用域内声明的同名名字,这里的名字将函数名和变量名视为同一的,即在局部声明的变量名和全局函数名同名,则在此局部作用域内此函数将会被屏蔽,导致不能调用此函数;这种一般的作用域规则同样适用于重载的函数;如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数;编译器在遇到函数调用时立即向上寻找相关的函数声明以进行类型匹配检查,当在一个作用域内找到与调用同名的函数时就开始检查形参类型等是否匹配,而不会。

37.  重载函数中类型匹配时类型转换等级以降序排列如下:1. 精确匹配,实参与形参类型相同;2. 通过类型提升实现的匹配;3. 通过标准转换实现的匹配;4. 通过类类型转换实现的匹配。

38.  C++ 语言允许使用函数指针指向重载的函数;指针的类型必须与重载函数的一个版本精确匹配,如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误。

39.  IO 类型在三个独立的头文件中定义:iostream 定义读写控制窗口的类型,fstream 定义读写已命名文件的类型,而 sstream 所定义的类型则用于读写存储在内存中的 string 对象;在 fstream 和 sstream 里定义的每种类型都是从iostream 头文件中定义的相关类型派生而来;用 iostream 类型在同一个流上实现输入和输出操作,标准库还定义了另外两个继承 iostream 的类型,这些类型可用于读写文件或 string 对象;无论是设备的类型还是字符的大小,都不影响需要执行的 IO操作(不管我们是从控制窗口、磁盘文件或内存中的字符串读入数据,都可使用 >> 操作符,这种差异的实现是通过继承的方式解决的);每个类都加上“w”前缀,即得到用相应的类读取宽字符的IO库。

40.  标准库类型不允许做复制或赋值操作;只有支持复制的元素类型可以存储在 vector 或其他容器类型里。由于流对象不能复制,因此不能存储在 vector(或其他)容器中(即不存在存储流对象的 vector 或其他容器);形参或返回类型也不能为流类型,如果需要传递或返回 IO对象,则必须传递或返回指向该对象的指针或引用;一般情况下,如果要传递 IO 对象以便对它进行读写,可用非 const 引用的方式传递这个流对象,对 IO 对象的读写会改变它的状态,因此引用必须是非const 的。

41.  关闭流并不能改变流对象的内部状态,如果最后的读写操作失败了,对象的状态将保持为错误模式,直到执行 clear 操作重新恢复流的状态为止;如果打算重用已存在的流对象,那么 在下一次使用前记得关闭(close)和清空(clear)文件流。

42.  iostream 标准库支持内存中的输入/输出,只要将流与存储在程序内存中的string 对象捆绑起来即可;此时,可使用 iostream 输入和输出操作符读写这个 string 对象。

43.  泛型算法中,所谓“泛型(generic)”指的是两个方面:这些算法可作用于各种不同的容器类型,而这些容器又可以容纳多种不同类型的元素。

44.  指针就是迭代器,因此允许通过使用内置数组中的一对指针初始化容器。

45.  容器元素类型必须满足以下两个约束:1元素类型必须支持赋值运算;2元素类型的对象必须可以复制(最低要求,特殊容器还有其它要求);引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器;除输入输出(IO)标准库类型和auto_ptr 类型之外,所有其他标准库类型都是有效的容器元素类型;特别地,容器本身也满足上述要求,因此,可以定义元素本身就是容器类型的容器。

46.  capacity操作获取在容器需要分配更多的存储空间之前能够存储的元素总数,而 reserve操作则告诉 vector 容器应该预留多少个元素的存储空间。

47.  在类内部,声明成员函数是必需的,而定义成员函数则是可选的;在类内部定义的函数默认为 inline;除了定义数据和函数成员之外,类还可以定义自己的局部类型名字(typedef),将其放在public部分则类的使用者就可以使用这个别名类型;类中成员函数的重载只能针对成员函数本身,不能对外部成员函数实现重载。

48.  可以在类定义体内部指定一个成员为inline,作为其声明的一部分;也可以在类定义外部的函数定义上指定 inline;在声明和定义处指定 inline都是合法的(但是仅能在一处指定);像其他 inline 一样,inline 成员函数的定义必须在调用该函数的每个源文件中是可见的;不在类定义体内定义的 inline成员函数,其定义通常应放在有类定义的同一头文件中。

49.  可以只声明一个类而不定义它(class test),这个称为前向声明,它是一个不完全的类型只能进行有限的操作,不能定义它的类型对象,只能定义指向该类型的指针和引用以及用它作为形参和返回类型;在使用指针和引用前该类必须被定义;因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明;因此,类的数据成员可以是指向自身类型的指针或引用。

50.  一般来说定义类类型时不进行存储分配,当定义类对象时将为其分配存储空间;每个对象具有自己的类数据成员的副本。

51.  没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化;因为,在初始化列表中对成员进行的是初始化,在构造函数体中进行的是赋值,const类型的变量和引用只能进行初始化,不能进行赋值,在开始执行构造函数的函数体之前,要完成对它们的初始化;成员被初始化的顺序是其定义顺序而不是在初始化列表中的顺序;为所有形参提供默认实参的构造函数也定义了(被认为)默认构造函数。

52.  隐式类类型的转换定义了从其它类型到类类型的(隐式)转换,可以用单个实参来调用的构造函数(即含有一个形参的构造函数)定义了从形参类型到该类类型的一个隐式转换;即在需要该类对象的地方可以通过提供一个构造函数中形参类型的对象(变量)自动转换(构造)一个该类类型的对象;但是这种隐式的转换往往具有一定的危险性,很可能最后的结果与用户的意图在逻辑上有差异,因此最好还是在需要的地方显示的调用构造函数生成所需的对象,若果要禁止构造函数进行隐式的转换(构造)可以在构造函数前用explicit声明。

52.  为了正确地构造类,需要注意友元声明与友元定义之间的互相依赖,必须先声明(或定义,如果要用到其它类的私有成员则定义要推迟)要声明为其它类的友元类的那个类,然后再定义这个接收这个类的成员函数为其友元的其它类,最后定义作为友元的函数或类的具体实现;更一般地讲,必须先定义包含成员函数的类,才能将成员函数设为友元;另一方面,不必预先声明类和非成员函数来将它们设为友元;友元声明将已命名的类或非成员函数引入到外围作用域中;此外,友元函数可以在类的内部定义,该函数的作用域扩展到包围该类定义的作用域;因此,用友元引入的类名和函数(定义或声明),可以像预先声明的一样使用

(class X

{friend class Y;friend void f() { /* ok to define friend function in the class
body */ }
};
class Z {
Y *ymem; // ok: declaration for class Y introduced by friend in
X
void g() { return ::f(); } // ok: declaration of f introduced
by X
};)。

53.  static 数据成员(不是方法)必须在类定义体的外部定义(正好一次);static 成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化;保证对象正好定义一次的最好办法,就是将 static 数据成员的定义放在包含类非内联成员函数定义的文件中(ex:class test{static int test1;}; int test::test1 = 1;(用于初始化(外部定义)时可以调用类中的私有成员函数和成员))。

54.  一般而言,类的 static 成员,像普通数据成员一样,不能在类的定义体中初始化(必须用构造函数进行初始化);相反,static 数据成员通常在定义时才初始化;这个规则的一个例外是,只要初始化式是一个常量表达式,整型 const static 数据成员就可以在类的定义体中进行初始化;const static 数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义;在外部进行定义时不必再指定初始值(与static成员在外部进行定义一样)。

55. static 数据成员的类型可以是该成员所属的类类型,非 static 成员被限定仅能声明为其自身类对象的指针或引用;static 数据成员可用作默认实参而非static数据成员不能。

56.  为了防止复制,类必须显式声明其复制构造函数为 private,这样用户代码就不能进行复制,然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。

57.  动态分配的对象只有在指向该对象的指针被删除时才撤销;如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放;当对象的引用或指针超出作用域时,不会运行析构函数;只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数;撤销一个容器(容器变量超出作用域)(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数;析构函数通常用于释放在构造函数或在对象生命期内获取的资源(如动态分配的资源);析构函数并不仅限于用来释放资源,一般而言,析构函数可以执行任意操作,该操作是类设计者希望在该类对象的使用完毕之后执行的。

58.  通常需要复制构造函数那么也需要赋值操作符的重载;如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则,这个规则常称为三法则,指的是如果需要析构函数,则需要所有这三个复制控制成员,但是基类的析构函数是一个另外,基类总是需要虚析构函数但是其它的复制控制函数则不一定;与其它两者不同的是编译器总会为我们合成析构函数,合成析构函数按对象创建时的逆序撤销每个非 static 成员,因此,它按成员在类中声明次序的逆序撤销成员;合成析构函数并不删除指针成员所指向的对象(而它对内置类型和复合类型则进行清理时不需要做什么特别的工作);析构函数调用时首先调用用户定义的析构函数,然后调用系统合成的析构函数。

59.  重载操作符必须具有至少一个类类型或枚举类型的操作数;重载操作符时使用默认实参是非法的;重载操作符并不保证操作数的求值顺序(因此一般不要重载|| &&和逗号操作符);大多数重载操作符可以定义为普通非成员函数或类的成员函数,作为类成员的重载函数,其形参看起来比操作数数目少 1,作为成员函数的操作符有一个隐含的 this 形参,限定为第一个操作数,因此,重载一元操作符如果作为成员函数就没有(显式)形参,如果作为非成员函数就有一个形参;一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员函数;操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元;IO 操作符必须为非成员函数。

60.  重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象;如果返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员;如果返回类型是类类型的其他对象(或是这种对象的引用),则将递归应用该操作符,编译器检查返回对象所属类型是否具有成员箭头,如果有,就应用那个操作符。

61.  动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数;在 C++ 中,多态性仅用于通过继承而相关联的类型的引用或指针;在 C++ 中,通过基类的引用(或指针)调用虚函数时,发生动态绑定;static 成员函数都不可以是虚函数(虚函数是为动态绑定相关的,而动态绑定牵涉到具体的对象)。

62.  派生类只能通过派生类对象访问其基类的 protected 成员,派生类对其基类类型对象的 protected 成员没有特殊访问权限,即是说派生类(像派生类的成员函数中)中不能通过基类对象访问基类中protected成员。

63.  派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数,派生类中的虚函数还可以返回基类函数所返回类型的派生类的引用(或指针)。

64.  如果需要覆盖虚函数机制并强制函数调用时使用虚函数的特定版本,可以使用作用域操作符指定特定版本的函数调用;像其他任何函数一样,虚函数也可以有默认实参,默认实参的值将在编译时确定,如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关(即通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值)。

65.  类继承机制中如果要改变个别基类成员的访问限制,可以用using声明,这样可以使声明后的基类成员在派生类中的访问权限与基类中一样,而不受继承访问控制的限制。

66.  友元关系不能继承;基类的友元对派生类的成员没有特殊访问权限;如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

67.  如果基类定义 static 成员,则整个继承层次中只有一个这样的成员,无论从基类派生出多少个派生类,每个 static 成员只有一个实例。

68.  类中定义的访问控制和继承使用的访问控制是不相同的,例如在基类中定义private成员属于基类的私有成员,无论使用何种继承在派生类中都无法直接(像派生类自身成员那样)访问,当使用private继承时,基类中的成员变成了派生类中的私有成员,这对于用户代码的访问来说是按照成员访问控制来进行限制的(即无法在用户代码中直接访问这些成员),对于派生类的定义(如派生类中成员函数的定义)对这些成员和成员函数(从基类中继承的成员)可以直接使用(这跟普通的规则是一致的),但是需要注意的是,如果派生类的对象作为派生类成员函数的形参时,形参变量的访问权限与继承时的访问控制无关,只与基类中成员定义时申明的访问控制有关(即在基类中的protected和public成员在派生类成员函数的定义中访问时不受继承控制的影响,无论是使用派生类对象还是基类对象均可以访问)。ex:class A{public: int test;protected:int test2} class B :private A{void display(A&a,B&b){a.test; b.test; b.test2; 前面正确 a.test2;错误}};总的来说:类成员可以直接访问其私有成员以及公有和受保护成员。

69.  将对象传给希望接受引用的函数时(一般都是派生类对象传递给基类引用形参),引用直接绑定到该对象,虽然看起来在传递对象,实际上实参是该对象的引用,对象本身未被复制,并且,转换不会在任何方面改变派生类型对象,该对象仍是派生类型对象(派生类对象转换为基类引用);将派生类对象传给希望接受基类类型对象(而不是引用)的函数时派生类对象的基类部分被复制到基类对象形参(相当于用派生类对象进行初始化和赋值),一般来说这是通过基类的复制构造函数和赋值操作符实现的。

70.  构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员;一个类(通过构造函数)只能初始化自己的直接基类。

71.  如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分,否则在复制基类部分时将调用默认构造函数初始化对象的基类部分;赋值操作符重载也是如此,但是重载赋值操作符时必须防止自身赋值;编译器总是显式调用派生类对象基类部分的析构函数,每个析构函数只负责清楚自己类定义的成员,清除顺序与构造顺序相反。

72.  对象、引用或指针的静态类型决定了对象能够完成的行为,即这几者决定了我们能用这些类型的变量调用的函数和成员变量,如果静态类型为基类的只能调用基类中定义的成员函数和变量,即使它的实际是一个派生类对象也是如此。

73.  派生类中同名的变量和函数将屏蔽基类中同名的变量和函数,当变量的类型,函数的原型(返回类型、参数列表)不同时也会屏蔽;派生类中定义的函数不重载基类中定义的成员;在用对象、引用和指针进行函数调用时都是根据这些的静态类型决定调用的函数,以及对这些调用函数的查找,查找是从静态类型的类中开始,向基类进行查找,找到后进行类型检查看调用是否合法,最后再根据是否为虚函数确定是否在运行时确定调用哪个版本。

74.  模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参,每个模板类型形参前面必须带上关键字 class 或 typename,每个非类型形参前面必须带上类型名字;函数模板可以声明inline函数;同一模板的声明和定义中,模板形参的名字(typename后面跟的名字)不必相同,这与函数的声明和定义一致;非类型形参用已知的类型指定,在调用函数时非类型形参将用值代替,它在模板定义的内部被当做常量对待(即可以作为数组维数等)。

75.  编写泛型代码的两个重要原则:模板的形参是 const 引用(允许使用不允许复制的类型);函数体中的测试只用 < 比较。

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

77.  模版调用的实参和形参格式必须严格匹配,即使是能进行隐式转换的调用也将被视为错误(与普通重载函数调用不同);一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例,但是有两个例外:const 转换:接受 const 引用或 const 指针的函数可以分别用非 const对象的引用或指针来调用,无须产生新的实例化;数组或函数到指针的转换。

78.  当使用类模板的名字的时候,必须指定模板形参,这一规则有个例外,在类本身的作用域内部,可以使用类模板的非限定名;但是在类中使用其它模板时必须指定模板的形参。

79.  类模板中可以出现三种友元声明:普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数;类模板或函数模板的友元声明,授予对友元所有实例的访问权;只授予对类模板或函数模板的特定实例的访问权的友元声明。

80.  异常是通过抛出对象而引发的。该对象的类型决定应该激活哪个处理代码,被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个;异常可以是可传给非引用形参的任意类型的对象,即抛出的异常必须是可复制的;在处理异常的时候,抛出异常的块中的局部内存会被释放;抛出对象的静态编译时类型将决定异常对象的类型;如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源,若为类对象则会调用相应的析构函数进行资源释放;析构函数中一般不应抛出新的异常,否则将导致程序退出。

81.  在编译的时候,编译器不能也不会试图验证异常说明。

82.  未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件;未命名的命名空间可以在给定文件中不连续,每个文件有自己的未命名的命名空间;未命名的命名空间用于声明局部于文件的实体,在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在;未命名的命名空间中定义的名字可直接使用,未命名的命名空间中定义的名字只在包含该命名空间的文件中可见;未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到(未命名空间中定义的名字作用域超出其本身,扩展至其外部空间中),如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同;如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体;未命名空间代替static声明(c++中使用未命名空间)。

83.  名字查找总是以两个步骤发生:编译器查找匹配的声明;编译器检查声明是否合法;在多重继承中两个继承的函数只要是同名(在多个基类中定义且同名),在调用时没有指名调用属于哪一个基类中继承的版本则出现调用二义性,即使在基类中同名时形参列表不同,访问控制属性不同(公有、私有)也会导致调用二义性。

84.  在虚派生中,由最低层派生类的构造函数初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式,这些初始化式只有创建中间类型的对象时使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值