以下代码编译及运行环境均为 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2

调用时机

看例子

  1. //
  2. // main.cpp
  3. // test
  4. //
  5. // Created by dabao on 15/9/30.
  6. // Copyright (c) 2015年 Peking University. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10.  
  11. class Base
  12. {
  13. public:
  14. Base()
  15. {
  16. std::cout<<"constructor"<<std::endl;
  17. }
  18.  
  19. Base(Base &copy)
  20. {
  21. std::cout<<"copy constructor"<<std::endl;
  22. }
  23.  
  24. const Base &operator=(Base &copy)
  25. {
  26. std::cout<<"operator="<<std::endl;
  27. return *this;
  28. }
  29. };
  30.  
  31. int main(int argc, const char * argv[])
  32. {
  33.  
  34. Base a;     // 1
  35. Base b = a;  // 2
  36. Base c(a);   // 3
  37.  
  38. Base d; // 4
  39. d = a;
  40.  
  41. return 0;
  42. }

  输出

  1. constructor
  2. copy constructor
  3. copy constructor
  4. constructor
  5. operator=

  1,2,3,4 是我们创建一个变量的最主要的方法(构造序列本文不讨论), 其中1,2,3是变量定义, 4是赋值. 因此很明显:

  1. 定义会调用构造函数, 赋值会调用赋值函数(operator=)
  2. 复制构造函数是一种特殊的构造函数, 参数是一个变量实例而已
  3. 2和3等价, 3不会调用赋值函数(手误) 2不会调用赋值函数, 出现等号未必就是赋值
  4. 如果没有重载以上函数, 3和4效果会一样, 但会少一次函数调用

const来捣乱

那么const又起到什么作用了呢?

继续来看例子

  1. //
  2. // main.cpp
  3. // test
  4. //
  5. // Created by dabao on 15/9/30.
  6. // Copyright (c) 2015年 Peking University. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10.  
  11. class Base
  12. {
  13. public:
  14. Base()
  15. {
  16. std::cout<<"constructor"<<std::endl;
  17. }
  18.  
  19. Base(Base &copy)
  20. {
  21. std::cout<<"copy constructor"<<std::endl;
  22. }
  23.  
  24. const Base &operator=(Base &copy)
  25. {
  26. std::cout<<"operator="<<std::endl;
  27. return *this;
  28. }
  29. };
  30.  
  31. Base creator()
  32. {
  33. Base ret;
  34. return ret;
  35. }
  36.  
  37. int main(int argc, const char * argv[])
  38. {
  39.  
  40. Base a = creator(); // 1
  41.  
  42. Base b;
  43. b = creator();     // 2
  44.  
  45. return 0;
  46. }

  上述代码都会编译出错, 原因是 "No matching constructor". 看代码不难发现原因, creator函数返回的是Base类型, 在c++11里面, 这个称为右值(rvalue), 但是我们的复制构造函数和赋值函数的参数类型都是非const引用类型, 而右值是不允许做这种类型参数的, 所以就编译出错了. 解决方案有两个:

  1. 使用const引用类型
  2. 使用右值类型

如下所示

  1. Base(const Base &copy)
  2. {
  3. std::cout<<"copy constructor"<<std::endl;
  4. }
  5.  
  6. const Base &operator=(Base &&copy)
  7. {
  8. std::cout<<"operator="<<std::endl;
  9. return *this;
  10. }

  其中, const引用类型是最通用的作法, 它可以兼容左值和右值, 也兼容古老的编译器, 右值类型则是c++11引进的新特性(使用&&表明), 可以针对左值和右值选择不同的实现, 比如使用std::move替代operator=, 从而减少内存的申请. 因此, 如果没有特殊需要, 使用const引用类型作为复制构造函数与赋值函数的参数类型.

至此, 构造函数的坑基本说完了, 因为不牵扯到返回值和函数类型的问题, 但是赋值函数(operator=)还有更多的坑来理一理.

const继续搅局

在一个类的成员函数中, const可以出现三个地方: 返回值, 参数, 函数.

  1. const A& operator=(const A& a) const

因此一个函数可以有8个变种, 但是c++不允许参数类型相同,返回值类型不同的重载, 因此一个函数最多有4种实现.

