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

【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.kler.cn/a/402140.html

相关文章:

  • 【读书笔记-《网络是怎样连接的》- 7】Chapter3_2 路由器
  • 软件测试之测试用例扩展
  • springBoot插件打包部署
  • 10万字208道Java经典面试题总结(2024修订版)- SpringBoot篇下篇
  • 语义通信论文略读(十六)多任务+中继通道
  • macbook外接2k/1080p显示器调试经验
  • DBSCAN聚类——基于密度的聚类算法(常用的聚类算法)
  • HarmonyOS4+NEXT星河版入门与项目实战-------- Text 组件与国际化实现
  • 魔乐社区平台下载书生模型
  • DNS协议详解:原理、查询过程及常见问题
  • How to install rust in Ubuntu 24.04
  • NAT网络地址转换——Easy IP
  • git操作总结
  • 在Unity中实现电梯升降功能的完整指南
  • 关于selenium元素找不到的问题(Unable to locate element: {“method“:“xpath“,“selector“:“)
  • 使用GDB或Delve对已经运行起来的Go程序进行远程调试
  • 11.13机器学习_KNN和模型选择调优
  • 基于docker搭建mysql主从架构
  • 网络安全协议
  • git在创建分支时如何将默认分支名字设为master
  • Python 使用Django进行单元测试unittest
  • 活着就好20241120
  • I.MX6U 裸机开发12.主频修改和PLL配置
  • 用PHP实现一个简单的http服务器
  • 学习记录:js算法(九十八):课程表 II
  • 【Python数据可视化分析实战】数据爬取—京东手机品牌信息数据爬取和数据分析与可视化