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、下面举个例子看看动态内存分配的情况不定义显示的赋值构造函数会出现什么问题
// 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include<iostream>
using namespace std;
class Str
{
public:
char * str;
int len;
static int num;
Str()
{
num++;
cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
}
Str(const char *s)
{
len=strlen(s);
str=new char[len+];
strcpy(str,s);
num++;
cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
}
~Str()
{
num--;
cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
delete []str;
}
}; //初始化静态变量
int Str::num=;
void show1(Str & a)
{
cout<<a.str<<endl;
}
void show2(Str a)
{
cout<<a.str<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
Str s1("s1");
show1(s1);
show2(s1);
return ;
}
上述代码如果注释掉第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;
}
// 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include<iostream>
using namespace std;
class Str
{
public:
char * str;
int len;
static int num;
Str()
{
num++;
cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
}
Str(const Str & s)
{
len=s.len;
str=new char[len+];
strcpy(str,s.str);
num++;
cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
}
Str(const char *s)
{
len=strlen(s);
str=new char[len+];
strcpy(str,s);
num++;
cout<<"新创建了对象"<<str<<"现在的对象个数一共是"<<num<<endl;
}
~Str()
{
num--;
cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
delete []str;
}
}; //初始化静态变量
int Str::num=;
void show1(Str & a)
{
cout<<a.str<<endl;
}
void show2(Str a)
{
cout<<a.str<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
Str s1("s1");
//show1(s1);
show2(s1);
return ;
}

