C++ primer学习笔记

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

2.对内置变量,函数体外定义的初始化为0,函数体内定义的不进行初始化,类类型定义时必须初始化,要么调用默认构造函数,要么显示初始化。

3.如果声明有初始化式,那么它可被当做是定义,即使声明标记为extern即已初始化的extern声明被当做是定义,只有当extern声明位于函数外部时,才能有初始化式;对于const变量,如果需要在其它文件中访问,则在定义时指定该const变量为extern。C++默认的const变量具有内连接(static),所以其它文件要访问的话需要指定extern,而C的话默认是extern。

4.普通的引用不能绑定到const对象上,必须是const引用,指向const对象的指针也必须具有const特性。

5.引用主要用作形参,定义引用时必须初始化,且不可修改。

6.非const引用只能绑定到与该引用同类型的对象,const引用则可以绑定到不同但相关的类型或绑定到右值。(比如const int &ri=temp;其中temp是double类型)

7.给枚举类型直接赋int类型是错误的,即使该int值和枚举成员相关联

8.class和struct的区别是class默认的标号是private,struct的默认标号是public。

9.头文件一般包含类的定义,extern变量的声明和函数的声明,但有3个例外,头文件可以定义类、值在编译时就已知道的const对象和inline函数,这些实体可以在多个源文件中定义,只要每个源文件中的定义是相同的。比如const变量用常量表达式初始化可以放在头文件中,每个包含该头文件的源文件都有了自己的const变量,看成局部变量。

10.string::size_type与机器无关的类型,为unsigned类型,保证足够大能任意存储string 对象的长度,不要把size_type(size的返回值)赋值给int类型,ptrdiff_t与unsined的区别就是前者是signed类型

11.如果vector为空,begin和end返回的迭代器相同,const_iterator和const interator的区别,前者表示指向的元素的值不可以修改,后者表示迭代器不能修改,即初始化后不可以指向其他值。

12.非const变量和以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数。

13.不能用一个数组初始化另一个数组,同时也不能将一个数组赋值给另一个数组。

14.void*指针只支持有限的操作:与另一个指针比较,不允许使用void*指针操作它所指向的对象。

15.指针和引用的区别:第一,定义引用时必须初始化。第二,对引用赋值是改变引用所关联的值,对指针赋值是改变指针所指向的对象。

16.C++允许指针指向数组或者对象超出末端的地址,但是不能解引用操作。

17.const double *p 与int *const curerr=&a的区别,前者是指无法通过p指针修改p指向的对象的值,但是p可以指向非const对象,后者是指针本身是const类型

18.typedef string *pstring;const pstring cstr;等价于pstring *const cstr;把const放在pstring之后和之前都等价于pstring *const cstr;

19.C风格字符串是以空字符null(’/0’)结束的字符数组,包含头文件<cstring>操作

20.如果字符数组没有结束符,那么使用c风格字符串的标准库函数(如strlen)会产生不可预计的结果。

21.尽可能使用标准库类型string,不使用c风格字符串。

22.动态分配的数组必须显式释放,同时只能初始化元素类型的默认值,无法用初始化列表。const对象的动态数组意义不大, 因为只能默认初始化,且之后无法修改其值。

23.数组不支持直接复制和赋值,vector支持。

24.strcpy(c,s.c_str());应该这么使用c_str而不是c=s.c_str(),防止s的析构导致c中的字符串无效。

25.int ia[3][4];int (*ip)[4]=ia;声明了ip是指向含有4个元素的数组的指针。也可以使用typedef来简化,typedef int int_array[4];int_array *ip=ia;

26.位操作符那块不理解,P134

27.只有必要时才使用后置自增操作符,因为后置自增要先保存原先的值,代价大。

28.如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同,对于类类型对象,用该类的默认构造函数初始化,而内置类型的对象则无初始化。如内置类型int *pi=new int;int *pi=new int();前者无初始化,后者初始化为0。对于类类型加不加()都调用默认构造函数。