我们先考虑返回const类型的情况

  1. //
  2. // main.cpp
  3. // test
  4. //
  5. // Created by dabao on 15/9/30.
  6. // Copyright (c) 2015年 Peking University. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10.  
  11. class A
  12. {
  13. public:
  14. const A& operator=(const A& a) const
  15. {
  16. std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
  17. return *this;
  18. }
  19.  
  20. const A& operator=(const A& a)
  21. {
  22. std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
  23. return *this;
  24. }
  25.  
  26. const A& operator=(A& a) const
  27. {
  28. std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
  29. return *this;
  30. }
  31.  
  32. const A& operator=(A& a)
  33. {
  34. std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
  35. return *this;
  36. }
  37.  
  38. std::string x;
  39.  
  40. A() : x(""){}
  41. A(std::string x_) : x(x_) {}
  42. };
  43.  
  44. int main(int argc, const char * argv[])
  45. {
  46.  
  47. A a("a"), b("b");
  48. const A c("const c"),d("const d");
  49.  
  50. c = d;
  51. c = b;
  52. a = d;
  53. a = b;
  54.  
  55. return 0;
  56. }

 输出结果

  1. const A& operator=(const A& a) const [const d > const c]
  2. const A& operator=(A& a) const [b > const c]
  3. const A& operator=(const A& a) [const d > a]
  4. const A& operator=(A& a) [b > a]

结果很明显, 被赋值变量决定函数, 赋值变量决定参数, a=b 等价于 a.operator(b), 这里没什么问题.

但是, 有一个很奇怪的地方, a=d 这一句, a是非const的, 调用了 const A& operator=(const A& a) [const d > a], 返回值是个const类型, 这怎么可以呢? 返回值的const是什么意思呢? 这是非常有迷惑性的. 这个问题的关键点在于:

a是这个函数的一部分, 并不是返回值的承接者. 因此 a=d 实际上是等价于 const A& ret = a.operator=(d), 也就是说, operator=的返回值类型和被赋值的变量是没有任何关系的!

加入以下代码

  1. const A &m = (a = d);  // 1
  2. A &n = (a = d);      // 2

2会编译错误, 原因就在于把 const A& 绑定给 A&, 这肯定是错误的. 因此再重复一遍, operator=的返回值和被赋值变量没有任何关系.

那么返回值有什么意义呢? 这就和iostream类似了, 是为了进行串联赋值, 亦即 a=b=c

来看最后的例子

  1. //
  2. // main.cpp
  3. // test
  4. //
  5. // Created by dabao on 15/9/30.
  6. // Copyright (c) 2015年 Peking University. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10.  
  11. class A
  12. {
  13. public:
  14. const A& operator=(const A& a) const
  15. {
  16. std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
  17. return *this;
  18. }
  19.  
  20. const A& operator=(const A& a)
  21. {
  22. std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
  23. return *this;
  24. }
  25.  
  26. const A& operator=(A& a) const
  27. {
  28. std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
  29. return *this;
  30. }
  31.  
  32. const A& operator=(A& a)
  33. {
  34. std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
  35. return *this;
  36. }
  37.  
  38. std::string x;
  39.  
  40. A() : x(""){}
  41. A(std::string x_) : x(x_) {}
  42. };
  43.  
  44. int main(int argc, const char * argv[])
  45. {
  46.  
  47. A a("a"), b("b");
  48.  
  49. const A c("const c"),d("const d");
  50.  
  51. (a = b) = c;    // 1
  52.  
  53. (a = c) = b;    // 2
  54.  
  55. a = b = c;     // 3
  56.  
  57. return 0;
  58. }

输出

  1. const A& operator=(A& a) [b > a]
  2. const A& operator=(const A& a) const [const c > a]
  3. const A& operator=(const A& a) [const c > a]
  4. const A& operator=(A& a) const [b > a]
  5. const A& operator=(const A& a) [const c > b]
  6. const A& operator=(const A& a) [b > a]

  

可以得出如下结论:

  1. 1和3比较可以发现, 赋值的顺序是从右往左执行的
  2. 返回值是const类型, 那么再被赋值就会调用const函数了

总结

  1. 复制构造函数和赋值函数出现在两种不同的场景里, 不是出现等号就会调用赋值函数
  2. 赋值函数的返回值和被赋值变量是完全独立的

