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

【C++初阶】四、类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【C++初阶】三、类和对象
(面向过程、class类、类的访问限定符和封装、类的实例化、类对象模型、this指针)
-CSDN博客

 =========================================================================

                     

引入:类的六个默认成员函数

如果一个类中什么成员都没有简称为空类
但空类中并不是什么都没有任何类在什么都不写时
编译器自动生成以下六个默认成员函数
默认成员函数用户没有显式实现编译器自动生成成员函数称为默认成员函数

                     

  • 初始化和清理
    构造函数(1) -- 完成成员变量的初始化工作
    析构函数(2) -- 完成一个对象结束生命周期后的资源清理工作
                  
  • 拷贝复制
    拷贝构造函数(3) -- 使用同类对象初始化创建对象
    赋值重载(4) -- 把一个对象赋值给另一个对象
                    
  • 取地址重载
    主要是普通对象(5)const对象取地址(6),这两个很少会自己实现

                 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

一 . 构造函数(难)

构造函数的概念和特性:

                   

C++构造函数的概念:

还是假设有以下Date类:
//日期类:
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(2023, 11, 16);d1.Print();Date d1;d1.Init(2023, 11, 17);d1.Print();return 0;
}
  • 对于以上的Date类,可以通过我们自己定义的 Init共有函数方法给对象设置日期

    如果每次创建对象都需要调用该方法初始化对象成员的话

    有点麻烦而且可能会忘记初始化,那能否在对象创建时就自动进行初始化呢?

                        
  • C++为了优化C语言需要自己初始化的情况,有了一个新概念构造函数
    构造函数特殊的成员函数其名字类名相同
    创建类类型对象时由编译器自动调用进行对象的初始化
    保证每个数据成员都有一合适的初始值,并且在对象整个声明周期内只会调用一次
                      
  • 构造函数分为有参构造函数无参构造函数
    我们
    创建对象时可以设置各成员变量初始化的值
    如果
    没有设置,则对象初始化时会调用无参构造函数
    如果
    设置了,则对象初始化时会调用相应的有参构造函数

Date类 -- 图示:

                  

主函数通过构造函数创建对象 -- 图示:

                          

                          
---------------------------------------------------------------------------------------------

                    

C++构造函数特征:

                   

  • 构造函数名名相同构造函数没有返回值
    对象实例化编译器自动调用对应的构造函数
                    
  • 如果类中没有显式定义构造函数,则C++编译器自动生成一个无参的默认构造函数
    一旦用户显式定义构造函数编译器将不再自动生成构造函数
    所以如果定义了有参构造函数最好再定义一个无参构造函数
    防止创建对象时需要无参构造函数而又无法调用到
                    
  • 构造函数也是函数可以有参数,所以也可以对其设置缺省参数
    将一个有参构造函数初始化全部成员变量的构造函数
    所有参数都设置一个缺省参数全缺省构造函数),
    这样该构造函数就既实现了有参构造函数的任务
    又实现了无参构造函数的任务,因为初始化对象时如果不给初始化值
    那么有参构造函数的缺省参数会发挥作用实现无参构造函数的任务
    这样一个构造函数就可以替代有参无参两个构造函数
                
  • 构造函数支持重载虽然支持重载
    但如果已经定义了全缺省构造函数已经能够实现无参构造函数的情况下
    这时如果再定义一个无参构造函数虽然构成了构造函数重载
    但是实际调用时是会出错的,因为全缺省构造函数无参构造函数功能重复
    编译器就会不知道该调用哪个构造函数
                  
  • 无参的构造函数全缺省的构造函数都称为默认构造函数
    并且默认构造函数只能有一个否则会有调用歧义
    注意:
    无参构造函数全缺省构造函数编译器默认生成的构造函数
    都可以认为是默认构造函数
    不传参数还可以被调用的构造函数都可以叫默认构造函数
全缺省构造函数 -- 图示:

                            

编译器默认生成的构造函数的作用:
  • C++中把类型分成了内置类型基本类型自定义类型
    内置类型就是语言原生的数据类型intdouble指针……);
    自定义类型就是我们使用 class / struct / union 自己定义的类型
    关于编译器生成的默认构造函数该构造函数会对我们未定义的成员变量进行初始化
                   
  • 不同编译器初始化方式不同
    VS2013中:
    如果对象的成员变量为内置类型
    默认生成构造函数不会对其进行处理为随机值);
    如果对象的成员变量为自定义类型
    默认生成构造函数则会调用该自定义类型的默认构造函数
    VS2019情况会更复杂

    如果对象的成员变量全是内置类型
    默认生成构造函数不会对其进行处理为随机值);
    如果对象的成员变量既有内置类型又有自定义类型
    则会对其中的内置类型进行处理int类型成员变量会被初始化为0),
    对其中的自定义类型,会调用该自定义类型的默认构造函数
               
  • 所以默认生成的构造函数根据对象的成员变量的情况判断是否要对其进行处理
    如果对象的成员变量为自定义类型,就调用该自定义类型的默认构造函数
    如果是内置类型,则不进行处理为随机值
    会处理自定义类型不一定处理内置类型(看编译器),建议统一当成不会进行处理
