一.构造函数和成员初始化列表
1.派生类对象存储了基类的数据成员(派生类继承了基类的实现:数据成员和方法)派生类可以使用基类的方法,条件是方法不是私有的。
派生类对象可以使用基类的方法(派生类继承了基类的接口)
2.派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。派生类构造函数必须使用基类构造函数。
3. C++使用成员初始化列表来在派生类构造函数之前创建基类构造函数,构造函数不能被继承。
例如:derived(type1 x,type2 y) :base(x,y)
其中derived是派生类,base是基类,x和y是基类构造函数使用变量。除虚基类外,类只能值传递回相邻基类,但后者可以使用相同机制将信息传递给相邻基类。如果没有在成员初始化列表提供基类构造函数,将使用默认基类构造函数。注:成员初始化列表只能用于构造函数。
4.如果定义了某种构造函数,编译器将不会定义默认构造函数。如果程序没有使用(显式或隐式)复制构造函数,编译器将提供原型,但不提供函数定义:否则,程序将定义一个执行成员初始化的复制构造函数,新对象每个成员被初始化为原始对象相应成员值,如果成员为类对象,则将使用相应类的复制构造函数。一些情况成员初始化不合适:new初始化的成员指针通常要求执行深度复制,或者类可能包含需修改的静态变量,需要定义自己的复制构造函数。-
二.基类指针和引用
1.基类指针或基类引用可以在不进行显示类型转换情况下指向派生类对象或引用派生类对象。
2.然而基类指针或引用只能用于调用基类方法,不能用来调用派生类方法。这是因为派生类继承了基类的方法,只调用基类方法二者都i有,但若是调用派生类方法代表为基类使用派生类方法,这是非法的。
3.不可以将基类对象和地址赋给派生类引用和指针。
4.引用兼容性可以将基类对象初始化为派生类对象
例:b olaf1(1840,”olaf”);
a olaf2(olaf1) (a是b的基类)
要初始化olaf2,要有构造函数:a(const a&) 但类中没有。
但类中存在隐式复制构造函数:a(consta&)形参是基类引用,因此它可以引用派生类,换句话说它将olaf2初始化为嵌套在b对象olaf1中的a对象。
同样也可以将派生对象赋给基类对象:a winner; winner=olaf1;
在这种情况下将隐式重载赋值运算符:a&operator=(const a&)const。
同样这种情况只是将olaf1基类部分复制给winner。
三.虚方法
1.使用virtual关键字,如果基类与派生类中有相同的方法,如果没有virtual将会根据引用类型或指针类型选择方法
例:a a1(“something”);
b b1(“something”)
a&pa1=a1;
a&pa2=b1;
pa1.c()//use a::c();
pa2.c()//use a::c();
因c没有virtual且pa1,pa2都为a的引用因此使用a的c方法。如果c是虚函数,那么pa1.c()指向a的c方法。而pa2.c是调用b的c方法。而且方法在基类中被声明为虚的后,它在派生类中将自动变成虚方法,但也可以在派生类的方法上加上virtual。
2.如果析构函数是虚的,将调用相应对象类型的析构函数。因此,个指针指向b对象,将调用b的析构函数,然后自动调用基类的析构函数。因此使用虚析构函数确保正确的析构函数序列1被调用。
3.编译器处理虚函数方法:给每个对象添加一个隐藏成员,成员中保存了一个指向函数地址数组的指针。这个数组称为虚函数表,存储了为类对象进行声明的虚函数地址。例如基类包含一个指针,该指针指向基类中所有虚函数的地址表。派生类包含一个指向独立地址表的指针。如果派生类提供虚函数新定义,该虚函数表将保存新函数地址。如果没有则保留函数原始版本地址。无论类包含多少虚函数,只是表大小不同。
四.静态联编和动态联编
1.将源代码中的函数调用解释为执行特地函数代码块被称为函数名联编。在编译过程中进行联编称为静态联编,又称早期联编。但编译器必须生成在程序运行时选择正确的虚方法代码,称为动态联编,也称晚期联编。
2.派生类引用或指针转换为基类引用或指针被称为向上强制转换,向上强制转换是可传递的也就是说如果b派生出c,则a指针可以指向a,b,c对象。
将基类指针或引用转换为派生类指针或引用称为向下强制转换,如果不显式类型转换,则这是不允许的。
3.隐式向上强制转换可以使基类指针或引用指向基类对象或派生类对象。编译器对非虚方法使用静态联编,对虚方法使用动态联编。静态联编效率更高,因此被设置为c++默认选择。调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。
4.构造函数不能是虚函数,析构函数应当是虚函数,除非类不用做基类.给类定义一个虚析构函数并非错误,即使这个类不用做基类,这只是效率方面问题。
5.友元不能是虚函数,友元不是类成员,只有成员才能是虚函数。如果由于这个问题引起了设计问题,可以通过让友元使用虚成员函数解决。如果要派生类友元使用基类友元函数,可以通过强制类型转换将派生类引用或指针转换为基类指针或引用。
6.如果派生类没有重新定义函数,将使用函数的基类版本,如果派生类位于派生链中,将使用最新的虚函数版本。如果基类声明被重载了,则应在派生类中重新定义所有的基类版本,如果只重新定义一个版本,则另外的版本将被隐藏,派生类对象无法使用他们。如果不需要修改,则新定义可只调用基类版本。
7.重新定义继承的方法并不是重载,如果重新定义派生类中的函数,将不只是使用相同的函数参数列表覆盖基类声明,无论参数列表是否相同,该操作将隐藏所有同名基类方法。这引出两条经验规则:第一.如果重新定义继承的方法,应确保与原来原型完全相同,返回类型是基类引用指针或引用,则可以修改为指向派生类的引用或指针,这种特性被称为返回类型协变,这种只适用于返回值而不适用于参数。
五.protected
1.对于外部世界,保护成员行为与私有成员相似,对于派生类来说,这和公有成员相似。
2.最好对类数据成员采用私有访问控制,同时通过基类方法使派生类能访问基类数据。
六.转换
1.使用1个参数就可以调用的构造函数定义了从参数类型到类类型的转换
例:a(const char);
a(const s&,int member=1);
a a1;
a1=’aaaa’;
将可转换的类型传递给以类为参数的函数时,将调用转换构造函数。第四句调用a::operator=(const a&)函数,使用a::a(const char)生成一个a对象,该对象被用作上述赋值运算符函数参数,这里假设没有定义将char*赋值给a的赋值运算符。
2.在一个带参数的构造函数原型中使用explicit将禁止进行隐式转换,但仍允许显式转换,c++11支持将关键字explicit用于转换函数。