波奇学C++:继承

继承是为了复用代码,成员的变量或者成员函数

cpp 复制代码
class Person
{
public:
	
protected:
	string _name="li";
	int _age=1;
};
class Student :public Person
{
public:
	void print()
	{
		cout << _age;
	}
protected:
	int _stuid=2;
};

子类student公有继承基类Person,子类,继承Person所有成员变量,相当于copy一份,成员函数在公有,但子类可以访问。

子类的访问受到继承方式和父类访问限定符的影响

|---------|-------------|--------------|------------|
| | 父类的public | 父类的protected | 父类的private |
| public | 子类public | 子类的protected | 子类private |
| protect | 子类protected | 子类protected | 子类private |
| private | 不可见 | 不可见 | 不可见 |

不可见指的是类外都不能访问,private相比,至少类中可以访问。

实际上子类的访问=min(父类的访问限定符,继承方式)

protected 用于存放可以私有且子类可以访问。

不加继承方式,class默认私有继承,struct默认公有继承。

父类不能直接赋值子类(向下转换),子类可以给父类(向上转换),这是一种赋值兼容,把子类中父类的部分切割赋值给父类,而不是强制类型转换

cpp 复制代码
student=person;//报错 
person=student;

子类重定义/隐藏父类成员

子类和父类同名成员变量,成员函数,编辑器优先选择子类,通过父类域可以选择父类的变量,函数,编译器优先顺序:作用域>类域>全局域。

bash 复制代码
class Student:public Person
........
void Print()
{
cout<< num;
cout<< Person::num;
}

*注意不是重载关系,因为重载要在同一个作用域,而子类和父类分别为两个类域。

派生类的构造,拷贝构造,赋值重载

cpp 复制代码
//基类
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)"<< endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};
cpp 复制代码
//子类
class Student :public Person
{
public:
	Student()
		:_id(0)
	{

	}
protected:
	int _id;
};

子类Student继承了父类Person,继承相当于copy了一份父类的成员变量,因此我们理解子类中有_name和_id两个变量。但不能在初始化列表中初始化_name,但可以在函数内部中。

初始化列表相当于在定义时初始化,可以理解为编译器在基类的定义找不到父类中的_name声明。

实际上继承的父类的成员变量,在初始化列表,默认调用父类构造函数来初始化。

cpp 复制代码
// 子类构造函数
Student()
		:_id(0)
		,Person("xiaoli")
	{
		_name="xixiaong"
	}

注意Person的初始化优先于,_id的初始化

理解成父类成员变量的声明先于子类成员变量。

派生类的拷贝构造

cpp 复制代码
Student(const Student& s)
		:Person(s)
		,_id(s._id)
{

}

用父类的拷贝构造,子类作为参数,拷贝构造父类的成员变量,我们此前所说的向上转换,子类给父类是指出的,再拷贝构造基类自身成员变量。

如果不显示调用拷贝构造,会调用默认构造。

派生类的赋值重载,除了子类向上构造,还有注意operator=()重定义的问题。

cpp 复制代码
Student& operator=(const Student& s)
{
	if (this != &s)
	{
		Person::operator=(s);
		_id = s._id;
	}
	return *this;
}

析构函数不用显式调用父类析构函数

析构函数名被特殊处理destructor,在子类析构调用后,会自动调用父类析构函数。来保证先子后父

cpp 复制代码
~Student()
	{

	}

先子后父的原因:初始化列表时,先调用父类构造函数,所以结束时后调用父类析构函数。子类可能用到父类的函数,成员变量,如果父类被析构了,那么就会报错

友元关系不能继承。

静态成员变量属于子父类公有,子父类的静态成员变量是共有的,而不是复制的关系

cpp 复制代码
class Person
......
protected:
	string _name;
	static int _count;
};
int Person::_count = 0;

多继承和菱形继承

Assiatant类继承了Student和Teacher两个类

cpp 复制代码
class Assistant :public Student, public Teacher
{
protected:
	string _majorCourse;
};

菱形继承存在的问题,student可能和teacher同时从Person那里继承了age,使得assistant类中有两个年龄造成数据冗余和二义性

虚继承解决冗余,二义性问题

cpp 复制代码
// Student和Teacheer类虚继承了Person
class Student :virtual public Person
{
protected:
	int  _num;
};
class Teacher :virtual public Person
{
protected:
	int _id;
};

二义性的证明

cpp 复制代码
class A
{
public:
	int _a;
};
class B : public A
{
public: int _b;
};
class C : public A
{
public: int _c;
};
class D : public C, public B
{
public: int _d;
};

由图可得_a有两个分别在B,C中。并且类A,B,C的变量依次排列

虚继承virtual

_a 的位置存放着一串地址

对应地址的下个位置的数值为20,12,恰好就是B,C到A的偏移量。

本质上保存的是_a的相对位置,那么为什么不直接保存A的地址,或者偏移量而是要保存指向一块空间的地址?如果存在多个D对象,因为相对位置是固定的,所以可以直接复用。d2 和d1的绝对地址是不一样的,但是相对位置是一样的。

右边的表又叫为虚基表,专门保存偏移量。

总结:把它单独划分出来便于复用。

虚继承后B,C,D 都报错相似的结构,有虚基表

偏移量的运用

当ptr无论指向B 还是C 都可以通过偏移量,找到A

相关推荐
家有狸花22 分钟前
VSCODE驯服日记(三):配置C++环境
c++·ide·vscode
dengqingrui1231 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝1 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O1 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
吾爱星辰3 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
ChinaDragonDreamer3 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
IT良3 小时前
c#增删改查 (数据操作的基础)
开发语言·c#
小飞猪Jay4 小时前
C++面试速通宝典——13
jvm·c++·面试
Kalika0-04 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j