您的位置:首页 > 编程语言 > C语言/C++

C++ primer

2014-04-22 15:01 92 查看
1.C++有两个地方空格不能由换行符代替,第一个字符串字面值,第二就是空格符不能出现在预处理指示中。

2.C++是静态类型,即在编译时进行检查。

3.char有3中类型,普通,unsigned,signed.

4.C++所有字符串字面值都由编译器在后面自动加上一个空字符。两个相邻仅由空格,制表符,或换行符分开的字符串字面值可以连接成一个新字符串字面值。

1.1024fwrong

2.3.14ULwrong

3.左值就是变量的地址,或者是一个代表“对象在内存中的位置”的表达式。右值就是变量的值。

4.char_这个声明是正确的

5.初始化不是赋值,初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。比如intival=1024;//copy-initialization,

intival;ival=1024;//赋值

6.只能定义一次,可以多次声明

6.直接初始化语法更灵活且效率更高

7.有多个初始化式时,不能使用复制初始化。

8.intmonth=09,day=07,firtwrong,secondright.

9.intival=ival,语法正确,但是仍然未初始化

10.doublesalary=wage=9999.99;//wrong

11.没有默认构造函数的类类型,每个定义必须提供显示的初始化式,没有初始值是根本不能定义这种类型的变量的。

12.externinti;//声明只有当extern声明位于函数外部时,才可以含有初始化式。externconstint&ri;//声明一个const引用

13.常量在定以后就不能修改,所以定义时必须初始化。

14.把一个非const变量定义在一个文件中(全局),在另一个文件可以通过extern使用这个变量。但是对于全局作用域声明的const变量是定义该对象的文件的局部变量,不能被其他文件访问。但是可以通过指定const变量为extern,就可以在整个程序中访问const对象。非const变量默认为extern,而const变量必须显示指定extern。

15.constintbufsize=fcn();//right

16.复合类型指用其他类型定义的类型。

17.不能定义到引用类型的引用。

18.int&refVal=10;//wrong

19.const引用(指向const对象的引用)是指向const(对象)的引用。constintival=1024;constint&refVal=ival;//rightint&ref2=ival;//wrong,非const引用不能绑定到const对象。

20.const引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:

inti=42;
//legalforconstreferencesonly
constint&r=42;
constint&r2=r+i;

同样的初始化对于非const引用却是不合法的,而且会导致编译时错误。其原因非常微妙,值得解释一下。观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。假如我们编写

doubledval=3.14;
constint&ri=dval;

编译器会把这些代码转换成如以下形式的编码:
inttemp=dval;//createtemporaryintfromthedouble
constint&ri=temp;//bindritothattemporary

如果ri不是const,那么可以给ri赋一新值。这样做不会修改dval,而是修改了temp。期望对ri的赋值会修改dval的程序员会发现dval并没有被修改。仅允许const引用绑定到需要临时使用的值完全避免了这个问题,因为const引用是只读的。
总之,非const引用只能绑定到与该引用同类型的对象。const引用则可以绑定到不同但相关的类型的对象或者绑定到右值。
21.常量表达式是编译器在编译时就能够计算出结果的整型表达式。
22.enum....
23.定义变量和定义数据成员有个区别,一般不能把类成员的初始化作为其定义的一部分。类通过构造函数初始化数据成员。
24.public部分可以使程序的任何代码执行这些部分,但是对于private部分,只有类的组成部分代码(还有类的友元)才能访问private。
25.头文件一般包含类的定义、extern变量的声明和函数的声明。头文件可以含有三中定义:类的定义,编译时就已知道的const对象(默认定义时该变量的文件的局部变量),和inline函数。这些实体可以在多个源文件定义,只要每个源文件中的定义时相同的。当我们在头文件中定义了const变量后,每个包含该头文件的源文件都有了自己的const变量,其名称和值都是一样的。当该const变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些
const变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的const变量。如果const变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该const变量应该在一个源文件中定义并初始化。应在头文件中为它添加extern声明,以使其能被多个文件共享。constdoublepi=3.1415926可以放在头文件中,而constdoublepi=sqrt(2.0)不应该。因为不是由常量表达式产生的。
26.头文件中必须总是使用完全限定的标准库名字,一般不适用using。
27.字符串字面值与标准库string类型不是同一种类型。
28.默认构造函数就是不带参数或者为所有形参提供默认实参的构造函数。
29.getline函数返回时丢弃换行符,换行符将不会存储在string对象中。并且不忽略前面的空白符,直到遇到换行符。
30.下标操作返回左值!!!!!!!!!!
31.vector保存内置类型如int类型,将用0值初始化,前提是定义的时候指明了vector大小。
32.STL在循环中一般使用!=而不使用<等,原因在于STL中vector等容器是动态变化的。
33.我们对const_iterator类型解引用时,可以得到一个指向const对象的引用。
34.数组元素不能是引用,可以是其他任意复合类型,类类型,以及内置类型,数组不管在哪里定义,如果元素是类类型,就自动调用该类默认构造函数初始化,如果没有默认构造函数,则必须提供显示初始化。对于内置类型数组,除非显示提供元素初值,否则内置类型局部数组元素没有初始化。
35.非const变量以及要到运行阶段才知道的const变量不能作为数组维数。
36.charca1[]={'C','+','+'};//nonullcharca2[]={'C','+','+','\0'};//explicitnullcharca3[]="C++";//nullterminatoraddedautomatically,大小分别为3,4,4,但是puts(ca1)时,不会正常结束,strlen结果未知,但是sizeof结果是3;
37.constcharca4[3]="ily";//wrong
38.vector<int>ivec={0,1,1,2,3,5,8};错误
39.把int型变量赋给指针是非法的,尽管此int型变量的值可能为0。但允许把数值0或在编译时可获得0值的const量赋给指针。
40.void*指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void*指针或从函数返回void*指针;给另一个void*指针赋值。不允许使用void*指针操纵它所指向的对象。我们将在第5.12.4节讨论如何重新获取存储在void*指针中的地址。
41.constdouble*cptr;//cptrmaypointtoadoublethatisconst
*cptr=42;//error:*cptrmightbeconst
doublei;cptr=&i;//right
constdoublepi=3.14;

double*ptr=π//error:ptrisaplainpointer

constdouble*cptr=π//ok:cptrisapointertoconst
不能使用void*指针保存const对象的地址,而必须使用constvoid*类型的指针保存const对象的地址。允许把非const对象的地址赋给指向const对象的指针,但不允许通过该指针修改其所指对象的值
42.int*constcurErr=&errNumb;//curErrisaconstantpointer。与其他const量一样,const指针的值不能修改,这就意味着不能使curErr指向其他对象。与任何const量一样,const指针也必须在定义时初始化。指针本身是const的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
43.指向const对象的const指针。
constdoublepi=3.14159;

//pi_ptrisconstandpointstoaconstobject

constdouble*constpi_ptr=π、
本例中,既不能修改pi_ptr所指向对象的值,也不允许修改该指针的指向(即pi_ptr中存放的地址值)。
44.在typedef(第2.6节)中使用指针往往会带来意外的结果。下面是一个几乎所有人刚开始时都会答错的问题。假设给出以下语句:

typedefstring*pstring;

constpstringcstr;

请问cstr变量是什么类型?简单的回答是constpstring类型的指针。进一步问:constpstring指针所表示的真实类型是什么?很多人都认为真正的类型是:

conststring*cstr;//wronginterpretationofconstpstringcstr

也就是说,constpstring是一种指针,指向string类型的const对象,但这是错误的。错误的原因在于将typedef当做文本扩展了。声明constpstring时,const修饰的是pstring的类型,这是一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的const指针,这个定义等价于:

//cstrisaconstpointertostring

string*constcstr;//equivalenttoconstpstringcstr。
strings;

typedefstring*pstring;

constpstringcstr1=&s;//writtenthiswaythetypeisobscured

pstringconstcstr2=&s;//allthreedecreationsarethesametype

string*constcstr3=&s;//they'reallconstpointerstostring

把const放在类型pstring之后,然后从右向左阅读该声明语句就会非常清楚地知道cstr2是constpstring类型,即指向string对象的const指针。问题1:typedef后怎么声明一个指向const对象的指针?
45.允许把非const对象的地址赋给指向const对象的指针。不能把一个const对象的地址赋值给一个const指针或者普通指针,只能赋值给一个指向const对象的指针。
46.字符串字面值的类型就是constchar类型的数组。
47.在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化。也可以这样int*pia2=newint[10]();//arrayof10uninitializedints圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为0。对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。如果我们在自由存储区中创建的数组存储了内置类型的
const对象,则必须为这个数组提供初始化:因为数组元素都是const对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化。C++允许定义类类型的const数组,但该类类型必须提供默认构造函数,当然,已创建的常量元素不允许修改——因此这样的数组实际上用处不大。C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组是合法的,长度为0的动态数组不能解引用。
48.c_str返回的数组并不保证一定是有效的,接下来对st2的操作有可能会改变st2的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制c_str函数返回的数组。
49.int*ip[4];//arrayofpointerstoint

int(*ip)[4];//pointertoanarrayof4ints
以下程序用typedef为ia的元素类型定义新的类型名:
typedefintint_array[4];

int_array*ip=ia;
50.一般表达式的结果是右值。
51.如果操作数为负数,则位操作符如何如何处理操作数的符号位依赖于机器。因此建议使用unsigned整型操作数。
52.移位操作的右操作数不可以是负数,而且必须是严格小于左操作数的位数的值,否则操作效果未定义。
53.重载的操作符与该操作符的内置类型版本有相同的优先级和结合性。
54.cout<<10<42;//wrong
55.算术表达式产生右值。下标和解引用以及赋值操作产生左值
56.inti,int*p;i=p=0;//wrong
57.inti=0,j;j=++i;j=i++;i==2,j==1;前置操作返回对象本身,后置操作返回右值
58.vector<int>::iteratoriter=ivec.begin();
while(iter!=ivec.end())
cout<<*iter++<<endl;
小心这段代码。这段代码是正确的。
59.cout<<(i<j)?i:j;//prints1or0!

cout<<i<j?i:j;//error:comparescouttoint

