类与对象(二)--类的六个默认成员函数超详细讲解

目录

1.类的默认六个成员函数✒️ 

2.构造函数

2.1构造函数的概念✒️ 

2.2构造函数的特性✒️ 

 3.析构函数

3.1析构函数的概念✒️ 

3.2析构函数的特征✒️ 

4.拷贝构造函数

4.1拷贝构造函数的概念✒️ 

 4.2拷贝构造函数的特征✒️ 

4.3思考❓

4.4深拷贝和浅拷贝⭐️✒️ 

4.4.1浅拷贝⭐️✒️ 

4.4.2深拷贝⭐️✒️

4.4.3总结✒️✒️

5.赋值运算符重载

5.1什么叫赋值运算符重载?✒️ 

5.2赋值运算符重载✒️ 

5.3 前置++和后置++重载✒️ 

6.const成员✒️ 

6.1思考❓

 7.取地址及const取地址操作符重载✒️ 


1.类的默认六个成员函数✒️ 

🔎什么是默认的成员函数?当我们创建一个类,如果没有显式的定义以下六个成员函数,编译器会为我们自动生成这些函数。

🔥.构造函数

🔥析构函数

🔥.拷贝构造函数

🔥.赋值重载函数

🔥.普通对象取地址重载函数

🔥.const对象取地址重载函数

✋也就意味着,即使我们创建一个空类,编译器也自动生成这六个成员函数,只不过是隐式的

2.构造函数

2.1构造函数的概念✒️ 

🔎构造函数是一种特殊的成员函数,用于初始化类的对象。构造函数在对象被创建时自动调用,确保对象在使用之前处于合适的状态。

观察以下代码🚦

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

 ✋上述代码中的Date类可以通过Init成员函数(公有方法)给对象设置日期,但是如果我们每次创建对象的时候都要调用这个函数的话就显得比较麻烦。构造函数就能很好的解决这个问题。因为构造函数在对象在创建的时候由编译器自动调用,并且在对象的整个生命周期只调用一次

⭐️构造函数是一个特殊的成员函数,其函数名和类名相同,且没有返回值

2.2构造函数的特性✒️ 

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象

特性:

1️⃣. 函数名与类名相同。

2️⃣. 无返回值。void类型也不是

3️⃣. 对象实例化时编译器自动调用对应的构造函数

4️⃣. 构造函数可以重载。也就意味着可以有多个函数名相同参数不同的构造函数。

5️⃣.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

✋上述代码中通过无参构造创建了对象d1,对象后面不需要加括号,如果加了括号就变成了

Date d1() ,编译器会认为这行代码是在声明一个返回值为Date的函数。

观察以下代码🚦

⁉️为什么我将不带参数的构造函数注释掉后创建对象d1会报错呢?编译器不是会自动生成不带参数的构造函数吗?

上面我们已经提到,如果我们已经显式的定义构造函数那么编译器就不会再生成构造函数。✋就像上述代码中,我们其实已经显示定义了构造函数date(int y,iny m,int d),编译器就不会再生成任何的构造函数,也就不会生成Date()。当Date d1创建d1这个对象时编译器找不到Date()这个函数,也就会报错了。

 6️⃣.成员初始化列表。构造函数可以使用成员初始化列表,在构造函数体之前对成员进行初始化

7️⃣.编译器默认生成的构造函数在初始化成员变量时,对于自定义类型变量调用它们自己的构造函数,对于内置类型变量则不做处理。

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型。

观察以下代码🚦

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

8️⃣.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

⭐️注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

 3.析构函数

3.1析构函数的概念✒️ 

🔎跟构造函数相反,析构函数是用来对对象被销毁时清理的。但是值得注意的是,析构函数并不是完成对对象本身的销毁,局部对象的销毁工作是由编译器完成的。当对象被销毁的时候,会自动调用该对象的析构函数,完成对对象资源的具体清理,一般是动态资源。析构函数是最后一次使用对象执行的动作。

3.2析构函数的特征✒️ 

特征:

1️⃣. 析构函数名是在类名前加上字符 ~

2️⃣. 无参数无返回值类型

3️⃣. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

跟构造函数不同,析构函数必须无参。

4️⃣. 对象生命周期结束时,C++编译系统系统自动调用析构函数

5️⃣.类中成员的销毁过程依赖成员的数据类型。对于内置类型系统会自动回收,不需要析构函数。但是对于自定义类型,编译器就会调用该成员本身的析构函数

✋对于上面代码,Date类中有Time类类型的变量,当调用Date类的析构函数时,也会调用Time的析构函数。

6️⃣.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时(比如new在堆上开辟的空间),一定要写,否则会造成资源泄漏(内存泄漏)

✋上述类对象在初始化构造的时候会调用构造函数,用new开辟动态空间,如果我们不定义析构函数对该动态资源进行处理,默认生成的析构函数也不会回收,这样一来就会造成内存泄漏。

