C++继承基本概念

继承

在这里插入图片描述

1、使用关键字class默认继承方式是private,使用struct默认是public继承,不过最好显示的写出继承方式: class A : public B

2、实际运用中都是public继承,几乎很少用protected/private继承,也不提倡用protected/private继承,因为继承下来的成员只能在子类中的类(父类)里面使用。

基类(父类)和派生类(子类)

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
//protected:   // 在子类可见的 只能防外面
private:  // 在子类是不可见(不能用)  不仅仅可以防外人还可以防儿子
	string _name = "peter"; // 姓名
	int _age = 18;  // 年龄
};

class Student : public Person
{
public:
	void func()
	{
		// cout <<_name << endl;//不可见
		cout << "void func()" << endl;
	}
protected:
	int _stuid; // 学号
};

代码中,Person是Student的父类,继承中除了public和private,还多了一个protected,它的作用是为了防止类外的函数调用其中的东西,但子类可以调用protected的成员且改变值。而private子类也不能调用,对子类来说是不可见的。

基类和派生类对象赋值转换

派生类对象可以赋值给基类的对象/指针/引用。形象的说法叫:切片或者切割。

切片父类只能用子类中继承过去的类型,即用不了子类自带的_NO。

切片是天然支持的,赋值过程不像异类型变量赋值,它是没有隐式转换的,是直接赋值。

隐式转换double d=1.1; int i=d; 把d赋值给i时,会产生中间值去类型转换。

在这里插入图片描述

基类对象不能赋值给派生类。父=子 子!=父

隐藏(重定义)

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; 		// 被隐藏
};

class Student : public Person
{
public:
	void Print()
	{
		cout << Person::_num << endl;
		cout << _num << endl;//输出的是Student中的_num
	}

protected:
	int _num = 999; // 学号
};

当子类和父类中有同名成员,子类将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。在子类函数中可以使用基类::基类成员显示访问。(最好不要定义同名)

✔️class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "func(int i)->" << i << endl;
	}
};

上面两个fun()函数构成隐藏,当调用fun()时使用的是子类中的fun(),不会构成函数重载。

默认成员函数调用(构造、析构)

在这里插入图片描述

子类析构后,会自定调用父类析构,保证先析构子类再析构父类。

构造、析构顺序是栈行为:先构后析,基派派基

多继承和菱形继承

多继承就是一个子类(Assistant)都继承了多个父类(Student,Teacher)

在这里插入图片描述

而菱形继承则是两个父类(Student,Teacher)又继承了一个父类(Person)

在这里插入图片描述

但是如果单单是这样定义:

class Student: public Person class Teacher: public Person

则会导致数据的冗余性和二义性,所以又引入了virtual的概念:虚继承,virtual在使用多继承时需要写在继承形式public前面。

冗余性和二义性

int main()
{
    // 这样会有二义性无法明确知道访问的是哪一个
    Assistant a;
    //a._name = "张三";

    // 数据冗余 和 二义性
    a.Student::_name = "小张";
    a.Teacher::_name = "老张";
}

当我们没有使用虚继承,定义一个Assistant类对象a时,这个a可以使用:

Student继承Person的_name:Student::_name
Teacher继承Person的_name:Teacher::_name

而它自己还有从父类继承的_name

在这里插入图片描述

一轮赋值下来,我们监视发现,_name有两个值:小张和老张,那么a的_name到底是哪个呢,此时数据就有了二义性

在这里插入图片描述

再看内存视图,理论上a继承过的_name只需要一个空间存储即可,这里却有两个不同地址存储着相同的值,如果继承10个,100个父类,那就会浪费很多空间,此时数据的冗余性已经体现出来了。

虚继承

为了解决二义性和冗余性,引入了virtual虚继承这个概念,如下定义:

**class** Student: **virtual public** Person **class** Teacher: **virtual public** Person

具体是怎么解决的呢,我们定义一段程序,再来看内存空间中是怎么分配的:

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
    string _name; // 姓名
};
class Student : virtual public Person
{
//protected:
public:
    int _num=10; //学号
    int _addres=12;
};
class Teacher : virtual public Person
{
//protected:
public:
    int _id=11; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
    //string _majorCourse="张三"; // 主修课程
};

int main()
{
    // 这样会有二义性无法明确知道访问的是哪一个
    Assistant a;
    a._name = "_name";
    // 数据冗余 和 二义性
    a.Student::_name = "小张";
    a.Teacher::_name = "老张";
}

程序说明:Student和Teacher都可以访问_name,当它们两个中间的public能够被Assistant继承,那么**_num=10 , _address=12 , _id=11** 在对象a中都可以显现,分别对应十六进制 0a 0c 0b

这样定义方便我们观察内存值。

在这里插入图片描述

从图中下面的内存视图中可以清楚定位到**_num , _address , _id** ,也就是0a、0c、0b的位置,我们可以发现,0a的地址前面和0b的地址前面还存了一串数据。

我们在上面的内存视图中寻找可以发现,蓝色地址中存放的是0x10,也就是16,这个数字是地址的偏移量,而红色地址中存放的居然是蓝色地址,也指向了偏移量地址。

这个偏移量是从存储0c的空间末尾开始偏移计算地址的,偏移16个地址位,正好到存储**_name的首地址**。这就是虚继承中的虚表

Clion和VS内存视图会有些不一样,但是原理是一样的,都是存储偏移量。

虚表

Student和Teacher类空间都应该有一个自己的地址指向偏移量(虚表)也就是说红色地址和蓝色地址各指向一个虚表,Student的虚表中是16,Teacher的虚表中是8;当定义多个Assistant对象时,每个对象都是共用这两个虚表的,解决冗余性和二义性只需要两个地址空间即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值