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

【C++课程学习】:继承:默认成员函数

🎁个人主页:我们的五年

🔍系列专栏:C++课程学习

🎉欢迎大家点赞👍评论📝收藏⭐文章

 

目录

构造函数

🍩默认构造函数(这里指的是编译器生成的构造函数):

🍩显式写构造函数: 

拷贝构造函数:

🍩编译器自己生成的拷贝构造:

🍩显式写拷贝构造函数:

赋值重载:

析构函数:

🍩析构函数名称变化

🍩父类和子类的析构顺序

继承和友元:

继承和静态成员:

菱形继承:

继承的总结和反思:


构造函数

子类的构造函数在初始化列表时,我们发现父类的声明在子类之前。所以不管在初始化列表怎么写,都是先初始化父类,再调子类的构造。

🍩默认构造函数(这里指的是编译器生成的构造函数):

首先来看看,如果我们在子类中不显式写构造函数,看看编译器生成的默认构造会干什么事情(环境:VS2022):

我们可以把子类的成员分成三类:

1.父类的成员。(看成整体)

2.子类的内置类型。

3.子类的自定义类型。

从VS2022中可以看出,如果不写字类的默认构造函数,那么编译器生成的默认构造函数做了:

1.调用父类的构造函数,对父类成员进行初始化。

2.内置类型不做处理。(有些环境可能会初始化为0)。

3.子类的自定义类型,调用它的构造函数。(有缺省值,进行有参的构造函数)。


🍩显式写构造函数: 

我们在显示写构造函数时,一定要去调用父类的构造函数,构造函数调用的规则如下:
想调用哪个构造函数就传什么参数,根据不同的参数,可以调用不同的构造函数。

class person {    

public:
    person()        //无参的构造函数
    {
        cout << "person()" << endl;
    }
    person(int age,string name)        //传age,和name的构造函数
        :_age(age)
        ,_name(name)
    {
        cout << "person(int age,string name)" << endl;
    }
protected:
    int _age;
    string _name;
};

	student(int num):person()    //父类构造函数的调用,_num(num){}


当我们显式写子类的构造函数时,但是又没有调用父类的构造函数,编译器会帮我们怎么处理?

如果我们没有调用构造函数,那么编译器会去调用父类默认构造函数(无参的构造函数)。


但是会有一个问题?,如果父类没有无参的构造函数怎么办?

此时,由于子类写构造函数时,没有调用父类的构造函数。让编译器去调用父类的无参的构造函数。结果父类还没有无参的构造函数,编译器就会报错。

解决办法就是:在子类构造函数的初始化列表中调用父类带参的构造函数。

全缺省的可以不传参,所以也可以调到。

另外写person()=default;

可以让编译器强制生成默认构造函数。

拷贝构造函数:

🍩编译器自己生成的拷贝构造:

1.对于子定义类型,调用它的拷贝构造。

2.对于内置类型,进行值拷贝。

3.对于父类,调用父类的拷贝构造。

🍩显式写拷贝构造函数:

当我们显式写拷贝构造时,我们就需要注意要去显示调用父类的拷贝构造。

当我们显式写了拷贝构造,却又没有在初始化列表调用父类的拷贝构造,编译器不会帮我们调用父类的拷贝构造,因为父类的拷贝构造是带参的。

像下面一样,如果我们要显式调用person的拷贝构造,我们要怎么给person的拷贝构造传参呢?

传过来的是student类型的对象st,怎么变成person呢?

父类和子类有这样的特点:(赋值兼容转换

子类对象可以赋值给父类的对象,父类的指针,父类的引用。

解决办法:直接传student类型的对象st就可以了。

    //父类拷贝构造person(const person& p):_age(p._age),_name(p._name){cout << "person(person & p)" << endl;}//子类拷贝构造student(const student& st):person(st),_num(st._num){}

赋值重载:

先来看看下面这种写法的赋值运算符重载可行不?

答案是不行的,此时父类的operator=和子类operator=构成隐藏,它会去调用子类的operator=。

然后就会进入死循环

解决办法:在operator=前面指定父类的作用域。

person::operator=(st);

	//父类(基类)的赋值重载person& operator=(person& p){if (&p != this){_age = p._age;_name = p._name;}return *this;}//子类(派生类)的赋值重载student& operator=(student& st){if (this != &st){operator=(st);_num = st._num;}return *this;}

析构函数:

🍩析构函数名称变化

由于析构函数的名称最后都会被处理成destruct,所以父类的析构和子类的析构是构成隐藏关系的。要想调用父类的析构函数,必须指定在父类的作用域。

🍩父类和子类的析构顺序

必须保证先析构子类,再析构父类。

由于在构造函数中,初始化列表是按照声明的顺序进行,父类的声明在子类的前面,所以可以保证父类先初始化,子类后初始化。所以构造函数可以只调用子类构造函数。

但是在析构中,编译器会在子类对象声明周期结束时,先调用子类析构,然后再调用父类析构。

	~person(){cout << "~person()" << endl;}	~student(){cout << "~student()" << endl;}

继承和友元:

友元关系不能继承,父类的友元不能访问子类的私有成员。父亲的朋友不一定是我的朋友。

继承和静态成员:

如果父类定义了静态成员,那么在整个继承体系中,就只有这样一个成员,不管派生出多少个子类,都只有一个。

菱形继承:

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

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

菱形继承 菱形继承是多继承的一种特殊情况。

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。

继承的总结和反思:

1.在实际中,一定不要设计出菱形继承。

2.类复用的方式有继承组合两冲方式。

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

优先使用组合,再是继承。

3.继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。


4.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。


5.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。


http://www.mrgr.cn/news/74916.html

相关文章:

  • 287. 寻找重复数(二分查找)
  • 搭建Python2和Python3虚拟环境
  • 计算机代码python代做matlab编程c语言编写接单matlabqt开发java
  • TypeScript 中的三斜杠指令语法
  • 10款PDF翻译工具的探索之旅:我的使用经历与工具特色!!
  • 访问网页的全过程(知识串联)
  • 一级注册消防工程师《消防安全技术实务》真题及详解
  • 1.0版-结构化(经典)软件开发方法: 需求分析阶段+设计阶段
  • 自闭症机构解析:去机构是否是最好的选择?
  • openwebui二改界面环境搭建
  • 通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制
  • RK3568笔记六十九: 事件回调处理之Libevent 简单使用
  • 就是这个样的粗爆,手搓一个计算器:加倍时间计算器
  • 不吹不黑,客观理性深入讨论国产编程语言
  • cmake 语法
  • Dolby TrueHD和Dolby Digital Plus (E-AC-3)编码介绍
  • RegEnumKeyExW函数遍历注册表的错乱问题
  • 修改电脑ip
  • Java线程的sleep和wait的区别
  • 5 for循环——抽奖概率计算器
  • Puppeteer教程:使用CSS选择器点击和爬取动态数据
  • Apache Paimon、Apache Hudi、Apache Iceberg对比分析
  • 最懂AI算法的软件,MATLAB实战深度学习大模型带给我不少惊喜!
  • JacksonObjectMapper的作用
  • 【深度学习】神经网络优化方法 正则化方法 价格分类案例
  • Android Studio 中三方库依赖无法找到的解决方案