C++之 构造函数 拷贝构造函数 析构函数 赋值操作
2015-10-17 15:17
381 查看
编译器默认给C++生成四个成员函数,分别是构造函数、拷贝构造函数、析构函数和赋值操作。对这四个函数的考察经常在很多IT公司的笔试或者面试题目中出现。本文以mystring类为例,讲述默认生成的四个成员函数的基本操作。
mystring类的声明
构造函数
析构函数,默认的析构函数不能删除new运算符在堆中分配的对象或对象成员。如果对象中成员占用的内存是在堆中分配的,必须定义析构函数,然后显示的使用delete运算符来释放内存。
如果不显示的编写类的拷贝构造函数和赋值操作符,编译器将以“位拷贝”的方式自动生成缺省的函数,倘若类中含有指向堆上申请的内存的指针,那么这两个缺省的成员函数就隐含了错误。
如string类定义两个变量 A和B,A.m_data = "hello", B.m_data="world",若将A = B,则将造成三个错误,① A.m_data对应的内存未被释放,② 此时A.m_data和B.m_data指向同一个内存块,A和B任何一方变动都会影响另一方,③ B中的m_data被释放了两次,导致运行时程序崩溃。因此需要我们自定义拷贝构造函数。
拷贝构造函数,其参数是const对象的引用。
赋值操作符函数
一直以为上面的赋值操作符函数是一个标准的写法,在《名企面试官精讲典型编程题》中,上面的赋值操作符函数只是初级程序员的写法。
上面的赋值操作函数,若在分配内存之前先用delete释放了m_pData的内存。如果此时内存不足导致new char抛出异常,m_pData将是一个空指针,这样非常容易导致程序崩溃。也就是说一旦在赋值运算符函数内部抛出一个异常,mystring的实例不再保持有效状态,这就违背了C++异常安全性的原则(Exception Safety)。
有两个方法可以解决上述问题,① 内存分配成功之后再删除m_pData对应的内存,② (更好的方法)创建一个临时对象,再交换临时对象和原来的对象,代码如下。
上述代码中,先创建一个临时对象obTmp,接着把obTmp.m_pData 和自身的 m_pData交换,由于obTmp是一个局部变量,当程序执行完if内的语句之后,程序会自动调用obTmp的析构函数,把obTmp.m_pData指向的内存释放掉。在新的代码中,我们在mystring的构造函数里用new分配内存,如果出现因内存不足抛出bad_alloc等异常,以前对象的状态还没有修改,对象的状态还是有效的,这也就保证了异常安全性。
测试
参考
《名企面试官精讲典型编程题》
mystring类的声明
#include <iostream> #include <string> using namespace std; class mystring { public: mystring(char* str=NULL); //构造函数 mystring(const mystring &obString); //拷贝构造函数 ~mystring(); //析构函数 mystring& operator=(const mystring &obString); //赋值操作符 void show(){ cout<<m_pData<<endl; } private: char* m_pData; };
构造函数
mystring::mystring(char* str) { if (NULL == str){ m_pData = new char[1]; m_pData[0]='\0'; }else{ m_pData = new char[strlen(str)+1]; strcpy(m_pData, str); } }
析构函数,默认的析构函数不能删除new运算符在堆中分配的对象或对象成员。如果对象中成员占用的内存是在堆中分配的,必须定义析构函数,然后显示的使用delete运算符来释放内存。
mystring::~mystring() { delete [] m_pData; }
如果不显示的编写类的拷贝构造函数和赋值操作符,编译器将以“位拷贝”的方式自动生成缺省的函数,倘若类中含有指向堆上申请的内存的指针,那么这两个缺省的成员函数就隐含了错误。
如string类定义两个变量 A和B,A.m_data = "hello", B.m_data="world",若将A = B,则将造成三个错误,① A.m_data对应的内存未被释放,② 此时A.m_data和B.m_data指向同一个内存块,A和B任何一方变动都会影响另一方,③ B中的m_data被释放了两次,导致运行时程序崩溃。因此需要我们自定义拷贝构造函数。
拷贝构造函数,其参数是const对象的引用。
mystring::mystring(const mystring &obString) { m_pData = new char[strlen(obString.m_pData)+1]; strcpy(m_pData, obString.m_pData); }
赋值操作符函数
mystring& mystring::operator=(const mystring &obString) { if (this == &obString){ //检查是否是自赋值 return *this; } delete[] m_pData; //删除原来申请的内存空间 m_pData = new char[strlen(obString.m_pData)+1]; strcpy(m_pData, obString.m_pData); return *this; }
一直以为上面的赋值操作符函数是一个标准的写法,在《名企面试官精讲典型编程题》中,上面的赋值操作符函数只是初级程序员的写法。
上面的赋值操作函数,若在分配内存之前先用delete释放了m_pData的内存。如果此时内存不足导致new char抛出异常,m_pData将是一个空指针,这样非常容易导致程序崩溃。也就是说一旦在赋值运算符函数内部抛出一个异常,mystring的实例不再保持有效状态,这就违背了C++异常安全性的原则(Exception Safety)。
有两个方法可以解决上述问题,① 内存分配成功之后再删除m_pData对应的内存,② (更好的方法)创建一个临时对象,再交换临时对象和原来的对象,代码如下。
mystring& mystring::operator=(const mystring &obString) { if (this != &obString){ mystring obTmp(obString); char* pTmp = obTmp.m_pData; obTmp.m_pData = m_pData; m_pData = pTmp; } return *this; }
上述代码中,先创建一个临时对象obTmp,接着把obTmp.m_pData 和自身的 m_pData交换,由于obTmp是一个局部变量,当程序执行完if内的语句之后,程序会自动调用obTmp的析构函数,把obTmp.m_pData指向的内存释放掉。在新的代码中,我们在mystring的构造函数里用new分配内存,如果出现因内存不足抛出bad_alloc等异常,以前对象的状态还没有修改,对象的状态还是有效的,这也就保证了异常安全性。
测试
int main() { char *p = "ABCDE"; mystring B(p); //验证构造函数 B.show(); mystring AD = B; //验证赋值操作符 AD.show(); mystring C(B); //验证拷贝构造函数 C.show(); return 0; }
参考
《名企面试官精讲典型编程题》
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- 解析C++中派生的概念以及派生类成员的访问属性
- C++ Custom Control控件向父窗体发送对应的消息