【C++】---继承

news/2025/2/19 7:42:18/

文章目录

  • 继承的概念与定义
    • 继承的定义格式
  • 父类和子类的对象赋值转换
  • 继承中的作用域
  • 子类的默认成员函数
  • 菱形继承
    • 虚拟继承
  • 总结

继承的概念与定义

继承是面向对象编程三大特性之一,是一种可以使代码复用最重要的手段,在原有类特性的基础上进行扩展,产生新的类。例如人和学生的关系,学生是一个人,那么人所有的特性学生都有,比如人有姓名、身份证号码、性别等,这些学生也有具备。在此基础上学生还有额外的特性,例如学号,班级

为了方便的去创建类,因此我们可以在人这个类的基础上去创建出新的学生类,当我们使用继承时,那么在创建学生类时就不用再去定义人这个类的所有特性

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{protected:int _stuid; // 学号
};

像上面的代码就是使用了继承去创建学生类,那么当我们实例化对象之后,学生类的对象就也会包含了人类的所有成员

image-20230406124028551

像这种继承我们就可以称人的类为父类或者基类,学生这个类就称为子类或者派生类。

主要注意:友元关系不能继承,当基类定义了静态成员那么整个继承体系里面只有一个这样的成员

继承的定义格式

image-20230406124425518

继承方式可以有三种,也就是我们所知道的三种访问限定方式。那么不同的继承方式也就意味着,派生类能够继承基类的不同特性

公有继承时,派生类可以访问基类除私有成员外的所有成员

保护继承时,只能访问基类中的保护成员

私有继承时,基类中的所有成员都不能访问

基类中的私有成员不论什么继承方式都不可以访问

父类和子类的对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 ,也就是说可以用基类的指针或者引用去指向派生类的对象,要注意基类指向派生类可以,但是派生类不可以指向基类。这种方式可以形象的称为切片

int main() {Student s;// 1.子类对象可以赋值给父类对象/指针/引用Person p = s;Person* pp = &s;Person& rp = s;//2.基类对象不能赋值给派生类对象//s = p;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &s;Student* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_stuid = 10;pp = &p;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_stuid = 10;return 0;
}

image-20230406140241226

继承中的作用域

在继承体系中基类和派生类都有独立的作用域 ,如果基类和派生类中都有着一个同名的函数,那么派生类对象就会自动屏蔽对基类同名函数的访问,这种情况称为隐藏/重定义

