C++干货 --类和对象(二)

前言:

         上文中,我们介绍了类这一重要知识点,包括为什么要有类、类的使用方法、封装、以及对象实例化。详情可以去看我的文章:写文章-CSDN创作中心C++干货 --类和对象(一)-CSDN博客写文章-CSDN创作中心

这篇文章,我们简单分析一下默认成员函数这一重要知识点。

默认成员函数: 

        如果一个类中什么也没有,我们称之为空类(占有1个字节的空间。不记得就翻前面的博客)。

但是空类也不是什么也没有,当类中什么也没有时,编译器会自动生成6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

构造函数 

          构造函数的概念:

                  在C++中,构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

   构造函数的特性: 

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

           特性:

            1、构造函数名与类名相同
            2、构造函数无返回值(不是void类型,而是完全没有返回值)

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

                        当在C++中创建一个类的对象(即对象实例化)时,编译器会自动调用与提供的参数列表匹配的构造函数来初始化该对象。这个过程是自动的,无需显式调用构造函数。具体来说,当你在代码中声明一个类的变量时(这通常被称为对象的实例化或对象的创建),编译器会自动为该对象分配内存,并调用适当的构造函数来初始化该对象的成员变量。这个过程在对象的生命周期开始时发生,且只发生一次

          4. 构造函数可以重载。             

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明  

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

1、

2、

6. 不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?

     d1对象调用了编译器生成的默认构造函数,但是d1对象_year/_month/_day,依旧是随机值。反而我们给定参数的d2对象,成功打印了目标数据。那这个默认构造函数有什么用呢? 

  解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数。  

举个例子:

       总结一下,C++98规定,默认构造函数,对内置类型不做处理,对自定义类型成员调用它的默认构造。

       但是C++11,进行了改进,允许在声明的位置给内置类型添加缺省值(还是不允许对内置类型做处理)。

  根据结果,我们给了缺省值的内置类型,编译器才按缺省值处理,不给,还是随机值。

       总之,大多数情况下,我们都需要自己写构造函数,默认构造函数尽可能是不需要写构造函数的时候,再放任编译器自己生成。

          7、无参的构造函数全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。

              注意:半缺省的构造函数不是默认构造函数,因为它至少需要一个非缺省的参数。然而,在某些情况下,它可以像默认构造函数那样使用(即只提供那些有默认值的参数的子集),但在其他情况下则需要提供至少一个非缺省的参数。

               所以,只要是不需要传参就可以调用构造函数的,都可以叫默认构造函数。日常中我们最推荐的是写全缺省构造函数。

析构函数

        析构函数的概念:

                      前面我们讲,构造函数是C++用来创造对象的,那么我们只创造对象吗?学习C语言时,我们讲到,我们有时候是有需求在堆上开辟空间的,而这部分的空间是需要我们自己最后手动清理的,否则会造成内存泄漏。像链表、栈,我们经常会记得初始化而忘记最后的destroy。

                      C++ -- 我们的救星带着析构函数向我们走来了。与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作             

      析构函数的特性:
     1、~    析构函数名是在类名前加上字符 ~。         
     2、析构函数无参数无返回值。

         3、一个类只能有一个析构函数。作为默认成员函数,若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(析构函数不能被重载的一个重要原因是它用于在对象生命周期结束时执行清理操作,特别是释放由对象分配的资源。由于每个对象只能有一个销毁的过程,因此每个对象也只需要一个析构函数来执行这个操作)
         4、对象生命周期结束时,C++编译系统系统自动调用析构函数。(最爽的一点就在这,妈妈再也不用担心我忘记destroy了。)

         

(我们没有调用析构函数,编译器确实自己调用了该函数。)

