| |
private:
char* m_data; //用于保存字符串
};
//String的普通构造函数
String::String(const char* str)
{
if(str == NULL)
{
m_data = new char[1];
*m_data = '\0';
}
else
{
int length = strlen(str);
m_data = new char[length + 1];
strcpy(m_data, str);
}
}
//String的析构函数
String::~String(void)
{
delete[] m_data;
}
//拷贝构造函数
String::String(const String& other)
{
//允许操作other的私有成员m_data
int length = strlen(other, m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
}
//赋值函数
String& String::operator=(const String& other)
{
//(1)检查自赋值
if(this == &other)
return *this;
//(2)释放原有的内存资源
delete[] m_data;
//(3)分配新的内存资源,并复制内容
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
//(4)返回本对象的引用
return *this;
}
5)构造函数与析构函数的起源:
a)根据经验,不少哪一察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人以往。所以,在设计C++语言时,把对象的初始化工作放在构造函数中,把清除工作放在析构函数中。当对象被创建时,构造函数被自动执行,当对象消亡时,析构函数被自动执行。这样,就不用担心忘了对象的初始化和清除工作。
b)构造函数与析构函数的名字不能随便起,必须让编译器认得出才可以被自动执行,所以,让构造函数、析构函数与类同名,由于析构函数的目的与构造函数的相反,就在前面加一个~以示区别。
c)除了名字以外,构造函数与析构函数的另一个特别之处就是没有返回值类型。因为构造函数与析构函数的使命非常明确,如果它们又返回值类型,那么编译器将不知所错。为了防止节外生枝,干脆设定没有返回值类型。
6)构造函数的初始化表
a)初始化表位于函数参数表之后,却在函数体{}之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
b)构造函数初始化表的使用规则:
i)如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
ii)类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。
iii)类的数据成员的初始化可以采用初始化表或者函数体内复制两种方式,这两种方式的效率不完全相同:非内部数据类型的成员对象应当采用第一种方式(初始化表)初始化,以获取更高的效率。
iv)对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但是后者的程序版式更清晰些。
7)构造和析构的次序
a)构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构次序相反。
b)成员对象初始化的次序只由成员对象在类中声明的次序决定,而与它们在初始化表中的次序无关。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序。
8)不要轻视拷贝构造函数与复制函数
a)如果不主动编写拷贝构造函数和复制函数,编译器将以“位拷贝”的方式自动生成缺省的函数。
b)拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
9)示例:类String的拷贝构造函数与赋值函数
a)类String拷贝构造函数与普通构造函数的区别是:在函数入口处无需与NULL进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。
b)类String的赋值函数比构造函数复杂得多,分四部实现:
i)检查自赋值。因为间接的自赋值有可能出现。
ii)用delete释放原有的内存资源。防止造成内存泄露。
iii)分配新的内存资源,并复制字符串。注意:strcpy连'\0'一起复制。
iv)返回本对象的引用。目的是为了实现像a=b=c这样的链式表达。
10)偷懒的办法处理拷贝构造函数与赋值函数:如果实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成缺省函数,偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
11)如何在派生类中实现类的基本函数?
a)基类的构造函数、析构函数、赋值函数都不能被派生类继承。
b)如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:
i)派生类的构造函数应在其初始化表里调用基类的构造函数。
ii)基类与派生类的析构函数应该为虚(即加virtual关键字)。
iii)在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。
class Base
{
public:
//...
Base& perator=(const Base& other);// 类Base的赋值函数
private:
int m_i, m_j, m_k;
};
class Derived:public Base
{
public:
//...
Derived& perator=(const Derived* other); //类Derived的赋值函数
private:
int m_x, m_y, m_z;
};
Derived& Derived::operate=(const Derived& other)
{
//(1)检查自赋值
if(this == &other)//注意不要错写成if(*this == other)
return *this;
//(2)对基类的数据成员重新赋值
Base::operate=(other);//因为不能直接操作私有数据成员
//(3)对派生类的数据成员赋值
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
//(4)返回本对象的引用
return *this;//不要错写成return this;
//也不能写成return other;因为我们不知道参数other的生命期。有可能other是个临时对象,在赋值结束后它马上消失,那么return other返回的将是垃圾。
}