C++类的复制构造函数和赋值运算符

前言:

C++面向对象的编程过程中,凡是在类中运用到动态内存分配的时候总是会写一个显示的复制构造函数和赋值重载运算符,本文将结合C++ Primer Plus一书的内容分析下原因:

一、在C++编程中如果没有编写下列成员函数,系统会自动的提供:

(1)构造函数

(2)析构函数

(3)地址运算符

(4)赋值构造函数

(5)赋值运算符

其中(1)-(3)在编程中不会产生什么影响,但是(4)(5)会造成较大的影响

二、赋值构造函数

1、函数原型  Class_name(const Class_name &)

2、什么时候会用调用复制构造函数?

当同时满足以下两个条件的时候就会自动调用复制构造函数:

(1)新建一个对象;

(2)使用同类中现有对象初始化新对象。

除了直接看出来的一些表达式能满足以上两个条件,函数的按值传递(函数按值传递的是变量的副本)和函数返回对象的情况也同时满足了以上两个条件。而且有些情况编译器会生成临时变量,然后将临时变量在赋值给被传递的对象。

3、默认复制构造函数做了哪些事情?

默认赋值构造函数逐个复制非静态成员的值。注意是值,是一种浅复制。

浅复制会导致两个对象的指针指向同一个内存单元,这时如果某个对象已经析构执行delete,那么剩下的那个指针将会变成野指针,将造成灾难性的后果。特别当编译器会生成临时对象的情况,临时对象很快就执行析构函数了。。。

4、下面举个例子看看动态内存分配的情况不定义显示的赋值构造函数会出现什么问题

 1 // 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
 2 //
 3
 4 #include "stdafx.h"
 5 #include<iostream>
 6 using namespace std;
 7 class Str
 8 {
 9 public:
10     char * str;
11     int len;
12     static int num;
13     Str()
14     {
15         num++;
16         cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
17     }
18     Str(const char *s)
19     {
20         len=strlen(s);
21         str=new char[len+1];
22         strcpy(str,s);
23         num++;
24         cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
25     }
26     ~Str()
27     {
28         num--;
29         cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
30         delete []str;
31     }
32 };
33
34 //初始化静态变量
35 int Str::num=0;
36 void show1(Str & a)
37 {
38     cout<<a.str<<endl;
39 }
40 void show2(Str a)
41 {
42     cout<<a.str<<endl;
43 }
44 int _tmain(int argc, _TCHAR* argv[])
45 {
46     Str s1("s1");
47     show1(s1);
48     show2(s1);
49     return 0;
50 }

上述代码如果注释掉第48行,运行结果是这样的

如果注释掉第47行,而恢复第48行结果会变成这样

究其原因,因为void show1(Str & a)是按引用传递的,show1(s1);只需要将是s1的引用给a就可以了,并没有新建一个Str对象,所以不会调用默认的复制构造函数。

而void show2(Str a)是按值传递的,按值传递的过程是需要拷贝参数的副本到形参中的,这就需要新建一个Str对象,然后用已有的s1对象初始化,满足了调用复制构造函数的两个条件。而实际上过程比这还要麻烦一点,编译器会先生成一个临时对象,然后将s1拷贝给临时对象,再将临时对象拷贝给a,而由于临时对象析构的时候将str指向的内存释放掉了,而再执行析构s1(delete []str;)的时候,由于str指向的内容已被释放,所以cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;会乱码,而试图delete已经delete过的指针将会造成程序异常终止。由于默认复制构造函数中没有num++,而不管用那个构造函数构造出的对象调用的都是同一个析构函数,而析构函数中含有num--,所以临时对象导致num多减了一次,所以最后一句话会出现,“析构后对象的个数是-1”这样的结果。

5、解决办法:

定义一个显示的复制构造函数

Str(const Str & s)
 {
  len=s.len;
  str=new char[len+1];
  strcpy(str,s.str);
  num++;
  cout<<"现在的对象个数一共是"<<num<<endl;
 }

 1 // 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
 2 //
 3
 4 #include "stdafx.h"
 5 #include<iostream>
 6 using namespace std;
 7 class Str
 8 {
 9 public:
10     char * str;
11     int len;
12     static int num;
13     Str()
14     {
15         num++;
16         cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
17     }
18     Str(const Str & s)
19     {
20         len=s.len;
21         str=new char[len+1];
22         strcpy(str,s.str);
23         num++;
24         cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
25     }
26     Str(const char *s)
27     {
28         len=strlen(s);
29         str=new char[len+1];
30         strcpy(str,s);
31         num++;
32         cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
33     }
34     ~Str()
35     {
36         num--;
37         cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
38         delete []str;
39     }
40 };
41
42 //初始化静态变量
43 int Str::num=0;
44 void show1(Str & a)
45 {
46     cout<<a.str<<endl;
47 }
48 void show2(Str a)
49 {
50     cout<<a.str<<endl;
51 }
52 int _tmain(int argc, _TCHAR* argv[])
53 {
54     Str s1("s1");
55     //show1(s1);
56     show2(s1);
57     return 0;
58 }

