C++之 继承 (inheritance)

目录

启示

一、基本语法

二、继承的方式

三种:

公共基础 / 保护继承 / 私有继承

三、继承中的对象模型

①父类中所有非静态成员属性都会继承给子类

②而私有成员属性同样继承过去,但是被编译器隐藏,因此无法访问

四、继承中构造和析构顺序

构造的顺序:      父类 > 子类

析构的顺序:        子类 > 父类

五、继承同名成员处理方式

六、继承同名静态成员处理方式

七、多继承

八、菱形继承


启示

首先,对于一个学校,他有自己的学院/部门,学院下有自己的专业,如图

继承就是承接上一级的内容,同时延续自己下一级的内容


一、基本语法

class 子类 : 继承方式 父类
{}

子类又称派生类 ,体现了个性

父类又称基类,体现了共性

继承方式有public/private/protected


二、继承的方式

三种:

公共基础 / 保护继承 / 私有继承

他们有如下关系

 显而易见的,父类中的private,无论是哪种继承方式,子类都不可访问

接下来我们使用代码进行测试


首先是public公共继承

class B :public A
{
public:void func(){a = 1;// 父类中公共权限成员,到子类中仍然是公共权限成员b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员c = 3;// 父类中私有权限成员,不可访问}
};

 c不可访问 

 同时,带上测试函数

 因为b是保护权限成员,只能在类内访问,类外不可访问


然后是protected保护继承

class B :protected A // 保护继承
{
public:void func(){a = 1;// 父类中公共权限成员,到子类中变成是保护权限成员b = 2;// 父类中保护权限成员,到子类中仍然是保护权限成员c = 3;// 父类中私有权限成员,不可访问}
};