第二个表达式比较有趣:它将i和j的比较结果视为<<操作符的操作数,输出1或0。<<操作符返回cout值,然后将返回结果作为条件操作符的判断条件.
60.sizeof*p中,指针p可以持有一个无效地址,因为不需要对p做解引用操作。
61.C++只规定了&&和||,逗号操作符,以及条件操作符的运算顺序。以下为编译器相关的语句:f1()*f2();if(ia[index++]<ia[index])
62.下面两个指导原则有助于处理复合表达式:

1.如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。

2.如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的。
63.practice5.28
64.ival!=jval<kvalptr!=0&&*ptr++ival++&&ivalvec[ival++]<=vec[ival]
65.C++没有明确定义如何释放指向不是用new分配的内存地址的指针。
66.C++保证:删除0值的指针是安全的。一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。对同一个内存空间使用两次delete表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做delete运算,将该对象的内存空间返还给自由存储区,然后接着delete第二个指针,此时则自由存储区可能会被破坏。
67.vector<string>**pvec2=newvector<string>[10];//wrong
68.最简单的转换为整型提升:对于所有比int小的整型,包括char、signedchar、unsignedchar、short和unsignedshort,如果该类型的所有可能的值都能包容在int内,它们就会被提升为int型,否则,它们将被提升为unsignedint。如果将bool值提升为int,则false转换为0,而true则转换为1。
69.不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或sizeof操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针.C++还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void*类型;整型数值常量0可转换为任意指针类型。
70.当使用非const对象初始化const对象的引用时,系统将非const对象转换为const对象。此外,还可以将非const对象的地址(或非const指针)转换为指向相关const类型的指针(problem?)
71.const_cast,顾名思义,将转换掉表达式的const性质,dynamic_cast支持运行时识别指针或引用所指向的对象,除了添加或删除const特性,用const_cast符来执行其他任何类型转换,都会引起编译错误.int*ip;char*pc=reinterpret_castchar*>(ip);程序员必须永远记得pc所指向的真实对象其实是int型,而并非字符数组。任何假设pc是普通字符指针的应用,都有可能带来有趣的运行时错误。旧式的强制类型转换依次与static_cast,
const_cast,reinterpret_cast匹配。
72.与其他大多数语句不同,块并不是以分号结束的。
73.如果在条件表达式中定义了变量,那么变量必须初始化。将已初始化的变量值转换为bool值(第5.12.3节)后,该bool值决定条件是否成立。
74.case标号必须是整型常量表达式。对于switch结构,只能在它的最后一个case标号或default标号后面定义变量,或者引入块语句。
75.*dest++=*source++;执行过程:1.指针dest加1。2.指针source加1。3.将source原来指向的对象赋给dest原来所指向的对象。
76.可以在for语句的init-statement中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型

constintsize=42;

intval=0,ia[size];

//declare3variableslocaltotheforloop:

//ivalisanint,piapointertoint,andriareferencetoint

for(intival=0,*pi=ia,&ri=val;

ival!=size;

++ival,++pi,++ri)

//...
77.dowhile语句中不能在循环条件中定义变量。
78.goto语句提供了函数内部的无条件跳转,实现从goto语句跳转到同一函数内某个带标号的语句。goto语句不能跨越变量的定义语句向前跳转,如果确实需要在goto和其跳转对应的标号之间定义变量,则定义必须放在一个块语句中。向后(在源代码中,定义出现在goto之前)跳过已经执行的变量定义语句则是合法的,会导致重新定义该变量。//backwardjumpoverdeclarationstatementok

begin:

intsz=get_size();

if(sz<=0){

gotobegin;

}

注意:执行goto语句时,首先撤销变量sz,然后程序的控制流程跳转到带begin:标号的语句继续执行,再次重新创建和初始化sz变量。
79.异常机制提供程序中错误检测与错误处理部分之间的通信。C++的异常处理(实现错误处理与其他代码分离)中包括:

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

2.try块,错误处理部分使用它来处理异常。try语句块以try关键字开始,并以一个或多个catch子句结束。在try块中执行的代码所抛出(throw)的异常,通常会被其中一个catch子句处理。由于它们“处理”异常,catch子句也称为处理代码。

3.由标准库定义的一组异常类,用来在throw和相应的catch之间传递有关的错误信息。
80.与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用assert来测试“不可能发生”的条件。
79.调用操作符的操作数是函数名和一组参数。
80.字符串常量是constchar*;
81.形参是变量,实参是表达式。
81.函数的运行以形参的(隐式)定义和初始化开始。函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针
82.管函数的形参是const,但是编译器却将fcn的定义视为其形码被声明为普通的
int型:为了兼容C,C语言不区别const形参和非const形参
voidfcn(constinti){/*fcncanreadbutnotwritetoi*/}
voidfcn(inti){/*...*/}//error:redefinesfcn(int)

形参与const形参的等价性仅适用于非引用形参。有const引用形参的函数与有非
const引用形参的函数是不同的。类似地,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不相同。
83.但比较容易忽略的是,调用这样的函数时,传递一个右值(第2.3.1节)或具有需要转换的类型的对象同样是不允许的,const引用实参可以转换为形参类型:
//functiontakesanon-constreferenceparameter
intincr(int&val)
{
return++val;
}
intmain()
{
shortv1=0;
constintv2=42;
intv3=incr(v1);//error:v1isnotanint
v3=incr(v2);//error:v2isconst
v3=incr(0);//error:literalsarenotlvalues
v3=incr(v1+v2);//error:additiondoesn'tyieldanlvalue
intv4=incr(v3);//ok:v3isanonconstobjecttypeint
}


83.如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的const特性。但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的;而const引用形参是允许的。
84.指向指针的引用egint*&i;
85.果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:
//ok:parameterisareferencetoanarray;sizeofarrayisfixed
voidprintValues(int(&arr)[10]){/*...*/}
intmain()
{
inti=0,j[2]={0,1};
intk[10]={0,1,2,3,4,5,6,7,8,9};
printValues(&i);//error:argumentisnotanarrayof10ints
printValues(j);//error:argumentisnotanarrayof10ints
printValues(k);//ok:argumentisanarrayof10ints
return0;
}

int(&arr)[10]与int&arr[10]的区别,指针相似。
86.C++中的省略符形参是为了编译使用了varargs的C语言程序。关于如何使用varargs,请查阅所用C语言编译器的文档。对于C++程序,只能将简单数据类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制。
87.返回类型为void的函数通常不能使用第二种形式(返回了值)的return语句,但是,它可以返回另一个返回类型同样是void的函数的调用结果。
88.在含有return语句的循环后没有提供return语句是很危险的(比如while里面有return,但是函数最后没有return),因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。
89.理解返回引用至关重要的是:千万不能返回局部变量的引用。返回引用的函数返回一个左值。千万不要返回指向局部对象的指针。
90.主函数main不能调用自身(即不能递归)。main函数不能重载。
91.既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。
92.只有当定义某个局部变量的函数被调用时才存在的对象叫做自动变量。
92.static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销,注意静态变量的作用域还是遵循局部变量。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。考虑下面的小例子,这个函数计算了自己被调用的次数:
size_tcount_calls()
{
staticsize_tctr=0;//valuewillpersistacrosscalls
return++ctr;
}
intmain()
{
for(size_ti=0;i!=10;++i)
cout<<count_calls()<<endl;
return0;
}

在第一次调用函数count_calls之前,ctr就已创建并赋予初值0。局部变量分为静态和非静态变量。
93.为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:

精确匹配。实参与形参类型相同。

通过类型提升实现的匹配(第5.12.2节)。

通过标准转换实现的匹配(第5.12.3节),标准转换之间都是等级的。

通过类类型转换实现的匹配(第14.9节将介绍这类转换)。

int*const
93.大多数的编译器都不支持递归函数的内联。内联函数应该在头文件中定义,这一点不同于其他函数。inline函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。
94.在使用有枚举类型形参的重载函数时,请记住:由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器。
99.除了用作函数调用的左操作数外,对函数名的任何使用都被解释为相应的函数指针。指向不同函数类型的指针之间不存在转换。指向相应函数的指针调用它所指向的函数时,可以不用接引用操作符。
100.
函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:
voiduseBigger(conststring&,conststring&,
bool(conststring&,conststring&));
//equivalentdeclaration:explicitlydefinetheparameterasapointertofunction
voiduseBigger(conststring&,conststring&,
bool(*)(conststring&,conststring&));

函数可以返回指向函数的指针,但是,正确写出这种返回类型相当不容易:
//ffisafunctiontakinganintandreturningafunctionpointer
//thefunctionpointedtoreturnsanintandtakesanint*andanint
int(*ff(int))(int*,int);

使用typedef可使该定义更简明易懂:
//PFisapointertoafunctionreturninganint,takinganint*andanint
typedefint(*PF)(int*,int);
PFff(int);//ffreturnsapointertofunction


有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现:
//funcisafunctiontype,notapointertofunction!
typedefintfunc(int*,int);
voidf1(func);//ok:f1hasaparameteroffunctiontype
funcf2(int);//error:f2hasareturntypeoffunctiontype
func*f3(int);//ok:f3returnsapointertofunctiontype

94.编译器隐式地将在类内定义的成员函数当作内联函数,inline成员函数必须在调用该函数的每个源文件中是可见的,不在类定义体中定义的inline函数通常应放在类定义的头文件中。
95.const对象和指向const对象的指针或引用只能用于调用其const成员函数。
95.const成员函数中const关键字必须同时出现在声明和定义中,否则会导致编译时错误
96.不完全类型只能定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或者返回类型的函数。在使用引用或指针访问类的成员之前,必须已经定义类。类不能拥有自身类型的数据成员。类只有该类型的对象时才分配对象,一般定义类不分配对象。
95.每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。每个成员函数(除了在第12.6节介绍的static成员函数外)都有一个额外的、隐含的形参this(是一个const指针)。在调用成员函数时,形参this初始化为调用函数的对象的地址。成员函数声明的形参表后面的const改变了隐含的this形参的类型。
96.const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的。基于成员函数是否为cosnt可以重载成员函数,const对象只能使用const成员,非const对象都可以,但是非cosnt版本是更好的匹配。const成员函数只能返回*this作为一个const引用。
97.在成员函数中,不必显式地使用this指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针this实现的引用。
98.类定义实际是在两个阶段中处理:首先编译成员声明,只有在所有成员出现后才编译它们的定义本身。
99.在C++中,名字查找发生在类型检查之前。
99.一旦一个名字被用作类型名,改名字就不能被重复定义。
typedefdoubleMoney;
classAccount{
public:
Moneybalance(){returnbal;}//usesglobaldefinitionofMoney
private:
//error:cannotchangemeaningofMoney
typedeflongdoubleMoney;
Moneybal;
//...
};