三、赋值运算符
1、函数原型:Class_name & Class_name::operator=(const Class_name &)
2、什么时候调用默认的赋值运算符?
当将已有的对象赋给另一个对象时,将使用赋值运算符。
3、默认复制运算符做了什么事情?
其实它和默认的赋值构造函数差不多,都是进行浅复制。
4、还是浅复制造成的问题,下面举个例子
// 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include<iostream>
using namespace std;
class Str
{
public:
char * str;
int len;
static int num;
Str()
{
len=;
str=new char[];
str[]='\0';
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
Str(const char *s)
{
len=strlen(s);
str=new char[len+];
strcpy(str,s);
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
~Str()
{
num--;
//cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
delete []str;
}
//显示的拷贝构造函数
Str(const Str & s)
{
len=s.len;
str=new char[len+];
strcpy(str,s.str);
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
} }; //初始化静态变量
int Str::num=;
void show1(Str & a)
{
cout<<a.str<<endl;
}
void show2(Str a)
{
cout<<a.str<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
Str s1("hello");
//show1(s1);
//show2(s1);
Str s2;
s2=s1;
cout<<s2.str<<endl;
return ;
}

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;后往哪找值赋给自己?
// 复制构造函数探索.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include<iostream>
using namespace std;
class Str
{
public:
char * str;
int len;
static int num;
Str()
{
len=;
str=new char[];
str[]='\0';
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
Str(const char *s)
{
len=strlen(s);
str=new char[len+];
strcpy(str,s);
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
~Str()
{
num--;
//cout<<"将析构了一个对象"<<str<<"析构后对象的个数是"<<num<<endl;
delete []str;
}
//显示的拷贝构造函数
Str(const Str & s)
{
len=s.len;
str=new char[len+];
strcpy(str,s.str);
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
//显示的重载赋值运算符
Str & operator=(const Str & );
}; Str & Str::operator=(const Str & st)
{
if(this==&st)
{
return *this;
}
delete [] str;
len=st.len;
str=new char[len+];
strcpy(str,st.str);
return *this;
} //初始化静态变量
int Str::num=;
void show1(Str & a)
{
cout<<a.str<<endl;
}
void show2(Str a)
{
cout<<a.str<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
Str s1("hello");
//show1(s1);
//show2(s1);
Str s2;
s2=s1;
cout<<s2.str<<endl;
return ;
}

程序中除了注意上述两点外还要注意构造函数写的是否全面,一开始写重载运算符=的时候忽略了下面这个构造函数中的str和len,导致Str s2后一直报错,晕。。。
Str()
{
len=0;
str=new char[1];
str[0]='\0';
num++;
//cout<<"现在的对象个数一共是"<<num<<endl;
}
C++类的复制构造函数和赋值运算符的更多相关文章
- C++ 复制构造函数 与 赋值运算符
在C++中,将一个对象赋给另外一个对象,编译器将提供赋值运算符的定义. 有两种情况,下面假设catman是Monster的一个实例 第一种:初始化 Monster golblen= catman; 第 ...
- C++学习之路(五):复制构造函数与赋值运算符重载
之前没有细想过两者的区别,今天对此进行简要记录,后续完善补充. 复制构造函数是在类对象被创建时调用的,但是赋值运算符是被已经存在的对象调用完成赋值操作. 复制构造函数只在对象实例化时才被调用,即在复制 ...
- 为my_string类创建复制构造函数copy constructor ,拷贝函数名和类同名
为下面的my_string类创建一个复制构造函数,并将定义该类的代码提交. my_string类的定义: class my_string { char *s; public: my_string(ch ...
- C++ 类 复制构造函数 The Copy Constructor
一.复制构造函数的定义 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性.复制构造函数创建一个新的对象,作为另一个对象的拷贝.复制构造函数只含有一个形参,而且其形参为本类对象的引用.复制构 ...
- C++中复制构造函数与重载赋值操作符总结
前言 这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义: 2.复制构造函数和重载赋值操作符的调用时机: 3.复制构造函数和重载赋值操作符 ...
- C++:复制构造函数在什么时候被调用?
这个问题不是疑问了,查了一下国外网站,总结一下.假设Person是一个类,复制构造函数的调用会在以下几种情况下发生: 1.对象在创建时使用其他的对象初始化 Person p(q); //此时复制构造函 ...
- C++中复制构造函数与重载赋值操作符
我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类: class CTe ...
- C++ 复制控制之复制构造函数
7月26日更新: 过了这么长的时间回过头来看,发现文章中有几个点说错(用红字标出): 构造函数不是只有唯一一个参数,它也可以是多参数形式,其第二参数及后继以一个默认值供应. 不是没有声明复制控制函数时 ...
- C++ 复制构造函数
C++类的设计中,如果某些函数没有显式定义,C++会自动生成,复制构造函数便是其中之一,其他的还有默认构造函数.赋值操作符.默认析构函数.地址操作符.一个类的复制构造函数的原型一般为: Class_n ...
随机推荐
- Mac下使用Web服务器性能/压力测试工具webbench、ab、siege
Web开发,少不了的就是压力测试,它是评估一个产品是否合格上线的基本标准,下面我们来一一剖析他们的使用方式. 测试前,前面先把系统的端口限制数改大,看看Mac下面的默认限制 ulimit -a ope ...
- Get code int value for different encoding
http://msdn.microsoft.com/en-us/library/system.text.encodinginfo.getencoding%28v=vs.110%29.aspx usin ...
- PHP取二进制文件头快速判断文件类型
<?php /*文件扩展名说明 *7173 gif *255216 jpg *13780 png *6677 bmp *239187 txt,aspx,asp,sql *208207 xls.d ...
- python字典根据value排序
参考: http://docs.python.org/2/howto/sorting.html http://www.cnpythoner.com/post/266.html http://ghost ...
- const和#define的区别
在刷题的时候经常遇到定义全局常量我一般都是用#define(可能是因为很少接触const的原因) 在昨天做到51nod1082时照常暴力用#define定义最大.可是提交超时..... 后来看他人写的 ...
- 用JS写的无缝滚动特效
代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta ...
- Python 信号量
信号的概念 信号(signal)-- 进程之间通讯的方式,是一种软件中断.一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号. 几个常用信号: SIGINT 终止进程 中断进 ...
- FPGA位宽的转换和定义
数字表达式的定义<位宽><进制><数字>,这是一种全面的描述方式 例如:如果我要定义一个变量counter = 5000 0000 ,10进制数:那么他的位宽应该是 ...
- matlab之点运算基本思想及几何平移变换
1.对数变换可以增强图像中较暗部分的细节,因为对数可以将较小的值放大,而较大的值缩小 2.伽马变换:y = (x + esp) ^ γ,x,y的取值范围是0到1,esp是补偿系数,γ为伽马系数.γ的不 ...
- 微软职位内部推荐-Senior Network Engineer
微软近期Open的职位: Global Foundation Services is the team behind the cloud. GFS is responsible for deliver ...