前言:

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. Mysql 存储过程小例子

    创建存储过程: DELIMITER $$ USE `database_name`$$ DROP PROCEDURE IF EXISTS `add_or_update_user`$$ )) BEGIN ...

  2. Dapper.ColumnMapper 的使用

    using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; usin ...

  3. if...else..的错误用法

    1.最近在写js代码完成一个前段DOM操作的函数时,自己错误的使用了if..else..控制体.为什么是错误的呢?看看我的 代码你就明白了: document.getElementsByClassNa ...

  4. Java实现MySQL在线管理

    问题: 1.如何实现在线创建MySQL数据库.表: 2.如何实现在线执行sql语句? 思路: 1.可以通过java.sql.Statement类的executeUpdate(String sql)方法 ...

  5. MySQL日期和时间函数

    WEEKDAY( date ) 返回date的星期索引( = 星期天 ) . mysql> select WEEKDAY( '1997-10-04 22:23:00' ) ; mysql> ...

  6. nginx+php-fpm 502 bad gateway

    输出日志配置: http://blog.csdn.net/wzy_1988/article/details/8486888 解决方案: http://www.cnblogs.com/jackluo/p ...

  7. Mac 启用http-dav功能(WebDAV服务器)

    启用Mac的WebDAV可以实现文件的上传以及Windows.Linux和Mac之间的数据互传. 客户端使用:windows下使用网上邻居 --> 添加一个网上邻居  --> 输入uplo ...

  8. DB天气app冲刺第十天

    好了 这是第十天了,按照白板任务上的来说的话,今天没有完成,所以等一下还要继续看看今天能不能把他做完,今天出的问题在于我又自己调整了一下UI设计,因为发现以前的设计发面有重复,浪费了屏幕.所以还不如省 ...

  9. Linux Mint SmoothTask2的安装方法

    首先,先下载smooth task:点击这里下载 下载之后解压缩,里面有个install文件,点击打开: To install plasmoid unpack archive, go to the d ...

  10. [转]BluetoothDevice.getType()-一个常常被忽略了的函数。好用的不要不要的

    自动安卓发布了4.0版本的蓝牙协议之后,越来越多的开发者收到了各种针对于BLE蓝牙的开发需求. 而且有很多时候还需要兼容以前的3.0版本,给大家的开发带来了困扰,笔者也遇到了这样的问题,偶然间发现了g ...