29.一旦调用delete删除指针,立即将指针置为0,防止滥用。

30.size_t是unsigned类型,与平台无关,确保足以保存存储内存中对象的大小。

31.程序员应该尽量避免使用强制类型转换,同时尽量使用命名的强制转换操作符(如static_cast,dynamic_cast,const_cast,reinterpret_cast)来代替旧式强制类型转换。

32.switch中定义变量为了避免代码跳过变量的定义和初始化情况引入块语句,在该块语句中定义变量。

33.用{}括起来的代码具有局部性,里面声明的变量在外部是不可见的。

34.利用const引用形参避免复制,非const引用形参只能与完全同类型的非const对象关联,所以应该将不需要修改的引用形参定义为const引用,这样可以传入非const对象。

35.void ptrswap(int *&v1,int *&v2),v1只是传递进ptrswap函数的任意指针的别名,这样可以直接通过交换指针来交换两个数,而不需要交换指针指向的值来交换两个数。

36.函数不应该有vector或其他标准库容器类型的形参。调用含有普通的非引用vector形参的函数将赋值vector的每一个元素,一般使用迭代器来传递容器。

37.数组作为形参通常操纵指向元素组中的元素的指针来处理数组,参数int *,int[]和int[10]是等价的,这里的10无任何意义。

38.如果数组形参为数组的引用,那么数组大小成为参数的一部分,比如形参为int(&arr)[10],那么实参必须是大小为10的参数。

39.int *matrix[10]表示指针数组,int(*matrix)[10]表示指向一个十个元素数组的指针。

40.如果传递数组作为参数,则需要确保不越界,一般有以下方法:1、放置标记位,类如C风格字符串 2、传递指向数组第一个和最后一个元素下一位置的指针,类如标准库的实现。3、显示传递数组大小

41.千万不要返回局部对象的引用,也不要返回指向局部对象的指针。

42.默认实参只能用来替换函数调用缺少的尾部实参。

43.函数static局部对象在函数结束时也不会被销毁,多次调用保持其值。

44.内联函数的定义应该放在头文件中,因为允许内联函数重复定义,但某个源文件只出现一次,且不同源文件的定义相同,放入头文件可以确保所用的定义是相同的。

45.const成员函数表示隐含的this形参指针指向的对象不可改变,即不能修改其对象的数据成员。

46.如果没有为一个类显示定义任何构造函数,则自动生成默认构造函数,即合成默认构造函数(一般适用于只包含类类型成员的类),合成默认构造函数不初始化内置类型。

47.函数不能仅仅基于不同的返回类型而实现重载

48.重载时const形参和普通形参的等价性仅适用于非引用和非指针形参,有const引用形参与有非const引用形参的函数是不同的,指针类似。当形参是以副本传递时,不能基于形参是否为const来实现重载。

49.局部地声明一个函数将屏蔽而不是重载在外层作用域中声明的同名函数。局部地声明函数不是一种好选择,函数声明应放在头文件中。

50.函数指针声明形式 bool (*pf)(const string &,const string &)

51.使用typedef简化函数指针定义,typedef bool (*cmpFcn)(const string&,const string&),cmpFcn pf1;pf1即为返回bool,还有两个const string &参数的函数指针。

52.返回指向函数的指针, int(*ff(int))(int*,int);该函数名为ff,带一个int形参,该函数返回int(*)(int*,int),用typedef可以简化,cmpFcn ff(int),cmpFcn如50条中定义。

53.允许将形参定义为函数类型,但函数的返回类型必须是指向函数的指针,不能是函数,typedef int func(int*,int);void f1(func),func为函数类型

54.如果函数指针指向的函数有重载版本,则函数指针必须和重载函数的一个版本精确匹配。

55.IO对象不可复制或赋值,因此不能作为形参或者返回值,可以传递引用或者指针。

56.只有支持复制的元素才可以存储在vector或其他容器中,比如ofstream对象不能复制,因此不能放入容器中。

