在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识。在effective C++中说过这么一点:拷贝构造函数的参数必须是引用类型的。但是为什么呢?

拷贝构造函数的参数必须是引用类型的

如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。

需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),是可以运行的。但是参数变指针后,已经不是拷贝构造函数了,已经变成了普通的构造函数,程序不会在需要拷贝构造函数的时候自动调用它。

看下面的代码:

  1. #include<iostream>
  2. using namespace std;
  3.  
  4. class CExample
  5. {
  6. private:
  7. int m_nTest;
  8.  
  9. public:
  10. CExample(int x) : m_nTest(x) //带参数构造函数
  11. {
  12. cout << "constructor with argument"<<endl;
  13. }
  14.  
  15. // 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的
  16. CExample(const CExample & ex) //拷贝构造函数
  17. {
  18. m_nTest = ex.m_nTest;
  19. cout << "copy constructor"<<endl;
  20. }
  21.  
  22. CExample& operator = (const CExample &ex) //赋值函数(赋值运算符重载)
  23. {
  24. cout << "assignment operator"<<endl;
  25. m_nTest = ex.m_nTest;
  26. return *this;
  27. }
  28.  
  29. void myTestFunc(CExample ex)
  30. {
  31. }
  32. };
  33.  
  34. int main(void)
  35. {
  36. CExample aaa(2);
  37. CExample bbb(3);
  38. bbb = aaa;
  39. CExample ccc = aaa;
  40. bbb.myTestFunc(aaa);
  41.  
  42. return 0;
  43. }

  这个例子的输出结果:

  1. constructor with argument // CExample aaa(2);
  2. constructor with argument // CExample bbb(3);
  3. assignment operator // bbb = aaa;
  4. copy constructor // CExample ccc = aaa;
  5. copy constructor // bbb.myTestFunc(aaa);

  分析:第一个和第二个没有什么特别的地方,就是普通的构造函数。第三个和第四个为什么结果不一样:

原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数。但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数。

第五个:实际上是aaa作为参数传递给bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四个一致的, 所以还是拷贝构造函数,而不是赋值函数。

通过这个例子, 我们来分析一下为什么拷贝构造函数的参数只能使用引用类型。

看第四个输出: copy constructor                      // CExample ccc = aaa;

构造ccc,实质上是ccc.CExample(aaa); 我们假如拷贝构造函数参数不是引用类型的话, 那么将使得 ccc.CExample(aaa)变成aaa传值给ccc.CExample(CExample ex),即CExample ex = aaa,因为 ex 没有被初始化, 所以 CExample ex = aaa 继续调用拷贝构造函数,接下来的是构造ex,也就是 ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex), 即 CExample ex = aaa;那么又会触发拷贝构造函数,就这下永远的递归下去。

在C++中,调用拷贝构造函数有三种情况:

1.一个对象作为函数参数,以值传递的方式传入函数体

2.一个对象作为函数返回值,以值传递的方式从函数返回

3.一个对象用于给另外一个对象进行初始化(创建对象时初始化).

如果在没有显式声明构造函数的情况下,编译器都会为一个类合成一个缺省的构造函数。如果在一个类中声明了一个构造函数,那么就会阻止编译器为该类合成缺省的构造函数。但是定义了其他构造函数(但没有定义拷贝构造函数),编译器总是会为我们合成一个拷贝构造函数,也就是说自定义的构造函数不会阻止默认的拷贝构造函数。

另外函数的返回值是不是引用也有很大的区别,返回的不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。

  1. #include<iostream>
  2. using namespace std;
  3.  
  4. class A
  5. {
  6. private:
  7. int m_nTest;
  8. public:
  9. A()
  10. {
  11. }
  12. A(const A& other) //构造函数重载
  13. {
  14. m_nTest = other.m_nTest;
  15. cout << "copy constructor"<<endl;
  16. }
  17. A & operator =(const A& other)
  18. {
  19. if(this != &other)
  20. {
  21. m_nTest = other.m_nTest;
  22. cout<<"Copy Assign"<<endl;
  23. }
  24. return *this;
  25. }
  26. };
  27.  
  28. A fun(A &x)
  29. {
  30. return x; //返回的不是引用的时候,需要调用拷贝构造函数
  31. }
  32.  
  33. int main(void)
  34. {
  35. A test;
  36. fun(test);
  37. system("pause");
  38. return 0;
  39. }

  

什么时候需要自定义拷贝构造函数和赋值符函数.

简单的规则:如果需要定义一个非空的析构函数,那么,通常情况下也需要定义一个拷贝构造函数和赋值符函数.

通常的原则是:

1.对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;

2.在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符,即提供赋值符函数.

当我们知道需要自定义拷贝构造函数和赋值符函数时,就得考虑如何良好的实现它们.

当自定义copying函数(包含拷贝构造函数和赋值符函数)时,需要确保以下两点:

1.复制所有的local成员变量

2.调用所有base classes内的适当的copying函数,完成基类的copying.

下面是一个具体的例子:

  1. class Customer {
  2. public:
  3. ...
  4. Customer(const Customer& rhs);
  5. Customer& operator=(const Customer& rhs);
  6. ...
  7. private:
  8. std::string name;
  9. };
  10.  
  11. Customer::Customer(const Customer& rhs):name(rhs.name)
  12. {
  13. cout << "Customer copy constructor" << endl;
  14. }
  15. Customer& Customer::operator=(const Customer& rhs)
  16. {
  17. cout << "Customer copy assignment operator" << endl;
  18. name = rhs.name; //疑惑,为什么在copying函数里可以通过对象调用私有变量?
  19. return *this;
  20. }
  21.  
  22. class PriorityCustomer:public Customer {
  23. public:
  24. ...
  25. PriorityCustomer(const PriorityCustomer& rhs);
  26. PriorityCustomer& operator=(const PriorityCustomer& rhs);
  27. ...
  28. private:
  29. int priority;
  30. };
  31.  
  32. PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority)
  33. {
  34. cout << "PriorityCustomer copy constructor" << endl;
  35. }
  36. PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
  37. {
  38. cout << "PriorityCustomer copy assignment operator" << endl;
  39. Customer::operator=(rhs); //对base class成分进行赋值动作
  40. priority = rhs.priority;
  41. return *this;
  42. }

  上面代码中,通过对象直接访问private的成员变量,似乎违背了对象的封装,具体的分析和理解参考下面的链接:

