Team LiB
Previous Section Next Section

1.5. Introducing Classes

1.5. 类的简介

The only remaining feature we need to understand before solving our bookstore problem is how to write a data structure to represent our transaction data. In C++ we define our own data structure by defining a class. The class mechanism is one of the most important features in C++. In fact, a primary focus of the design of C++ is to make it possible to define class types that behave as naturally as the built-in types themselves. The library types that we've seen already, such as istream and ostream, are all defined as classesthat is, they are not strictly speaking part of the language.

解决书店问题之前,还需要弄明白如何编写数据结构来表示交易数据。C++ 中我们通过定义类来定义自己的数据结构。机制是 C++ 中最重要的特征之一。事实上,C++ 设计的主要焦点就是使所定义的类类型的行为可以像内置类型一样自然。我们前面已看到的像 istreamostream 这样的库类型,都是定义为类的,也就是说,它们严格说来不是语言的一部分。

Complete understanding of the class mechanism requires mastering a lot of information. Fortunately, it is possible to use a class that someone else has written without knowing how to define a class ourselves. In this section, we'll describe a simple class that we can use in solving our bookstore problem. We'll implement this class in the subsequent chapters as we learn more about types, expressions, statements, and functionsall of which are used in defining classes.

完全理解类机制需要掌握很多内容。所幸我们可以使用他人写的类而无需掌握如何定义自己的类。在这一节,我们将描述一个用于解决书店问题的简单类。当我们学习了更多关于类型、表达式、语句和函数的知识(所有这些在类定义中都将用到)后,将会在后面的章节实现这个类。

To use a class we need to know three things:

使用类时我们需要回答三个问题:

  1. What is its name?

    类的名字是什么?

  2. Where is it defined?

    它在哪里定义?

  3. What operations does it support?

    它支持什么操作?

For our bookstore problem, we'll assume that the class is named Sales_item and that it is defined in a header named Sales_item.h.

对于书店问题,我们假定类命名为 Sales_item 且类定义在命名为 Sales_item.h 的头文件中。

1.5.1. The Sales_item Class

1.5.1. Sales_item

The purpose of the Sales_item class is to store an ISBN and keep track of the number of copies sold, the revenue, and average sales price for that book. How these data are stored or computed is not our concern. To use a class, we need not know anything about how it is implemented. Instead, what we need to know is what operations the class provides.

Sales_item 类的目的是存储 ISBN 并保存该书的销售册数、销售收入和平均售价。我们不关心如何存储或计算这些数据。使用类时我们不需要知道这个类是怎样实现的,相反,我们需要知道的是该类提供什么操作。

As we've seen, when we use library facilities such as IO, we must include the associated headers. Similarly, for our own classes, we must make the definitions associated with the class available to the compiler. We do so in much the same way. Typically, we put the class definition into a file. Any program that wants to use our class must include that file.

正如我们所看到的,使用像 IO 一样的库工具,必须包含相关的头文件。类似地,对于自定义的类,必须使得编译器可以访问和类相关的定义。这几乎可以采用同样的方式。一般来说,我们将类定义放入一个文件中,要使用该类的任何程序都必须包含这个文件。

Conventionally, class types are stored in a file with a name that, like the name of a program source file, has two parts: a file name and a file suffix. Usually the file name is the same as the class defined in the header. The suffix usually is .h, but some programmers use .H, .hpp, or .hxx. Compilers usually aren't picky about header file names, but IDEs sometimes are. We'll assume that our class is defined in a file named Sales_item.h.

依据惯例,类类型存储在一个文件中,其文件名如同程序的源文件名一样,由文件名和文件后缀两部分组成。通常文件名和定义在头文件中的类名是一样的。通常后缀是 .h,但也有一些程序员用 .H.hpp.hxx。编译器通常并不挑剔头文件名,但 IDE 有时会。假设我们的类定义在名为 Sale_item.h 的文件中。

Operations on Sales_item Objects
Sales_item 对象上的操作

Every class defines a type. The type name is the same as the name of the class. Hence, our Sales_item class defines a type named Sales_item. As with the built-in types, we can define a variable of a class type. When we write

每个类定义一种类型,类型名与类名相同。因此,我们的 Sales_item 类定义了一种命名为 Sales_item 的类型。像使用内置类型一样,可以定义类类型的变量。当写下

    Sales_item item;

