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

news2024/12/26 0:23:43

前言:

         上文中,我们介绍了类这一重要知识点,包括为什么要有类、类的使用方法、封装、以及对象实例化。详情可以去看我的文章:写文章-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取地址操作符重载 

               

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

           

           

           

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

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

相关文章

全球点赞最高的人颜廷利:真正的人生目标是什么

在那个充满生机的2024年春天&#xff0c;记者有幸对中国第一起名大师的老师颜廷利教授进行了深入的访谈。带着对其人生哲学的强烈好奇&#xff0c;记者紧张而期待地提出了问题&#xff1a;“颜教授&#xff0c;您在漫长的人生旅途中最追求的是什么&#xff1f;” 宁夏银川、山东…

【模拟面试问答】深入解析力扣163题:缺失的区间(线性扫描与双指针法详解)

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

文盘Rust -- 生命周期问题引发的 static hashmap 锁

100编程书屋_孔夫子旧书网 2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:https://github.com/jiashiwen/interactcli-rs。 春节以前看到axum已经0.4.x了,于是想看看能不能用rust做个服务端的框架。 春节…

C++的哈希 哈希表 哈希桶

目录 Unordered系列关联式容器 什么是哈希 哈希表 闭散列 载荷因子α 扩容 查找 删除 字符串哈希算法 最终代码 开散列 插入 查找 删除 最终代码 完整代码 Unordered系列关联式容器 C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0…

浅谈Docker容器的网络通信原理

文章目录 1、回顾容器概念2、容器网络3、容器与主机之间的网络连通4、交换机的虚拟实现---虚拟网桥&#xff08;Bridge&#xff09;5、Docker 守护进程daemon管理容器网络 1、回顾容器概念 我们知道容器允许我们在同一台宿主机&#xff08;电脑&#xff09;上运行多个服务&…

【蓝桥杯省赛真题44】python计算N+N的值 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析

目录 python计算NN的值 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python计算NN的值 第十四届蓝桥杯青少年组python省赛真题 一、题目要求…

VMware安装Ubuntu系统(超详细)

一.Ubuntu官网下载镜像 Ubuntu官网&#xff1a;Enterprise Open Source and Linux | Ubuntu 二.安装Ubuntu系统 选择文件->创建虚拟机新建虚拟机&#xff08;ControlN&#xff09;&#xff0c;这里直接选择典型即可 选择稍后安装系统 选择linux Ubuntu 64位 填写虚拟机名称…

PanTools v1.0.25 多网盘批量管理工具 批量管理、分享、转存、重命名、复制...

一款针对多个热门网盘的文件管理、批量分享、批量转存、批量复制、批量重命名、批量链接检测、跨账号移动文件、多账号文件搜索等&#xff0c;支持不同网盘的不同账号的资源文件操作。适用于网站站长、资源爱好者等&#xff0c;对于管理名下具有多个网盘多个账号具有实用的效果…

这方法真牛B!论文降重从81%直降1.9%

目录 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时二、获取途径三、论文从81&#xff05;降到1.9&#xff05;四、内容是别人的&#xff0c;话是自己的五、AI工具 --> 中文论文降重六、论文降重小技巧 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时 通过O…

Github 2024-05-27 开源项目周报Top15

根据Github Trendings的统计,本周(2024-05-27统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目6Rust项目3Python项目3JavaScript项目3Java项目1C#项目1C++项目1Cuda项目1C项目1Lua项目1JavaScript算法与数据结构 创建周期:2…

读人工智能时代与人类未来笔记15_改变人类经验

1. 认识世界的方式 1.1. 信仰 1.2. 理性 1.2.1. 理性不仅革新了科学&#xff0c;也改变了我们的社会生活、艺术和信仰 1.2.2. 在其浸染之下&#xff0c;封建等级制度瓦解了&#xff0c;而民主&#xff0c;即理性的人应该自治的理念崛起了 1.3. 人工智能 1.3.1. 这种转变将…

关于我转生从零开始学C++这件事:升级Lv.25

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ OK了老铁们&#xff0c;又是一个周末&#xff0c;大伟又来继续给大家更新我们的C的内容了。那么根据上一篇博…

Spring Boot 项目统一异常处理

在 Spring Boot 项目开发中&#xff0c;异常处理是一个非常重要的环节。良好的异常处理不仅能提高应用的健壮性&#xff0c;还能提升用户体验。本文将介绍如何在 Spring Boot 项目中实现统一异常处理。 统一异常处理有以下几个优点&#xff1a; 提高代码可维护性&#xff1a;…

每日AIGC最新进展(12):在舞蹈视频生成中将节拍与视觉相融合、Text-to-3D综述、通过内容感知形状调整进行 3D 形状增强

Diffusion Models专栏文章汇总&#xff1a;入门与实战 Dance Any Beat: Blending Beats with Visuals in Dance Video Generation https://DabFusion.github.io 本文提出了一种名为DabFusion的新型舞蹈视频生成模型&#xff0c;该模型能够根据给定的静态图像和音乐直接生成舞蹈…

优化FPGA SelectIO接口VREF生成电路

引言&#xff1a;FPGA设计中使用了各种PCB SelectIO™接口VREF生成电路。有时即使在以前的设计中已经成功的在电路板上设计了VREF生成电路&#xff0c;也会在VREF引脚上发现大量噪声&#xff08;200–400mV&#xff09;。大量VREF噪声的存在可能导致高性能SelectIO接口&#xf…

Jenkins部署成功后自动发通知到钉钉群

钉钉上如何配置 选择钉钉群&#xff0c;找到群设置-机器人-添加机器人 选择自定义 选择【添加】 选择【加签】&#xff0c;复制值&#xff0c;后续在jenkins里配置时会用到 复制Webhook地址&#xff0c;后面在jenkins里配置的时候要用到 Jenkins上如何配置 系统管理-插件管…

Vue3实战笔记(46)—Vue 3高效开发定制化Dashboard的权威手册

文章目录 前言Dashboard开发总结 前言 后台管理系统中的Dashboard是一种图形化的信息显示工具&#xff0c;通常用于提供一个特定领域或系统的概况。它可以帮助用户监控和分析数据&#xff0c;快速获取重要信息。可以帮助用户监控业务状况、分析数据、获取关键信息和管理资源。…

PyTorch学习笔记:新冠肺炎X光分类

前言 目的是要了解pytorch如何完成模型训练 https://github.com/TingsongYu/PyTorch-Tutorial-2nd参考的学习笔记 数据准备 由于本案例目的是pytorch流程学习&#xff0c;为了简化学习过程&#xff0c;数据仅选择了4张图片&#xff0c;分为2类&#xff0c;正常与新冠&#xf…

Golang | Leetcode Golang题解之第114题二叉树展开为链表

题目&#xff1a; 题解&#xff1a; func flatten(root *TreeNode) {curr : rootfor curr ! nil {if curr.Left ! nil {next : curr.Leftpredecessor : nextfor predecessor.Right ! nil {predecessor predecessor.Right}predecessor.Right curr.Rightcurr.Left, curr.Righ…

95.网络游戏逆向分析与漏洞攻防-ui界面的设计-ui的设计与架构

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…