大纲
继承的基础使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream>
using namespace std;
class Parent {
public: Parent(int a = 0, int b = 0) { this->a = a; this->b = b; }
void print() { cout << "a=" << this->a << ", b=" << this->b << endl; }
public: int a; int b; };
class Child : public Parent {
public:
Child(int a = 0, int b = 0, int c = 0) { this->a = a; this->b = b; this->c = c; }
void echo() { cout << "a=" << this->a << ", b=" << this->b << ", c=" << this->c << endl; }
private: int c; };
int main() { Child child(1, 2, 3); child.print(); child.echo(); return 0; }
|
程序运行的输出结果如下:
派生类的构造过程
派生类对象构造的过程:
- 派生类先调用基类的构造函数,初始化从基类继承来的成员。
- 派生类后调用自己的构造函数,初始化派生类自己特有的成员。
派生类对象析构的过程:
- 派生类先调用自己的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件等)。
- 派生类后调用基类的析构函数,释放派生类内存中从基类继承来的成员可能占用的外部资源(堆内存、文件等)。
总结
在 C++ 的类继承中,先构造的后析构,即后构造的先析构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <iostream>
using namespace std;
class Base {
public: Base(int data) : ma(data) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
protected: int ma;
};
class Device : public Base {
public: Device(int data) : Base(data), mb(data) { cout << "Device()" << endl; }
~Device() { cout << "~Device()" << endl; }
private: int mb;
};
int main() { Device d(2); return 0; }
|
程序运行的输出结果如下:
1 2 3 4
| Base() Device() ~Device() ~Base()
|
重载、重写、隐藏
重载关系
- 一组函数要重载,必须处在同一个作用域当中,且函数名字相同,但参数列表不同。
隐藏关系
- 在继承结构当中,派生类的同名成员会将基类的同名成员给隐藏掉,这里的隐藏是指作用域的隐藏。
重写关系
- 基类和派生类的函数,其函数名、返回值以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就会被编译器自动处理成虚函数,它们之间成为重写(覆盖)关系。
案例代码一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream>
using namespace std;
class Base {
public: Base(int data = 10) : ma(data) {
}
void show() { cout << "Base::show()" << endl; }
void show(int a) { cout << "Base::show(int)" << endl; }
private : int ma;
};
class Device : public Base {
public: Device(int data = 20) : Base(data), mb(data) {
}
void show() { cout << "Device::show()" << endl; }
private: int mb;
};
int main() { Device device; device.show(); device.Base::show(20); return 0; }
|
程序运行的输出结果如下:
1 2
| Device::show() Base::show()
|
案例代码二
特别注意
在 C++ 的继承结构中进行上下的类型转换时,默认只支持从下(派生类)到上(基类)的类型的转换,不支持从上(基类)到下(派生类)的类型的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include <iostream>
using namespace std;
class Base {
public: Base(int data = 10) : ma(data) {
}
void show() { cout << "Base::show()" << endl; }
void show(int a) { cout << "Base::show(int)" << endl; }
private : int ma;
};
class Device : public Base {
public: Device(int data = 20) : Base(data), mb(data) {
}
void show() { cout << "Device::show()" << endl; }
private: int mb;
};
void test01() { cout << "=========== test01() ===========" << endl; Base base; Device device; base = device; base.show(); base.show(30); }
void test02() { cout << "=========== test02() ===========" << endl; Base base; Device device;
Base *_base = &device; _base->show(); _base->show(40); }
int main() { test01(); test02(); return 0; }
|
程序运行的输出结果如下:
1 2 3 4 5 6
| =========== test01() =========== Base::show() Base::show(int) =========== test02() =========== Base::show() Base::show(int)
|
虚函数、静态绑定和动态绑定
静态绑定(不带虚函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <iostream> #include <typeinfo>
using namespace std;
class Base {
public: Base(int data = 10) : ma(data) {
}
void show() { cout << "Base::show()" << endl; }
void show(int num) { cout << "Base::show(int num)" << endl; }
private: int ma;
};
class Device : public Base {
public: Device(int data = 20) : Base(data), mb(data) {
}
void show() { cout << "Device::show()" << endl; }
private: int mb;
};
int main() { Device device(50);
Base *pb = &device; pb->show(); pb->show(20);
cout << sizeof(Base) << endl; cout << sizeof(Device) << endl;
cout << typeid(pb).name() << endl; cout << typeid(*pb).name() << endl;
return 0; }
|
程序运行的输出结果如下:
1 2 3 4 5 6
| Base::show() Base::show(int num) 4 8 class Base * class Base
|
动态绑定(带虚函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream> #include <typeinfo>
using namespace std;
class Base {
public: Base(int data = 10) : ma(data) {
}
virtual void show() { cout << "Base::show()" << endl; }
virtual void show(int num) { cout << "Base::show(int num)" << endl; }
private: int ma;
};
class Device : public Base {
public: Device(int data = 20) : Base(data), mb(data) {
}
void show() { cout << "Device::show()" << endl; }
private: int mb;
};
int main() { Device device(50);
Base *pb = &device; pb->show(); pb->show(20);
cout << sizeof(Base) << endl; cout << sizeof(Device) << endl;
cout << typeid(pb).name() << endl; cout << typeid(*pb).name() << endl;
return 0; }
|
程序运行的输出结果如下:
1 2 3 4 5 6
| Device::show() Base::show(int num) 16 16 class Base * class Device
|
查看类的内存布局
在 Visual Studio 开发人员命令提示窗口内,可以使用以下命令查看类的内存布局信息,其中 YYY
是类的名称,xxx
是 C++ 源文件的名称。
1
| cl /d1 reportSingleClassLayoutYYY xxx.cpp
|
- 在上述动态绑定(带虚函数)的代码中,Base 类的内存布局如下:
- 在上述动态绑定(带虚函数)的代码中,Device 类的内存布局如下:
虚函数的使用总结
(1) 当一个类里面定义了虚函数,那么在编译阶段,编译器会给这个类的类型产生一个唯一的 vftable
虚函数表,虚函数表中主要存储的内容是 RTTI
指针和虚函数的地址(如图所示)。当程序运行时,每一张虚函数表都会加载到内存的 .rodata
区。
(2) 当一个类里面定义了虚函数,那么这个类定义的对象在其运行时,内存中开始的部分会多存储一个 vfptr
虚函数指针(占 4 字节大小),它指向相应类型的虚函数表 vftable
。一个类定义 N 个对象,它们的 vfptr
虚函数指针指向的都是同一张虚函数表。
(3) 如果派生类中的函数和从基类继承来的某个函数,其函数名、返回值、参数列表都相同,而且基类的函数是 virtua1
关键字修饰的,那么派生类的这个函数会被编译器自动处理成虚函数。
(4) 一个类里面虚函数的个数,不影响类对象的内存大小(vfptr
虚函数指针永远只占用 4 个字节大小),影响的是虚函数表的大小。