c++-----继承

01:继承是什么

定义

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

继承的语法

继承权限可以不写,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过****最好显示的写出继承方式

打个比方

我们要实现一个学生类和一个老师类

cpp 复制代码
class student {
	int name;					//姓名
	char Date_of_birth[32];		//出生日期
	char Origin[32];			//籍贯
	//.......

	long long School_Degree;	//学号
	long long Dormitory_number;	//宿舍号
	//........
};
class teacher {
	int name;					//姓名
	char Date_of_birth[32];		//出生日期
	char Origin[32];			//籍贯
	//.......

	char Graduation_school[32];	//毕业学校
	char posts[32];				//职位
	//........
};

可以看出老师类与学生类有很多相同的内容,如果要在两个类中都写个add函数的话,两个函数会有很多相同之处.这实在不够优雅**,**所以c++的祖师爷搞出来继承这套东西.

cpp 复制代码
//个人类
class Person {
	int name;					//姓名
	char Date_of_birth[32];		//出生日期
	char Origin[32];			//籍贯
	//.......
};
//学生类,学生类依赖于个人类
class student : public Person
{
	long long School_Degree;	//学号
	long long Dormitory_number;	//宿舍号
	//........
};
//老师类,老师类依赖于个人类
class teacher : public Person
{
	char Graduation_school[32];	//毕业学校
	char posts[32];				//职位
	//........
};

子类中可以按照相应的权限来使用父类的内容


02.继承关系与访问限定符

3种继承关系:public, protected ,private与3种访问限定符:public, protected ,private.总共能组合出九种访问权限.

继承关系与访问限定符的关键字字符一样,但书写位置不同,含义不同.

巧记:

  1. 基类的private在派生类都是不可见的.
  2. public>protected>private,基类的非private元素取继承关系与访问限定符较小的为权限.

举个例子

cpp 复制代码
class parent {
public:
	int pub;
protected:
	int pro;
private:
	int pri;
};
class child :public parent{		//以共有的方式继承
	void fun()
	{
		pub = 1;	//	public和public	->公有继承
		pro = 2;	//	public和protected	->保护继承
		//pri = 3;	//	父类为private	->在子类中不可见,访问会报错
	}
};

最常见的继承方式是公有继承

父类的私有元素是否会被子类继承?

以上段代码为例,我们来使用一下sizeof()

一个int为4字节,打印的12证明child继承到了三个父类元素,也就代表子类会继承父类元素

给父类写一个构造函数,通过内存窗口也能看到

结论:子类会继承父类的全部元素,但是父类的私有元素不可访问

03:子类与父类的赋值转换

子类可以直接给父类赋值,这个过程叫做切片,(请注意父类不可给子类复制,当然有手动重载的除外)

父类及父类的指针,父类的引用都可直接由对应的子类赋值.子类的指针可以通过父类指针强制类型转换来赋值,但是有越界的可能

例:

cpp 复制代码
class Person {
	string _name;
	int _sex;
	int _age;
};
class Student : public Person {
	long long _no;
};
int main()
{
	Person p;
	Student s;
	Person* pp = &s;
	Person& tmp = s;
	Person qp = s;

    Student* ss = (Student*)&p;//有可能越界

	return 0;
}

04:继承的作用域

在继承的体系中,父类和子类有相互独立的作用域.

当子类和父类有同名函数时,会构成隐藏,请注意,隐藏只与函数名有关,与参数和返回值无关!!!!

默认调用的时子类函数,可以通过加上作用域来指定作用域

子类与父类最好不要定义重名函数


05.子类的默认成员函数

有的人死了,但他还活着!有的人活着,但他已经死了!

----鲁迅

不知道大家自类和对象之后再次看到默认成员函数是什么感觉,反正我有点心肌梗塞,你先别怕,怕了也学不会其实这里的要比类和对象里的简单一些.

  1. 构造函数:调用子类的构造函数时必须要调用父类的构造函数,如果父类没有默认构造函数,那么在初始化子类时必须显式的调用父类的构造函数
  2. 子类的拷贝构造必须要调用父类的拷贝构造.而在子类的构造函数中给父类元素赋值
  3. 子类operator=函数必须调用父类的operator=完成赋值,而非直接访问父类元素
  4. 子类的析构在调用完之后会自动的去调用父类的析构,子类的析构中不需要显示调用父类的析构
  5. 子类的构造会先调用父类的构造函数
  6. 子类的析构会最后调用父类的析构函数

06.继承与友元

父类的友元不是子类的友元函数,也就是说父类友元不能直接访问子类保护和私有