图示:

           

  • 因此C++11中针对内置类型成员不初始化的缺陷,又打了一个补丁
    内置类型成员变量类中声明可以给默认值
    给了默认值又有定义显式构造函数的话显式构造函数为准
图示:

                     

总结:
  • 一般情况下我们都要自己写构造函数
                 
  • 成员变量如果都是自定义类型或者成员变量声明时给了缺省值
    那就可以考虑让编译器自己生成构造函数

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

二 . 析构函数

析构函数的概念和特性:

                 

C++析构函数的概念:

            

  • 通过前面对构造函数的了解,我们知道了一个对象是怎么来的
    可一个对象又是怎么没的呢?如果说构造函数是我们以前写的Init初始化函数

    那么析构函数就是我们以前写的Destroy销毁函数
                            

  • 析构函数构造函数功能相反析构函数不是完成对对象本身的销毁
    局部对象销毁工作是由编译器完成
    对象销毁时会自动调用析构函数完成对象中资源的清理工作

                          

                          
---------------------------------------------------------------------------------------------

                    

C++析构函数的特性:

                

  • 析构函数名 = 在类名前加上字符 ~” (按位取反符号
                        
  • 析构函数没有返回值函数参数
                
  • 一个类只能有一个析构函数没有显式定义编译器自动生成默认的析构函数
    析构函数不支持重载
                      
  • 对象声明周期结束C++编译系统自动调用析构函数

析构函数 -- 图示:

                            

编译器默认生成的构造函数的作用:
  • 默认生成的析构函数其行为跟构造函数的类似
    针对内置类型的成员变量析构函数不会对其进行处理
    针对自定义类型的成员变量析构函数也会调用该自定义类型的默认析构函数
                       
  • 如果类中没有申请资源析构函数可以不写直接使用编译器生成的默认析构函数
    比如之前写的Date日期类就可以不写
    而如果类中有申请资源则一定要写析构函数否则会导致资源内存泄漏
    比如Stack栈类需要显式定义析构函数进行资源清理
图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

三 . 拷贝构造函数(难)

拷贝构造函数的概念和特性:

                

C++拷贝构造函数的概念:

                     

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

图示:

                          

                          
---------------------------------------------------------------------------------------------

                    

C++拷贝构造函数的特性:

                       

  • 拷贝构造函数也是特殊的成员函数构造函数的一个重载形式
                     
  • 拷贝构造函数的参数只有一个必须是类类型对象的引用
    使用传值方式作为其参数编译器会直接崩溃因为会引发无穷递归调用
                           
  • 如果没有显式定义拷贝构造函数编译器会生成默认的拷贝构造函数
    默认的拷贝构造函数拷贝对象时会按内存存储按字节序完成拷贝
    这种拷贝叫做浅拷贝或者值拷贝
注:

在编译器生成的默认拷贝构造函数内置类型按照字节方式直接拷贝值拷贝),
自定义类型则会调用该自定义类型的拷贝构造函数完成拷贝

图示:

                

                

  • 编译器生成的默认拷贝构造函数已经可以完成字节序的值的拷贝值拷贝
    当类中没有涉及资源申请申请动态空间等
    浅拷贝已经足够使用是否显式定义拷贝构造函数都可以
    但是一旦涉及到了资源申请拷贝构造函数一定要显式定义进行深拷贝
图示:

                   

  • 拷贝构造函数典型调用场景
    使用已存在的对象拷贝创建新对象函数参数类型类类型对象
    函数返回值类型类类型对象
注:

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

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

四 . 赋值运算符重载

运算符重载的使用和注意事项

                   

引言:

对于内置类型intdouble……)数据我们可以直接对其使用运算符
假设我们有整型变量ab,我们可以对其使用
​​​​​​​a == b判断相等) 、a > b判断大小),
但对自定义类型的数据而言,就不能直接对其使用运算符
因为编译器不知道怎么判断我们自定义的类型,所以需要我们自己定义其判断的规则

图示 --  自定义类型判断规则:

                          

                          
---------------------------------------------------------------------------------------------

                    

运算符重载的使用:

  • C++为了增强代码的可读性引入了运算符重载运算符重载具有特殊函数名的函数
    ​​​​​​​也具有其返回值类型函数名字以及参数列表
    返回值类型参数列表和普通的函数类似
                    
  • 函数名字关键字operator后接需要重载的运算符符号
    加法运算符重载  --  operator+)​
                      
  • 函数原型返回值类型 operator操作符(参数列表)

