赋值运算符和拷贝构造函数的区别与联系

简述:

C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。


1、构造函数



构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它被调用来对类的数据成员进行初始化和分配内存构造函数的命名必须和类名完全相同

② 首先说一下一个C++的空类,编译器会加入哪些默认的成员函数

·默认构造函数和拷贝构造函数

·析构函数

·赋值函数(赋值运算符

·取值函数

**即使程序没定义任何成员,编译器也会插入以上的函数! 

注意:构造函数可以被重载,可以多个,可以带参数;析构函数只有一个,不能被重载,不带参数

 

③ 而默认构造函数没有参数,它什么也不做。当没有重载无参构造函数时,

  Person man就是通过默认构造函数来创建一个对象

④下面代码为构造函数重载的实现


class Person  
{    
public:
    Person()   
    {  
        qDebug()<<”无参构造函数”<<endl;  
    }  
    Person(int i):m_i(i) {}  //初始化列表  
private:
    int m_i;
}


2、初步认识拷贝构造函数赋值运算符


① 拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。

② 在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数赋值运算符。但用户可以使用delete来指定不生成拷贝构造函数赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算。


class Person
{
public:

    Person(const Person& p) = delete; //不生成拷贝构造函数

    Person& operator=(const Person& p) = delete; //不生成赋值运算符

private:
    int age;
    string name;
};

上面的定义的类Person显式的删除了拷贝构造函数赋值运算符,在需要调用拷贝构造函数或者赋值运算符的地方,会提示_无法调用该函数,它是已删除的函数_。

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


3、调用场合


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

② 调用拷贝构造函数主要有以下场景:

    • 对象作为函数的参数,以传递的方式传给函数。 
    • 对象作为函数的返回值,以的方式从函数返回
    • 使用一个对象给另一个对象初始化

③  什么时候编译器会生成默认的拷贝构造函数

A. 如果用户没有自定义拷贝构造函数,并且在代码中使用到了拷贝构造函数,编译器就会生成默认的拷贝构造函数。但如果用户定义了拷贝构造函数,编译器就不在生成。

B. 如果用户定义了一个构造函数,但不是拷贝构造函数,而此时代码中又用到了拷贝构造函数,那编译器也会生成默认的拷贝构造函数


④ 样例代码如下:


class Person
{
public:
    Person(){}
    Person(const Person& p)
    {
        cout << "Copy Constructor" << endl;
    }

    Person& operator=(const Person& p)
    {
        cout << "Assign" << endl;
        return *this;
    }

private:
    int age;
    string name;
};

void f(Person p)
{
    return;
}

Person f1()
{
    Person p;
    return p;
}

int main()
{
    Person p;
    Person p1 = p;    // A
    Person p2;
    p2 = p;           // B
    f(p2);            // C

    p2 = f1();        // D

    Person p3 = f1(); // E

    getchar();
    return 0;
}

上面代码中定义了一个类Person,显式的定义了拷贝构造函数赋值运算符。然后定义了两个函数:f(),以值的方式参传入Person对象;f1(),以值的方式返回Person对象。在main中模拟了5中场景,测试调用的是拷贝构造函数还是赋值运算符。执行结果如下:


分析如下:

A. 这是虽然使用了"=",但是实际上使用对象p来创建一个新的对象p1。也就是产生了新的对象,所以调用的是拷贝构造函数

B. 首先声明一个对象p2,然后使用赋值运算符"=",将p的值复制给p2,显然是调用赋值运算符,为一个已经存在的对象赋值 。

C. 以值传递的方式将对象p2传入函数f内,调用拷贝构造函数构建一个函数f可用的实参

D. 这条语句拷贝构造函数赋值运算符都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符将临时对象tmp赋值给p2.

E. 按照4的解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象p3。


4、深拷贝、浅拷贝


① 通常,默认生成的拷贝构造函数赋值运算符,只是简单的进行值的复制。例如:上面的Person类,字段只有intstring两种类型,这在拷贝或者赋值时进行值复制创建的出来的对象和源对象也是没有任何关联对源对象的任何操作都不会影响到拷贝出来的对象。反之,假如Person有一个对象为int *,这时在拷贝时还只是进行值复制,那么创建出来的Person对象的int *的值就和源对象的int *指向的是同一个位置。任何一个对象对该值的修改都会影响到另一个对象,这种情况就是浅拷贝

② 系统提供的默认拷贝构造函数工作方式是内存拷贝,也就是浅拷贝。如果对象中用到了需要手动释放的对象,则会出现问题,这时就要手动重载拷贝构造函数,实现深拷贝。


深拷贝与浅拷贝

浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)