we are saying that item is an object of type Sales_item. We often contract the phrase "an object of type Sales_item" to"aSales_ item object" or even more simply to "a Sales_item."

就表示 item 是类型 Sales_item 的一个对象。通常将“类型 Sales_item 的一个对象”简称为“一个 Sales_item 对象”,或者更简单地简称为“一个 Sales_item”。

In addition to being able to define variables of type Sales_item, we can perform the following operations on Sales_item objects:

除了可以定义 Sales_item 类型的变量,我们还可以执行 Sales_item 对象的以下操作:

  • Use the addition operator, +, to add two Sales_items

    使用加法操作符,+,将两个 Sales_item 相加。

  • Use the input operator, << to read a Sales_item object,

    使用输入操作符,<<,来读取一个 Sales_item 对象。

  • Use the output operator, >> to write a Sales_item object

    使用输出操作符,>>,来输出一个 Sales_item 对象。

  • Use the assignment operator, =, to assign one Sales_item object to another

    使用赋值操作符,=,将一个 Sales_item 对象赋值给另一个 Sales_item 对象。

  • Call the same_isbn function to determine if two Sales_items refer to the same book

    调用 same_isbn 函数确定两个 Sales_item 是否指同一本书。

Reading and Writing Sales_items
读入和写出 Sales_item 对象

Now that we know the operations that the class provides, we can write some simple programs to use this class. For example, the following program reads data from the standard input, uses that data to build a Sales_item object, and writes that Sales_item object back onto the standard output:

知道了类提供的操作,就可以编写一些简单的程序使用这个类。例如,下面的程序从标准输入读取数据,使用该数据建立一个 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;
    }

If the input to this program is

如果输入到程序的是

    0-201-70353-X 4 24.99

then the output will be

则输出将是

    0-201-70353-X 4 99.96 24.99

Our input said that we sold four copies of the book at $24.99 each, and the output indicates that the total sold was four, the total revenue was $99.96, and the average price per book was $24.99.

输入表明销售了 4 本书,每本价格是 24.99 美元。输出表明卖出书的总数是 4 本,总收入是 99.96 美元,每本书的平均价格是 24.99 美元。

This program starts with two #include directives, one of which uses a new form. The iostream header is defined by the standard library; the Sales_item header is not. Sales_item is a type that we ourselves have defined. When we use our own headers, we use quotation marks (" ") to surround the header name.

这个程序以两个 #include 指示开始,其中之一使用了一种新格式。iostream 头文件由标准库定义,而 Sales_item 头文件则不是。Sales_item 是一种自定义类型。当使用自定义头文件时,我们采用双引号(" ")把头文件名括起来。

Headers for the standard library are enclosed in angle brackets (< >). Nonstandard headers are enclosed in double quotes (" ").

标准库的头文件用尖括号 < > 括起来,非标准库的头文件用双引号 " " 括起来。

Inside main we start by defining an object, named book, which we'll use to hold the data that we read from the standard input. The next statement reads into that object, and the third statement prints it to the standard output followed as usual by printing endl to flush the buffer.

main 函数中,首先定义一个对象,命名为 book,用它保存从标准输入读取的数据。下一条语句读入数据到此对象,第三条语句将它打印到标准输出,像平常一样紧接着打印 endl 来刷新缓冲区。

Key Concept: Classes Define Behavior

关键概念:类定义行为

As we go through these programs that use Sales_items, the important thing to keep in mind is that the author of the Sales_item class defined all the actions that can be performed by objects of this class. That is, the author of the Sales_item data structure defines what happens when a Sales_item object is created and what happens when the addition or the input and output operators are applied to Sales_item objects, and so on.

在编写使用 Sales_item 的程序时,重要的是记住类 Sales_item 的创建者定义该类对象可以执行的所有操作。也就是说, Sales_item 数据结构的创建者定义创建 Sales_item 对象时会发生什么,以及加操作符或输入输出操作符应用到 Sales_item 对象时又会发生什么,等等。

In general, only the operations defined by a class can be used on objects of the class type. For now, the only operations we know we can peeform on Sales_item objects are the ones listed on page 21.

通常,只有由类定义的操作可被用于该类类型的对象。此时,我们知道的可以在 Sales_item 对象上执行的操作只是前面列出的那些。