图示 --  类外运算符重载:

                          

                          
---------------------------------------------------------------------------------------------

                    

运算符重载的注意事项:

  • 不能通过连接其它符号来创建新的操作符:比如operator@
                  
  • 重载操作符必须有一个类类型参数
                        
  • 用于内置类型的运算符含义不能改变
    例如内置的整型+ 不能改变其含义 ++= 是不一样的
                   
  • 重点
    作为类成员函数重载形参看起来比操作数数目少一个
    因为成员函数第一个参数为隐藏的this指针
                    
  • 注意以下五个运算符不能重载
    .* ”  、​“ :: ”  、“ sizeof ”  、“ ?: ”  、“ . ”​​​​​
图示 --  类中运算符重载:

                      

  • 一个类要重载哪些运算符主要看这个运算符对这个类来说有没有意义
    有意义可以重载没有意义不要重载
    日期类来说日期的 +() *() /() 都没有意义-() 有意义
    两个日期相减可以计算两日期相差了多少天
    日期+日期没有意义日期+整型有意义
    d1 + 100 计算d1日期的100天后的日期
图示 --  类中实现 += 和 + 运算符重载:

:“+=运算符重载中要设置返回值 -- return *this ,这里忘了写了
                     

                     


                    

赋值运算符(=)重载

                

赋值运算符 -- "=" ,赋值运算符重载就是让自定义类型也能像内置类型一样使用=

               

赋值运算符重载格式:

  • 参数类型const T&
    const修饰参数,能够防止赋值拷贝时左右值写反了,导致改变了原对象
    T& 传参引用接收右值的别名”,提高传参效率
                     ​​​​​​​
  • 返回值类型T&
    引用返回可以提高返回的效率设置返回值还为了支持=连续赋值
                     
  • 定义赋值运算符重载函数时需要检测是不是自己给自己赋值的情况
                         
  • 最终返回*this(即返回被赋值对象本身),能够符合=连续赋值的含义
                 
  • 用户没有显式实现编译器生成一个默认的赋值运算符重载函数
    行为拷贝构造函数类似
    针对内置类型成员变量:进行 值拷贝浅拷贝
    针对自定义类型成员变量:会调用该自定义类型的 赋值运算符重载函数
    注意:
    如果类中
    没有资源”(Date类),赋值运算符重载函数要不要显式定义都可以
    如果类中资源”(Stack类),赋值运算符重载函数必须要显式定义
                          
  • 赋值运算符只能重载成类的成员函数只能在类中定义重载),不能重载为全局函数
    原因

    赋值运算符重载函数如果不显式实现编译器生成一个默认的
    此时如果再在类外实现一个全局的赋值运算符重载函数
    就会和编译器在类中生成的默认赋值运算符重载函数冲突
    ​​​​​​​所以赋值运算符重载函数只能是类的成员函数

图示 -- 以Date类为例:

 ​​​​

http://www.lryc.cn/news/241902.html

相关文章:

  • js粒子效果(二)
  • 01.让自己习惯C++
  • ElementUI table+dialog实现一个简单的可编辑的表格
  • Rust语言精讲:数据类型全解析
  • 《数据结构、算法与应用C++语言描述》-代码实现散列表(线性探查与链式散列)
  • Hadoop学习笔记:运行wordcount对文件字符串进行统计案例
  • python编写简单登录系统(密码混淆加密)
  • UVA1025 城市里的间谍 A Spy in the Metro
  • 【科普知识】什么是步进电机?
  • AWS云服务器EC2实例实现ByConity快速部署
  • Docker的项目资源参考
  • wsl-ubuntu 系统端口总被主机端口占用问题解决
  • 详解自动化之单元测试工具Junit
  • 超声波雪深传感器冬季里的科技魔法
  • 2023年【熔化焊接与热切割】免费试题及熔化焊接与热切割模拟考试
  • 【数据结构】—搜索二叉树(C++实现,超详细!)
  • 机器人算法—ROS TF坐标变换
  • 路由VRRP配置例子
  • OpenGL 绘制点与三角形(Qt)
  • 究竟什么是阻塞与非阻塞、同步与异步
  • Openlayer【三】—— 绘制多边形GeoJson边界绘制
  • 用SOLIDWORKS画个高尔夫球,看似简单的建模却大有学问
  • Linux:Network: ARP被动删除的一个情况
  • 『接口测试干货』| Newman+Postman接口自动化测试完整过程
  • 根据商品链接获取拼多多商品详情数据接口|拼多多商品详情价格数据接口|拼多多API接口
  • KaiwuDB 监控组件及辅助 SQL 调优介绍
  • 双11再创新高!家电行业如何通过矩阵管理,赋能品牌增长?
  • 苏东坡最经典的诗词
  • iterator遍历赋值
  • 【从删库到跑路】MySQL数据库 — E-R图 | 关系模型