Sales_item 专题
1.5. 类的简介解决书店问题之前,还需要弄明白如何编写数据结构来表示交易数据。C++ 中我们通过定义类来定义自己的数据结构。类机制是 C++ 中最重要的特征之一。事实上,C++ 设计的主要焦点就是使所定义的类类型的行为可以像内置类型一样自然。我们前面已看到的像 istream 和 ostream 这样的库类型,都是定义为类的,也就是说,它们严格说来不是语言的一部分。 完全理解类机制需要掌握很多内容。所幸我们可以使用他人写的类而无需掌握如何定义自己的类。在这一节,我们将描述一个用于解决书店问题的简单类。当我们学习了更多关于类型、表达式、语句和函数的知识(所有这些在类定义中都将用到)后,将会在后面的章节实现这个类。 使用类时我们需要回答三个问题:
对于书店问题,我们假定类命名为 Sales_item 且类定义在命名为 Sales_item.h 的头文件中。 1.5.1. Sales_item 类Sales_item 类的目的是存储 ISBN 并保存该书的销售册数、销售收入和平均售价。我们不关心如何存储或计算这些数据。使用类时我们不需要知道这个类是怎样实现的,相反,我们需要知道的是该类提供什么操作。 正如我们所看到的,使用像 IO 一样的库工具,必须包含相关的头文件。类似地,对于自定义的类,必须使得编译器可以访问和类相关的定义。这几乎可以采用同样的方式。一般来说,我们将类定义放入一个文件中,要使用该类的任何程序都必须包含这个文件。 依据惯例,类类型存储在一个文件中,其文件名如同程序的源文件名一样,由文件名和文件后缀两部分组成。通常文件名和定义在头文件中的类名是一样的。通常后缀是 .h,但也有一些程序员用 .H、.hpp 或 .hxx。编译器通常并不挑剔头文件名,但 IDE 有时会。假设我们的类定义在名为 Sale_item.h 的文件中。 Sales_item 对象上的操作每个类定义一种类型,类型名与类名相同。因此,我们的 Sales_item 类定义了一种命名为 Sales_item 的类型。像使用内置类型一样,可以定义类类型的变量。当写下 Sales_item item;
就表示 item 是类型 Sales_item 的一个对象。通常将“类型 Sales_item 的一个对象”简称为“一个 Sales_item 对象”,或者更简单地简称为“一个 Sales_item”。 除了可以定义 Sales_item 类型的变量,我们还可以执行 Sales_item 对象的以下操作:
读入和写出 Sales_item 对象知道了类提供的操作,就可以编写一些简单的程序使用这个类。例如,下面的程序从标准输入读取数据,使用该数据建立一个 Sales_item 对象,并将该 Sales_item 对象写到标准输出: #include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item book;
// read ISBN, number of copies sold, and sales price
std::cin >> book;
// write ISBN, number of copies sold, total revenue, and average price
std::cout << book << std::endl;
return 0;
}
如果输入到程序的是 0-201-70353-X 4 24.99
则输出将是 0-201-70353-X 4 99.96 24.99
输入表明销售了 4 本书,每本价格是 24.99 美元。输出表明卖出书的总数是 4 本,总收入是 99.96 美元,每本书的平均价格是 24.99 美元。 这个程序以两个 #include 指示开始,其中之一使用了一种新格式。iostream 头文件由标准库定义,而 Sales_item 头文件则不是。Sales_item 是一种自定义类型。当使用自定义头文件时,我们采用双引号(" ")把头文件名括起来。
在 main 函数中,首先定义一个对象,命名为 book,用它保存从标准输入读取的数据。下一条语句读入数据到此对象,第三条语句将它打印到标准输出,像平常一样紧接着打印 endl 来刷新缓冲区。 将 Sales_item 对象相加更有趣的例子是将两个 Sales_item 对象相加: #include <iostream> #include "Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1 >> item2; // read a pair of transactions std::cout << item1 + item2 << std::endl; // print their sum return 0; } 如果我们给这个程序下面的输入: 0-201-78345-X 3 20.00
0-201-78345-X 2 25.00
则输出为 0-201-78345-X 5 110 22
程序首先包含两个头文件 Sales_item 和 iostream。接下来定义两个 Sales_item 对象来存放要求和的两笔交易。输出表达式做加法运算并输出结果。从前面列出的操作,可以得知将两个 Sales_item 相加将创建一个新对象,新对象的 ISBN 是其操作数的 ISBN,销售的数量和收入反映其操作数中相应值的和。我们也知道相加的项必须具有同样的 ISBN。 值得注意的是这个程序是如何类似于第 1.2.2 节中的程序:读入两个输入并输出它们的和。令人感兴趣的是,本例并不是读入两个整数并输出两个整数的和,而是读入两个 Sales_item 对象并输出两个 Sales_item 对象的和。此外,“和”的意义也不同。在整数的实例中我们产生的是传统求和——两个数值相加后的结果。在 Sales_item 对象的实例上我们使用了在概念上有新意义的求和——两个 Sales_item 对象的成分相加后的结果。 1.5.2. 初窥成员函数不幸的是,将 Sales_item 相加的程序有一个问题。如果输入指向了两个不同的 ISBN 将发生什么?将两个不同 ISBN 的数据相加没有意义。为解决这个问题,首先检查 Sales_item 操作数是否都具有相同的 ISBN。 #include <iostream> #include "Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1 >> item2; // first check that item1 and item2 represent the same book if (item1.same_isbn(item2)) { std::cout << item1 + item2 << std::endl; return 0; // indicate success } else { std::cerr << "Data must refer to same ISBN" << std::endl; return -1; // indicate failure } } 这个程序和前一个程序不同之处在于 if 测试语句以及与它相关联的 else 分支。在解释 if 语句的条件之前,我们明白程序的行为取决于 if 语句中的条件。如果测试成功,那么产生与前一程序相同的输出,并返回 0 表示程序成功运行完毕。如果测试失败,执行 else 后面的语句块,输出信息并返回错误提示。 什么是成员函数上述 if 语句的条件 // first check that item1 and item2 represent the same book
if (item1.same_isbn(item2)) {
调用命名为 item1 的 Sales_item 对象的成员函数。成员函数是由类定义的函数,有时称为类方法。 成员函数只定义一次,但被视为每个对象的成员。我们将这些操作称为成员函数,是因为它们(通常)在特定对象上操作。在这个意义上,它们是对象的成员,即使同一类型的所有对象共享同一个定义也是如此。 当调用成员函数时,(通常)指定函数要操作的对象。语法是使用点操作符(.): item1.same_isbn
意思是“命名为 item1 的对象的 same_isbn 成员”。点操作符通过它的左操作数取得右操作数。点操作符仅应用于类类型的对象:左操作数必须是类类型的对象,右操作数必须指定该类型的成员。
通常使用成员函数作为点操作符的右操作数来调用成员函数。执行成员函数和执行其他函数相似:要调用函数,可将调用操作符(())放在函数名之后。调用操作符是一对圆括号,括住传递给函数的实参列表(可能为空)。 same_isbn 函数接受单个参数,且该参数是另一个 Sales_item 对象。函数调用 item1.same_isbn(item2)
将 item2 作为参数传递给名为 same_isbn 的函数,该函数是名为 item1 的对象的成员。它将比较参数 item2 的 ISBN 与函数 same_isbn 要操作的对象 item1 的 ISBN。效果是测试两个对象是否具有相同的 ISBN。 如果对象具有相同的 ISBN,执行 if 后面的语句,输出两个 Sales_item 对象的和;否则,如果对象具有不同的 ISBN,则执行 else 分支的语句块。该块输出适当的错误信息并退出程序,返回 -1。回想 main 函数的返回值被视为状态指示器;本例中,返回一个非零值表示程序未能产生期望的结果。 |