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 ...
随机推荐
- 前端构建工具gulp入门教程
本文假设你之前没有用过任何任务脚本(task runner)和命令行工具,一步步教你上手Gulp.不要怕,它其实很简单,我会分为五步向你介绍gulp并帮助你完成一些惊人的事情.那就直接开始吧. 第一步 ...
- Delphi Variant oleVariant
The OleVariant type exists on both the Windows and Linux platforms. The main difference between Vari ...
- 大型网站用什么技术比较好,JSP,PHP,ASP.NET
大型网站,我建议要考虑的问题: 首先讨论一下大型网站需要注意和考虑的问题. 数据库海量数据处理:负载量不大的情况下select.delete和update是响应很迅速的,最多加几个索引就可以搞定,但千 ...
- Python本地化例子 - gettext 模块
关键字:Python 3.4,gettext,本地化,Localization OS:Windows 7,Mac 1. 创建一个locsample.py文件,文件内容如下,把所有需要本地化的字符串放到 ...
- vs中使用过的扩展和好的nuget库
扩展 ReAttach ReAttach gives you an easy way to ReAttaching your prior debug targets. ReAttach stores ...
- WinForm控件选择器
jQuery和Css的控件选择器用起来非常畅快,相信用过的人都会有这种感觉,而WinForm则是通过Name来实现窗体中控件的选择,在选择单个控件的时候是很方便,但是当选择具有一类特征的控件时,则显得 ...
- bnu 4359 无爱编号(规律)
http://www.bnuoj.com/bnuoj/problem_show.php?pid=4359 [题意]:输入N,表示几位数,0-这个N位数,有多少个满足条件的号码,不满足的情况为出现4,1 ...
- 如何通过logcat查看系统程序的意图
如果在logcat中不能看到系统程序启动时的意图的类名, 以打开图库(gallery)为例,可以通过在ddms中如图设置,就可以在tomcat中查看到gallery启动时的意图.
- Codeforces Round #260 (Div. 1) C. Civilization 树的中心+并查集
题目链接: 题目 C. Civilization time limit per test1 second memory limit per test256 megabytes inputstandar ...
- Mybatis的分页查询
示例1:查询业务员的联系记录 1.控制器代码(RelationController.java) //分页列出联系记录 @RequestMapping(value="toPage/custom ...