注意:

        值得注意的是,析构函数是对象被销毁时自动调用,也就说,虽然一个类里面只有一个析构函数,但是我们有几个对象,销毁时析构函数就要调用几次。

      上面的代码中,为什么会打印 ~Time() 呢?

       实际上,我们有一句总结: 内置类型不做处理(C++98),自定义类型调用它的构造/析构。

           上面的代码,我们创建了Date类的对象,在函数栈帧销毁时,Date类中的四个成员:_year, _month, _day三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收,而_t是Time类的对象,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁

         讲了这么多析构函数的好处,它那么的懂事,编译器会自动调用,我们不显示提供,编译器会提供默认的析构函数。那么我们就真的不需要管析构函数了吗?

         实际上,关于析构函数是否自己写,如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数如果有,一定要写,否则有可能造成内存泄漏。

       ps:所以,家人们,重要的事没人能帮你承担,人出生在这个世界还是需要承担某些躲不开的责任的。省心的析构函数也不是完全省心,能自己写还是尽量自己写。

拷贝构造函数:

        拷贝构造函数的概念:

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

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

构造函数的使用方式:

为什么一定要用引用呢?传值方式为什么会引发无穷递归调用呢??

答:因为C++规定,自定义的类型都会调用拷贝构造。

而如果我们这时的拷贝构造采用的是传值的方式:

那么调用拷贝构造要先传参,而这时传参,又会构建新的构造函数

而引用,就是最完美的解决方式: 使用引用可以避免在拷贝过程中产生递归调用。因为引用直接指向原始对象,而不是创建对象的副本。(d2是d1的别名)

详情看我的博客:C++干货--引用-CSDN博客

        3、深拷贝与浅拷贝:
              1、浅拷贝:若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝(内置类型成员),这种拷贝叫做浅拷贝,或者值拷贝。

拷贝构造也是一个默认成员函数,他与拷贝、析构不同的是,默认拷贝时它对内置类型做了处理。

          那么默认拷贝构造函数的自定义类型呢??它是被如何处理的?

       自定义类型成员调用它的拷贝构造。

  

         编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?  为什么还会有深拷贝的概念呢??

           2、深拷贝:

举个例子:

这里我们并没有写拷贝构造,应由编译器默认生成,也就是进行浅拷贝

为什么呢?

原因还是出在浅拷贝它确实是按字节将对象进行了拷贝

但是,它将地址也原模原样的拷贝过去了啊!!!

我们上面提到,析构函数是有几个对象,调用几次。

那我析构函数第一次将一个对象资源给释放了,地址也没了。与它地址相同的那哥们咋办??一块空间能释放两次吗?如果这块空间已经被分配给别人了,咋办?

        所以深拷贝就是解决这样的问题的。(不是说浅拷贝就不好,但是我们要具体问题具体分析)

那如何深拷贝呢?

自己来呗,自己写。我们得看对象的地址多大,然后开一块同样大小的空间,再将其他资源放到这块独立的空间。

空间不同,自然析构也没啥问题。

所以,拷贝构造到底要不要写?

我们之前说过,自定义类型调用它的拷贝构造,

q1和q2实际上也是完成的深拷贝,但是活是自定义类型调用它的拷贝构造干的。

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

         传值传参,会有很大的风险,还是建议使用引用传参比较安全。

赋值运算符重载

            引子:

            上文中,我们经常以Date即日期类来举例,那么,有时候我们可能需要比较日期的大小。

在C语言中我们是怎样做的呢??

如果是内置类型,编译器有自己的一套指令集可以完成一系列的操作,但是自定义类型,由于可能相当复杂,所以编译器并不支持像d1<d2这样的操作,因此我们只能自己完成。

这样是可以实现我们的目的,但是这样的写法很考验我们的素养

不看代码,你能知道我们具体在干嘛吗?

为了增强代码的可读性,减少此类代码的出现,祖师爷推出了赋值运算符重载这样的概念

     运算符重载

            C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

            函数原型:返回值类型 operator操作符(参数列表)

  operator的写法:          

那么上面的日期类我们就可以写成这样:

 但是,祖师爷搞一个这样的东西出来,肯定还有更深的目的,他是想让我们能像使用内置类型一样用自定义类型:

