C++继承

👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍

🏐1.什么是继承

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

在这里插入图片描述
根据不同的成员类型和继承方式可以组合出3×3共9种

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

小结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的。
  3. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

🏐2.继承中的作用域

在C语言中域分两个,全局域和局部域,域影响着生命周期,也有特例比如静态的。到C++里又多了命名空间域和类域,不影响生命周期,影响访问,做到名字的隔离,防止命名冲突。
在继承体系中的基类和派生类都有独立的作用域。若子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。
基类和派生类类有相同变量时,派生类继承了基类的,也就是说现在派生类有两个同名字的变量,那么我们再进行访问时,默认访问的是什么呢?答:是派生类的。可以类比全局域和局部域在局部域访问时默认访问的是局部域的。如果想要访问基类的,加访问限定符::即可。

🏐3.基类和派生类对象赋值转换

两个不同类型的赋值,常常会进行隐式类型转换或者强制类型转换才可以,那么这里的基类和派生类的赋值转换也是这样吗?

int main()
{
	Student sobj;
	
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;
	
	return 0;
}

派生类类对象可以赋值给基类对象/指针/引用,这里虽然是不同类型,但不是隐式类型转换,是一个特殊支持。中间不会产生临时对象。
那么反过来,基类对象可以赋值给派生类对象吗?不行,哪怕强制类型转换也不行。

	sobj = (Student)pobj;//错误

是不可以的,基类对象不能赋值给派生类对象

🏐4.派生类的默认成员函数

在这里插入图片描述

“默认”的意思就是指我们不写,编译器会变我们自动生成一个。
基类跟我们之前的普通类没什么区别,真正被影响的是派生类。派生类分为两个部分,一个是自己的,一个是基类继承下来的。那么它的默认成员函数会是什么样呢?我们先来看构造

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

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:

protected:
	int _num;
};
int main()
{
	Student s;
	return 0;
}

在这里插入图片描述
通过上面代码,我们发现我们没有调用基类,但是这里却调用了一次基类的构造和一次基类的析构,这说明派生类默认生成的构造函数会做到:

  1. 跟类和对象一样,对于内置类型不做处理,自定义类型调用它的默认构造。
  2. 对于继承的基类成员必须调用基类的构造函数初始化。

我们再来验证一下第二条,假如说我们基类不用张三初始化,那么会发生什么呢?答:编译器报错,信息如下。

在这里插入图片描述
这说明要么基类提供默认构造,要么我们显式去写派生类的默认构造函数。那现在就剩我们自己写一个派生类的默认构造这一个选项了。

Student(const char* name, int num)
		:_name(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}

写完之后编译器又报错了
在这里插入图片描述
非法的成员初始化,因为在派生类这里看来基类是一个整体,对于基类的成员就必须调用基类的构造函数。 应该这么去写派生类的默认构造

Student(const char* name, int num)
		:Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}

我们再把拷贝构造,赋值成员函数写一下

Student(const Student& s)
	:Person(s)
	, _num(s._num)
{
	cout << "Student(const Student& s)" << endl;
}

Student& operator=(const Student& s)
{
	if (this != &s)
	{
		Person::operator=(s);

		_num = s._num;
	}

	cout << "Student& operator=(const Student& s)" << endl;

	return *this;
}

析构函数

~Student()
{
	~Person();
}

编译报错,原因是找不到~Person(),后面由于多态的需要析构函数名字会统一处理成destructor,这样派生类的析构函数和基类的析构函数构成隐藏。 那我们这么写

~Student()
{
	Person::~Person();
}

这样也是不行的,这不符合先构造后析构。在这里插入图片描述

得出结论:不需要显示调用父类析构函数。每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类,再析构父类。 这样即可:

~Student()
{}

🏐5.继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员。如果派生类也要用,那么在派生类中也定义友元。注意:友元尽量少用,会破坏封装。

🏐6.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个static成员实例 。所以静态成员变量可以用来计数。

🏐7.如何定义一个不被继承的类(最终类)

我们可以将构造私有化

class A 
{
private:
	A()
	{}

protected:
	int _a;
};

class B : public A
{
};

基类构造函数私有,派生类是不可见的,就是类里类外都不能调用。派生类对象实例化,无法调用构造函数。 在实例化时才体现才报错,这是C++98,C++11新增关键字final

🏀7.1final关键字

不被继承的类也叫最终类,关键字final加在类名后,这样不用实例化,也会报错更为显著,推荐这种方式。

class A final
{
private:
	A()
	{}

protected:
	int _a;
};

class B : public A
{
};

🏐8.继承方式

🏀8.1单继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述

🏀8.2多继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

🏀8.3菱形继承

菱形继承:菱形继承是多继承的一种特殊情况。
在这里插入图片描述
我们很容易就能看出这样很容易出现数据冗余和二义性的问题,因为创建对象时初始化多次,并且同名的对象在多个父类中都包含,不明确。对于二义性,我们指定类域即可。

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

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

class Teacher : public Person
{
protected:
	int _id; // 工号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

int main()
{
	Assistant at;

	// 菱形继承的二义性问题
	//at._name = "彭彭";
	at.Student::_name = "老彭";
	at.Teacher::_name = "小彭";
 
	return 0;
}

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。 用到关键字virtual

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

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

class Teacher : virtual public Person
{
protected:
	int _id; // 工号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

int main()
{
	Assistant at;

	// 菱形虚拟继承解决了二义性和数据冗余
	at._name = "彭彭";
	at.Student::_name = "老彭";
	at.Teacher::_name = "小彭";

	return 0;
}

这样每次改变的就都是那一个对象了。

💬总结

  • 常用的是公有继承,保护继承和私有继承基本不用,很少用。
  • 父类的私有成员继承可以认为没有意义,因为我们设计出来就是为了让子类复用,可是你又不让它用,太没有意义了。
  • 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,也一定不要设计出菱形继承。否则在复杂度及性能上就会有影响。
  • 多继承可以认为是C++的缺陷之一,很多后来的语言都没有多继承,如Java

在这里插入图片描述

觉得还不错的铁汁点赞关注一下吧😀
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江不平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值