赋值兼容性原则:
子类对象可以当做父类对象使用;
子类对象可以直接赋值给父类对象;
子类对象可以直接初始化父类对象;
父类指针可以直接指向子类对象;
父类引用可以直接引用子类对象;
#include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; class Parent { protected: const char* name; public: Parent() { name = "Parent"; } void print() { cout<<"name = "<<name<<endl; } }; class Child: public Parent { private: int i; public: Child(int i) { this->name = "Child"; this->i = i; } }; int main(int argc, char** argv) { Child child(100); Parent parent = child; Parent* pp = &child; Parent& rp = child; parent.print(); pp->print(); rp.print(); return 0; }
输出:
name = Child
name = Child
name = Child
继承对象模型:
类在C++编译器的内部可以理解为结构体:
子类是由父类成员叠加子类新成员得到的:
Parent: char* name;
Child: char* name + int i;
如何初始化父类成员?
父类与子类的构造函数有什么关系?
在子类对象构造的时候需要调用父类构造函数,对其继承得来的成员进行初始化:
比如上面的Child char* name 由Parent的构造函数进行初始化,
而int i 由Child的构造函数进行初始化;
同样,在子类对象析构的时候需要调用父类析构函数对其继承得来的成员进行清理;
继承与构造和析构
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行结束后,执行子类的构造函数
当父类的构造函数有参数时,需要在子类的初始化列
表中显示调用
析构函数调用的先后顺序与构造函数相反
//子类对象在创建时会首先调用父类的构造函数 //父类构造函数执行结束后,执行子类的构造函数 //当父类的构造函数有参数时,需要在子类的初始化列 //表中显示调用 //析构函数调用的先后顺序与构造函数相反 #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; class Parent { int i; public: Parent(int i) { cout<<"Parent()"<<endl<<"i="<<i<<endl; } ~Parent() { cout<<"~Parent()"<<endl; } }; class Child: public Parent { public: Child():Parent(4) { cout<<"Child()"<<endl; } ~Child() { cout<<"~Child()"<<endl; } }; void func() { Child child; } int main(int argc, char** argv) { func(); return 0; }
输出为:
Parent()
i=4
Child()
~Child()
~Parent()
--------------------------------
Process exited with return value 0
Press any key to continue . . .
继承与组合的混搭:
类中的成员变量可以是其他类的对象;
如果一个类继承自父类并且有其它的对象作
为成员,那么构造函数如何调用?
口诀:先父母,后客人,再自己;
#include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; class Object { public: Object(const char* s) { cout<<"Object(const char* s)"<<" "<<s<<endl; } }; class Parent: public Object { int i; public: Parent(const char*s):Object(s) { cout<<"aParent(const char*s)"<<" "<<s<<endl; } }; class Child: public Parent { protected: Object o1; Object o2; public: Child():o2("ss"),o1("dd"),Parent("Parameter from Child!")//这里先父母,所以先执行Parent("Parameter from Child!"),但是Parent也有父母,所以第一个执行 //Object(const char* s),执行完Object再执行Parent,再朋友,初始化o1,o2,初始化顺序按照类对象的声明顺序,所以先o1再o2,最后 自己Child(); { cout<<"Child()"<<endl; } }; void func() { Child child; } int main(int argc, char** argv) { func(); return 0; } /* 输出: Object(const char* s) Parameter from Child! aParent(const char*s) Parameter from Child! Object(const char* s) dd Object(const char* s) ss Child() 继承与组合的混搭 类中的成员变量可以是其它类的对象。 问题 如果一个类继承自父类并且有其它的对象作 为成员,那么构造函数如何调用? 口诀:先父母,后客人,再自己。 2014年9月2日22:52:50 同名成员变量 思考 当子类中定义的成员变量与父类中 的成员变量同名时会发生什么? 当子类成员变量与父类成员变量同名时 子类依然从父类继承同名成员 在子类中通过作用域分别符::进行同名成员区分 同名成员存储在内存中的不同位置 子类对象可以当作父类对象使用 子类对象在创建时需要调用父类构造函数进行初始化 子类对象在销毁时需要调用父类析构函数进行清理 先执行父类构造函数,再执行成员构造函数 在继承中的析构顺序与构造顺序对称相反 同名成员通过作用域分辨符进行区分 */
同名成员变量思考:
当子类中定义的成员变量与父类中定义的成员变量同名的时候会发生什么?
当子类成员变量与父类成员变量同名时
子类依然从父类继承同名成员
在子类中通过作用域分别符::进行同名成员区分
同名成员存储在内存中的不同位置
class Parent { protected: int i; int f; }; class Child: public Parent { protected: int i; void f() { cout <<"Parent::i = "<<Parent::i<<endl; //通过域名操作符来区分,一个类就相当于一个namespace cout <<"Child::i = "<<Child::i<<endl; cout <<"Parent::f = "<<Parent::f<<endl; } public: Child(int i, int j) { Parent::i = i; Child::i = j; Parent::f = i+j; f(); } }
小结
子类对象可以当作父类对象使用
子类对象在创建时需要调用父类构造函数进行初始化
子类对象在销毁时需要调用父类析构函数进行清理
先执行父类构造函数,再执行成员构造函数
在继承中的析构顺序与构造顺序对称相反
同名成员通过作用域分辨符进行区分