IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++——继承、多态 -> 正文阅读

[C++知识库]c++——继承、多态

一继承

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

2.继承的定义:

(1)友元关系不能继承,基类友元不能访问子类私有和保护成员

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

class Student :public Person   //Student是子类(派生类),Person是父类(基类)
{
public:
    int _a;
    int _b;
};

3.继承的访问限定符

?

4.结论:

(1). 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私
有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
(2). 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
(3). 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public>protected>private。
(4). 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。
(5). 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强.

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

(1).派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
(2)基类对象不能赋值给派生类对象。
(3)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的

class Person
{
protected :
string _name; 
string _sex; 
int _age; 
};
class Student : public Person
{
public :
int _No ;
};
void Test ()
{
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}

6.继承中的作用域

(1). 在继承体系中基类和派生类都有独立的作用域。
(2). 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
(3). 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
(4). 注意在实际中在继承体系里面最好不要定义同名的成员
7.派生类的默认成员函数:如果我们不写,编译器会变我们自动生成一个

(1). 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
(2). 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
(3). 派生类的operator=必须要调用基类的operator=完成基类的复制。
(4). 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。
(5). 派生类对象初始化先调用基类构造再调派生类构造。
(6). 派生类对象析构清理先调用派生类析构再调基类的析构。
(7). 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

8.菱形继承:

(1)单继承:一个子类只有一个直接父类时称这个继承关系为单继承

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

(3)菱形继承:菱形继承是多继承的一种特殊情况

(3)菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份

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

9.继承和组合

(1)?public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
(2)组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

(3)优先使用对象组合,而不是类继承 。
(4)继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。
(4)对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。
(5)实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合
二.多态

1.概念:具有多种形态,多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

2.继承中要构成多态的两个条件:

(1). 必须通过基类的指针或者引用调用虚函数
(2). 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

(3)虚函数:即被virtual修饰的类成员函数称为虚函数

3虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

class Person {
public:
virtual void lin() { cout << "1" << endl; }
};
class Student : public Person {
public:
virtual void lin() { cout << "2" << endl; }
};
//在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)

4.派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变

5.析构函数的重写(基类与派生类析构函数的名字不同):
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor

6.检查是否重写关键字

(1)final:修饰虚函数,表示该虚函数不能再被重写
?

class Car
{
public:
virtual void lin() final {cout << "1" << endl;}
};
class Benz :public Car
{
public:
virtual void lin() {cout << "2" << endl;}
};

(2). override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};






?

?7.抽象类

(1)在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

virtual void Drive() = 0;

(2)接口继承和实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

8.虚函数表

//size()大小=8
class Base
{
public:
virtual void lin()
{
cout << "lin()" << endl;
}
private:
int _a = 1;
};

(1)除了_a成员,还多一个__vfptr放在对象的前面,对象中的这个指针我们叫做虚函数表指针(v代
表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数
的地址要被放到虚函数表中,虚函数表也简称虚表

(2). 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚
表指针也就是存在部分的另一部分是自己的成员。
(3). 基类b对象和派生类d对象虚表是不一样的,这里我们发现a完成了重写,所以d的虚表
中存的是重写的lin::a,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数
的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
(4). 另外b继承下来后是虚函数,所以放进了虚表,c也继承下来了,但是不是虚函
数,所以不会放进虚表。
(5). 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
(6). 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生
类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己
新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
(7).?虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是
他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。

(8)满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的。不满足多态的函数调用时编译时确认好的。

9.动态绑定与静态绑定

(1). 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,
比如:函数重载
(2). 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体
行为,调用具体的函数,也称为动态多态。
?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:06:35  更:2022-11-05 00:11:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 4:38:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码