类型别名的定义必须出现在它的使用之前。
100.当函数的返回类型不同时,编译器会认为是重载,但是又不是合法重载,所以导致编译错误。
101.不管成员是否在构造函数初始化列表中显示初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。假如内置类型和复合类型成员没有在初始化列表初始化,则依赖于对象的作用域。全局初始化为0,局部作用域不初始化。
102.没有默认构造函数的类类型成员,以及const以及引用类型成员,必须在构造函数初始化列表进行初始化。
98.合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的构造函数初始化这些成员。没有默认构造函数的类型不能用作动态分配数组的元素类型,作为静态数组的元素则必须为每个元素提供一个显示的初始化式。对于保存该类对象的容器,不能使用接受容器大小但是没有同时提供一个元素初始化式的构造函数。
99.假如定义了一个构造函数,一般应该定义一个默认构造函数,复制构造函数也是构造函数,如果需要定义一个析构函数,则也需要其他复制构造函数和赋值操作符。只有析构函数编译器总是会合成一个。合成的析构函数并不会删除指针成员所指向的对象。析构函数不能指定任何形参,所以不能重载析构函数。先运行自己定义的析构函数,再运行合成的。合成的析构函数不会撤销static成员,static成员怎么构造,怎么析构呢???????????????????

100.可以多次发生隐式转换。对于定义了构造函数的类,不能使用显示初始化式。对于没有定义构造函数而且数据成员都为public才能使用显示初始化式。
99.假如存在两个或多个构造函数的形参都有默认参数,将导致默认构造函数重复定义,构成非法重载。
101.友元也是接口的一部分。
102.假如将A类的成员函数设为B类的友元,则必须A类必须先定义,但是A的该成员函数必须定义在B之后,为了访问B的成员。
103.static成员函数可以直接访问所属类的static成员,但是不能直接使用非static成员,并且没有this形参。static数据成员必须在类定义体外定义,且只定义一次,不是由构造函数初始化的,conststatic数据成员可以在类体内进行初始化,同样不是由构造函数实现,但是仍然需要在类体外进行定义,但是不用指定初始化值。一般可放在类的实现文件。在类定义体外部实现static成员函数时,可以不重复指定static关键字。static成员函数不能为const,理由在于。。。也不能声明为虚函数。static关键字只能出现声明中,不能出现在定义中。static数据成员可以是该成员所属的类类型,也可以用作默认实参,非static数据成员不能,因为无法提供对象以获取该成员的值。
104.如果没有为类类型数组提供初始化式,则用默认构造函数初始化每个元素。但是如果用常规的花括号括住的数组初始化列表来提供显示元素初始化式,则使用复制初始化每个元素。
105.
Pointglobal;

Pointfoo_bar(Pointarg)
{
Pointlocal=arg;//复制构造函数
Point*heap=newPoint(global);//复制构造函数
*heap=local;//只是赋值操作符
Pointpa[4]={local,*heap};//复制构造函数
return*heap;//复制构造函数
}

106.假如类有一个数组成员,那么复制构造函数将复制数组的每个成员,一般情况下数组是不能复制的。复制构造函数一般都不为explicit,因为要用于传参和从函数返回对象。合成的复制构造函数只适用于类只含有类类型或者内置类型,对于有指针成员或者引用成员或者分配了资源,必须自己定义复制构造函数。
107.复制构造函数的参数必须为引用,理由。。。
108.能够声明成员,但是不定义,将导致链接失败,运行时错误。通过声明但是不定义一个private复制构造函数,则在用户代码中复制这个类对象,将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。不允许复制的类对象只能作为引用传递给函数或从函数返回,也不能用作容器的元素。
109.当对象的指针或者引用超出作用域时,不会运行析构函数。

110.复制构造函数和其他构造函数一样,如果没有初始化某个类成员,则该成员的默认构造函数初始化,而不是使用成员的复制构造函数。
111.重载操作符必须具有一个类类型操作数或者枚举类型。不能为内置类型重载操作符,也不能为其添加新的操作符。除了函数调用操作符operator()之外,重载操作符使用默认实参是非法的。重载操作符优先级和结合性是固定的,但不再具备短路求值特性(如,,and,or,)。一般将算术和关系操作符定义为非成员函数,而将赋值操作符定义为成员。
112.算术运算和输入输出已经关系操作符一般定义为非成员函数,而赋值操作符包括+=,++,--,解引用应定为成员函数,但不强制,=,[],->和()必须定义为成员函数,否则会编译错误,()调用操作符可以重载多个版本。
113.如果没有特定的重载版本,编译器将合成赋值操作符,取地址操作符,逗号操作符,内置逻辑与和逻辑或,保持各个操作符的原有特性。但是如果重载了将发生改变。
114.IO操作符必须定义为非成员函数,理由是否则左操作数将只能是该类类型的对象:
//ifoperator<<isamemberofSales_item
Sales_itemitem;
item<<cout;

与习惯相反。如果要想是成员函数,也要保持习惯,则只能是ostream的成员,但是不能为标准库增加成员。
115.对象的读入可能只读入一部分内容,必须在读入的时候检测流的状态。
116.返回局部对象的引用将导致运行时错误。
117.一般由复合复制操作符实现算术操作符。
118.前自增和前自减操作符返回的被增量或减量对象的引用,但是后缀式返回的是修改前的对象。
99.函数不能仅仅基于不同的返回类型而实现重载。
100.//constisirreleventfornonreferenceparameters

Recordlookup(Phone);

Recordlookup(constPhone);//redeclaration
形参与const形参的等价性仅适用于非引用形参。有const引用形参的函数与有非const引用形参的函数是不同的。类似地,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不相同。建议
101.stringinit();//thenameinithasglobalscope

voidfcn()

{

intinit=0;//initislocalandhidesglobalinit

strings=init();//error:globalinitishidden

}
如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
102.在C++中,名字查找发生在类型检查之前。
103.如果有且仅有一个函数满足下列条件,则匹配成功:

1.其每个实参的匹配都不劣于其他可行函数需要的匹配。

2.至少有一个实参的匹配优于其他可行函数提供的匹配。
104.在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:需要使用强制类型转换意味着所设计的形参集合不合理。
105.转换等级以降序排列如下:

1.精确匹配。实参与形参类型相同。

2.通过类型提升实现的匹配(第5.12.2节)。

3.通过标准转换实现的匹配(第5.12.3节)。

4.通过类类型转换实现的匹配(第14.9节将介绍这类转换)。

106.整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。在使用有枚举类型形参的重载函数时,请记住:由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器。
下面的函数调用是否合法?如果不合法,请解释原因。

enumStat{Fail,Pass};

voidtest(Stat);

test(0);

该函数调用不合法。因为函数的形参为枚举类型Stat,函数调用的实参为int类型。枚举类型对象只能用同一枚举类型的另一对象或者一个枚举成员进行初始化,因此不能将int类型的实参值传递给枚举类型的形参。
107.仅当形参是引用或指针时,形参是否为const才有影响。注意不能基于指针本身是否为const来实现函数的重载。
108.bool(*pf)(conststring&,conststring&);
//pf
pointstofunctionreturningbool
thattakestwoconststring
references
bool(*pf)(conststring&,conststring&);

typedefbool(*cmpFcn)(conststring&,conststring&);
直接引用函数名等效于在函数名上应用取地址操作符:

cmpFcnpf1=lengthCompare;

cmpFcnpf2=&lengthCompare;

函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。将函数指针初始化为0,表示该指针不指向任何函数。指向不同函数类型的指针之间不存在转换。向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数:
cmpFcnpf=lengthCompare;
lengthCompare("hi","bye");//directcall
pf("hi","bye");//equivalentcall:pf1implicitlydereferenced
(*pf)("hi","bye");//equivalentcall:pf1explicitlydereferenced

如果指向函数的指针没有初始化,或者具有0值,则该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,方能安全地用来调用函数。

函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:

