拷贝和赋值都是为了使一个变量具有和某个变量一样的值。C++ 中的拷贝是指 拷贝构造函数 (Copy Constructor),赋值是指重载运算符 = 。
它们看起来可能如下。
class A { public: int _v; public: A(const A &a){ // 拷贝构造函数 _v = a._v; } A& operator = (const A &a) { // 赋值运算符 _v = a._v; return *this; } };
注意上面赋值运算符中的 "return *this"。如果去掉这一句会有什么影响呢?
既然都是使为了复制变量的值,又为什么又需要两种操作呢。考虑下面的代码:
classA a; classA b(a); // 调用 copy constructor classA c; // 调用default constructor c = a; // 调用 assignment operator
拷贝构造函数和赋值运算符可以说是事物的一体两面。
拷贝和赋值都是对一个对象进行复制,是需要耗费资源的。为了节省资源,就有了引用的概念。
先看如下代码:
classA a; a._v = 3; classA b = a; classA c; c = a; a._v = 5; std::cout<< c._v << std::endl; // 输出 3 classA &d = a; // 引用 a._v = 6; std::cout<< d._v << std::endl; // 输出 6 classA e; &e = a; // 错误用法 class A *f; f = &a; // 指针 std::cout<< f->_v << std::endl; // 输出 6
前两种情况 (b=a, c=a)我们已经讲过,效果上是一样的,只是用了不同的方式实现,其中 c=a 的实现更费资源,因为多了一步构造调用默认函数。
d = a 和 b=a 相比多了一个 '&'号,表示引用,这样 d 和 a 实际上指向内存中同一个地址。这个就叫做引用。看似两个不同的变量,实则是同一个对象。
至于 e = a,则是错误的用法。因为这里的 '&' 号成立取地址符号。等号左边是地址,右边是对象,不是同一个概念。改成下面的 f = &a 就对了。这个就是指针。指针的实质就是对象的地址。其中的'&'号表示获取对象 a 的地址。通过指针访问对象就要用符号 '->' 了,这与变量用'.'是不一样的。
明白了上面的例子,应该就抓住了指针和引用的本质。
下面的例子既是对拷贝构造函数的分析,也是对值或值的引用应用的分析,最好能深刻理解。
#include <iostream> class A { public: int v; A(int i):v(i){ std::cout<<"init A ..."<<v<<std::endl; } A(const A &a){ v = a.v; std::cout<<"copy A ..."<<v<<std::endl; } ~A(){ std::cout << "destroy A ..." << v<< std::endl; } void print(){ std::cout<<"print A ..."<< v<< std::endl; } }; class B { public: int v; A class_a; B(int i, int j):v(i),class_a(j){ std::cout<<"init B ..."<< v<< std::endl; } ~B(){ std::cout<<"destroy B ..."<< v<< std::endl; } void print(){ std::cout<<"print B ..."<< v<< std::endl; } A& getA1(){ return class_a; } A getA2(){ return class_a; } }; int main(int argc, char **argv){ B b(3,4); std::cout<<"----------"<< std::endl; b.getA1().print(); std::cout<<"----------"<< std::endl; b.getA2().print(); std::cout<<"----------"<< std::endl; { A a1 = b.getA1(); a1.v = 5; } std::cout<<"----------"<< std::endl; { A a1 = b.getA2(); a1.v = 6; } std::cout<<"----------"<< std::endl; { A &a2 =b.getA1(); a2.v = 7; } std::cout<<"----------"<< std::endl; { // A &a2 =b.getA2(); // error this one // a2.v = 8; } std::cout<<"----------"<< std::endl; return 0; }
上面程序的输出结果如下:
init A ...4 init B ...3 ---------- print A ...4 ---------- copy A ...4 print A ...4 destroy A ...4 ---------- copy A ...4 destroy A ...5 ---------- copy A ...4 destroy A ...6 ---------- ---------- destroy B ...3 destroy A ...7
TODO: 拷贝构造函数和赋值运算时的自赋值