深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。

深拷贝浅拷贝主要是针对类中的指针动态分配的空间来说的,因为对于指针只是简单的值复制并不能分割开两个对象的关联,任何一个对象对该指针的操作都会影响到另一个对象。这时候就需要提供自定义的深拷贝的拷贝构造函数,消除这种影响。通常的原则是:

    • 含有指针类型的成员或者有动态分配内存的成员都应该提供自定义的拷贝构造函数
    • 在提供拷贝构造函数的同时,还应该考虑实现自定义的赋值运算符

⑤ 对于拷贝构造函数的实现要确保以下几点:

    • 对于值类型的成员进行值复制
    • 对于指针和动态分配的空间,在拷贝中应重新分配分配空间
    • 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝

5、总结



    • 拷贝构造函数赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生
    • 关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符
    • 对象不存在,且没用别的对象来初始化,就是调用了构造函数

      对象不存在,且用别的对象来初始化,就是拷贝构造函数

      对象存在,用别的对象来给它赋值,就是赋值函数。




参考文献:

http://www.cnblogs.com/wangguchangqing/p/6141743.html

http://blog.csdn.net/zcyzsy/article/details/52132936

http://blog.csdn.net/qq_16445683/article/details/46043505

https://zhidao.baidu.com/question/492929165756813972.html


http://www.niftyadmin.cn/n/1081654.html

相关文章

稠密矩阵 稀疏矩阵

看书的时候看到dense matrix 就比较好奇&#xff0c;为什么会有这个定义&#xff0c;如果一个矩阵是稠密矩阵那么会有什么性质&#xff1f; 某度搜索一下&#xff0c;出来一个看似很合理但是没什么性质显示的解释&#xff1a; 非0元素占所有元素比例较大的矩阵称为稠密矩阵。…

IT职场人生系列之十五:语言与技术II

本文是IT职场人生系列的第十五篇本篇延续了技术与语言I的内容&#xff08;之十二&#xff09;&#xff0c;搜集了之后大家的一些评论和我的反馈&#xff0c;整理在这里。“新人学老技术有风险”的实质其实不是说老技术没有学习的价值了&#xff0c;而是指新人依托老技术存活&am…

Qt Creator中的.pro文件的详解

简述&#xff1a; 在QT中&#xff0c;有一个工具qmake可以生成一个makefile文件&#xff0c;它是由.pro文件生成而来的。.pro是qmake的工程文件&#xff08;project&#xff09;&#xff1b;.pri文件可以把 *.pro 文件内的一部分单独放到一个 *.pri 文件内(include)&#xff0c…

独立同分布 正态分布

概率论里是这样的概念&#xff1a;X1,X2,…,Xn是独立同分布的n个随机变量&#xff0c;当n很大时&#xff0c;它们的和XX1X2…Xn可以近似看作服从正态分布的。 中心极限定理就是说的这个概念&#xff0c;定理证明了X的标准化以后的随机变量&#xff0c;当n→∞时趋向于标准正态分…

数学符号

原址&#xff1a;http://www.zybang.com/question/c9ddfc1f7059fb7d91e7cf2e140f730e.html 常用数学符号读法大全以及主要数学符号含义-转载 大写 小写 英文注音 国际音标注音 中文注音 Α α alpha alfa 阿耳法Β β beta beta 贝塔Γ γ gamma gamma 伽马Δ δ deta delt…

VBAnbsp;检查文件或文件名是否存在

******部分代码****** 引用 Microsoft Scripting Runtime 引用文件 C:\WINDOWS\SYSTEM32\Scrrun.dll Sub 保存备份文件() workbooks.add Dim datafolder As String Dim chfolder As Object Dim checkfn As Object Dim savename As String Dim backupfilename As String Set chf…

Qt 解决_CRT_SECURE_NO_WARNINGS及_SCL_SECURE_NO_WARNINGS的问题

简述&#xff1a;关于 VS20XX 中出现 _CRT_SECURE_NO_WARNINGS 及 _SCL_SECURE_NO_WARNINGS 的安全警告处理。 1、简单介绍.pro跨平台&#xff08;Win32/Linux&#xff09;的写法 win32 { #remove safe warning win32:DEFINES _CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNING…

(二)Android Framework概述

1. 写文章之前的声明&#xff1a;首先&#xff0c;文章写上原创标签是有点牵强的&#xff0c;本质上是读书笔记。上周周六的时候参加了博文视点的一个作者交流会&#xff0c;编辑送我了一本android内核方面的书《Android内核剖析》&#xff0c;拿回来读了一下&#xff0c;非常的…