三、赋值运算符

1、函数原型:Class_name & Class_name::operator=(const Class_name &)

2、什么时候调用默认的赋值运算符?

当将已有的对象赋给另一个对象时,将使用赋值运算符。

3、默认复制运算符做了什么事情?

其实它和默认的赋值构造函数差不多,都是进行浅复制。

4、还是浅复制造成的问题,下面举个例子

 1 // 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
 2 //
 3
 4 #include "stdafx.h"
 5 #include<iostream>
 6 using namespace std;
 7 class Str
 8 {
 9 public:
10     char * str;
11     int len;
12     static int num;
13     Str()
14     {
15         len=0;
16         str=new char[1];
17         str[0]=‘\0‘;
18         num++;
19         //cout<<"现在的对象个数一共是"<<num<<endl;
20     }
21     Str(const char *s)
22     {
23         len=strlen(s);
24         str=new char[len+1];
25         strcpy(str,s);
26         num++;
27         //cout<<"现在的对象个数一共是"<<num<<endl;
28     }
29     ~Str()
30     {
31         num--;
32         //cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
33         delete []str;
34     }
35     //显示的拷贝构造函数
36     Str(const Str & s)
37     {
38         len=s.len;
39         str=new char[len+1];
40         strcpy(str,s.str);
41         num++;
42         //cout<<"现在的对象个数一共是"<<num<<endl;
43     }
44
45 };
46
47
48 //初始化静态变量
49 int Str::num=0;
50 void show1(Str & a)
51 {
52     cout<<a.str<<endl;
53 }
54 void show2(Str a)
55 {
56     cout<<a.str<<endl;
57 }
58 int _tmain(int argc, _TCHAR* argv[])
59 {
60     Str s1("hello");
61     //show1(s1);
62     //show2(s1);
63     Str s2;
64     s2=s1;
65     cout<<s2.str<<endl;
66     return 0;
67 }

Str s2;s2=s1;这两句用到了赋值运算符,而浅复制导致s1和s2的指针指向了同一个位置,当s1被析构的时候s2指向的内存单元也被释放掉,所以再delete s2中的str的时候系统就崩溃啦。

4、解决办法:定义一个显示的赋值重载运算符

Str & Str::operator=(const Str & st)
{
 if(this==&st)
 {
  return *this;
 }
 delete [] str;
 len=st.len;
 str=new char[len+1];
 strcpy(str,st.str);
 return *this;
}

注意:返回引用是为了链式表达式,检测不能自己给自己赋值,否则delete [] str;后往哪找值赋给自己?

 1 // 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
 2 //
 3
 4 #include "stdafx.h"
 5 #include<iostream>
 6 using namespace std;
 7 class Str
 8 {
 9 public:
10     char * str;
11     int len;
12     static int num;
13     Str()
14     {
15         len=0;
16         str=new char[1];
17         str[0]=‘\0‘;
18         num++;
19         //cout<<"现在的对象个数一共是"<<num<<endl;
20     }
21     Str(const char *s)
22     {
23         len=strlen(s);
24         str=new char[len+1];
25         strcpy(str,s);
26         num++;
27         //cout<<"现在的对象个数一共是"<<num<<endl;
28     }
29     ~Str()
30     {
31         num--;
32         //cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
33         delete []str;
34     }
35     //显示的拷贝构造函数
36     Str(const Str & s)
37     {
38         len=s.len;
39         str=new char[len+1];
40         strcpy(str,s.str);
41         num++;
42         //cout<<"现在的对象个数一共是"<<num<<endl;
43     }
44     //显示的重载赋值运算符
45     Str & operator=(const Str & );
46 };
47
48 Str & Str::operator=(const Str & st)
49 {
50     if(this==&st)
51     {
52         return *this;
53     }
54     delete [] str;
55     len=st.len;
56     str=new char[len+1];
57     strcpy(str,st.str);
58     return *this;
59 }
60
61 //初始化静态变量
62 int Str::num=0;
63 void show1(Str & a)
64 {
65     cout<<a.str<<endl;
66 }
67 void show2(Str a)
68 {
69     cout<<a.str<<endl;
70 }
71 int _tmain(int argc, _TCHAR* argv[])
72 {
73     Str s1("hello");
74     //show1(s1);
75     //show2(s1);
76     Str s2;
77     s2=s1;
78     cout<<s2.str<<endl;
79     return 0;
80 }

程序中除了注意上述两点外还要注意构造函数写的是否全面,一开始写重载运算符=的时候忽略了下面这个构造函数中的str和len,导致Str s2后一直报错,晕。。。

Str()
 {
  len=0;
  str=new char[1];
  str[0]=‘\0‘;
  num++;
  //cout<<"现在的对象个数一共是"<<num<<endl;
 }