C++私有成员变量的理解

[C++参考]拷贝构造函数的参数必须是引用类型的更多相关文章

  1. C++学习之拷贝构造函数

    嘛是拷贝构造函数? 如果一个构造函数的第一个参数是’自身类‘ ‘类型’的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数.如: [代码1] 1 2 3 4 5 6 class A{ publ ...

  2. C++ 为什么拷贝构造函数参数必须为引用?赋值构造函数参数也必须为引用吗?

    之前写拷贝构造函数的时候,以为参数为引用,不为值传递,仅仅是为了减少一次内存拷贝.然而今天看到一篇文章发现自己对拷贝构造的参数理解有误. 参数为引用,不为值传递是为了防止拷贝构造函数的无限递归,最终导 ...

  3. 2.13 C++拷贝构造函数

    参考:http://www.weixueyuan.net/view/6344.html 总结: 如果拷贝构造函数的参数不是对象的引用,则是不允许的.如 book(book b); 是无法编译通过的. ...

  4. 初始化列表(const和引用成员)、拷贝构造函数

    一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 构造函数的执行分为两个阶段 初始化段 普通计算段 (一).对象成员及其初始化  C++ Code  1 2 3 4 5 6 7 8 9 1 ...

  5. [c++基础]3/5原则--拷贝构造函数+拷贝赋值操作符

    /* * main.cpp * * Created on: Apr 7, 2016 * Author: lizhen */ #include <iostream> #include &qu ...

  6. 《剑指offer》面试题1:为类CMyString添加赋值运算符函数——C++拷贝构造函数与赋值函数

    题中已给出CMyString的类定义,要求写赋值运算符函数. #include<iostream> #include<cstring> using namespace std; ...

  7. C++构造函数详解(复制构造函数 也是 拷贝构造函数)

    构造函数是干什么的 该类对象被创建时,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作,故:构造函数的作用:初始化对象的数据成员. 构造函数的种类 1 class Com ...

  8. C++ 拷贝构造函数和赋值运算符

    本文主要介绍了拷贝构造函数和赋值运算符的区别,以及在什么时候调用拷贝构造函数.什么情况下调用赋值运算符.最后,简单的分析了下深拷贝和浅拷贝的问题. 拷贝构造函数和赋值运算符 在默认情况下(用户没有定义 ...

  9. 一个CString的实现 拷贝构造函数的应用

    class CString { public: CString (char* s); CString(); ~CString(); private: char *str; int len; stati ...

随机推荐

  1. UPPH、UPH

    UPPH=units Per Hour Per Person,单位小时人均产能,是公司作为衡量员工工作绩效的重要指标. UPPH是衡量员工单位时间工作量的一种绩效指标. UPPH计算方式如下: UPP ...

  2. MySQL Binlog的介绍

    binlog基本定义:二进制日志,也成为二进制日志,记录对数据发生或潜在发生更改的SQL语句,并以二进制的形式保存在磁盘中: 作用:MySQL的作用类似于Oracle的归档日志,可以用来查看数据库的变 ...

  3. libmemcached安装及简单例子

    libmemcached安装及简单例子 1.下载安装libmemcached  $ wget http://launchpad.net/libmemcached/1.0/0.44/+download/ ...

  4. js正则验证"汉字"

    var nickname = value; var regex = new RegExp("^([\u4E00-\uFA29]|[\uE7C7-\uE7F3]|[a-zA-Z0-9_]){1 ...

  5. MFC解决Static控件背景透明时文本覆盖重影

    通过映射OnCtlColor消息,而后在该函数中使用pDC->SetBkMode(TRANSPARENT)使Static控件背景透明时会出现:文本覆盖重影的问题,当改变控件的文本就会发生新文本与 ...

  6. [转]maven入门

    http://wentao365.iteye.com/blog/903396 Maven是一个采用纯Java编写的开 源项目管理工具.Maven采用了一种被称之为project object mode ...

  7. VC++深入详解-第五章学习心得

    这一章节主要讲解了文本相关的一些编程 插入符的使用 CreateSolidCaret(100,200);//插入符的宽度和高度 ShowCaret(); 插入符的一般使用方法 int CTestVie ...

  8. Android开发之布局优化

    1.抽象布局标签 (1) <include>标签 include标签经常使用于将布局中的公共部分提取出来供其它layout共用,以实现布局模块化.这在布局编写方便提供了大大的便利. 以下以 ...

  9. linux select 网络模型

    io模型: 同步IO: 阻塞形式,非阻塞形式(轮询).信号驱动IO.IO复用(select, poll, epoll): 异步io:aio_read() 典型场景: 1.客户端处理多种IO------ ...

  10. docker基础入门之一

    一.概述 1.传统虚拟化技术: 纯软件的虚拟化是通过对于硬件层的模拟从而实现允许运行多个操作系统: 硬件辅助虚拟化需要硬件层面对于虚拟化的支持,类似Intel-VT技术等,具有更高的运行效率: 解决方 ...