4.拷贝构造函数

4.1拷贝构造函数的概念✒️ 

🔎拷贝构造函数是用来用一个同类对象作为模板,生成构造一个一模一样新的对象。

⭐️拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),再用已存在的类类型对象创建新对象时由编译器自动调用。 

 4.2拷贝构造函数的特征✒️ 

1️⃣. 拷贝构造函数是构造函数的一个重载形式。

2️⃣. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

3️⃣.如果没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。默认的拷贝构造函数对于内置类型变量按字节完成拷贝,也叫浅拷贝(值拷贝)。对于自定义类型变量则调用其拷贝构造函数完成拷贝。

4️⃣.类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

举例🚦

✋上面代码中A类成员有一个指针arr指向了一片动态空间。如果启用编译器默认的拷贝构造函数进行值拷贝的话,会将指针的值再复制一遍,也就意味着与复制出来的指针指向了同一片地址。这样一来就会非常危险,因为当需要销毁对象a和对象a1的时候会调用其析构函数,也就会对同一片空间释放两次,这是不被允许的。

5️⃣. 拷贝构造函数典型调用场景🚦

🔥 使用已存在对象创建新对象

🔥 函数参数类型为类类型对象

🔥 函数返回值类型为类类型对象

4.3思考❓

 观察以下代码思考其构造函数,析构函数的调用顺序以及输出结果🚦

✋其实Test函数返回值的时候也会进行拷贝构造,但是现在很多编译器都将这一步优化掉了,所以也就看不到了。不同的编译器对以上代码的输出结果可能会有差别,比如在linux中的g++工具编译此代码的析构顺序就是3、2、1。

⭐️为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用

4.4深拷贝和浅拷贝⭐️✒️ 

🔎深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在对象拷贝过程中涉及的两个概念,主要关注于如何处理对象的成员变量(尤其是指针类型的成员变量)。

❓上面讲到,编译器自动生成的拷贝构造函数对对象的拷贝方式是浅拷贝,也就是按值拷贝。那么什么又是深拷贝?二者的区别在哪呢?

4.4.1浅拷贝⭐️✒️ 

  • 浅拷贝只是简单地复制对象的值,包括成员变量。如果对象包含指针,那么浅拷贝只是复制指针的值,而不是复制指针所指向的内容。
  • 对象的拷贝和原始对象共享相同的资源(如动态分配的内存区域),这可能导致潜在的问题,因为一个对象的修改可能会影响到另一个对象(之前出现的对一片空间释放两次)
  • 默认情况下,C++ 的复制构造函数和赋值运算符执行的是浅拷贝。

4.4.2深拷贝⭐️✒️

  • 深拷贝会复制对象的值,同时为对象的指针类型成员变量分配新的内存,并复制指针所指向的内容。这样,原始对象和拷贝对象将拥有独立的资源,对一个对象的修改不会影响另一个对象。
  • 深拷贝需要程序员显式实现,通常涉及到复制构造函数、赋值运算符或者自定义的拷贝逻辑。

4.4.3总结✒️✒️

  • 浅拷贝简单快速,但容易引发潜在的问题,特别是当对象包含动态分配的资源时。
  • 深拷贝较为安全,但由于需要额外的内存分配和复制操作,可能效率较低。在实现深拷贝时需要小心管理资源,防止内存泄漏等问题。

5.赋值运算符重载

5.1什么叫赋值运算符重载?✒️ 

🔎C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。运算符重载允许程序员自定义特定运算符的操作,以适应用户定义的类型。

🚩函数名字为:关键字operator后面接需要重载的运算符符号

注意🚦

🔥不能通过连接其他符号来创建新的操作符:比如operator@

🔥重载操作符必须有一个类类型参数

🔥用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

🔥作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

🔥 .*”  “::”  “sizeof”  “?:”  “.” 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

 举例🚦

🚩我们先定义一个日期类,再重载一个比较日期类是否相等的运算符==

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	 bool operator==(const Date& d2)
	{
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;//因为没有重载<<,所以需要用括号括起来
	cout << d1.operator==(d2) << endl;//调用成员函数的形式也可以
}

✋上述代码中的成员函数 operator==()会有一个隐式的this指针,所以在声明的时候只需要再设一个形参就可以了。

5.2赋值运算符重载✒️ 

1️⃣.赋值运算符重载格式

🔥参数类型:const T&,传递引用可以提高传参效率

🔥返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

🔥检测是否自己给自己赋值

🔥返回*this :要复合连续赋值的含义

 Date& operator=(const Date& d) {
	 if (this != &d) {//如果参数是对象本身,那就不用继续赋值
		 _year = d._year;
		 _month = d._month;
		 _day = d._day;
	 }
	 return *this;
 }