57.如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除该流的状态,因为读取文件结束或者出现错误文件流就会处于错误状态,接着在此流上做的任何输入运算都会失败。

58.在打开文件时,无论是调用open还是以文件名作为流初始化的一部分,都要指定文件模式,当fstream以out模式打开时文件会被清空,当文件同时以in和out打开时不会被清空,当指定了trunc模式,则无论是否指定in模式都会被清空。

59.对于用ofstream打开的文件,要保存文件中已存在的数据,唯一的方法是显示指定app模式打开。(因为如果不指定,ofstream默认打开的是tranc模式)

60.stringstream一个非常典型的用法见P258,多种数据类型之间实现自动格式转化。

61.容器主要有vector、deque、list,vector适用于随机访问元素,list适用于在容器中间插入或者删除元素,deque适用于在首部和尾部插入删除元素,又需要随机访问元素。

62.容器适配器主要有queue,priority_queue,stack。

63.容器元素类型必须满足以下两个约束:1、元素类型必须支持赋值运算 2、元素类型的对象必须可以复制

64.引用不支持一般意义上的赋值运算,所以引用不能作为容器的类型。

65.如果容器的元素是类类型且需要初始化,那么要么提供默认构造函数,要么显示调用构造函数,例如vector<Foo> temp(10,1),且Foo无默认构造函数,传入1调用int形参的构造函数。

66.vector和deque迭代器支持算术运算以及关系运算,list容器迭代器不支持算术运算及关系运算,它只提供自增自减和相等不等运算。push_front只在list和deque中使用,vector不能使用。可以使用insert在容器的指定位置插入元素。

67.对于在内存中顺序存储的迭代器,如果插入元素或者删除元素会导致后面的迭代器都失效,如果重新分配空间,那会导致前面的迭代器也失效,而采用链表存储的迭代器则不会失效。

68.任何insert和push操作都可能导致迭代器失效,当编写循环将元素插入到vector或deque容器中时,程序必须确保迭代器在每次循环后得到更新。如果存储了end操作返回的迭代器,那么添加或者删除元素后该迭代器失效

69.两个容器进行比较的条件:容器类型相同,容器的元素类型相同。如果两个容器长度和元素都相同,则两个容器相同。

70.容器关系运算是基于其存储的元素,因此如果其元素支持关系元素则两者容器就可以实现对应的关系运算。

71.容器判空用empty成员函数,不要用size函数。resize成员函数调整容器大小,如果当前容器大小大于新的长度,则删除多余元素,反之在后部添加元素,如果不指定元素的值则初始化其默认值或调用默认构造函数。

72.容器的back和front函数可以返回容器的第一个和最后一个元素的引用。List<int>::reference rf=lst.front();在调用这些函数时需要先判断容器是否为空。

73.删除元素的容器成员函数有erase,pop_back,pop_front,其中pop_front不支持vector,erase返回删除元素(段)后面的元素的迭代器。

74.swap交换两个容器的元素,但是迭代器不会失效。c.assign(b,e)重新设置c的元素为b和e标记的范围,会使得左操作数的所有迭代器失效。

75.如果事先能确定vector容器的容量,可以先调用reserve来预留空间,提高效率。

76.如果需要读取输入并在容器的中间插入输入的元素,然后需要随机访问,可以考虑在输入时将元素读入到一个list容器,然后对容器重新排序,使其适合顺序访问,然后将排序后的list复制到一个vector容器中

77.对于容器适配器stack和queue默认是基于deque实现,priority_queue是基于vector实现。在定义容器适配器的时候可以指定第二个参数来覆盖关联的基础容器。比如stack< string,vector<string> > str_stk;这样这个stack就基于vector来实现了。stack是可以基于vector,list或者deque容器之上,queue要求push_front,所以不能基于vector,priority_queue(默认使用<来确定优先级,可以指定template参数来指定排序规则)要提供随机访问,所以不能建立在list上。