 c不可访问

接下来是测试函数

可以看到,由于a/b是保护权限成员,因此类外都不能访问 


最后是private私有继承

class B :private A // 私有继承
{
public:void func(){a = 1;// 父类中公共权限成员,到子类中变成是私有权限成员b = 2;// 父类中保护权限成员,到子类中仍然是私有权限成员c = 3;// 父类中私有权限成员,不可访问}

c不可访问 

测试函数:

 同样的,由于a/b是私有权限成员,因此类外都不能访问 


三、继承中的对象模型

①父类中所有非静态成员属性都会继承给子类

②而私有成员属性同样继承过去,但是被编译器隐藏,因此无法访问

class Base // 父类
{
public:int a;
protected:int b;
private:int c;
};
class Son :public Base
{
public:int d;
};
void test01()
{cout << sizeof(Son) << endl;
}


接下来我们使用vs的开发人员命令提示符工具展示真实的分布情况

①打开 vs2022的开发人员命令提示符工具

② 跳转盘符:输入F:,点击回车

③再接着输入 cd 具体路径, 点击回车

 ④接下来输入 cl d1 reportSingleClassLayout子类名 文件名

文件名可以输入首字符,然后按一下Tab,就会自动补全

 可以看到子类Son父类Base,总Size是16。

子类包含父类的abc变量和自身的d


四、继承中构造和析构顺序

子类继承父类后,创建子类对象,也会调用父类的构造函数

那么构造和析构的顺序是什么?


下面创建父类Base子类Son,并添加各自的构造和析构函数

class Base // 父类
{
public:Base(){cout << "父类构造函数" << endl;}~Base(){cout << "父类析构函数" << endl;}
};
class Son :public Base // 子类
{
public:Son(){cout << "子类构造函数" << endl;}~Son(){cout << "子类析构函数" << endl;}
};
void test01()
{Son s;
}

 因此,


五、继承同名成员处理方式

当父类和子类出现同名对象或函数


 创建父类Base子类Son,并在2者种加入属性m_A,在测试函数test01中尝试输出m_A

class Base
{
public:Base(){m_A = 50; }int m_A;
};
class Son :public Base
{
public:Son(){m_A = 100;}int m_A;
};

 直接输出,是子类同名成员

若要输出父类同名成员,要加上作用域


 同理,如果要输出同名成员函数,也是要加父类的作用域

class Base
{
public:Base(){m_A = 50; }void func(){cout << "Base_func" << endl;}int m_A;
};
class Son :public Base
{
public:Son(){m_A = 100;}	void func(){cout << "Son_func" << endl;}int m_A;
};
void test02()
{Son s;s.func();s.Base::func();
}


 同时,如果子类出现了和父类的同名函数,子类将会隐藏掉父类中所有的同名函数,如下

class Base
{
public:Base(){m_A = 50; }void func(){cout << "Base_func" << endl;}void func(int a) // 同名函数,而且重载{cout << "Base_fun(int a)" << endl;}int m_A;
};
class Son :public Base
{
public:Son(){m_A = 100;}	void func(){cout << "Son_func" << endl;}int m_A;
};

子类中有func函数,而父类也有func函数以及其重载函数func(int a

此时即使子类没有同名重载函数,也不能直接调用重载函数,因为被隐藏

需要加父类的作用域 

 


六、继承同名静态成员处理方式

与同名成员相同的处理方式


父类Base与子类Son,都有静态成员static m_a; 

class Base
{
public:static int m_a; // 类内声明
};
int Base::m_a = 10; // 类外初始化
class Son :public Base
{
public:static int m_a;
};
int Son::m_a = 5;

接下来使用2种方式访问:①通过对象访问②通过类名访问

void test01()
{// 通过对象访问Son s;cout << s.m_a << endl;cout << s.Base::m_a << endl;// 通过类名访问cout << Son::m_a << endl;cout << Base::m_a << endl;cout << Son::Base::m_a << endl;
}

 值得注意的是,Son::Base::m_a ,意思是,

Son::通过类名访问Base::作用域下的m_a数据

而Base::m_a是直接通过类名访问m_a


同样的,如果要访问静态成员函数,也是加作用域或不加作用域

class Base
{
public:static int m_a;static void func(){cout << "Base的静态成员函数" << endl;}
};
int Base::m_a = 10;
class Son :public Base
{
public:static int m_a;static void func(){cout << "Son的静态成员函数" << endl;}
};
int Son::m_a = 5;
void test02()
{// 通过对象访问Son s;s.func();s.Base::func();// 通过类名访问Son::func();Base::func();Son::Base::func();
}


 同理,如果父类中出现了同名静态成员函数的重载,也会被隐藏,在调用时必须要加父类的作用域


七、多继承

语法:

class 子类: 继承方式 父类1,继承方式 父类 2......

不建议使用


例:

class Base1 // 父类1
{
public:Base1(){m_a = 100;}int m_a;
};
class Base2 // 父类2
{
public:Base2(){m_b = 200;}int m_b;
};
//class 子类: 继承方式 父类1,继承方式 父类 2......
class Son :public Base1, public Base2
{
public:Son(){m_c = 300;m_d = 400;}int m_c;int m_d;
};

使用Son类创建对象,并查看其大小

void test01()
{Son s;cout << sizeof(s) << endl;
}

4个int,大小16

 使用开发人员命令提示符也可以看到包含父类1和父类2的两个int,以及自己的2个int,一共是16


而当父类中出现同名成员时,不可以直接输出 

class Base1 // 父类1
{
public:Base1(){m_a = 100;}int m_a;
};
class Base2 // 父类2
{
public:Base2(){m_a = 200;}int m_a;
};
//class 子类: 继承方式 父类1,继承方式 父类 2......
class Son :public Base1, public Base2
{
public:Son(){m_c = 300;m_d = 400;}int m_c;int m_d;
};

可以看到,父类1和父类2都有m_a

尝试输出时,会提示不明确,因为重名了

因此,需要加作用域


八、菱形继承

 

代码:

class car // 父类 车类
{
public:int price;
};class pure_gasoline_car:public car // 纯汽油汽车
{};
class pure_electric_vehicle:public car // 纯电动汽车
{};
class hybrid_electric_vehicle :public pure_gasoline_car, public pure_electric_vehicle //混合动力汽车
{};

而在测试函数中,不可直接调用任何一个的price

void test01()
{hybrid_electric_vehicle c;c.price = 10;
}

必须要加上作用域 

void test01()
{hybrid_electric_vehicle c;c.pure_gasoline_car::price = 10;c.pure_electric_vehicle::price = 20;cout << c.pure_gasoline_car::price << endl;cout << c.pure_electric_vehicle::price << endl;
}

打印输出

但是,菱形继承导致数据有2份,造成资源浪费

使用开发人员命令提示符工具查看

 

确实会有2份

接下来,使用虚继承解决 

	//				在public前加上virtua,变为虚继承
class pure_gasoline_car:virtual public car // 纯汽油汽车
{};
class pure_electric_vehicle:virtual public car // 纯电动汽车
{};

再次输出

可以发现 打印出了同样的数据,因为此时两个子类的price共用同一块空间

因此我们不再需要区分作用域,也可以直接打印出price

 使用开发人员命令提示符工具查看

本文链接:https://my.lmcjl.com/post/5613.html

展开阅读全文

4 评论

留下您的评论.