C++ 类之间的纵向关系-继承
目录
继承的基本概念
定义
使用方法
内存空间
继承下构造·析构执行的顺序
构造函数
继承的基本概念
定义
被继承的类叫做基类(父类),继承的类叫派生类(子类),在子类类名后面加:继承方式 父类
class CFather {};
class CSon :public CFather {};
使用方法
通过继承关系,子列可以使用父类的成员。如果子类和父类有同名的成员,默认使用子类的成员,如果想要使用父类成员,需要在成员名前加上类名::用于显式的指定区分。
父类:
class CFather {
public:int m_fa;int m_money;void funFather() {cout << __FUNCTION__ << endl;}void fun() {cout << "CFather::fun" << endl;}
};
子类:
class CSon :public CFather {
public:int m_money;CSon() {m_fa = 1;CFather::m_money = 100;//父类的m_moneyCSon::m_money = 200;//子类的m_moneym_money = 300; //默认使用的是子类的成员}void funSon() {cout << m_fa << endl;funFather();}void fun() {cout << "CSon::fun" << endl;}};
测试:
创建一个子类对象,通过子类成员函数调用父类的成员属性,在子类中的构造函数中也可给父类的成员属性初始化值。当子类与父类有同名成员时,可以通过类名作用域来区分,默认使用的是当前类对象的成员。
int main()
{CSon son;son.funSon();cout << son.CFather::m_money << endl; //100cout << son.CSon::m_money << endl; //300son.fun();//默认子类son.CFather::fun();//父类son.CSon::fun();//子类return 0;
}
内存空间
子类继承父类,相当于父类的成员包含到自己的类里,所以定义子类对象所占用的空间大小除了子类自身的成员,还包括父类的成员。成员在内存空间分布为:先父类成员后子类成员,而每个类中的成员分布与在类中声明的顺序一致。
内存大小:
cout << sizeof(CFather) << " " << sizeof(CSon) << endl; //8 12
内存分配:
CSon son;cout << &son << endl;//00D4FC6Ccout << &son.m_fa << " " << &son.CFather::m_money << " " << &son.m_money << endl;//00D4FC6C 00D4FC70 00D4FC74
继承下构造·析构执行的顺序
构造函数
执行的顺序:父构造->子构造(函数体代码)
构造顺序说明:在子类创建对象的时候,会先跳转到子类的构造函数(注意这里并不是直接先执行父类的构造函数),先执行构造的初始化参数列表,初始化顺序看在类中声明的先后顺序(等同于成员在内存排布的先后顺序),由于父类在前,子类成员在后,所以先初始化父类成员,调用父类的构造函数。初始化完毕后,再回到子类初始化参数列表,按照顺序继续初始化其他成员,所有成员初始化完毕后,再执行子类自己的构造函数体代码。
注意1:在初始化列表中会默认调用父类的无参构造初始化父类成员,如果父类只有带参数的构造,在子类的初始化列表中必须显示的指定父类构造进行初始化。这有点像之前说的组合关系形式。
注意2:不能在子类构造函数的初始化参数列表中直接对父类中的成员属性初始化,更不能在子类构造函数的函数体代码中初始化(实际是赋值操作),因为如果在创建对象时要创建父类对象,那么将不会调用子类的构造函数,而父类中又没有构造函数。
默认调用父类无参构造:
class CFather {
public:int m_fa;CFather() :m_fa(3) {cout << __FUNCTION__ << endl;}};class CSon :public CFather {
public:int m_son;CSon():/*CFather(),编译器会默认调用父类的无参构造*/m_son(1)/*,m_fa(0)*/ {m_fa = 0;//赋值操作cout << __FUNCTION__ << endl;}
};
手动显示指定调用父类的构造:
class CFather {
public:int m_fa;CFather() :m_fa(3) {cout << __FUNCTION__ << endl;}};class CSon :public CFather {
public:int m_son;CSon() :CFather(),/* 手动显示指定调用父类的构造 */m_son(1) {cout << __FUNCTION__ << endl;}
};
父类中没有无参构造时,必须手动显式指定出来:
class CFather {
public:int m_fa;CFather() :m_fa(3) {cout << __FUNCTION__ << endl;}
};class CSon :public CFather {
public:int m_son;CSon(int a) :CFather(a), m_son(1) {}
};
如果父类中存在多个构造函数,想使用带参数的构造函数,也需要手动指定,否则会调用无参的父类构造了
class CFather {
public:int m_fa;CFather() :m_fa(3) {cout << __FUNCTION__ << endl;}CFather(int a) :m_fa(a) {cout << __FUNCTION__ << endl;}
};class CSon :public CFather {
public:int m_son;CSon(int a) :CFather(a), m_son(1) {}
};
析构函数
执行的顺序:子析构->父析构
析构顺序说明:子类对象的生命周期结束后,因为是子类所以自动调用子类析构,当析构执行完了,才会回收对象分配的空间(和构造的顺序相反),当然这个空间包含创建的父类成员,那么回收父类匿名对象前,会自动调用父类的析构,再回收父类对象本身。如果是new出来的子类对象,同理。
class CTest {
public:int m_tst;
public:CTest(int a) :m_tst(a) {cout << __FUNCTION__ << endl;}~CTest() {cout << __FUNCTION__ << endl;}};class CFather {
public:int m_fa;
public:CFather(int a) :m_fa(a) {cout << __FUNCTION__ << endl;}~CFather() {cout << __FUNCTION__ << endl;}
};class CSon :public CFather {
public:CTest tst;int m_son;
public:CSon(int a) :tst(a + 2), CFather(a + 1), m_son(a) {cout << __FUNCTION__ << endl;}~CSon() {cout << __FUNCTION__ << endl;}};
int main() {CSon son(1);return 0;
}
上三行为构造函数,下三行为析构函数,构造函数和析构函数执行的顺序相反。
继承的优点
可以将一些功能比较相似的类中公共的成员,抽离出来形成一个类,即是一个父类,这些子类继承父类,可以使用父类成员,公共的成员在父类中只维护一份代码即可,如果要添加新的公共的成员,只需要在父类中添加一份即可。增加新的子类公共的成员不必再重复写了,增加了程序代码的复用性、扩展性、灵活性,减少代码冗余。
class CPeople {
public:int m_money;CPeople():m_money(100){}void cost(int n) {cout << __FUNCTION__ << endl;m_money -= n;}void play() {cout << "溜溜弯" << endl;}void drink() {//如果增加公共的功能,属性,只需要在父类中增加一份即可cout << "喝饮料" << endl;}
};class CWhite : public CPeople{public://CWhite():m_money(100){}void eat() {cout << "吃牛排" << endl;}};class CYellow : public CPeople {
public:void eat() {cout << "小烧烤" << endl;}};class CBlack : public CPeople {
public:void eat() {cout << "西瓜炸鸡" << endl;}};class CBlue : public CPeople {
public:void eat() {cout << "吃另一个阿凡达" << endl;}
};