深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)的更多相关文章

  1. String的构造函数、析构函数和赋值函数

    编写类String的构造函数.析构函数和赋值函数 已知类String的原型为: class String { public: String(const char *str = NULL); // 普通 ...

  2. 编写类String 的构造函数、析构函数和赋值函数

    编写类String 的构造函数.析构函数和赋值函数,已知类String 的原型为:class String{public:String(const char *str = NULL); // 普通构造 ...

  3. C/C++面试题:编写类String的构造函数、析构函数和赋值函数。

    转https://www.cnblogs.com/alinh/p/9636500.html 考点:构造函数.析构函数和赋值函数的编写方法出现频率:☆☆☆☆☆已知类String的原型为:         ...

  4. 编写类String的构造函数、析构函数和赋值函数

    已知类String的原型为: class String {   public:  String(const char *str = NULL); // 普通构造函数  String(const Str ...

  5. C++构造函数(复制构造函数)、析构函数

    注:若类中没有显示的写如下函数,编译会自动生成:默认复制构造函数.默认赋值构造函数(浅拷贝).默认=运算符重载函数(浅拷贝).析构函数: 1.默认构造函数(默认值)构造函数的作用:初始化对象的数据成员 ...

  6. 编写类String的构造函数、拷贝构造函数、析构函数和赋值函数

    一.题目: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &othe ...

  7. C++ 拷贝构造函数 copy ctor & 拷贝赋值函数 copy op=

    类中含有  指针类型  的成员变量时,就必须要定义 copy ctor 和 copy op= copy ctor 请见: class Rectangle { public: Rectangle(Rec ...

  8. C++学习基础六——复制构造函数和赋值操作符

    1.什么是复制构造函数 复制构造函数:是构造函数,其只有一个参数,参数类型是所属类的类型,且参数是一个const引用. 作用:将本类的成员变量赋值为引用形参的成员变量. 2.什么是赋值操作符 赋值操作 ...

  9. C++雾中风景6:拷贝构造函数与赋值函数

    在进行C++类编写的过程之中,通常会涉及到类的拷贝构造函数与类的赋值函数.初涉类编写的代码,对于两类函数的用法一直是挺让人困惑的内容.这篇文章我们会详细来梳理拷贝构造函数与赋值函数的区别. 1.调用了 ...

随机推荐

  1. Linux内核分析第一周学习总结:计算机是如何工作的?

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.冯诺依曼体系 ...

  2. device tree 生成device node 到 platform_device

    http://blog.csdn.net/lichengtongxiazai/article/details/38942033 http://blog.csdn.net/mcgrady_tracy/a ...

  3. 【MVC】 非常简单的页面导出 WORD, EXCEL方法

    [MVC] 页面导出 WORD, EXCEL 前端 js function output() { var para = new Object(); para.html = getHtml(" ...

  4. NDK相关以及同步相关博客收集

    http://www.cnblogs.com/heiing/archive/2013/01/20/2868268.htmlhttp://blog.sina.com.cn/s/blog_461c24d5 ...

  5. Composite(组合)--对象结构型模式

    1.意图 将对象组合成树形结构以表示“部分-整体”的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. 2.动机 可以组合多个简单组件以形成一些较大的组件,这些组件又可以组合成 ...

  6. gdal编译C#开发版本

    gdal的编译比较麻烦,情况有很多种,今天我编译的gdal遇到的问题就和以前的有点不一样,仅供参考借鉴. 1.下载gdal源码 gdal源码下载地址:https://trac.osgeo.org/gd ...

  7. 使用Aspose插件将程序中的表格,导出生成excel表格

    http://www.cnblogs.com/lanyue52011/p/3372452.html这个是原文地址 /// <summary> /// 点击按钮,将内存表导出excel表格! ...

  8. 在sqlserver存储过程中给in参数传带逗号值的办法,如传'1','2','3'这样的

    最近在一项目修改中,要在存储过程中给in参数传值,语句写的也对,但怎么执行都得不出结果,如果把这语句直接赋值.执行,却能得出结果,很是奇怪,如: 直接执行select schoolname from ...

  9. css3新属性@ text-shadow

    text-shodow是css3的新属性,可以利用这个属性使字体更有立体感,还可以创造有趣的效果. 1.语法形式:text-shadow : x-offset(x轴偏移量) y-offset(y轴偏移 ...

  10. where子句的使用

    关系运算符: = > < <= >= != <> 略. 有一个<=> 有啥用? 其实也是判断等于. 不比较NULL值,效果就和= 一样,比较NULL值, ...