第二章 用户自定义类型
2.3 类
将数据说明与其操作分离开来有其优势,例如我们可以以任意方式使用数据。但对于用户自定义类型来说,为了具备“真正的类型”所需的所有性质,在其表示形式和操作之间建立紧密的联系是很有必要的。特别是,我们通常希望保持数据表示对用户不可见,从而实现易用性、保证数据使用的一致性以及允许设计者未来改进数据表示。为此,我们必须将类型的接口(所有人均可使用)与其实现(可访问对外部不可见的数据)分离开来。在C++中,实现上述目的的语言机制称为类(class)。类含有一系列成员(member),它可以是数据、函数或者类型。类的public成员定义了接口,private成员则只能通过接口访问。例如:
class Vector{
public:
Vector(int s):elem{new double[s]}, sz{s}{} //构造一个Vector
double& operator[](int i){return elem[i];} //使用下标访问元素
int size(){return sz;}
private:
double* elem; //指向元素的指针
int sz; //元素数目
};
在此基础上,我们可以定义新类型Vector的一个变量:
Vector v(6); //该Vector对象含有6个元素
下面解释了这个Vector变量的构成:
本质上,Vector对象是一个“句柄”,它包含指向元素的指针(elem)以及元素数目(sz)。在不同Vector对象中元素数目可能不同,即使同一个Vector对象在不同时刻也可能含不同数目的元素,但Vector对象本身的大小永远保持不变。这是C++语言处理可变数量信息的一项基本技术:一个固定大小的句柄指向位于“别处”(如通过new分配的*空间)的一组可变数量的数据。
在这里,我们只能通过Vector的接口访问其数据表示(成员elem和sz),而接口是由其public成员提供的:Vector(),operator()和size()。这样,2.2的read_and_sum()示例可简化为:
double read_and_sum(int s)
{
Vector v(s); //创建一个包含s个元素的向量
for(int i = 0; i != v.size(); ++i)
cin>>v[i]; //读入元素
double sum = 0;
for(int i = 0; i != v.size(); ++i)
sum+=v[i]; //计算元素的和
return sum;
}
与所属类同名的成员“函数”称为构造函数(constructor),即,它是用来构造类的对象的。因此构造函数Vector()替换了Vector_init()。与普通函数不同,编译器会保证在初始化类对象时使用构造函数,因此,定义构造函数可以消除类变量未初始化问题。
Vector(int)规定了Vector对象的构造方式。特别是,它声明需要一个整数来构造对象,这个整数用于指定元素数目。构造函数使用成员初始化列表来初始化Vector的成员:
:elem{new double[s]},sz{s}
这条语句的含义是:首先从*空间获取s个double类型的元素,然后用指向这些元素的指针初始化elem; 然后使用s初始化sz。
访问元素的功能由下标函数operator[]提供的,它返回所需元素的引用(double&,即允许读也允许写)。
size()函数的作用是向使用者提供元素数目。
显然,我们完全没有涉及错误处理,但将在3.5节提及。类似地,我们也没有提供一种机制来“归还”通过new获取的double数组,4.2.2节将介绍如何使用析构函数来优雅地完成这一任务。
struct和class没有本质区别,struct就是一种成员默认为public的class。例如,你也可以为struct定义构造函数和其他成员函数。