C++——继承

我们之前学习了C++的一些基础特性,从这节开始,我们就要开始继续学习C++的特性。

继承

我们之前提到过,面向对象有三大特性:封装继承多态。我们之前学过了封装(类的引入),今天我们来聊聊继承:

一个场景

我们知道两个事物可能在某些方面有许多的共同点,比如学校里的学生和老师,学生和老师都有自己的名字,年龄,性别。但两个事物之间也有不同的地方,比如学生和老师,学生有自己的学号,老师可能有自己的工号。
我们用封装的思想,将学生和老师的信息封装成类:

//学生类
class Stduent
{
public:
	string _stu_name; //学生姓名
	int _stu_age; //学生年龄
	bool _stu_sex; //学生性别
	string _stu_id; //学生学号
};
//老师类
class Teacher
{
public:
	string _tea_name; //老师姓名
	int _tes_name; //学生年龄
	bool _tea_sex; //老师性别
	string _tea_id; //老师工号
};

我们仔细观察,可以发现,这两个类当中有很多成员是重复的,比如姓名,年龄,性别,工号。我们也许可以把这些属性提出来,单独封装成一个类。

//个人类
class Person
{
public:
	string _name; //姓名
	int _age; //年龄
	bool _sex; //性别
};

现在我们想要Person类作为Stduent类和Teacher类的成员,这样可以减少代码量,这是候我们要用到继承了:

//个人类
class Person
{
public:
	string _name; //姓名
	int _age; //年龄
	bool _sex; //性别
};

//学生类
class Stduent:public Person //继承Person
{
public:
	string _stu_id; //学生学号
};

我们创建一个Stduent对象来看看:
在这里插入图片描述在这里插入图片描述
我们看见s的成员除了自身的_stu_id之外,还继承了Person的_name,_age,_sex等成员。
继承不仅会继承成员,成员函数也会被一并继承。

继承方式

我们在继承时,我们也有三种继承方式:公有(public)保护(protected)私有(private)
在这里插入图片描述那么三种继承方式有什么不同呢?
首先我们的要先知道:不同的继承方式会影响,继承之后成员的属性。同时被继承之后的成员属性也和原先中父类中成员的属性相关
简单来说:

被继承后的成员属性受继承方式父类中原有成员的属性的影响。

在这里插入图片描述
这张表可能会让人很头疼,别着急我们一个一个来看:
我们先来看一个最特殊的,如果基类(父类)的成员属性是私有的,那么无论以任何方式继承,被继承之后的成员在类中和类外都无法被访问
我们来举一个栗子:
在这里插入图片描述
类外也是无法访问的:
在这里插入图片描述
好了,我们解决完最特殊的情况之后我们来看看,其他情况:
除去基类成员为私有的情况,被继承的成员属性是继承方式和基类成员属性中,属性较为严苛的那一个
比如说,基类成员是public属性,如果是protected继承,被继承之后的成员就是protected。如果基类成员是protected属性,继承方式为public,被继承之后的成员属性仍然是protected。

那么这个protected属性呢?简单来说就是**我不想让人在类外访问这个成员,但我想这个成员可以在派生类中被访问。**此时就可以运用protected。
在这里插入图片描述这时我们发现,我们可以在派生类B中访问A的保护成员了,但是在类外依然是无法访问的:
在这里插入图片描述
实际情况中,public继承是最实用的,protected和private继承都不是很常用。

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

我们之前学过临时对象这个东西,我们到现在都知道临时对象具有常性。这个东西经常会在引用那里用到:
在这里插入图片描述在这里插入图片描述

而我们来看看在公有继承下的派生类对象赋值给基类会有什么现象?
我们还是以上面Stduent,Teacher,Person类举例子。

//个人类
class Person
{
public:
	string _name; //姓名
	int _age; //年龄
	bool _sex; //性别
};

//学生类
class Stduent:public Person //继承Person
{
public:
	string _stu_id; //学生学号
};

//老师类
class Teacher:public Person
{
public:
	string _tea_id; //老师工号
};

在这里插入图片描述
我们发现代码没有报错,但这里不是赋值吗?赋值会产生临时对象,临时对象具有常性啊?
这里我们可以当做编译器做了特殊处理:父类引用子类,直接截取相同部分(切割)

在这里插入图片描述
而且引用对象对象改变,被引用对象的值也会被改变:
在这里插入图片描述在这里插入图片描述

继承中的作用域

首先我们有一个概念:不同的类中有不同的作用域。比如Student和Person这两个类,类不同,各自管自己的成员,井水不犯河水。
那么我们可以这么想,我们可以在不同的类中,定义同名的成员:

//个人类
class Person
{
public:
	string _id; //个人类中_id
};

