面向对象程序设计
1. 类型转换与继承
2. 虚函数
1. 虚函数与虚函数列表
含有(纯)虚函数的类会有一个虚函数列表。虚函数列表是一个指针数组,里面存放的是指向虚函数的函数指针。类的每一个对象都会有一个虚函数列表指针,指向类的虚函数列表。
2. 多重继承时的虚函数列表
3. 动态绑定
class Person {
public:
Person() { cout << "--Created person." << endl; }
virtual void id() { cout << "This is a person." << endl; }
virtual ~Person() { cout << "--Released person." << endl; }
};
class Man :public Person {
public:
Man() { cout << "--Created man." << endl; }
void id() { cout << "This is a man." << endl; }
~Man() { cout << "--Released man." << endl; }
};
void testP(Person* p) { p->id(); } // 用基类指针调用所指对象实际类型中定义的函数。
void testR(Person& p) { p.id(); } // 用基类引用调用所引对象实际类型中定义的函数。
void testO(Person p) { p.id(); } // 不用指针和引用只能调用形参类型中定义的函数。
int main() {
{
Man m1 = Man(); // 基类构造函数、派生类构造函数。
Person *p = &m1;
p->id(); // 派生类函数。
m1.id(); // 派生类函数。
testP(&m1); // 派生类函数。
testR(m1); // 派生类函数。
testO(m1); // 基类拷贝构造函数、基类函数、基类析构函数。
// 派生类析构函数、基类析构函数。
}
{
Man* m2 = new Man();// 基类构造函数、派生类构造函数。
m2->id(); // 派生类函数。
testP(m2); // 派生类函数。
testR(*m2); // 派生类函数。
testO(*m2); // 基类拷贝构造函数、基类函数、基类析构函数。
delete m2; // 派生类析构函数、基类析构函数。
}
{
Person* m3 = new Man(); // 基类构造函数、派生类构造函数。
m3->id(); // 派生类函数。
testP(m3); // 派生类函数。
testR(*m3); // 派生类函数。
testO(*m3); // 基类拷贝构造函数、基类函数、基类析构函数。
delete m3; // 基类析构函数。/派生类析构函数、基类析构函数。
}
{
Person m4 = Man(); // 基类构造函数、派生类构造函数、基类拷贝构造函数、派生类析构函数、基类析构函数。
m4.id(); // 基类函数。
// 基类析构函数。
}
}
使用多态
1. 形参为基类指针或引用:通过形参调用的是实参实际类型中定义的函数(第15、16行)。
2. new创建的派生类对象赋值给基类指针:通过该基类指针调用的是派生类中定义的函数(第39、40行)。
3. 基类指针指向派生类对象:通过该基类指针调用的是派生类中定义的函数(第22行)。
上面三种情况本质是一样的:通过基类指针或引用调用的是对象实际类型中定义的函数。
注意
多态三要素:虚函数或纯虚函数、继承、指针或引用。缺少任一个都不行。如第47行没有指针/引用,调用的是类型说明符Person中定义的函数。
派生类中重写的虚函数可以是virtual void id() override;也可以简化为void id();
多态的实现原理
- 类的虚函数列表vtbl是一个虚函数指针数组,元素是指向虚函数的指针。每个对象包含一个虚函数列表指针*__vptr,指向类的虚函数列表。
- 对Base* b = new Derive();来说,基类指针b指向的是派生类Derive的匿名对象d的内存地址,这块内存中的虚表指针指向的是派生类Derive的虚表。于是通过基类指针b调用的是派生类Derive中的虚函数(重写的Base的虚函数)。
- 对Base b = Derive();来说,因为基类对象b是由基类Base的拷贝构造函数创建的,拷贝构造函数拷贝派生类Derive的匿名对象d的数据时并没有拷贝d的虚表指针。所以基类对象b的虚表指针指向的是基类Base的虚表(这符合对象的虚表指针指向类的虚表原则)。于是通过基类对象b调用的是基类Base中的虚函数。
【多态与虚函数】【多态与指针/引用】
虚函数与虚析构函数
delete指向派生类对象的基类指针时(第44行),基类的析构函数须是虚函数才调用派生类的析构函数,否则只调用基类的析构函数。delete其它类型的指针,派生类和基类的析构函数都会被调用。
3. 访问控制与继承
1. 控制对象
访问控制是对类成员的控制,包括构造函数、析构函数、静态成员函数。友元不是成员函数,所以不受访问控制限制。
2. 访问控制与重写
无论(纯)虚函数在基类中的访问说明符是什么,也无论继承方式是public、protected还是private,派生类中重写的函数的访问控制都以派生类中的访问说明符为准。
3. public 继承时,基类成员的可见性
标题行(第一行)是基类成员的访问说明符。
public继承 | public | protected | private |
---|---|---|---|
基类对象 | √ | × | × |
基类函数 | √ | √ | √ |
基类的友元函数 | √ | √ | √ |
派生类函数 | √ | √ | × |
3. 继承方式对基类成员可见性的影响
横标题行是基类成员的访问说明符,纵标题列是继承方式,内容部分是基类成员在派生类中的访问控制类型:
继承方式 | public | protected | private |
---|---|---|---|
public | public | private | inaccessible |
protected | protected | protected | inaccessible |
private | private | private | inaccessible |