cpp 复制代码
class Student;
class Person
{
public:
 friend void Display(const Person& p, const Student& s);
protected:
 string _name; // 姓名
};
class Student : public Person
{
protected:
 int _stuNum; // 学号
};
void Display(const Person& p, const Student& s) {
 cout << p._name << endl;
 cout << s._stuNum << endl;     //不是子类友元,这里不能访问
}

07:父类与静态成员

在父类中定义的静态成员,当父类被继承到子类时,所有的子类都会只使用一个静态成员.

cpp 复制代码
class A {
public:
	static int a;
};
class B : public A {
public:
	void fun() {
		cout << "在B中查看全局变量a,a的值为:"<< a << endl;
	}
};
class C : public A {
public:
	void fun() {
		cout << "在C中查看全局变量a,a的值为:" << a << endl;
	}
};
int A::a = 0;
int main() {
	A aa;
	B bb;
	C cc;
	cout << "A中aa的值改为0" << endl;
	bb.fun();
	cc.fun();
	aa.a = 10;
	cout << "A中aa的值改为10" << endl;
	bb.fun();
	cc.fun();
	system("pause");
	return 0;
}

08.多继承

多继承与单继承

单继承很好理解,譬如学生是一个人,,那么学生类就可以做人类类的子类,二者关系为单继承.

多继承可以理解为在学生和人类的基础之上,学生张三是一名某某游戏会员,那么在描述张三时,可以让张三继承两个类,这样一个子类对应多个父类,他们的关系为多继承.

Java是没有的多继承的,由此可见c++这门语言是一门旷世神作,c++程序员都聪明绝顶,Java语言过于局限,Java程序员好逸恶劳(手动滑稽),多继承毫无疑问是有现实意义的,譬如有些博士生既是老师又是学生,谷爱凌既是中国人又是美国人,马克思既是犹太人又是中国人(不是),如果使用多继承的话可以很好的实现一些面向对象功能,但是毫无疑问在后续使用菱形继承之类复杂继承时,出现了很多语法困难,c++早期版本也是一直在改进继承.

总之,多继承是有他独特的意义的,是值得学习的

多继承语法

语法很简单:在单继承的基础之上加个逗号,再加上第二个父类即可

菱形继承

可以看出,在Student类和Teacher类中都有Person类,进而在Assistant类中会有两份Person类,这就造成了代码冗余,因为两个Person中的数据很显然是一模一样的

ps:棱形继承要访问Person元素的话,需要在前面加上父类作用域,否则编译器分不清是在操作哪个Person

cpp 复制代码
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 ; // 主修课程
};
void Test ()
{
 // 这样会有二义性无法明确知道访问的是哪一个
 Assistant a ;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
 a.Student::_name = "xxx";
 a.Teacher::_name = "yyy"; }

虚拟继承

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

cpp 复制代码
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 ; // 主修课程
};
void Test ()
{
 Assistant a ;
 a._name = "peter"; }

虚拟继承的底层原理(vs2022编译器32位)

这是一些简单的类构成的菱形继承.

cpp 复制代码
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; }

首先是非虚函数,看以看到A存了两份,有数据冗余的情况

虚函数,看以看到A只存了一份

可以看出,虚函数通过存储指针->偏移量来找到二者共有的A的地址

相关推荐
凡人的AI工具箱15 分钟前
40分钟学 Go 语言高并发:Pipeline模式(一)
开发语言·后端·缓存·架构·golang
微澜-15 分钟前
编译以前项目更改在x64下面时报错:函数“PVOID GetCurrentFiber(void)”已有主体
c++
闻缺陷则喜何志丹24 分钟前
【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844
c++·算法·动态规划·力扣·网格·数目·涂色
achaoyang24 分钟前
【Python中while循环】
开发语言·python
呆呆小雅26 分钟前
C# 封装
java·开发语言·c#
蒜蓉大猩猩33 分钟前
Vue.js - 组件化编程
开发语言·前端·javascript·vue.js·前端框架·ecmascript
南鸳6101 小时前
Scala:根据身份证号码,输出这个人的籍贯
开发语言·后端·scala
eclipsercp1 小时前
PyQt5:Python GUI开发的超级英雄
开发语言·python·qt
熬夜学编程的小王1 小时前
【C++篇】解锁C++模板的魔法:从万能钥匙到精准雕刻
c++·进阶模版·c++模版·类模版实例化·函数模版实例化
军训猫猫头1 小时前
44.扫雷第二部分、放置随机的雷,扫雷,炸死或成功 C语言
c语言·开发语言