第16课 - 继承中的构造与析构
一.赋值兼容性原则
1.1 子类对象可以当作父类对象使用
1.2 子类对象可以直接赋值给父类对象
1.3 子类对象可以直接初始化父类对象
1.4 父类指针可以直接指向子类对象
1.5 父类引用可以直接引用子类对象
Source Example1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { protected: const char *name; public: Parent() { name = "parent"; } void print() { printf ("name = %s\n", name); } }; class Child : public Parent { protected: int i; public: Child(int i) { this->name = "child"; this->i = i; } }; int main(int argc, char** argv) { Child child(1000); /* 并没有执行父类提供的构造函数,而是执行了编译器提供的拷贝构造函数,进行了赋值的操作 */ Parent parent = child; Parent*pp = &child; Parent& rp = child; /* 都打印了child */ parent.print(); pp->print(); rp.print(); return 0; }
二.继承对象模型
2.1 类在C++编译器内部可以理解为结构体
2.1.1 子类是有父类成员叠加子类新成员得到的
问题:如何初始化父类成员?
父类与子类的构造函数有什么关系?
a.在子类对象构造的时候需要调用父类构造函数对其继承的来的成员进行初始化
子类构造函数只需要初始化新增的成员即可。
b.在子类对象析构的时候需要调用父类析构函数对其继承的来的成员进行析构
子类析构函数只需要析构新增的成员即可。
Source Example2.1.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { public: Parent(char *s) { printf("Parent()\n"); printf("%s\n", s); } ~Parent() { printf("~Parent()\n"); } }; class Child : public Parent { public: Child() :Parent("Parameter from child!") { printf("Child()\n"); } ~Child() { printf("~Child()\n"); } }; void run() { /* 构造时,先调用父类的构造函数,在调用子类的构造函数 */ Child child; /* 析构时,先调用子类的析构函数,再调用父类的析构函数 */ } int main(int argc, char** argv) { run(); return 0; }
输出结果如下:
2.2 小总结
2.2.1 子类对象在创建时会首先调用父类的构造函数
2.2.2 父类构造函数执行结束后,执行子类的构造函数
2.2.3 当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
Source Example 2.2.3: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { public: Parent(char *s) { printf("Parent()\n"); printf("%s\n", s); } ~Parent() { printf("~Parent()\n"); } }; class Child : public Parent { public: /* 初始化列表的具体使用方法见第9课 */ /* 会将Parameter from child!作为实参传递给Parent的构造函数 */ Child():Parent("Parameter from child!") { printf("Child()\n"); } ~Child() { printf("~Child()\n"); } }; void run() { /* 构造时,先调用父类的构造函数,在调用子类的构造函数 */ Child child; /* 析构时,先调用子类的析构函数,再调用父类的析构函数 */ } int main(int argc, char** argv) { run(); return 0; }
2.2.4 析构函数的调用先后顺序与构造函数相反
三.继承与组合的混搭
3.1 组合回顾: 类中的成员变量可以是其它类的对象
问题: 如果一个类继承自父类并且有其它的对象作为成员,那么构造函数如何调用?
口诀: 先父母,后家人,再自己
Source Example3.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Object { public: Object(const char *s) { printf ("Object() %s\n", s); } ~Object() { printf ("~Object()\n"); } }; class Parent : public Object { public: /* 构造该调用函数使,会先调用Parent父类的构造函数Object的构造函数 */ Parent(const char *s) : Object(s) { printf("Parent()"); printf("%s\n", s); } ~Parent() { printf("~Parent()\n"); } }; class Child : public Parent { protected: /* 会根据调用顺序来调用成员变量的构造函数 */ /* 即先调用o1的构造函数 */ Object o1; Object o2; public: /* 调用顺序与初始化列表中的定义顺序毫无关系 */ Child():o2("o2"), o1("o1"), Parent("Parameter from child!") { printf("Child()\n"); } ~Child() { printf("~Child()\n"); } }; void run() { /* * 构造时,先调用父类的构造函数 * 再调用成员变量的初始化函数 * 最后调用自己的构造函数 */ Child child; } int main(int argc, char** argv) { run(); return 0; }输出结果如下:
3.2 问题: 当子类中定义的成员变量的名字与父类的成员变量的名字同名时会发生什么?
(最好不要使用同名变量,可读性不好)
答案:
3.2.1 子类依然从父类继承同名成员
3.2.2 在子类中通过作用于分别符::进行同名成员区分
3.2.3 同名成员存储在内存中的不同位置
Source Example 3.2: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { protected: int i; int f; }; class Child : public Parent { protected: int i; void f() { /* 输出1 */ printf("Parent::i = %d\n", Parent::i); /* 输出2 */ printf("Child::i = %d\n", Child::i); /* 输出3 */ printf("Parent::f = %d\n", Parent::f); } public: Child(int i, int j) { Parent::i = i; Child::i = j; Parent::f = i + j; /* 默认调用自己的f函数 */ f(); } }; void run() { Child child(1,2); } int main(int argc, char** argv) { run(); return 0; }