【总结】默认构造/拷贝/赋值函数及初始化列表---从深浅拷贝问题引入

REF:

https://blog.csdn.net/qq_29344757/article/details/76037255

https://www.cnblogs.com/gaochaochao/p/8370762.html(讲解比较好)

https://blog.csdn.net/qq_33757398/article/details/81331918(构造函数和初始化列表区别)

 

构造函数与初始化列表

1.初始化和赋值的区别:

1)两者作用相同

2)对于数组和结构体来说,初始化和赋值的的形式不同。对于数组,可以使用花括号一起初始化,如果赋值的话,就只能单个元素就行;对于结构体,可以使用花括号初始化,否则只能通过“.”来访问变量进行赋值

3)对于引用和const常量来说,只能初始化不能赋值

2.构造函数

定义

1)函数的名字与类的名字相同

2)在创建一个对象时,构造函数就自动执行,但是在声明一个类的指针对象时,构造函数不会被调用,当new一个空间的时候,构造函数才会被调用

3)构造函数一般用来对数据成员的赋值,这也是它的一般性作用

4)构造函数没有返回值

5)一个类里面也可以有多个构造函数,这些构造函数根据参数的不同,构成重载,根据参数的传递来选择调用哪个构造函数

6)可以不用显式的定义构造函数,这种情况下,编译器会自动帮我们生成一个空构造函数,什么也不执行;如果我们显式的声明了一个构造函数,那么这个构造函数就会覆盖默认的空构造函数

3.初始化列表

1)作用:

对数据成员进行初始化

2)格式:

构造函数():变量名1(数值),变量名2(数值)

{}      //!变量名不在花括号的后面,而是在花括号的前面

3)注意

a. 在构造函数执行时,先执行初始化列表,实现变量的初始化,然后再执行函数内部的语句

b. 成员初始化的顺序只与声明的顺序有关,而跟初始化列表的顺序无关。例如在上面的初始化列表中,我们写成:_c(cc), _b(bb), _a(aa),但是我们还是先初始化变量_a,然后_b,然后_c,因为我们先声明的变量_a,然后_b,然后_c

c. 成员之间可以相互初始化:a(12), b(a)  //a,b为相同类型的话

4)必须使用初始化列表的情况

https://blog.csdn.net/sinat_20265495/article/details/53670644

类对象的构造顺序是这样的:

1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员;

2.进入构造函数后在构造函数中执行一般赋值与计算。

使用初始化列表有两个原因:

原因1.必须这样做:

《C++ Primer》中提到在以下三种情况下需要使用初始化成员列表:

   情况一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化); 

 ■说明:数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数;

     如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
 

   情况二、需要初始化const修饰的类成员或初始化引用成员数据;

    说明:当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

   情况三、子类初始化父类的私有成员;(也可以用父类public函数来做)

    ■说明:子类初始化父类的私有成员,需要在参数初始化列表中显示调用父类的构造函数

原因2.效率要求这样做:

     类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。

注意:构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。
 

4.引用和const常量的初始化

1)当类成员中有引用和const常量时就一定得初始化,否则会报错

6.数组初始化

格式:构造函数():数组名()  //后面的这个括号里不能添加0或者其他数值

7.结构体的初始化

格式1:构造函数():结构体名({各个结构体成员的初始值,按顺序用逗号隔开})

注意:结构体可以这样用花括号进行初始化,但是数组不可以

格式2:构造函数(传递进来一个结构体):结构体名(传进来的结构体)

 

坑1

c++类的默认拷贝构造函数的弊端(浅拷贝)如果有动态成员(例如指针)时,如果使用默认拷贝构造函数,在析构时会出现问题

c++类的中有两个特殊的构造函数,(1)无参构造函数,(2)拷贝构造函数。它们的特殊之处在于: 
(1)当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空; 
(2)当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(这个拷贝操作是浅拷贝)

拷贝构造函数:

         拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用拷贝构造函数。归结来说。有三个场合要用到拷贝构造函数:

l  对象作为函数的参数,以值传递的方式传给函数

l  对象作为函数的返回值,以值传递的方式从函数返回调用处

l  使用一个对象去初始化一个新建的对象

      即有拷贝构造函数的调用一定会有新对象生成

      还有一点需要注意的是,拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。

#include<iostream.h>
#include<string.h>
class Person{
public :
    Person();  //无参构造函数
    Person(int age,char na[]);  //重载一般构造函数
    Person(const Person & p);//拷贝构造函数
    ~Person(); // 析构函数
    void disp();
private :
    int age;
    char *name;
};
Person::Person(){
    age=0;
    name=new char[2];
    strcpy(name,"\0");
    cout<<"default constructor\n";}
Person::Person(int age,char na[])
{
    this->age=age;
    name=new char[strlen(na)+1]; //为指针变量动态分配空间
    strcpy(name,na); //赋值
    cout<<"constructor\n";
}
Person::Person(const Person & p)
{
    this->age=p.age;
    this->name=new char[strlen(p.name)+1];
    strcpy(name,p.name);
    cout<<"copy constructor\n";
}
Person::~Person()
{
    delete [] name;
    cout<<"destroy\n";
}
void Person::disp()
{
    cout<<"age "<<age<<"  name  "<<name<<endl;
}
void f(Person p)
{
    cout<<"enter f \n";
    p.disp();
    return ;
}
Person f1()
{
    cout<<"enter f \n";
    Person p;
    cout<<"next is return object of Person\n";
    return p;
}
void main()
{
    Person p1(21,"xiaowang");//调用一般构造函数
    p1.disp();
    Person p2(p1);//调用拷贝构造函数
    p2.disp();
    Person p3=p1;//调用拷贝构造函数
    p3.disp();
    cout<<"true\n";
    cout<<"拷贝构造函数调用在函数形参是对象且值传递\n";
    f(p1);   //①
    cout<<"拷贝构造函数调用在函数返回值是对象且值传递\n";
    f1();   //②
    cout<<"主函数结束,调用三次析构函数销毁对象\n";
}

注:如果函数的形参是对象,或者返回值是对象,但是是以引用传递的方式,那么靠诶构造函数就不会被调用,这也是引用的作用,即对同一个对象起别名

坑2 

对象以值传递方式从函数返回时,若接受返回值的对象已经初始化过,则会调用赋值构造函数,且该对象还会调用析构函数,当对象中包含指针时,会使该指针失效,因此需要重载赋值构造函数,使用类似深拷贝或移动构造函数的方法赋值,才能避免指针失效。

拷贝构造函数

     拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值