您的位置:首页 > 编程语言 > C语言/C++

C++之 构造函数 拷贝构造函数 析构函数 赋值操作

2015-10-17 15:17 381 查看
编译器默认给C++生成四个成员函数,分别是构造函数、拷贝构造函数、析构函数和赋值操作。对这四个函数的考察经常在很多IT公司的笔试或者面试题目中出现。本文以mystring类为例,讲述默认生成的四个成员函数的基本操作。

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;
}

参考

《名企面试官精讲典型编程题》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息