78.关联容器不提供front,push_front,pop_front,back,push_back,pop_back,assign,resize操作。map容器的元素类型必须定义<操作符,map容器的value_type是pair类型,值成员可以修改,键成员不可以修改。下表操作符返回的类型(mapped_type)和对迭代器解引用返回的类型(pair)不一样。

79.map容器的元素除了满足可以赋值和复制外,map容器中对于键类型唯一的约束就是必须支持<操作符,map迭代器返回的类型是value_type类型,在这里是pair类型。

80.使用下标访问map与使用下标访问数组或vector的行为截然不同,用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键就为该下标值。该map容器记录文件中单次的出现次数是非常方便的。

81.map的m.insert(e)插入元素,如果e.first不在m中,则插入值为e.second的新元素,否则不做改变,函数返回一个pair类型,包含指向键的first元素以及一个bool类型,表示是否插入了元素。m.insert(beg,end),对于beg和end范围内的所有元素,如果它的键不在m中,则插入,最后返回void。

82.map的m.earse(k)删除键为k的元素,返回size_type表示删除元素的个数,必定为0或1,m.erase(p)删除p指向的元素,返回void,m.erase(beg,end)删除指定范围的元素,返回void。

83.在使用迭代器遍历map容器时,迭代器指向的元素按键的升序排序。

84.set容器只是键的集合,且存储的键是唯一的,而且不能修改,不支持下标操作,当只想知道一个值是否存在时,使用set容器是最合适的,set容器查找可以通过find函数一个迭代器。

85.map和set容器一个键只对应一个实例,而multiset和mulimap则允许一个键对应多个实例。multimap和multiset的操作分别与map和set的操作一样,只有一个例外,multimap不支持下标操作,因为对应多个值。

86.multimap在获取多个元素时可以先获取第一个元素的迭代器,使用count函数得知该键出现的次数,通过iter++可以获取其余的元素,因为迭代器指向的元素按键升序排序。

87.multimap和multiset在获取所有元素的另一个方法是可以使用lower_bound(k)和upper_bound(k)方法,如果该键存在,使用这两个方法可以获得两个不同的迭代器,一个指向不小于的第一个实例,一个指向大于k的第一个实例,如果该键不存这两个方法将返回同一个迭代器,指向依据元素的排列顺序该键应该插入的位置。也可使用equal_range方法,返回pair类型对象,first成员等价于m.lower_bound(k),second成员等价于m.upper_bound(k)。

88.泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现,因此与容器的类型无关。

89.泛型算法的第二迭代器一般是超出末端迭代器,本身不是要操作的元素,被用作终止哨兵。

90.find算法要求元素类型定义==操作符,如果元素类型不支持==或者打算用不同的测试方法来比较,那么可使用第二个版本的find,需要一个额外参数来指定函数。

91.map容器使用自己成员函数的find和count,vector容器使用泛型算法的count和find函数。

92.避免在没有元素的容器上调用fill_n或者copy之类的函数,可以使用插入迭代器,比如copy(vec1.begin(),vec1.end(),back_interator(vec2)),这里的vec2是个空容器,如果不用插入迭代器那么无法将vec1中的元素复制过来。

93.降序排列vector可以传入一对反向迭代器。sort(vec.rbegin(),vec.rend())。

94.list容器的迭代器不支持随机访问,所以无法使用sort及相关算法,虽然有merge,remove,reverse,unique的泛型算法可用,但是却付出性能代价,标准库根据list的特点提供了适合list的函数,这些函数通过成员函数的形式提供。

95.在类内部定义的函数默认为inline,const成员不能改变其所操作的对象的数据成员,且const必须同时出现在定义和声明中。

96.数据抽象和封装,数据抽象是一种依赖于接口和实现分离的变成技术。封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。vector是具备抽象和封装性,数组不具备

97.需要声明类的成员函数内inline函数有以下方法:1、类内部定义 2、类的声明里加inline,外部定义的时候不加inline。3、类的声明里不加inline,外部定义为inline。4、声明和定义都加inline。inline函数需要在每个源文件中可见,所以其定义往往放在类定义体里或者类定义的同一头文件中。(在高质量C++中指出:inline只有放在定义体时才真正内联)

98.可以声明一个类但不定义它:class screen;这个声明称为前向声明,往往用于编写相互依赖的类,类screen是不完全类型,不能定义该类的对象,只能定义指针,引用,用于声明使用该类型作为形参类型或返回类型的函数。

99.成员函数中返回对调用该函数的对象的引用往往显示返回*this。在某些操作中,用户希望将这些操作的序列连接成一个单独的表达式,此时操作序列前面的成员函数需返回*this。

100.基于成员函数是否为const可以重载一个函数,即使两者形参是相同的,当一个const对象调用该成员函数时将调用const版本。

101.如果我们希望数据成员在const成员函数内也能被修改,那么可以将数据成员声明为mutable

102.如果返回类型是由类定义的类型,比如类screen中定义了typedef string::size_type index;那么返回类型是index时必须使用完全限定名screen::index。总结如下:对于在外部定义的成员函数,如果出现在函数名后面(形参表和成员函数体)的那些类中变量是无需使用完全限定名,可以直接应用,如果出现在函数名之前(如返回类型),那需要使用完全限定名。

103.全局作用域操作符::,如果类外和类内部定义了名字相同的一个变量,要使用外部那个变量可以加“::变量名”来调用

104.构造函数如果不使用初始化列表,则使用初始化变量相同的规则来进行初始化:类类型的数据成员调用各自的默认构造函数(如果没有默认构造函数,则必须使用初始化列表),内置或复合类型:局部作用域不被初始化,全局作用域被初始化为0

105.没有默认构造函数的类类型成员,const或者引用类型成员,必须在构造函数的初始化列表中初始化,因为const对象和引用类型的对象只能初始化,不能赋值。没有默认构造函数的对象也无法进行初始化,因此初始化的唯一机会就是在构造函数的初始化列表中。

106.为所有形参提供默认实参的构造函数也定义了默认构造函数。如果一个类定义了一个构造函数,编译器就不会自动合成默认构造函数。

107.合成的默认构造函数使用与变量初始化相同的规则来初始化成员,如果一个类中包含内置或者复合类型的成员,则应该定义自己的构造函数来初始化这些成员。

108.类通常应定义一个默认构造函数。如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。

109.可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换,explicit只用于类内部的构造函数声明上,外部的定义不再重复它。该关键字防止在需要隐式转换的上下文中使用构造函数。

110.友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。需要注意友元声明与友元定义之间的相互依赖。

111.用友元函数引入的类名和函数,可以像预先声明一样使用,即作用域扩到了类外。比如在class x中声明了friend class y,则下面的代码可以用y了。

112.类中static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用类的非static成员(因为static成员函数没有this指针)。

113.static成员函数不能被声明为const,也不能被声明为虚函数,因为它不是任何对象的组成部分。

114.static数据成员必须在类定义体的外部定义(正好一次),const static成员可以在类的定义体中初始化(VC++中貌似无法直接初始化),但该数据成员必须在类的定义体之外进行定义,不过不必再指定初始值。//较难理解

115.static成员不是类对象的组成部分,static数据成员的类型可以是该成员所属的类类型,非static成员被限定声明为其自身类对象的指针或引用。static数据成员可以作为默认实参,而非static不能独立于所属的对象而使用,无法作为默认实参。

116.复制构造函数、赋值操作符、析构函数统称为复制控制。

117.复制构造函数:特殊的构造函数,具有单个形参,该形参是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。

118.虽然一般不能复制数组,但合成复制构造函数会复制数组。

119.析构函数:当对象超出作用域范围或动态分配的对象被删除时,将自动应用析构函数。(直接申明的对象,比如object temp;超出作用域后会自动调用析构函数;动态分配的对象,比如object *temp=new object(),则必须使用delete来销毁,因为超出作用域后自动销毁的是指针temp,而指针指向的对象是不会被销毁的)

