拷贝构造函数&重载赋值运算=

一 简介

以类A为例。

1.1 拷贝构造函数

1.1.1 形式

A(const A&)

1.1.2 作用

首先,它是一个构造函数,所以是用于对象的初始化。其次,它构造对象的规则是通过拷贝各个成员变量完成。

1.2 重载operator=

1.2.1 形式

A& operator=(const A&)

1.2.2 作用

对于一个对象的赋值(注意,不是初始化,初始化调用的是拷贝构造函数而不是重载运算符)。

1.3 两者相同点

  1. 编译器都会提供默认的实现(即成员变量复制)
  2. 都用来构造"一样的"对象

二 编译器对拷贝构造函数的优化

2.1 当函数返回局部变量且是用于对对象初始化时

代码

#include 
#include 

using namespace std;

class A 
{
     
    public:
        int num;
        A(int num):num(num){
     cout<<"A constructor"<<endl;}
        A(const A& a){
     num = a.num;cout << "A copy constructor" << endl;}
        ~A(){
     cout << "~A" << endl;}
};
A getA()
{
     
    A a(5);
    cout << "getA a&:" << &a << endl;
    return a;
}
int main()
{
     
    A a = getA();    
    cout << "main a&:" << &a << endl;
    system("pause");
    return 0;
}

结果

在这里插入图片描述

解释

可以看出,函数getA内的对象a和main中用于接收getA返回值的a地址一样,且getA结束时未调用A的析构函数,且没有调用A的拷贝构造函数。这是因为:编译器觉得在函数getA中,初始化了一个对象a,之后又得返回它,而返回它之前正常的操作是:创建一个临时变量复制a,再析构a,再将临时变量的引用返回用于初始化或者赋值。编译器看到这么麻烦,于是想:我不如直接将a给你,这样也省去了复制和析构的成本。因此,用于初始化对象时,返回的对象和接收的对象地址相同。

2.2 当函数返回局部变量且是用于对对象赋值时

#include 
#include 

using namespace std;

class A 
{
     
    public:
        int num;
        A(int num):num(num){
     cout<<"A constructor"<<endl;}
        A(const A& a){
     num = a.num;cout << "A copy constructor" << endl;}
        ~A(){
     cout << "~A&:" << this << endl;}
        A& operator=(const A& a){
     cout << "operator=,&a:" << &a << endl;num = a.num; return *this;}
};
A getA()
{
     
    A a(5);
    cout << "getA a&:" << &a << endl;
    return a;
}
int main()
{
     
    A a(1);
    a = getA();    
    cout << "main a&:" << &a << endl;
    system("pause");
    return 0;
}

结果

拷贝构造函数&重载赋值运算=_第1张图片

解释

在 a = getA()该行,getA()返回的仍然是函数内部局部变量的引用,且返回后马上进行了operator=的函数调用(a = getA() 相当于 a.operator=(getA()))。而可以看到,最后面析构的对象仍然是getA()产生的a对象,这是因为到operator=运算符结束时,该对象的声明周期已经结束了(局部变量,跳出了函数栈,且没有其他对象引用它),所以会被析构。而=运算最后返回*this并没有触发析构以及拷贝构造函数,这是因为*this的声明周期还未结束(在main中定义的对象)所以没有被析构,同时因为返回的是A&,所以没有产生对象的复制。然而下面的非正规operator=的实现却又有不同

2.3 非正规operator=的实现

代码

#include 
#include 

using namespace std;

class A 
{
     
    public:
        int num;
        A(int num):num(num){
     cout<<"A constructor"<<endl;}
        A(const A& a){
     num = a.num;cout << "A copy constructor" << endl;}
        ~A(){
     cout << "~A&:" << this << endl;}
        // 返回的是一个A对象 而不是A对象的引用
        A operator=(const A& a){
     cout << "operator=,&a:" << &a << endl;num = a.num; return *this;}
};
A getA()
{
     
    A a(5);
    cout << "getA a&:" << &a << endl;
    return a;
}
int main()
{
     
    A a(1);
    a = getA();    
    cout << "main a&:" << &a << endl;
    system("pause");
    return 0;
}

结果

拷贝构造函数&重载赋值运算=_第2张图片

解释

可以看出,在=运算符里面,调用了一次拷贝构造函数以及两次析构函数。调用拷贝构造函数的原因是:函数返回值类型对象,因此需要创建一个临时变量(暂且命名为tmp)对*this进行拷贝,之后再传回临时对象tmp的引用。在=结束后,发生了两次调用析构函数,第一次析构的对象地址并没有在之前出现过,应该是创建的tmp对象被析构;而第二次析构的对象是getA()产生的对象。以这个顺序析构的原因是:函数栈帧入栈时的顺序是:形参声明顺序的逆序 -> 返回值 -> 局部变量声明顺序。所以函数调用完毕后,栈帧按入栈相反顺序出栈,所以先析构返回值,再析构形参(本例形参为getA()产生的对象a)。若将=的结果用于初始化一个对象,则可证明只析构了形参。

2.4 将赋值运算的结果用于初始化新对象

代码

#include 
#include 

using namespace std;

class A 
{
     
    public:
        int num;
        A(int num):num(num){
     cout<<"A constructor"<<endl;}
        A(const A& a){
     num = a.num;cout << "A copy constructor" << endl;}
        ~A(){
     cout << "~A&:" << this << endl;}
        A operator=(const A& a){
     cout << "operator=,&a:" << &a << endl;num = a.num; return *this;}
};
A getA()
{
     
    A a(5);
    cout << "getA a&:" << &a << endl;
    return a;
}
int main()
{
     
    A a(1);
    A b = a = getA(); // 使用=运算的结果初始化对象b  
    cout << "main a&:" << &a << endl;
    cout << "main b&:" << &b << endl;
    system("pause");
    return 0;
}

结果

拷贝构造函数&重载赋值运算=_第3张图片

解释

可以看到,=运算符结束后只调用了一次析构函数,且析构的对象是getA()产生的a

三 总结

  1. 拷贝构造函数用于对象初始化,赋值运算符用于对象的赋值(对象已初始化完成)
  2. 当返回值类型为对象且返回的是局部变量时,由于编译器优化的原因,会放弃复制返回对象以及析构局部变量两个操作,直接返回局部变量对象的引用。
  3. 注意函数结束后析构对象的顺序与函数中对象入栈顺序相反。

你可能感兴趣的:(C++,c++,类,拷贝构造函数)