We'll see how these operations are defined in Sections 7.7.3 and 14.2.

我们将在第 7.7.3 节第 14.2 节看到如何定义这些操作。


Adding Sales_items
Sales_item 对象相加

A slightly more interesting example adds two Sales_item objects:

更有趣的例子是将两个 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;
     }

If we give this program the following input

如果我们给这个程序下面的输入:

     0-201-78345-X 3 20.00
     0-201-78345-X 2 25.00

our output is

则输出为

     0-201-78345-X 5 110 22

This program starts by including the Sales_item and iostream headers. Next we define two Sales_item objects to hold the two transactions that we wish to sum. The output expression does the addition and prints the result. We know from the list of operations on page 21 that adding two Sales_items together creates a new object whose ISBN is that of its operands and whose number sold and revenue reflect the sum of the corresponding values in its operands. We also know that the items we add must represent the same ISBN.

程序首先包含两个头文件 Sales_itemiostream。接下来定义两个 Sales_item 对象来存放要求和的两笔交易。输出表达式做加法运算并输出结果。从前面列出的操作,可以得知将两个 Sales_item 相加将创建一个新对象,新对象的 ISBN 是其操作数的 ISBN,销售的数量和收入反映其操作数中相应值的和。我们也知道相加的项必须具有同样的 ISBN。

It's worth noting how similar this program looks to the one on page 6: We read two inputs and write their sum. What makes it interesting is that instead of reading and printing the sum of two integers, we're reading and printing the sum of two Sales_item objects. Moreover, the whole idea of "sum" is different. In the case of ints we are generating a conventional sumthe result of adding two numeric values. In the case of Sales_item objects we use a conceptually new meaning for sumthe result of adding the components of two Sales_item objects.

值得注意的是这个程序是如何类似于第 1.2.2 节中的程序:读入两个输入并输出它们的和。令人感兴趣的是,本例并不是读入两个整数并输出两个整数的和,而是读入两个 Sales_item 对象并输出两个 Sales_item 对象的和。此外,“和”的意义也不同。在整数的实例中我们产生的是传统求和——两个数值相加后的结果。在 Sales_item 对象的实例上我们使用了在概念上有新意义的求和——两个 Sales_item 对象的成分相加后的结果。

Exercises Section 1.5.1

Exercise 1.21:

The Web site (http://www.awprofessional.com/cpp_primer) contains a copy of Sales_item.h in the Chapter 1 code directory. Copy that file to your working directory. Write a program that loops through a set of book sales transactions, reading each transaction and writing that transaction to the standard output.

本书配套网站第一章的代码目录下有 Sales_item.h 源文件。复制该文件到你的工作目录。编写程序,循环遍历一组书的销售交易,读入每笔交易并将交易写至标准输出。

Exercise 1.22:

Write a program that reads two Sales_item objects that have the same ISBN and produces their sum.

编写程序,读入两个具有相同 ISBN 的 Sales_item 对象并产生它们的和。

Exercise 1.23:

Write a program that reads several transactions for the same ISBN. Write the sum of all the transactions that were read.

编写程序,读入几个具有相同 ISBN 的交易,输出所有读入交易的和。


1.5.2. A First Look at Member Functions

1.5.2. 初窥成员函数

Unfortunately, there is a problem with the program that adds Sales_items. What should happen if the input referred to two different ISBNs? It doesn't make sense to add the data for two different ISBNs together. To solve this problem, we'll first check whether the Sales_item operands refer to the same ISBNs:

不幸的是,将 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
        }
    }

The difference between this program and the previous one is the if test and its associated else branch. Before explaining the if condition, we know that what this program does depends on the condition in the if. If the test succeeds, then we write the same output as the previous program and return 0 indicating success. If the test fails, we execute the block following the else, which prints a message and returns an error indicator.

这个程序和前一个程序不同之处在于 if 测试语句以及与它相关联的 else 分支。在解释 if 语句的条件之前,我们明白程序的行为取决于 if 语句中的条件。如果测试成功,那么产生与前一程序相同的输出,并返回 0 表示程序成功运行完毕。如果测试失败,执行 else 后面的语句块,输出信息并返回错误提示。

What Is a Member Function?
什么是成员函数

The if condition