时间: 2024-11-06 18:28:51

C++类的复制构造函数和赋值运算符的相关文章

继承和动态内存分配——需要为继承类定义 显式析构函数、复制构造函数和赋值运算符

当派生类使用了new时,必须为派生了定义显式析构函数.复制构造函数和赋值运算符.(这里假设hasDMA类继承自baseDMA类)显式析构函数: baseDMA::~baseDMA() // takes care of baseDMA stuff { delete [] label; } hasDMA::~hasDMA() { delete [] style; } 复制构造函数: baseDMA::baseDMA(const baseDMA & rs) { label = new char[std

带复制构造函数、赋值运算符的模板队列

#ifndef QUEUE_HPP #define QUEUE_HPP #include <assert.h> #include <stddef.h> template <typename T> class Queue; template <typename T> class Node{ friend class Queue<T>; public: Node(T data = 0, Node *next = NULL) :data_(data),

关于C++类中的土著民:构造函数,复制构造函数,析构函数

我们初学C++时可能会对类的构造函数,复制构造函数,析构函数有点疑问.整理如下(个人见解,如有错误,还望指正.): 1.构造函数 根据构造函数的定义知它的作用是初始化类的数据成员或内嵌类的对象,所以它的参数表就应该是它要初始化的对象类型.构造函数分三类:默认构造函数.构造函数.委托构造函数. 默认构造函数 默认构造函数没有返回值,没有参数表,没有函数体,如果类内没有显式的定义构造函数,系统会自动生成默认构造函数,如果已经定义了构造函数,但仍需要默认构造函数,可以在默认构造函数参数表后加defau

c++OOP之复制控制 ------复制构造函数、赋值重载、析构

本博文我们讨论OOP复制控制的一些内容: 首先考虑对象复制的时机: 非引用类型 1):根据一个类去显式或者隐式初始化一个对象: 2):复制一个对象,将它作为实参传给一个函数: 3):从函数返回时复制一个对象.(string tolittle(string word)) 一个空类,编译器提供默认无参数构造函数.拷贝构造函数.赋值运算符以及析构函数,一共四个函数.(面试) 11.复制构造函数.赋值运算符以及析构函数,称为三法则,一旦提供了其中一个,务必提供其余两个.以String为例: a) 涉及到

C++的简单总结(复制构造函数,深拷贝,前拷贝,默认属性)

类的三大属性: private,public,protected 1,对于类的成员变量或者函数,缺省即为私有 #include <iostream> using namespace std; class A { int y; //私有成员 int x; //私有成员 public: A(int xx, int yy) {x = xx; y = yy;} void setx(int m) {x = m;} void sety(int n) {y = n;} }; int main() { A a

复制构造函数的运用

构造函数和析构函数 一.构造函数: 1.普通构造函数:在对象被创建时利用特定的值构造对象,将对象初始化到一个特定的状态. 特性:构造函数的函数名和类名相同:没有返回值:在对象被创建时被自动调用:如果有构造函数,建立对象时必须给出初始值. 2.复制构造函数(特殊的构造函数) 复制构造函数和赋值运算符的行为差不多,都是将一个对象的值复制给另一个对象:但是复制构造函数是使用一个已经存在的对象去初始化一个同类的新对象而赋值运算符是把对象的值复制给一个已经存在的变量. 复制构造函数在以下三种情况下会被调用

函数重载与复制构造函数

函数重载与复制构造函数   一.函数重载 1.普通函数重载 用main函数多次重复调用一个相同名字但是不同类型的函数来处理不同类型的数据. 如 void func(int); void func(double); float func(float); void func(double); 2.成员函数的重载 我们可以将函数的重载推广到类的成员函数. Class  boy { Public: void  sum(); void  sum(int  x, int  y); } 二.函数的默认参数 在

C++ 复制构造函数

C++类的设计中,如果某些函数没有显式定义,C++会自动生成,复制构造函数便是其中之一,其他的还有默认构造函数.赋值操作符.默认析构函数.地址操作符.一个类的复制构造函数的原型一般为: Class_name (const Class_name &); 一.何时调用复制构造函数 在新建一个对象并将其初始化为同类对象的时候,常常会调用复制构造函数,如: Class_name A(B); Class_name A = B ; Class_name A = Class_name(B); Class_na

不使用编译器自动生成的拷贝构造函数和赋值运算符的方法

方法1:声明私有的拷贝构造函数和赋值运算符,这样不但阻止了编译器生成默认版本,并且使得用户无法调用他们,但是这时成员函数和友元函数还是可以调用他们,为了阻止他们的调用可以不定义这些私有的拷贝构造函数和赋值运算符.(标准库中也是如此阻止拷贝的) 代码段1.1:HomeForSale.h文件 #ifndef HOMEFORSALE_H #define HOMEFORSALE_H class CHomeForSale { public: CHomeForSale(){} private: CHomeF