2️⃣.赋值运算符只能重载成类的成员函数不能重载成全局函数

✋✋上面我们已经讲过,赋值运算重载函数是默认成员函数,如果我们不在类中显式定义那么编译器就会自动生成一个赋值运算符重载函数。这样一来如果我们再全局定义了也一个赋值运算重载函数,就会和编译器生成的重载函数起冲突。所以,赋值运算符重载函数只能定义成类成员。

🚩在《C++ prime》第5版p500页有提到

3️⃣. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝(拷贝而不是拷贝构造)。

注意🚦内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。 

4️⃣如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

⭐️跟上面拷贝构造函数需要自己显式定义的道理是一样的,如果类中涉及到资源的管理,比如有一个指针arr维护了一片动态空间。这样的类就需要自己实现一个赋值重载函数,不然靠编译器自动生成的函数只会复制一个相同值的指针,也就是指向同一片空间的指针,这样就容易发生错误。

5.3 前置++和后置++重载✒️ 

🔎通过上面对运算符重载的学习,我们知道对于一个操作符来说,顺序非常重要,就像i++和++i的含义是不一样的。前置++和后置++重载又该怎么写呢?

前置++

对于一个日期类的对象来说,+1其实就是往后加一天。

Date& operator++() {//前置++
	_day++;
	return *this;
}

✋返回+1之后的结果 ,this指向的对象函数结束后不会销毁,故以引用方式返回提高效率

⭐️后置++

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载

C++规定后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。

	Date operator++(int) {//后置++
		Date temp(*this);
		_day++;
		return temp;
	}

后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存 一份,然后给this+1。又temp是临时对象,因此只能以值的方式返回,不能返回引用。

6.const成员✒️ 

🔎将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。 

🔎由于我们的类里this指针是隐式的,想要用const修饰this指针该怎么办呢?

🔎c++规定,若在类里面的成员函数想用const修饰this指针,就将const写在函数括号的外面。

举例🚦

//显示日期
void Dispaly()const {
	cout << _year << "-" << _month << "-" << _day << endl;
}

6.1思考❓

1 、const对象可以调用非const成员函数吗?

💡不能,权限放大。

2、非const对象可以调用const成员函数吗?

💡可以,权限变小

3、const成员函数内可以调用其它的非const成员函数吗?

💡不能,权限放大

4、非const成员函数内可以调用其它的const成员函数吗?

💡能,权限变小

 7.取地址及const取地址操作符重载✒️ 

 🔎这两个重载一般都不用自己重新定义,用编译器自动生成的就够了

class Date
{ 
public :
 Date* operator&()
 {
 return this ;
}
 
 const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

✋ 这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/431659.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【[STM32]标准库-自定义BootLoader】

[STM32]标准库-自定义BootLoader BootloaderBootloader的实现BOOTloader工程APP工程 Bootloader bootloader其实就是一段启动程序&#xff0c;它在芯片启动的时候最先被执行&#xff0c;可以用来做一些硬件的初始化或者用作固件热更新&#xff0c;当初始化完成之后跳转到对应的…

CDN是什么?CDN能为我们做什么?

CDN 概念 CDN&#xff0c;全称为 Content Delivery Network&#xff0c;意为内容分发网络&#xff0c;是一种通过在全球各地部署服务器节点来加速内容传输的网络架构。 传统上&#xff0c;当用户访问一个网站或应用时&#xff0c;请求会直接发送到托管网站的服务器。但是&…

[前端][死循环]问题发现[easyui]

文章目录 问题描述问题细节 解决思路综合分析 解决办法 问题描述 页面点击按钮跳转弹窗页面回显出数据 此弹窗页面中有年份&#xff0c;类型等&#xff0c;当选中年份/类型会重新触发回显方法(onSelect 中调用方法)&#xff0c;回显对应年份/类型得数据 问题细节 最开始调试…

linux小记(1)

基本概念&#xff1a;不依靠扩展名来区分文件类型 好处&#xff1a;除了文本文件其他所有windows文件都无法在Linux下运行&#xff0c;包括病毒木马。 坏处&#xff1a;所有的软件都需要对linux单独开发 习惯用后缀来区分文件&#xff0c;方便管理。 -压缩包&#xff1a;*.…

Springboot配置MySQL数据库

Springboot配置MySQL数据库 一、创建springboot项目&#xff0c;并添加如下依赖 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope> </dependency>二、在applica…

从事测绘地信,你需要这些插件、软件、小工具、图源...

特别声明&#xff0c;本篇是来自公众号GIS前沿的资源&#xff0c;看着比较好&#xff0c;特别给大家推荐。加粗样式 今天&#xff0c;我们又来汇总了一些工作中实用的插件、小工具、数据等等&#xff0c;小助手又来帮你提高工作效率了****。 因为小助手每年都会总结一次&…

【网站项目】308学生档案管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

UOS 20 安装redis 7.0.11 安装redis 7.0.11时 make命令 报错 /bin/sh: cc: command not found

UOS 20 安装redis 7.0.11 1、下载redis 7.0.112、安装redis 7.0.113、启动停止redis 7.0.114、安装过程问题记录 UOS 20 安装redis 7.0.11 安装redis 7.0.11时 make命令 报错 /bin/sh: cc: command not found、zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such fil…

cuda python torch 虚拟环境配置

以下是Pytorch和CUDA对应的版本 以下是Pytorch和Python对应的版本 检查cuda与Python版本是否匹配 import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.empty(3,4,devicecuda))cuda 删除cuda conda uninstall cudatoolkit --forceconda u…