这样是不是就很好分辨了?

      不知道大家有没有意识到,这段代码的实现,取决于我们将class类里的private给注释掉了?

       我们能在类外面随便访问成员吗?

 

 这里我们有两种解决方法:

        1、提供函数: 

          2、让其成为成员函数:

这里的原因是因为:成员函数默认有一个this指针作为第一个参数。

因此我们的写法都要发生改变:

这里我们就变成了调用成员函数

   终于铺垫完了,下面我们正式分析运算符重载。

            运算符重载,与函数重载不同,函数重载是允许函数名相同,参数不同的函数存在,而运算符重载是指可以让自定义类型的对象可以用运算符,可以理解为通过一个函数定义该运算符的行为。          

    注意:

            1、 不能通过连接其他符号来创建新的操作符:比如operator@(必须是C/C++中存在的)

             2、重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义(不能重载运算符改变内置类型的行为)

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

             4、 .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载。

      赋值运算符重载

              前面我们谈到拷贝构造:指同类型的已经存在的对象,进行初始化要创建的对象,而赋值重载是指已经存在的对象,一个拷贝赋值给另一个

               为了实现上图中的d1 = d2,所以我们要赋值运算符重载,那么如何赋值运算符重载呢??

       这样,赋值运算符重载就算完成了吗?

       C语言中,我们好像可以连续赋值的吧?

出问题了。

有问题我们就要解决问题。

复习:我们都记得,连续赋值是从右到左依次赋值(没括号的情况下),拿上图举例,10 先赋值给j,该表达式有返回值(即左操作数)然后该返回值作为右操作数又赋给了i。那么对象d1,d2也应该符合该行为才行。也就是说,d2=10该表达式应有返回值d2

但是=没有找到返回值也就是d2,所以这里我们要实现连续赋值,就要有返回值(左操作数),代码应有下面的改进:

改进之后,代码也就支持连续赋值了。

但是,之前我们提到,传值返回有很大的风险,传值返回,返回的是它的拷贝,而这样就要调用拷贝构造。所以我们最好需要干嘛?

当然是引用了。

当然,如果有需要,还需要加Const,即常引用。

还有一个小问题,会不会有人我赋我自己呢?

这样有意义吗?

所以:

          赋值运算符重载的格式:

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

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

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

          用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。(注 意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。)          

                   作为我们默认成员函数的一员,如果我们不写,是不是也会默认生成呢?

事实证明,确实如此

实际上,与拷贝构造函数相同,赋值重载也是对内置类型进行值拷贝,自定义类型会去调用它的赋值重载。

         但是,这也绝不是我们不用学习它的理由,因为,与构造函数相同,意味着,如果没有涉及资源申请时,是否写都可以;一旦涉及到资源申请 时,则是一定要自己写的,否则就是浅拷贝。

 

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

              原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。     

         前置++和后置++重载   

                 在C语言中,前置++和后置++的区别是,一个先参与运算再自增,另一个是先自增然后再参与运算。但是因为运算符重载,我们有这样的困境:

哪个是前置?哪个是后置?

所以,在C++中为了区分这两种自增方式,祖师爷做了以下特殊处理:        

 

也就是说,为了做区分,我们有意的给后置++增加一个int类型的参数。

具体的细节上图都呈现了。

const成员函数:

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

举个例子:

该对象的打印能成功吗?

这里就是典型的权限的放大。指针和引用在传递时,权限可缩小,但是不能放大。因为有const,隐含this指针也需要受到const的约束。

          也就是说,const修饰类成员函数,实际上是将权限进行了缩小     

      const成员函数的写法:

由于this指针是隐含的,我们只能在函数内部用,我们在实参和形参位置都不能动,所以,我们只能这样改:

这里的const修饰的是this指针指向的内容

这样,我们就能正常调用,就是权限的平移

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

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

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

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

注意:加不加const,取决于我们成员函数到底是对成员变量只 读 的函数,还是读 写 的函数。

对成员变量只 读 的函数,建议添加const,这样const对象和非constd对象都能用。

