C++之 继承 (inheritance)

目录

启示

一、基本语法

二、继承的方式

三种:

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

三、继承中的对象模型

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

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

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

构造的顺序:      父类 > 子类

析构的顺序:        子类 > 父类

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

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

七、多继承

八、菱形继承


启示

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

C++之 继承 (inheritance)_第1张图片

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


一、基本语法

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

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

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

继承方式有public/private/protected


二、继承的方式

三种:

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

他们有如下关系

C++之 继承 (inheritance)_第2张图片

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

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


首先是public公共继承

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

C++之 继承 (inheritance)_第3张图片

 c不可访问 

 同时,带上测试函数

C++之 继承 (inheritance)_第4张图片

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


然后是protected保护继承

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

C++之 继承 (inheritance)_第5张图片

 c不可访问

接下来是测试函数

C++之 继承 (inheritance)_第6张图片

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


最后是private私有继承

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

C++之 继承 (inheritance)_第7张图片

c不可访问 

测试函数:

C++之 继承 (inheritance)_第8张图片

 同样的,由于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;
}

C++之 继承 (inheritance)_第9张图片


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

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

C++之 继承 (inheritance)_第10张图片

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

C++之 继承 (inheritance)_第11张图片

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

C++之 继承 (inheritance)_第12张图片

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

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

C++之 继承 (inheritance)_第13张图片

 可以看到子类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;
}

C++之 继承 (inheritance)_第14张图片

 因此,

构造的顺序:      父类 > 子类

析构的顺序:        子类 > 父类


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

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

①访问子类同名成员,直接访问

②访问父类同名成员,加作用域(所有同名) 


 创建父类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;
};

C++之 继承 (inheritance)_第15张图片

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

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

C++之 继承 (inheritance)_第16张图片


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

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();
}

C++之 继承 (inheritance)_第17张图片


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

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

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

C++之 继承 (inheritance)_第18张图片

需要加父类的作用域 

C++之 继承 (inheritance)_第19张图片

C++之 继承 (inheritance)_第20张图片 


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

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

①访问子类同名成员,直接访问

②访问父类同名成员,加作用域(所有)


父类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;
}

C++之 继承 (inheritance)_第21张图片

 值得注意的是,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();
}

C++之 继承 (inheritance)_第22张图片


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


七、多继承

语法:

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;
}

C++之 继承 (inheritance)_第23张图片

4个int,大小16

C++之 继承 (inheritance)_第24张图片

 使用开发人员命令提示符也可以看到包含父类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

C++之 继承 (inheritance)_第25张图片

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

因此,需要加作用域

C++之 继承 (inheritance)_第26张图片


八、菱形继承

C++之 继承 (inheritance)_第27张图片

 

 ①传电动汽车继承了汽车的price,纯汽油汽车同样继承了汽车的price,当混合动力汽车使用公共属性price时,就会产生二义性

②混合动力汽车继承自汽车的属性price继承了2份,造成额外开销,只需一份即可

代码:

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;
}

C++之 继承 (inheritance)_第28张图片

必须要加上作用域 

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;
}

打印输出

C++之 继承 (inheritance)_第29张图片

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

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

 C++之 继承 (inheritance)_第30张图片

确实会有2份

接下来,使用虚继承解决 

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

在2个子类的继承方式前加上virtual,即变成虚继承

此时,原父类/基类称为虚基类

再次输出

C++之 继承 (inheritance)_第31张图片

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

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

C++之 继承 (inheritance)_第32张图片

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

C++之 继承 (inheritance)_第33张图片

 可以看到,pure_gasoline_car的vbptr(虚基类指针)指向它的虚基类表(下面红色),起始位置是0(左边红色),偏移量是8(下面虚基类表里写的8),因此指向8的位置

同理,pure_electric_vehicle的虚基类指针从4偏移4到8的位置,也指向8

因此,两个虚基类指针指向同一块空间

你可能感兴趣的:(c++,开发语言)