class Person
{
public:void Print(){cout << "Person:" << _name << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{
public:void Print(){cout << "Student:" << _name << endl;}protected:int _stuid; // 学号
};int main() {Person p;Student s;s.Print();return 0;
}

image-20230406140619509

可以看到这种情况访问的就是派生类中的函数。还有一种情况需要注意 如果派生类和基类的同名函数在派生类里面带有参数,但我们调用该函数并且不给函数传参的时候程序就无法运行通过。因为基类中的函数已经被隐藏了,所以就找不到对应的函数去调用

image-20230406141023900

子类的默认成员函数

派生类的6个默认函数的生成规则

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构

菱形继承

继承会有单继承和多继承。单继承就是一个类继承一个类,多继承就是多个类继承一个类

image-20230406141701709

那么因为有多继承的存在,则会产生出一种菱形继承的现象

image-20230406142013775

如图所示,它们之间的继承关系形成了一个菱形形状,那么这种菱形继承会导致什么问题呢。

首先,因为学生和老师的类中都继承了一份人的类,接着助理又去继承学生和老师这两个类,那么也就是说助理这个类就会有两份人的类的属性。

class Person
{
public:void Print(){cout << "Person:" << endl;}
public:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{
public:void Print(){cout << "Student:" << endl;}public:int _stuid; // 学号
};class Teacher : public Person
{
public:void Print(int n){cout << "Teacher:" << endl;}public:int _teaid; // 学号
};class Assistant : public Student , public Teacher
{
public:void Print(int n){cout << "Assistant:" << endl;}public:int _assid; // 学号
};int main() {Person p;Student s;Teacher t;Assistant a;return 0;
}

image-20230406142818901

可以看到a对象里面有两个 _name , __age,所以如果这时候我们去访问a的name就会出现问题,因为编译器根本就不知道你想要访问的是哪一个。这样就造成了数据的冗余和二义性

虚拟继承

那么为了解决这种菱形继承的问题,我们可以采用一种方法叫做虚拟继承。在继承方式前加上 virtual 就可以定义为虚拟继承

为了研究虚拟继承,首先先建立一个菱形继承体系,运行起来之后利用内存窗口查看数据

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;
}

image-20230406145039792

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A

如上图,b的虚基表地址为 内存2窗口的地址,对应内存1的青蓝色框,通过这个地址找到虚基表后可以看到一个数字 28 这个机会表里存的偏移量,就可以通过这个偏移量往下找到A。c的虚拟表也同理,窗口3里可以看出。

通过这个方式,可以通过偏移量去修改数据这样就可以避免了数据的冗余和二义性了。

总结

多继承可以认为是C++的缺陷,因为会导致菱形继承的发生。

继承是一个依赖关系很强,耦合度很高的方法,所以在日常编写程序时,不是那种特别特定的关系能不用继承就不继承,后面的多态要靠继承实现。否则可以多用组合。


http://www.ppmy.cn/news/39246.html

相关文章

redis 第一章

开始学习redis 之旅吧 关于redis 的介绍 redis 是一个开源的软件&#xff0c;可以存储结构化的数据在内存中&#xff0c;像内存数据库&#xff0c;缓存、消息中间件、流处理引擎。 redis 提供的数据结构像strings, hashes, lists, sets, sorted sets 。Redis具有内置复制、Lua…

中国版的ChatGPT,你最看好谁?

一、百度&#xff1a;文心一言升级中&#xff0c;未来支持开源 3月16日&#xff0c;百度正式推出国内首款生成式AI产品“文心一言”&#xff0c;可支持文学创作、文案创作、数理推算、多模态生成等功能。 “文心一言”基于全栈自研的AI基础设施进行学习和训练&#xff1a; ①…

ChatGPT能够干翻谷歌吗?

目前大多数人对于ChatGPT的喜爱&#xff0c;主要源自于其强大的沟通能力&#xff0c;当我们向ChatGPT提出问题时&#xff0c;它不仅能够为我们提供结论&#xff0c;而且还能够与我们建立沟通&#xff0c;向ChatGPT提出任何问题&#xff0c;感觉都像是在与一个真实的人类进行交谈…

k8s常用软件包下载

离线搭建 Kubernetes 集群需要下载一系列软件的安装包&#xff0c;可以从官网下载&#xff0c;也可以从镜像网站下载&#xff0c;下面提供一些常用软件的下载链接&#xff1a; Docker&#xff1a;https://download.docker.com/linux/static/stable/x86_64/ Kubernetes&#xf…

User-agent大全

一、基础知识篇&#xff1a; Http Header之User-Agent User Agent中文名为用户代理&#xff0c;是Http协议中的一部分&#xff0c;属于头域的组成部分&#xff0c;User Agent也简称UA。它是一个特殊字符串头&#xff0c;是一种向访问网站提供你所使用的浏览器类型及版本、操作系…

华为OD机试-严格递增字符串-2022Q4 A卷-Py/Java/JS

定义字符串完全由&#xff0c;A"和"B"组成&#xff0c;当然也可以全是A"或全是"B”。如果字符串从前往后都是以字典序排列的&#xff0c;那么我们称之为严格递增字符串。 给出一个字符串s&#xff0c;允许修改字符串中的任意字符&#xff0c;即可以将…

API 接口设计

1、场景描述 比如说我们要做一款 APP&#xff0c;需要通过 api 接口给 app 提供数据。假设我们是做商城&#xff0c;比如我们卖书的。我们可以想象下这个 APP 大概有哪些内容&#xff1a; 1&#xff09;首页&#xff1a;banner 区域&#xff08;可以是一些热门书籍的图片做推广…

怎么调节PDF文件的尺寸大小?

很多人在日常工作中比较喜欢使用PDF形式的文档来存储资料&#xff0c;PDF文档创建完成后&#xff0c;很多人觉得页面展示效果不够显眼&#xff0c;这时候可以通过调节PDF文件的尺寸大小来更改显示效果&#xff0c;在调节PDF文件的尺寸大小时&#xff0c;可以借助一些比较好用的…