上述 if 语句的条件

    // first check that item1 and item2 represent the same book
    if (item1.same_isbn(item2)) {

calls a member function of the Sales_item object named item1. A member function is a function that is defined by a class. Member functions are sometimes referred to as the methods of the class.

调用命名为 item1Sales_item 对象的成员函数。成员函数是由类定义的函数,有时称为类方法

Member functions are defined once for the class but are treated as members of each object. We refer to these operations as member functions because they (usually) operate on a specific object. In this sense, they are members of the object, even though a single definition is shared by all objects of the same type.

成员函数只定义一次,但被视为每个对象的成员。我们将这些操作称为成员函数,是因为它们(通常)在特定对象上操作。在这个意义上,它们是对象的成员,即使同一类型的所有对象共享同一个定义也是如此。

When we call a member function, we (usually) specify the object on which the function will operate. This syntax uses the dot operator (the "." operator):

当调用成员函数时,(通常)指定函数要操作的对象。语法是使用点操作符(.):

    item1.same_isbn

means "the same_isbn member of the object named item1." The dot operator fetches its right-hand operand from its left. The dot operator applies only to objects of class type: The left-hand operand must be an object of class type; the right-hand operand must name a member of that type.

意思是“命名为 item1 的对象的 same_isbn 成员”。点操作符通过它的左操作数取得右操作数。点操作符仅应用于类类型的对象:左操作数必须是类类型的对象,右操作数必须指定该类型的成员。

Unlike most other operators, the right operand of the dot (".") operator is not an object or value; it is the name of a member.

与大多数其他操作符不同,点操作符(“.”)的右操作数不是对象或值,而是成员的名字。



When we use a member function as the right-hand operand of the dot operator, we usually do so to call that function. We execute a member function in much the same way as we do any function: To call a function, we follow the function name by the call operator (the "()" operator). The call operator is a pair of parentheses that encloses a (possibly empty) list of arguments that we pass to the function.

通常使用成员函数作为点操作符的右操作数来调用成员函数。执行成员函数和执行其他函数相似:要调用函数,可将调用操作符(())放在函数名之后。调用操作符是一对圆括号,括住传递给函数的实参列表(可能为空)。

The same_isbn function takes a single argument, and that argument is another Sales_item object. The call

same_isbn 函数接受单个参数,且该参数是另一个 Sales_item 对象。函数调用

    item1.same_isbn(item2)

passes item2 as an argument to the function named same_isbn that is a member of the object named item1. This function compares the ISBN part of its argument, item2, to the ISBN in item1, the object on which same_isbn is called. Thus, the effect is to test whether the two objects refer to the same ISBN.

item2 作为参数传递给名为 same_isbn 的函数,该函数是名为 item1 的对象的成员。它将比较参数 item2 的 ISBN 与函数 same_isbn 要操作的对象 item1 的 ISBN。效果是测试两个对象是否具有相同的 ISBN。

If the objects refer to the same ISBN, we execute the statement following the if, which prints the result of adding the two Sales_item objects together. Otherwise, if they refer to different ISBNs, we execute the else branch, which is a block of statements. The block prints an appropriate error message and exits the program, returning -1. Recall that the return from main is treated as a status indicator. In this case, we return a nonzero value to indicate that the program failed to produce the expected result.

如果对象具有相同的 ISBN,执行 if 后面的语句,输出两个 Sales_item 对象的和;否则,如果对象具有不同的 ISBN,则执行 else 分支的语句块。该块输出适当的错误信息并退出程序,返回 -1。回想 main 函数的返回值被视为状态指示器;本例中,返回一个非零值表示程序未能产生期望的结果。

Exercises Section 1.5.2

Exercise 1.24:

Write a program that reads several transactions. For each new transaction that you read, determine if it is the same ISBN as the previous transaction, keeping a count of how many transactions there are for each ISBN. Test the program by giving multiple transactions. These transactions should represent multiple ISBNs but the records for each ISBN should be grouped together.

编写程序,读入几笔不同的交易。对于每笔新读入的交易,要确定它的 ISBN 是否和以前的交易的 ISBN 一样,并且记下每一个 ISBN 的交易的总数。通过给定多笔不同的交易来测试程序。这些交易必须代表多个不同的 ISBN,但是每个 ISBN 的记录应分在同一组。


Team LiB
Previous Section Next Section