第十二章 使类对象像一个数值一样工作
当我们定义了自己的类的时候,我们就可以控制对类进行的扩展,使其对象可以像数值一样工作。适当地定义复制与赋值操作,可以使该类的对象工作起来就像数值一样。也就是说,类的编写者们可以设法使各个对象之间彼此独立。
类的编写者们可以控制类型转换以及对类型对象的相关操作,从而写出与C++自带类型的对象几乎一样的类。在标准库中的string类就是这种类型的一个很好的例子,它有丰富的函数支持自动转换。在本章我们将定义string类的一个简化版本,并且把该类的名字叫做Str。本章的大部分讨论将围绕着如何为Str类设计一个友好的接口而展开。
12.1 一个简单的string类
class Str{
public:
typedef Vec<char>::size_type size_type;
//默认构造函数,创建一个空的Str
Str(){
}
//生成一个Str对象,包含c的n个复件
Str(size_type n,char c):data(n,c){
}
//生成一个Str对象并用一个空字符结尾的字符数组来初始化
Str(const char* cp){
std::copy(cp,cp+std::strlen(cp),std::back_inserter(data));
}
//生成一个Str对象并用迭代器b和e之间的内容对它进行初始化
template<class In>Str(In b,In e){
std::copy(b,e,std::back_inserter(data));
}
private:
Vec<char> data;
};
最后一个构造函数,它本身是一个模板函数。因为是一个模板,它事实上有效地定义了一组构造函数,随着不同类型的迭代器实例化出不同的构造函数。例如,这个构造函数可以被用来从一个字符组构造一个Str类型对象,也可以从一个Vec类型对象构造Str类型对象。
在Str类中并没有定义复制构造函数,也没有定义赋值运算符函数或者析构函数。因为有默认的操作。
Str类中本身没有分配内存的能力。它把管理内存的细节留给编译器,让编译器自动生成相应的函数,而这些函数通过调用Vec中的相应的函数来进行操作。为了看清这些默认的操作,我们注意到Str类不需要一个析构函数。实际上,如果真要为它写一个析构函数,这个析构函数也没有什么工作要做。一般来说,一个不需要析构函数的类也不需要显式的定义复制构造函数或赋值运算符函数。
12.2 自动转换
在Str类中已经有了这样的一个构造函数,它带有一个const char*
类型的参数。所以,编译器可以在需要一个Str类型的对象,但是程序提供的却是一个const char*
类型的对象时候调用这个构造函数。在把一个const char*
类型的对象赋给Str类型的变量的时候也会调用这个构造函数。在我们写出s="hello"
这样的表达式时,编译器实际上调用Str(const char*)
构造函数为这个字符串常量构造一个没有名字、局部的、临时的Str类型对象。然后再调用编译器自动生成的赋值运算符函数把这一临时值赋给s。
12.3 Str操作
cin >> s;
cout << s;
s[i];
s1 + s2;
如果被定义的运算符是一个成员函数,那么其中一个参数可以是隐式被提供的
class Str{
public:
//构造函数同前
char& operator[](size_type i){
return data[i];