文章目录
一、派生类构造函数与基类构造函数
派生类的构造函数做了如下事情:
- 创建基类对象
- 派生类构造函数通过成员初始化列表将基类信息传递给基类构造函数
- 派生类构造函数初始化派生类新增的数据成员
二、创建与销毁派生类对象时,构造函数和析构函数的调用
创建派生类对象时
-
程序调用基类构造函数
-
初始化继承的数据成员
-
使用初始化器列表语法指明要使用的基类构造函数
Derived::Derived(type1 x, type2 y):Base(x,y) //初始化列表 { ... }
-
若无初始化器列表语法,则使用默认的基类构造函数
-
-
程序调用派生类构造函数
- 初始化新增的数据成员
销毁派生类对象时
- 程序调用派生类的析构函数
- 程序调用基类的析构函数
三、派生类和基类之间的特殊关系
-
基类指针可在不进行显式转换的情况下指向派生类对象
基类引用可在不进行显式转换的情况下引用派生类对象
基类指针或引用只能调用基类方法,不能调用派生类方法
Derived derived; Base *basePointer = &derived; Base &baseReference = derived;
一般要求引用和指针类型与赋给的类型相匹配,这一规则对继承来说是例外。
然而此例外是单向的,即不可以将基类对象和地址赋给派生类引用和指针。
Base base; //Derived *derived = &base; Not Allowed //Derived &derived = base; Not Allowed
-
派生类对象可以使用基类的方法,条件是此方法不是私有的
class Base{ private: int number; void privateMethod(){ } public: void publicMethod(){ } } class Derived: public Base{ } Derived derived; //derived.privateMethod(); Not allowed derived.publicMehod(); //Allowed
四、公有继承
(一)、何为公有继承
公有继承为 is-a-kind-of
关系,通常使用术语 is-a
。如从 Fruit
类中派生出 Banana
类,即应采用公有派生,因为 Banana is a kind of fruit
但公有继承不建立 has-a
关系,不建立 is-like-a
关系,不建立 is-implemented-as-a
关系,不建立 uses-a
关系。因此以下例子都不应该采用公有继承。
Fruit->Lunch
Shark->Lawyer
Array->Stack
Computer->Printer
(二)、多态公有继承
实现多态公有继承有两种方法:
- 在派生类中重新定义基类的方法
- 使用虚函数
以下例子将说明这两种方法的使用,以及它们的不同之处:
class Base {
private:
int number1;
public:
Base(int number1 = 0);
virtual void show1(){};
void show2(){}
virtual ~Base(){}
}
class Derived : public Base
{
private:
int number2;
public:
Derived(const Base &b,int number2 = 0);
Derived(int number1 = 0, int number2 = 0);
virtual void show1(){}
void show2(){}
}
int main()
{
Base base(0);
Derived derived(0,0);
//此处代码将演示第一种方法
//在派生类中重新定义基类的方法,使用对象类型确认使用哪个版本
//此处 base 和 derived 的类型不同
base.show(); //use Base::show()
derived.show(); //use Derived::show()
//此处代码将演示第二种方法,并说明虚函数与第一种方法的区别
//此处 base1 和 base2 的类型相同
Base &base1 = base;
Base &base2 = derived;
//使用了virtual,程序将根据引用或指针 **指向的对象** 去选择相应的方法
base1.show1(); //use Base::show1()
base2.show1(); //use Derived::show1()
//没有使用virtual,程序将根据 **引用类型** 或 **指针类型** 去选择相应的方法
base1.show2(); //use Base::show2()
base2.show2(); //use Base::show2()
return 0;
}
另外,注意到了,上述程序使用了虚析构函数。
如果析构函数不为虚,则将只调用对应于指针类型的析构函数。这意味着对于 base1
和 base2
,二者均只有 Base
的析构函数被调用,即使base2
指针指向的为一个Derived
对象。
如果析构函数为虚,则将调用对应于指针所指对象类型的析构函数。这意味着对于 base1
和 base2
,base1
会调用Base
的析构函数,而base2
会先调用Derived
的析构函数,再调用Base
的析构函数。
(三)、虚函数的工作原理
调用虚函数时:
- 程序将查看存储在对象中的vtbl(vitual function table)地址。vtbl 为虚函数表,是对象的一个隐藏成员里保存着的指针。
- 程序转向相应的函数地址表。
- 程序将使用函数地址表里的地址,执行具有该地址的函数。
如下图所示。
(四)、虚函数注意事项
-
在基类方法声明中,使用
virtual
关键字,可使该方法在基类以及所有派生类中是虚的。包括从派生类里面派生出来的类。 -
若使用指向对象的引用或指针来调用虚方法,则程序进行动态联编(亦称晚绑定),将使用为对象类型定义的方法,而不适用为指针或引用类型定义的方法。
通过晚绑定,基类指针或引用可以指向派生类对象。
-
如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。
-
以下内容一定要理解并记住
-
构造函数不可是虚函数
-
析构函数应当是虚函数,除非类不用做基类。但即使类不做基类,将类的析构函数声明为虚也并非错误,只不过可能牺牲效率
-
友元
friend
不能是虚函数。只有类成员才能是虚函数。而友元并不是类成员 -
若派生类没有重新定义函数,则将使用该函数的基类版本
-
在派生类里重新定义函数,并不是重载。此举将隐藏方法。
class Base{ private: int num; public: virtual void show(int i)const{} } class Derived: public Base{ public: virtual void show()const{} } Derived derived; derived.show(); //valid //derived.show(2); invalid
-
五、访问控制:protected
派生类可直接访问基类的保护成员,但不能直接访问基类的私有成员。
在类外,只能用公有类成员去访问protected
的类成员。
六、抽象基类
当类声明包含纯虚函数时,不能创建该类的对象。