面试题:
用c++编写类的String的构造函数、拷贝构造函数、析构函数和赋值函数
具体实现代码如下:
#include<iostream>
using namespace std;
class String
{
private:
char *p;
public:
String(const char*p); //普通构造函数
String(const String& other); //拷贝构造函数
String& operator=(const String& other); //赋值运算符重载函数
~String(void); //析构函数
char* C_str(void) const; //常量函数
};
String::String(const char*p)
{
cout << "String::String(const char*p)" << endl;
this->p = new char[strlen(p)+1]; //申请存放字符串的空间
strcpy(this->p,p); //用以传参
}
String::String(const String& other)
{
cout << "String::String(const String& other)" << endl;
this->p = new char[strlen(other.p)+1]; //自定义复制构造函数,避免指针的double free
strcpy(this->p,other.p);
}
String& String::operator=(const String& other)
{
cout << "operator=()is calling...." << endl;
if(this != &other)
{
char *tmp=new char[strlen(other.p)+1];
if( tmp==NULL )
return *this;
strcpy(tmp,other.p);
delete[]p;
p=tmp;
}
return *this;
}
String::~String(void)
{
cout << "String::~String(void)" << endl;
if(p)
{
delete[]p;
p=NULL;
}
}
char* String::C_str(void) const
{
return p;
}
int main()
{
String x("Hello,world");
String y("my name is leikun");
String z("comeon,baby!");
cout << x.C_str() << endl;
cout << y.C_str() << endl;
cout << z.C_str() << endl;
z=y=x;
cout << z.C_str() << endl;
String s1=x;
cout << "s1=" << s1.C_str() << endl;
return 0;
}
程序运行结果截图:
程序分析解读:
1:什么是赋值运算符函数?
默认的复制赋值运算符函数对于基本类型的成员变量,按字节复制,对于指针的类型成员变量值,复制指针本身,而不是指针所指向的真实数据,这和默认复制构造函数浅复制效果类似,当然,也会出现使用默认复制构造函数时的二次释放问题。所以,建议使用自定义复制赋值运算符函数;
赋值运算符函数的返回值本身没有限制,但建议将其返回值的类型定义为和内置该运算符函数的类型相同(eg: clock& operator=(const clock& t) );
通常当类的数据成员存在指针类型时,就需要在构造函数中为该指针分配内存空间,在析构函数中释放空间,但是为了防止空间的重复释放,需要自定义拷贝构造函数和赋值运算符函数;
赋值运算符函数的实现流程通常分为五步,详情参见上面例子。(eg: String& operator=(const String& other) )
2:拷贝构造函数和赋值函数之间区别与联系
两者都是从看起来有很大的相同点,都是从一个数据对象中赋值数据,但是使用时有很大的区别:具体归纳为以下三点:
是否产生新对象:
拷贝构造函数是一个构造函数,他调用时必须产生一个对象,也就是用一个已存在的对象去构造一个不存在的对象,重点体会“构造”两字;而复制赋值运算符函数是通过参数传进来,对已经存在的对象赋值,并不产生新对象,即用一个已经存在的对象去覆盖另一个已经存在的对象,重点体会“赋值”两字。
调用时机不同:
复制(拷贝)构造函数是用已经存在对象去初始化另一个新的对象,也就是说复制构造函数是在对象创建并初始化的时候调用;而复制赋值运算符函数要在对象初始化完成后调用。(eg: Sting s("hello,world"); String t=s; //此处调用复制构造函数;Sting s("hello,world"); String t; t=s; //此处调用的是复制赋值运算符函数)。
是否释放原来的内存空间:
如果宿主类的数据成员是指针,都需要分配空间。复制构造函数需要重新申请内存空间,但不需要先释放原来的内存空间,这是因为不能和传递的对象公用相同的内存空间,否则在析构函数中会出现二次释放( double free )的问题。而复制赋值运算符函数使用时,对象已经存在,也就是说数据指针的内存空间已经存在,但这段空间的长度不一定满足赋值的需求,所以需要先释放已有的内存空间,在根据赋值对象的成员大小重新申请内存空间,然后在进行赋值。