1、对象的动态分配有两种版本:
1.1 分配特定类型的单个对象,例:int *pint=new int(1024);
1.2 分配特定类型和维数的数组,但没办法给每个元素显示地指定一个初始值,例:int *pia=new int[4];
1.3 对应的其删除形式为:delete pint;delete [] pia;
2、将一个类设计成基类,要考虑找出类型相关的成员函数,并将这些成员函数记为virtual
3、派生类对象的构成:每个基类是一个类的子对象,由基类的构造函数初始化;派生类的构造函数应该只初始化那些在派生类中被定义的数据成员,而不是基类中的数据成员。
4、派生类为什么需要自己的构造函数?
4.1 派生类没有继承基类的构造函数(以及析构函数及拷贝赋值操作符);
4.2 派生类要有一个接口把必要的参数传递给基类的构造函数。
5、虚函数不能定义成内联,因为内联发生在编译时刻,而虚函数是在运行时刻处理的。
6、基类、派生类、成员类构造顺序?
基类---》成员类---》派生类
7、文字常量不可寻址
8、字符串文字的类型是常量字符数组:由字符串文字本身以及编译器加上的表示结束的空字符组成。
9、宽字符串文字的类型是常量宽字符的数组:有一个等价的宽空字符作为结束标志。
10、每个变量都有两个值与其相关联:数据值,又叫右值;地址值,又叫左值。文字常量不能作为左值。一般地,赋值操作符的左边总是要求一个左值。
11、对象的声明(extern):在一个头文件中声明该对象,然后再需要声明该对象的时候包含这个头文件。
12、指向c风格的字符串的字符指针总是指向一个相关联的字符数组
13、const对象的地址只能赋值给指向const 对象的指针,但指向const对象的指针可以被赋以一个非const 对象的地址。
14、注意const指针(类型 * const p)与指向const对象的指针(const 类型 * p)的区别
15、引用
15.1 引用的所有操作实际上都是应用在它所指的对象身上,包括取地址符。
15.2 ?引用会不会引起额外内存的分配,引用是如何实现的?
15.3 const引用可以用不同类型的对象初始化(只要能从一种类型转换到另一种类型即可),也可以是不可寻址的值,如文字常量
15.4 而非const引用则不能做上述的初始化,原因在于上述初始化需要一个临时变量去存放不同类型的值或不可寻址的值,然后引用指向这个临时变量。如果该引用是非const的,那么修改这个引用的值
就是修改临时变量的值,而对于原值没有影响。对于用户来说对这个引用的修改无效,所以从更好的角度来设计,只有const引用才能指向临时变量。
16、 const int ival=1024; &ival类型是const int *,如果定义&ival的引用,const int * const & ref=&ival;因为&ival是个常量值,只有常引用才能这样初始化。
17、枚举类型:不能直接打印枚举成员的实际名,一种解决方法是定义一个由枚举成员的值索引的字符串数组;不能使用枚举类型进行迭代,不支持在枚举成员之间的前后移动。
18、c++没有引用数组(即由引用组成的数组)
19、vector的元素被初始化为与其类型相关的缺省值,算术和指针类型的缺省值是0
20、typedef char * cstring, extern const cstring cstr; 这时cstr是什么类型? 应该是char * const cstr而不是const char * cstr;因为typedef与宏定义不同
21、volatile限定修饰符:提示编译器该对象的值可能在编译器未监测到的情况下被改变,因此编译器不能武断地对引用这些对象的代码作优化处理
22、判断指针是否为空且指针所指向的对象是否为空的常用写法是:ptr&&*ptr
23、二元关系操作符,其左右操作数的计算顺序在标准C++中未定义。例:if(ia[index++]<ia[index])
24、sizeof操作符:应用在指针类型上的sizeof返回的是包含该地址所需的内存长度,例sizeof(short *)==4;应用在引用上sizeof返回的是被引用对象所需的内存长度,例sizeof(short&)==2;
sizeof(数组名)==数组所占内存。sizeof(string)==12;
25、逗号表达式:从左往右计算,最右边表达式的值是最终的值。
26、if语句,在condition中定义的对象,只在与if相关的语句或语句块中可见。
27、switch语句中关键字case后面的值必须是一种整数类型的常量表达式。
28、把一条声明语句放在与case 或default相关联的语句中是非法的,除非被放在一个语句块中。
29、do while循环的条件部分不支持声明语句,而while和for都支持。
30、break语句终止最近的while、do while、for或switch语句。
31、continue导致最近的循环语句的当前迭代结束,执行权被传递给条件计算部分。
32、当不希望一个类的对象用另一个同类的对象赋值或者初始化,可将其拷贝构造函数和拷贝赋值操作符声明为私有成员且不提供定义。
33、简单类与复杂类:复杂类指该类提供了一个拷贝构造函数和一个拷贝赋值操作符。
34、无论对于list还是vector,简单类对象通过按位拷贝插入,复杂类对象通过调用拷贝构造函数来插入。对于小的数据类型,vector的性能比list好,而对于大型的数据类型(包括大型简单类的对象)list的性能要好。
一种解决方案是用vector存放指向大型数据类型的指针。
35、容器中的每个元素都被初始化为“与该类型相关联的缺省值”
36、适用于容器的自定义类型:元素类型必须支持等于操作符,元素类型必须支持小于操作符,元素类型必须支持一个缺省值。
37、对于遍历const容器,需要用const_iterator类型。
38、iterator的算术运算只适用于vector或deque。对于list,ilist.begin()+2是不对的。
39、map里定义了一个类型value_type,表示相关键值对:map<string,int>::value_type(string("cms"),1)创建了一个对象,可以直接插入map
40、栈类型被称为容器适配器,因为它抽象在底层容器集上,默认实现是deque,也可以这样指定:stack<int,list<int> > intStack;栈的元素是按值拷贝的。
41、函数定义可以用作声明,在程序中定义只能一次,但声明可以多次。一般把声明(以及内联函数的定义)放在头文件中
42、c++是一种强类型语言,每个函数调用的实参在编译期间都要经过类型检查。函数声明为编译器提供了必须的信息
43、引用参数使用情况:当需要改变实参时;需要向主调函数返回额外的结果;向函数传递大型类对象;
44、引用参数与指针参数的区别:引用参数的实参不能为0,引用参数用于重载操作符时,比用指针跟直观。
45、数组参数:数组长度与参数声明无关,这几种情况等价:int *与 int [] 与 int [10],数组长度不是参数类型的一部分
46、数组的引用:当参数被声明为数组的引用时,数组长度成为参数类型的一部分,int (&arr)[10],编译器会检查实参的长度是否与参数类型指定的长度一致。
47、多维数组:编译器会检查除了第一维之外的所有维的长度。
48、设计带默认参数的函数,使最可能取用户指定值的参数先出现,而最可能使用默认参数的放在后面。默认参数一般是在函数声明中提供的。一个参数只能在一个文件中被指定一次缺省实参。函数后继的声明可以指定其他参数的默认值。默认参数值可以是任意表达式。
49、函数返回引用两个常见错误:一是返回局部变量的引用,二是对引用的无意识修改,例如:int & get_val() ; get_val()++; 为防止如此,应该将返回值声明为const int & get_val();
50、inline函数的定义一般放在头文件中,在每个调用这个函数的文件中调用该头文件。
51、指向函数的指针:函数类型只和返回类型,参数类型有关,省略号是函数类型的一部分,int printf(const char *,…)和 int strlen(const char *)是不同的类型。
52、函数名是该类型函数的指针,对函数名取地址也一样。二者等价。在函数指针的初始化与赋值之间,不存在隐式类型转换,必须完全匹配。可以用0来赋值或初始化,表示不指向任何函数。用函数指针调用函数与用函数名调用,没有区别。
53、函数指针可以作为函数返回值类型,函数类型不能作为函数返回值类型。函数类型不能作为函数参数,将被自动转换为函数类型的指针。
54、全局对象和非inline全局函数在一个程序内只能定义一次,inline函数可以定义多次,符号常量也可以被定义多次。
55、类型安全链接:把函数的参数和数目编到函数名中。c++中的函数重载就是通过这种机制实现的。
56、头文件中不应该含有非inline函数或对象的定义。inline函数和符号常量是可以被定义多次的。
57、符号常量:在程序编译期间,在可能的情况下,符号常量的值会代替名字的出现。这种情况叫做常量折叠。如果不可能做到常量折叠,最好将初始化移到文本文件中,在头文件中用extern声明。例如:
/头文件/ extern char * const bufp; /cpp文件/ char * const bufp=new char[max]; 如果初始化放在头文件中,那么在每个包含它的头文件都将被定义,这不仅浪费空间,且可能与作者意图不符。
58、符号常量是任意的const 对象。
59、静态局部对象只初始化一次。
60、delete表达式会测试指针是否为0,所以不需要判断指针是否为0,例如:if(pi) delete pi; 这句没有必要判断。
59、auto_ptr是c++标准库提供的类模板,可用来自动管理用new表达式动态分配的单个对象(不支持数组)。
其定义有三种形式:
auto_ptr<type> p1(new type());
auto_ptr<type> p2(p1);//所有权由p1转到p2,p1不再指向源对象
auto_ptr<type> p3;
所有权发生转移也发生在赋值上,例:p1=p2;p1原来的对象被删除,p1指向p2的对象。p2不再指向。
测试auto_ptr是否为0的办法:if(p_auto_ptr.get()!=0),用get办法返回对象内部的底层指针。
auto_ptr 指针没有指向一个对象,使其指向一个的办法是:p_auto_ptr.reset(xxx);
使用注意:不能指向一个不是由new动态分配的内存;不能用同一个指针初始化两个auto_ptr对象;不能用get操作赋值给auto_ptr对象,例auto_ptr<string> pstr_auto(pstr_auto.get());应改为release();
60、空闲存储区分配常量对象:例: const int * pi=new const int(2048);//必须被初始化
61、定位表达式:new(address) type;//将对象放入指定内存中,此内存之前已经分配好,定位表达式不分配内存。
62、名字空间的定义是可以累积的,名字空间的定义可以非连续,同一个名字空间的定义可以跨越几个不同的程序文本文件。名字空间可以嵌套。
63、名字空间成员定义:一、在名字空间内部定义;二、在名字空间内部声明,在外部定义。对于在外部定义函数,函数的返回类型要加修饰,参数类型不需要。例如:
namespace cplusplus{
class matrix{}
Matrix operator+(const matrix& m1,const matrix& m2);
}
cplusplus::Matrix cplusplus::operator+(const matrix& m1,const matrix& m2)
{}
64、作为名字空间的函数和对象,一般将其声明放在头文件中,实现放在其他文件中。声明对象时要加extern,声明函数可加可不加。
65、匿名名字空间:声明一个局部于某一文件的实体,其他文件不可用。例如:
namespace {
void swap(){}
}
与
static void swap(){}效果等价。
66、using声明:与其他方式声明的效果一样。s
67、using指示符:相当于在全局域定义的。using namespace xxx;
68、函数重载:参数不同(个数或类型)则构成重载;如果参数一样,返回类型不同,则表示声明出错。
传值类型的参数,const和volatile不作为函数声明的判断,对于引用和指针类型,则会判断。例如:
void f(int )和 void f(const int)是同一个声明。
void f(int *)和void f(const int *) 不是同一个声明。
voiid f(int &) 和void f(const int&) 声明了同一个函数。
69、重载函数的集合中的全部函数都应在同一个域中声明,不论这声明是用using声明还是using指示符。一个声明为局部的函数将隐藏而不是重载全局域中声明的函数。例如:
namespace blip {
void print()
{
std::cout<<"cms"<<std::endl;
}
}
void print(int)
{
std::cout<<"xll"<<std::endl;
}
int main(int argc, const char * argv[])
{
using blip::print;//在局部域声明print,会导致print(1)出错
//using namespace blip;//等价与在全局域声明print(),可以调用print(1)
print(1); //error
}
70、候选函数:在调用点上可见的函数及在实参类型所在的名字空间中声明的同名函数。
71、模板参数表:模板参数可以是模板类型参数也可以是模板非类型参数(代表了一个常量表达式)。
在函数模板定义中声明的对象或类型不能与模板参数同名。
typename和class 在定义模板类型参数时可以混用。
72、函数模板可以被声明为inline或extern,但应该将指示符放在模板参数表后面。
73、告诉编译器一个表达式是类型表达式的机制是加上关键字typename,例如:
param::name * p;//搞不清楚这是一个指针声明还是一个乘法
typename param::name *p;//这是一个指针声明。
74、函数模板的实例化:在被调用或取地址时实例化。例:
template<typename type,int size>
type min(type (&arr)[size]){}
int (* pf)(int (&)[10])=&min;//这时模板也会实例化。
75、模板实参推演:函数实参类型与函数参数类型不必完全匹配,下列转换都可以:左值、限定修饰转换、从派生类到基类类型的转换。
76、显式模板实参:对于模板实参不能推演的情况,可以显示指定实参,对于可以推演出来的,可以省略显示实参;
template<class T1,class T2,class T3>
T1 sum(T2 op1,T3 op2){}
sum<int,char>(x,y);//T1为int型
77、c++模板编译模式:包含模式 和 分离模式
包含模式:模板定义放在头文件中,在用到的地方包含头文件。何时实例化取决于具体的编译器。
分离模式:函数模板的声明放在头文件中,组织方式同非inline函数。但定义时需要export一次,一个函数模板只能export一次,并不是所有的编译器都支持分离模式。
78、模板显式实例化的必要性:虽然模板可能会实例化多次,但最终只有一个。实例化多次的结果增加了编译时间。
对于给定的模板,程序只能出现显式实例化的声明一次,且所在的文件要模板的定义。
79、模板显示特化:某种类型不能用通用模板来实例化。
一个程序中对于同样的实参不能存在通用模板实例化和特化两种版本。显示特化的声明应该放在头文件中。
函数模板的显示特化声明必须被声明为该通用模板被定义的名字空间内。
80、函数模板可以被重载,函数模板可以与一个普通非模板函数同名。当函数模板实例化与普通函数同时满足实参时,优先选择普通函数。
例如:template<class T> T min(T,T); int min(int,int)
当 min(3,5)时,优先调用普通函数。
64、try块中声明的对象不能在catch语句中使用。
extern void no_problem() 表示可以抛出任何异常;
被抛出的异常类型与异常规范中指定的类型之间不允许类型转换。
例如:
int convert(int ) throw (string)
{
if(xxx)
throw "help";//error 应该抛出string("help");
}
异常规范与函数指针:用作初始值的异常规范必须比用作左值的函数指针更严格,即右值的异常规范比左值更少。
例如:void recoup(int ,int) throw(exceptionType)
void no_problem() throw()
void doit(int,int) throw (string,exceptionType);
void (*pf1) (int ,int) throw(string) =&no_problem;
82、每个类定义引入一个不同的类类型,即使两个类类型具有完全相同的成员表。
83、除了静态数据成员外,数据成员不能在类体中被显式的初始化。
84、友元函数不受限定修饰符(public、protected、private)的影响。
85、类声明和类定义:只有类已经被定义了,才能声明一个数据成员为该类的对象;否则,只能声明该类类型的指针或引用。例如:
class screen; //声明
class stackScreen{
screen* stack;
};
同理,一个类只能定义指向自身类型的指针或引用作为数据成员,但不能有一个自身类型的数据成员,因为此时类定义没有完成。
86、基类的引用或指针可以引用到派生类的对象。
87、每个类对象都有自己的类数据成员拷贝,但每个类成员函数的拷贝只有一份。
88、在类体中定义的函数自动作为inline函数处理。
89、没有在类体中定义的内联成员函数,其定义必须放在类定义出现的头文件中。
普通内联函数的定义也应该放在头文件中。
90、不修改类对象的函数应该声明为const成员函数,一个const类对象只能调用被声明为const的成员函数(除了构造和析构之外)。
const成员函数可以被相同参数表的非const成员函数重载,例如:
char get(int) {}
char get(int) const{}
此时,类对象的常量性决定了调用哪个函数。
91、对于一个const对象的数据成员,可以声明为mutable,这样函数可以修改其值,否则const对象只能调用const函数,不能修改任何数据成员。
92、静态数据成员:其初始化不应该放在头文件中,因为同全局变量一样只能提供一个定义。
93、静态数据成员的类型可以是其所属类,非静态成员类型只能是该类的对象或引用。
class Bar{
static Bar mem1;//ok
Bar * mem2;//ok
Bar mem3;//error
}
94、静态数据成员可以被作为类成员函数的缺省实参,非静态成员不行。
class Foo{
int var;
static int stcvar;
int mem1(int =var);//error
int mem2(int =stcvar);//ok
}
95、静态成员函数只能访问静态数据成员。
96、普通函数指针不能被赋值为成员函数的地址
97、指向类成员的指针,必须通过特定的对象或指向某对象的指针来访问。例如:
int (Screen::*pmfi)()=&Screen::height;
Screen myScreen,*bufScreen;
(mySceen.*pmfi)();//调用
(bufScreen->*pmfi)();//调用,bufScreen必须指向有效的对象
98、对于静态类成员的指针,与普通的指针用法一样;例如:
class{
public:
static double interest(){...}//
};
double (* pf) ()=&Account::interest;
pf();//调用
union的成员可以被声明为公有、私有或者保护的。
一个类类型定义了构造函数、析构函数或拷贝赋值操作符,则它不能成为union的成员类型。
union不能有静态数据成员或者引用成员。
可以为union定义成员函数包括构造和析构函数。
匿名union:其数据成员可以在定义匿名union的域中被直接访问,匿名union不能有私有或保护的成员,也不能定义成员函数。
在全局域中定义的匿名union必须被声明在未命名的名字空间中(或者被声明为static)。
100、类域:
在类体中,后声明的成员不能被先声明的成员声明使用。
但有两种例外情况:用在inline成员函数定义中的名字;
函数声明(即函数返回类型和参数表)在其所在的类定义中出现的位置被处理,函数体在完整的类域中被处理----所有的成员的声明都被看到。
另一种是被用作缺省实参的名字(需是静态成员,非静态成员不能用作缺省实参)。
如果类成员的定义在类体之外,则跟在被定义的成员名后面的程序,直到该成员定义线束,都被认为是在类域中。如果是在被定义的成员名字之前的程序文本,不在该类的域内。例如:
class Account{
typedef double Money;
private:
static Money _interestRate;
static Money initInterest();
}
//Money 必须限定修饰,而initInterest()不需要。
Account::Money Account::_interestRate=initInterest();
101、类域中的名字解析
用在类定义中的名字(除上述两种例外情况之外),其解析过程为:
(1)在名字使用之前出现的类成员的声明
(2)如果过程1不成功,则在类定义之前的名字空间域中出现的声明
用在类成员函数定义中的名字解析过程:
1、成员函数局部域
2、1不成功,则考虑所有的类的成员声明
3、2不成功,则考虑成员函数定义之前的名字空间域出现的声明。
用在类静态成员定义中的名字解析过程 :
1、考虑所有类成员的声明;
2、1不成功,则考虑出现在静态数据成员定义之前的名字空间域中的声明,而不只是出现在类定义之前的声明。
外围类只有被声明为嵌套类的友元,才能访问嵌套类的私有成员;同理,嵌套类访问外围类的私有成员也需要声明为友元。
嵌套类不能直接访问其外围类的非静态成员,需要通过外围类对象的指针、引用或对象等。但可以直接访问外围类的静态成员数据,类型名、枚举值等。
103、在嵌套类域中的名字解析:
1、考虑出现在名字使用点之前的嵌套类的成员声明;
2、如果1不成功,则考虑出现在名字使用点之前的外围类的成员声明。
3、如果2不成功,则考虑出现在嵌套类定义之前的名字空间域中的声明。
如果在全局域中,在外围域之外定义嵌套类,则外围类的所有成员都 已经被声明完毕,编译器将考虑其所有声明。
104、域的概念
不能用const和volatile关键字声明。
缺省情况下,单参数构造函数或有多个参数,但除第一个参数外,其他都有缺省实参的,可被用作转换操作符。explicit修饰符(只能用在构造函数上)可以通知编译器不提供隐式转换。
106、当在一个函数内删除一个独立的堆对象时,最好是用auto_ptr类对象,而不是一个实际的指针。
107、显式的析构调用:不会造成对应区域内存的回收,但delete时,会调用对应类型的析构函数。如果delete的地址不对应类类型,则不会调用任何析构函数。
108、类对像的vector初始化:要经历临时对象的创建、拷贝、析构等,比对象数组初始化代价要大,一般做插入用,即创建时vector为空。
109、构造函数:分为俩个阶段,一个是初始化阶段(包括隐式的和显示的,取决于是否存在初始化表),另一个是计算阶段(即括号内的赋值运算),
初始化顺序跟成员在类中被声明的顺序有关,跟初始化表顺序无关。
110、const成员和引用成员必须用初始化表。
109、堆数组的初始化:
111、拷贝赋值操作符:应防止自身的拷贝,因为可能会先释放该对象的当前资源
112、怎样决定把一个操作符声明为类成员还是名字空间成员:操作符的左操作数必须是其他类型,那么重载操作符必须是名字空间成员。
113、赋值(=)、[]、()、->操作符必须为类成员操作符。()参数表可以有任意数目的参数。
114、转换函数:operator type();type不能表示数组或函数类型。转换函数必须是成员函数。它的声明不能指定返回类型和参数表。
显式的强制类型转换会导致调用转换函数。
115、对于一个函数调用的实参是一个类类型的对象、类类型的指针、类类型的引用或是指向类成员的指针,候选函数为:在调用点可见的函数,在定义该类的名字空间中声明的函数,以及在类成员表中声明为友元的函数。
116、成员函数重载:成员函数只能在类成员表中声明一次,不像名字空间成员可以声明多次。重载成员函数集可以包含静态和非静态成员函数。即使成员函数没有通过指针和对象的方式去调用(即为静态成员函数方式),非静态成员函数也会包含在可行函数集中。
例如:
class MyClass{
public;
static void mf (int); // a
char mf (char); // b
};
int main()
{
char cobj;
MyClass::mf (cobj);//会优先选择 b,虽然b不符合调用方式
}
对于const对象,只有静态成员函数和const的非静态函数可以作为可行函数。
117、操作符的重载解析:
候选函数:1、调用点可见的
2、操作数类型定义的名字空间中的操作符集合。
3、操作数类型的友元
4、左操作数的类中声明的成员操作符的集合
5、内置的操作符集合。
118、类模板:模板参数表中的参数不能为空,可以为类类型参数,也可以是非类类型参数,非类类型参数代表一个常量表达式。
声明一个类模板实例的指针或引用不会引起类模板的实例化。
119、类模板的成员函数本身也是一个模板,只有在被调用或者取地址时才被实例化。当类模板被实例化时,类模板的成员函数并不自动被实例化,只有当一个成员函数被程序用到时(函数调用或取地址)时,才被实例化。
120、类模板的友元声明:友元类,友元普通函数,友元成员函数(这需要成员函数的类已经定义);
121、按种类可以分为三种:非模板友元、绑定的友元模板和非绑定的友元模板;
122、静态数据成员的模板定义必须出现在类模板定义之外,只有当程序使用静态数据成员时,才会从模板定义中被实例化。
123、对于类模板的公有的嵌套类型,一般的程序只能引用该嵌套类型的一个实例,嵌套类型的名字前必须要加上类模板实例的名字。
124、成员模板:只有当成员模板在程序中使用时,才会被实例化。任何成员函数都可以被定义为成员模板,构造函数也可以。
125、可导出的类模板:分离编译模式下,类模板的定义和其内联成员函数的定义都放在头文件中,非内联成员函数和静态数据成员放在文本文件中,当非内联成员函数的模板定义在使用时不可见,需要把类模板声明为可导出的,对于可导出的类模板
,他的成员函数实例或静态数据成员实例在使用时,编译器只需要类模板的定义。 export template<class type> …
126、也可以将类模板中的某个成员声明为导出。
127、类模板特化:成员函数的显示特化定义不能放在头文件中,要放在文本文件中。只有当通用的类模板被声明后,显示特化才可以定义。整个类模板显示特化,必须为其所有成员函数提供定义。类通用模板不会给特化版本提供定义。
128、类必须被定义好,才能作为基类。
129、基类与派生类的同名成员函数不会构成一个重载函数集,因为属于不同的域。可以用using声明来解决,例如:
class A:public B
{
void play();
using B::play;//将基类中的函数引到A的域中。
}
130、派生类不能访问另一个独立的基类对象的protected成员。
131、基类指针只能访问在该类中被声明(或继承)的数据成员和成员函数,包括虚拟函数,而与它可能指向的实际对象无关,把一个成员函数声明为虚拟的,只是推迟了“在程序执行期间根据pq指向的实际类型,对于要调用的实例
的解析过程”
132、友元关系不能继承。
133、构造函数的调用顺序:基类构造函数、成员类对象构造函数、派生类构造函数。析构的顺序与之相反。
134、当一个成员函数非虚拟的时候,通过一个类对象(指针或引用)而被调用的成员函数,就是该类对象静态类型定义的成员函数。当一个成员函数是虚拟的时候,通过一个类对象(指针或引用)调用,是该类对象动态类型中定义的成员函数。一个类对象的静态类型和动态类型
是相同的,虚拟函数机制只在使用指针和引用时才会起作用。
135、包含一个或多个的纯虚拟函数的类为抽象基类,不能实例化。
136、派生类的虚拟函数要改写基类的虚拟函数实例,原型必须相同,或者派生类实例的返回值可以是基类实例返回类型的公有派生类型。
例如: class A{
public:
virtual A print(){}
};
class B:public A{
virtual B printf(){};//同样构成改写。
};
137、虚拟函数的静态调用:
class A{
public:
virtual A * print(int a=50) =0;
};
class B:public A{
public:
virtual B * print(int a=100)
{
A::print();
return this;
}
};
A * A::print() //纯虚函数可以定义
{
cout<<"A"<<endl;
return this;
}
B b;
A * pa=&b;
pa->A::print();//调用A中的print
138、虚拟函数和缺省实参:通过指针或引用调用虚拟函数时,缺省实参是指针或引用的类型中指定的,例如:
B * pb=new B;
A * pa=pb;
pa->print();//缺省实参为50,调用B中的函数
pb->print();//缺省实参为100,调用B中的函数
139、虚拟函数承接了调用者所属类型的访问级别。例如:
class A{
protected:
void print();
};
class B:public A{
public:
virtual void print();
};
int main(int argc, const char * argv[])
{
A * pa=new B;
pa->print();//error,print为protected级别
return 0;
}
140、多继承:基类构造函数被调用的顺序以类派生表中声明的顺序为准,析构时相反。
141、多继承下虚拟函数的调用:
class A;
class B;
class C:public A,public B
A * pa=new C;//pa只能访问A,C共有的虚拟函数
B * pb=new C;//pb只能访问B、C共有的虚拟函数
删除时,效果相同,delete pa或是delete pb,都会调用所有类的虚拟析构函数
142、免除个别成员的私有继承的影响:
class B:private A
{
using A::print;
}
143、编译器总是先解析一个类成员,例如:
class A{
int dval;
}
int dval;
int A::sum(int ival)
{
return ival+dval;//dval被解析成A中的dval
}
144、虚拟基类:共享的基类子对象
class A:public virtual C ;
class B:public virtual C;
class D:public A,public B;
C就是虚拟基类,虚拟基类的初始化由最终派生类执行,中间类不再调用虚拟基类的构造函数,即D会调用C的构造,而A、B不再调用。
145、存在虚拟基类情况下的构造和析构:
先虚拟基类构造,再非虚拟基类构造,当然虚拟基类构造由最终派生类执行
146、特化的派生类实例优先级高于共享的虚拟基类实例。例如:
class C//虚拟基类,同144
{
print();
}
class A{
print();
}
D.print();//调用A中的print
如果
class B{
print();
}
则会造成二义
147、RTTI操作符:
dynamic_cast 可以转换指针,也可以转换引用
A * pa=dynamic_cast<A *>(pb);//如果转换失败,pa为null
A& a=dynamic_cast<A&>(b);//如果转换失败,会抛出异常
typeid用于获取一个表达式的类型,当操作数是一个类类型时,如果操作数类带有虚拟函数,则将返回实际指向的对象类型,否则返回操作数类的类型,例如:
class A{
virural xxx;
}
class B:public A{
}
B b;
A& a=b;
typeid(a).name();//class B
如果不存在虚拟函数,则结果为class A;
注意:typeid(指针)时,返回的是指针的类型,不会返回指针实际指向的对象指针类型,例如:
A * pa=new B;
typeid(pa).name();//返回A *
typeid(* pa).name();//返回class B
148、异常和继承:
异常对象:异常处理中,存储异常临时对象的区域,它保证会持续到异常被处理完毕
抛出的异常总是在throw表达式中指定确切的类型,例如:
A * pa=new B;//A是B的基类
throw * pa; //异常对象的类型是A,而不是B
149、处理类类型的异常:当异常被组织成类层次结构时,类类型异常可能会被该类类型的公有基类catch子句捕获,所以在catch子句列表中最特化的catch子句必须先出现。
150、throw表达式重新抛出的是原来的异常对象,如果被基类接受,会被部分的处理,基类接收的是子类的部分拷贝,然后重新抛出原来的对象,例如:
try{
throw B;
}
catch( A a)
{
//部分的处理了当前异常,a接收的是B部分值的拷贝
throw;//重新抛出了B的对象
}
151、异常对象的销毁:一个异常对象直到该异常最后一个catch子句退出时,才被销毁。
152、异常对象和虚拟函数:如果被抛出的异常对象是派生类类型的,并且被针对基类的catch子句处理,则catch语句一般不能用派生类类型的特性。除非定义为指针或引用调用虚拟函数的形式。
153、栈展开:在函数调用链中查找catch子句的过程。
154、异常规范:基类中虚拟函数的异常规范可以与派生类改写的成员函数的异常规范不同,但派生类的异常规范要更严格。
当异常规范指定一个类类型或类类型的指针,则可以抛出从该类公有派生的类类型的异常对象或类指针。
155、函数try块:将整个函数包括在try中
156、iostream库:#include<iostream>
iostream库的文件流组件:#include<fstream>(已经包含了iostream的文件)
iostream库的内存输入输出:#include<sstream>(已经包含了iostream的文件)
157、string流:
ostreamstring:可以借助输出操作符将各种类型数据转换成相应的字符串表示
ostreamstring format_message;
format_message<<"ival: "<<ival<<endl;
string msg=format_message.str();
istreamstring:由string对象构造,可以将string对象中数值字符串转换为算术值 。