120.赋值控制是定义任意C++类必不可少的部分,如果我们不定义编译器会默认合成。有一种常见的情况需要显示定义复制控制成员:类具有指针。

121.直接初始化将直接调用与实参匹配的构造函数,string s; 复制初始化指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。      string s=string();

122.如果想要禁止赋值,类必须显示声明其复制构造函数为private。但此时友元和成员仍然可以复制,此时可以声明private复制构造函数但不对其定义(链接时会错误)。

123.一般而言如果类需要复制构造函数,它也会需要赋值操作符(operator=)。

124.如果类需要析构函数,则它也需要赋值操作符和复制构造函数。这规则常称为“三法则”。

125.分配了资源的类一般需要定义析构函数释放那些资源。即使我们编写了析构函数,合成的析构函数仍然运行。

126.编写自己定义的复制构造函数时,必须显示复制需要复制的任意成员。显示定义的复制构造函数不会进行任何自动复制。

127.定义智能指针类(可以引入计数器)管理指针成员用得很频繁,因此使用带指针成员类的程序员必须充分熟悉这些编程技术。处理指针成员的另一个方法是给指针成员提供值语义。

128.重载操作符必须具有一个类类型或枚举类型操作数。用于内置类型的操作符,其含义不能改变。操作符的优先级、结合性或操作数目是不能改变的。重载后不再具有短路求值特性。

129.作为成员函数的操作符有一个隐含的this形参,限定为第一个操作数。因此重载一元操作符如果作为成员函数就没有显示形参,作为非成员函数就有一个形参。

130.操作符定义为非成员函数时,通常必须将他们设置为所操作类的友元。

131.用作关联容器的类应该定义<操作符。即使用于顺序容器,也应该定义==和<操作符,比如sort算法和find算法要用到这些操作符。还有如果定义了==,也应该定义!=。

132.对称的操作符,比如算术操作符,相等操作符,关系操作符和位操作符,最好定义为普通非成员函数。其余大部分适合定义为成员函数。P435

133.IO操作符必须为非成员函数,因为左操作数只能是该类类型的对象,所以如果作为成员函数,那用法和正常使用方式相反。通常将IO操作符设为友元。

134.在定义前缀式和后缀式操作符时存在一个问题,他们的形参数目和类型都相同,因此后缀式操作符函数接受一个额外的int形参(比如i++)。

135.下标操作符[]一般定义const(定义为返回const的const成员函数)和非const版本的,const对象调用返回const引用的版本。

136.定义了调用操作符(“()”)的类(int operator()(int val)),其对象常称为函数对象,即它们是行为类似函数的对象。将函数对象用于标准库算法可以比函数更加灵活。标准库定义了一组算术、关系与逻辑函数对象类。

137.箭头操作符重载必须是成员函数,解引用操作符都可以。//不是太理解

138.标准库定义了一组函数适配器,有如下两类:绑定器和求反器,每个绑定器接受一个函数对象和一个值,绑定器有bind1st和bind2nd,求反器有not1和not2。bind2nd(less_equal<int>,10)和not1(bind2nd(less_equl<int>(),10))。

139.转换操作符是一种特殊的类成员函数(operator type(),不指定返回类型,形参表为空),它定义将类类型值转变为其他类型值的转换,转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。不能指定返回类型,形参表必须为空。

140.在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)即可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。

141.非static函数都可以是虚函数,保留字virtual只在类内部的成员函数声明中出现,不能在类定义体外部出现的函数定义上。

142.protected成员可以被类的派生类访问,但不能被类的用户访问。

143.派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。P475,好好理解下

144.基类中的private成员也是被继承的,只不过在派生类中无法直接访问,只有通过继承基类中的public函数间接访问。一般虚函数中如果要修改某数据成员,则该数据成员设置成protected。

145.用作基类的类必须是已定义的(仅声明不行,但可以不要求实现)。

146.要触发动态绑定需满足两个条件:1、只有指定为虚函数的成员函数才可以进行动态绑定。2、必须通过基类类型的引用或指针进行函数调用。