对成员变量 写 的函数,建议不添加const,否则不能修改成员变量

取地址及const取地址操作符重载 

               

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

           

           

           

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

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

相关文章

短视频矩阵系统4年独立开发正规代发布接口源码搭建部署开发

1. 短视频矩阵源码技术开发要求及实现流程&#xff1a; 短视频矩阵源码开发要求具备视频录制、编辑、剪辑、分享等基本功能&#xff0c;支持实时滤镜、特效、音乐等个性化编辑&#xff0c;能够实现高效的视频渲染和处理。开发流程主要包括需求分析、技术选型、设计架构、编码实…

保险业务管理系统的设计与实现(论文 + 源码)

保险业务管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89361419 保险业务管理系统的设计与实现 摘要 历经二十余年的高速发展&#xff0c;我国保险行业的市场竞争已经达到白热化的程度&#xff0c;在同一个城市往往有数十家主体参与保险业务的竞争。保…

vue koa post 请求代理失败问题总结

场景是在使用 koa 写接口时&#xff0c;客户端发送 post 请求&#xff0c;服务端会报下面这个错误&#xff0c;导致接口未能访问成功&#xff1a; 前端接口一直 Pending 状态&#xff0c; 解决方案&#xff1a;走的是本地 mock 数据&#xff0c;未访问服务端的接口 总结&#x…

Facebook:解锁社交媒体的无限可能性

在当今数字化时代&#xff0c;社交媒体已经成为人们生活中不可或缺的一部分。而在众多社交媒体平台中&#xff0c;Facebook无疑是最为知名和影响力最大的之一。从其创立至今&#xff0c;Facebook一直在不断地演变和发展&#xff0c;成为了连接世界的桥梁&#xff0c;也是社交媒…

以不变应万变:在复杂世界中保持初心,坚持原则

在这个日新月异、瞬息万变的世界里&#xff0c;人情世故也显得尤为复杂。我们常常会因为忙碌的生活、工作压力以及人际关系的纠葛而感到迷茫和疲惫。在面对这些复杂局面的同时&#xff0c;如何保持内心的平静&#xff0c;坚持自己的原则&#xff0c;并在变幻莫测的环境中持续成…

【调试笔记-20240521-Linux-编译 QEMU/x86_64 可运行的 OpenWrt 固件】

调试笔记-系列文章目录 调试笔记-20240521-Linux-编译 QEMU/x86_64 可运行的 OpenWrt 固件 文章目录 调试笔记-系列文章目录调试笔记-20240521-Linux-编译 QEMU/x86_64 可运行的 OpenWrt 固件 前言一、调试环境操作系统&#xff1a;Ubuntu 22.04.4 LTS编译环境调试目标 二、调…

一个专为程序员设计的精致 Java 博客系统

大家好&#xff0c;我是 Java陈序员。 今天&#xff0c;给大家介绍一个设计精致的博客系统&#xff0c;基于 Java 实现&#xff01; 关注微信公众号&#xff1a;【Java陈序员】&#xff0c;获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。 项目介绍 bolo-solo …

抄表:现代生活中的数据采集关键

1.界定与发源 抄表&#xff0c;简单的说&#xff0c;指从各种各样计量机器设备(如智能水表、电度表、天然气表等)载入做好记录使用量的全过程。这一概念自工业化时代至今就出现了&#xff0c;最初由人工进行&#xff0c;伴随着科技创新&#xff0c;如今已经演化出自动化和远程…

Netflix Conductor整合Apache Seata实现支持分布式事务的服务编排方案

一、背景 Netflix Conductor是Netflix开源的一个微服务编排引擎。它旨在简化和自动化微服务架构中复杂的业务流程和工作流处理。Conductor允许开发人员使用声明性的方式定义工作流&#xff0c;将多个服务和任务组合成一个完整的业务流程。它提供了一个用户友好的UI界面&#xf…

echarts- 热力图, k线图,雷达图

