前言:

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++类的复制构造函数和赋值运算符的更多相关文章

  1. C++ 复制构造函数 与 赋值运算符

    在C++中,将一个对象赋给另外一个对象,编译器将提供赋值运算符的定义. 有两种情况,下面假设catman是Monster的一个实例 第一种:初始化 Monster golblen= catman; 第 ...

  2. C++学习之路(五):复制构造函数与赋值运算符重载

    之前没有细想过两者的区别,今天对此进行简要记录,后续完善补充. 复制构造函数是在类对象被创建时调用的,但是赋值运算符是被已经存在的对象调用完成赋值操作. 复制构造函数只在对象实例化时才被调用,即在复制 ...

  3. 为my_string类创建复制构造函数copy constructor ,拷贝函数名和类同名

    为下面的my_string类创建一个复制构造函数,并将定义该类的代码提交. my_string类的定义: class my_string { char *s; public: my_string(ch ...

  4. C++ 类 复制构造函数 The Copy Constructor

    一.复制构造函数的定义 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性.复制构造函数创建一个新的对象,作为另一个对象的拷贝.复制构造函数只含有一个形参,而且其形参为本类对象的引用.复制构 ...

  5. C++中复制构造函数与重载赋值操作符总结

    前言 这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义: 2.复制构造函数和重载赋值操作符的调用时机: 3.复制构造函数和重载赋值操作符 ...

  6. C++:复制构造函数在什么时候被调用?

    这个问题不是疑问了,查了一下国外网站,总结一下.假设Person是一个类,复制构造函数的调用会在以下几种情况下发生: 1.对象在创建时使用其他的对象初始化 Person p(q); //此时复制构造函 ...

  7. C++中复制构造函数与重载赋值操作符

    我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类:   class CTe ...

  8. C++ 复制控制之复制构造函数

    7月26日更新: 过了这么长的时间回过头来看,发现文章中有几个点说错(用红字标出): 构造函数不是只有唯一一个参数,它也可以是多参数形式,其第二参数及后继以一个默认值供应. 不是没有声明复制控制函数时 ...

  9. C++ 复制构造函数

    C++类的设计中,如果某些函数没有显式定义,C++会自动生成,复制构造函数便是其中之一,其他的还有默认构造函数.赋值操作符.默认析构函数.地址操作符.一个类的复制构造函数的原型一般为: Class_n ...

随机推荐

  1. C#源码大汇总

    C#高仿QQ2013可在广域网部署聊天系统GG叽叽 动态显示硬盘分区容量饼图 自定义ProgressBar控件高仿Win8进度条 多皮肤精美在线QQ悬浮客服插件 jQuery仿天猫首页多格焦点图片轮播 ...

  2. Export功能 导致 页面显示很多非法字符,还可能页面显示两次

    private void exportBinaryToExcel(byte[] bytes, string filename) { Response.AddHeader("Content-D ...

  3. php无极分类

    <?php date_default_timezone_set('PRC'); header('Content-type:text/html;charset=UTF-8'); /* $a_lis ...

  4. http://phantomjs.org/page-automation.html

    http://phantomjs.org/page-automation.html install brew curl -LsSf http://github.com/mxcl/homebrew/ta ...

  5. nodejs read/write file

    fs.readFile('c:\\tmp\\helloworld.txt','utf8',function(err,data){console.log(data);}) var token=fs.re ...

  6. PHPCMS搭建wap手机网站

    PHPCMS搭建PC端网站比较方便,但是在wap手机端方面却不怎么实用,而且自带的手机建站感觉不是很好,而且模版不好控制,现在对其进行修改,手机建站个人感觉比较方便 首先在phpcms/libs/fu ...

  7. Ext4.2 grid 条件查询使用

    项目中用到Ext4.2,初次接触,用的不是太熟,做个总结,恳请指正! 1.grid重新设置条件,查询结果不是从第1页开始 在处理grid条件查询时,点击搜索按钮调用store.load()方法时,会把 ...

  8. [转载]Eziriz .NET Reactor 4.7.0.0 官方原版+破解补丁(强大的代码保护和软件防盗版工具)

    Eziriz .NET Reactor 是一个强大的代码保护和软件防盗版工具,完全由.NET框架编写..NET Reactor支持NET平台的软件许可系统,并支持NET程序集所有语言.当.Net编译器 ...

  9. P​H​P​ ​5​.​3​连​接​s​q​l​ ​s​e​r​v​e​r​ ​2​0​0​8​ ​R​2

    我的机器为: xp sp3 sql server 2008 developer apache 2.2.2 php 5.3  从5.3开始,php就不再提供mssql.dll了,所以要php连接sql  ...

  10. Akka Stream文档翻译:Motivation

    动机 Motivation The way we consume services from the internet today includes many instances of streami ...