前言:

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. 用JS写的放大镜

    代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta ...

  2. Linux进程间通信IPC学习笔记之有名管道

    基础知识: 有名管道,FIFO先进先出,它是一个单向(半双工)的数据流,不同于管道的是:是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)有一个与路径名关联的名 ...

  3. Linux安装oracle 10g常见问题之——OUI-25031

    OUI-25031:Some of the configuration assistants failed/cancelled. 这是安装过程中常见的错误之一. 引起此错误的原因:/etc/hosts ...

  4. 十五、mysql 分区之 分区管理

    1.mysql分区处理分区字段NULL值的方式 1.range分区null被当作最小值处理 2.list分区null值必须被枚举出来,否则将出错 3.hash/key分区 null值当作0处理 2.R ...

  5. centos6.5安装配置LDAP服务[转]

    安装之前查一下 1 find / -name openldap* centos6.4默认安装了LDAP,但没有装ldap-server和ldap-client 于是yum安装 1 su root 2 ...

  6. 如何在windows下安装GIT

    如何在windows下安装GIT 分步阅读 Git是一个免费的.开源的版本控制软件.在Windows上安装git,一般为msysgit,官方下载地址为 http://code.google.com/p ...

  7. 关于mapreduce过程中出现的错误:Too many fetch-failures

    Reduce task启动后第一个阶段是shuffle,即向map端fetch数据.每次fetch都可能因为connect超时,read超时,checksum错误等原因而失败.Reduce task为 ...

  8. sql with递归

       with temp as ( select Id, UserId, OfficeID, RoleId, DeptId, IsDelete, IsEnd, ParentId from [dbo]. ...

  9. DateTime.ToString()

       /*            [y]代表年份,注意是小写的y,大写的Y并不代表年份.            [M]表示月份.            [d]表示日期,注意D并不代表什么.       ...

  10. nodejs phantom add click event

    page.evaluate( function() { // find element to send click to var element = document.querySelector( ' ...