引:一道经常见到的面试题 ,已知类String的原型为:
class String
{
public:
String(const char *str = NULL);// 普通构造函数
String(const String &other); // 复制构造函数
~ String(void); // 析构函数
String & operate =(const String &other);// 重载赋值操作符
private:
char *m_data;
};
请实现上述String的四个函数。
这道题涉及到了类型的复制构造函数,赋值操作符和析构函数的实现,其中有许多要注意的地方。参考c++ primer第13章
1. 复制构造函数
它是一种特殊的构造函数,单个形参,该形参为对该类型的引用(常为const),当定义一个新对象并用铜类型的对象进行初始化时,将显示的调用复制构造函数,当把该类型的对象传递给函数或从函数返回该类型的对象时,将隐式的调用复制构造函数。
a.为什么形参必须为引用?考虑如下代码
class A { private: int value; public: A(int n){value=n;} A(A other){value=other.value;} void functionA(){}; ~A(){} }; int main(void) { A a = 10; A b = a; b.functionA(); }
通常,这段代码会显示编译错误的,假设能编译执行,那么由于A(A other)是值传递,在执行A b = a 时,会调用A(A other)并把a当作形参复制到实参(隐式调用A(A other)),即出现了在复制构造函数中调用构造函数,形成无条件递归。所以C++标准不允许复制构造函数为值传递。正确形式应为:A(const A& other)。
b.何时需要自定义复制构造函数?
如果我们没有定义复制构造函数,编译器会为我们合成一个(即便定义了其他构造函数),默认行为为逐个成员初始化为原对象的副本。
当类中有指针,或有成员在构造时需要分配其他资源,通常需要定义复制构造函数。这其中有涉及到对象的深拷贝和浅拷贝,如果一个类拥有资源(A如char *str 指向一个字符数组),当这个类的对象发生复制过程时,资源再次分配(B:str=new char[length+1]; if(str!=NULL);strcpy(str,A.str);),这个过程就是深拷贝,反之,没有再次分配资源(B:str=A.str),就是浅拷贝;
类禁止复制时,需要定义复制构造函数(声明为private)。
通过以上,则String的复制构造函数可以定义如下:
String::String(const String& other) { int length = strlen(other.m_data); m_data = new char[length+1]; //注意'\0' strcpy(m_data, other.m_data); }
2.重载赋值操作符
赋值操作符以this(隐藏)和同类型对象的const引用作为形参。返回对同类型的引用。通常一个类定义复制构造函数的同时需要定义赋值操作符。
a.在赋值之前,需要释放当前对象的资源,否则会形成内存泄露;又因为如此,在这之前需要判断传入的参数和当前对象是否是同一个实例,否则delete时会造成严重错误。
String& String::operator=(const String& other) { if(this == &other) { return *this; } delete[] m_data; int length = strlen(other.m_data); m_data = new char[lenght+1]; if(m_data != NULL) { strcpy(m_data,other.m_data); } return *this; } //可能有更好更安全的实现,以后再说
3.析构函数
a.销对象时会调用析构函数。对于动态分配的对象,只有指向该对象的指针被删除时才撤销。析构函数通常释放在构造函数或在对象生命周期内获取的资源;
b.默认析构函数(编译器总会生成),它按成员在类中声明的次序的逆序撤销成员(非static)。但默认析构函数不删除指针指向的对象;
c.析构函数没有返回值,没有形参,所以不能重载它。
String::~String()
{
delete [] mdata;
}
后记:通常这三个复制控制成员中我们定义一个,则它也需要定义另外两个 ,即所谓的“rule of three”。