int a 100;int b a;
而类对象与普通对象不同类对象内部结构一般较为复杂存在各种成员变量。
下面看一个类对象拷贝的简单例子:
1 #include 2 using namespace std; 3 4 class CExample 5 { 6 private: 7 int a; 8 9 public:10 CExample(int b) //构造函数11 {12 a b;13 printf("constructor is called\n");14 }15 16 CExample(const CExample c.a;19 printf("copy constructor is called\n");20 }21 22 ~CExample() //析构函数23 {24 cout <<"destructor is called\n";25 }26 27 void Show()28 {29 cout < endl;30 }31 };32 33 int main()34 {35 CExample A(100);36 CExample B A;37 B.Show();38 return 0;39 }
运行结果
二、拷贝构造函数的调用时机
1. 当函数的参数为类的对象时:
1 #include 2 using namespace std; 3 4 class CExample 5 { 6 private: 7 int a; 8 public: 9 CExample(int b)10 {11 a b;12 printf("constructor is called\n");13 }14 15 CExample(const CExample c.a;18 printf("copy constructor is called\n");19 }20 21 ~CExample()22 {23 cout <<"destructor is called\n";24 }25 26 void Show()27 {28 cout < endl;29 }30 };31 32 void g_fun(CExample c)33 {34 cout <<"g_func" << endl;35 }36 37 int main()38 {39 CExample A(100);40 CExample B A;41 B.Show();42 g_fun(A);43 return 0;44 }
运行结果
2. 当函数的返回值是类的对象时
1 #include 2 using namespace std; 3 4 class CExample 5 { 6 private: 7 int a; 8 9 public: 10 CExample(int b) //构造函数11 {12 a b;13 printf("constructor is called\n");14 }15 16 CExample(const CExample c.a;19 printf("copy constructor is called\n");20 }21 22 ~CExample() //析构函数23 {24 cout <<"destructor is called\n";25 }26 27 void Show()28 {29 cout < endl;30 }31 };32 33 CExample g_fun()34 {35 CExample temp(0);36 return temp;37 }38 39 int main()40 {41 g_fun();42 return 0;43 }
运行结果
【分析】 当g_Fun()函数执行到return时会产生以下几个重要步骤 (1). 先会产生一个临时变量就叫XXXX吧。 (2). 然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像CExample XXXX(temp); (3). 在函数执行到最后先析构temp局部变量。 (4). 等g_fun()执行完后再析构掉XXXX对象。
3. 对象需要通过另外一个对象进行初始化
CExample A(100);CExample B A;
三、浅拷贝与深拷贝
1. 默认拷贝构造函数
很多时候在我们都不知道拷贝构造函数的情况下传递对象给函数参数或者函数返回对象都能很好的进行这是因为编译器会给我们自动产生一个拷贝构造函数这就是“默认拷贝构造函数”这个构造函数很简单仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值它一般具有以下形式
Rect::Rect(const Rect r.width;heigh t r.height;}
当然以上代码不用我们编写编译器会为我们自动生成。但是如果认为这样就可以解决对象的复制问题那就错了让我们来考虑以下一段代码
1 #include 2 using namespace std; 3 class Rect 4 { 5 public: 6 Rect() 7 { 8 count; 9 }10 11 ~Rect()12 {13 count--;14 }15 16 static int getCount()17 {18 return count;19 }20 21 private:22 int width;23 int height;24 static int count;25 };26 27 int Rect::count 0;28 29 int main()30 {31 Rect rect1;32 cout <<"The count of Rect:" <
【分析】这段代码对前面的类加入了一个静态成员目的是进行计数。在主函数中首先创建对象rect1输出此时的对象个数然后使用rect1复制出对象rect2再输出此时的对象个数按照理解此时应该有两个对象存在但实际程序运行时输出的都是1反应出只有1个对象。此外在销毁对象时由于会调用销毁两个对象类的析构函数会调用两次此时的计数器将变为负数。
原因就是因为拷贝构造函数没有处理静态数据成员。
出现这些问题最根本就在于在复制对象时计数器没有递增我们重新编写拷贝构造函数如下
1 { 2 public: 3 Rect() 4 { 5 count; 6 } 7 8 Rect(const Rect r.width;11 height r.height;12 count;13 }14 15 ~Rect()16 {17 count--;18 }19 static int getCount()20 {21 return count;22 }23 24 private:25 int width;26 int height;27 static int count;28 };29 30 int Rect::count 0;31 32 int main()33 {34 Rect rect1;35 cout <<"The count of Rect:" <
2. 浅拷贝
所谓浅拷贝指的是在对象复制时只对对象中的数据成员进行简单的赋值默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了但是一旦对象存在了动态成员那么浅拷贝就会出问题了让我们考虑如下一段代码
1 #include 2 #include 3 using namespace std; 4 5 class Rect 6 { 7 public: 8 Rect() 9 {10 p new int(100);11 }12 13 ~Rect()14 {15 assert(p ! NULL);16 delete p;17 }18 19 private:20 int width 0;21 int height 0;22 int *p;23 };24 25 int main()26 {27 Rect rect1;28 Rect rect2(rect1);29 return 0;30 }
在这段代码运行结束之前会出现一个运行错误。原因就在于在进行对象复制时对于动态分配的内容没有进行正确的操作。我们来分析一下
在运行定义rect1对象后由于在构造函数中有一个动态分配的语句因此执行后的内存情况大致如下
在使用rect1复制rect2时由于执行的是浅拷贝只是将成员的值进行赋值这时 rect1.p rect2.p也即这两个指针指向了堆里的同一个空间如下图所示
当然这不是我们所期望的结果在销毁对象时两个对象的析构函数将对同一个内存空间释放两次这就是错误出现的原因。我们需要的不是两个p有相同的值而是两个p指向的空间有相同的值解决办法就是使用“深拷贝”。
3. 深拷贝
在“深拷贝”的情况下对于对象中动态成员就不能仅仅简单地赋值了而应该重新动态分配空间如上面的例子就应该按照如下的方式进行处理
#include#includeusing namespace std;class Rect{public:Rect(){p new int(100);}Rect(const Rect r.width;height r.height;p new int(100);*p *(r.p);}~Rect(){assert(p ! NULL);delete p;}private:int width 0;int height 0;int *p;};int main(){Rect rect1;Rect rect2(rect1);return 0;}
此时在完成对象的复制后内存的一个大致情况如下
此时rect1的p和rect2的p各自指向一段内存空间但它们指向的空间具有相同的内容这就是所谓的“深拷贝”。
3. 防止默认拷贝发生
通过对对象复制的分析我们发现对象的复制大多在进行“值传递”时发生这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数这样因为拷贝构造函数是私有的如果用户试图按值传递或函数返回该类对象将得到一个编译错误从而可以避免按值传递或返回对象。
1 #include 2 using namespace std; 3 4 class CExample //防止按值传递 5 { 6 private: 7 int a; 8 9 public:10 CExample(int b) //构造函数11 {12 a b;13 cout <<"creat: " < endl;14 }15 16 private:17 CExample(const CExample //拷贝构造函数只是声明18 19 public:20 ~CExample()21 {22 cout <<"delete: " < endl;23 }24 25 void Show()26 {27 cout < endl;28 }29 };30 31 void g_Fun(CExample C) //???? 32 {33 cout <<"test" << endl;34 }35 36 int main()37 {38 CExample test(1);39 //g_Fun(test); //按值传递将出错40 41 return 0;42 }
小结 拷贝有两种深拷贝浅拷贝。
当出现类的等号赋值时会调用拷贝函数在未定义显示拷贝构造函数的情况下系统会调用默认的拷贝函数——即浅拷贝它能够完成成员的一一复制。当数据成员中没有指针时浅拷贝是可行的。但当数据成员中有指针时如果采用简单的浅拷贝则两类中的两个指针将指向同一个地址当对象快结束时会调用两次析构函数而导致指针悬挂现象。所以这时必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据从而也就解决了指针悬挂的问题。简而言之当数据成员中有指针时必须要用深拷贝。
四、拷贝构造函数的几个细节
1.为什么拷贝构造函数必须是引用传递不能是值传递 解答简单的回答是为了防止递归引用。 当 一个对象需要以值方式传递时编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话当 需要调用类A的拷贝构造函数时需要以值方式传进一个A的对象作为实参 而以值方式传递需要调用类A的拷贝构造函数结果就是调用类A的拷贝构造函数导 致又一次调用类A的拷贝构造函数这就是一个无限递归。
2. 拷贝构造函数里能调用private成员变量吗? 解答其时从名子我们就知道拷贝构造函数其时就是一个特殊的构造函数操作的还是自己类的成员变量所以不受private的限制。
3. 以下函数哪个是拷贝构造函数,为什么?
1 X::X(const X //拷贝构造函数2 X::X(X); 3 X::X(X1); //拷贝构造函数4 X::X(X1, int b2); //拷贝构造函数
4. 一个类中可以存在多于一个的拷贝构造函数吗? 解答类中可以存在超过一个拷贝构造函数。
1 class X { 2 public: 3 X(const X // const 的拷贝构造 4 X(X // 非const的拷贝构造 5 };
【注意】• 如果一个类中只存在一个参数为 X构造函数能否重载析构函数能否重载为什么回答一构造函数可以析构函数不可以。
问题二析构函数为什么一般情况下要声明为虚函数 回答二虚函数是实现多态的基础当我们通过基类的指针是析构子类对象时候如果不定义成虚函数那只调用基类的析构函数子类的析构函数将不会被调用。如果定义为虚函数则子类父类的析构函数都会被调用。 问题三什么情况下必须定义拷贝构造函数 回答三当类的对象用于函数值传递时值参数返回类对象拷贝构造函数会被调用。如果对象复制并非简单的值拷贝那就必须定义拷贝构造函数。例如大的堆栈数据拷贝。如果定义了拷贝构造函数那也必须重载赋值操作符。
六、 参考资料
【1】c拷贝构造函数(深拷贝浅拷贝)详解
转:https://www.cnblogs.com/sunbines/p/9036107.html
【文章出处:香港gpu服务器 http://www.558idc.com/hkgpu.html 复制请保留原URL】