MySQL的初学者教程—Navicat的基本操作方法

MySQL的初学者教程—Navicat的基本操作方法 1、运行Navicat 双击桌面的Navicat 12 for MySQL。 2、新建MySQL连接 点击【测试连接】。 zyyMySQL的连接创建成功&#xff01; 3、新建数据库 4、新建表 点击【保存】 表【usermanage】建好了。 点【usermanage】的鼠标右键&#…

基于springboot实现流浪动物救助网站系统项目【项目源码+论文说明】

基于springboot实现流浪动物救助网站系统演示 摘要 然而随着生活的加快&#xff0c;也使很多潜在的危险日益突显出来&#xff0c;比如在各种地方会发现很多无家可归的、伤痕累累的、可怜兮兮的动物&#xff0c;当碰到这种情况&#xff0c;是否会立马伸出双手去帮助、救助它们&…

Golang 开发实战day02 - Print Formatting

Golang 教程02 - Print&#xff0c;Formatting Strings Go语言提供了丰富的格式化字符串功能&#xff0c;用于将数据格式化为特定格式的字符串。本课程将详细介绍Go语言中Print和Formatting Strings的用法&#xff0c;并提供代码示例供大家参考。 Print 类型及使用 1.Print …

⭐北邮复试刷题2369. 检查数组是否存在有效划分__DP (力扣每日一题)

2369. 检查数组是否存在有效划分 给你一个下标从 0 开始的整数数组 nums &#xff0c;你必须将数组划分为一个或多个 连续 子数组。 如果获得的这些子数组中每个都能满足下述条件 之一 &#xff0c;则可以称其为数组的一种 有效 划分&#xff1a; 子数组 恰 由 2 个相等元素…

基于Springboot的足球俱乐部管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的足球俱乐部管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍: 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff…

【JavaEE】_Spring MVC项目使用数组与集合传参

目录 1. 使用数组传参 1.2 传递单个参数 1.3 传递多个名称相同的参数 1.3.1 关于urlencode 2. 使用集合传参 1. 使用数组传参 创建一个Spring MVC项目&#xff0c;其中 .java文件内容如下&#xff1a; package com.example.demo.controller;import com.example.demo.Per…

【快速上手QT】07-对话框QDialog

QDialog 今天讲一个我们这个系列的第一篇就提到的东西&#xff1a;QDialog。 相信经过前几篇的学习&#xff0c;大家应该是能够通过QT助手来对QDialog有个初步的了解。 我们就直接来测试一下。 #include "Zhetu.h"#include <qdebug.h> #include <QPushBu…

MySQL从入门到实战

MySQL从入门到实战 1.连接数据库 在操作数据库之前&#xff0c;需要连接它&#xff0c;输入命令&#xff1a;mysql -u用户名 -p密码。 2.创建数据库 创建完数据库之后我们可以通过show databases;命令查看MySQL中已存在的数据库。[请注意&#xff1a;数据库名区分大小写。] 3…

异常-Exception

异常-Exception Java语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 执行过程中所发生的异常事件可分为两大类 1&#xff09;Error(错误)&#xff1a;Java虚拟机无法解决的严重问题。如JVM系统…

2024.3.5每日一题

LeetCode 到达目的地的方案数 题目链接&#xff1a;1976. 到达目的地的方案数 - 力扣&#xff08;LeetCode&#xff09; 题目描述 你在一个城市里&#xff0c;城市由 n 个路口组成&#xff0c;路口编号为 0 到 n - 1 &#xff0c;某些路口之间有 双向 道路。输入保证你可以…

INFINI Labs 产品更新 | Easysearch 1.7.1发布

INFINI Labs 产品又更新啦~&#xff0c;包括 Console&#xff0c;Gateway&#xff0c;Agent 1.23.0 和 Easysearch 1.7.1。此次版本重点修复历史遗留 Bug 、网友们提的一些需求等。以下是本次更新的详细说明。 INFINI Console v1.23.0 INFINI Console 是一款非常轻量级的多集…