[/*useBiggerfunction'sthirdparameterisapointertofunction

*thatfunctionreturnsa
boolandtakestwo
conststringreferences

*twowaystospecifythatparameter:
*/

//thirdparameterisafunctiontypeandisautomaticallytreatedasapointertofunction
voiduseBigger(conststring&,conststring&,bool(conststring&,conststring&));

//equivalentdeclaration:explicitlydefinetheparameterasapointertofunction
voiduseBigger(conststring&,conststring&,bool(*)(conststring&,conststring&));
函数可以返回指向函数的指针,但是,正确写出这种返回类型相当不容易:
//ffisafunctiontakinganintandreturningafunctionpointer
//thefunctionpointedtoreturnsanintandtakesanint*andanint
int(*ff(int))(int*,int);

阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。

要理解该声明的含义,首先观察:

ff(int)

将ff声明为一个函数,它带有一个int型的形参。该函数返回

int(*)(int*,int);

它是一个指向函数的指针,所指向的函数返回int型并带有两个分别是int*型和int型的形参。

使用typedef可使该定义更简明易懂:
//PFisapointertoafunctionreturninganint,takinganint*andanint
typedefint(*PF)(int*,int);
PFff(int);//ffreturnsapointertofunction

允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现。

//funcisafunctiontype,notapointertofunction!

typedefintfunc(int*,int);

voidf1(func);//ok:
f1hasaparameteroffunctiontype

funcf2(int);//error:
f2hasareturntypeoffunctiontype

func*f3(int);//ok:
f3returnsapointertofunctiontype

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

109.标准库类型不允许做复制或赋值操作,只有支持复制的元素类型可以存储在vector或其他容器类型里。由于流对象不能复制,因此不能存储在vector(或其他)容器(即不存在存储流对象的vector或其他容器),形参或返回类型也不能为流类型。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用。
110.
intival;
//readcinandtestonlyforEOF;loopisexecutedevenifthereareotherIOfailures
while(cin>>ival,!cin.eof()){
if(cin.bad())//inputstreamiscorrupted;bailout
throwruntime_error("IOstreamcorrupted");
if(cin.fail()){//badinput
cerr<<"baddata,tryagain";//warntheuser
cin.clear(istream::failbit);//resetthestream
continue;//getnextinput
}
//oktoprocessival
}

因此,循环条件只读入cin而忽略了其结果。该条件的结果是!cin.eof()的值。
111.

1.程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区。

2.在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。

3.用操纵符(第1.2.2节)显式地刷新缓冲区,例如行结束符endl。

4.在每次输出操作执行完后,用unitbuf操作符设置流的内部状态,从而清空缓冲区。

5.可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。

6.如果程序崩溃了,则不会刷新缓冲区。
112.如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除该流的状态。
113.stringstream对象的一个常见用法是,需要在多种数据类型之间实现自动格式化时使用该类类型。例如,有一个数值型数据集合,要获取它们的string表示形式,或反之。sstream输入和输出操作可自动地把算术类型转化为相应的string表示形式,反过来也可以。
intval1=512,val2=1024;
ostringstreamformat_message;
//ok:convertsvaluestoastringrepresentation
format_message<<"val1:"<<val1<<"\n"
<<"val2:"<<val2<<"\n";


114.成员函数声明为常量,const必须同时出现在声明和定义中。
115.数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。封装是一项低层次的元素组合起来的形成新的、高层次实体珠技术。函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。同样地,类也是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。
116.如果类具有内置类型或复合类型数据成员,那么定义构造函数来初始化这些成员就是一个好主意。
117.在public之后定义的成员称为公有成员,可以由程序的所有的部分访问;在private之后定义的成员称为私有成员,只能由本类(的成员函数)访问;在protected之后定义的成员称为受保护成员,只能由本类及本类的后代类访问。
118.在一个给定的源文件中,一个类只能被定义一次。如果在多个文件中定义一个类,那么每个文件中的定义必须是完全相同的。
119.不完全类型只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。类不能具有自身类型的数据成员。类的数据成员可以是指向自身类型的指针或引用。
120.尽管在成员函数内部显式引用this通常是不必要的,但有一种情况下必须这样做:当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用this:该函数返回对调用该函数的对象的引用。
121.在普通的非const成员函数中,this的类型是一个指向类类型的
const指针。可以改变this所指向的值,但不能改变this所保存的地址。在const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this
作为一个const引用。
122.基于成员函数是否为const,可以重载一个成员函数。const对象只能使用const成员。非const对象可以使用任一成员,但非const版本是一个更好的匹配。
123.可变数据成员永远都不能为const,甚至当它是const对象的成员时也如此。因此,const成员函数可以改变mutable成员。
124.类定义实际上是在两个阶段中处理:首先,编译成员声明;只有在所有成员出现之后,才编译它们的定义本身。
125.一旦一个名字被用作类型名,该名字就不能被重复定义。
126.const构造函数是不必要的。构造函数初始化式只在构造函数的定义中而不是声明中指定。
127.构造函数初始化列表难以理解的一个原因在于,省略初始化列表在构造函数的函数体内对数据成员赋值是合法的。例如,可以将接受一个string的Sales_item构造函数编写为:
//legalbutsloppierwaytowritetheconstructor:
//noconstructorinitializer
Sales_item::Sales_item(conststring&book)
{
isbn=book;
units_sold=0;
revenue=0.0;
}

这个构造函数给类Sales_item的成员赋值,但没有进行显式初始化。不管是否有显式的初始化式,在执行构造函数之前,要初始化isbn成员。这个构造函数隐式使用默认的string构造函数来初始化isbn。执行构造函数的函数体时,isbn成员已经有值了。该值被构造函数函数体中的赋值所覆盖。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为
0。有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。可以初始化const对象或引用类型的对象,但不能对它们赋值。初始化const或引用类型数据成员的唯一机会是构造函数初始化列表中。必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。按照与成员声一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。
128.习题12.26
129.只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。
130.。NoDefault没有默认构造函数,意味着:

1.具有NoDefault成员的每个类的每个构造函数,必须通过传递一个初始的string值给NoDefault构造函数来显式地初始化NoDefault成员。

2.编译器将不会为具有NoDefault类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式地定义,并且默认构造函数必须显式地初始化其NoDefault成员。

3.NoDefault类型不能用作动态分配数组的元素类型。

4.NoDefault类型的静态分配数组必须为每个元素提供一个显式的初始化式。

5.如果有一个保存NoDefault对象的容器,例如vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。

实际上,如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。
131.可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。
132.实际上,我们构造了一个在测试完成后被丢弃的对象。这个行为几乎肯定是一个错误。可以通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数。explicit关键字只能用于类内部的构造函数声明上。显式使用构造函数只是中止了隐式地使用构造函数。任何构造函数都可以用来显式地创建临时对象。通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式地构造对象。
133.尽管大多数对象可以通过运行适当的构造函数进行初始化,但是直接初始化简单的非抽象类的数据成员仍是可能的。对于没有定义构造函数并且其全体数据成员均为public的类,可以采用与初始化数组元素相同的方式初始化其成员,这种方式不好。
134.友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。friendWindow_Mgr&Window_Mgr::relocate(Window_Mgr::index,Window_Mgr::index);friendclassWindow_Mgr;
135.友元声明将已命名的类或非成员函数引入到外围作用域中。此外,友元函数可以在类的内部定义,该函数的作用域扩展到包围该类定义的作用域。用友元引入的类名和函数(定义或声明),可以像预先声明的一样使用。
用友元引入的类名和函数(定义或声明),可以像预先声明的一样使用:
classX{
friendclassY;
friendvoidf(){/*oktodefinefriendfunctionintheclassbody*/}
};
classZ{
Y*ymem;//ok:declarationforclassYintroducedbyfriendinX
voidg(){return::f();}//ok:declarationoffintroducedbyX
};


136.每个static数据成员是与类关联的对象,并不与该类的对象相关联。static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。可以通过作用域操作符从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。

137.static数据成员必须在类定义体的外部定义(正好一次)。static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。保证对象正好定义一次的最好办法,就是将static数据成员的定义放在包含类非内联成员函数定义的文件中。像使用任意的类成员一样,在类定义体外部引用类的static成员时,必须指定成员是在哪个类中定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标示为static。,只要初始化式是一个常量表达式,整型conststatic数据成员就可以在类的定义体中进行初始化。const
static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义。在类内部提供初始化式时,成员的定义不必再指定初始值。因static数据成员不是任何对象的组成部分,所以它们的使用方式对于非static数据成员而言是不合法的。

例如,static数据成员的类型可以是该成员所属的类类型。非static成员被限定声明为其自身类对象的指针或引用:
classBar{
public:
//...
private:
staticBarmem1;//ok
Bar*mem2;//ok
Barmem3;//error
};

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

138.不管类是否定义了自己的析构函数,编译器都自动执行类中非static数据成员的析构函数。

139.。有一种特别常见的情况需要类定义自己的复制控制成员当类具有指针成员或者另一些类在创建新对象时必须做一些特定工作。为了防止复制,类必须显式声明其复制构造函数为private。然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。通过声明(但不定义)private复制构造函数,可以禁止任何复制类类型对象的尝试:用户代码中复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。如果定义了复制构造函数,也必须定义默认构造函数。不定义复制构造函数和/或默认构造函数,会严重局限类的使用。不允许复制的类对象只能作为引用传递给函数或从函数返回,它们也不能用作容器的元素。

140.只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数,一般不用explicit修饰。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:

•根据另一个同类型的对象显式或隐式初始化一个对象。

•复制一个对象,将它作为实参传给一个函数。

•从函数返回时复制一个对象。

•初始化顺序容器中的元素。

•根据元素初始化式列表初始化数组元素。

141.复制初始化使用=符号,而直接初始化将初始化式放在圆括号中。直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象然后用复制构造函数将那个临时对象复制到正在创建的对象。

142.通常直接初始化和复制初始化仅在低级别上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:

ifstreamfile1("filename");//ok:directinitialization
ifstreamfile2="filename";//error:copyconstructorisprivate
//Thisinitializationisokayonlyif
//theSales_item(conststring&)constructorisnotexplicit
Sales_itemitem=string("9-999-99999-9");

item的初始化是否正确,取决于正在使用哪个版本的Sales_item类。某些版本将参数为一个string的构造函数定义为explicit。如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。
143.const引用,不能复制。
144.复制构造函数可用于初始化顺序容器中的元素。例如,可以用表示容量的单个形参来初始化容器。容器的这种构造方式使用默认构造函数和复制构造函数:
//defaultstringconstructorandfivestringcopyconstructorsinvoked
vector<string>svec(5);

编译器首先使用string默认构造函数创建一个临时值来初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素。
145.如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。然而,如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素。
146.合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是个例外。虽然一般不能复制数组,但如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。合成操作依次取得每个成员,根据成员类型进行成员的复制、赋值或撤销。如果成员为类类型的,合成操作调用该类的相应操作(即,复制构造函数调用成员的复制构造函数,析构函数调用成员的析构函数,等等)。如果成员为内置类型或指针,则直接复制或赋值,析构函数对撤销内置类型或指针类型的成员没有影响。如果成员为数组,则根据元素类型以适当方式复制、赋值或撤销数组中的元素。
147.动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。
148.析构函数并不仅限于用来释放资源。一般而言,析构函数可以执行任意操作,该操作是类设计者希望在该类对象的使用完毕之后执行的。合成析构函数按对象创建时的逆序撤销每个非static成员。对于类类型的每个成员,合成析构函数调用该成员的析构函数来撤销对象。撤销内置类型成员或复合类型的成员没什么影响。尤其是,合成析构函数并不删除指针成员所指向的对象。因为不能指定任何形参,所以不能重载析构函数。虽然可以为一个类定义多个构造函数,但只能提供一个析构函数,应用于类的所有对象。析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行。合成析构函数在自定义析构函数之后运行。
149.习题13.14
150.下面的代码段中发生了多少次析构函数的调用?
voidfcn(constSales_item*trans,Sales_itemaccum)
{
Sales_itemitem1(*trans),item2(accum);
if(!item1.same_isbn(item2))return;
if(item1.avg_price()<=99)return;
elseif(item2.avg_price()<=99)return;
//...
}

3次,分别用于函数fcn返回时撤销形参对象accum,局部对象item1和iterm2。
151.编写自己的复制构造函数时,必须显式复制需要复制的任意成员。显式定义的复制构造函数不会进行任何自动复制。像其他任何构造函数一样,如果没有初始化某个类成员,则那个成员用该成员的默认构造函数初始化。复制构造函数中的默认初始化不会使用成员的复制构造函数。
152.包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。
153.



复制对象后:



/*smartpointerclass:takesownershipofthedynamicallyallocated
*objecttowhichitisbound
*UsercodemustdynamicallyallocateanobjecttoinitializeaHasPtr
*andmustnotdeletethatobject;theHasPtrclasswilldeleteit
*/
classHasPtr{
public:
//HasPtrownsthepointer;pmusthavebeendynamicallyallocated
HasPtr(int*p,inti):ptr(newU_Ptr(p)),val(i){}

//copymembersandincrementtheusecount
HasPtr(constHasPtr&orig):
ptr(orig.ptr),val(orig.val){++ptr->use;}
HasPtr&operator=(constHasPtr&);

//ifusecountgoestozero,deletetheU_Ptrobject
~HasPtr(){if(--ptr->use==0)deleteptr;}
private:
U_Ptr*ptr;//pointstouse-countedU_Ptrclass
intval;
};

接受一个指针和一个int值的HasPtr构造函数使用其指针形参创建一个新的
U_Ptr对象。HasPtr构造函数执行完毕后,HasPtr对象指向一个新分配的U_Ptr对象,该U_Ptr对象存储给定指针。新U_Ptr中的使用计数为1,表示只有一个HasPtr对象指向它。

复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。

析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。

赋值操作符比复制构造函数复杂一点:
HasPtr&HasPtr::operator=(constHasPtr&rhs)
{
++rhs.ptr->use;//incrementusecountonrhsfirst
if(--ptr->use==0)
deleteptr;//ifusecountgoesto0onthisobject,deleteit
ptr=rhs.ptr;//copytheU_Ptrobject
val=rhs.val;//copytheintmember
return*this;
}

在这里,首先将右操作数中的使用计数加1,然后将左操作数对象的使用计数减1并检查这个使用计数。像析构函数中那样,如果这是指向U_Ptr对象的最后一个对象,就删除该对象,这会依次撤销int基础对象。将左操作数中的当前值减1(可能撤销该对象)之后,再将指针从rhs复制到这个对象。赋值照常返回对这个对象的引用。这个赋值操作符在减少左操作数的使用计数之前使rhs的使用计数加1,从而防止自身赋值。
154.使用计数是复制控制成员中使用的编程技术。将一个计数器与类指向的对象相关联,用于跟踪该类有多少个对象共享同一指针。创建一个单独类指向共享对象并管理使用计数。由构造函数设置共享对象的状态并将使用计数置为1.每当由复制构造函数或赋值操作符生成一个新副本时,使用计数加1,。由析构函数撤销对象或作为赋值操作符的左操作数撤销对象时,使用计数减1.赋值操作符和析构函数检查使用计数是否已减至0,如果是,则撤销对象。
155.什么是智能指针?智能指针类如何与实现普通指针行为的类相区别?

智能指针是一个行为类似指针但也提供其他功能的类。智能指针类与实现普通指针行为的类的区别在于:智能指针通常接受指向动态分配对象的指针并负责删除该对象。用户分配对象,但由智能指针类删除它,因此智能指针类需要实现复制控制成员来管理指向共享对象的指针。只有在撤销了指向共享对象的最后一个智能指针后,才能删除该共享对象。
156.要使指针成员表现得像一个值,复制HasPtr对象时必须复制指针所指向的对象:
/*
*ValuelikebehavioreventhoughHasPtrhasapointermember:
*EachtimewecopyaHasPtrobject,wemakeanewcopyofthe
*underlyingintobjecttowhichptrpoints.
*/
classHasPtr{
public:
//nopointtopassingapointerifwe'regoingtocopyitanyway
//storepointertoacopyoftheobjectwe'regiven
HasPtr(constint&p,inti):ptr(newint(p)),val(i){}

//copymembersandincrementtheusecount
HasPtr(constHasPtr&orig):
ptr(newint(*orig.ptr)),val(orig.val){}

HasPtr&operator=(constHasPtr&);
~HasPtr(){deleteptr;}
//accessorsmustchangetofetchvaluefromPtrobject
intget_ptr_val()const{return*ptr;}
intget_int()const{returnval;}

//changetheappropriatedatamember
voidset_ptr(int*p){ptr=p;}
voidset_int(inti){val=i;}

//returnorchangethevaluepointedto,sookforconstobjects
int*get_ptr()const{returnptr;}
voidset_ptr_val(intp)const{*ptr=p;}
private:
int*ptr;//pointstoanint
intval;
};

赋值操作符不需要分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:
HasPtr&HasPtr::operator=(constHasPtr&rhs)
{
//Note:EveryHasPtrisguaranteedtopointatanactualint;
//Weknowthatptrcannotbeazeropointer
*ptr=*rhs.ptr;//copythevaluepointedto
val=rhs.val;//copytheint
return*this;
}

157.什么是值型类?

所谓值型类,是指具有值语义的类,其特征为:对该类对象进行复制时,会得到一个不同的新副本,对副本所做的改变不会影响原有对象。
158.重载操作符是具有特殊名称的函数,用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:
//error:cannotredefinebuilt-inoperatorforints
intoperator+(int,int);

也不能为任何内置类型定义额外的新的操作符。例如不能定义接受两个数组类型操作符的operator+。重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。作符的优先级、结合性或操作数目不能改变。除了函数调用操作符operator()之外,重载操作符时使用默认实参是非法的。重载操作符并不保证操作数的求值顺序,在&&和||的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。因此,重载&&、||
或逗号操作符以及取地址符不是一种好的做法。也可以像调用普通函数一样调用重载操作符函数,指定函数并传递适当类型适当数目的形参:
//equivalentdirectcalltononmemberoperatorfunction
cout<<operator+(item1,item2)<<endl;


item1+=item2;//expressionbased"call"
item1.operator+=(item2);//equivalentcalltomemberoperatorfunction

159.下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数:

•赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

•像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。

•改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。

•对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

160.重载输出操作符一般的简单定义如下:
//generalskeletonoftheoverloadedoutputoperator
ostream&
operator<<(ostream&os,constClassType&object)
{
//anyspeciallogictoprepareobject

//actualoutputofmembers
os<<//...

//returnostreamobject
returnos;
}

我们不能将该操作符(输出操作符同样)定义为类的成员,否则,左操作数将只能是该类类型的对象:
//ifoperator<<isamemberofSales_item
Sales_itemitem;
item<<cout;

161.设计输入操作符时,如果可能,要确定错误恢复措施,这很重要。
162.为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时错误。既定义了算术操作符又定义了相关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。
163.赋值操作符可以重载。无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合赋值操作符有所不同。一般而言,赋值操作符与复合赋值操作符应返回操作符的引用。
164.类定义下标操作符时,一般需要定义两个版本:一个为非const成员并返回引用,另一个为
const成员并返回const引用。
165.箭头操作符与众不同。它可能表现得像二元操作符一样:接受一个对象和一个成员名。对对象解引用以获取成员。不管外表如何,箭头操作符不接受显式形参。这里没有第二个形参,因为->的右操作数不是表达式,相反,是对应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形参传递给函数,相反,由编译器处理获取成员的工作。当这样编写时:
point->action();

由于优先级规则,它实际等价于编写:
(point->action)();

换句话说,我们想要调用的是对point->action求值的结果。编译器这样对该代码进行求值:

如果point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的action成员。

否则,如果point是定义了
operator->操作符的类的一个对象,则point->action与point.operator->()->action相同。即,执行point的operator->(),然后使用该结果重复这三步。

否则,代码出错。

166.重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。如果返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。如果被指向的类型没有定义那个成员,则编译器产生一个错误。如果返回类型是类类型的其他对象(或是这种对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,如果有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其他值,在后一种情况下,代码出错。(递归的)。需要解引用操作符的const
和非const版本。
167.后缀式操作符函数接受一个额外的(即,无用的)int型形参。为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用。了与内置操作符一致,后缀式操作符应返回旧值(即,尚未自增或自减的值),并且,应作为值返回,而不是返回引用。const对象不能自增自减。
//postfix:increment/decrementobjectbutreturnunchangedvalue
CheckedPtrCheckedPtr::operator++(int)
{

//nocheckneededhere,thecalltoprefixincrementwilldothecheck
CheckedPtrret(*this);//savecurrentvalue
++*this;//advanceoneelement,checkingtheincrement
returnret;//returnsavedstate
}
CheckedPtrCheckedPtr::operator--(int)
{
//nocheckneededhere,thecalltoprefixdecrementwilldothecheck
CheckedPtrret(*this);//savecurrentvalue
--*this;//movebackwardoneelementandcheck
returnret;//returnsavedstate
}

168.可以显式调用重载操作符而不是将它作为操作符用在表达式中。如果想要使用函数调用来调用后缀式操作符,必须给出一个整型实参值:
CheckedPtrparr(ia,ia+size);//iapointstoanarrayofints
parr.operator++(0);//callpostfixoperator++
parr.operator++();//callprefixoperator++

169.习题14.24
170.函数调用操作符必须声明为成员函数。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。函数对象可以比函数更灵活。
171.标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象。函数适配器分为如下两类:绑定器(bind1st,bind2nd),求反器(not1,not2)
172.习题14.37
173.转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类

型是可以的。转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定

类型的值。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const成员。
174.

只要存在转换,编译器将在可以使用内置转换的地方自动调用。只能有一次类型转换,但是后面可以跟多次标准转换。标准转换优于类类型转换。
175.使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可在类类型转换之后跟上标准转换以获得想要的类型。只能应用一个类类型转换。使用构造函数执行隐式转换的时候,构造函数的形参类型不必与所提供的类型完全匹配,在类型转换前可以有标准转换。
176.习题14.41。
177.一般而言,给出一个类与两个内置类型之间的转换是不好的做法。如果两个转换操作符都可用在一个调用中,而且在转换函数之后存在标准转换则根据该标准转换的类别选择最佳匹配。当两个类定义了相互转换时,很可能存在二义性。
classIntegral;
classSmallInt{
public:
SmallInt(Integral);//convertfromIntegraltoSmallInt
//...
};
classIntegral{
public:
operatorSmallInt()const;//convertfromSmallInttoIntegral
//...
};
voidcompute(SmallInt);
Integralint_val;
compute(int_val);//error:ambiguous

实参int_val可以用两种不同方式转换为SmallInt对象,编译器可以使用接受Integral对象的构造函数,也可以使用将Integral对象转换为SmallInt对象的Integral转换操作。因为这两个函数没有高下之分,所以这个调用会出错。在这种情况下,不能用显式类型转换来解决二义性——显式类型转换本身既可以使用转换操作又可以使用构造函数,相反,需要显式调用转换操作符或构造函数:
compute(int_val.operatorSmallInt());//ok:useconversionoperator
compute(SmallInt(int_val));//ok:useSmallIntconstructor

而且,由于某些似乎微不足道的原因,我们认为可能有二义性的转换是合法的。例如,SmallInt类构造函数复制它的Integral实参,如果改变构造函数以接受 constIntegral引用:
classSmallInt{
public:
SmallInt(constIntegral&);
};

则对compute(int_val)的调用不再有二义性!原因在于使用SmallInt构造函数需要将一个引用绑定到int_val,而使用Integral类的转换操作符可以避免这个额外的步骤。这一小小区别足以使我们倾向于使用转换操作符。避免二义性最好的方法是避免编写互相提供隐式转换的成对的类。保证最多只有一种途径将一个类型转换为另

一类型。做到这点,最好的办法是限制转换操作符的数目,尤其是,到一种内置类型应该只有一个转换。编译器将不会试图区别两个不同的类类型转换。具体而言,即使一个调用需要在类类型转换之后跟一个标准转换,而另一个是完全匹配,编译器仍会将该调用标记为错误。面对二义性转换,程序员可以使用强制转换来显式指定应用哪个转换操作。在调用重载函数时,需要使用构造函数或强制类型转换来转换实参,这是设计拙劣的表现。
正确设计类的重载操作符、转换构造函数和转换函数需要多加小心。尤其是,如果类既定义了转换操作符又定义了重载操作符,容易产生二义性。下面几条经验规则会有所帮助:

不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数,不要再为类Bar定义到类型Foo的转换操作符。

避免到内置算术类型的转换。具体而言,如果定义了到算术类型的转换,则

不要定义接受算术类型的操作符的重载版本。如果用户需要使用这些操作符,转换操作符将转换你所定义的类型的对象,然后可以使用内置操作符。

不要定义转换到一个以上算术类型的转换。让标准转换提供到其他算术类型的转换。

178.重载操作符就是重载函数。使用与确定重载函数调用一样的过程来确定将哪个操作符(内置的还是类类型的)应用于给定表达式。给定如下代码:
ClassXsc;
intiobj=sc+3;

有四种可能性:

有一个重载的加操作符与ClassX和int相匹配。

存在转换,将sc和/或int值转换为定义了+的类型。如果是这样,该表达式将先使用转换,接着应用适当的加操作符。

因为既定义了转换操作符又定义了+的重载版本,该表达式具有二义性。

因为既没有转换又没有重载的+可以使用,该表达式非法。

179.确定指定函数的调用时,与操作符的使用相反,由调用本身确定所考虑的名字的作用域。如果是通过类类型的对象(或通过这种对象的引用或指针)的调用,则只需考虑该类的成员函数。具有同一名字的成员函数和非成员函数不会相互重载。使用重载操作符是时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本都必须考虑。

180.一般而言,函数调用的候选集只包括成员函数或非成员函数,不会两者都包括。而确定操作符的使用时,操作符的非成员和成员版本可能都是候选者。

180.既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性。

181.在C++中,多态性仅用于通过继承而相关联的类型的引用或指针。

182.在C++中,基类必须指出希望派生类重写哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

183.除了构造函数之外,任意非static成员函数都可以是虚函数。virtual保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。

184.。protected成员可以被派生类对象访问但不能被该类型的普通用户访问。

185.classDerived:publicBase;//wrong

如果需要声明(但并不实现)一个派生类,则声明包含类名但不包含派生列表。例如,下面的前向声明会导致编译时错误:

//error:aforwarddeclarationmustnotincludethederivationlist
classBulk_item:publicItem_base;

正确的前向声明为:

//forwarddeclarationsofbothderivedandnonderivedclass
classBulk_item;
classItem_base;


186.派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。

例如,假定Bulk_item定义了一个成员函数,接受一个Bulk_item对象的引用和一个Item_base对象的引用,该函数可以访问自己对象的protected成员以及Bulk_item形参的protected成员,但是,它不能访问Item_base形参的protected成员。

voidBulk_item::memfcn(constBulk_item&d,constItem_base&b)
{
//attempttouseprotectedmember
doubleret=price;//ok:usesthis->price
ret=d.price;//ok:usespricefromaBulk_itemobject
ret=b.price;//error:noaccesstopricefromanItem_base
}

d.price的使用正确,因为是通过Bulk_item类型对象引用price;b.price的使用非法,因为对Base_item类型的对象没有特殊访问访问权限(可能指的b对Bulk_item的price没有访问权限)。

188.派生类必须对想要重定义的每个继承成员进行声明。派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。一旦函数声明为虚函数,它就一直为虚函数。派生类重定义虚函数可以使用virtual关键字也可以不使用。

189.C++语言不要求编译器将对象的基类部分和派生部分连续排列。

188.已定义的类才可以用作基类。这一规则暗示着不可能从类自身派生出一个类。如果需要声明但是并不实现一个派生类,不能使用派生列表,否则将导致编译错误。

189.因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象。基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。将基类类型的引用或指针绑定到派生类对象对基类对象没有影响,对象本身不会改变,仍为派生类对象。对象的实际类型可能不同于该对象引用或指针的静态类型,这是C++中动态绑定的关键。。对象的动态类型

总是与静态类型相同,这一点与引用或指针相反。运行的函数(虚函数或非虚函数)是由对象的类型定义的。

190.注意注释

//calculateandprintpriceforgivennumberofcopies,applyinganydiscounts
voidprint_total(ostream&os,
constItem_base&item,size_tn)
{
os<<"ISBN:"<<item.book()//callsItem_base::book
<<"\tnumbersold:"<<n<<"\ttotalprice:"
//virtualcall:whichversionofnet_pricetocallisresolvedatruntime
<<item.net_price(n)<<endl;
}


190.只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。

Item_base*baseP=&derived;
//callsversionfromthebaseclassregardlessofthedynamictypeofbaseP
doubled=baseP->Item_base::net_price(42);


191.如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会

引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这时就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传给派生类定义的版本,而派生类版本是用不同的默认实参定义的(就是这么规定的)。

192.使用private或protected派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。

193.派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松。using声明访问基类中的名字(恢复基类中的访问控制)

194.如果基类定义static成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个static成员只有一个实例。static成员遵循常规访问控制:如果成员在基类中为private,则派生类不能访问它。假定可以访问成员,则既可以通过基类访问static成员,也可以通过派生类访问static成员。一般而言,既可以使用作用域操作符也可以使用点或箭头成员访问操作符。

structBase{
staticvoidstatmem();//publicbydefault
};
structDerived:Base{
voidf(constDerived&);
};
voidDerived::f(constDerived&derived_obj)
{
Base::statmem();//ok:Basedefinesstatmem
Derived::statmem();//ok:Derivedinheritsstatmem
//ok:derivedobjectscanbeusedtoaccessstaticfrombase
derived_obj.statmem();//accessedthroughDerivedobject
statmem();//accessedthroughthisclass

195.习题15.13

196.存在从派生类型引用到基类类型引用的自动转换(指针同样)。一个基类对象可能是也可能不是一个派生类对象的部分,结果,没有从基类引用(或基类指针)到派生类引用(或派生类指针)的(自动)转换。虽然一般可以使用派生类型的对象对基类类型的对象进行初始化或赋值,但,没有从派生类型对象到基类类型对象的直接转换。一个是派生类对象转换为基类类型引用,一个是用派生类对象对基类对象进行初始化或赋值,理解它们之间的区别很重要。

197.对基类对象进行初始化或赋值,实际上是在调用函数:初始化时调用构造函数,赋值时调用赋值操作符。用派生类对象对基类对象进行初始化或赋值时,有两种可能性。第一种(虽然不太可能的)可能性是,基类可能显式定义了将派生类型对象复制或赋值给基类对象的含义,这可以通过定义适当的构造函数或赋值操作符实现基类一般(显式或隐式地)定义自己的复制构造函数和赋值操作符),这些成员接受一个形参,该形参是基类类型(const)引用。因为存在从派生类引用到基类引用的转换,这些复制控制成员可用于从派生类对象对基类对象进行初始化或赋值。

用。

198.派生类要确定到基类的转换是否可访问,可以考虑基类的public成员是否访问,如果可以,转换是可访问的,否则,转换是不可访问的。

198.编译器在编译时无法知道特定转换在运行时实际上是安全的。编译器确定转换是否合法,只看指针或引用的静态类型,所以即使一个基类指针指向的是派生类对象,仍然不能赋给派生类指针。在这些情况下,如果知道从基类到派生类的转换是安全的,就可以使用static_cast强制编译器进行转换。或者,可以用dynamic_cast申请在运行时进行检查。

199.构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。像任意其他成员一样,构造函数可以为protected或private,某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为protected。(感觉与前一句话有点矛盾)。
200.派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。相反派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据声明次序初始化派生类的成员。一个类只能初始化自己的直接基类。构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。定义Disc_item
时,通过定义它的构造函数指定了怎样初始化Disc_item对象。一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口,即使对象是派生类对象的一部分也不例外。假如基类定义了构造函数,则派生类构造函数必须在初始化列表给出基类的构造函数。

202.初始化函数Base(d)将派生类对象d转换为它的基类部分的引用,并调用基类复制构造函数。如果省略基类初始化函数,如下代码:
//probablyincorrectdefinitionoftheDerivedcopyconstructor
Derived(constDerived&d)/*derivedmemberinitizations*/
{/*...*/}

效果是运行Base的默认构造函数初始化对象的基类部分。假定Derived成员的初始化从d复制对应成员,则新构造的对象将具有奇怪的配置:它的Base部分将保存默认值,而它的Derived成员是另一对象的副本。
202.编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。(由编译器自己来,无需程序猿写代码)。删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数。
203.基类析构函数是三法则的一个重要例外。三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。虽然可以在基类中将成员函数operator=
定义为虚函数,但这样做并不影响派生类中使用的赋值操作符。每个类有自己的赋值操作符,派生类中的赋值操作符有一个与类本身类型相同的形参,该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。将赋值操作符设为虚函数可能会令人混淆,因为虚函数必须在基类和派生类中具有同样的形参。基类赋值操作符有一个形参是自身类类型的引用,如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的operator=。但是,对派生类而言,这个操作符与赋值操作符是不同的。将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。

205.习题15.20
206.构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。在这两种情况下,运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。构造或析构期间的对象类型对虚函数的绑定有影响。如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
207.对象、引用或指针的静态类型决定了对象能够完成的行为。甚至当静态类型和动态类型可能不同的时候,就像使用基类类型的引用或指针时可能会发生的,静态类型仍然决定着可以使用什么成员。
208.虽然可以直接访问基类成员,就像它是派生类成员一样,但是成员保留了它的基类成员资格(基类的成员同时也是派生类中的成员)。与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。可以使用作用域操作符访问被屏蔽成员。

210.局部作用域中声明的函数不会重载全局作用域中定义的函数同样,派生类中定义的函数也不重载基类中定义的成员。
211.如果不想重定义基类某个函数的所有重载版本,而且也不想屏蔽掉基类中的该函数,可以使用using声明。
212.要获得动态绑定,必须通过基类的引用或指针调用虚成员。当我们这样做时,编译器器将在基类中查找函数。假定找到了名字,编译器就检查实参是否与形参匹配。现在可以理解虚函数为什么必须在基类和派生类中拥有同一原型了(返回类型可以不同)。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。考虑如下(人为的)为集合:
classBase{
public:
virtualintfcn();
};
classD1:publicBase{
public:
//hidesfcninthebase;thisfcnisnotvirtual
intfcn(int);//parameterlistdiffersfromfcninBase
//D1inheritsdefinitionofBase::fcn()
};
classD2:publicD1{
public:
intfcn(int);//nonvirtualfunctionhidesD1::fcn(int)
intfcn();//redefinesvirtualfcnfromBase
};

D1中的fcn版本没有重定义Base的虚函数fcn,相反,它屏蔽了基类的fcn。结果D1有两个名为fcn的函数:类从Base继承了一个名为fcn的虚函数,类又定义了自己的名为fcn的非虚成员函数,该函数接受一个int形参。但是,从Base
继承的虚函数不能通过D1对象(或D1的引用或指针)调用,因为该函数被fcn(int)的定义屏蔽了。类D2重定义了它继承的两个函数,它重定义了Base中定义的fcn的原始版本并重定义了D1中定义的非虚版本。

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:
Basebobj;D1d1obj;D2d2obj;
Base*bp1=&bobj,*bp2=&d1obj,*bp3=&d2obj;
bp1->fcn();//ok:virtualcall,willcallBase::fcnatruntime
bp2->fcn();//ok:virtualcall,willcallBase::fcnatruntime
bp3->fcn();//ok:virtualcall,willcallD2::fcnatruntime

三个指针都是基类类型的指针,因此通过在Base中查找fcn来确定这三个调用,所以这些调用是合法的。另外,因为fcn是虚函数,所以编译器会生成代码,在运行时基于引用指针所绑定的对象的实际类型进行调用。在bp2的情况,基本对象是D1类的,D1类没有重定义不接受实参的虚函数版本,通过bp2的函数调用(在运行时)调用Base
中定义的版本。
214.派生类重定义继承的虚函数时,可以去掉是否为const成员//////////////////////////////////////
213.理解C++中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:
1.
首先确定进行函数调用的对象、引用或指针的静态类型。

2.
在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。

3.
一旦找到了该名字,就进行常规类型检查(第7.1.2节),查看如果给定找到的定义,该函数调用是否合法。

4.
假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

214.voidBase::eval()const;voidDerived::eval();这个是重定义。
215.在函数形参表后面写上=0以指定纯虚函数:(这个类将不能创建该类的对象,该类称为抽象类)
classDisc_item:publicItem_base{
public:
doublenet_price(std::size_t)const=0;
};


216.对于派生类的返回类型必须与基类实例的返回类型完全匹配的要求,但有一个例外。这个例外支持像这个类这样的情况。如果虚函数的基类实例返回类类型的引用或指针,则该虚函数的派生类实例可以返回基类实例返回的类型的派生类(或者是类类型的指针或引用)。
217.习题15.42,all
218.习题16.3
219.用作模板形参的名字不能在模板内部重用。这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次
220.除了定义数据成员或函数成员之外,类还可以定义类型成员。必须告诉编译器我们正在使用的名字指的是一个类型。
221.习题16.12.
222.模板非类型形参是模板定义内部的常量值。
223.泛型编码的规则:1.模板的形参使用const引用(两个原因)。2.函数体中的测试只用<比较。
224.类模板不定义类型,只有特定的实例才定义了类型。多个类型形参的实参必须完全匹配,不能有任何转换。设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义。不能转换有两个例外:

const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略const,即,无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化。

数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
例如,考虑对函数fobj和fref的调用。fobj函数复制它的形参,而fref的形参是引用:
template<typenameT>Tfobj(T,T);//argumentsarecopied
template<typenameT>
Tfref(constT&,constT&);//referencearguments
strings1("avalue");
conststrings2("anothervalue");
fobj(s1,s2);//ok:callsf(string,string),constisignored
fref(s1,s2);//ok:nonconstobjects1convertedtoconstreference
inta[10],b[42];
fobj(a,b);//ok:callsf(int*,int*)
fref(a,b);//error:arraytypesdon'tmatch;argumentsaren'tconvertedtopointers

第一种情况下,传递string对象和conststring对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在fobj的调用中,实参被复制,因此原来的对象是否为const无关紧要。在fref的调用中,形参类型是const引用,对引用形参而言,转换为const是可以接受的转换,所以这个调用也正确。在第二种情况中,将传递不同长度的数组实参。fobj
的调用中,数组不同无关紧要,两个数组都转换为指针,fobj的模板形参类型是int*。但是,fref的调用是非法的,当形参为引用时(第7.2.4节),数组不能转换为指针,a和b的类型不匹配,所以调用将出错。
224.用普通类型定义的形参可以使用常规转换。
225.可以使用函数模板对函数指针进行初始化或赋值(第7.9节),这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。例如,假定有一个函数指针指向返回int值的函数,该函数接受两个形参,都是constint引用,可以用该指针指向compare的实例化
template<typenameT>intcompare(constT&,constT&);
//pf1pointstotheinstantiationintcompare(constint&,constint&)
int(*pf1)(constint&,constint&)=compare;

获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。如果不能从函数指针类型确定模板实参,就会出错。
226.一般我们在函数模板的定义中指明函数模板为导出的,这是通过在关键字template之前包含
export关键字而实现的。对类模板使用export更复杂一些。通常,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字export,如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。相反,应该在类的实现文件中使用export。
227.习题16.28。
228.通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。编译器不会为类中使用的其他模板的模板形参进行这样的推断。这一事实意味着,调用类模板成员函数比调用类似函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换
229.模板形参表不能为空
230.函数模板可以用与非模板函数一样的方式声明为inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字template之

231.
template<classParm,classU>
Parmfcn(Parm*array,Uvalue)
{
Parm::size_type*p;//IfParm::size_typeisatype,thenadeclaration
//IfParm::size_typeisanobject,thenmultiplication
}

232.模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度

232.对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。
233.通过将形参设为const引用,就可以允许使用不允许复制的类型。

234.泛型实例化时,只会执行两种转换,只针对模板形参,对于普通形参执行一般的转换。
const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略const,即,无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化。
数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
235.考虑对函数fobj和fref的调用。fobj函数复制它的形参,而fref的形参是引用:
template<typenameT>Tfobj(T,T);//argumentsarecopied
template<typenameT>
Tfref(constT&,constT&);//referencearguments
strings1("avalue");
conststrings2("anothervalue");
fobj(s1,s2);//ok:callsf(string,string),constisignored
fref(s1,s2);//ok:nonconstobjects1convertedtoconstreference
inta[10],b[42];
fobj(a,b);//ok:callsf(int*,int*)
fref(a,b);//error:arraytypesdon'tmatch;argumentsaren'tconvertedtopointers

当形参为引用时(第7.2.4节),数组不能转换为指针。
236.获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。
237.在一个程序中,一个模板只能定义为导出一次。对类模板使用export更复杂一些。通常,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字export,如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。相反,应该在类的实现文件中使用export:也可以将类模板的个别成员声明为导出的,在这种情况下,关键字export不在类模板本身指定,而是只在被导出的特定成员定义上指定。导出成员函数的定义不必在使用成员时可见。任意非导出成员的定义必须像在包含模型中一样对待:定义应放在定义类模板的头文件中。
238.在类本身的作用域内部,可以使用类模板的非限定名。编译器不会为类中使用的其他模板的模板形参进行这样的推断
239.模板类只有在使用其模板类指针时,才会实例化该指针所指向的模板。非类型模板实参必须是编译时常量表达式。
240.任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。
241.异常对象必须是可复制的。不存在数组和函数类型的异常,因为会发生转换。
242.无论对象实际类型是什么,异常对象的类型都与指针的静态类型相匹配。如果该指针是一个指向派生类对象的基类类型指针,则该对象将被分割,只抛出基类部分。
243.一个块如果发生异常而退出,其动态分配的内存不会被释放。
244.析构函数发生了异常,将导致调用标准库terminate函数,从而导致调用abort函数,强制整个程序非正常退出。所以析构函数不应该抛出异常。构造函数发生异常,将导致部分的构造对象。
245.对于找不到匹配的catch异常,将调用terminate。
246.除下面几种可能的区别之外,异常的类型与catch说明符的类型必须完全匹配:

Conversionsfromnonconsttoconstareallowed.Thatis,athrowofanonconstobjectcanmatchacatchspecifiedtotakeaconstreference.
允许从非const到const的转换。也就是说,非const对象的throw可以与指定接受const引用的catch匹配。

Conversionsfromderivedtypetobasetypeareallowed.
允许从派生类型型到基类类型的转换。

Anarrayisconvertedtoapointertothetypeofthearray;afunctionisconvertedtotheappropriatepointertofunctiontype.
将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。

247.通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。
248.catch可以改变它的形参。在改变它的形参之后,如果catch重新抛出异常,那么,只有当异常说明符是引用的时候,才会传播那些改变。
catch(my_error&eObj){//specifierisareferencetype
eObj.status=severeErr;//modifiestheexceptionobject
throw;//thestatusmemberoftheexceptionobjectissevereErr
}catch(other_erroreObj){//specifierisanonreferencetype
eObj.status=badErr;//modifieslocalcopyonly
throw;//thestatusmemberoftheexceptionrethrownisunchanged
}

249.用捕获所有异常catch子句的。捕获所有异常的catch子句形式为
(...)。例如:
//matchesanyexceptionthatmightbethrown
catch(...){
//placeourcodehere
}

如果catch(...)与其他catch子句结合使用,它必须是最后一个,否则,任何跟在它后面的catch子句都将不能被匹配。
250.auto_ptr只能用于管理从new返回的一个对象,它不能管理动态分配的数组。
251.auto_ptr被复制或赋值的时候,有不寻常的行为,因此,不能将auto_ptrs存储在标准库容器类型中。
252.如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。
253.如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数unexpected。默认情况下,unexpected函数调用terminate函数,terminate函数一般会终止程序。
254.基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。这个限制保证,当使用指向基类类型的指针调用派生类虚函数的时候,派生类的异常说明不会增加新的可抛出异常。例如:
classBase{
public:
virtualdoublef1(double)throw();
virtualintf2(int)throw(std::logic_error);
virtualstd::stringf3()throw
(std::logic_error,std::runtime_error);
};
classDerived:publicBase{
public:
//error:exceptionspecificationislessrestrictivethanBase::f1's
doublef1(double)throw(std::underflow_error);

//ok:sameexceptionspecificationasBase::f2
intf2(int)throw(std::logic_error);
//ok:Derivedf3ismorerestrictive
std::stringf3()throw();
};

255.用另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同,但是,源指针的异常说明必须至少与目标指针的一样严格。
voidrecoup(int)throw(runtime_error);
//ok:recoupisasrestrictiveaspf1
void(*pf1)(int)throw(runtime_error)=recoup;
//ok:recoupismorerestrictivethanpf2
void(*pf2)(int)throw(runtime_error,logic_error)=recoup;
//error:recoupislessrestrictivethanpf3
void(*pf3)(int)throw()=recoup;
//ok:recoupismorerestrictivethanpf4
void(*pf4)(int)=recoup;

256.命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。不能在不相关的命名空间中定义成员。
257.可以用作用域操作符引用全局命名空间的成员。因为全局命名空间是隐含的,它没有名字,所以记号
::member_name

引用全局命名空间的成员。
258.未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体。
259.
namespaceblip{
intbi=16,bj=15,bk=23;
//otherdeclarations
}
intbj=0;//ok:bjinsideblipishiddeninsideanamespace
voidmanip()
{

//usingdirective-namesinblip"added"toglobalscope
usingnamespaceblip;
//clashbetween::bjandblip::bj
//detectedonlyifbjisused
++bi;//setsblip::bito17
++bj;//error:ambiguous
//globalbjorblip::bj?
++::bj;//ok:setsglobalbjto1
++blip::bj;//ok:setsblip::bjto16
intbk=97;//localbkhidesblip::bk
++bk;//setslocalbkto98
}


260.接受类类型形参(或类类型指针及引用形参)的函数(包括重载操作符),以及与类本身定义在同一命名空间中的函数(包括重载操作符),在用类类型对象(或类类型的引用及指针)作为实参的时候是可见的。
261.有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间。这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类(以及定义其基类)的每个命名空间,将那些命名空间中任意与被调用函数名字相同的函数加入候选集合。即使这些函数在调用点不可见,也将之加入候选集合。将那些命名空间中带有匹配名字的函数加入候选集合:
namespaceNS{
classItem_base{/*...*/};
voiddisplay(constItem_base&){}
}
//Bulk_item'sbaseclassisdeclaredinnamespaceNS
classBulk_item:publicNS::Item_base{};
intmain(){
Bulk_itembook1;
display(book1);
return0;
}

262.如果using声明在已经有同名且带相同形参表的函数的作用域中引入函数,则using声明出错
263.模板的显式特化必须在定义通用模板的命名空间中声明,否则,该特化将与它所特化的模板不同名。
264.构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。
255.用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。当一个类继承于多个基类的时候,那些基类之间没有隐含的关系,不允许使用一个基类的指针访问其他基类的成员。
266.当一个类有多个基类的时候,通过所有直接基类同时进行名字查找。多重继承的派生类有可能从两个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。虽然两个继承的print成员的二义性相当明显,但是也许更令人惊讶的是,即使两个继承的函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公用或受保护的,也是错误的。
267.无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。
268.因为new(或delete)表达式与标准库函数同名,所以二者容易混淆。
269.成员new和delete函数必须是静态的,因为它们要么在构造对象之前使用(operatornew),要么在撤销对象之后使用(operatordelete),
270.如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型;但是,如果指针实际指向派生类对象,则typeid操作符将说表达式的类型是派生类型。
271.如果指针p的值是0,那么,如果p的类型是带虚函数的类型,则typeid(*p)抛出一个bad_typeid异常;如果p的类型没有定义任何虚函数,则结果与p的值是不相关的。正像计算表达式sizeof一样,编译器不计算*p,它使用p的静态类型,这并不要求p本身是有效指针。
272.嵌套类只是引入了一个作用域。嵌套类定义了其外围类中的一个类型成员。
273.在看到在类定义体外部定义的嵌套类的实际定义之前,该类是不完全类型,应用所有使用不完全类型的常规限制。嵌套类可以直接引用外围类的静态成员、类型名和枚举成员,当然,引用外围类作用域之外的类型名或静态成员,需要作用域确定操作符。
274.
classOuter{
public:
structInner{
//ok:referencetoincompleteclass
voidprocess(constOuter&);
Inner2val;//error:Outer::Inner2notinscope
};
classInner2{
public:
//ok:Inner2::valusedindefinition
Inner2(inti=0):val(i){}
//ok:definitionofprocesscompiledafterenclosingclassiscomplete
voidprocess(constOuter&out){out.handle();}
private:
intval;
};
voidhandle()const;//memberofclassOuter
};

275.union的访问控制与struct相同,可以定义成员类,但是不能为基类,因此不能有虚函数成员。union不能具有静态数据成员或引用成员,而且,union不能具有定义了构造函数、析构函数或赋值操作符的类类型的成员:
unionillegal_members{
Screens;//error:hasconstructor
staticintis;//error:staticmember
int&rfi;//error:referencemember
Screen*ps;//ok:ordinarybuilt-inpointertype
};

像其他内置类型一样,默认情况下union对象是未初始化的。可以用与显式初始化简单类对象一样的方法显式初始化union对象。但是,只能为第一个成员提供初始化式。该初始化式必须括在一对花括号中。匿名union不能有私有成员或受保护成员,也不能定义成员函数。
276.局部类的所有成员(包括函数)必须完全定义在类定义体内部,因此,局部类远不如嵌套类有用。类似地,不允许局部类声明
static数据成员,没有办法定义它们。局部类只能访问在外围作用域中定义的类型名、static变量和枚举成员,不能使用定义该类的函数中的变量。外围函数对局部类的私有成员没有特殊访问权。
277.可以将一个类嵌套在局部类内部。这种情况下,嵌套类定义可以出现在局部类定义体之外,但是,嵌套类必须在定义局部类的同一作用域中定义。照常,嵌套类的名字必须用外围类的名字进行限定,并且嵌套类的声明必须出现在局部类的定义中。嵌套在局部类中的类本身是一个带有所有附加限制的局部类。嵌套类的所有成员必须在嵌套类本身定义体内部定义。
278.位域必须是整型数据类型。位是否压缩到整数以及如何压缩与机器有关。通常最好将位域设为unsigned类型。存储在signed类型中的位域的行为由实现定义。地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域也不能是类的静态成员。
279.当可以用编译器的控制或检测之外的方式改变对象值的时候,应该将对象声明为volatile。关键字volatile是给编译器的指示,指出对这样的对象不应该执行优化。volatile对象只能调用volatile
成员函数。不能使用合成的复制和赋值操作符从volatile对象进行初始化或赋值,理由是不能将volatile对象传递给普通引用或const引用。
280.链接指示不能出现在类定义或函数定义的内部,它必须出现在函数的第一次声明上。注意,花括号并没有定义一个作用域。可以将多重声明形式应用于整个头文件。
//compoundstatementlinkagedirective
extern"C"{
intstrcmp(constchar*,constchar*);
char*strcat(char*,constchar*);
}

有时需要在C和C++中编译同一源文件。当编译C++时,自动定义预处理器名字
__cplusplus(两个下划线),所以,可以根据是否正在编译C++有条件地包含代码。
#ifdef__cplusplus
//ok:we'recompilingC++
extern"C"
#endif
intstrcmp(constchar*,constchar*);

在C++程序中,重载C函数很常见,但是,重载集合中的其他函数必须都是C++函数:
classSmallInt{/*...*/};
classBigNum{/*...*/};
//theCfunctioncanbecalledfromCandC++programs
//theC++functionsoverloadthatfunctionandarecallablefromC++
extern"C"doublecalc(double);
externSmallIntcalc(constSmallInt&);
externBigNumcalc(constBigNum&);

可以从C程序和C++程序调用calc的C版本。其余函数是带类型形参的C++函数,只能从C++程序调用。声明的次序不重要。C函数的指针与C++函数的指针具有不同的类型,不能将C函数的指针初始化或赋值为C++函数的指针(反之亦然)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: