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

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

原因:

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

需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),也是不行的。事实上,只有传引用不是传值外,其他所有的传递方式都是传值。

先从一个小例子开始:(自己测试一下自己看看这个程序的输出是什么?)

[cpp]

#include

using namespace std;

class CExample

{

private:

int m_nTest;

public:

CExample(int x) : m_nTest(x)      //带参数构造函数

{

cout

}

// 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的

CExample(const CExample & ex)     //拷贝构造函数

{

m_nTest = ex.m_nTest;

cout

}

CExample& operator = (const CExample &ex)   //赋值函数(赋值运算符重载)

{

cout

m_nTest = ex.m_nTest;

return *this;

}

void myTestFunc(CExample ex)

{

}

};

int main(void)

{

CExample aaa(2);

CExample bbb(3);

bbb = aaa;

CExample ccc = aaa;

bbb.myTestFunc(aaa);

return 0;

}

这个例子的输出结果是:

[cpp]

constructor with argument        // CExample aaa(2);

constructor with argument        // CExample bbb(3);

assignment operator              // bbb = aaa;

copy constructor                 // CExample ccc = aaa;

copy constructor                 //  bbb.myTestFunc(aaa);

第三个输出: assignment operator                // bbb = aaa;

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

这两个得放到一块说。 肯定会有人问为什么两个不一致。原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数! 但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数!

第五个输出: copy constructor                      //  bbb.myTestFunc(aaa);

实际上是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;那么又会触发拷贝构造函数,就这下永远的递归下去。

所以绕了那么大的弯子,就是想说明拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。

还有一点需要说明,以引用返回函数值,定义函数时需要在函数名前加&,用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。当函数的返回值是对象时,返回类型若不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。

若去掉代码22行中的引用符号,第三个输出就会是:

assignment operator                // bbb = aaa;

copy constructor                       // bbb = aaa;

引用作为返回值,必须遵守以下规则:

(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

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

原因:

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

需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),也是不行的。事实上,只有传引用不是传值外,其他所有的传递方式都是传值。

先从一个小例子开始:(自己测试一下自己看看这个程序的输出是什么?)

[cpp]

#include

using namespace std;

class CExample

{

private:

int m_nTest;

public:

CExample(int x) : m_nTest(x)      //带参数构造函数

{

cout

}

// 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的

CExample(const CExample & ex)     //拷贝构造函数

{

m_nTest = ex.m_nTest;

cout

}

CExample& operator = (const CExample &ex)   //赋值函数(赋值运算符重载)

{

cout

m_nTest = ex.m_nTest;

return *this;

}

void myTestFunc(CExample ex)

{

}

};

int main(void)

{

CExample aaa(2);

CExample bbb(3);

bbb = aaa;

CExample ccc = aaa;

bbb.myTestFunc(aaa);

return 0;

}

这个例子的输出结果是:

[cpp]

constructor with argument        // CExample aaa(2);

constructor with argument        // CExample bbb(3);

assignment operator              // bbb = aaa;

copy constructor                 // CExample ccc = aaa;

copy constructor                 //  bbb.myTestFunc(aaa);

第三个输出: assignment operator                // bbb = aaa;

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

这两个得放到一块说。 肯定会有人问为什么两个不一致。原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数! 但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数!

第五个输出: copy constructor                      //  bbb.myTestFunc(aaa);

实际上是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;那么又会触发拷贝构造函数,就这下永远的递归下去。

所以绕了那么大的弯子,就是想说明拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。

还有一点需要说明,以引用返回函数值,定义函数时需要在函数名前加&,用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。当函数的返回值是对象时,返回类型若不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。

若去掉代码22行中的引用符号,第三个输出就会是:

assignment operator                // bbb = aaa;

copy constructor                       // bbb = aaa;

引用作为返回值,必须遵守以下规则:

(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。


相关内容

  • C++模拟题2答案
  • C++程序设计模拟试卷(二) 一.单项选择题(本大题共20小题,每小题1分,共20分) 在每小题列出的四个备选项中 只有一个是符合题目要求的,请将其代码填写在题后的括号内.错选.多选或未选均无 分. 1. 静态成员函数没有() A. 返回值 B. this 指针 C. 指针参数 D. 返回类型 答案 ...

  • 类和对象3
  • 4.Java中的类和对象[第三章节草案] 收藏 本文目录:[蓝色部分为本章的目录] 1.基本概念 2.Java变量相关 1)Java变量分类 2)Java中变量的初始化 3)Java变量修饰符和访问域 4)Java类修饰符[不包含内部类] 3.Java涉及OO的关键知识点[主体] 1)继承的基本概念 ...

  • C面试笔试题
  • C/C++ 笔试.面试题目(1) 1.求下面函数的返回值(微软) int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; } 假定x = 9999. 答案:8 思路:将x转化为2进制, ...

  • C++面向对象程序设计考试试卷(详细讲解)
  • C++面向对象程序设计 考试试卷(详细讲解) 一. 单项选择题(共20题,每题1分,共20分) 1.下列关于C++标识符的命名不合法的是 C 与C#一样 A. Pad B. name_1 C. A#bc D. _a12 2.若有以下类型标识符定义: ( )D int x=2: char w='a': ...

  • 南昌大学C++题库
  • 一.单项选择题 1. 在C++语言中,对函数参数默认值描述正确的是:( ) A) 函数参数的默认值只能设定一个 B) 一个函数的参数若有多个,则参数默认值的设定可以不连续 C) 函数参数必须设定默认值 D) 在设定了参数的默认值后,该参数后面定义的所有参数都必须设定默认值 2. 假定 AB 为一个类 ...

  • 构造函数的作用是在创建对象时
  • 第八章 一,构造函数的作用是在创建对象时,系统自动调用它来给所创建的对象初始化. 构造函数的特点: ● 构造函数是一种成员函数,他的说明在类体内,它的函数体可写在类体内,也可以 写在类体外. ● 构造函数是一种特殊的成员函数,该函数的名字与类名相同.定义和说明构造函数 时,不必指明函数的类型. ● ...

  • C++_4_继承和派生
  • 第四章 继承和派生 §4-1 继承 §4-2 单继承 §4-3多继承 §4-1 继承 一.继承的含义 ① 面向对象方法提供了从问题域到解域的自然映射 客观世界中的对象既有共性,也有个性,通过不同程度的抽象,形成了分类和层次结构 不同层次的类之间体现了概括(generalize)和特化(specify ...

  • c++单选题
  • 单选题 1.下列关于面向对象概念的描述中,错误的是( C ). A.面向对象方法比面向过程方法更加先进 B.面向对象方法中使用了一些面向过程方法中没有的概念 C.面向对象方法替代了结构化程序设计方法 D.面向对象程序设计方法要使用面向对象的程序设计语言 2.下列各种高级语言中,不是面向对象的程序设计 ...

  • C++模拟试卷(一)
  • C++程序设计模拟试卷(一) 一.单项选择题(本大题共20小题,每小题1分,共20分) 在每小题列出的四个备选项中 只有一个是符合题目要求的,请将其代码填写在题后的括号内.错选.多选或未选均无 分. 1. 编写C++程序一般需经过的几个步骤依次是() A. 编辑.调试.编译.连接 B. 编辑.编译. ...