zoukankan      html  css  js  c++  java
  • C++基础知识--DAY4

    今天主要讲的是类中除了构造器析构器以外的拷贝构造器,运算符重载等问题

    首先是拷贝构造器

    1. copy constructor(拷贝构造)

    其也是构造器,其地位和constructor的地位是一样的

    (1)  由普通数值做参数完成构造,constructor

    这个就是一种构造,这种数值类型的构造器主要的列表形式为:

    (2)  由同类对象做参数完成构造,copy constructor

    拷贝构造器最简单的举例

    如果不写那一段拷贝构造器则会发生报错,报错提示为:error: no matching constructor for initialization of 'Date'
    Date d(2016,8,8);

    因此此已经为一种深拷贝了,所以说需要我们认为构造拷贝构造器

    另外一种情况,若上面的程序加入Date dd(d);dd.dis(),则这个也可以直接输出和d一样的数据,发生这种可以直接完成拷贝的原因是系统提供了默认的拷贝构造器,格式比较固定,一经实现,不复存在

    (3)拷贝构造器的格式

    class A

    {

      A(const A &another)

      拷贝构造体

    }

    (4)拷此贝构造规则,不是空的,提供了一个等位拷贝机制

    (5)  系统提供的默认拷贝构造器是一种浅拷贝,shallow copy

    如果对象中不含有堆上的空间(指针指向的空间),此时浅拷贝可以满足需求,此时我们是不需要自实现的

    (6)  deep copy

    如果对象中含有堆上的空间(通过*来实现),此时浅拷贝不能满足需求,需要自实现,浅拷贝会带来重析构,即double free

    以下两种情况

    这种情况是完全没有问题的,即申请一个对象,使这个对象指向堆上的一段空间,这段代码使用的是系统的默认存在的拷贝构造器

    但是下列情况就是有问题的

    这种情况存在的问题就是

    因为等位拷贝的原因,比如在上面的代码中,我们由于在拷贝时采用等位拷贝,因此在拷贝时,将a1的地址拷贝给a2的地址,那么堆上的空间“C++ is the …..”就是同时被两个地址所指向,在进行析构时,两个地址均会进行一次析构,因此堆上的空间“C++ is the …..”会被释放两次,造成double free,因此此时我们需要自实现拷贝析构器

    (7)  拷贝析构器实现的原理

    如上所示,发生double free的原因是两个地址指向了堆上的同一段空间,如下图所示

    因此为了解决这个问题,我们需要做的就是将堆上的空间拷贝一份,实现每个地址指向一段堆上的空间,并且两段堆上的空间的内容是一样的

    因此首先应该是申请一段堆空间

    深拷贝的原理即为

    注意,两个指针的地址并未发生拷贝,而是首先在堆上申请了一段空间,然后将语句拷贝到新申请的空间中去,即a1,a2不进行复制拷贝等活动

    (8)  同类对象方法中进行穿参可以访问其私有成员,其他则不可以,拷贝构造器主要用于传参和返值

    另一种情况

    也是属于拷贝构造器

    传参的另一种情况

    相当于实参到形參有一个拷贝,因此也是会发生两次构造,两次析构,但是如果改成引用,则只会发生一次析构。

    2.  课堂实战,mystring

    (1)  sizeof(string)会出现版本的差异,我们主要考虑版本为4的情况

    (2)  string本质是对char *的包装

    (3)  默认参数做标记位

    构造顺序和析构顺序相反,先构造的后析构。

    下面在程序代码的层面上对mysyting来进行具体的解释

    a. 参见如下的代码

    在代码中,我们并未对string定义任何的东西,但是我们最终的输出为空格和china这两个,说明C++内部有对string是有相关的定义的,因此我们利用mystring来对其内部机制做一个深入的理解

    因此我们为了自己实现这个功能,我们需要写一个构造器,对于者两种情况,实际上是可以写两个构造器的,

    对于string s来说,相当于是建立一个空的构造器,内部只有一个字符串,因此我们可以将其写为

    即建立一个新的空间,在这个空间中拷贝上

    对于string s1="china",这个是需要创建一个新的空间并对这个空间赋值

     

    为了将这两种情况和并在一起,我们将最终构造器的代码写成如下

     

    b. 上述情况是考虑的是数值等对其进行赋值,对于更多的时候,可能是对象对其进行赋值

    把对象s2的内容赋给对象s1,为了实现这种赋值,我们引入拷贝构造器

    c. 在上面的情况中,对于string s1="china",可以直接将字符串进行赋值的原因是因为我们对赋值运算符=进行了重载

    d. 对于 >   <    ==    >=    <=,C++的string中也对其进行了相应的定义

     

    为了在mystring中相应的功能,我们编写了如下的程序

    e. 我们可以见如下代码

    对于字符串来说可以直接相加减也是C++相对于 C语言的扩展,因此我们也可以自己实现运算符重载+

    f. 也可以实现+=的运算符重载

     3. this 指针

    系统在创建对象时,默认生成的指向当前对象的指针,其应用是避免了名相同的问题,如果重名了系统采用的是就近原则,可以实现串联调用

    (1)  this 的使用,在哪里可以用

    打印出相同的地址,说明this确实是指向当前位置的指针

    (2)  this指针以什么样的形式存在

    this作为函数参传隐式传进来,是不占用对象的体积大小

    不论是否有this指针,Stu的大小是不变的

    (3)  使用this指针有什么好处

    下面存在一种问题

    对于这种情况,系统会报错,因为他分不清楚是在私有成员的name还是在函数形參的name,因此我们常常使用this指针指明是具体的哪一个name

    好处:

    a. 避免形參与数据成员重名

    b. 可以实现链式表达(串联,a=b=c)

    c. 赋值运算符重载

    用一个已有对象,给另一个已有对象赋值,两个对象均已创建结束后,发生的赋值行为

    类名

    {

           类名&operator=(const类名&源对象)

           拷贝体

    }

    class A

    {

    A& operator=(const A& another)

    {

           //函数体

           return *this;

    }

    }

    注意以下两种情况的区别

    第一种情况是利用已有变量创建一个新的变量,第二种就是首先创建一个新的变量,再给其赋值

    d. 系统提供默认的赋值运算符重载,也是一种浅赋值行为,如果对象中不存在由*构成的堆空间,此时默认也是满足需求的

    赋值运算符重载的格式比较固定,A& operator=(const A& another),一旦发生自实现,即默认不存在

    e. operator特性

    系统提供默认的赋值运算符重载,一经实现,不复存在

    A. 系统提供的也是等位拷贝,也就是浅拷贝,会造成内存泄漏,重析构

    B. 要实现深深的赋值,必须自定义

    自实现需要            1 自赋值

    C. 解决的问题        2 内存泄漏

                               3 重析构

    D. 返回引用,且不能用const修饰,string a,b,c; (a=b)=c; (a+b)=c

    (4)  运算符重载=  mystring operator = (const string & another)

    (5)  运算符重载==   >   < >=   <=

    (6)  运算符重载+

     +的运算符重载的原理是,本身是有两个变量的,然后我们将生成一个变量,但是生成变量的同时也会产生一个存储空间,因此我们首先将其释放掉,然后开辟一个两个字符串长度的空间

    因此写出来的代码为

    (7)  运算符重载+=

      +=要做的事情是,假设本身有一个空间内部有10个存储单位,另一个空间内部有15个存储单位,现在要做的事情就是将这个有10个存储单位的空间扩容

     

    realloc()的工作原理是:如果在所需要的分配的空间处有空间的话,那么就将直接将其扩容,如果在所需要分配的空间不够空间来分配,那么就在新的地方开辟一个空间,将原始的数据拷贝到新的空间中。

     4. C语言返回栈对象

    中间变量:下面这段程序

    返回的a是存储在CPU中的

    因此可以得出结论:栈上的对象是可以返回的,不可以返回栈上对象的引用,如上,如果返回对象的引用,因此相当于将其作用域扩展到main函数,但是刚刚扩展到main函数后,func这一段空间已经消失了。

    (1)  RVO/NRVO

    (具名)返回值优化,是这么一种优化机制:当函数需要返回一个对象时,如果自己构建一个临时对象用户返回,那么这个临时对象会消耗一个析构函数的调用,一个复制构造函数的调用以及一个析构函数的调用的代价,通过优化的方式,可以减小这些开销,以下有几种情况

    (a) 直接返回A(),不具名的情况

    (b)具名情况

    所得的结果在不同平台有差异,

    优化的本质:在main函数调用foo之前,会在自己的栈帧中开辟一个临时空间,该空间的地址作为隐藏参数传递给foo,在需要返回对象A之前,就在这个临时空间构造一个A a,然后这个空间的地址再利用寄存器eax返回给main函数,这样main函数就能获得foo的返回值了

    (c)赋值情况

    这种情况所得的结果为:

    说明在调用的时候,首先是r产生了一个空间,然后调用foo产生了一个空间,然后将foo产生空间的内容赋值给r空间,然后再进行析构。

    5. 对象数组

    (1)  对象数组中,有100对象,就会发生100次构造

    (2)  构造器无论是重载还是默认参数,一定要把系统默认的无参构造器包含进来,不然生成数组的时候会有些麻烦

    (3)  二段式初始化

    在对象数组中,要求对象必须包含默认无参构造器的情况,但有时,默认无参构造器并不能完全满足我们的要求,可能需要再次初始化。

    二段式初始化,常将默认无参构造器置为空,然后再次调用初始化函数

    其中,对象数组就是二段初始化的原因之一

  • 相关阅读:
    避免前置声明
    CLion在WSL上远程调试代码设置
    push_back与构造函数
    _BLOCK_TYPE_IS_VALID(pHead->nBlockUse问题解析
    Qt报错
    关于引用与指针实现多态的一些记录
    Vue-Axios异步通信
    Kafka概述
    学习Ajax看着一篇就够了
    学习Json看着一篇就够了
  • 原文地址:https://www.cnblogs.com/Cucucudeblog/p/10089458.html
Copyright © 2011-2022 走看看