C++中,通过定义类(class)来自定义数据类型。类定义了该类型的对象包含的数据和该类型的对象可以执行的操作。标准库类型string、istream和ostream都定义成类。
C++对类的支持非常丰富——事实上,定义类是如此重要,我们把第三到第五部分对应于用来描述C++对类及类操作的支持。
在第1章中,我们使用Sales_item类型来解决书店问题。使用Sales_item类型的对象来记录对应于特定ISBN的销售数据。在这节中,我们先了解如何定义简单的类,如Sales_item类。
1. 设计类从其操作开始
每个类都定义了一个接口(interface)和一个实现(implementation)。接口包括我们期望使用该类的代码执行的操作。实现一般包括该类所需要的数据。实现还包括用来定义该类但又不作一般性使用的函数。
定义类时,通常先定义该类的接口,即该类所提供的操作。通过这些操作,可以决定该类完成其功能所需要的数据,以及是否需要定义一些函数来支持该类的实现。
我们将要定义的类型所支持的操作,就是我们在第1章中所用到的操作。这些操作如下(参见1.5.1节):
l 加法符,将两个Sales_item相加。
l 输入和输出操作符,读和写Sales_item对象。
l 赋值操作符,把Sales_item对象赋给另一个Sales_item对象。
l same_isbn函数,检测两个对象是否指同一本书。
在学完怎样定义函数和操作符后,我们将会在第7章和第14章看到该怎样来定义这些操作。虽然现在不能实现这些函数,但通过思考这些操作必须要实现的功能,我们可以看出该类需要什么样的数据。Sales_item类必须
(1) 记录特定书的销售册数。
(2) 记录该书的总销售收入。
(3) 计算该书的平均售价。
察看以上所列出的任务,可以知道需要一个unsigned类型的对象来记录书的销售册数,一个double类型的对象来记录总销售收入,然后可以用总收入除以销售册数计算出平均售价。因为我们还想知道是在记录哪本书,所以还需要定义一个string类型的对象来记录书的ISBN。
2. 定义Sales_item类
很明显,我们需要能够定义一种包含这三个数据元素和在第1章所用到的操作的数据类型。在C++语言中,定义这种数据类型的方法就是定义类:
class Sales_item {
public:
// operations on Sales_item objects will go here
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
类定义以关键字class开始,其后是该类的名字标识符。类体位于花括号里面。花括号后面必须要跟一个分号。
![]()
编程新手经常会忘记类定义后面的分号,这是个很普遍的错误!
类体可以为空。类体定义了组成该类型的数据和操作。这些操作和数据是类的一部分,也称为类的成员(member)。操作称为成员函数(1.5.2节),而数据则称为数据成员(data member)。
类也可以包含0个到多个private或public访问标签(access label)。访问标签控制类的成员在类外部是否可访问。使用该类的代码可能只能访问public成员。
定义了类,也就定义了一种新的类型。类名就是该类型的名字。通过命名Sales_item类,表示Sales_item是一种新的类型,而且程序也可以定义该类型的变量。
每一个类都定义了它自己的作用域(2.3.6节)。也就是说,数据和操作的名字在类的内部必须唯一,但可以重用定义在类外的名字。
3. 类的数据成员
定义类的数据成员和定义普通变量有些相似。我们同样是指定一种类型并给该成员一个名字:
std::string isbn;
unsigned units_sold;
double revenue;
这个类含有三个数据成员:一个名为isbn的string类型成员,一个名为units_sold的unsigned类型成员,一个名为revenue的double类型成员。类的数据成员定义了该类类型对象的内容。当定义Sales_item类型的对象时,这些对象将包含一个string型变量,一个unsigned型变量和一个double型变量。
定义变量和定义数据成员存在非常重要的区别:一般不能把类成员的初始化作为其定义的一部分。当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数(2.3.3节)的特殊成员函数控制初始化。我们将在7.7.3节定义Sales_item的构造函数。
4. 访问标签
访问标签控制着使用该类的代码是否可以使用给定的成员。类的成员函数可以使用类的任何成员,而不管其访问级别。访问标签public、private可以多次出现在类定义中。给定的访问标签应用到下一个访问标签出现为止。
类中public部分定义的成员在程序的任何部分都可以访问。一般把操作放在public部分,使得程序的任何代码都可以执行这些操作。
不是类的组成部分的代码不能访问private成员。通过设定Sales_item的数据成员为private,可以保证对Sales_item对象进行操作的代码不能直接操纵其数据成员。就像我们在第1章编写的程序那样,程序不能访问类中的private成员。Sales_item类型的对象可以执行那些操作,但是不能直接修改这些数据。
5. 使用struct关键字
C++支持另一个关键字struct,它也可以定义类类型。struct关键字是从C语言中继承过来的。
如果使用class关键字来定义类,那么定义在第一个访问标签前的任何成员都隐式指定为private;如果使用struct关键字,那么这些成员都是public。使用class还是struct关键字来定义类,仅仅影响默认的初始访问级别。
可以等效地定义Sales_item类为:
struct Sales_item {
// no need for public label, members are public by default
// operations on Sales_item objects
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
本例的类定义和前面的类定义只有两个区别:这里使用了关键字struct,并且没有在花括号后使用关键字public。struct的成员都是public,除非有其他特殊的声明,所以就没有必要添加public标签。
![]()
用class和struct关键字定义类的唯一差别在于默认访问级别:默认情况下,struct的成员为public,而class的成员为private。
习题
习题2.28 编译以下程序,确定你的编译器是否会警告遗漏了类定义后面的分号。
class Foo {
// empty
} // Note: no semicolon
int main()
{
return 0;
}
如果编译器的诊断结果难于理解,记住这些信息以备后用。
习题2.29 区分类中的public部分和private部分。
习题2.30 定义表示下列类型的类的数据成员:
(a) 电话号码 (b) 地址
(c) 员工或公司 (d) 某大学的学生