147.非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。

148.派生类虚函数调用基类版本时,必须显示使用作用域操作符。这种情况往往是基类版本可以完成继承层次中所有类型的公共任务,而每个派生类只添加自己的特殊工作。调用方式举例:baseP->Item_base::net_price(42);

149.尽量不要让基类版本和派生类版本的虚函数使用不同的默认实参。通过基类的引用或指针调用虚函数使用的是基类版本的默认实参,通过派生类的引用或指针调用的是派生类版本的默认实参。

150.公用继承:基类的public成员为派生类的public成员,基类的protected成员为派生类的protected成员。受保护继承:基类的public和protected成员为派生类的protected成员。私有继承:基类的所有成员在派生类中为private成员。

151.public继承被称为接口继承,派生类与基类具有相同的接口,private和protected继承被称为实现继承,派生类在实现中使用被继承类但继承类的部分并未成为其接口一部分。

152.在protected和private继承中,可以使用using Base::size;类似的结构来恢复继承成员的访问级别,但是不能使访问级别比基类中原来制定的更严格或更宽松。

153.class关键字定义的派生类默认具有private继承,用struct定义的具有public继承。class和struct仅有的区别就是默认的成员保护级别和默认的派生保护级别。

154.基类中的友元不会自动继承,需要显示指定。

155.派生类对象—>基类对象(可以赋值或初始化,往往是通过基类的复制构造函数和赋值操作符,此时派生类的基类部分复制给基类对象,但不能转换),当然派生类的引用和指针是可以自动转化为基类的指针和引用的。

156.基类对象—>派生类对象,不存在自动转换,甚至当基类指针或引用实际绑定到派生类对象时,从基类到派生类的转换也存在限制,可以使用static_cast强制转化。或使用dynamic_cast申请运行时进行检查。

157.派生类的默认构造函数除了初始化派生类的数据成员外,它还初始化派生类对象的基类部分,基类部分由基类的默认构造函数初始化。先调用基类的默认构造函数,再初始化派生类的数据成员。

158.派生类应该尊重基类接口,派生类构造函数不应该在函数体中(初始化列表只能初始化派生类自己定义的数据成员)对基类成员赋值(public或protected成员),应该使用基类构造函数尊重基类的初始化意图。

159.如果派生类定义了自己的复制构造函数,该复制构造函数一般应显示使用基类复制构造函数初始化对象的基类部分,Derived(const Derived & d):Base(d){…},如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示赋值Derived &Derived::operator=(const Derived &rhs){if(this!=&rhs){base::operator=(rhs);….}},赋值操作符必须防止自身赋值(因为往往会先把原来的指针成员delete)。派生类析构函数不需要负责撤销基类对象的成员。

160.在复制控制成员中,只有析构函数应定义为虚函数(否则删除基类指针只运行了基类析构函数,而此时如果对象是派生类对象,则派生类将不被析构),构造函数不能定义为虚函数。基类虚构函数是三法则的一个例外,三法则指出:如果类需要析构函数,则类几乎也需要其他赋值控制成员。但是如果类为了将析构函数设为虚函数而具有空析构函数,那么不代表它需要其余复制控制成员。

161.构造函数不应该定义为虚函数是因为构造函数在对象完全构造之前运行,此时对象的动态类型还不完整。

162.赋值操作符设为虚函数会令人混淆,因为如果基类的operator=为虚函数,那么派生类中将得到一个虚函数operator=,但这个成员和该派生类的赋值操作符是不同的,赋值操作符要求形参是自身类类型的引用,而这个成员是基类类型的引用。

163.构造派生类对象时首先运行基类的构造函数初始化对象的基类部分,撤销派生类对象时,首先撤销它的派生类部分然后按照与构造顺序的逆序撤销它的基类部分。

164.在基类构造函数和析构函数中,将派生类对象当做基类类型对待,因此如果在构造函数或析构函数中调用虚函数,则运行的是构造函数或析构函数自身类型定义的版本。

