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

目录

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.mzph.cn/news/722251.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5 C++ TPS开发 学习记录(九

p20 首先我们现在有一个多人游戏的系统类MultiplayerSessionsSubsystem 在这个系统内提供了很多会话系统的接口SessionInterface 当现在我们有一些SessionInterfaceDelegates的委托,这个委托的来源是SessionInterface,所以我们使用的委托可以接收到来自SessionInterface的消息(…

网络学习:MPLS标签与标签分配协议—LDP

目录 前言&#xff1a; 一、MPLS标签 1、定义&#xff1a; 2、标签结构&#xff1a; 3、标签识别&#xff1a; 二、标签分配协议---LDP&#xff08;Lable Distribution Protocol&#xff09; 1、定义&#xff1a; 2、标签分配协议的种类&#xff1a; 3、LDP消息类型 …

回溯五题【Leetcode17数独/37组合问题/51N皇后/212字典树/980状态压缩】

文章目录 关于回溯37. 解数独&#xff08;37.sudoku-solver&#xff09;17. 电话号码的数字组合&#xff08;17.letter-combinations-of-a-phone-number&#xff09;51. N皇后&#xff08;51.n-queens&#xff09;212. 单词搜索 II&#xff08;212.word-search-ii&#xff09;简…

K次取反后最大化的数组和 加油站 分发糖果 柠檬水找零

1005.K次取反后最大化的数组和 力扣题目链接(opens new window) 给定一个整数数组 A&#xff0c;我们只能用以下方法修改该数组&#xff1a;我们选择某个索引 i 并将 A[i] 替换为 -A[i]&#xff0c;然后总共重复这个过程 K 次。&#xff08;我们可以多次选择同一个索引 i。&a…

安装算法依赖时版本报错,依赖之间对应版本

困惑了很久&#xff0c;毕竟不是计算机专业专业出身&#xff0c;才知道安装深度学习算法各个依赖之间是有版本对应关系的。 &#xff08;本文使我随笔记录&#xff0c;无价值&#xff09; 比如&#xff1a; 再比如&#xff1a; 由于我第一步安装cuda时就和其他博主不一致&…

Vue基础入门(2)- Vue的生命周期、Vue的工程化开发和脚手架、Vue项目目录介绍和运行流程

Vue基础入门&#xff08;2&#xff09;- Vue的生命周期、Vue的工程化开发和脚手架、Vue项目目录介绍和运行流程 文章目录 Vue基础入门&#xff08;2&#xff09;- Vue的生命周期、Vue的工程化开发和脚手架、Vue项目目录介绍和运行流程5 生命周期5.1 Vue生命周期钩子5.2 在creat…

docker pull 拉取失败,设置docker国内镜像

遇到的问题 最近在拉取nginx时&#xff0c;显示如下错误&#xff1a;Error response from daemon: Get “https://registry-1.docker.io/v2/”: net/http: request canceled (Client.Timeout exceeded while awaiting headers)。 这个的问题是拉取镜像超时&#xff0c;通过检索…

c语言经典测试题11

1.题1 #include <stdio.h> int main() { int a[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, *p a 5, *q NULL; *q *(p5); printf("%d %d\n", *p, *q); return 0; }上述代码的运行结果是什么呢&#xff1f; 我们来分析一下&#xff1a;我们创建了一个数…

第1题:两数之和

题目内容&#xff1a; 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。…

数据持久层框架:MyBatis-Plus

数据持久层框架&#xff1a;MyBatis-Plus 前言注解代码生成器CURD接口Service CRUD 接口Mapper CRUD 接口 条件构造器QueryWrapper和UpdateWrapperallEqeq、negt、ge、lt、lebetween、notBetweenlike、notLike、likeLeft、likeRight、notLikeLeft、notLikeRightisNull、isNotNu…

C 判断

判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 false。 下面…

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…

代码随想录训练营第37天 | LeetCode 738.单调递增的数字、LeetCode 968.监控二叉树、

目录 LeetCode 738.单调递增的数字 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;贪心算法&#xff0c;思路不难想&#xff0c;但代码不好写&#xff01;LeetCode:738.单调自增的数字_哔哩哔哩_bilibili 思路 ​​​​​​LeetCode 968.监控二…

代码随想录算法训练营第十四天| 144. 二叉树的前序遍历 ,145. 二叉树的后序遍历,94. 二叉树的中序遍历

两种写法&#xff0c;递归和非递归写法 递归&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : va…

基于协同过滤的旅游推荐系统设计与实现

基于协同过滤的旅游推荐系统设计与实现 在当今旅游业蓬勃发展的背景下&#xff0c;人们对于旅游体验的需求日益增加&#xff0c;如何为用户提供更加个性化、精准的旅游推荐成为了旅游行业的一个重要课题。为解决这一问题&#xff0c;我们设计并实现了一个基于协同过滤的旅游推…

【设计模式 03】抽象工厂模式

一个具体的工厂&#xff0c;可以专门生产单一某一种东西&#xff0c;比如说只生产手机。但是一个品牌的手机有高端机、中端机之分&#xff0c;这些具体的属于某一档次的产品都需要单独建立一个工厂类&#xff0c;但是它们之间又彼此关联&#xff0c;因为都共同属于一个品牌。我…

android开发网络通信,带你彻底搞懂Android启动速度优化

实现方案 直接依赖 这种方式实现简单&#xff0c;但是耦合太严重&#xff0c;不方便维护与开发&#xff0c;当工程逐渐增大模块逐渐增多&#xff0c;依赖关系会非常复杂&#xff0c;不推荐这种方式。 事件或广播通信 EventBus&#xff1a; 我们非常熟悉的事件总线型的通信框…

Rust学习笔记:深度解析内存管理(二)

在这个信息爆炸的时代&#xff0c;学习一门新的编程语言不仅仅是为了找到一份好工作&#xff0c;更是为了打开思维的新窗口。Rust&#xff0c;作为一门注重安全、速度和并发的系统编程语言&#xff0c;正吸引着越来越多的年轻开发者的目光。今天&#xff0c;我们将一起深入探讨…

数据结构与算法:堆排序和TOP-K问题

朋友们大家好&#xff0c;本节内容来到堆的应用&#xff1a;堆排序和topk问题 堆排序 1.堆排序的实现1.1排序 2.TOP-K问题3.向上调整建堆与向下调整建堆3.1对比两种方法的时间复杂度 我们在c语言中已经见到过几种排序&#xff0c;冒泡排序&#xff0c;快速排序&#xff08;qsor…

微信小程序云开发教程——墨刀原型工具入门(安装以及基础使用教程)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…