当前位置: 首页 > news >正文

继承的了解与学习

目录

1. 继承的概念及定义

1.1 继承的概念

1.2继承的名称

1.3继承方式

1.4继承类模板

2.基类和派生类之间的转化

3.继承中的作用域

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

5.继承与友元

6.继承与静态函数

7.多继承与其菱形继承问题

8.虚继承

9.继承和组合


1. 继承的概念及定义

1.1 继承的概念

在C++中,继承(Inheritance) 是面向对象编程(OOP)的核心概念之一,它允许一个类(派生类/子类)基于另一个类(基类/父类)来构建,继承其成员变量和成员函数,并可以扩展或修改其行为。它是is-a的关系,就是什么事什么的关系。比如dog是动物。

在下列代码中,我们就是继承关系。

#include<iostream>
using namespace std;
class dongwu
{
public:dongwu(){cout << "666";}
};
class gou:public dongwu
{
public:gou(){cout << "777";}
};
int main()
{gou d;return 0;
}

1.2继承的名称

被继承的叫做父类或者基类,就是上述代码中的dongwu类,主动继承的类叫做子类或者派生类,就是上述代码中的gou类。

1.3继承方式

继承的方式在派生类后加:进行选择,其中总体来说,有点类似于权限只能缩小不能放大,父类中private成员不管选择哪种继承方式继承过来都是private类,类似的父类中public成员如果选择public继承就是public成员,选择protected就是protected成员,private继承就是private成员。

下面给出具体的细则:

1. 基类private成员在派⽣类中⽆论以什么⽅式继承都是不可⻅的。这⾥的不可⻅是指基类的私有成员,如下图基类中private成员继承过来以后还是不可以被子类访问。
还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问
它。
2. 基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类 中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。在上述基础上我们把a设置为protected变量就可以在子类中访问了。
3. 实际上⾯的表格我们进⾏⼀下总结会发现,基类的私有成员在派⽣类都是不可⻅。基类的其他成员
在派⽣类的访问⽅式 == Min(成员在基类的访问限定符,继承⽅式),public > protected > private。
4. 使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式。
5. 在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤
protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实
际中扩展维护性不强。

1.4继承类模板