热力图 热力图可以看成是一种矩形的散点图。 热力图的矩形受itemStyle的影响。 通常配合visualmap组件来根据值的大小做颜色的变化。 热力图主要通过颜色去表现数值的大小&#xff0c;必须要配合 visualMap 组件使用。 visualMap:视觉映射组件 let options {tooltip: {},xAx…

车辆相关识别API优化您的车辆系统

车辆相关识别API是开发者们所需的重要工具&#xff0c;它们基于先进的计算机视觉和深度学习技术&#xff0c;提供了强大的车辆识别和分类能力。这些API能够从图像或视频中快速准确地识别和提取车辆的关键信息&#xff0c;如车辆型号、品牌、颜色等。对于开发者而言&#xff0c;…

python-docx 在word中指定位置插入图片或表格

docx库add_picture()方法不支持对图片位置的设置 1、新建一个1行3列的表格&#xff0c;在中间的一列中插入图片 from docx import Document from docx.shared import Pt from docx.oxml.shared import OxmlElement from docx.enum.text import WD_ALIGN_PARAGRAPHdef add_cen…

Jenkins安装 :Aws EC2下Docker镜像安装

1 安装docker # 安装docker $ sudo yum install -y docker# 启动docker daemon $ sudo systemctl start docker# 用户加入docker组 $ sudo usermod -aG docker username 2 docker安装jenkins $ docker pull jenkins/jenkins:lts# 安装成功 $ docker images REPOSITORY …

动手学深度学习22 池化层

动手学深度学习22 池化层 1. 池化层2. 实现3. QA 课本&#xff1a; https://zh-v2.d2l.ai/chapter_convolutional-neural-networks/pooling.html 视频&#xff1a; https://www.bilibili.com/video/BV1EV411j7nX/?spm_id_fromautoNext&vd_sourceeb04c9a33e87ceba9c9a2e5f0…

第三方软件测试机构进行代码审计需要哪些专业的知识?

代码审计 进行代码审计需要专业的知识&#xff0c;包括编程语言、操作系统、数据库、网络知识以及安全知识等。 1.编程语言知识是进行代码审计的基础&#xff0c;因为你需要理解代码的语法和结构。对于不同的应用程序&#xff0c;你需要了解其所使用的编程语言的特点和语法规…

IO多路复用模型原理

在linux没有实现epoll事件驱动机制之前,常规的手段是选择select和poll等IO多路复用的方法来实现并发服务程序。但是在大数据、高并发、集群情况下,select和poll的性能瓶颈就出现了,于是epoll就诞生了 Select select函数监视的文件描述符分三类:writefds、readfds和exceptf…

Windows64位操作系统安装汇编语言环境

1、下载好MASM工具, 并存放在指定路径 2、再安装一个DOSBox 3、打开DOSBox, 并使用 mount 将刚刚存放MASM的路径挂在到C盘&#xff0c;然后进入C盘就可以使用 MASM的命令工具了。 例如&#xff1a; mount c G:\install\study\MASM C:但是DOSBox默认打开的窗口是很小的&…

openwrt 官方版 安装配置 AdGuard Home + smartdns 告别广告烦扰 教程 软路由实测 系列五

1 安装 adguard home opkg update opkg install adguardhome #启动 /etc/init.d/adguardhome start /etc/init.d/adguardhome enable #查看 rootOpenWrt:~# ps| grep AdGuardHome5101 root 1233m S /usr/bin/AdGuardHome -c /etc/adguardhome.yaml -w /var/adguardhom…

深入解读力扣154题:寻找旋转排序数组中的最小值 II(多种方法及详细ASCII图解)

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

深入了解Nginx(一):Nginx核心原理

一、Nginx核心原理 本节为大家介绍Nginx的核心原理,包含Reactor模型、Nginx的模块化设计、Nginx的请求处理阶段. &#xff08;本文源自微博客,且已获得授权&#xff09; 1.1、Reactor模型 Nginx对高并发IO的处理使用了Reactor事件驱动模型。Reactor模型的基本组件包含时间收集…