165.在基类和派生类中使用同一名字的成员函数将屏蔽基类成员,即使函数原型不同也会被屏蔽,不会重载。可使用作用域操作符访问被屏蔽的成员。

166.局部作用域中的函数不会重载而是屏蔽全局作用域中定义的函数,同样,派生类中定义的函数也不重载基类中定义的成员。通过派生类调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。

167.如果派生类重定义了重载成员,则通过派生类类型只能访问派生类中重定义的那些成员。有时派生类仅仅需要重定义一个重载集中某些版本的行为,并继承其他版本的含义,那可以using声明指定一个名字,不指定形参,using声明将该函数的所有重载实例加到派生类的作用域。

168.在函数形参表后面写上=0指定为纯虚函数,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用,用户也不能创建该对象。含有一个或多个纯虚函数的类是抽象基类。

169.派生类对象在赋值给基类对象时会被“切掉”,所以容器与通过继承相关的类型不能很好的融合,但是可以使用容器保存对象的指针,只是必须保证容器消失时适当地释放对象。

170.C++中面向对象编程一个颇具讽刺意味的地方是:不能使用对象支持面向对象编程,相反,必须使用指针或引用(base->fun())。如果使用对象调用虚函数(base.fun()),那么执行的函数必然是该对象所对应类的函数,无法实现动态绑定。

171.面向对象编程所依赖的多态性称为运行时多态性,泛型编程所依赖的多态性称为编译时多态性。

172.模板定义以关键字template开始 ,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表。模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参,类型形参跟在关键字class或typename之后。比如template<class T>。

173.在模板定义内部制定类型,通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当做类型,通过编写typename Parm::size_type,指出绑定到Parm的类型的size_type成员是类型的名字。如果不加typename,编译器默认是size_type是Parm的数据成员。

174.模板非类型形参是模板定义内部的常量值,如:template<class T,size_t N> void array_int(T (&parm)[N]),形参是数组的引用。

175.函数模板的显式实参,比如在返回类型中使用类型形参 sum<long>(i,ing),显示指定long为sum的返回类型。<>中按形参表顺序显示指定实参。

176.模板编译模型:模板要进行实例化,编译器必须能够访问定义模板的源代码,而定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。

177.标准C++为编译模板代码提供了两种模型:包含编译模型和分别编译模型。

178.包含编译模型需要编译器看到用到的所有模板的定义,一般可以在声明函数模板或类模板的头文件中添加一条include指示使定义可用,该include引入包含相关定义的源文件。

179.分别编译模型使得编译器会为我们跟踪相关的模板定义,但是我们需要使用export关键字来让编译器知道要记住给定的模板定义,export关键字能够指明给定的定义可能会需要在其他文件中产生实例化。一般在函数模板的定义中指明函数为导出的,声明像通常一样放在头文件中,不需要指明export。

180.在定义Queue模板类时书写格式如下:template <class Type> class Queue{…};类模板成员函数在外部定义是书写格式如下例:template <class Type> void Queue<Type>::destroy(){…}。注意在定义类时不需要在Queue后加模板形参列表,外部定义成员函数时需要加。

181.模板成员函数的模板形参由调用该函数的对象的类型确定,而且模板成员函数运行时支持实参的常规转换。

182.类模板中可以出现三种友元声明:1、普通非模板类或函数的友元声明。2、类模板或函数模板的友元声明,授予对友元所有实例的访问权。3、只授予对类模板或函数模板特定实例的访问权的友元声明。

183.任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员成为成员模板。典型的例子是标准容器的assign成员,可以使用assign成员使我们从不同但兼容的元素类型序列或不同容器类型建立容器。

184.如果类模板含有static成员,则实例化的每一个版本共享一个static,比如Foo<int>共享一个static,Foo<string>共享一个,static成员必须在类外部出现数据成员的定义。

185.模板特化:template<> int compare<const char *>(const char * const &v1, const char * const &v2),形参是const char*的const引用。template<> class Queue<const char*>{…}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值