//学生类
class Stduent 
{
public:
	string _id; //学生类中的_id
};

这时候我们让Student继承Person:
在这里插入图片描述

我们写一个函数打印_id,这时会打印谁的_id?:
在这里插入图片描述
在这里插入图片描述其实编译器都是很懒的,都会就近原则,看谁的_id的最近,就打印谁的_id,很明显,学生的_id最近,就打印学生的_id,个人的_id就会被忽视掉(被隐藏掉)。
那么有没有办法可以打印个人的_id呢?当然有,指定作用域
在这里插入图片描述在这里插入图片描述但是在一般情况下,不建议大家在两个不同类中写同名成员,很容易翻车。

派生类的默认成员函数

我们还是以Person和Student来举例子:

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

protected:
	string _name; // 姓名
};
class Student : public Person
{
public:

protected:
	int _num; //学号
};

我们派生类Stduent中没有写Student的构造函数,我们之前学过,如果一个类我们没有写构造函数,编译器会自动生成一个不带参的构造函数,我们来测试一下:
在这里插入图片描述在这里插入图片描述
我们看到,在派生类中,我们如果不去写构造函数,派生类会自动的调用父类的的构造函数
现在我们如果自己想写派生类的构造函数,可不可以呢?当然是可以的:
在这里插入图片描述
唉?报错了,这是为什么?这是因为_name虽说被继承了,但不是Student本身的成员,不能直接用name初始化。这时候我们想要初始化_name,我们就要调用Person的构造函数
在这里插入图片描述
我们再来测试一下:
在这里插入图片描述在这里插入图片描述

包括拷贝构造函数和赋值重载函数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里我们初始化Student的Person部分,我们直接传Student的对象就行,(因为切割的关系)。

还有赋值重载:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
但这里注意一下析构函数:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述接下来,派生类会自动跳到Person的析构函数:
在这里插入图片描述
这里,我们没有显示调用Person的析构函数,编译器自动在子类对象完结的时候自动会调用,这样可以保证先子后父的析构顺序,如果显示调用父类的析构函数,最后析构次数会有问题。所以我们对于派生类的析构函数,我们不用显示调用基类的析构函数,因为编译器会自动调用。

菱形继承

这其实是C++的一个坑,为什么呢?我们看看就知道了:
在这里插入图片描述
在这里Student和Teacher都继承了Person类,Assisant继承了Student和Teacher,那就会有一个问题:Assisant就会有两份Person的成员(一份来自Student,另一份来自Teacher),就会有数据冗余的问题
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

那么如何解决这个问题呢?为了解决这个问题,我们引入虚拟继承,这个概念:
在这里插入图片描述
在这里插入图片描述你看,这个Person类被单独出来了,意思就是这个Person类就只有一份了。解决了数据冗余的问题。

那么这个到底是怎样操作的呢?我们先用一个简单的类来查看一下:

class A
{
public:
	int _a;
};

// class B : public A
class B : virtual public A
{
public:
	int _b;
};

// class C : public A
class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

在这里插入图片描述
我们这个时候来查看一下d的地址:
这个时候打开 调试 -> 窗口:
在这里插入图片描述在这里插入图片描述这个时候我们来分析一下:
在这里插入图片描述我们发现_a好像放在了最后的位置,与此同时,B和C里除了自身的值之外,我们好像还存在一个地址,我们查看一下这个地址:
在这里插入图片描述
这里面装了一个值:14,我们来看看如果将_b对象的地址的值加上这个14会有什么发现:
在这里插入图片描述我们发现加完之后就是我们_a对象的地址,那么以此类推,_c对象应该会有一个类似的东西:
在这里插入图片描述
有一个值c(十进制为12),我们又来加一下:
在这里插入图片描述
跟我们想的一样,也是会加到共有成员_a的地址:
这个原理简单来说就是,在菱形虚继承承中,子类会储存一个值(相对于公有类成员的偏移量),在需要访问这个被继承的共有成员时,会自动加上这个偏移量找到共有成员

这个菱形继承很坑,大家平时要尽量避免这种写法(Java就是吸取了C++的教训,所以没有菱形继承这个概念。)

继承与友元

记住一句话:友元不能继承(也就是说基类友元不能访问子类私有和保护成员)。(你爸爸的朋友不一定是你的朋友。)

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name = "hhh"; // 姓名
};

class Student : public Person
{
public:
	int _stuNum = 129; // 学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}

int main()
{
	Person P;
	Student S;
	Display(P, S);
}

注意看,派生类得成员是公有,这时候父类的友元是可以访问子类的成员:
在这里插入图片描述如果这时候我们把派生类的成员属性不是public就没法进行访问:
在这里插入图片描述

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例:

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student
{
protected:
    string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

int main()
{
	TestPerson();
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值