namespace bit
{
//template<class T>
//class vector
//{};
// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public std::vector<T>
{
public:
void push(const T& x)
{
// 基类是类模板时,需要指定⼀下类域,
// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了
// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
vector<T>::push_back(x);
//push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
const T& top()
{
return vector<T>::back();
}
bool empty()
{
return vector<T>::empty();
}
};

2.基类和派生类之间的转化

1.public继承的派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤。这⾥有个形象的说法叫切⽚或者切 割。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出基类那分
2.基类对象不能赋值给派⽣类对象。
3.基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针 是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type
Information)的dynamic_cast 来进⾏识别后进⾏安全转换。
Student sobj ;
// 1.派⽣类对象可以赋值给基类的指针/引⽤
Person* pp = &sobj;
Person& rp = sobj;
// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的
Person pobj = sobj;
//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错
sobj = pobj;

3.继承中的作用域

在继承体系中基类和派生类都有独立的作用域,当派生类和基类有同名函数和同名成员变量时,基类的变量会被自动屏蔽,基类和派生类中都含有a当我们打印时不指定作用域它会默认打印派生类中的5,如果我们想访问基类中的a需要指定作用域

using namespace std;
class dongwu
{
public:dongwu(){cout << "666";}int a=5;
};
class gou:public dongwu
{
public:int a=10;gou(){cout << "777";cout << a;cout<<dongwu::a;}};

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

我们在学习类中,学习了构造函数,拷贝构造函数,还有析构函数。那么在派生类和基类中,它们的默认成员函数有什么区别呢?

首先我们的派生类继承的基类中的变量不需要在派生类中实现构造,如果我们不显示调用,编译器会自动调用基类的构造来实现,就是在派生类中要把基类当做一个整体来看待,关于构造的顺序是先构造基类,再构造派生类,因为派生类中可能会有用到基类的地方,这时如果基类未被构造实例化会出现问题,析构的时候我们先析构派生类,再析构基类,这同样是因为派生类中可能会有用到基类的地方,先把基类析构会报错。

在C++中,派生类对象的构造过程必须包含基类部分的构造。这句话的核心是:如果基类没有默认构造函数(即无参构造函数),派生类必须在自己的构造函数初始化列表中显式调用基类的某个构造函数,否则会导致编译错误。

派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的

operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域。

如果不指定作用域会出现递归,导致一直调用派生类的=,导致栈溢出崩掉。

Person& operator=(const Person& p )
{
cout<<"Person operator=(const Person& p)"<< endl;
if (this != &p)
_name = p ._name;
return *this ;
}

这里我们再看一个问题就是派生类的析构函数能显示调用基类的析构函数吗?乍一看是没有问题的,但是我们再细想一下,析构时派生类先析构,派生类析构要调派生类的析构函数,但是如果我们再在派生类的析构显示调用,那么派生类析构会调用一次,派生类析构完成又会调用一次,导致二次析构可以看见下面的显示调用导致的二次析构,所以我们不需要显示调用析构,编译器会自动处理,先析构派生类再去析构基类。

class dongwu
{
public:dongwu(){cout << "666";}int a=5;~dongwu(){cout << "~dongwu";}
};
class gou:public dongwu
{
public:int a=10;gou(){cout << "777"<<endl;cout << a<<endl;cout << dongwu::a<<endl;}~gou(){dongwu::~dongwu();cout << "~gou";}
};

如果类不想被继承加final关键字就好。关键字加在基类之后。

5.继承与友元

友元关系不能被继承。也就是说基类友元不能访问派⽣类私有和保护成员。

6.继承与静态函数

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都 只有⼀个static成员实例。
在理解这句话之前我们先明确,类一般都是要实例化的,但是静态成员属于类本身,而非属于某个类对象,当我们根据某个类模版实力化出很多个对象时,它们的静态成员的地址不会改变,而其他非静态成员会开辟出多个不同的空间,地址会改变。所以我们现在可以理解无论派生出多少个类,静态成员始终只有一份。
其次还要注意:静态成员必须在类外初始化。
下面我们再通过具体代码来理解一下:
可以看见实力出不同的gou,它们继承的基类中的静态成员b始终地址不变。
class dongwu
{
public:static int b;dongwu(){cout << "666";}int a=5;~dongwu(){cout << "~dongwu";}
};
int dongwu::b = 5;
class gou:public dongwu
{
public:int a=10;gou(){cout << "777"<<endl;cout << a<<endl;cout << dongwu::a<<endl;}~gou(){dongwu::~dongwu();cout << "~gou";}
};
int main()
{gou d;gou a;gou b;cout << &(a.b) << "  " << &(b.b) << "   " << &(d.b);return 0;
}

注意:静态成员只是影响生命周期并不影响访问权限的改变。有的可能访问不了,这个在类模范板块可以进一步进行理解。

7.多继承与其菱形继承问题

1.单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
2.多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的型 是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
3.菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以 看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就 ⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不议设计出菱形继承这样的模型的。
下图就是 菱形继承。 它会继承一个person两次 导致了我们有两份person的成员,如果直接访问会直接报错,必须指定类域去访问。

8.虚继承

比雅尼·斯特劳斯特鲁普为了解决数据冗余和二义性的问题,为此设计出了虚继承,哪里有在哪里加virtual。

9.继承和组合

继承是is-a的关系,是狗是动物,组合是has-a的关系,是汽车包含轮胎。

继承的耦合性高,它被父类影响很大,一些功能可能必须去依靠父类去实现,这时我们就不能轻易去改变父类。

组合的耦合性低,它主要是通过父类提供的接口进行实现的,和类的封装一样,外界只知道接口,不知道底层的实现细节。而继承的细节派生类就能观察到,一定程度上破坏了封装。

相关文章:

  • 使用 vcpkg 构建支持 HTTPS 的 libcurl 并解决常见链接错误
  • 【时时三省】(C语言基础)用do...while语句实现循环
  • Wireshark 搜索组合速查表
  • linux服务器命令行获取nvidia显卡SN的方法
  • 通过 winsw 把相关服务配置windows自启动
  • package.json 里面出现 workspace:*,关于工作区的解释
  • 文献总结:NIPS2023——车路协同自动驾驶感知中的时间对齐(FFNet)
  • 时序逻辑电路——序列检测器
  • 如何提高单元测试的覆盖率
  • PC主板及CPU ID 信息、笔记本电脑唯一 MAC地址获取
  • 目标检测综述
  • 深度解析生成对抗网络:原理、应用与未来趋势
  • 三维点拟合平面ransac c++
  • MCP 协议:AI 世界的 “USB-C 接口”,开启智能交互新时代
  • 管家婆财贸ERP BB095.销售单草稿自动填充组合品明细
  • Python 的 pip 命令详解,涵盖常用操作和高级用法
  • Vue 3.0 Composition API 与 Vue 2.x Options API 的区别
  • (论文阅读)RNNoise 基于递归神经网络的噪声抑制库
  • 频率合成方法及其实现原理
  • 嵌入式linux架构理解(宏观理解)6ull学习心得---从架构理解到自写程序运行及自写程序开机自启动
  • 神二十6个半小时到站
  • 日方炒作中国社会治安形势不佳,外交部:政治操弄意图明显
  • 广电总局加快布局超高清视听产业链,多项成果亮相
  • 三部门:对不裁员少裁员的参保企业实施稳岗返还政策至今年底
  • 上海之旅相册②俄罗斯Chaika:客居六年,致上海的情书
  • 哈佛大学就联邦经费遭冻结起诉特朗普政府