c++

P369,const成员函数不能改变其所操作的对象的数据成员。
*******************************************************************************
list 容器的迭代器既不支持算术运算(加法或减法),也不支持关系运算(<=, <, >=, >),它只提供前置和后置的自增、自减运算以及相等(不等)运算。*******************************************************************************
除了在第 12.6 节介绍的 static 成员函数外
********************************************************************************************
p318问题解决:原因是要把要转换的文件放在与可执行文件同一个文件夹。
***************************************************************************************************
break 仅在 do、for、while 或 switch 语句中合法。
*************************************************************************************************************************
P57,用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。

*********************************************************************************
P97
字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
P117  创建动态数组
  虽然数组长度是固定的,但动态分配的数组不必编译时知道其长度,可以(通常也是)在运行时才确定数组长度。
  动态分配数组时,只需指定类型和数组长度,不必为数组对象命名。
○两句话的差别在于一个“其”字。   

Chapter 1. Getting Started
第一章 快速入门
CONTENTS
Section 1.1 Writing a Simple C++ Program2
Section 1.2 A First Look at Input/Output5
Section 1.3 A Word About Comments10
Section 1.4 Control Structures11
Section 1.5 Introducing Classes20
Section 1.6 The C++ Program25
Chapter Summary28
Defined Terms28


This chapter introduces most of the basic elements of C++: built-in, library, and class types; variables; expressions; statements; and functions. Along the way, we'll briefly explain how to compile and execute a program.
本章介绍 C++ 的大部分基本要素:内置类型、库类型、类类型、变量、表达式、语句和函数。在这一过程中还会简要说明如何编译和运行程序。
Having read this chapter and worked through the exercises, the reader should be able to write, compile, and execute simple programs. Subsequent chapters will explain in more detail the topics introduced here.
读者读完本章内容并做完练习,就应该可以编写、编译和执行简单的程序。后面的章节会进一步阐明本章所介绍的主题。
Learning a new programming language requires writing programs. In this chapter, we'll write a program to solve a simple problem that represents a common data-processing task: A bookstore keeps a file of transactions, each of which records the sale of a given book. Each transaction contains an ISBN (International Standard Book Number, a unique identifier assigned to most books published throughout the world), the number of copies sold, and the price at which each copy was sold. Each transaction looks like
要学会一门新的程序语言,必须实际动手编写程序。在这一章,我们将缩写程序解决一个简单的数据处理问题:某书店以文件形式保存其每一笔交易。每一笔交易记录某本书的销售情况,含有 ISBN(国际标准书号,世界上每种图书的唯一标识符)、销售册数和销售单价。每一笔交易形如:
   0-201-70353-X 4 24.99

where the first element is the ISBN, the second is the number of books sold, and the last is the sales price. Periodically the bookstore owner reads this file and computes the number of copies of each title sold, the total revenue from that book, and the average sales price. We want to supply a program do these computations.
第一个元素是 ISBN,第二个元素是销售的册数,最后是销售单价。店主定期地查看这个文件,统计每本书的销售册数、总销售收入以及平均售价。我们要编写程序来进行这些计算。
Before we can write this program we need to know some basic features of C++. At a minimum we'll need to know how to write, compile, and execute a simple program. What must this program do? Although we have not yet designed our solution, we know that the program must
在编写这个程序之前,必须知道 C++ 的一些基本特征。至少我们要知道怎么样编写、编译和执行简单的程序。这个程序要做什么呢?虽然还没有设计解决方案,但是我们知道程序必须:
Define variables
定义变量。
Do input and output
实现输入和输出。
Define a data structure to hold the data we're managing
定义数据结构来保存要处理的数据。
Test whether two records have the same ISBN
测试是否两条记录具有相同的 ISBN。
Write a loop that will process every record in the transaction file
编写循环。处理交易文件中的每一条记录。
We'll start by reviewing these parts of C++ and then write a solution to our bookstore problem.
我们将首先考察 C++ 的这些部分,然后编写书店问题的解决方案。

      

1.1. Writing a Simple C++ Program
1.1. 编写简单的 C++ 程序
Every C++ program contains one or more functions, one of which must be named main. A function consists of a sequence of statements that perform the work of the function. The operating system executes a program by calling the function named main. That function executes its constituent statements and returns a value to the operating system.
每个 C++ 程序都包含一个或多个函数,而且必须有一个命名为 main。函数由执行函数功能的语句序列组成。操作系统通过调用 main 函数来执行程序,main 函数则执行组成自己的语句并返回一个值给操作系统。
Here is a simple version of main does nothing but return a value:
下面是一个简单的 main 函数,它不执行任何功能,只是返回一个值:
    int main()
    {
        return 0;
    }

The operating system uses the value returned by main to determine whether the program succeeded or failed. A return value of 0 indicates success.
操作系统通过 main 函数返回的值来确定程序是否成功执行完毕。返回 0 值表明程序程序成功执行完毕。
The main function is special in various ways, the most important of which are that the function must exist in every C++ program and it is the (only) function that the operating system explicitly calls.
main 函数在很多方面都比较特别,其中最重要的是每个 C++ 程序必须含有 main 函数,且 main 函数是(唯一)被操作系统显式调用的函数。
We define main the same way we define other functions. A function definition specifies four elements: the return type, the function name, a (possibly empty) parameter list enclosed in parentheses, and the function body. The main function may have only a restricted set of parameters. As defined here, the parameter list is empty; Section 7.2.6 (p. 243) will cover the other parameters that can be defined for main.
定义 main 函数和定义其他函数一样。定义函数必须指定 4 个元素:返回类型、函数名、圆括号内的形参表(可能为空)和函数体。main 函数的形参个数是有限的。本例中定义的 main 函数形参表为空。第 7.2.6 节将介绍 main 函数中可以定义的其他形参。
The main function is required to have a return type of int, which is the type that represents integers. The int type is a built-in type, which means that the type is defined by the language.
main 函数的返回值必须是 int 型,该类型表示整数。int 类型是内置类型,即该类型是由 C++ 语言定义的。
The final part of a function definition, the function body, is a block of statements starting with an open curly brace and ending with a close curly:
函数体函数定义的最后部分,是以花括号开始并以花括号结束的语句块:
    {
        return 0;
    }

The only statement in our program is a return, which is a statement that terminates a function.
例中唯一的语句就是 return,该语句终止函数。
Note the semicolon at the end of the return statement. Semicolons mark the end of most statements in C++. They are easy to overlook, but when forgotten can lead to mysterious compiler error messages.
注意 return 语句后面的分号。在 C++ 中多数语句以分号作为结束标记。分号很容易被忽略,而漏写分号将会导致莫名其妙的编译错误信息。

When the return includes a value such as 0, that value is the return value of the function. The value returned must have the same type as the return type of the function or be a type that can be converted to that type. In the case of main the return type must be int, and the value 0 is an int.
当 return 带上一个值(如 0)时,这个值就是函数的返回值。返回值类型必须和函数的返回类型相同,或者可以转换成函数的返回类型。对于 main 函数,返回类型必须是 int 型,0 是 int 型的。
On most systems, the return value from main is a status indicator. A return value of 0 indicates the successful completion of main. Any other return value has a meaning that is defined by the operating system. Usually a nonzero return indicates that an error occurred. Each operating system has its own way of telling the user what main returned.
在大多数系统中,main 函数的返回值是一个状态指示器。返回值 0 往往表示 main 函数成功执行完毕。任何其他非零的返回值都有操作系统定义的含义。通常非零返回值表明有错误出现。每一种操作系统都有自己的方式告诉用户 main 函数返回什么内容。
1.1.1. Compiling and Executing Our Program
1.1.1. 编译与执行程序
Having written the program, we need to compile it. How you compile a program depends on your operating system and compiler. For details on how your particular compiler works, you'll need to check the reference manual or ask a knowledgeable colleague.
程序编写完后需要进行编译。如何进行编译,与具体操作系统和编译器有关。你需要查看有关参考手册或者询问有经验的同事,以了解所用的编译器的工作细节。
Many PC-based compilers are run from an integrated development environment (IDE) that bundles the compiler with associated build and analysis tools. These environments can be a great asset in developing complex programs but require a fair bit of time to learn how to use effectively. Most of these environments include a point-and-click interface that allows the programmer to write a program and use various menus to compile and execute the program. Learning how to use such environments is well beyond the scope of this book.
许多基于 PC 的编译器都在集成开发环境(IDE)中运行,IDE 将编译器与相关的构建和分析工具绑定在一起。这些环境在开发复杂程序时非常有用,但掌握起来需要花费一点时间。通常这些环境包含点击式界面,程序员在此界面下可以编写程序,并使用各种菜单来编译与执行程序本书不介绍怎样使用这些环境。
Most compilers, including those that come with an IDE, provide a command-line interface. Unless you are already familiar with using your compiler's IDE, it can be easier to start by using the simpler, command-line interface. Using the command-line interface lets you avoid the overhead of learning the IDE before learning the language.
大多数编译器,包括那些来自 IDE 的,都提供了命令行界面。除非你已经很熟悉你的 IDE,否则从使用简单的命令行界面开始可能更容易些。这样可以避免在学习语言之前得先去学习 IDE。
Program Source File Naming Convention
程序源文件命名规范
Whether we are using a command-line interface or an IDE, most compilers expect that the program we want to compile will be stored in a file. Program files are referred to as source files. On most systems, a source file has a name that consists of two parts: a file namefor example, prog1and a file suffix. By convention, the suffix indicates that the file is a program. The suffix often also indicates what language the program is written in and selects which compiler to run. The system that we used to compile the examples in this book treats a file with a suffix of .cc as a C++ program and so we stored this program as
不管我们使用命令行界面还是 IDE,大多数编译器希望待编译的程序保存在文件中。程序文件称作源文件。大多数系统中,源文件的名字由文件名(如 prog1)和文件后缀两部分组成。依据惯例,文件后缀表明该文件是程序。文件后缀通常也表明程序是用什么语言编写的,以及选择哪一种编译器运行。我们用来编译本书实例的系统将带有后缀 .cc 的文件视为 C++ 程序,因此我们将该程序保存为:
    prog1.cc

The suffix for C++ program files depends on which compiler you're running. Other conventions include
C++ 程序文件的后缀与运行的具体编译器有关。其他的形式还包括。
    prog1.cxx
    prog1.cpp
    prog1.cp
    prog1.C


Invoking the GNU or Microsoft Compilers
调用 GNU 或微软编译器
The command used to invoke the C++ compiler varies across compilers and operating systems. The most common compilers are the GNU compiler and the Microsoft Visual Studio compilers. By default the command to invoke the GNU compiler is g++:
调用 C++ 编译器的命令因编译器和操作系统的不同而不同,常用的编译器是 GNU 编译器和微软 Visual Studio 编译器。调用 GNU 编译器的默认命令是 g++:
    $ g++ prog1.cc -o prog1

where $ is the system prompt. This command generates an executable file named prog1 or prog1.exe, depending on the operating system. On UNIX, executable files have no suffix; on Windows, the suffix is .exe. The -o prog1 is an argument to the compiler and names the file in which to put the executable file. If the -o prog1 is omitted, then the compiler generates an executable named a.out on UNIX systems and a.exe on Windows.
这里的 $ 是系统提示符。这个命令产生一个为 prog1 或 prog1.exe 的可执行文件。在 UNIX 系统下,可执行文件没有后缀;而在 Windows 下,后缀为 .exe。-o prog1 是编译器参数以及用来存放可执行文件的文件名。如果省略 -o prog1,那么编译器在 UNIX 系统下产生名为 a.out 而在 Windows 下产生名为 a.exe 的可执行文件。
The Microsoft compilers are invoked using the command cl:
微软编译器采用命令 cl 来调用:
    C:\directory> cl -GX prog1.cpp

where C:directory> is the system prompt and directory is the name of the current directory. The command to invoke the compiler is cl, and -GX is an option that is required for programs compiled using the command-line interface. The Microsoft compiler automatically generates an executable with a name that corresponds to the source file name. The executable has the suffix .exe and the same name as the source file name. In this case, the executable is named prog1.exe.
这里的 C:directory> 是系统提示符,directory 是当前目录名。cl 是调用编译器的命令。-GX 是一个选项,该选项在使用命令行界面编译器程序时是必需的。微软编译器自动产生与源文件同名的可执行文件,这个可执行文件具有 .exe 后缀且与源文件同名。本例中,可执行文件命名为 prog1.exe。
For further information consult your compiler's user's guide.
更多的信息请参考你的编译器用户指南。


Running the Compiler from the Command Line
从命令行编译器
If we are using a command-line interface, we will typically compile a program in a console window (such as a shell window on a UNIX system or a Command Prompt window on Windows). Assuming that our main program is in a file named prog1.cc, we might compile it by using a command such as:
如果使用命令行界面,一般在控制台窗口(例如 UNIX 的 shell 窗口或 Windows 的命令提示窗口)编译程序。假设 main 程序在名为 prog1.cc 的文件中,可以使用如下命令来编译:
    $ CC prog1.cc

where CC names the compiler and $ represents the system prompt. The output of the compiler is an executable file that we invoke by naming it. On our system, the compiler generates the executable in a file named a.exe. UNIX compilers tend to put their executables in a file named a.out. To run an executable we supply that name at the command-line prompt:
这里 CC 是编译器命令名,$ 表示系统提示符。编译器输出一个可执行文件,我们可以按名调用这个可执行文件。在我们的系统中,编译器产生一个名为 a.exe 的可执行文件。UNIX 编译器则会将可执行文件放到一个名为 a.out 的文件中。要运行可执行文件,可在命令提示符处给出该文件名:
    $ a.exe

executes the program we compiled. On UNIX systems you sometimes must also specify which directory the file is in, even if it is in the current directory. In such cases, we would write
执行编译过的程序。在 UNIX 系统中,即使在当前目录,有时还必须指定文件所在的目录。这种情况下,键入:
    $ ./a.exe

The "." followed by a slash indicates that the file is in the current directory.
“.”后面的斜杠表明文件处于当前目录下。
The value returned from main is accessed in a system-dependent manner. On both UNIX and Windows systems, after executing the program, you must issue an appropriate echo command. On UNIX systems, we obtain the status by writing
访问 main 函数的返回值的方式和系统有关。不论 UNIX 还是 Windows 系统,执行程序后,必须发出一个适当的 echo 命令。UNIX 系统中,通过键入如下命令获取状态:
    $ echo $?

To see the status on a Windows system, we write
要在 Windows 系统下查看状态,键入
    C:\directory> echo %ERRORLEVEL%


Exercises Section 1.1.1
Exercise 1.1:Review the documentation for your compiler and determine what file naming convention it uses. Compile and run the main program from page 2.
查看所用的编译器文档,了解它所用的文件命名规范。编译并运行本节的 main 程序。
Exercise 1.2:Change the program to return -1. A return value of -1 is often treated as an indicator that the program failed. However, systems vary as to how (or even whether) they report a failure from main. Recompile and rerun your program to see how your system treats a failure indicator from main.
修改程序使其返回 -1。返回值 -1 通常作为程序运行失败的指示器。然而,系统不同,如何(甚至是否)报告 main 函数运行失败也不同。重新编译并再次运行程序,看看你的系统如何处理 main 函数的运行失败指示器。

 
      

1.2. A First Look at Input/Output
1.2. 初窥输入/输出
C++ does not directly define any statements to do input or output (IO). Instead, IO is provided by the standard library. The IO library provides an extensive set offacilities. However, for many purposes, including the examples in this book, one needs to know only a few basic concepts and operations.
C++ 并没有直接定义进行输入或输出(IO)的任何语句,这种功能是由标准库提供的。IO 库提供了大量的设施。然而,对许多应用,包括本书的例子而言,编程者只需要了解一些基本概念和操作。
Most of the examples in this book use the iostream library, which handles formatted input and output. Fundamental to the iostream library are two types named istream and ostream, which represent input and output streams, respectively. A stream is a sequence of characters intended to be read from or written to an IO device of some kind. The term "stream" is intended to suggest that the characters are generated, or consumed, sequentially over time.
本书的大多数例子都使用了处理格式化输入和输出的 iostream 库。iostream 库的基础是两种命名为 istream 和 ostream 的类型,分别表示输入流和输出流。流是指要从某种 IO 设备上读入或写出的字符序列。术语“流”试图说明字符是随着时间顺序生成或消耗的。
1.2.1. Standard Input and Output Objects
1.2.1. 标准输入与输出对象
The library defines four IO objects. To handle input, we use an object of type istream named cin (pronounced "see-in"). This object is also referred to as the standard input. For output, we use an ostream object named cout (pronounced "see-out"). It is often referred to as the standard output. The library also defines two other ostream objects, named cerr and clog (pronounced "see-err" and "see-log," respectively). The cerr object, referred to as the standard error, is typically used to generate warning and error messages to users of our programs. The clog object is used for general information about the execution of the program.
标准库定义了 4 个 IO 对象。处理输入时使用命名为 cin(读作 see-in)的 istream 类型对象。这个对象也称为标准输入。处理输出时使用命名为 cout(读作 see-out)的 ostream 类型对象,这个对象也称为标准输出。标准库还定义了另外两个 ostream 对象,分别命名为 cerr 和 clog(分别读作“see-err”和“see-log”)。cerr 对象又叫作标准错误,通常用来输出警告和错误信息给程序的使用者。而 clog 对象用于产生程序执行的一般信息。
Ordinarily, the system associates each of these objects with the window in which the program is executed. So, when we read from cin, data is read from the window in which the program is executing, and when we write to cout, cerr, or clog, the output is written to the same window. Most operating systems give us a way of redirecting the input or output streams when we run a program. Using redirection we can associate these streams with files of our choosing.
一般情况下,系统将这些对象与执行程序的窗口联系起来。这样,当我们从 cin 读入时,数据从执行程序的窗口读入,当写到 cin、cerr 或 clog 时,输出写至同一窗口。运行程序时,大部分操作系统都提供了重定向输入或输出流的方法。利用重定向可以将这些流与所选择的文件联系起来。
1.2.2. A Program that Uses the IO Library
1.2.2. 一个使用IO库的程序
So far, we have seen how to compile and execute a simple program, although that program did no work. In our overall problem, we'll have several records that refer to the same ISBN. We'll need to consolidate those records into a single total, implying that we'll need to know how to add the quantities of books sold.
到目前为止,我们已经明白如何编译与执行简单的程序,虽然那个程序什么也不做。在开篇的书店问题中,有一些记录含有相同的 ISBN,需要将这些记录进行汇总,也就是说需要弄清楚如何累加已售出书籍的数量。
To see how to solve part of that problem, let's start by looking at how we might add two numbers. Using the IO library, we can extend our main program to ask the user to give us two numbers and then print their sum:
为了弄清楚如何解决这个问题,我们先来看应如何把两数相加。我们可以使用 IO 库来扩充 main 程序,要求用户给出两个数,然后输出它们的和:
    #include <iostream>
    int main()
    {
        std::cout << "Enter two numbers:" << std::endl;
        int v1, v2;
        std::cin >> v1 >> v2;
        std::cout << "The sum of " << v1 << " and " << v2
                  << " is " << v1 + v2 << std::endl;
        return 0;
    }

This program starts by printing
程序首先在用户屏幕上显示提示语:
      Enter two numbers:

on the user's screen and then waits for input from the user. If the user enters
然后程序等待用户输入。如果用户输入
      3 7

followed by a newline, then the program produces the following output:
跟着一个换行符,则程序产生下面的输出:
      The sum of 3 and 7 is 10

The first line of our program is a preprocessor directive:
程序的第一行是一个预处理指示:
      #include <iostream>

which tells the compiler that we want to use the iostream library. The name inside angle brackets is a header. Every program that uses a library facility must include its associated header. The #include directive must be written on a single linethe name of the header and the #include must appear on the same line. In general, #include directives should appear outside any function. Typically, all the #include directives for a program appear at the beginning of the file.
告诉编译器要使用 iostream 库。尖括号里的名字是一个。头文件。程序使用库工具时必须包含相关的头文件。#include 指示必须单独写成一行——头文件名和 #include 必须在同一行。通常,#include 指示应出现在任何函数的外部。而且习惯上,程序的所有 #include 指示都在文件开头部分出现。
Writing to a Stream
写入到流
The first statement in the body of main executes an expression. In C++ an expression is composed of one or more operands and (usually) an operator. The expressions in this statement use the output operator (the << operator) to print the prompt on the standard output:
main 函数体中第一条语句执行了一个表达式。C++ 中,一个表达式由一个或几个操作数和通常是一个操作符组成。该语句的表达式使用输出操作符(<< 操作符),在标准输出上输出提示语:
      std::cout << "Enter two numbers:" << std::endl;

This statement uses the output operator twice. Each instance of the output operator takes two operands: The left-hand operand must be an ostream object; the right-hand operand is a value to print. The operator writes its right-hand operand to the ostream that is its left-hand operand.
这个语句用了两次输出操作符。每个输出操作符实例都接受两个操作数:左操作数必须是 ostream 对象;右操作数是要输出的值。操作符将其右操作数写到作为其左操作数的 ostream 对象。
In C++ every expression produces a result, which typically is the value generated by applying an operator to its operands. In the case of the output operator, the result is the value of its left-hand operand. That is, the value returned by an output operation is the output stream itself.
C++ 中,每个表达式都会产生一个结果,通常是将操作符作用到其操作数所产生的值。当操作符是输出操作符时,结果是左操作数的值。也就是说,输出操作返回的值是输出流本身。
The fact that the operator returns its left-hand operand allows us to chain together output requests. The statement that prints our prompt is equivalent to
既然输出操作符返回的是其左操作数,那么我们就可以将输出请求链接在一起。输出提示语的那条语句等价于
      (std::cout << "Enter two numbers:") << std::endl;

Because (std::cout << "Enter two numbers:") returns its left operand, std::cout, this statement is equivalent to
因为((std::cout << "Enter two numbers:"))返回其左操作数 std::cout,这条语句等价于
      std::cout << "Enter two numbers:";
      std::cout << std::endl;

endl is a special value, called a manipulator, that when written to an output stream has the effect of writing a newline to the output and flushing the buffer associated with that device. By flushing the buffer, we ensure that the user will see the output written to the stream immediately.
endl 是一个特殊值,称为操纵符,将它写入输出流时,具有输出换行的效果,并刷新与设备相关联的缓冲区。通过刷新缓冲区,用户可立即看到写入到流中的输出。
Programmers often insert print statements during debugging. Such statements should always flush the stream. Forgetting to do so may cause output to be left in the buffer if the program crashes, leading to incorrect inferences about where the program crashed.
程序员经常在调试过程中插入输出语句,这些语句都应该刷新输出流。忘记刷新输出流可能会造成输出停留在缓冲区中,如果程序崩溃,将会导致程序错误推断崩溃位置。

Using Names from the Standard Library
使用标准库中的名字
Careful readers will note that this program uses std::cout and std::endl rather than just cout and endl. The prefix std:: indicates that the names cout and endl are defined inside the namespace named std. Namespaces allow programmers to avoid inadvertent collisions with the same names defined by a library. Because the names that the standard library defines are defined in a namespace, we can use the same names for our own purposes.
细心的读者会注意到这个程序中使用的是 std::cout 和 std::endl,而不是 cout 和 endl。前缀 std:: 表明 cout 和 endl 是定义在命名空间 std 中的。使用命名空间程序员可以避免与库中定义的名字相同而引起无意冲突。因为标准库定义的名字是定义在命名空间中,所以我们可以按自己的意图使用相同的名字。
One side effect of the library's use of a namespace is that when we use a name from the library, we must say explicitly that we want to use the name from the std namespace. Writing std::cout uses the scope operator (the :: operator) to say that we want to use the name cout that is defined in the namespace std. We'll see in Section 3.1 (p. 78) a way that programs often use to avoid this verbose syntax.
标准库使用命名空间的副作用是,当我们使用标准库中的名字时,必须显式地表达出使用的是命名空间 std 下的名字。std::cout 的写法使用了作用域操作符(scope operator,:: 操作符),表示使用的是定义在命名空间 std 中的 cout。我们将在第 3.1 节学习到程序中经常使用的避免这种冗长句法的方法。
Reading From a Stream
读入流
Having written our prompt, we next want to read what the user writes. We start by defining two variables named v1 and v2 to hold the input:
在输出提示语后,将读入用户输入的数据。先定义两个名为 v1 和 v2 的变量来保存输入:
      int v1, v2;

We define these variables as type int, which is the built-in type representing integral values. These variables are uninitialized, meaning that we gave them no initial value. Our first use of these variables will be to read a value into them, so the fact that they have no initial value is okay.
将这些变量定义为 int 类型,int 类型是一种代表整数值的内置类型。这些变量未初始化,表示没有赋给它们初始值。这些变量在首次使用时会读入一个值,因此可以没有初始值。
The next statement
下一条语句读取输入:
      std::cin >> v1 >> v2;

reads the input. The input operator (the >> operator) behaves analogously to the output operator. It takes an istream as its left-hand operand and an object as its right-hand operand. It reads from its istream operand and stores the value it read in its right-hand operand. Like the output operator, the input operator returns its left-hand operand as its result. Because the operator returns its left-hand operand, we can combine a sequence of input requests into a single statement. In other words, this input operation is equivalent to
输入操作符(>> 操作符)行为与输出操作符相似。它接受一个 istream 对象作为其左操作数,接受一个对象作为其右操作数,它从 istream 操作数读取数据并保存到右操作数中。像输出操作符一样,输入操作符返回其左操作数作为结果。由于输入操作符返回其左操作数,我们可以将输入请求序列合并成单个语句。换句话说,这个输入操作等价于:
      std::cin >> v1;
      std::cin >> v2;

The effect of our input operation is to read two values from the standard input, storing the first in v1 and the second in v2.
输入操作的效果是从标准输入读取两个值,将第一个存放在 v1 中,第二个存放在 v2 中。
Completing the Program
完成程序
What remains is to print our result:
剩下的就是要输出结果:
     std::cout << "The sum of " << v1 << " and " << v2
               << " is " << v1 + v2 << std::endl;

This statement, although it is longer than the statement that printed the prompt, is conceptually no different. It prints each of its operands to the standard output. What is interesting is that the operands are not all the same kinds of values. Some operands are string literals, such as
这条语句虽然比输出提示语的语句长,但概念上没什么区别。它将每个操作数输出到标准输出。有趣的是操作数并不都是同一类型的值,有些操作数是字符串字面值。例如
   "The sum of "

and others are various int values, such as v1, v2, and the result of evaluating the arithmetic expression:
其他是各种 int 值,如 v1、v2 以及对算术表达式
   v1 + v2

The iostream library defines versions of the input and output operators that accept all of the built-in types.
求值的结果。iostream 库定义了接受全部内置类型的输入输出操作符版本。
When writing a C++ program, in most places that a space appears we could instead use a newline. One exception to this rule is that spaces inside a string literal cannot be replaced by a newline. Another exception is that spaces are not allowed inside preprocessor directives.
在写 C++ 程序时,大部分出现空格符的地方可用换行符代替。这条规则的一个例外是字符串字面值中的空格符不能用换行符代替。另一个例外是空格符不允许出现在预处理指示中。

Key Concept: Initialized and Uninitialized Variables
关键概念:已初始化变量和未初始化变量
Initialization is an important concept in C++ and one to which we will return throughout this book.
在 C++ 中,初始化是一个非常重要的概念,对它的讨论将贯穿本书始终。
Initialized variables are those that are given a value when they are defined. Uninitialized variables are not given an initial value:
已初始化变量是指变量在定义时就给定一个值。未初始化变量则未给定初始值:
   int val1 = 0;     // initialized
   int val2;         // uninitialized

It is almost always right to give a variable an initial value, but we are not required to do so. When we are certain that the first use of a variable gives it a new value, then there is no need to invent an initial value. For example, our first nontrivial program on page 6 defined uninitialized variables into which we immediately read values.
给变量一个初始值几乎总是正确的,但不要求必须这样做。当我们确定变量在第一次使用时会赋一个新值,那就不需要创建初始值。例如,在本节开始我们的第一个有意义的程序中,定义了未初始化变量,并立即读取值给它们。
When we define a variable, we should give it an initial value unless we are certain that the initial value will be overwritten before the variable is used for any other purpose. If we cannot guarantee that the variable will be reset before being read, we should initialize it.
定义变量时,应该给变量赋初始值,除非确定将变量用于其他意图之前会覆盖这个初值。如果不能保证读取变量之前重置变量,就应该初始化变量。

Exercises Section 1.2.2
Exercise 1.3:Write a program to print "Hello, World" on the standard output.
编一个程序,在标准输出上打印“Hello, World”。
Exercise 1.4:Our program used the built-in addition operator, +, to generate the sum of two numbers. Write a program that uses the multiplication operator, *, to generate the product of two numbers.
我们的程序利用内置的加法操作符“+”来产生两个数的和。编写程序,使用乘法操作符“*”产生两个数的积。
Exercise 1.5:We wrote the output in one large statement. Rewrite the program to use a separate statement to print each operand.
我们的程序使用了一条较长的输出语句。重写程序,使用单独的语句打印每一个操作数。
Exercise 1.6:Explain what the following program fragment does:
解释下面的程序段:
   std::cout << "The sum of " << v1;
             << " and " << v2;
             << " is " << v1 + v2
             << std::endl;

Is this code legal? If so, why? If not, why not?
这段代码合法吗?如果合法,为什么?如果不合法,又为什么?

 
      

1.3. A Word About Comments
1.3. 关于注释
Before our programs get much more complicated, we should see how C++ handles comments. Comments help the human readers of our programs. They are typically used to summarize an algorithm, identify the purpose of a variable, or clarify an otherwise obscure segment of code. Comments do not increase the size of the executable program. The compiler ignores all comments.
在程序变得更复杂之前,我们应该明白C++如何处理注释。注释可以帮助其他人阅读程序,通常用于概括算法、确认变量的用途或者阐明难以理解的代码段。注释并不会增加可执行程序的大小,编译器会忽略所有注释。
In this book, we italicize comments to make them stand out from the normal program text. In actual programs, whether comment text is distinguished from the text used for program code depends on the sophistication of the programming environment.
本书中,注释排成斜体以区别于一般程序文本。实际程序中,注释文本是否区别于程序代码文本取决于编程环境是否完善。

There are two kinds of comments in C++: single-line and paired. A single-line comment starts with a double slash (//). Everything to the right of the slashes on the current line is a comment and ignored by the compiler.
C++ 中有单行注释和成对注释两种类型的注释。单行注释以双斜线(//)开头,行中处于双斜线右边的内容是注释,被编译器忽略。
The other delimiter, the comment pair (/* */), is inherited from the C language. Such comments begin with a /* and end with the next */. The compiler treats everything that falls between the /* and */ as part of the comment:
另一种定界符,注释对(/* */),是从 C 语言继承过来的。这种注释以“/*”开头,以“*/”结尾。编译器把落入注释对“/**/”之间的内容作为注释:
   #include <iostream>
   /* Simple main function: Read two numbers and write their sum */
   int main()
   {
       // prompt user to enter two numbers
       std::cout << "Enter two numbers:" << std::endl;
       int v1, v2;           // uninitialized
       std::cin >> v1 >> v2; // read input
       return 0;
   }

A comment pair can be placed anywhere a tab, space, or newline is permitted. Comment pairs can span multiple lines of a program but are not required to do so. When a comment pair does span multiple lines, it is often a good idea to indicate visually that the inner lines are part of a multi-line comment. Our style is to begin each line in the comment with an asterisk, thus indicating that the entire range is part of a multi-line comment.
任何允许有制表符、空格或换行符的地方都允许放注释对。注释对可跨越程序的多行,但不是一定要如此。当注释跨越多行时,最好能直观地指明每一行都是注释。我们的风格是在注释的每一行以星号开始,指明整个范围是多行注释的一部分。
Programs typically contain a mixture of both comment forms. Comment pairs generally are used for multi-line explanations, whereas double slash comments tend to be used for half-line and single-line remarks.
程序通常混用两种注释形式。注释对一般用于多行解释,而双斜线注释则常用于半行或单行的标记。
Too many comments intermixed with the program code can obscure the code. It is usually best to place a comment block above the code it explains.
太多的注释混入程序代码可能会使代码难以理解,通常最好是将一个注释块放在所解释代码的上方。
Comments should be kept up to date as the code itself changes. Programmers expect comments to remain accurate and so believe them, even when other forms of system documentation are known to be out of date. An incorrect comment is worse than no comment at all because it may mislead a subsequent reader.
代码改变时,注释应与代码保持一致。程序员即使知道系统其他形式的文档已经过期,还是会信任注释,认为它会是正确的。错误的注释比没有注释更糟,因为它会误导后来者。
Comment Pairs Do Not Nest
注释对不可嵌套
A comment that begins with /* always ends with the next */. As a result, one comment pair cannot occur within another. The compiler error message(s) that result from this kind of program mistake can be mysterious and confusing. As an example, compile the following program on your system:
注释总是以 /* 开始并以 */ 结束。这意味着,一个注释对不能出现在另一个注释对中。由注释对嵌套导致的编译器错误信息容易使人迷惑。例如,在你的系统上编译下面的程序:
   #include <iostream>
   /*
    * comment pairs /* */ cannot nest.
    * "cannot nest" is considered source code,
    * as is the rest of the program
    */
   int main()
   {
       return 0;
   }

When commenting out a large section of a program, it can seem easiest to put a comment pair around a region that you want to omit temporarily. The trouble is that if that code already has a comment pair, then the newly inserted comment will terminate prematurely. A better way to temporarily ignore a section of code is to use your editor to insert single-line comment at the beginning of each line of code you want to ignore. That way, you need not worry about whether the code you are commenting out already contains a comment pair.
当注释掉程序的一大部分时,似乎最简单的办法就是在要临时忽略的区域前后放一个注释对。问题是如果那段代码已经有了注释对,那么新插入的注释对将提前终止。临时忽略一段代码更好的方法,是用编辑器在要忽略的每一行代码前面插入单行注释。这样,你就无需担心要注释的代码是否已包含注释对。

Exercises Section 1.3
Exercise 1.7: Compile a program that has incorrectly nested comments.
编译有不正确嵌套注释的程序。
Exercise 1.8: Indicate which, if any, of the following output statements, are legal.
指出下列输出语句哪些(如果有)是合法的。
   std::cout << "/*";
   std::cout << "*/";
   std::cout << /* "*/" */;

After you've predicted what will happen, test your answer by compiling a program with these three statements. Correct any errors you encounter.
预测结果,然后编译包含上述三条语句的程序,检查你的答案。纠正所遇到的错误。


      

1.4. Control Structures
1.4. 控制结构
Statements execute sequentially: The first statement in a function is executed first, followed by the second, and so on. Of course, few programsincluding the one we'll need to write to solve our bookstore problemcan be written using only sequential execution. Instead, programming languages provide various control structures that allow for more complicated execution paths. This section will take a brief look at some of the control structures provided by C++. Chapter 6 covers statements in detail.
语句总是顺序执行的:函数的第一条语句首先执行,接着是第二条,依次类推。当然,少数程序——包括我们将要编写的解决书店问题的程序——可以仅用顺序执行语句编写。事实上,程序设计语言提供了多种控制结构支持更为复杂的执行路径。本节将简要地介绍 C++ 提供的控制结构,第六章再详细介绍各种语句。
1.4.1. The while Statement
1.4.1. while 语句
A while statement provides for iterative execution. We could use a while to write a program to sum the numbers from 1 through 10 inclusive as follows:
while 语句提供了迭代执行功能。可以用 while 语句编写一个如下所示的从 1 到 10(包括 10)的求和程序:
    #include <iostream>
    int main()
    {
        int sum = 0, val = 1;
        // keep executing the while until val is greater than 10
        while (val <= 10) {
            sum += val;  // assigns sum + val to sum
            ++val;       // add 1 to val
        }
        std::cout << "Sum of 1 to 10 inclusive is "
                  << sum << std::endl;
        return 0;
    }

This program when compiled and executed will print:
编译并执行后,将输出:
   Sum of 1 to 10 inclusive is 55

As before, we begin by including the iostream header and define a main function. Inside main we define two int variables: sum, which will hold our summation, and val, which will represent each of the values from 1 through 10. We give sum an initial value of zero and start val off with the value one.
与前面一样,程序首先包含 iostream 头文件并定义 main 函数。在 main 函数中定义两个 int 型变量:sum 保存总和,val 表示从 1 到 10 之间的每一个值。我们给 sum 赋初值 0,而 val 则从 1 开始。
The important part is the while statement. A while has the form
重要的部分是 while 语句。while 结构有这样的形式:
   while (condition) while_body_statement;

A while executes by (repeatedly) testing the condition and executing the associated while_body_statement until the condition is false.
while 通过测试 condition (条件)和执行相关 while_body_statement 来重复执行,直到 condition 为假。
A condition is an expression that is evaluated so that its result can be tested. If the resulting value is nonzero, then the condition is true; if the value is zero then the condition is false.
条件是一个可求值的表达式,所以可以测试其结果。如果结果值非零,那么条件为真;如果值为零,则条件为假。
If the condition is true (the expression evaluates to a value other than zero) then while_body_statement is executed. After executing while_body_statement, the condition is tested again. If condition remains true, then the while_body_statement is again executed. The while continues, alternatively testing the condition and executing while_body_statement until the condition is false.
如果 condition 为真(表达式求值不为零),则执行 while_body_statement。执行完后,再次测试 condition 。如果 condition 仍为真,则再次执行 while_body_statement。while 语句一直交替测试 condition 和执行 while_body_statement,直到 condition 为假为止。
In this program, the while statement is:
在这个程序中,while 语句是
    // keep executing the while until val is greater than 10
    while (val <= 10) {
        sum += val; // assigns sum + val to sum
        ++val; // add 1 to val
    }

The condition in the while uses the less-than-or-equal operator (the <= operator) to compare the current value of val and 10. As long as val is less than or equal to 10, we execute the body of the while. In this case, the body of the while is a block containing two statements:
while 语句的条件用了小于或等于操作符(<= 操作符),将 val 的当前值和 10 比较,只要 val 小于或等于 10,就执行 while 循环体。这种情况下,while 循环体是一个包含两个语句的块:
    {
        sum += val; // assigns sum + val to sum
        ++val; // add 1 to val
    }

A block is a sequence of statements enclosed by curly braces. In C++, a block may be used wherever a statement is expected. The first statement in the block uses the compound assignment operator, (the += operator). This operator adds its right-hand operand to its left-hand operand. It has the same effect as writing an addition and an assignment:
块是被花括号括起来的语句序列。C++ 中,块可用于任何可以用一条语句的地方。块中第一条语句使用了复合赋值操作符(+= 操作符),这个操作符把它的右操作数加至左操作数,这等效于编写含一个加法和一个赋值的语句:
    sum = sum + val; // assign sum + val to sum

Thus, the first statement adds the value of val to the current value of sum and stores the result back into sum.
因此第一条语句是把 val 的值加到 sum 的当前值,并把结果存入 sum。
The next statement
第二条语句
    ++val; // add 1 to val

uses the prefix increment operator (the ++ operator). The increment operator adds one to its operand. Writing ++val is the same as writing val = val + 1.
使用了前自增操作符(++ 操作符),自增操作符就是在它的操作数上加 1,++val 和 val = val + 1 是一样的。
After executing the while body we again execute the condition in the while. If the (now incremented) value of val is still less than or equal to 10, then the body of the while is executed again. The loop continues, testing the condition and executing the body, until val is no longer less than or equal to 10.
执行 while 的循环体后,再次执行 while 的条件。如果 val 的值(自增后)仍小于或等于 10,那么再次执行 while 的循环体。循环继续,测试条件并执行循环体,直到 val 的值不再小于或等于 10 为止。
Once val is greater than 10, we fall out of the while loop and execute the statement following the while. In this case, that statement prints our output, followed by the return, which completes our main program.
一旦 val 的值大于 10,程序就跳出 while 循环并执行 while 后面的语句,此例中该语句打印输出,其后的 return 语句结束 main 程序。
Key Concept: Indentation and Formatting of C++ Programs
关键概念:C++ 程序的缩排和格式
C++ programs are largely free-format, meaning that the positioning of curly braces, indentation, comments, and newlines usually has no effect on the meaning of our programs. For example, the curly brace that denotes the beginning of the body of main could be on the same line as main, positioned as we have done, at the beginning of the next line, or placed anywhere we'd like. The only requirement is that it be the first nonblank, noncomment character that the compiler sees after the close parenthesis that concludes main's parameter list.
C++ 程序的格式非常自由,花括号、缩排、注释和换行的位置通常对程序的语义没有影响。例如,表示 main 函数体开始的花括号可以放在与 main 同一行,也可以像我们那样,放在下一行的开始,或放在你喜欢的任何地方。唯一的要求是,它是编译器所看到在 main 的参数列表的右括号之后的第一个非空格、非注释字符。
Although we are largely free to format programs as we wish, the choices we make affect the readability of our programs. We could, for example, have written main on a single, long line. Such a definition, although legal, would be hard to read.
虽然说我们可以很自由地编排程序的格式,但如果编排不当,会影响程序的可读性。例如,我们可以将 main 写成单独的一长行。这样的定义尽管合法,但很难阅读。
Endless debates occur as to the right way to format C or C++ programs. Our belief is that there is no single correct style but that there is value in consistency. We tend to put the curly braces that delimit functions on their own lines. We tend to indent compound input or output expressions so that the operators line up, as we did with the statement that wrote the output in the main function on page 6. Other indentation conventions will become clear as our programs become more complex.
关于什么是 C 或 C++ 程序的正确格式存在无休止的争论,我们相信没有唯一正确的风格,但一致性是有价值的。我们倾向于把确定函数边界的花括号自成一行,且缩进复合的输入或输出表达式从而使操作符排列整齐,正如第 1.2.2 节的 main 函数中的输出语句那样。随着程序的复杂化,其他缩排规范也会变得清晰。
The important thing to keep in mind is that other ways to format programs are possible. When choosing a formatting style, think about how it affects readability and comprehension. Once you've chosen a style, use it consistently.
可能存在其他格式化程序的方式,记住这一点很重要。在选择格式化风格时,要考虑提高程序的可读性,使其更易于理解。一旦选择了某种风格,就要始终如一地使用。



1.4.2. The for Statement
1.4.2. for 语句
In our while loop, we used the variable val to control how many times we iterated through the loop. On each pass through the while, the value of val was tested and then in the body the value of val was incremented.
在 while 循环中,我们使用变量 val 来控制循环执行次数。每次执行 while 语句,都要测试 val 的值,然后在循环体中增加 val 的值。
The use of a variable like val to control a loop happens so often that the language defines a second control structure, called a for statement, that abbreviates the code that manages the loop variable. We could rewrite the program to sum the numbers from 1 through 10 using a for loop as follows:
由于需要频频使用像 val 这样的变量控制循环,因而 C++ 语言定义了第二种控制结构,称为 for 语句,简化管理循环变量的代码。使用 for 循环重新编写求 1 到 10 的和的程序,如下:
    #include <iostream>
    int main()
    {
        int sum = 0;
        // sum values from 1 up to 10 inclusive
        for (int val = 1; val <= 10; ++val)
            sum += val; // equivalent to sum = sum + val

        std::cout << "Sum of 1 to 10 inclusive is "
                  << sum << std::endl;
        return 0;
    }

Prior to the for loop, we define sum, which we set to zero. The variable val is used only inside the iteration and is defined as part of the for statement itself. The for statement
在 for 循环之前,我们定义 sum 并赋 0 值。用于迭代的变量 val 被定义为 for 语句自身的一部分。for 语句
    for (int val = 1; val <= 10; ++val)
        sum += val; // equivalent to sum = sum + val

has two parts: the for header and the for body. The header controls how often the body is executed. The header itself consists of three parts: an init-statement, a condition, and an expression. In this case, the init-statement
包含 for 语句头和 for 语句体两部分。for 语句头控制 for 语句体的执行次数。for 语句头由三部分组成:一个初始化语句,一个条件,一个表达式。在这个例子中,初始化语句
    int val = 1;

defines an int object named val and gives it an initial value of one. The initstatement is performed only once, on entry to the for. The condition
定义一个名为 val 的 int 对象并给定初始值 1。初始化语句仅在进入 for 语句时执行一次。条件
    val <= 10

which compares the current value in val to 10, is tested each time through the loop. As long as val is less than or equal to 10, we execute the for body. Only after executing the body is the expression executed. In this for, the expression uses the prefix increment operator, which as we know adds one to the value of val. After executing the expression, the for retests the condition. If the new value of val is still less than or equal to 10, then the for loop body is executed and val is incremented again. Execution continues until the condition fails.
将 val 的当前值和 10 比较,每次经过循环都要测试。只要 val 小于或等于 10,就执行 for 语句体。仅当 for 语句体执行后才执行表达式。在这个 for 循环中,表达式使用前自增操作符,val 的值加 1,执行完表达式后,for 语句重新测试条件,如果 val 的新值仍小于或等于 10,则执行 for 语句体,val 再次自增,继续执行直到条件不成立。
In this loop, the for body performs the summation
在这个循环中,for 语句体执行求和
  sum += val; // equivalent to sum = sum + val

The body uses the compound assignment operator to add the current value of val to sum, storing the result back into sum.
for 语句体使用复合赋值操作符,把 val 的当前值加到 sum,并将结果保存到 sum 中。
To recap, the overall execution flow of this for is:
扼要重述一下,for 循环总的执行流程为:
Create val and initialize it to 1.
创建 val 并初始化为 1。
Test whether val is less than or equal to 10.
测试 val 是否小于或等于 10。
If val is less than or equal to 10, execute the for body, which adds val to sum. If val is not less than or equal to 10, then break out of the loop and continue execution with the first statement following the for body.
如果 val 小于或等于 10,则执行 for 循环体,把 val 加到 sum 中。如果 val 大于 10,就退出循环,接着执行 for 语句体后的第一条语句。
Increment val.
val 递增。
Repeat the test in step 2, continuing with the remaining steps as long as the condition is true.
重复第 2 步的测试,只要条件为真,就继续执行其余步骤。
When we exit the for loop, the variable val is no longer accessible. It is not possible to use val after this loop terminates. However, not all compilers enforce this requirement.
退出 for 循环后,变量 val 不再可访问,循环终止后使用 val 是不可能的。然而,不是所有的编译器都有这一要求。

In pre-Standard C++ names defined in a for header were accessible outside the for itself. This change in the language definition can surprise people accustomed to using an older compiler when they instead use a compiler that adheres to the standard.
在标准化之前的 C++ 中,定义在 for 语句头的名字在 for 循环外是可访问的。语言定义中的这一改变,可能会使习惯于使用老式编译器的人,在使用遵循标准的新编译器时感到惊讶。
Compilation Revisited
再谈编译
Part of the compiler's job is to look for errors in the program text. A compiler cannot detect whether the meaning of a program is correct, but it can detect errors in the form of the program. The following are the most common kinds of errors a compiler will detect.
编译器的部分工作是寻找程序代码中的错误。编译器不能查出程序的意义是否正确, 但它可以查出程序形式上的错误。下面是编译器能查出的最普遍的一些错误。
Syntax errors. The programmer has made a grammatical error in the C++ language. The following program illustrates common syntax errors; each comment describes the error on the following line:
语法错误。程序员犯了 C++ 语言中的语法错误。下面代码段说明常见的语法错误;每个注释描述下一行的错误。
                     // error: missing ')' in parameter list for main
       int main ( {
                     // error: used colon, not a semicolon after endl
           std::cout << "Read each file." << std::endl:
                     // error: missing quotes around string literal
           std::cout << Update master. << std::endl;
                     // ok: no errors on this line
           std::cout << "Write new master." <<std::endl;
                     // error: missing ';' on return statement
           return 0
       }



Type errors. Each item of data in C++ has an associated type. The value 10, for example, is an integer. The word "hello" surrounded by double quotation marks is a string literal. One example of a type error is passing a string literal to a function that expects an integer argument.
类型错误。C++ 中每个数据项都有其相关联的类型。例如,值 10 是一个整数。用双引号标注起来的单词“hello”是字符串字面值。类型错误的一个实例是传递了字符串字面值给应该得到整型参数的函数。
Declaration errors. Every name used in a C++ program must be declared before it is used. Failure to declare a name usually results in an error message. The two most common declaration errors are to forget to use std:: when accessing a name from the library or to inadvertently misspell the name of an identifier:
声明错误。C++ 程序中使用的每个名字必须在使用之前声明。没有声明名字通常会导致错误信息。最常见的两种声明错误,是从标准库中访问名字时忘记使用“std::”,以及由于疏忽而拼错标识符名:
    #include <iostream>
    int main()
    {
        int v1, v2;
        std::cin >> v >> v2; // error: uses " v "not" v1"
        // cout not defined, should be std::cout
        cout << v1 + v2 << std::endl;
        return 0;
     }

An error message contains a line number and a brief description of what the compiler believes we have done wrong. It is a good practice to correct errors in the sequence they are reported. Often a single error can have a cascading effect and cause a compiler to report more errors than actually are present. It is also a good idea to recompile the code after each fixor after making at most a small number of obvious fixes. This cycle is known as edit-compile-debug.
错误信息包含行号和编译器对我们所犯错误的简要描述。按错误报告的顺序改正错误是个好习惯,通常一个错误可能会产生一连串的影响,并导致编译器报告比实际多得多的错误。最好在每次修改后或最多改正了一些显而易见的错误后,就重新编译代码。这个循环就是众所周知的编辑—编译—调试。

Exercises Section 1.4.2
Exercise 1.9:What does the following for loop do? What is the final value of sum?
下列循环做什么?sum 的最终值是多少?
    int sum = 0;
    for (int i = -100; i <= 100; ++i)
        sum += i;


Exercise 1.10: Write a program that uses a for loop to sum the numbers from 50 to 100. Now rewrite the program using a while.
用 for 循环编程,求从 50 到 100 的所有自然数的和。然后用 while 循环重写该程序。
Exercise 1.11:Write a program using a while loop to print the numbers from 10 down to 0. Now rewrite the program using a for.
用 while 循环编程,输出 10 到 0 递减的自然数。然后用 for 循环重写该程序。
Exercise 1.12:Compare and contrast the loops you wrote in the previous two exercises. Are there advantages or disadvantages to using either form?
对比前面两个习题中所写的循环。两种形式各有何优缺点?
Exercise 1.13:Compilers vary as to how easy it is to understand their diagnostics. Write programs that contain the common errors discussed in the box on 16. Study the messages the compiler generates so that these messages will be familiar when you encounter them while compiling more complex programs.
编译器不同,理解其诊断内容的难易程度也不同。编写一些程序,包含本小节“再谈编译”部分讨论的那些常见错误。研究编译器产生的信息,这样你在编译更复杂的程序遇到这些信息时就不会陌生。




1.4.3. The if Statement
1.4.3. if 语句
A logical extension of summing the values between 1 and 10 is to sum the values between two numbers our user supplies. We might use the numbers directly in our for loop, using the first input as the lower bound for the range and the second as the upper bound. However, if the user gives us the higher number first, that strategy would fail: Our program would exit the for loop immediately. Instead, we should adjust the range so that the larger number is the upper bound and the smaller is the lower. To do so, we need a way to see which number is larger.
求 1 到 10 之间数的和,其逻辑延伸是求用户提供的两个数之间的数的和。可以直接在 for 循环中使用这两个数,使用第一个输入值作为下界而第二个输入值作为上界。然而, 如果用户首先给定的数较大,这种策略将会失败:程序会立即退出 for 循环。因此,我们应该调整范围以便较大的数作上界而较小的数作下界。这样做,我们需要一种方式来判定哪个数更大一些。
Like most languages, C++ provides an if statement that supports conditional execution. We can use an if to write our revised sum program:
像大多数语言一样,C++ 提供支持条件执行的 if 语句。使用 if 语句来编写修订的求和程序如下:
    #include <iostream>
    int main()
    {
        std::cout << "Enter two numbers:" << std::endl;
        int v1, v2;
        std::cin >> v1 >> v2; // read input
        // use smaller number as lower bound for summation
        // and larger number as upper bound
        int lower, upper;
        if (v1 <= v2) {
            lower = v1;
            upper = v2;
        } else {
            lower = v2;
            upper = v1;
        }
        int sum = 0;
        // sum values from lower up to and including upper
        for (int val = lower; val <= upper; ++val)
            sum += val; // sum = sum + val

        std::cout << "Sum of " << lower
                  << " to " << upper
                  << " inclusive is "
                  << sum << std::endl;
        return 0;
    }

If we compile and execute this program and give it as input the numbers 7 and 3, then the output of our program will be
如果我们编译并执行这个程序给定输入数为 7 和 3,程序的输出结果将为:
  Sum of 3 to 7 inclusive is 25

Most of the code in this program should already be familiar from our earlier examples. The program starts by writing a prompt to the user and defines four int variables. It then reads from the standard input into v1 and v2. The only new code is the if statement
这个程序中大部分代码我们在之前的举例中已经熟知了。程序首先向用户输出提示并定义 4 个 int 变量,然后从标准输入读入值到 v1 和 v2 中。仅有 if 条件语句是新增加的代码:
    // use smaller number as lower bound for summation
    // and larger number as upper bound
    int lower, upper;
    if (v1 <= v2) {
        lower = v1;
        upper = v2;
    } else {
        lower = v2;
        upper = v1;
    }

The effect of this code is to set upper and lower appropriately. The if condition tests whether v1 is less than or equal to v2. If so, we perform the block that immediately follows the condition. This block contains two statements, each of which does an assignment. The first statement assigns v1 to lower and the second assigns v2 to upper.
这段代码的效果是恰当地设置 upper 和 lower 。if 的条件测试 v1 是否小于或等于 v2。如果是,则执行条件后面紧接着的语句块。这个语句块包含两条语句,每条语句都完成一次赋值,第一条语句将 v1 赋值给 lower ,而第二条语句将 v2 赋值给 upper。
If the condition is falsethat is, if v1 is larger than v2then we execute the statement following the else. Again, this statement is a block consisting of two assignments. We assign v2 to lower and v1 to upper.
如果这个条件为假(也就是说,如果 v1 大于 v2)那么执行 else 后面的语句。这个语句同样是一个由两个赋值语句组成的块,把 v2 赋值给 lower 而把 v1 赋值给 upper 。
Exercises Section 1.4.3
Exercise 1.14:What happens in the program presented in this section if the input values are equal?
如果输入值相等,本节展示的程序将产生什么问题?
Exercise 1.15:Compile and run the program from this section with two equal values as input. Compare the output to what you predicted in the previous exercise. Explain any discrepancy between what happened and what you predicted.
用两个相等的值作为输入编译并运行本节中的程序。将实际输出与你在上一习题中所做的预测相比较,解释实际结果和你预计的结果间的不相符之处。
Exercise 1.16:Write a program to print the larger of two inputs supplied by the user.
编写程序,输出用户输入的两个数中的较大者。
Exercise 1.17:Write a program to ask the user to enter a series of numbers. Print a message saying how many of the numbers are negative numbers.
编写程序,要求用户输入一组数。输出信息说明其中有多少个负数。


1.4.4. Reading an Unknown Number of Inputs
1.4.4. 读入未知数目的输入
Another change we might make to our summation program on page 12 would be to allow the user to specify a set of numbers to sum. In this case we can't know how many numbers we'll be asked to add. Instead, we want to keep reading numbers until the program reaches the end of the input. When the input is finished, the program writes the total to the standard output:
对第 1.4.1 节的求和程序稍作改变,还可以允许用户指定一组数求和。这种情况下,我们不知道要对多少个数求和,而是要一直读数直到程序输入结束。输入结束时,程序将总和写到标准输出:
    #include <iostream>
    int main()
    {
        int sum = 0, value;
        // read till end-of-file, calculating a running total of all values read
        while (std::cin >> value)
            sum += value; // equivalent to sum = sum + value
        std::cout << "Sum is: " << sum << std::endl;
        return 0;
     }

If we give this program the input
如果我们给出本程序的输入:
  3 4 5 6

then our output will be
那么输出是:
  Sum is: 18

As usual, we begin by including the necessary headers. The first line inside main defines two int variables, named sum and value. We'lluse value to hold each number we read, which we do inside the condition in the while:
与平常一样,程序首先包含必要的头文件。main 中第一行定义了两个 int 变量,命名为 sum 和 value。在 while 条件中,用 value 保存读入的每一个数:
  while (std::cin >> value)

What happens here is that to evaluate the condition, the input operation
这里所产生的是,为判断条件,先执行输入操作
  std::cin >> value

is executed, which has the effect of reading the next number from the standard input, storing what was read in value. The input operator (Section 1.2.2, p. 8) returns its left operand. The condition tests that result, meaning it tests std::cin.
它具有从标准输入读取下一个数并且将读入的值保存在 value 中的效果。输入操作符(第 1.2.2 节)返回其左操作数。while 条件测试输入操作符的返回结果,意味着测试 std::cin。
When we use an istream as a condition, the effect is to test the state of the stream. If the stream is validthat is, if it is still possible to read another input then the test succeeds. An istream becomes invalid when we hit end-of-file or encounter an invalid input, such as reading a value that is not an integer. An istream that is in an invalid state will cause the condition to fail.
当我们使用 istream 对象作为条件,结果是测试流的状态。如果流是有效的(也就是说,如果读入下一个输入是可能的)那么测试成功。遇到文件结束符或遇到无效输入时,如读取了一个不是整数的值,则 istream 对象是无效的。处于无效状态的 istream 对象将导致条件失败。
Until we do encounter end-of-file (or some other input error), the test will succeed and we'll execute the body of the while. That body is a single statement that uses the compound assignment operator. This operator adds its right-hand operand into the left hand operand.
在遇到文件结束符(或一些其他输入错误)之前,测试会成功并且执行 while 循环体。循环体是一条使用复合赋值操作符的语句,这个操作符将它的右操作数加到左操作数上。
Entering an End-of-file from the Keyboard
从键盘输入文件结束符
Operating systems use different values for end-of-file. On Windows systems we enter an end-of-file by typing a control-zsimultaneously type the "ctrl" key and a "z." On UNIX systems, including Mac OS-X machines, it is usually control-d.
操作系统使用不同的值作为文件结束符。Windows 系统下我们通过键入 control—z——同时键入“ctrl”键和“z”键,来输入文件结束符。Unix 系统中,包括 Mac OS—X 机器,通常用 control—d。

Once the test fails, the while terminates and we fall through and execute the statement following the while. That statement prints sum followed by endl, which prints a newline and flushes the buffer associated with cout. Finally, we execute the return, which as usual returns zero to indicate success.
一旦测试失败,while 终止并退出循环体,执行 while 之后的语句。该语句在输出 sum 后输出 endl,endl 输出换行并刷新与 cout 相关联的缓冲区。最后,执行 return,通常返回零表示程序成功运行完毕。
Exercises Section 1.4.4
Exercise 1.18:Write a program that prompts the user for two numbers and writes each number in the range specified by the two numbers to the standard output.
编写程序,提示用户输入两个数并将这两个数范围内的每个数写到标准输出。
Exercise 1.19:What happens if you give the numbers 1000 and 2000 to the program written for the previous exercise? Revise the program so that it never prints more than 10 numbers per line.
如果上题给定数 1000 和 2000,程序将产生什么结果?修改程序,使每一行输出不超过 10 个数。
Exercise 1.20: Write a program to sum the numbers in a user-specified range, omitting the if test that sets the upper and lower bounds. Predict what happens if the input is the numbers 7 and 3, in that order. Now run the program giving it the numbers 7 and 3, and see if the results match your expectation. If not, restudy the discussion on the for and while loop until you understand what happened.
编写程序,求用户指定范围内的数的和,省略设置上界和下界的 if 测试。假定输入数是 7 和 3,按照这个顺序,预测程序运行结果。然后按照给定的数是 7 和 3 运行程序,看结果是否与你预测的相符。如果不相符,反复研究关于 for 和 while 循环的讨论直到弄清楚其中的原因。




      

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++ 设计的主要焦点就是使所定义的类类型的行为可以像内置类型一样自然。我们前面已看到的像 istream 和 ostream 这样的库类型,都是定义为类的,也就是说,它们严格说来不是语言的一部分。
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:
使用类时我们需要回答三个问题:
What is its name?
类的名字是什么?
Where is it defined?
它在哪里定义?
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_item 和 iostream。接下来定义两个 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.
调用命名为 item1 的 Sales_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 的记录应分在同一组。


      

1.6. The C++ Program
1.6. C++ 程序
Now we are ready to solve our original bookstore problem: We need to read a file of sales transactions and produce a report that shows for each book the total revenue, average sales price, and the number of copies sold.
现在我们已经做好准备,可以着手解决最初的书店问题了:我们需要读入销售交易文件,并产生报告显示每本书的总销售收入、平均销售价格和销售册数。
We'll assume that all of the transactions for a given ISBN appear together. Our program will combine the data for each ISBN in a Sales_item object named total. Each transaction we read from the standard input will be stored in a second Sales_item object named trans. Each time we read a new transaction we'll compare it to the Sales_item object in total. If the objects refer to the same ISBN, we'll update total. Otherwise we'll print the value in total and reset it using the transaction we just read.
假定给定ISBN的所有交易出现在一起。程序将把每个 ISBN 的数据组合至命名为 total 的 Sales_item 对象中。从标准输入中读取的每一笔交易将被存储到命名为 trans 的第二个 Sales_item 对象中。每读取一笔新的交易,就将它与 total 中的 Sales_item 对象相比较,如果对象含有相同的 ISBN,就更新 total ;否则就输出 total 的值,并使用刚读入的交易重置 total。
    #include <iostream>
    #include "Sales_item.h"
    int main()
    {
        //  declare variables to hold running sum and data for the next record
        Sales_item total, trans;
        //  is there data to process?
        if (std::cin >> total) {
            // if so, read the transaction records
            while (std::cin >> trans)
                if  (total.same_isbn(trans))
                   //  match: update the running total
                   total = total + trans;
                else {
                   //  no match: print & assign to total
                   std::cout << total << std::endl;
                   total = trans;
                }
            //  remember to print last record
            std::cout << total << std::endl;
         } else {
            //  no input!, warn the user
            std::cout << "No data?!" << std::endl;
            return -1;  //  indicate failure
         }
         return 0;
    }

This program is the most complicated one we've seen so far, but it uses only facilities that we have already encountered. As usual, we begin by including the headers that we use: iostream from the library and Sales_item.h, which is our own header.
这个程序是到目前我们见到的程序中最为复杂的一个,但它仅使用了我们已遇到过的工具。和平常一样,我们从包含所使用的头文件开始:标准库中的 iostream 和自定义的头文件 Sales_item.h。
Inside main we define the objects we need: total, which we'll use to sum the data for a given ISBN, and trans, which will hold our transactions as we read them. We start by reading a transaction into total and testing whether the read was successful. If the read fails, then there are no records and we fall through to the outermost else branch, which prints a message to warn the user that there was no input.
在 main 中我们定义了所需要的对象 total 用来计算给定的 ISBN 的交易的总数,trans 用来存储读取的交易。我们首先将交易读入 total 并测试是否读取成功;如果读取失败,表示没有记录,程序进入最外层的 else 分支,输出信息警告用户没有输入。
Assuming we have successfully read a record, we execute the code in the if branch. The first statement is a while that will loop through all the remaining records. Just as we did in the program on page 18, our while condition reads a value from the standard input and then tests that valid data was actually read. In this case, we read a Sales_item object into TRans. As long as the read succeeds, we execute the body of the while.
假如我们成功读取了一个记录,则执行 if 分支里的代码。首先执行 while 语句,循环遍历剩余的所有记录。就像第 1.4.3 节的程序一样,while 循环的条件从标准输入中读取值并测试实际读取的是否是合法数据。本例中,我们将一个 Sales_item 对象读至 trans。只要读取成功,就执行 while 循环体。
The body of the while is a single if statement. We test whether the ISBNs are equal, and if so we add the two objects and store the result in total. If the ISBNs are not equal, we print the value stored in total and reset total by assigning trans to it. After execution of the if, we return to the condition in the while, reading the next transaction and so on until we run out of records.
while 循环体只是一条 if 语句。我们测试 ISBN 是否相等。如果相等,我们将这两个对象相加并将结果存储到 total 中。否则,我们就输出存储在 total 中的值,并将 trans 赋值给 total 来重置 total。执行完 if 语句之后,将返回到 while 语句中的条件,读入下一个交易,直到执行完所有记录。
Once the while completes, we still must write the data associated with the last ISBN. When the while terminates, total contains the data for the last ISBN in the file, but we had no chance to print it. We do so in the last statement of the block that concludes the outermost if statement.
一旦 while 完成,我们仍须写出与最后一个 ISBN 相关联的数据。当 while 语句结束时,total 包含文件中最后一条 ISBN 数据,但是我们没有机会输出这条数据。我们在结束最外层 if 语句的语句块的最后一条语句中进行输出。
Exercises Section 1.6
Exercise 1.25:Using the Sales_item.h header from the Web site, compile and execute the bookstore program presented in this section.
使用源自本书配套网站的 Sales_item.h 头文件,编译并执行本节给出的书店程序。
Exercise 1.26:In the bookstore program we used the addition operator and not the compound assignment operator to add trans to total. Why didn't we use the compound assignment operator?
在书店程序中,我们使用了加法操作符而不是复合赋值操作符将 trans 加到 total 中,为什么我们不使用复合赋值操作符?



      

Chapter Summary
小结
This chapter introduced enough of C++ to let the reader compile and execute simple C++ programs. We saw how to define a main function, which is the function that is executed first in any C++ program. We also saw how to define variables, how to do input and output, and how to write if, for, and while statements. The chapter closed by introducing the most fundamental facility in C++: the class. In this chapter we saw how to create and use objects of a given class. Later chapters show how to define our own classes.
本章介绍了足够多的 C++ 知识,让读者能够编译和执行简单 C++ 程序。我们看到了如何定义 main 函数,这是任何 C++ 程序首先执行的函数。我们也看到了如何定义变量,如何进行输入和输出,以及如何编写 if、for 和 while 语句。本章最后介绍 C++ 最基本的工具:类。在这一章中,我们看到了如何创建和使用给定类的对象。后面的章节中将介绍如何自定义类。

      

Defined Terms
术语
argument(实参)
A value passed to a function when it is called.
传递给被调用函数的值。
block(块)
Sequence of statements enclosed in curly braces.
花括号括起来的语句序列。
buffer(缓冲区)
A region of storage used to hold data. IO facilities often store input (or output) in a buffer and read or write the buffer independently of actions in the program. Output buffers usually must be explicitly flushed to force the buffer to be written. By default, reading cin flushes cout; cout is also flushed when the program ends normally.
一段用来存放数据的存储区域。IO 设备常存储输入(或输出)到缓冲区,并独立于程序动作对缓冲区进行读写。输出缓冲区通常必须显式刷新以强制输出缓冲区内容。默认情况下,读 cin 会刷新 cout;当程序正常结束时,cout 也被刷新。
built-in type(内置类型)
A type, such as int, defined by the language.
C++ 语言本身定义的类型,如 int。
cerr
ostream object tied to the standard error, which is often the same stream as the standard output. By default, writes to cerr are not buffered. Usually used for error messages or other output that is not part of the normal logic of the program.
绑定到标准错误的 ostream 对象,这通常是与标准输出相同的流。默认情况下,输出 cerr 不缓冲,通常用于不是程序正常逻辑部分的错误信息或其他输出。
cin
istream object used to read from the standard input.
用于从标准输入中读入的 istream 对象。
class
C++ mechanism for defining our own data structures. The class is one of the most fundamental features in C++. Library types, such as istream and ostream, are classes.
用于自定义数据结构的 C++ 机制。类是 C++ 中最基本的特征。标准库类型,如 istream 和 ostream,都是类。
class type
A type defined by a class. The name of the type is the class name.
由类所定义的类型,类型名就是类名。
clog
ostream object tied to the standard error. By default, writes to clog are buffered. Usually used to report information about program execution to a log file.
绑定到标准错误的 ostream 对象。默认情况下,写到 clog 时是带缓冲的。通常用于将程序执行信息写入到日志文件中。
comments(注释)
Program text that is ignored by the compiler. C++ has two kinds of comments: single-line and paired. Single-line comments start with a //. Everything from the // to the end of the line is a comment. Paired comments begin with a /* and include all text up to the next */.
编译器会忽略的程序文本。C++ 有单行注释和成对注释两种类型的注释。单行注释以 // 开头,从 // 到行的结尾是一条注释。成对注释以 /* 开始包括到下一个 */ 为止的所有文本。
condition(条件)
An expression that is evaluated as true or false. An arithmetic expression that evaluates to zero is false; any other value yields true.
求值为真或假的表达式。值为 0 的算术表达式是假,其他所有非 0 值都是真。
cout
ostream object used to write to the standard output. Ordinarily used to write the output of a program.
用于写入到标准输出的 ostream 对象,一般情况下用于程序的输出。
curly brace(花括号)
Curly braces delimit blocks. An open curly ({) starts a block; a close curly (}) ends one.
花括号对语句块定界。左花括号“{”开始一个块,右花括号“}”结束块。
data structure(数据结构)
A logical grouping of data and operations on that data.
数据及数据上操作的逻辑组合。
edit-compile-debug(编辑—编译—调试)
The process of getting a program to execute properly.
使得程序正确执行的过程。
end-of-file(文件结束符)
System-specific marker in a file that indicates that there is no more input in the file.
文件中与特定系统有关的标记,表示这个文件中不再有其他输入。
expression(表达式)
an
The smallest unit of computation. An expression consists of one or more operands and usually an operator. Expressions are evaluated to produce a result. For example, assuming i and j are ints, then i + j is an arithmetic addition expression d yields the sum of the two int values. Expressions are covered in more detail in Chapter 5.
最小的计算单元。表达式包含一个或多个操作数并经常含有一个操作符。表达式被求值并产生一个结果。例如,假定 i 和 j 都为 int 型,则 i + j 是一个算术加法表达式并求这两个 int 值的和。表达式将在第五章详细介绍。
for statement(for 语句)
Control statement that provides iterative execution. Often used to step through a data structure or to repeat a calculation a fixed number of times.
提供迭代执行的控制语句,通常用于步进遍历数据结构或对一个计算重复固定次数。
function(函数)
A named unit of computation.
有名字的计算单元。
function body(函数体)
Statement block that defines the actions performed by a function.
定义函数所执行的动作的语句块。
function name(函数名)
Name by which a function is known and can be called.
函数的名字标识,函数通过函数名调用。
header(头文件)
A mechanism whereby the definitions of a class or other names may be made available to multiple programs. A header is included in a program through a #include directive.
使得类或其他名字的定义在多个程序中可用的一种机制。程序中通过 #include 指示包含头文件。
if statement(if 语句)
Conditional execution based on the value of a specified condition. If the condition is true, the if body is executed. If not, control flows to the statement following the else if there is one or to the statement following the if if there is no else.
根据指定条件的值执行的语句。如果条件为真,则执行 if 语句体;否则控制流执行 else 后面的语句,如果没有 else 将执行 if 后面的语句。
iostream(输入输出流)
library type providing stream-oriented input and output.
提供面向流的输入和输出的标准库类型。
istream(输入流)
Library type providing stream-oriented input.
提供面向流的输入的标准库类型。
library type(标准库类型)
A type, such as istream, defined by the standard library.
标准库所定义的类型,如 istream。
main function(主函数)
Function called by the operating system when executing a C++ program. Each program must have one and only one function named main.
执行 C++ 程序时,操作系统调用的函数。每一个程序有且仅有一个主函数 main。
manipulator(操纵符)
Object, such as std::endl, that when read or written "manipulates" the stream itself. Section A.3.1 (p. 825) covers manipulators in more detail.
在读或写时“操纵”流本身的对象,如 std::endl。A.3.1 节详细讲述操纵符。
member function(成员函数)
Operation defined by a class. Member functions ordinarily are called to operate on a specific object.
类定义的操作。成员函数通常在特定的对象上进行操作。
method(方法)
Synonym for member function.
成员函数的同义词。
namespace(命名空间)
Mechanism for putting names defined by a library into a single place. Namespaces help avoid inadvertent name clashes. The names defined by the C++ library are in the namespace std.
将库所定义的名字放至单独一个地方的机制。命名空间有助于避免无意的命名冲突。C++ 标准库所定义的名字在命名空间 std 中。
ostream(输出流)
Library type providing stream-oriented output.
提供面向流的输出的库类型。
parameter list(形参表)
Part of the definition of a function. Possibly empty list that specifies what arguments can be used to call the function.
函数定义的组成部分。指明可以用什么参数来调用函数,可能为空。
preprocessor directive(预处理指示)
An instruction to the C++ preprocessor. #include is a preprocessor directive. Preprocessor directives must appear on a single line. We'll learn more about the preprocessor in Section 2.9.2.
C++ 预处理器的指示。#include 是一个预处理器指示。预处理器指示必须出现在单独的行中。第 2.9.2 节将对预处理器作详细的介绍。
return type(返回类型)
Type of the value returned by a function.
函数返回值的类型。
source file(源文件)
Term used to describe a file that contains a C++ program.
用来描述包含在 C++ 程序中的文件的术语。
standard error(标准错误)
An output stream intended for use for error reporting. Ordinarily, on a windowing operating system, the standard output and the standard error are tied to the window in which the program is executed.
用于错误报告的输出流。通常,在视窗操作系统中,将标准输出和标准错误绑定到程序的执行窗口。
standard input(标准输入)
The input stream that ordinarily is associated by the operating system with the window in which the program executes.
和程序执行窗口相关联的输入流,通常这种关联由操作系统设定。
standard library(标准库)
Collection of types and functions that every C++ compiler must support. The library provides a rich set of capabilities including the types that support IO. C++ programmers tend to talk about "the library," meaning the entire standard library or about particular parts of the library by referring to a library type. For example, programmers also refer to the "iostream library," meaning the part of the standard library defined by the iostream classes.
每个 C++ 编译器必须支持的类型和函数的集合。标准库提供了强大的功能,包括支持 IO 的类型。C++ 程序员谈到的“标准库”,是指整个标准库,当提到某个标准库类型时也指标准库中某个特定的部分。例如,程序员提到的“iostream 库”,专指标准库中由 iostream 类定义的那部分。
standard output(标准输出)
The output stream that ordinarily is associated by the operating system with the window in which the program executes.
和程序执行窗口相关联的输出流,通常这种关联由操作系统设定。
statement(语句)
The smallest independent unit in a C++ program. It is analogous to a sentence in a natural language. Statements in C++ generally end in semicolons.
C++ 程序中最小的独立单元,类似于自然语言中的句子。C++ 中的语句一般以分号结束。
std
Name of the namespace used by the standard library. std::cout indicates that we're using the name cout defined in the std namespace.
标准库命名空间的名字,std::cout 表明正在使用定义在 std 命名空间中的名字 cout。
string literal(字符串字面值)
Sequence of characters enclosed in double quotes.
以双引号括起来的字符序列。
uninitialized variable(未初始化变量)
Variable that has no initial value specified. There are no uninitialized variables of class type. Variables of class type for which no initial value is specified are initialized as specified by the class definition. You must give a value to an uninitialized variable before attempting to use the variable's value. Uninitialized variables can be a rich source of bugs.
没有指定初始值的变量。类类型没有未初始化变量。没有指定初始值的类类型变量由类定义初始化。在使用变量值之前必须给未初始化的变量赋值。未初始化变量是造成bug的主要原因之一。
variable(变量)
A named object.
有名字的对象。
while statement(while语句)
An iterative control statement that executes the statement that is the while body as long as a specified condition is true. The body is executed zero or more times, depending on the truth value of the condition.
一种迭代控制语句,只要指定的条件为真就执行 while 循环体。while 循环体执行0次还是多次,依赖于条件的真值。
() operator[()操作符]
The call operator: A pair of parentheses "()" following a function name. The operator causes a function to be invoked. Arguments to the function may be passed inside the parentheses.
调用操作符。跟在函数名后且成对出现的圆括号。该操作符导致函数被调用,给函数的实参可在括号里传递。
++ operator(++操作符)
Increment operator. Adds one to the operand; ++i is equivalent to i = i+ 1.
自增操作符。将操作数加 1,++i 等价于 i = i + 1。
+= operator(+= 操作符)
A compound assignment operator. Adds right-hand operand to the left and stores the result back into the left-hand operand; a += b is equivalent to a =a + b.
复合赋值操作符,将右操作数和左操作数相加,并将结果存储到左操作数中;a += b等价于 a = a + b。
. operator(. 操作符)
Dot operator. Takes two operands: the left-hand operand is an object and the right is the name of a member of that object. The operator fetches that member from the named object.
点操作符。接受两个操作数:左操作数是一个对象,而右边是该对象的一个成员的名字。这个操作符从指定对象中取得成员。
:: operator(:: 操作符)
Scope operator. We'll see more about scope in Chapter 2. Among other uses, the scope operator is used to access names in a namespace. For example, std::cout says to use the name cout from the namespace std.
作用域操作符。在第二章中,我们将看到更多关于作用域的介绍。在其他的使用过程中,:: 操作符用于在命名空间中访问名字。例如,std::cout 表示使用命名空间 std 中的名字 cout。
= operator(= 操作符)
Assigns the value of the right-hand operand to the object denoted by the left-hand operand.
表示把右操作数的值赋给左操作数表示的对象。
<< operator(<< 操作符)
Output operator. Writes the right-hand operand to the output stream indicated by the left-hand operand: cout << "hi" writes hi to the standard output. Output operations can be chained together: cout << "hi << "bye" writes hibye.
输出操作符。把右操作数写到左操作数指定的输出流:cout << "hi" 把 hi 写入到标准输出流。输出操作可以链接在一起使用:cout << "hi << "bye" 输出 hibye。
>> operator(>> 操作符)
Input operator. Reads from the input stream specified by the left-hand operand into the right-hand operand: cin >> i reads the next value on the standard input into i. Input operations can be chained together: cin >> i >> j reads first into i and then into j.
输入操作符。从左操作数指定的输入流读入数据到右操作数:cin >> i 把标准输入流中的下一个值读入到 i 中。输入操作能够链接在一起使用:cin >> i >> j 先读入 i 然后再读入 j。
== operator(== 操作符)
The equality operator. Tests whether the left-hand operand is equal to the right-hand.
等于操作符,测试左右两边的操作数是否相等。
!= operator(!=操作符)
Assignment operator. Tests whether the left-hand operand is not equal to the right-hand.
不等于操作符。测试左右两边的操作数是否不等。
<= operator(<= 操作符)
The less-than-or-equal operator. Tests whether the left-hand operand is less than or equal to the right-hand.
小于或等于操作符。测试左操作数是否小于或等于右操作数。
< operator(< 操作符)
The less-than operator. Tests whether the left-hand operand is less than the right-hand.
小于操作符。测试左操作数是否小于右操作数。
>= operator(>= 操作符)
Greater-than-or-equal operator. Tests whether the left-hand operand is greater than or equal to the right-hand.
大于或等于操作符。测试左操作数是否大于或等于右操作数。
> operator(> 操作符)
Greater-than operator. Tests whether the left-hand operand is greater than the right-hand.
大于操作符。测试左操作数是否大于右操作数。
       

Chapter 2. Variables and Basic Types
第二章 变量和基本类型
CONTENTS
Section 2.1 Primitive Built-in Types34
Section 2.2 Literal Constants37
Section 2.3 Variables43
Section 2.4 const Qualifier56
Section 2.5 References58
Section 2.6 Typedef Names61
Section 2.7 Enumerations62
Section 2.8 Class Types63
Section 2.9 Writing Our Own Header Files67
Chapter Summary73
Defined Terms73


Types are fundamental to any program. They tell us what our data mean and what operations we can perform on our data.
类型是所有程序的基础。类型告诉我们数据代表什么意思以及可以对数据执行哪些操作。
C++ defines several primitive types: characters, integers, floating-point numbers, and so on. The language also provides mechanisms that let us define our own data types. The library uses these mechanisms to define more complex types such as variable-length character strings, vectors, and so on. Finally, we can modify existing types to form compound types. This chapter covers the built-in types and begins our coverage of how C++ supports more complicated types.
C++ 语言定义了几种基本类型:字符型、整型、浮点型等。C++ 还提供了可用于自定义数据类型的机制,标准库正是利用这些机制定义了许多更复杂的类型,比如可变长字符串 string、vector 等。此外,我们还能修改已有的类型以形成复合类型。本章介绍内置类型,并开始介绍 C++ 如何支持更复杂的类型。
Types determine what the data and operations in our programs mean. As we saw in Chapter 1, the same statement
类型确定了数据和操作在程序中的意义。我们在第一章已经看到,如下的语句
     i =i +j;

can mean different things depending on the types of i and j. If i and j are integers, then this statement has the ordinary, arithmetic meaning of +. However, if i and j are Sales_item objects, then this statement adds the components of these two objects.
有不同的含义,具体含义取决于 i 和 j 的类型。如果 i 和 j 都是整型,则这条语句表示一般的算术“+”运算;如果 i 和 j 都是 Sales_item 对象,则这条语句是将这两个对象的组成成分分别加起来。
In C++ the support for types is extensive: The language itself defines a set of primitive types and ways in which we can modify existing types. It also provides a set of features that allow us to define our own types. This chapter begins our exploration of types in C++ by covering the built-in types and showing how we associate a type with an object. It also introduces ways we can both modify types and can build our own types.
C++ 中对类型的支持是非常广泛的:语言本身定义了一组基本类型和修改已有类型的方法,还提供了一组特征用于自定义类型。本章通过介绍内置类型和如何关联类型与对象来探讨 C++ 中的类型。本章还将介绍更改类型和建立自定义类型的方法。
 
      

2.1. Primitive Built-in Types
2.1. 基本内置类型
C++ defines a set of arithmetic types, which represent integers, floating-point numbers, and individual characters and boolean values. In addition, there is a special type named void. The void type has no associated values and can be used in only a limited set of circumstances. The void type is most often used as the return type for a function that has no return value.
C++ 定义了一组表示整数、浮点数、单个字符和布尔值的算术类型,另外还定义了一种称为 void 的特殊类型。void 类型没有对应的值,仅用在有限的一些情况下,通常用作无返回值函数的返回类型。
The size of the arithmetic types varies across machines. By size, we mean the number of bits used to represent the type. The standard guarantees a minimum size for each of the arithmetic types, but it does not prevent compilers from using larger sizes. Indeed, almost all compilers use a larger size for int than is strictly required. Table 2.1 (p. 36) lists the built-in arithmetic types and the associated minimum sizes.
算术类型的存储空间依机器而定。这里的存储空间是指用来表示该类型的位(bit)数。C++标准规定了每个算术类型的最小存储空间,但它并不阻止编译器使用更大的存储空间。事实上,对于int类型,几乎所有的编译器使用的存储空间都比所要求的大。int表 2.1 列出了内置算术类型及其对应的最小存储空间。
Table 2.1. C++: Arithmetic Types
表 2.1. C++ 算术类型
Type
类型Meaning
含义Minimum Size
最小存储空间
boolbooleanNA
charcharacter8 bits
wchar_twide character16 bits
shortshort integer16 bits
intinteger16 bits
longlong integer32 bits
floatsingle-precision floating-point6 significant digits
doubledouble-precision floating-point10 significant digits
long doubleextended-precision floating-point10 significant digits


Because the number of bits varies, the maximum (or minimum) values that these types can represent also vary by machine.
因为位数的不同,这些类型所能表示的最大(最小)值也因机器的不同而有所不同。




2.1.1. Integral Types
2.1.1. 整型
The arithmetic types that represent integers, characters, and boolean values are collectively referred to as the integral types.
表示整数、字符和布尔值的算术类型合称为整型。
There are two character types: char and wchar_t. The char type is guaranteed to be big enough to hold numeric values that correspond to any character in the machine's basic character set. As a result, chars are usually a single machine byte. The wchar_t type is used for extended character sets, such as those used for Chinese and Japanese, in which some characters cannot be represented within a single char.
字符类型有两种:char 和 wchar_t。char 类型保证了有足够的空间,能够存储机器基本字符集中任何字符相应的数值,因此,char 类型通常是单个机器字节(byte)。wchar_t 类型用于扩展字符集,比如汉字和日语,这些字符集中的一些字符不能用单个 char 表示。
The types short, int, and long represent integer values of potentially different sizes. Typically, shorts are represented in half a machine word, ints in a machine word, and longs in either one or two machine words (on 32-bit machines, ints and longs are usually the same size).
short、int 和 long 类型都表示整型值,存储空间的大小不同。一般, short 类型为半个机器字长,int 类型为一个机器字长,而 long 类型为一个或两个机器字长(在 32 位机器中 int 类型和 long 类型通常字长是相同的)。
Machine-Level Representation of The Built-in Types
内置类型的机器级表示
The C++ built-in types are closely tied to their representation in the computer's memory. Computers store data as a sequence of bits, each of which holds either 0 or 1. A segment of memory might hold
C++ 的内置类型与其在计算机的存储器中的表示方式紧密相关。计算机以位序列存储数据,每一位存储 0 或 1。一段内存可能存储着
     00011011011100010110010000111011 ...



At the bit level, memory has no structure and no meaning.
在位这一级上,存储器是没有结构和意义的。
The most primitive way we impose structure on memory is by processing it in chunks. Most computers deal with memory as chunks of bits of particular sizes, usually powers of 2. They usually make it easy to process 8, 16, or 32 bits at a time, and chunks of 64 and 128 bits are becoming more common. Although the exact sizes can vary from one machine to another, we usually refer to a chunk of 8 bits as a "byte" and 32 bits, or 4 bytes, as a "word."
让存储具有结构的最基本方法是用块(chunk)处理存储。大部分计算机都使用特定位数的块来处理存储,块的位数一般是 2 的幂,因为这样可以一次处理 8、16 或 32 位。64 和 128 位的块如今也变得更为普遍。虽然确切的大小因机器不同而不同,但是通常将 8 位的块作为一个字节,32 位或 4 个字节作为一个“字(word)”。
Most computers associate a numbercalled an addresswith each byte in memory. Given a machine that has 8-bit bytes and 32-bit words, we might represent a word of memory as follows:
大多数计算机将存储器中的每一个字节和一个称为地址的数关联起来。对于一个 8 位字节和 32 位字的机器,我们可以将存储器的字表示如下:
73642400011011
73642501110001
73642601100100
73642700111011


In this illustration, each byte's address is shown on the left, with the 8 bits of the byte following the address.
在这个图中,左边是字节的地址,地址后面为字节的 8 位。
We can use an address to refer to any of several variously sized collections of bits starting at that address. It is possible to speak of the word at address 736424 or the byte at address 736426. We can say, for example, that the byte at address 736425 is not equal to the byte at address 736427.
可以用地址表示从该地址开始的任何几个不同大小的位集合。可以说地址为 736424 的字,也可以说地址为 736426 的字节。例如,可以说地址为736425的字节和地址为 736427 的字节不相等。
To give meaning to the byte at address 736425, we must know the type of the value stored there. Once we know the type, we know how many bits are needed to represent a value of that type and how to interpret those bits.
要让地址为 736425 的字节具有意义,必须要知道存储在该地址的值的类型。一旦知道了该地址的值的类型,就知道了表示该类型的值需要多少位和如何解释这些位。
If we know that the byte at location 736425 has type "unsigned 8-bit integer," then we know that the byte represents the number 112. On the other hand, if that byte is a character in the ISO-Latin-1 character set, then it represents the lower-case letter q. The bits are the same in both cases, but by ascribing different types to them, we interpret them differently.
如果知道地址为 736425 的字节的类型是8位无符号整数,那么就可以知道该字节表示整数 112。另外,如果这个字节是 ISO-Latin-1 字符集中的一个字符,那它就表示小写字母 q。虽然两种情况的位相同,但归属于不同类型,解释也就不同。


The type bool represents the truth values, true and false. We can assign any of the arithmetic types to a bool. An arithmetic type with value 0 yields a bool that holds false. Any nonzero value is treated as true.
bool 类型表示真值 true 和 false。可以将算术类型的任何值赋给 bool 对象。0 值算术类型代表 false,任何非 0 的值都代表 true。
Signed and Unsigned Types
带符号和无符号类型
The integral types, except the boolean type, may be either signed or unsigned. As its name suggests, a signed type can represent both negative and positive numbers (including zero), whereas an unsigned type represents only values greater than or equal to zero.
除 bool 类型外,整型可以是带符号的(signed)也可以是无符号的(unsigned)。顾名思义,带符号类型可以表示正数也可以表示负数(包括 0),而无符号型只能表示大于或等于 0 的数。
The integers, int, short, and long, are all signed by default. To get an unsigned type, the type must be specified as unsigned, such as unsigned long. The unsigned int type may be abbreviated as unsigned. That is, unsigned with no other type implies unsigned int.
整型 int、short 和 long 都默认为带符号型。要获得无符号型则必须指定该类型为 unsigned,比如 unsigned long。unsigned int 类型可以简写为 unsigned,也就是说,unsigned 后不加其他类型说明符意味着是 unsigned int 。
Unlike the other integral types, there are three distinct types for char: plain char, signed char, and unsigned char. Although there are three distinct types, there are only two ways a char can be represented. The char type is respresented using either the signed char or unsigned char version. Which representation is used for char varies by compiler.
和其他整型不同,char 有三种不同的类型:plain char 、unsigned char 和 signed char。虽然 char 有三种不同的类型,但只有两种表示方式。可以使用 unsigned char 或 signed char 表示 char 类型。使用哪种 char 表示方式由编译器而定。
How Integral Values Are Represented
整型值的表示
In an unsigned type, all the bits represent the value. If a type is defined for a particular machine to use 8 bits, then the unsigned version of this type could hold the values 0 through 255.
无符号型中,所有的位都表示数值。如果在某种机器中,定义一种类型使用 8 位表示,那么这种类型的 unsigned 型可以取值 0 到 255。
The C++ standard does not define how signed types are represented at the bit level. Instead, each compiler is free to decide how it will represent signed types. These representations can affect the range of values that a signed type can hold. We are guaranteed that an 8-bit signed type will hold at least the values from 127 through 127; many implementations allow values from 128 through 127.
C++ 标准并未定义 signed 类型如何用位来表示,而是由每个编译器自由决定如何表示 signed 类型。这些表示方式会影响 signed 类型的取值范围。8 位 signed 类型的取值肯定至少是从 -127 到 127,但也有许多实现允许取值从 -128 到 127。
Under the most common strategy for representing signed integral types, we can view one of the bits as a sign bit. Whenever the sign bit is 1, the value is negative; when it is 0, the value is either 0 or a positive number. An 8-bit integral signed type represented using a sign-bit can hold values from 128 through 127.
表示 signed 整型类型最常见的策略是用其中一个位作为符号位。符号位为 1,值就为负数;符号位为 0,值就为 0 或正数。一个 signed 整型取值是从 -128 到 127。
Assignment to Integral Types
整型的赋值
The type of an object determines the values that the object can hold. This fact raises the question of what happens when one tries to assign a value outside the allowable range to an object of a given type. The answer depends on whether the type is signed or unsigned.
对象的类型决定对象的取值。这会引起一个疑问:当我们试着把一个超出其取值范围的值赋给一个指定类型的对象时,结果会怎样呢?答案取决于这种类型是 signed 还是 unsigned 的。
For unsigned types, the compiler must adjust the out-of-range value so that it will fit. The compiler does so by taking the remainder of the value modulo the number of distinct values the unsigned target type can hold. An object that is an 8-bit unsigned char, for example, can hold values from 0 through 255 inclusive. If we assign a value outside this range, the compiler actually assigns the remainder of the value modulo 256. For example, we might attempt to assign the value 336 to an 8-bit signed char. If we try to store 336 in our 8-bit unsigned char, the actual value assigned will be 80, because 80 is equal to 336 modulo 256.
对于 unsigned 类型来说,编译器必须调整越界值使其满足要求。编译器会将该值对 unsigned 类型的可能取值数目求模,然后取所得值。比如 8 位的 unsigned char,其取值范围从 0 到 255(包括 255)。如果赋给超出这个范围的值,那么编译器将会取该值对 256 求模后的值。例如,如果试图将 336 存储到 8 位的 unsigned char 中,则实际赋值为 80,因为 80 是 336 对 256 求模后的值。
For the unsigned types, a negative value is always out of range. An object of unsigned type may never hold a negative value. Some languages make it illegal to assign a negative value to an unsigned type, but C++ does not.
对于 unsigned 类型来说,负数总是超出其取值范围。unsigned 类型的对象可能永远不会保存负数。有些语言中将负数赋给 unsigned 类型是非法的,但在 C++ 中这是合法的。
In C++ it is perfectly legal to assign a negative number to an object with unsigned type. The result is the negative value modulo the size of the type. So, if we assign 1 to an 8-bit unsigned char, the resulting value will be 255, which is 1 modulo 256.
C++ 中,把负值赋给 unsigned 对象是完全合法的,其结果是该负数对该类型的取值个数求模后的值。所以,如果把 -1 赋给8位的 unsigned char,那么结果是 255,因为 255 是 -1 对 256 求模后的值。




When assigning an out-of-range value to a signed type, it is up to the compiler to decide what value to assign. In practice, many compilers treat signed types similarly to how they are required to treat unsigned types. That is, they do the assignment as the remainder modulo the size of the type. However, we are not guaranteed that the compiler will do so for the signed types.
当将超过取值范围的值赋给 signed 类型时,由编译器决定实际赋的值。在实际操作中,很多的编译器处理 signed 类型的方式和 unsigned 类型类似。也就是说,赋值时是取该值对该类型取值数目求模后的值。然而我们不能保证编译器都会这样处理 signed 类型。
2.1.2. Floating-Point Types
2.1.2. 浮点型
The types float, double, and long double represent floating-point single-, double-, and extended-precision values. Typically, floats are represented in one word (32 bits), doubles in two words (64 bits), and long double in either three or four words (96 or 128 bits). The size of the type determines the number of significant digits a floating-point value might contain.
类型 float、 double 和 long double 分别表示单精度浮点数、双精度浮点数和扩展精度浮点数。一般 float 类型用一个字(32 位)来表示,double 类型用两个字(64 位)来表示,long double 类型用三个或四个字(96 或 128 位)来表示。类型的取值范围决定了浮点数所含的有效数字位数。
The float type is usually not precise enough for real programsfloat is guaranteed to offer only 6 significant digits. The double type guarantees at least 10 significant digits, which is sufficient for most calculations.
对于实际的程序来说,float 类型精度通常是不够的——float 型只能保证 6 位有效数字,而 double 型至少可以保证 10 位有效数字,能满足大多数计算的需要。



Advice: Using the Built-in Arithmetic Types
建议:使用内置算术类型
The number of integral types in C++ can be bewildering. C++, like C, is designed to let programs get close to the hardware when necessary, and the integral types are defined to cater to the peculiarities of various kinds of hardware. Most programmers can (and should) ignore these complexities by restricting the types they actually use.
C++ 中整型数有点令人迷惑不解。就像 C 语言一样,C++ 被设计成允许程序在必要时直接处理硬件,因此整型被定义成满足各种各样硬件的特性。大多数程序员可以(应该)通过限制实际使用的类型来忽略这些复杂性。
In practice, many uses of integers involve counting. For example, programs often count the number of elements in a data structure such as a vector or an array. We'll see in Chapters 3 and 4 that the library defines a set of types to use when dealing with the size of an object. When counting such elements it is always right to use the library-defined type intended for this purpose. When counting in other circumstances, it is usually right to use an unsigned value. Doing so avoids the possibility that a value that is too large to fit results in a (seemingly) negative result.
实际上,许多人用整型进行计数。例如:程序经常计算像 vector 或数组这种数据结构的元素个数。在第三章和第四章中,我们将看到标准库定义了一组类型用于统计对象的大小。因此,当计数这些元素时使用标准库定义的类型总是正确的。其他情况下,使用 unsigned 类型比较明智,可以避免值越界导致结果为负数的可能性。
When performing integer arithmetic, it is rarely right to use shorts. In most programs, using shorts leads to mysterious bugs when a value is assigned to a short that is bigger than the largest number it can hold. What happens depends on the machine, but typically the value "wraps around" so that a number too large to fit turns into a large negative number. For the same reason, even though char is an integral type, the char type should be used to hold characters and not for computation. The fact that char is signed on some implementations and unsigned on others makes it problematic to use it as a computational type.
当执行整型算术运算时,很少使用 short 类型。大多数程序中,使用 short 类型可能会隐含赋值越界的错误。这个错误会产生什么后果将取决于所使用的机器。比较典型的情况是值“截断(wrap around)”以至于因越界而变成很大的负数。同样的道理,虽然 char 类型是整型,但是 char 类型通常用来存储字符而不用于计算。事实上,在某些应用中 char 类型被当作 signed 类型,在另外一些应用中则被当作 unsigned 类型,因此把 char 类型作为计算类型使用时容易出问题。
On most machines, integer calculations can safely use int. Technically speaking, an int can be as small as 16 bitstoo small for most purposes. In practice, almost all general-purpose machines use 32-bits for ints, which is often the same size used for long. The difficulty in deciding whether to use int or long occurs on machines that have 32-bit ints and 64-bit longs. On such machines, the run-time cost of doing arithmetic with longs can be considerably greater than doing the same calculation using a 32-bit int. Deciding whether to use int or long requires detailed understanding of the program and the actual run-time performance cost of using long versus int.
在大多数机器上,使用 int 类型进行整型计算不易出错。就技术上而言,int 类型用 16 位表示——这对大多数应用来说太小了。实际应用中,大多数通用机器都是使用和 long 类型一样长的 32 位来表示 int 类型。整型运算时,用 32 位表示 int 类型和用 64 位表示 long 类型的机器会出现应该选择 int 类型还是 long 类型的难题。在这些机器上,用 long 类型进行计算所付出的运行时代价远远高于用 int 类型进行同样计算的代价,所以选择类型前要先了解程序的细节并且比较 long 类型与 int 类型的实际运行时性能代价。
Determining which floating-point type to use is easier: It is almost always right to use double. The loss of precision implicit in float is significant, whereas the cost of double precision calculations versus single precision is negligible. In fact, on some machines, double precision is faster than single. The precision offered by long double usually is unnecessary and often entails considerable extra run-time cost.
决定使用哪种浮点型就容易多了:使用 double 类型基本上不会有错。在 float 类型中隐式的精度损失是不能忽视的,而 double 类型精度代价相对于 float 类型精度代价可以忽略。事实上,有些机器上,double 类型比 float 类型的计算要快得多。long double 类型提供的精度通常没有必要,而且还需要承担额外的运行代价。

Exercises Section 2.1.2
Exercise 2.1:What is the difference between an int, a long, and a short value?
int、long 和 short 类型之间有什么差别?
Exercise 2.2:What is the difference between an unsigned and a signed type?
unsigned 和 signed 类型有什么差别?
Exercise 2.3:If a short on a given machine has 16 bits then what is the largest number that can be assigned to a short? To an unsigned short?
如果在某机器上 short 类型占 16 位,那么可以赋给 short 类型的最大数是什么?unsigned short 类型的最大数又是什么?
Exercise 2.4:What value is assigned if we assign 100,000 to a 16-bit unsigned short? What value is assigned if we assign 100,000 to a plain 16-bit short?
当给 16 位的 unsigned short 对象赋值 100 000 时,赋的值是什么?
Exercise 2.5:What is the difference between a float and a double?
float 类型和 double 类型有什么差别?
Exercise 2.6:To calculate a mortgage payment, what types would you use for the rate, principal, and payment? Explain why you selected each type.
要计算抵押贷款的偿还金额,利率、本金和付款额应分别选用哪种类型?解释你选择的理由。


      

Chapter Summary
小结
Types are fundamental to all programming in C++.
类型是 C++ 程序设计的基础。
Each type defines the storage requirements and the operations that may be performed on all objects of that type. The language provides a set of fundamental built-in types such as int and char. These types are closely tied to their representation on the machine's hardware.
每种类型都定义了其存储空间要求和可以在该类型的所有对象上执行的操作。C++ 提供了一组基本内置类型,如 int、char 等。这些类型与它们在机器硬件上的表示方式紧密相关。
Types can be nonconst or const; a const object must be initialized and its value may not be changed. In addition, we can define compound types, such as references. A reference provides another name for an object. A compound type is a type that is defined in terms of another type.
类型可以为 const 或非 const;const 对象必须要初始化,且其值不能被修改。另外,我们还可以定义复合类型,如引用。引用为对象提供了另一个名字。复合类型是用其他类型定义的类型。
The language lets us define our own types by defining a class. The library uses the class facility to provide a set of higher-level abstractions such as the IO and string types.
C++ 语言支持通过定义类来自定义类型。标准库使用类设施来提供一组高级的抽象概念,如 IO 和 string 类型。
C++ is a statically typed language: Variables and functions must be declared before they are used. A variable can be declared many times but defined only once. It is almost always a good idea to initialize variables when you define them.
C++ 是一种静态类型语言:变量和函数在使用前必须先声明。变量可以声明多次但是只能定义一次。定义变量时就进行初始化几乎总是个好主意。

      

Defined Terms
术语
access labels(访问标号)
Members in a class may be defined to be private, which protects them from access from code that uses the type. Members may also be defined as public, which makes them accessible code throughout the program.
类的成员可以定义为 private,这能够防止使用该类型的代码访问该成员。成员还可以定义为 public,这将使该整个程序中都可访问成员。
address(地址)
Number by which a byte in memory can be found.
一个数字,通过该数字可在存储器上找到一个字节。
arithmetic types(算术类型)
The arithmetic types represent numbers: integers and floating point. There are three types of floating point values: long double, double, and float. These represent extended, double, and single precision values. It is almost always right to use double. In particular, float is guaranteed only six significant digits too small for most calculations. The integral types include bool, char, wchar_t, short, int, and long. Integer types can be signed or unsigned. It is almost always right to avoid short and char for arithmetic. Use unsigned for counting. The bool type may hold only two values: true or false. The whcar_t type is intended for characters from an extended character set; char type is used for characters that fit in 8 bits, such as Latin-1 or ASCII.
表示数值即整数和浮点数的类型。浮点型值有三种类型:long double 、double 和 float,分别表示扩展精度值、双精度值和单精度值。一般总是使用 double 型。特别地,float 只能保证六位有效数字,这对于大多数的计算来说都不够。整型包括 bool、char、wchar_t、short 、int 和 long 。整型可以是带符号或无符号的。一般在算术计算中总是避免使用 short 和 char。 unsigned 可用于计数。bool 类型只有 true 和 false 两个值。wchar_t 类型用于扩展字符集的字符;char 类型用于适合 8 个位的字符,比如 Latin-1 或者 ASCII。
array(数组)
Data structure that holds a collection of unnamed objects that can be accessed by an index. This chapter introduced the use of character arrays to hold string literals. Chapter 4 will discuss arrays in much more detail.
存储一组可通过下标访问的未命名对象的数据结构。本章介绍了存储字符串字面值的字符数组。第四章将会更加详细地介绍数组。
byte(字节)
Typically the smallest addressable unit of memory. On most machines a byte is 8 bits.
最小的可寻址存储单元。大多数的机器上一个字节有 8 个位(bit)。
class(类)
C++ mechanism for defining data types. Classes are defined using either the class or struct keyword. Classes may have data and function members. Members may be public or private. Ordinarily, function members that define the operations on the type are made public; data members and functions used in the implementation of the class are made private. By default, members in a class defined using the class keyword are private; members in a class defined using the struct keyword are public.
C++ 中定义数据类型的机制。类可以用 class 或 struct 关键字定义。类可以有数据和函数成员。成员可以是 public 或 private。一般来说,定义该类型的操作的函数成员设为 public ;用于实现该类的数据成员和函数设为 private。默认情况下,用 class 关键字定义的类其成员为 private ,而用 struct 关键字定义的类其成员为 public。
class member(类成员)
A part of a class. Members are either data or operations.
类的一部分,可以是数据或操作。
compound type(复合类型)
A type, such as a reference, that is defined in terms of another type. Chapter 4 covers two additional compound types: pointers and arrays.
用其他类型定义的类型,如引用。第四章将介绍另外两种复合类型:指针和数组。
const reference(const 引用)
A reference that may be bound to a const object, a nonconst object, or to an rvalue. A const reference may not change the object to which it refers.
可以绑定到 const 对象、非 const 对象或右值的引用。const 引用不能改变与其相关联的对象。
constant expression(常量表达式)
An integral expression whose value can be evaluated at compile-time.
值可以在编译时计算出来的整型表达式。
constructor(构造函数)
Special member function that is used to initialize newly created objects. The job of a constructor is to ensure that the data members of an object have safe, sensible initial values.
用来初始化新建对象的特殊成员函数。构造函数的任务是保证对象的数据成员拥有可靠且合理的初始值。
copy-initialization(复制初始化)
Form of initialization that uses the = symbol to indicate that variable should be initialized as a copy of the initializer.
一种初始化形式,用“=”表明变量应初始化为初始化式的副本。
data member(数据成员)
The data elements that constitute an object. Data members ordinarily should be private.
组成对象的数据元素。数据成员一般应设为私有的。
declaration(声明)
Asserts the existence of a variable, function, or type defined elsewhere in the program. Some declarations are also definitions; only definitions allocate storage for variables. A variable may be declared by preceeding its type with the keyword extern. Names may not be used until they are defined or declared.
表明在程序中其他地方定义的变量、函数或类型的存在性。有些声明也是定义。只有定义才为变量分配存储空间。可以通过在类型前添加关键字 extern 来声明变量。名字直到定义或声明后才能使用。
default constructor(默认构造函数)
The constructor that is used when no explicit values are given for an initializer of a class type object. For example, the default constructor for string initializes the new string as the empty string. Other string constructors initialize the string with characters specified when the string is created.
在没有为类类型对象的初始化式提供显式值时所使用的构造函数。例如,string 类的默认构造函数将新建的 string 对象初始化为空 string,而其他构造函数都是在创建 string 对象时用指定的字符去初始化 string 对象。
definition(定义)
Allocates storage for a variable of a specified type and optionally initializes the variable. Names may not be used until they are defined or declared.
为指定类型的变量分配存储空间,也可能可选地初始化该变量。名字直到定义或声明后才能使用。
direct-initialization(直接初始化)
Form of initialization that places a comma-separated list of initializers inside a pair of parentheses.
一种初始化形式,将逗号分隔的初始化式列表放在圆括号内。
enumeration(枚举)
A type that groups a set of named integral constants.
将一些命名整型常量聚成组的一种类型。
enumerator(枚举成员)
The named members of an enumeration. Each enumerator is initialized to an integral value and the value of the enumerator is const. Enumerators may be used where integral constant expressions are required, such as the dimension of an array definition.
枚举类型的有名字的成员。每个枚举成员都初始化为整型值且值为 const。枚举成员可用在需要整型常量表达式的地方,比如数组定义的维度。
escape sequence(转义字符)
Alternative mechanism for representing characters. Usually used to represent nonprintable characters such as newline or tab. An escape sequence is a backslash followed by a character, a three-digit octal number, or a hexadecimal number. The escape sequences defined by the language are listed on page 40. Escape sequences can be used as a literal character (enclosed in single quotes) or as part of a literal string (enclosed in double quotes).
一种表示字符的可选机制。通常用于表示不可打印字符如换行符或制表符。转义字符是反斜线后面跟着一个字符、一个 3 位八进制数或一个十六进制的数。C++ 语言定义的转义字符列在第 2.2 节。转义字符还可用作字符字面值(括在单引号里)或用作字符串字面值的一部分(括在双引号里)。
global scope(全局作用域)
Scope that is outside all other scopes.
位于任何其他作用域外的作用域。
header(头文件)
A mechanism for making class definitions and other declarations available in multiple source files. User-defined headers are stored as files. System headers may be stored as files or in some other system-specific format.
使得类的定义和其他声明在多个源文件中可见的一种机制。用户定义的头文件以文件方式保存。系统头文件可能以文件方式保存,也可能以系统特有的其他格式保存。
header guard(头文件保护符)
The preprocessor variable defined to prevent a header from being included more than once in a single source file.
为防止头文件被同一源文件多次包含而定义的预处理器变量。
identifier(标识符)
A name. Each identifier is a nonempty sequence of letters, digits, and underscores that must not begin with a digit. Identifiers are case-sensitive: Upper- and lowercase letters are distinct. Identifiers may not use C++ keywords. Identifiers may not contain two adjacent underscores nor may they begin with an underscore followed by a uppercase letter.
名字。每个标识符都是字母、数字和下划线的非空序列,且序列不能以数字开头。标识符是大小写敏感的:大写字母和小写字母含义不同。标识符不能使用C++中的关键字,不能包含相邻的下划线,也不能以下划线后跟一个大写字母开始。
implementation(实现)
The (usually private) members of a class that define the data and any operations that are not intended for use by code that uses the type. The istream and ostream classes, for example, manage an IO buffer that is part of their implementation and not directly accessible to users of those classes.
定义数据和操作的类成员(通常为 private),这些数据和操作并非为使用该类型的代码所用。例如,istream 和 ostream 类管理的 IO 缓冲区是它们的实现的一部分,但并不允许这些类的使用者直接访问。
initialized(已初始化的)
A variable that has an initial value. An initial value may be specified when defining a variable. Variables usually should be initialized.
含有初始值的变量。当定义变量时,可指定初始值。变量通常要初始化。
integral types(整型)
See arithmetic type.
见 arithmetic type。
interface(接口)
The operations supported by a type. Well-designed classes separate their interface and implementation, defining the interface in the public part of the class and the implementation in the private parts. Data members ordinarily are part of the implementation. Function members are part of the interface (and hence public) when they are operations that users of the type are expected to use and part of the implementation when they perform operations needed by the class but not defined for general use.
由某种类型支持的操作。设计良好的类分离了接口和实现,在类的 public 部分定义接口,private 部分定义实现。数据成员一般是实现的一部分。当函数成员是期望该类型的使用者使用的操作时,函数成员就是接口的一部分(因此为 public);当函数成员执行类所需要的、非一般性使用的操作时,函数成员就是实现的一部分。
link(链接)
Compilation step in which multiple object files are put together to form an executable program. The link step resolves interfile dependencies, such as linking a function call in one file to a function definition contained in a second file.
一个编译步骤,此时多个目标文件放置在一起以形成可执行程序。链接步骤解决了文件间的依赖,如将一个文件中的函数调用链接到另一个文件中的函数定义。
literal constant(字面值常量)
A value such as a number, a character, or a string of characters. The value cannot be changed. Literal characters are enclosed in single quotes, literal strings in double quotes.
诸如数、字符或字符串的值,该值不能修改。字面值字符用单引号括住,而字面值字符串则用双引号括住。
local scope(局部作用域)
Term used to describe function scope and the scopes nested inside a function.
用于描述函数作用域和函数内嵌套的作用域的术语。
lvalue(左值)
A value that may appear on the left-hand of an assignment. A nonconst lvalue may be read and written.
可以出现在赋值操作左边的值。非 const 左值可以读也可以写。
magic number(魔数)
A literal number in a program whose meaning is important but not obvious. It appears as if by magic.
程序中意义重要但又不明显的字面值数字。它的出现好像变魔术一般。
nonconst reference(非 const 引用)
A reference that may be bound only to a nonconst lvalue of the same type as the reference. A nonconst reference may change the value of the underlying object to which it refers.
只能绑定到与该引用同类型的非 const 左值的引用。非 const 引用可以修改与其相关联的对象的值。
nonprintable character(非打印字符)
A character with no visible representation, such as a control character, a backspace, newline, and so on.
不可见字符。如控制符、回退删除符、换行符等。
object(对象)
A region of memory that has a type. A variable is an object that has a name.
具有类型的一段内存区域。变量就是一个有名字的对象。
preprocessor(预处理器)
The preprocessor is a program that runs as part of compilation of a C++ program. The preprocessor is inherited from C, and its uses are largely obviated by features in C++. One essential use of the preprocessor remains: the #include facility, which is used to incorporate headers into a program.
预处理器是作为 C++ 程序编译的一部分运行的程序。预处理器继承于 C 语言,C++ 的特征大量减少了它的使用,但仍保存了一个很重要的用法:#include 设施,用来把头文件并入程序。
private member(私有成员)
Member that is inaccessible to code that uses the class.
使用该类的代码不可访问的成员。
public member(公用成员)
Member of a class that can be used by any part of the program.
可被程序的任何部分使用的类成员。
reference(引用)
An alias for another object. Defined as follows:
对象的别名。定义如下:
     type &id = object;



Defines id to be another name for object. Any operation on id is translated as an operation on object.
定义 id 为 object 的另一名字。任何对 id 的操作都会转变为对 object 的操作。
run time(运行时)
Refers to the time during which the program is executing.
指程序正执行的那段时间。
rvalue(右值)
A value that can be used as the right-hand, but not left-hand side of an assignment. An rvalue may be read but not written.
可用于赋值操作的右边但不能用于左边的值。右值只能读而不能写。
scope(作用域)
A portion of a program in which names have meaning. C++ has several levels of scope:
程序的一部分,在其中名字有意义。C++ 含有下列几种作用域:
global names defined outside any other scope.
全局——名字定义在任何其他作用域外。
class names defined by a class.
类——名字由类定义。
namespace names defined within a namespace.
命名空间——名字在命名空间中定义。
local names defined within a function.
局部——名字在函数内定义。
block names defined within a block of statements, that is, within a pair of curly braces.
块——名字定义在语句块中,也就是说,定义在一对花括号里。
statement names defined within the condition of a statement, such as an if, for, or while.
语句——名字在语句( 如if、while 和 for 语句)的条件内定义。
Scopes nest. For example, names declared at global scope are accessible in function and statement scope.
作用域可嵌套。例如,在全局作用域中声明的名字在函数作用域和语句作用域中都可以访问。
separate compilation(分别编译)
Ability to split a program into multiple separate source files.
将程序分成多个分离的源文件进行编译。
signed(带符号型)
Integer type that holds negative or positive numbers, including zero.
保存负数、正数或零的整型。
statically typed(静态类型的)
Term used to refer to languages such as C++ that do compile-time type checking. C++ verifies at compile-time that the types used in expressions are capable of performing the operations required by the expression.
描述进行编译时类型检查的语言(如 C++)的术语。C++ 在编译时验证表达式使用的类型可以执行该表达式需要的操作。
struct
Keyword that can be used to define a class. By default, members of a struct are public until specified otherwise.
用来定义类的关键字。除非有特殊的声明,默认情况下 struct 的成员都为公用的。
type-checking(类型检查)
Term used to describe the process by which the compiler verifies that the way objects of a given type are used is consistent with the definition of that type.
编译器验证给定类型的对象的使用方式是否与该类型的定义一致,描述这一过程的术语。
type specifier(类型说明符)
The part of a definition or declaration that names the type of the variables that follow.
定义或声明中命名其后变量的类型的部分。
typedef
Introduces a synonym for some other type. Form:
为某种类型引入同义词。格式:
     typedef type synonym;



defines synonym as another name for the type named type.
定义 synonym 为名为 type 的类型的另一名字。
undefined behavior(未定义行为)
A usage for which the language does not specify a meaning. The compiler is free to do whatever it wants. Knowingly or unknowingly relying on undefined behavior is a great source of hard-to-track run-time errors and portability problems.
语言没有规定其意义的用法。编译器可以自由地做它想做的事。有意或无意地依赖未定义行为将产生大量难于跟踪的运行时错误和可移值性问题。
uninitialized(未初始化的)
Variable with no specified initial value. An uninitialized variable is not zero or "empty;" instead, it holds whatever bits happen to be in the memory in which it was allocated. Uninitialized variables are a great source of bugs.
没有指定初始值的变量。未初始化变量不是0也不是“空”,相反,它会保存碰巧遗留在分配给它的内存里的任何位。未初始化变量会产生很多错误。
unsigned(无符号型)
Integer type that holds values greater than or equal to zero.
保存大于等于零的值的整型。
variable initialization(变量初始化)
Term used to describe the rules for initializing variables and array elements when no explicit initializer is given. For class types, objects are initialized by running the class's default constructor. If there is no default constructor, then there is a compile-time error: The object must be given an explicit initializer. For built-in types, initialization depends on scope. Objects defined at global scope are initialized to 0; those defined at local scope are uninitialized and have undefined values.
描述当没有给出显式初始化式时初始化变量或数组元素的规则的术语。对类类型来说,通过运行类的默认构造函数来初始化对象。如果没有默认构造函数,那么将会出现编译时错误:必须要给对象指定显式的初始化式。对于内置类型来说,初始化取决于作用域。定义在全局作用域的对象初始化为 0,而定义在局部作用域的对象则未初始化,拥有未定义值。
void type(空类型)
Special-purpose type that has no operations and no value. It is not possible to define a variable of type void. Most commonly used as the return type of a function that does not return a result.
用于特殊目的的没有操作也没有值的类型。不可能定义一个 void 类型的变量。最经常用作不返回结果的函数的返回类型。
word(字)
The natural unit of integer computation on a given machine. Usually a word is large enough to hold an address. Typically on a 32-bit machine machine a word is 4 bytes.
机器上的自然的整型计算单元。通常一个字足以容纳一个地址。一般在 32 位的机器上,机器字长为 4 个字节。
 
      

2.2. Literal Constants
2.2. 字面值常量
A value, such as 42, in a program is known as a literal constant: literal because we can speak of it only in terms of its value; constant because its value cannot be changed. Every literal has an associated type. For example, 0 is an int and 3.14159 is a double. Literals exist only for the built-in types. There are no literals of class types. Hence, there are no literals of any of the library types.
像 42 这样的值,在程序中被当作字面值常量。称之为字面值是因为只能用它的值称呼它,称之为常量是因为它的值不能修改。每个字面值都有相应的类型,例如:0 是 int 型,3.14159 是 double 型。只有内置类型存在字面值,没有类类型的字面值。因此,也没有任何标准库类型的字面值。
Rules for Integer Literals
整型字面值规则
We can write a literal integer constant using one of three notations: decimal, octal, or hexadecimal. These notations, of course, do not change the bit representation of the value, which is always binary. For example, we can write the value 20 in any of the following three ways:
定义字面值整数常量可以使用以下三种进制中的任一种:十进制、八进制和十六进制。当然这些进制不会改变其二进制位的表示形式。例如,我们能将值 20 定义成下列三种形式中的任意一种:
     20     // decimal
     024    // octal
     0x14   // hexadecimal



Literal integer constants that begin with a leading 0 (zero) are interpreted as octal; those that begin with either 0x or 0X are interpreted as hexadecimal.
以 0(零)开头的字面值整数常量表示八进制,以 0x 或 0X 开头的表示十六进制。
By default, the type of a literal integer constant is either int or long. The precise type depends on the value of the literalvalues that fit in an int are type int and larger values are type long. By adding a suffix, we can force the type of a literal integer constant to be type long or unsigned or unsigned long. We specify that a constant is a long by immediately following the value with either L or l (the letter "ell" in either uppercase or lowercase).
字面值整数常量的类型默认为 int 或 long 类型。其精度类型决定于字面值——其值适合 int 就是 int 类型,比 int 大的值就是 long 类型。通过增加后缀,能够强制将字面值整数常量转换为 long、unsigned 或 unsigned long 类型。通过在数值后面加 L 或者 l(字母“l”大写或小写)指定常量为 long 类型。
When specifying a long, use the uppercase L: the lowercase letter l is too easily mistaken for the digit 1.
定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。




In a similar manner, we can specify unsigned by following the literal with either U or u. We can obtain an unsigned long literal constant by following the value by both L and U. The suffix must appear with no intervening space:
类似地,可通过在数值后面加 U 或 u 定义 unsigned 类型。同时加 L 和 U 就能够得到 unsigned long 类型的字面值常量。但其后缀不能有空格:
     128u     /* unsigned   */          1024UL    /* unsigned long   */
     1L       /* long    */             8Lu        /* unsigned long   */



There are no literals of type short.
没有 short 类型的字面值常量。
Rules for Floating-Point Literals
浮点字面值规则
We can use either common decimal notation or scientific notation to write floating-point literal constants. Using scientific notation, the exponent is indicated either by E or e. By default, floating-point literals are type double. We indicate single precision by following the value with either F or f. Similarly, we specify extended precision by following the value with either L or l (again, use of the lowercase l is discouraged). Each pair of literals below denote the same underlying value:
通常可以用十进制或者科学计数法来表示浮点字面值常量。使用科学计数法时,指数用 E 或者 e 表示。默认的浮点字面值常量为 double 类型。在数值的后面加上 F 或 f 表示单精度。同样加上 L 或者 l 表示扩展精度(再次提醒,不提倡使用小写字母l)。下面每一组字面值表示相同的值:
     3.14159F            .001f          12.345L            0.
     3.14159E0f          1E-3F          1.2345E1L          0e0

Boolean and Character Literals
布尔字面值和字符字面值
The words true and false are literals of type bool:
单词 true 和 false 是布尔型的字面值:
     bool test = false;

Printable character literals are written by enclosing the character within single quotation marks:
可打印的字符型字面值通常用一对单引号来定义:
     'a'         '2'         ','         ' ' // blank

Such literals are of type char. We can obtain a wide-character literal of type wchar_t by immediately preceding the character literal with an L, as in
这些字面值都是 char 类型的。在字符字面值前加 L 就能够得到 wchar_t 类型的宽字符字面值。如:
     L'a'

Escape Sequences for Nonprintable Characters
非打印字符的转义序列
Some characters are nonprintable. A nonprintable character is a character for which there is no visible image, such as backspace or a control character. Other characters have special meaning in the language, such as the single and double quotation marks, and the backslash. Nonprintable characters and special characters are written using an escape sequence. An escape sequence begins with a backslash. The language defines the following escape sequences:
有些字符是不可打印的。不可打印字符实际上是不可显示的字符,比如退格或者控制符。还有一些在语言中有特殊意义的字符,例如单引号、双引号和反斜线符号。不可打印字符和特殊字符都用转义字符书写。转义字符都以反斜线符号开始,C++ 语言中定义了如下转义字符:
newline
换行符 \n
horizontal tab
水平制表符\t
vertical tab
纵向制表符\v
backspace
退格符\b
carriage return
回车符\r
formfeed
进纸符\f
alert (bell)
报警(响铃)符\a
backslash
反斜线\\
question mark
疑问号\?
single quote
单引号\'
double quote
双引号\"  


We can write any character as a generalized escape sequence of the form
我们可以将任何字符表示为以下形式的通用转义字符:
     \ooo

where ooo represents a sequence of as many as three octal digits. The value of the octal digits represents the numerical value of the character. The following examples are representations of literal constants using the ASCII character set:
这里 ooo 表示三个八进制数字,这三个数字表示字符的数字值。下面的例子是用 ASCII 码字符集表示字面值常量:
     \7 (bell)      \12 (newline)     \40 (blank)
     \0 (null)      \062 ('2')        \115 ('M')

The character represented by '\0' is often called a "null character," and has special significance, as we shall soon see.
字符’\0’通常表示“空字符(null character)”,我们将会看到它有着非常特殊的意义。
We can also write a character using a hexadecimal escape sequence
同样也可以用十六进制转义字符来定义字符:
     \xddd

consisting of a backslash, an x, and one or more hexadecimal digits.
它由一个反斜线符、一个 x 和一个或者多个十六进制数字组成。
Character String Literals
字符串字面值
All of the literals we've seen so far have primitive built-in types. There is one additional literalstring literalthat is more complicated. String literals are arrays of constant characters, a type that we'll discuss in more detail in Section 4.3 (p. 130).
之前见过的所有字面值都有基本内置类型。还有一种字面值(字符串字面值)更加复杂。字符串字面值是一串常量字符,这种类型将在第 4.3 节详细说明。
String literal constants are written as zero or more characters enclosed in double quotation marks. Nonprintable characters are represented by their underlying escape sequence.
字符串字面值常量用双引号括起来的零个或者多个字符表示。不可打印字符表示成相应的转义字符。
     "Hello World!"                 // simple string literal
     ""                             // empty string literal
     "\nCC\toptions\tfile.[cC]\n"   // string literal using newlines and tabs

For compatibility with C, string literals in C++ have one character in addition to those typed in by the programmer. Every string literal ends with a null character added by the compiler. A character literal
为了兼容 C 语言,C++ 中所有的字符串字面值都由编译器自动在末尾添加一个空字符。字符字面值
     'A' // single quote: character literal

represents the single character A, whereas
表示单个字符 A,然而
     "A" // double quote: character string literal

represents an array of two characters: the letter A and the null character.
表示包含字母 A 和空字符两个字符的字符串。
Just as there is a wide character literal, such as
正如存在宽字符字面值,如
        L'a'

there is a wide string literal, again preceded by L, such as
也存在宽字符串字面值,一样在前面加“L”,如
      L"a wide string literal"

The type of a wide string literal is an array of constant wide characters. It is also terminated by a wide null character.
宽字符串字面值是一串常量宽字符,同样以一个宽空字符结束。
Concatenated String Literals
字符串字面值的连接
Two string literals (or two wide string literals) that appear adjacent to one another and separated only by spaces, tabs, or newlines are concatenated into a single new string literal. This usage makes it easy to write long literals across separate lines:
两个相邻的仅由空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值。这使得多行书写长字符串字面值变得简单:
     // concatenated long string literal
     std::cout << "a multi-line "
                  "string literal "
                  "using concatenation"
               << std::endl;

When executed this statement would print:
执行这条语句将会输出:
     a multi-line string literal using concatenation

What happens if you attempt to concatenate a string literal and a wide string literal? For example:
如果连接字符串字面值和宽字符串字面值,将会出现什么结果呢?例如:
     // Concatenating plain and wide character strings is undefined
     std::cout << "multi-line " L"literal " << std::endl;

The result is undefinedthat is, there is no standard behavior defined for concatenating the two different types. The program might appear to work, but it also might crash or produce garbage values. Moreover, the program might behave differently under one compiler than under another.
其结果是未定义的,也就是说,连接不同类型的行为标准没有定义。这个程序可能会执行,也可能会崩溃或者产生没有用的值,而且在不同的编译器下程序的动作可能不同。
Multi-Line Literals
多行字面值
There is a more primitive (and less useful) way to handle long strings that depends on an infrequently used program formatting feature: Putting a backslash as the last character on a line causes that line and the next to be treated as a single line.
处理长字符串有一个更基本的(但不常使用)方法,这个方法依赖于很少使用的程序格式化特性:在一行的末尾加一反斜线符号可将此行和下一行当作同一行处理。
As noted on page 14, C++ programs are largely free-format. In particular, there are only a few places that we may not insert whitespace. One of these is in the middle of a word. In particular, we may not break a line in the middle of a word. We can circumvent this rule by using a backslash:
正如第 1.4.1 节提到的,C++ 的格式非常自由。特别是有一些地方不能插入空格,其中之一是在单词中间。特别是不能在单词中间断开一行。但可以通过使用反斜线符号巧妙实现:
      // ok: A \ before a newline ignores the line break
      std::cou\
      t << "Hi" << st\
      d::endl;

is equivalent to
等价于
      std::cout << "Hi" << std::endl;

We could use this feature to write a long string literal:
可以使用这个特性来编写长字符串字面值:
           // multiline string literal
           std::cout << "a multi-line \
      string literal \
      using a backslash"
                    << std::endl;
          return 0;
      }

Note that the backslash must be the last thing on the lineno comments or trailing blanks are allowed. Also, any leading spaces or tabs on the subsequent lines are part of the literal. For this reason, the continuation lines of the long literal do not have the normal indentation.
注意反斜线符号必须是该行的尾字符——不允许有注释或空格符。同样,后继行行首的任何空格和制表符都是字符串字面值的一部分。正因如此,长字符串字面值的后继行才不会有正常的缩进。
Advice: Don't Rely on Undefined Behavior
建议:不要依赖未定义行为
Programs that use undefined behavior are in error. If they work, it is only by coincidence. Undefined behavior results from a program error that the compiler cannot detect or from an error that would be too much trouble to detect.
使用了未定义行为的程序都是错误的,即使程序能够运行,也只是巧合。未定义行为源于编译器不能检测到的程序错误或太麻烦以至无法检测的错误。
Unfortunately, programs that contain undefined behavior can appear to execute correctly in some circumstances and/or on one compiler. There is no guarantee that the same program, compiled under a different compiler or even a subsequent release of the current compiler, will continue to run correctly. Nor is there any guarantee that what works with one set of inputs will work with another.
不幸的是,含有未定义行为的程序在有些环境或编译器中可以正确执行,但并不能保证同一程序在不同编译器中甚至在当前编译器的后继版本中会继续正确运行,也不能保证程序在一组输入上可以正确运行且在另一组输入上也能够正确运行。
Programs should not (knowingly) rely on undefined behavior. Similarly, programs usually should not rely on machine-dependent behavior, such as assuming that the size of an int is a fixed and known value. Such programs are said to be nonportable. When the program is moved to another machine, any code that relies on machine-dependent behavior may have to be found and corrected. Tracking down these sorts of problems in previously working programs is, mildly put, a profoundly unpleasant task.
程序不应该依赖未定义行为。同样地,通常程序不应该依赖机器相关的行为,比如假定 int 的位数是个固定且已知的值。我们称这样的程序是不可移植的。当程序移植到另一台机器上时,要寻找并更改任何依赖机器相关操作的代码。在本来可以运行的程序中寻找这类问题是一项非常不愉快的任务。
Exercises Section 2.2
Exercise 2.7:Explain the difference between the following sets of literal constants:
解释下列字面值常量的不同之处。
  (a) 'a',L 'a',"a",L"a"
  (b) 10, 10u, 10L, 10uL, 012, 0xC
  (c) 3.14, 3.14f, 3.14L


Exercise 2.8:Determine the type of each of these literal constants:
确定下列字面值常量的类型:
      (a) -10 (b) -10u (c) -10. (d) -10e-2


Exercise 2.9:Which, if any, of the following are illegal?
下列哪些(如果有)是非法的?
      (a) "Who goes with F\145rgus?\012"
      (b) 3.14e1L          (c) "two" L"some"
      (d) 1024f            (e) 3.14UL
      (f) "multiple line
           comment"


Exercise 2.10:Using escape sequences, write a program to print 2M followed by a newline. Modify the program to print 2, then a tab, then an M, followed by a newline.
使用转义字符编写一段程序,输出 2M,然后换行。修改程序,输出 2,跟着一个制表符,然后是 M,最后是换行符。

 
      

2.3. Variables
2.3. 变量
Imagine that we are given the problem of computing 2 to the power of 10. Our first attempt might be something like
如果要计算 2 的 10 次方,我们首先想到的可能是:
      #include <iostream>
      int main()
      {
          // a first, not very good, solution
          std::cout << "2 raised to the power of 10: ";
          std::cout << 2*2*2*2*2*2*2*2*2*2;
          std::cout << std::endl;
          return 0;
      }

This program solves the problem, although we might double- or triple-check to make sure that exactly 10 literal instances of 2 are being multiplied. Otherwise, we're satisfied. Our program correctly generates the answer 1,024.
这个程序确实解决了问题,尽管我们可能要一而再、再而三地检查确保恰好有 10 个字面值常量 2 相乘。这个程序产生正确的答案 1024。
We're next asked to compute 2 raised to the power of 17 and then to the power of 23. Changing our program each time is a nuisance. Worse, it proves to be remarkably error-prone. Too often, the modified program produces an answer with one too few or too many instances of 2.
接下来要计算 2 的 17 次方,然后是 23 次方。而每次都要改变程序是很麻烦的事。更糟的是,这样做还容易引起错误。修改后的程序常常会产生多乘或少乘 2 的结果。
An alternative to the explicit brute force power-of-2 computation is twofold:
替代这种蛮力型计算的方法包括两部分内容:
Use named objects to perform and print each computation.
使用已命名对象执行运算并输出每次计算。
Use flow-of-control constructs to provide for the repeated execution of a sequence of program statements while a condition is true.
使用控制流结构,当某个条件为真时重复执行一系列程序语句。
Here, then, is an alternative way to compute 2 raised to the power of 10:
以下是计算 2 的 10 次方的替代方法:
      #include <iostream>
      int main()
      {
          // local objects of type int
          int value = 2;
          int pow = 10;
          int result = 1;
          // repeat calculation of result until cnt is equal to pow
          for (int cnt = 0; cnt != pow; ++cnt)
              result *= value;   // result = result * value;
          std::cout << value
                    << " raised to the power of "
                    << pow << ": \t"
                    << result << std::endl;
          return 0;
      }

value, pow, result, and cnt are variables that allow for the storage, modification, and retrieval of values. The for loop allows for the repeated execution of our calculation until it's been executed pow times.
value、pow、result 和 cnt 都是变量,可以对数值进行存储、修改和查询。for 循环使得计算过程重复执行 pow 次。
Exercises Section 2.3
Exercise 2.11:Write a program that prompts the user to input two numbers, the base and exponent. Print the result of raising the base to the power of the exponent.
编写程序,要求用户输入两个数——底数(base)和指数(exponent),输出底数的指数次方的结果。

Key Concept: Strong Static Typing
关键概念:强静态类型
C++ is a statically typed language, which means that types are checked at compile time. The process by which types are checked is referred to as type-checking.
C++ 是一门静态类型语言,在编译时会作类型检查。
In most languages, the type of an object constrains the operations that the object can perform. If the type does not support a given operation, then an object of that type cannot perform that operation.
在大多数语言中,对象的类型限制了对象可以执行的操作。如果某种类型不支持某种操作,那么这种类型的对象也就不能执行该操作。
In C++, whether an operation is legal or not is checked at compile time. When we write an expression, the compiler checks that the objects used in the expression are used in ways that are defined by the type of the objects. If not, the compiler generates an error message; an executable file is not produced.
在 C++ 中,操作是否合法是在编译时检查的。当编写表达式时,编译器检查表达式中的对象是否按该对象的类型定义的使用方式使用。如果不是的话,那么编译器会提示错误,而不产生可执行文件。
As our programs, and the types we use, get more complicated, we'll see that static type checking helps find bugs in our programs earlier. A consequence of static checking is that the type of every entity used in our programs must be known to the compiler. Hence, we must define the type of a variable before we can use that variable in our programs.
随着程序和使用的类型变得越来越复杂,我们将看到静态类型检查能帮助我们更早地发现错误。静态类型检查使得编译器必须能识别程序中的每个实体的类型。因此,程序中使用变量前必须先定义变量的类型


2.3.1. What Is a Variable?
2.3.1. 什么是变量
A variable provides us with named storage that our programs can manipulate. Each variable in C++ has a specific type, which determines the size and layout of the variable's memory; the range of values that can be stored within that memory; and the set of operations that can be applied to the variable. C++ programmers tend to refer to variables as "variables" or as "objects" interchangeably.
变量提供了程序可以操作的有名字的存储区。C++ 中的每一个变量都有特定的类型,该类型决定了变量的内存大小和布局、能够存储于该内存中的值的取值范围以及可应用在该变量上的操作集。C++ 程序员常常把变量称为“变量”或“对象(object)”。
Lvalues and Rvalues
左值和右值
We'll have more to say about expressions in Chapter 5, but for now it is useful to know that there are two kinds of expressions in C++:
我们在第五章再详细探讨表达式,现在先介绍 C++ 的两种表达式:
lvalue (pronounced "ell-value"): An expression that is an lvalue may appear as either the left-hand or right-hand side of an assignment.
左值(发音为 ell-value):左值可以出现在赋值语句的左边或右边。
rvalue (pronounced "are-value"): An expression that is an rvalue may appear on the right- but not left-hand side of an assignment.
右值(发音为 are-value):右值只能出现在赋值的右边,不能出现在赋值语句的左边。
Variables are lvalues and so may appear on the left-hand side of an assignment. Numeric literals are rvalues and so may not be assigned. Given the variables:
变量是左值,因此可以出现在赋值语句的左边。数字字面值是右值,因此不能被赋值。给定以下变量:
      int units_sold = 0;
      double sales_price = 0, total_revenue = 0;



it is a compile-time error to write either of the following:
下列两条语句都会产生编译错误:
      // error: arithmetic expression is not an lvalue
      units_sold * sales_price = total_revenue;
      // error: literal constant is not an lvalue
      0 = 1;



Some operators, such as assignment, require that one of their operands be an lvalue. As a result, lvalues can be used in more contexts than can rvalues. The context in which an lvalue appears determines how it is used. For example, in the expression
有些操作符,比如赋值,要求其中的一个操作数必须是左值。结果,可以使用左值的上下文比右值更广。左值出现的上下文决定了左值是如何使用的。例如,表达式
      units_sold = units_sold + 1;

the variable units_sold is used as the operand to two different operators. The + operator cares only about the values of its operands. The value of a variable is the value currently stored in the memory associated with that variable. The effect of the addition is to fetch that value and add one to it.
中,units_sold 变量被用作两种不同操作符的操作数。+ 操作符仅关心其操作数的值。变量的值是当前存储在和该变量相关联的内存中的值。加法操作符的作用是取得变量的值并加 1。
The variable units_sold is also used as the left-hand side of the = operator. The = operator reads its right-hand side and writes to its left-hand side. In this expression, the result of the addition is stored in the storage associated with units_sold; the previous value in units_sold is overwritten.
变量 units_sold 也被用作 = 操作符的左操作数。= 操作符读取右操作数并写到左操作数。在这个表达式中,加法运算的结果被保存到与 units_sold 相关联的存储单元中,而 units_sold 之前的值则被覆盖。
In the course of the text, we'll see a number of situations in which the use of an rvalue or lvalue impacts the behavior and/or the performance of our programsin particular when passing and returning values from a function.
在本书中,我们将看到在许多情形中左值或右值的使用影响程序的操作和/或性能——特别是在向函数传递值或从函数中返回值的时候。

Exercises Section 2.3.1
Exercise 2.12:Distinguish between an lvalue and an rvalue; show examples of each.
区分左值和右值,并举例说明。
Exercise 2.13:Name one case where an lvalue is required.
举出一个需要左值的例子。

Terminology: What Is an object?
术语:什么是对象?
C++ programmers tend to be cavalier in their use of the term object. Most generally, an object is a region of memory that has a type. More specifically, evaluating an expression that is an lvalue yields an object.
C++ 程序员经常随意地使用术语对象。一般而言,对象就是内存中具有类型的区域。说得更具体一些,计算左值表达式就会产生对象。
Strictly speaking, some might reserve the term object to describe only variables or values of class types. Others might distinguish between named and unnamed objects, always referring to variables when discussing named objects. Still others distinguish between objects and values, using the term object for data that can be changed by the program and using the term value for those that are read-only.
严格地说,有些人只把术语对象用于描述变量或类类型的值。有些人还区别有名字的对象和没名字的对象,当谈到有名字的对象时一般指变量。还有一些人区分对象和值,用术语对象描述可被程序改变的数据,用术语值描述只读数据。
In this book, we'll follow the more colloquial usage that an object is a region of memory that has a type. We will freely use object to refer to most of the data manipulated by our programs regardless of whether those data have built-in or class type, are named or unnamed, or are data that can be read or written.
在本书中,我们遵循更为通用的用法,即对象是内存中具有类型的区域。我们可以自由地使用对象描述程序中可操作的大部分数据,而不管这些数据是内置类型还是类类型,是有名字的还是没名字的,是可读的还是可写的。

2.3.2. The Name of a Variable
2.3.2. 变量名
The name of a variable, its identifier, can be composed of letters, digits, and the underscore character. It must begin with either a letter or an underscore. Upper- and lowercase letters are distinct: Identifiers in C++ are case-sensitive. The following defines four distinct identifiers:
变量名,即变量的标识符,可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++ 中的标识符都是大小写敏感的。下面定义了 4 个不同的标识符:
      // declares four different int variables
      int somename, someName, SomeName, SOMENAME;



There is no language-imposed limit on the permissible length of a name, but out of consideration for others that will read and/or modify our code, it should not be too long.
语言本身并没有限制变量名的长度,但考虑到将会阅读和/或修改我们的代码的其他人,变量名不应太长。

For example,
例如:
      gosh_this_is_an_impossibly_long_name_to_type

is a really bad identifier name.
就是一个糟糕的标识符名。
C++ Keywords
C++ 关键字
C++ reserves a set of words for use within the language as keywords. Keywords may not be used as program identifiers. Table 2.2 on the next page lists the complete set of C++ keywords.
C++ 保留了一组词用作该语言的关键字。关键字不能用作程序的标识符。表 2.2 列出了 C++ 所有的关键字。
Table 2.2. C++ Keywords
表 2.2. C++ 关键字
asmdoifreturntry
autodoubleinlineshorttypedef
booldynamic_castintsignedtypeid
breakelselongsizeoftypename
caseenummutablestaticunion
catchexplicitnamespacestatic_castunsigned
charexportnewstructusing
classexternoperatorswitchvirtual
constfalseprivatetemplatevoid
const_castfloatprotectedthisvolatile
continueforpublicthrowwchar_t
defaultfriendregistertruewhile
deletegotoreinterpret_cast  


C++ also reserves a number of words that can be used as alternative names for various operators. These alternative names are provided to support character sets that do not support the standard set of C++ operator symbols. These names, listed in Table 2.3, also may not be used as identifiers:
C++ 还保留了一些词用作各种操作符的替代名。这些替代名用于支持某些不支持标准C++操作符号集的字符集。它们也不能用作标识符。表 2.3列出了这些替代名。
Table 2.3. C++ Operator Alternative Names
表 2.3. C++ 操作符替代名
andbitandcomplnot_eqor_eqxor_eq
and_eqbitornotorxor


In addition to the keywords, the standard also reserves a set of identifiers for use in the library. Identifiers cannot contain two consecutive underscores, nor can an identifier begin with an underscore followed immediately by an upper-case letter. Certain identifiersthose that are defined outside a functionmay not begin with an underscore.
除了关键字,C++ 标准还保留了一组标识符用于标准库。标识符不能包含两个连续的下划线,也不能以下划线开头后面紧跟一个大写字母。有些标识符(在函数外定义的标识符)不能以下划线开头。
Conventions for Variable Names
变量命名习惯
There are a number of generally accepted conventions for naming variables. Following these conventions can improve the readability of a program.
变量命名有许多被普遍接受的习惯,遵循这些习惯可以提高程序的可读性。
A variable name is normally written in lowercase letters. For example, one writes index, not Index or INDEX.
变量名一般用小写字母。例如,通常会写成 index,而不写成 Index 或 INDEX。
An identifier is given a mnemonic namethat is, a name that gives some indication of its use in a program, such as on_loan or salary.
标识符应使用能帮助记忆的名字,也就是说,能够提示其在程序中的用法的名字,如 on_loan 或 salary。
An identifier containing multiple words is written either with an underscore between each word or by capitalizing the first letter of each embedded word. For example, one generally writes student_loan or studentLoan, not studentloan.
包含多个词的标识符书写为在每个词之间添加一个下划线,或者每个内嵌的词的第一个字母都大写。例如通常会写成 student_loan 或 studentLoan,而不写成 studentloan。
The most important aspect of a naming convention is that it be applied consistently.
命名习惯最重要的是保持一致。

Exercises Section 2.3.2
Exercise 2.14:Which, if any, of the following names are invalid? Correct each identified invalid name.
下面哪些(如果有)名字是非法的?更正每个非法的标识符名字。
      (a) int double = 3.14159;        (b) char _;
      (c) bool catch-22;               (d) char 1_or_2 ='1';
      (e) float Float = 3.14f;




2.3.3. Defining Objects
2.3.3. 定义对象
The following statements define five variables:
下列语句定义了 5 个变量:
      int units_sold;
      double sales_price, avg_price;
      std::string title;
      Sales_item curr_book;

Each definition starts with a type specifier, followed by a comma-separated list of one or more names. A semicolon terminates the definition. The type specifier names the type associated with the object: int, double, std::string, and Sales_item are all names of types. The types int and double are built-in types, std::string is a type defined by the library, and Sales_item is a type that we used in Section 1.5 (p. 20)and will define in subsequent chapters. The type determines the amount of storage that is allocated for the variable and the set of operations that can be performed on it.
每个定义都是以类型说明符开始,后面紧跟着以逗号分开的含有一个或多个说明符的列表。分号结束定义。类型说明符指定与对象相关联的类型:int 、double、std::string 和 Sales_item 都是类型名。其中 int 和 double 是内置类型,std::string 是标准库定义的类型,Sales_item 是我们在第 1.5 节使用的类型,将会在后面章节定义。类型决定了分配给变量的存储空间的大小和可以在其上执行的操作。
Multiple variables may be defined in a single statement:
多个变量可以定义在同一条语句中:
      double salary, wage;    // defines two variables of type double
      int month,
          day, year;          // defines three variables of type int
      std::string address;    // defines one variable of type std::string



Initialization
初始化
A definition specifies a variable's type and identifier. A definition may also provide an initial value for the object. An object defined with a specified first value is spoken of as initialized. C++ supports two forms of variable initialization: copy-initialization and direct-initialization. The copy-initialization syntax uses the equal (=) symbol; direct-initialization places the initializer in parentheses:
变量定义指定了变量的类型和标识符,也可以为对象提供初始值。定义时指定了初始值的对象被称为是已初始化的。C++ 支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中:
      int ival(1024);     // direct-initialization
      int ival = 1024;    // copy-initialization



In both cases, ival is initialized to 1024.
这两种情形中,ival 都被初始化为 1024。
Although, at this point in the book, it may seem obscure to the reader, in C++ it is essential to understand that initialization is not assignment. Initialization happens when a variable is created and gives that variable its initial value. Assignment involves obliterating an object's current value and replacing that value with a new one.
虽然在本书到目前为止还没有清楚说明,但是在 C++ 中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

Many new C++ programmers are confused by the use of the = symbol to initialize a variable. It is tempting to think of initialization as a form of assignment. But initialization and assignment are different operations in C++. This concept is particularly confusing because in many other languages the distinction is irrelevant and can be ignored. Moreover, even in C++ the distinction rarely matters until one attempts to write fairly complex classes. Nonetheless, it is a crucial concept and one that we will reiterate throughout the text.
使用 = 来初始化变量使得许多 C++ 编程新手感到迷惑,他们很容易把初始化当成是赋值的一种形式。但是在 C++ 中初始化和赋值是两种不同的操作。这个概念特别容易误导人,因为在许多其他的语言中这两者的差别不过是枝节问题因而可以被忽略。即使在 C++ 中也只有在编写非常复杂的类时才会凸显这两者之间的区别。无论如何,这是一个关键的概念,也是我们将会在整本书中反复强调的概念。
There are subtle differences between copy- and direct-initialization when initializing objects of a class type. We won't completely explain these differences until Chapter 13. For now, it's worth knowing that the direct syntax is more flexible and can be slightly more efficient.
当初始化类类型对象时,复制初始化和直接初始化之间的差别是很微妙的。我们在第十三章再详细解释它们之间的差别。现在我们只需知道,直接初始化语法更灵活且效率更高。

Using Multiple Initializers
使用多个初始化式
When we initialize an object of a built-in type, there is only one way to do so: We supply a value, and that value is copied into the newly defined object. For built-in types, there is little difference between the direct and the copy forms of initialization.
初始化内置类型的对象只有一种方法:提供一个值,并且把这个值复制到新定义的对象中。对内置类型来说,复制初始化和直接初始化几乎没有差别。
For objects of a class type, there are initializations that can be done only using direct-initialization. To understand why, we need to know a bit about how classes control initialization.
对类类型的对象来说,有些初始化仅能用直接初始化完成。要想理解其中缘由,需要初步了解类是如何控制初始化的。
Each class may define one or more special member functions (Section 1.5.2, p. 24) that say how we can initialize variables of the class type. The member functions that define how initialization works are known as constructors. Like any function, a constructor can take multiple arguments. A class may define several constructors, each of which must take a different number or type of arguments.
每个类都可能会定义一个或几个特殊的成员函数(第 1.5.2 节)来告诉我们如何初始化类类型的变量。定义如何进行初始化的成员函数称为构造函数。和其他函数一样,构造函数能接受多个参数。一个类可以定义几个构造函数,每个构造函数必须接受不同数目或者不同类型的参数。
As an example, we'll look a bit at the string class, which we'll cover in more detail in Chapter 3. The string type is defined by the library and holds character strings of varying sizes. To use strings, we must include the string header. Like the IO types, string is defined in the std namespace.
我们以 string 类为例(string 类将在第三章详细讨论)。string 类型在标准库中定义,用于存储不同长度的字符串。使用 string 时必须包含 string 头文件。和 IO 类型一样,string 定义在 std 命名空间中。
The string class defines several constructors, giving us various ways to initialize a string. One way we can initialize a string is as a copy of a character string literal:
string 类定义了几个构造函数,使得我们可以用不同的方式初始化 string 对象。其中一种初始化 string 对象的方式是作为字符串字面值的副本:
      #include <string>
      // alternative ways to initialize string from a character string literal
      std::string titleA = "C++ Primer, 4th Ed.";
      std::string titleB("C++ Primer, 4th Ed.");

In this case, either initialization form can be used. Both definitions create a string object whose initial value is a copy of the specified string literal.
本例中,两种初始化方式都可以使用。两种定义都创建了一个 string 对象,其初始值都是指定的字符串字面值的副本。
However, we can also initialize a string from a count and a character. Doing so creates a string containing the specified character repeated as many times as indicated by the count:
也可以通过一个计数器和一个字符初始化string对象。这样创建的对象包含重复多次的指定字符,重复次数由计数器指定:
      std::string all_nines(10, '9');   // all_nines= "9999999999"

In this case, the only way to initialize all_nines is by using the direct form of initialization. It is not possible to use copy-initialization with multiple initializers.
本例中,初始化 all_nines 的唯一方法是直接初始化。有多个初始化式时不能使用复制初始化。
Initializing Multiple Variables
初始化多个变量
When a definition defines two or more variables, each variable may have its own initializer. The name of an object becomes visible immediately, and so it is possible to initialize a subsequent variable to the value of one defined earlier in the same definition. Initialized and uninitialized variables may be defined in the same definition. Both forms of initialization syntax may be intermixed:
当一个定义中定义了两个以上变量的时候,每个变量都可能有自己的初始化式。 对象的名字立即变成可见,所以可以用同一个定义中前面已定义变量的值初始化后面的变量。已初始化变量和未初始化变量可以在同一个定义中定义。两种形式的初始化文法可以相互混合。
      #include <string>
      // ok: salary defined and initialized before it is used to initialize wage
      double salary = 9999.99,
            wage(salary + 0.01);
      // ok: mix of initialized and uninitialized
      int interval,
          month = 8, day = 7, year = 1955;
      // ok: both forms of initialization syntax used
      std::string title("C++ Primer, 4th Ed."),
                  publisher = "A-W";



An object can be initialized with an arbitrarily complex expression, including the return value of a function:
对象可以用任意复杂的表达式(包括函数的返回值)来初始化:
      double price = 109.99, discount = 0.16;
      double sale_price = apply_discount(price, discount);

In this example, apply_discount is a function that takes two values of type double and returns a value of type double. We pass the variables price and discount to that function and use its return value to initialize sale_price.
本例中,函数 apply_discount 接受两个 double 类型的值并返回一个 double 类型的值。将变量 price 和 discount 传递给函数,并且用它的返回值来初始化 sale_price。
Exercises Section 2.3.3
Exercise 2.15:What, if any, are the differences between the following definitions:
下面两个定义是否不同?有何不同?
      int month = 9, day = 7;

      int month = 09, day = 07;



If either definition contains an error, how might you correct the problem?
如果上述定义有错的话,那么应该怎样改正呢?
Exercise 2.16:Assuming calc is a function that returns a double, which, if any, of the following are illegal definitions? Correct any that are identified as illegal.
假设 calc 是一个返回 double 对象的函数。下面哪些是非法定义?改正所有的非法定义。
     (a) int car = 1024, auto = 2048;
     (b) int ival = ival;
     (c) std::cin >> int input_value;
     (d) double salary = wage = 9999.99;
     (e) double calc = calc();





2.3.4. Variable Initialization Rules
2.3.4. 变量初始化规则
When we define a variable without an initializer, the system sometimes initializes the variable for us. What value, if any, is supplied depends on the type of the variable and may depend on where it is defined.
当定义没有初始化式的变量时,系统有时候会帮我们初始化变量。这时,系统提供什么样的值取决于变量的类型,也取决于变量定义的位置。
Initialization of Variables of Built-in Type
内置类型变量的初始化
Whether a variable of built-in type is automatically initialized depends on where it is defined. Variables defined outside any function body are initialized to zero. Variables of built-in type defined inside the body of a function are uninitialized. Using an uninitialized variable for anything other than as the left-hand operand of an assignment is undefined. Bugs due to uninitialized variables can be hard to find. As we cautioned on page 42, you should never rely on undefined behavior.
内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。除了用作赋值操作符的左操作数,未初始化变量用作任何其他用途都是没有定义的。未初始化变量引起的错误难于发现。正如我们在第 2.2 节劝告的,永远不要依赖未定义行为。
Caution: Uninitialized Variables Cause Run-Time Problems
警告:未初始化的变量引起运行问题
Using an uninitialized object is a common program error, and one that is often difficult to uncover. The compiler is not required to detect a use of an uninitialized variable, although many will warn about at least some uses of uninitialized variables. However, no compiler can detect all uses of uninitialized variables.
使用未初始化的变量是常见的程序错误,通常也是难以发现的错误。虽然许多编译器都至少会提醒不要使用未初始化变量,但是编译器并未被要求去检测未初始化变量的使用。而且,没有一个编译器能检测出所有未初始化变量的使用。
Sometimes, we're lucky and using an uninitialized variable results in an immediate crash at run time. Once we track down the location of the crash, it is usually pretty easy to see that the variable was not properly initialized.
有时我们很幸运,使用未初始化的变量导致程序在运行时突然崩溃。一旦跟踪到程序崩溃的位置,就可以轻易地发现没有正确地初始化变量。
Other times, the program completes but produces erroneous results. Even worse, the results can appear correct when we run our program on one machine but fail on another. Adding code to the program in an unrelated location can cause what we thought was a correct program to suddenly start to produce incorrect results.
但有时,程序运行完毕却产生错误的结果。更糟糕的是,程序运行在一部机器上时能产生正确的结果,但在另外一部机器上却不能得到正确的结果。添加代码到程序的一些不相关的位置,会导致我们认为是正确的程序产生错误的结果。
The problem is that uninitialized variables actually do have a value. The compiler puts the variable somewhere in memory and treats whatever bit pattern was in that memory as the variable's initial state. When interpreted as an integral value, any bit pattern is a legitimate valuealthough the value is unlikely to be one that the programmer intended. Because the value is legal, using it is unlikely to lead to a crash. What it is likely to do is lead to incorrect execution and/or incorrect calculation.
问题出在未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态。当被解释成整型值时,任何位模式都是合法的值——虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃。可能的结果是导致程序错误执行和/或错误计算。

We recommend that every object of built-in type be initialized. It is not always necessary to initialize such variables, but it is easier and safer to do so until you can be certain it is safe to omit an initializer.
建议每个内置类型的对象都要初始化。虽然这样做并不总是必需的,但是会更加容易和安全,除非你确定忽略初始化式不会带来风险。

Initialization of Variables of Class Type
类类型变量的初始化
Each class defines how objects of its type can be initialized. Classes control object initialization by defining one or more constructors (Section 2.3.3, p. 49). As an example, we know that the string class provides at least two constructors. One of these constructors lets us initialize a string from a character string literal and another lets us initialize a string from a character and a count.
每个类都定义了该类型的对象可以怎样初始化。类通过定义一个或多个构造函数来控制类对象的初始化(第 2.3.3 节)。例如:我们知道 string 类至少提供了两个构造函数,其中一个允许我们通过字符串字面值初始化 string 对象,另外一个允许我们通过字符和计数器初始化 string 对象。
Each class may also define what happens if a variable of the type is defined but an initializer is not provided. A class does so by defining a special constructor, known as the default constructor. This constructor is called the default constructor because it is run "by default;" if there is no initializer, then this constructor is used. The default constructor is used regardless of where a variable is defined.
如果定义某个类的变量时没有提供初始化式,这个类也可以定义初始化时的操作。它是通过定义一个特殊的构造函数即默认构造函数来实现的。这个构造函数之所以被称作默认构造函数,是因为它是“默认”运行的。如果没有提供初始化式,那么就会使用默认构造函数。不管变量在哪里定义,默认构造函数都会被使用。
Most classes provide a default constructor. If the class has a default constructor, then we can define variables of that class without explicitly initializing them. For example, the string type defines its default constructor to initialize the string as an empty stringthat is, a string with no characters:
大多数类都提供了默认构造函数。如果类具有默认构造函数,那么就可以在定义该类的变量时不用显式地初始化变量。例如,string 类定义了默认构造函数来初始化 string 变量为空字符串,即没有字符的字符串:
      std::string empty;  // empty is the empty string; empty =""

Some class types do not have a default constructor. For these types, every definition must provide explicit initializer(s). It is not possible to define variables of such types without giving an initial value.
有些类类型没有默认构造函数。对于这些类型来说,每个定义都必须提供显式的初始化式。没有初始值是根本不可能定义这种类型的变量的。
Exercises Section 2.3.4
Exercise 2.17:What are the initial values, if any, of each of the following variables?
下列变量的初始值(如果有)是什么?
      std::string global_str;
      int global_int;
      int main()
      {
          int local_int;
          std::string local_str;
          // ...
          return 0;
      }





2.3.5. Declarations and Definitions
2.3.5. 声明和定义
As we'll see in Section 2.9 (p. 67), C++ programs typically are composed of many files. In order for multiple files to access the same variable, C++ distinguishes between declarations and definitions.
正如将在第 2.9 节所看到的那样,C++ 程序通常由许多文件组成。为了让多个文件访问相同的变量,C++ 区分了声明和定义。
A definition of a variable allocates storage for the variable and may also specify an initial value for the variable. There must be one and only one definition of a variable in a program.
变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。
A declaration makes known the type and name of the variable to the program. A definition is also a declaration: When we define a variable, we declare its name and type. We can declare a name without defining it by using the extern keyword. A declaration that is not also a definition consists of the object's name and its type preceded by the keyword extern:
声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。可以通过使用extern关键字声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern:
      extern int i;   // declares but does not define i
      int i;          //  declares and defines i



An extern declaration is not a definition and does not allocate storage. In effect, it claims that a definition of the variable exists elsewhere in the program. A variable can be declared multiple times in a program, but it must be defined only once.
extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
A declaration may have an initializer only if it is also a definition because only a definition allocates storage. The initializer must have storage to initialize. If an initializer is present, the declaration is treated as a definition even if the declaration is labeled extern:
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:
      extern double pi = 3.1416; // definition

Despite the use of extern, this statement defines pi. Storage is allocated and initialized. An extern declaration may include an initializer only if it appears outside a function.
虽然使用了 extern ,但是这条语句还是定义了 pi,分配并初始化了存储空间。只有当 extern 声明位于函数外部时,才可以含有初始化式。
Because an extern that is initialized is treated as a definition, any subseqent definition of that variable is an error:
因为已初始化的 extern 声明被当作是定义,所以该变量任何随后的定义都是错误的:
      extern double pi = 3.1416; // definition
      double pi;                 // error: redefinition of pi

Similarly, a subsequent extern declaration that has an initializer is also an error:
同样,随后的含有初始化式的 extern 声明也是错误的:
      extern double pi = 3.1416; // definition
      extern double pi;          // ok: declaration not definition
      extern double pi = 3.1416; // error: redefinition of pi

The distinction between a declaration and a definition may seem pedantic but in fact is quite important.
声明和定义之间的区别可能看起来微不足道,但事实上却是举足轻重的。
In C++ a variable must be defined exactly once and must be defined or declared before it is used.
在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。

Any variable that is used in more than one file requires declarations that are separate from the variable's definition. In such cases, one file will contain the definition for the variable. Other files that use that same variable will contain declarations forbut not a definition ofthat same variable.
任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
Exercises Section 2.3.5
Exercise 2.18:Explain the meaning of each of these instances of name:
解释下列例子中 name 的意义
      extern std::string name;
      std::string name("exercise 3.5a");
      extern std::string name("exercise 3.5a");




2.3.6. Scope of a Name
2.3.6. 名字的作用域
Every name in a C++ program must refer to a unique entity (such as a variable, function, type, etc.). Despite this requirement, names can be used more than once in a program: A name can be reused as long as it is used in different contexts, from which the different meanings of the name can be distinguished. The context used to distinguish the meanings of names is a scope. A scope is a region of the program. A name can refer to different entities in different scopes.
C++程序中,每个名字都与唯一的实体(比如变量、函数和类型等)相关联。尽管有这样的要求,还是可以在程序中多次使用同一个名字,只要它用在不同的上下文中,且通过这些上下文可以区分该名字的不同意义。用来区分名字的不同意义的上下文称为作用域。作用域是程序的一段区域。一个名称可以和不同作用域中的不同实体相关联。
Most scopes in C++ are delimited by curly braces. Generally, names are visible from their point of declaration until the end the scope in which the declaration appears. As an example, consider this program, which we first encountered in Section 1.4.2 (p. 14):
C++ 语言中,大多数作用域是用花括号来界定的。一般来说,名字从其声明点开始直到其声明所在的作用域结束处都是可见的。例如,思考第 1.4.2 节中的程序:
      #include <iostream>
      int main()
      {
          int sum = 0;
          //  sum values from 1 up to 10 inclusive
          for (int val = 1; val <= 10; ++val)
              sum += val;   // equivalent to sum = sum + val

          std::cout << "Sum of 1 to 10 inclusive is "
                    << sum << std::endl;
          return 0;
      }

This program defines three names and uses two names from the standard library. It defines a function named main and two variables named sum and val. The name main is defined outside any curly braces and is visible throughout the program. Names defined outside any function have global scope; they are accessible from anywhere in the program. The name sum is defined within the scope of the main function. It is accessible throughout the main function but not outside of it. The variable sum has local scope. The name val is more interesting. It is defined in the scope of the for statement (Section 1.4.2, p. 14). It can be used in that statement but not elsewhere in main. It has statement scope.
这个程序定义了三个名字,使用了两个标准库的名字。程序定义了一个名为 main 的函数,以及两个名为 sum 和 val 的变量。名字 main 定义在所有花括号之外,在整个程序都可见。定义在所有函数外部的名字具有全局作用域,可以在程序中的任何地方访问。名字 sum 定义在 main 函数的作用域中,在整个 main 函数中都可以访问,但在 main 函数外则不能。变量 sum 有局部作用域。名字 val 更有意思,它定义在 for 语句的作用域中,只能在 for 语句中使用,而不能用在 main 函数的其他地方。它具有语句作用域。
Scopes in C++ Nest
C++ 中作用域可嵌套
Names defined in the global scope can be used in a local scope; global names and those defined local to a function can be used inside a statement scope, and so on. Names can also be redefined in an inner scope. Understanding what entity a name refers to requires unwinding the scopes in which the names are defined:
定义在全局作用域中的名字可以在局部作用域中使用,定义在全局作用域中的名字和定义在函数的局部作用域中的名字可以在语句作用域中使用,等等。名字还可以在内部作用域中重新定义。理解和名字相关联的实体需要明白定义名字的作用域:
      #include <iostream>
      #include <string>
      /*  Program for illustration purposes only:
       *  It is bad style for a function to use a global variable and then
       *  define a local variable with the same name
       */
      std::string s1 = "hello";  // s1 has global scope
      int main()
      {
          std::string s2 = "world"; // s2 has local scope
          // uses global s1; prints "hello world"
          std::cout << s1 << " " << s2 << std::endl;
          int s1 = 42; // s1 is local and hides global s1
          // uses local s1;prints "42 world"
          std::cout << s1 << " " << s2 << std::endl;
          return 0;
      }

This program defines three variables: a global string named s1, a local string named s2, and a local int named s1. The definition of the local s1 hides the global s1.
这个程序中定义了三个变量:string 类型的全局变量 s1、string 类型的局部变量 s2 和 int 类型的局部变量 s1。局部变量 s1 的定义屏蔽了全局变量 s1。
Variables are visible from their point of declaration. Thus, the local definition of s1 is not visible when the first output is performed. The name s1 in that output expression refers to the global s1. The output printed is hello world. The second statement that does output follows the local definition of s1. The local s1 is now in scope. The second output uses the local rather than the global s1. It writes 42 world.
变量从声明开始才可见,因此执行第一次输出时局部变量 s1 不可见,输出表达式中的 s1 是全局变量 s1,输出“hello world”。第二条输出语句跟在 s1 的局部定义后,现在局部变量 s1 在作用域中。第二条输出语句使用的是局部变量 s1 而不是全局变量 s1,输出“42 world”。
Programs such as the preceeding are likely to be confusing. It is almost always a bad idea to define a local variable with the same name as a global variable that the function uses or might use. It is much better to use a distinct name for the local.
像上面这样的程序很可能让人大惑不解。在函数内定义一个与函数可能会用到的全局变量同名的局部变量总是不好的。局部变量最好使用不同的名字。

We'll have more to say about local and global scope in Chapter 7 and about statement scope in Chapter 6. C++ has two other levels of scope: class scope, which we'll cover in Chapter 12 and namespace scope, which we'll see in Section 17.2.
第七章将详细讨论局部作用域和全局作用域,第六章将讨论语句作用域。C++ 还有另外两种不同级别的作用域:类作用域(第十二章将介绍)和命名空间作用域(第 17.2 节将介绍)。
2.3.7. Define Variables Where They Are Used
2.3.7. 在变量使用处定义变量
In general, variable definitions or declarations can be placed anywhere within the program that a statement is allowed. A variable must be declared or defined before it is used.
一般来说,变量的定义或声明可以放在程序中能摆放语句的任何位置。变量在使用前必须先声明或定义。
It is usually a good idea to define an object near the point at which the object is first used.
通常把一个对象定义在它首次使用的地方是一个很好的办法。

Defining an object where the object is first used improves readability. The reader does not have to go back to the beginning of a section of code to find the definition of a particular variable. Moreover, it is often easier to give the variable a useful initial value when the variable is defined close to where it is first used.
在对象第一次被使用的地方定义对象可以提高程序的可读性。读者不需要返回到代码段的开始位置去寻找某一特殊变量的定义,而且,在此处定义变量,更容易给它赋以有意义的初始值。
One constraint on placing declarations is that variables are accessible from the point of their definition until the end of the enclosing block. A variable must be defined in or before the outermost scope in which the variable will be used.
放置声明的一个约束是,变量只在从其定义处开始到该声明所在的作用域的结束处才可以访问。必须在使用该变量的最外层作用域里面或之前定义变量。
Exercises Section 2.3.6
Exercise 2.19:What is the value of j in the following program?
下列程序中 j 的值是多少?
      int i = 42;
      int main()
      {
          int i = 100;
          int j = i;
          // ...
      }


Exercise 2.20:Given the following program fragment, what values are printed?
下列程序段将会输出什么?
      int i = 100, sum = 0;
      for (int i = 0; i != 10; ++i)
           sum += i;
      std::cout << i << " " << sum << std::endl;


Exercise 2.21:Is the following program legal?
下列程序合法吗?
      int sum = 0;
      for (int i = 0; i != 10; ++i)
          sum += i;
      std::cout << "Sum from 0 to " << i
                << " is " << sum << std::endl;




 
      

2.4. const Qualifier
2.4. const 限定符
There are two problems with the following for loop, both concerning the use of 512 as an upper bound.
下列 for 循环语句有两个问题,两个都和使用 512 作为循环上界有关。
      for (int index = 0; index != 512; ++index) {
          // ...
      }



The first problem is readability. What does it mean to compare index with 512? What is the loop doingthat is, what makes 512 matter? (In this example, 512 is known as a magic number, one whose significance is not evident within the context of its use. It is as if the number had been plucked by magic from thin air.)
第一个问题是程序的可读性。比较 index 与 512 有什么意思呢?循环在做什么呢?也就是说 512 作用何在?[本例中,512 被称为魔数(magic number),它的意义在上下文中没有体现出来。好像这个数是魔术般地从空中出现的。
The second problem is maintainability. Imagine that we have a large program in which the number 512 occurs 100 times. Let's further assume that 80 of these references use 512 to indicate the size of a particular buffer but the other 20 use 512 for different purposes. Now we discover that we need to increase the buffer size to 1024. To make this change, we must examine every one of the places that the number 512 appears. We must determinecorrectly in every casewhich of those uses of 512 refer to the buffer size and which do not. Getting even one instance wrong breaks the program and requires us to go back and reexamine each use.
第二个问题是程序的可维护性。假设这个程序非常庞大,512 出现了 100 次。进一步假设在这 100 次中,有 80 次是表示某一特殊缓冲区的大小,剩余 20 次用于其他目的。现在我们需要把缓冲区的大小增大到 1024。要实现这一改变,必须检查每个 512 出现的位置。我们必须确定(在每种情况下都准确地确定)哪些 512 表示缓冲区大小,而哪些不是。改错一个都会使程序崩溃,又得回过头来重新检查。
The solution to both problems is to use an object initialized to 512:
解决这两个问题的方法是使用一个初始化为 512 的对象:
      int bufSize = 512;    // input buffer size
      for (int index = 0; index != bufSize; ++index) {
          // ...
      }



By choosing a mnemonic name, such as bufSize, we make the program more readable. The test is now against the object rather than the literal constant:
通过使用好记的名字如 bufSize,增强了程序的可读性。现在是对对象 bufSize 测试而不是字面值常量 512 测试:
      index != bufSize



If we need to change this size, the 80 occurrences no longer need to be found and corrected. Rather, only the one line that initializes bufSize requires change. Not only does this approach require significantly less work, but also the likelihood of making a mistake is greatly reduced.
现在如果想要改变缓冲区大小,就不再需要查找和改正 80 次出现的地方。而只有初始化 bufSize 那行需要修改。这种方法不但明显减少了工作量,而且还大大减少了出错的可能性。
Defining a const Object
定义 const 对象
There is still a serious problem with defining a variable to represent a constant value. The problem is that bufSize is modifiable. It is possible for bufSize to be changedaccidentally or otherwise. The const type qualifier provides a solution: It transforms an object into a constant.
定义一个变量代表某一常数的方法仍然有一个严重的问题。即 bufSize 是可以被修改的。bufSize 可能被有意或无意地修改。const 限定符提供了一个解决办法,它把一个对象转换成一个常量。
      const int bufSize = 512;     // input buffer size



defines bufSize to be a constant initialized with the value 512. The variable bufSize is still an lvalue (Section 2.3.1, p. 45), but now the lvalue is unmodifiable. Any attempt to write to bufSize results in a compile-time error.
定义 bufSize 为常量并初始化为 512。变量 bufSize 仍然是一个左值(第 2.3.1 节),但是现在这个左值是不可修改的。任何修改 bufSize 的尝试都会导致编译错误:
      bufSize = 0; // error: attempt to write to const object



Because we cannot subsequently change the value of an object declared to be const, we must initialize it when it is defined:
因为常量在定义后就不能被修改,所以定义时必须初始化:


      const std::string hi = "hello!"; // ok: initialized
      const int i, j = 0;  // error: i is uninitialized const





const Objects Are Local to a File By Default
const 对象默认为文件的局部变量
When we define a nonconst variable at global scope (Section 2.3.6, p. 54), it is accessible throughout the program. We can define a nonconst variable in one file andassuming an appropriate declaration has been madecan use that variable in another file:
在全局作用域(第 2.3.6 节)里定义非 const 变量时,它在整个程序中都可以访问。我们可以把一个非 const 变更定义在一个文件中,假设已经做了合适的声明,就可在另外的文件中使用这个变量:
      // file_1.cc
      int counter;  // definition
      // file_2.cc
      extern int counter; // uses counter from file_1
      ++counter;          // increments counter defined in file_1



Unlike other variables, unless otherwise specified, const variables declared at global scope are local to the file in which the object is defined. The variable exists in that file only and cannot be accessed by other files.
与其他变量不同,除非特别说明,在全局作用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。
We can make a const object accessible throughout the program by specifying that it is extern:
通过指定 const 变更为 extern,就可以在整个程序中访问 const 对象:
      // file_1.cc
      // defines and initializes a const that is accessible to other files
      extern const int bufSize = fcn();
      // file_2.cc
      extern const int bufSize; // uses bufSize from file_1
      // uses bufSize defined in file_1
      for (int index = 0; index != bufSize; ++index)
            // ...



In this program, file_1.cc defines and initializes bufSize to the result returned from calling the function named fcn. The definition of bufSize is extern, meaning that bufSize can be used in other files. The declaration in file_2.cc is also made extern. In this case, the extern signifies that bufSize is a declaration and hence no initializer is provided.
本程序中,file_1.cc 通过函数 fcn 的返回值来定义和初始化 bufSize。而 bufSize 定义为 extern,也就意味着 bufSize 可以在其他的文件中使用。file_2.cc 中 extern 的声明同样是 extern;这种情况下,extern 标志着 bufSize 是一个声明,所以没有初始化式。
We'll see in Section 2.9.1 (p. 69) why const objects are made local to a file.
我们将会在第 2.9.1 节看到为何 const 对象局部于文件创建。
Nonconst variables are extern by default. To make a const variable accessible to other files we must explicitly specify that it is extern.
非 const 变量默认为 extern。要使 const 变量能够在其他的文件中访问,必须地指定它为 extern。



Exercises Section 2.4
Exercise 2.22:The following program fragment, while legal, is an example of poor style. What problem(s) does it contain? How would you improve it?
下种段虽然合法,但是风格很糟糕。有什么问题呢?怎样改善?
      for (int i = 0; i < 100; ++i)
          // process i



Exercise 2.23:Which of the following are legal? For those usages that are illegal, explain why.
下列哪些语句合法?对于那些不合法的,请解释为什么不合法。
      (a) const int buf;
      (b) int cnt = 0;
          const int sz = cnt;
      (c) cnt++; sz++;






      

2.5. References
2.5. 引用
A reference serves as an alternative name for an object. In real-world programs, references are primarily used as formal parameters to functions. We'll have more to say about reference parameters in Section 7.2.2 (p. 232). In this section we introduce and illustrate the use of references as independent objects.
引用就是对象的另一个名字。在实际程序中,引用主要用作函数的形式参数。我们将在第 7.2.2 节 再详细介绍引用参数。在这一节,我们用独立的对象来介绍并举例说明引用的用法。
A reference is a compound type that is defined by preceding a variable name by the & symbol. A compound type is a type that is defined in terms of another type. In the case of references, each reference type "refers to" some other type. We cannot define a reference to a reference type, but can make a reference to any other data type.
引用是一种复合类型,通过在变量名前添加“&”符号来定义。复合类型是指用其他类型定义的类型。在引用的情况下,每一种引用类型都“关联到”某一其他类型。不能定义引用类型的引用,但可以定义任何其他类型的引用。
A reference must be initialized using an object of the same type as the reference:
引用必须用与该引用同类型的对象初始化:
      int ival = 1024;
      int &refVal = ival; // ok: refVal refers to ival
      int &refVal2;       // error: a reference must be initialized
      int &refVal3 = 10;  // error: initializer must be an object

A Reference Is an Alias
引用是别名
Because a reference is just another name for the object to which it is bound, all operations on a reference are actually operations on the underlying object to which the reference is bound:
因为引用只是它绑定的对象的另一名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上:
      refVal += 2;



adds 2 to ival, the object referred to by refVal. Similarly,
将 refVal 指向的对象 ival 加 2。类似地,
      int ii = refVal;

assigns to ii the value currently associated with ival.
把和 ival 相关联的值赋给 ii。
When a reference is initialized, it remains bound to that object as long as the reference exists. There is no way to rebind a reference to a different object.
当引用初始化后,只要该引用存在,它就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。

The important concept to understand is that a reference is just another name for an object. Effectively, we can access ival either through its actual name or through its alias, refVal. Assignment is just another operation, so that when we write
要理解的重要概念是引用只是对象的另一名字。事实上,我们可以通过 ival 的原名访问 ival,也可以通过它的别名 refVal 访问。赋值只是另外一种操作,因此我们编写
      refVal = 5;

the effect is to change the value of ival to 5. A consequence of this rule is that you must initialize a reference when you define it; initialization is the only way to say to which object a reference refers.
的效果是把 ival 的值修改为5。这一规则的结果是必须在定义引用时进行初始化。初始化是指明引用指向哪个对象的唯一方法。
Defining Multiple References
定义多个引用
We can define multiple references in a single type definition. Each identifier that is a reference must be preceded by the & symbol:
可以在一个类型定义行中定义多个引用。必须在每个引用标识符前添加“&”符号:
      int i = 1024, i2 = 2048;
      int &r = i, r2 = i2;      // r is a reference, r2 is an int
      int i3 = 1024, &ri = i3;  // defines one object, and one reference
      int &r3 = i3, &r4 = i2;   // defines two references

const References
const 引用
A const reference is a reference that may refer to a const object:
const 引用是指向 const 对象的引用:
      const int ival = 1024;
      const int &refVal = ival;      // ok: both reference and object are const
      int &ref2 = ival;              // error: non const reference to a const object

We can read from but not write to refVal. Thus, any assignment to refVal is illegal. This restriction should make sense: We cannot assign directly to ival and so it should not be possible to use refVal to change ival.
可以读取但不能修改 refVal ,因此,任何对 refVal 的赋值都是不合法的。这个限制有其意义:不能直接对 ival 赋值,因此不能通过使用 refVal 来修改 ival。
For the same reason, the initialization of ref2 by ival is an error: ref2 is a plain, nonconst reference and so could be used to change the value of the object to which ref2 refers. Assigning to ival through ref2 would result in changing the value of a const object. To prevent such changes, it is illegal to bind a plain reference to a const object.
同理,用 ival 初始化 ref2 也是不合法的:ref2 是普通的非 const 引用,因此可以用来修改 ref2 指向的对象的值。通过 ref2 对 ival 赋值会导致修改 const 对象的值。为阻止这样的修改,需要规定将普通的引用绑定到 const 对象是不合法的。
Terminology: const Reference is a Reference to const
术语:const 引用是指向 const 的引用
C++ programmers tend to be cavalier in their use of the term const reference. Strictly speaking, what is meant by "const reference" is "reference to const." Similarly, programmers use the term "nonconst reference" when speaking of reference to a nonconst type. This usage is so common that we will follow it in this book as well.
C++ 程序员常常随意地使用术语 const 引用。严格来说,“const 引用”的意思是“指向 const 对象的引用”。类似地,程序员使用术语“非 const 引用”表示指向非 const 类型的引用。这种用法非常普遍,我们在本书中也遵循这种用法。


A const reference can be initialized to an object of a different type or to an rvalue (Section 2.3.1, p. 45), such as a literal constant:
const 引用可以初始化为不同类型的对象或者初始化为右值(第 2.3.1 节),如字面值常量:
      int i = 42;
      //  legal for const references only
      const int &r = 42;
      const int &r2 = r + i;



The same initializations are not legal for nonconst references. Rather, they result in compile-time errors. The reason is subtle and warrants an explanation.
同样的初始化对于非 const 引用却是不合法的,而且会导致编译时错误。其原因非常微妙,值得解释一下。
This behavior is easiest to understand when we look at what happens when we bind a reference to an object of a different type. If we write
观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。假如我们编写
      double dval = 3.14;
      const int &ri = dval;

the compiler transforms this code into something like this:
编译器会把这些代码转换成如以下形式的编码:
      int temp = dval;          // create temporary int from the double
      const int &ri = temp;   // bind ri to that temporary

If ri were not const, then we could assign a new value to ri. Doing so would not change dval but would instead change temp. To the programmer expecting that assignments to ri would change dval, it would appear that the change did not work. Allowing only const references to be bound to values requiring temporaries avoids the problem entirely because a const reference is read-only.
如果 ri 不是 const,那么可以给 ri 赋一新值。这样做不会修改 dval,而是修改了 temp。期望对 ri 的赋值会修改 dval 的程序员会发现 dval 并没有被修改。仅允许 const 引用绑定到需要临时使用的值完全避免了这个问题,因为 const 引用是只读的。
A nonconst reference may be attached only to an object of the same type as the reference itself.
非 const 引用只能绑定到与该引用同类型的对象。


A const reference may be bound to an object of a different but related type or to an rvalue.
const 引用则可以绑定到不同但相关的类型的对象或绑定到右值。
Exercises Section 2.5
Exercise 2.24:Which of the following definitions, if any, are invalid? Why? How would you correct them?
下列哪些定义是非法的?为什么?如何改正?
     (a) int ival = 1.01;     (b) int &rval1 = 1.01;
     (c) int &rval2 = ival;   (d) const int &rval3 = 1;



Exercise 2.25:Given the preceeding definitions, which, if any, of the following assignments are invalid? If they are valid, explain what they do.
在上题给出的定义下,下列哪些赋值是非法的?如果赋值合法,解释赋值的作用。
     (a) rval2 = 3.14159;  (b) rval2 = rval3;
     (c) ival = rval3;     (d) rval3 = ival;



Exercise 2.26:What are the differences among the definitions in (a) and the assignments in (b)? Which, if any, are illegal?
(a) 中的定义和 (b) 中的赋值存在哪些不同?哪些是非法的?
     (a) int ival = 0;          (b) ival = ri;
         const int &ri = 0;         ri = ival;



Exercise 2.27:What does the following code print?
下列代码输出什么?
     int i, &ri = i;
     i = 5; ri =10;
     std::cout << i << " " << ri << std::endl;




 
      

2.6. Typedef Names
2.6. typedef 名字
A typedef lets us define a synonym for a type:
typedef 可以用来定义类型的同义词:
     typedef double wages;       //  wages is a synonym for double
     typedef int exam_score;     //  exam_score is a synonym for int
     typedef wages salary;       //  indirect synonym for double

A typedef name can be used as a type specifier:
typedef 名字可以用作类型说明符:
     wages hourly, weekly;     // double hourly, weekly;
     exam_score test_result;   // int test_result;

A typedef definition begins with the keyword typedef, followed by the data type and identifier. The identifier, or typedef name, does not introduce a new type but rather a synonym for the existing data type. A typedef name can appear anywhere in a program that a type name can appear.
typedef 定义以关键字 typedef 开始,后面是数据类型和标识符。标识符或类型名并没有引入新的类型,而只是现有数据类型的同义词。typedef 名字可出现在程序中类型名可出现的任何位置。
Typedefs are commonly used for one of three purposes:
typedef 通常被用于以下三种目的:
To hide the implementation of a given type and emphasize instead the purpose for which the type is used
为了隐藏特定类型的实现,强调使用类型的目的。
To streamline complex type definitions, making them easier to understand
简化复杂的类型定义,使其更易理解。
To allow a single type to be used for more than one purpose while making the purpose clear each time the type is used
允许一种类型用于多个目的,同时使得每次使用该类型的目的明确。
 
      

2.7. Enumerations
2.7. 枚举
Often we need to define a set of alternative values for some attribute. A file, for example, might be open in one of three states: input, output, and append. One way to keep track of these state values might be to associate a unique constant number with each. We might write the following:
我们经常需要为某些属性定义一组可选择的值。例如,文件打开的状态可能会有三种:输入、输出和追加。记录这些状态值的一种方法是使每种状态都与一个唯一的常数值相关联。我们可能会这样编写代码:
     const int input = 0;
     const int output = 1;
     const int append = 2;

Although this approach works, it has a significant weakness: There is no indication that these values are related in any way. Enumerations provide an alternative method of not only defining but also grouping sets of integral constants.
虽然这种方法也能奏效,但是它有个明显的缺点:没有指出这些值是相关联的。枚举提供了一种替代的方法,不但定义了整数常量集,而且还把它们聚集成组。
Defining and Initializing Enumerations
定义和初始化枚举
An enumeration is defined using the enum keyword, followed by an optional enumeration name, and a comma-separated list of enumerators enclosed in braces.
枚举的定义包括关键字 enum,其后是一个可选的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员列表。
     // input is 0, output is 1, and append is 2
     enum open_modes {input, output, append};

By default, the first enumerator is assigned the value zero. Each subsequent enumerator is assigned a value one greater than the value of the enumerator that immediately precedes it.
默认地,第一个枚举成员赋值为 0,后面的每个枚举成员赋的值比前面的大 1。
Enumerators Are const Values
枚举成员是常量
We may supply an initial value for one or more enumerators. The value used to initialize an enumerator must be a constant expression. A constant expression is an expression of integral type that the compiler can evaluate at compile time. An integral literal constant is a constant expression, as is a const object (Section 2.4, p. 56) that is itself initialized from a constant expression.
可以为一个或多个枚举成员提供初始值,用来初始化枚举成员的值必须是一个常量表达式。常量表达式是编译器在编译时就能够计算出结果的整型表达式。整型字面值常量是常量表达式,正如一个通过常量表达式自我初始化的 const 对象(第 2.4 节)也是常量表达式一样。
For example, we might define the following enumeration:
例如,可以定义下列枚举类型:
     // shape is 1, sphere is 2, cylinder is 3, polygon is 4
     enum Forms {shape = 1, sphere, cylinder, polygon};



In the enum Forms we explicitly assigned shape the value 1. The other enumerators are implicitly initialized: sphere is initialized to 2, cylinder to 3, and polygon to 4.
在 枚举类型 Forms 中,显式将 shape 赋值为 1。其他枚举成员隐式初始化:sphere 初始化为 2,cylinder 初始化为 3,polygon 初始化为 4。
An enumerator value need not be unique.
枚举成员值可以是不唯一的。
     // point2d is 2, point2w is 3, point3d is 3, point3w is 4
     enum Points { point2d = 2, point2w,
                   point3d = 3, point3w };

In this example, the enumerator point2d is explicitly initialized to 2. The next enumerator, point2w, is initialized by default, meaning that its value is one more than the value of the previous enumerator. Thus, point2w is initialized to 3. The enumerator point3d is explicitly initialized to 3, and point3w, again is initialized by default, in this case to 4.
本例中,枚举成员 point2d 显式初始化为 2。下一个枚举成员 point2w 默认初始化,即它的值比前一枚举成员的值大 1。因此 point2w 初始化为 3。枚举成员 point3d 显式初始化为 3。一样,point3w 默认初始化,结果为 4。
It is not possible to change the value of an enumerator. As a consequence an enumerator is itself a constant expression and so can be used where a constant expression is required.
不能改变枚举成员的值。枚举成员本身就是一个常量表达式,所以也可用于需要常量表达式的任何地方。
Each enum Defines a Unique Type
每个 enum 都定义一种唯一的类型
Each enum defines a new type. As with any type, we can define and initialize objects of type Points and can use those objects in various ways. An object of enumeration type may be initialized or assigned only by one of its enumerators or by another object of the same enumeration type:
每个 enum 都定义了一种新的类型。和其他类型一样,可以定义和初始化 Points 类型的对象,也可以以不同的方式使用这些对象。枚举类型的对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行:
     Points pt3d = point3d; //  ok: point3d is a Points enumerator
     Points pt2w = 3;       //  error: pt2w initialized with int
     pt2w = polygon;        //  error: polygon is not a Points enumerator
     pt2w = pt3d;           //  ok: both are objects of Points enum type

Note that it is illegal to assign the value 3 to a Points object even though 3 is a value associated with one of the Points enumerators.
注意把 3 赋给 Points 对象是非法的,即使 3 与一个 Points 枚举成员相关联。
      

2.8. Class Types
2.8. 类类型
In C++ we define our own data types by defining a class. A class defines the data that an object of its type contains and the operations that can be executed by objects of that type. The library types string, istream, and ostream are all defined as classes.
C++ 中,通过定义类来自定义数据类型。类定义了该类型的对象包含的数据和该类型的对象可以执行的操作。标准库类型string、istream和ostream都定义成类。
C++ support for classes is extensivein fact, defining classes is so important that we shall devote Parts III through V to describing C++ support for classes and operations using class types.
C++对类的支持非常丰富——事实上,定义类是如此重要,我们把第三到第五部分全部用来描述 C++ 对类及类操作的支持。
In Chapter 1 we used the Sales_item type to solve our bookstore problem. We used objects of type Sales_item to keep track of sales data associated with a particular ISBN. In this section, we'll take a first look at how a simple class, such as Sales_item, might be defined.
在第一章中,我们使用 Sales_item 类型来解决书店问题。使用 Sales_item 类型的对象来记录对应于特定 ISBN 的销售数据。在这节中,我们先了解如何定义简单的类,如 Sales_item 类。
Class Design Starts with the Operations
从操作开始设计类
Each class defines an interface and implementation. The interface consists of the operations that we expect code that uses the class to execute. The implementation typically includes the data needed by the class. The implementation also includes any functions needed to define the class but that are not intended for general use.
每个类都定义了一个接口和一个实现。接口由使用该类的代码需要执行的操作组成。实现一般包括该类所需要的数据。实现还包括定义该类需要的但又不供一般性使用的函数。
When we define a class, we usually begin by defining its interfacethe operations that the class will provide. From those operations we can then determine what data the class will require to accomplish its tasks and whether it will need to define any functions to support the implementation.
定义类时,通常先定义该类的接口,即该类所提供的操作。通过这些操作,可以决定该类完成其功能所需要的数据,以及是否需要定义一些函数来支持该类的实现。
The operations our type will support are the operations we used in Chapter 1. These operations were outlined in Section 1.5.1 (p. 21):
我们将要定义的类型所支持的操作,就是我们在第一章中所用到的操作。这些操作如下(参见第 1.5.1 节):
The addition operator to add two Sales_items
加法操作符,将两个 Sales_item 相加。
The input and output operators to read and write Sales_item objects
输入和输出操作符,读和写 Sales_item 对象。
The assignment operator to assign one Sales_item object to another
赋值操作符,把 Sales_item 对象赋给另一个 Sales_item 对象。
The same_isbn function to determine if two objects refer to the same book
same_isbn 函数,检测两个对象是否指同一本书。
We'll see how to define these operations in Chapters 7 and 14 after we learn how to define functions and operators. Even though we can't yet implement these functions, we can figure out what data they'll need by thinking a bit about what these operations must do. Our Sales_item class must
在学完怎样定义函数和操作符后,我们将会在第七章和第十四章看到该怎样来定义这些操作。虽然现在不能实现这些函数,但通过思考这些操作必须要实现的功能,我们可以看出该类需要什么样的数据。Sales_item 类必须
Keep track of how many copies of a particular book were sold
记录特定书的销售册数。
Report the total revenue for that book
记录该书的总销售收入。
Calculate the average sales price for that book
计算该书的平均售价。
Looking at this list of tasks, we can see that we'll need an unsigned to keep track of how many books are sold and a double to keep track of the total revenue. From these data we can calculate the average sales price as total revenue divided by number sold. Because we also want to know which book we're reporting on, we'll also need a string to keep track of the ISBN.
查看以上所列出的任务,可以知道需要一个 unsigned 类型的对象来记录书的销售册数,一个 double 类型的对象来记录总销售收入,然后可以用总收入除以销售册数计算出平均售价。因为我们还想知道是在记录哪本书,所以还需要定义一个 string 类型的对象来记录书的 ISBN。
Defining the Sales_item Class
定义 Sales_item 类
Evidently what we need is the ability to define a data type that will have these three data elements and the operations we used in Chapter 1. In C++, the way we define such a data type is to define a class:
很明显,我们需要能够定义一种包含这三个数据元素和在第一章所用到的操作的数据类型。在 C++ 语言中,定义这种数据类型的方法就是定义类:
     class Sales_item {
     public:
         // operations on Sales_item objects will go here
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };

A class definition starts with the keyword class followed by an identifier that names the class. The body of the class appears inside curly braces. The close curly must be followed by a semicolon.
类定义以关键字 class 开始,其后是该类的名字标识符。类体位于花括号里面。花括号后面必须要跟一个分号。
 It is a common mistake among new programmers to forget the semicolon at the end of a class definition.
编程新手经常会忘记类定义后面的分号,这是个很普遍的错误!

The class body, which can be empty, defines the data and operations that make up the type. The operations and data that are part of a class are referred to as its members. The operations are referred to as the member functions (Section 1.5.2, p. 24) and the data as data members.
类体可以为空。类体定义了组成该类型的数据和操作。这些操作和数据是类的一部分,也称为类的成员。操作称为成员函数(第 1.5.2 节),而数据则称为数据成员。
The class also may contain zero or more public or private access labels. An access label controls whether a member is accessible outside the class. Code that uses the class may access only the public members.
类也可以包含 0 个到多个 private 或 public 访问标号。访问标号控制类的成员在类外部是否可访问。使用该类的代码可能只能访问 public 成员。
When we define a class, we define a new type. The class name is the name of that type. By naming our class Sales_item we are saying that Sales_item is a new type and that programs may define variables of this type.
定义了类,也就定义了一种新的类型。类名就是该类型的名字。通过命名 Sales_item 类,表示 Sales_item 是一种新的类型,而且程序也可以定义该类型的变量。
Each class defines its own scope (Section 2.3.6, p. 54). That is, the names given to the data and operations inside the class body must be unique within the class but can reuse names defined outside the class.
每一个类都定义了它自己的作用域(第 2.3.6 节)。也就是说,数据和操作的名字在类的内部必须唯一,但可以重用定义在类外的名字。
Class Data Members
类的数据成员
The data members of a class are defined in somewhat the same way that normal variables are defined. We specify a type and give the member a name just as we do when defining a simple variable:
定义类的数据成员和定义普通变量有些相似。我们同样是指定一种类型并给该成员一个名字:
     std::string isbn;
     unsigned units_sold;
     double revenue;

Our class has three data members: a member of type string named isbn, an unsigned member named units_sold, and a member of type double named revenue. The data members of a class define the contents of the objects of that class type. When we define objects of type Sales_item, those objects will contain a string, an unsigned, and a double.
这个类含有三个数据成员:一个名为 isbn 的 string 类型成员,一个名为 units_sold 的 unsigned 类型成员,一个名为 revenue 的 double 类型成员。类的数据成员定义了该类类型对象的内容。当定义 Sales_item 类型的对象时,这些对象将包含一个 string 型变量,一个 unsigned 型变量和一个 double 型变量。
There is one crucially important difference between how we define variables and class data members: We ordinarily cannot initialize the members of a class as part of their definition. When we define the data members, we can only name them and say what types they have. Rather than initializing data members when they are defined inside the class definition, classes control initialization through special member functions called constructors (Section 2.3.3, p. 49). We will define the Sales_item constructors in Section 7.7.3 (p. 262).
定义变量和定义数据成员存在非常重要的区别:一般不能把类成员的初始化作为其定义的一部分。当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数(第 2.3.3 节)的特殊成员函数控制初始化。我们将在第 7.7.3 节定义 Sales_item 的构造函数。
Access Labels
访问标号
Access labels control whether code that uses the class may use a given member. Member functions of the class may use any member of their own class, regardless of the access level. The access labels, public and private, may appear multiple times in a class definition. A given label applies until the next access label is seen.
访问标号负责控制使用该类的代码是否可以使用给定的成员。类的成员函数可以使用类的任何成员,而不管其访问级别。访问标号 public、private 可以多次出现在类定义中。给定的访问标号应用到下一个访问标号出现时为止。
The public section of a class defines members that can be accessed by any part of the program. Ordinarily we put the operations in the public section so that any code in the program may execute these operations.
类中public 部分定义的成员在程序的任何部分都可以访问。一般把操作放在 public 部分,这样程序的任何代码都可以执行这些操作。
Code that is not part of the class does not have access to the private members. By making the Sales_item data members private, we ensure that code that operates on Sales_item objects cannot directly manipulate the data members. Programs, such as the one we wrote in Chapter 1, may not access the private members of the class. Objects of type Sales_item may execute the operations but not change the data directly.
不是类的组成部分的代码不能访问 private 成员。通过设定 Sales_item 的数据成员为 private,可以保证对 Sales_item 对象进行操作的代码不能直接操纵其数据成员。就像我们在第一章编写的程序那样,程序不能访问类中的 private 成员。Sales_item 类型的对象可以执行那些操作,但是不能直接修改这些数据。
Using the struct Keyword
使用 struct 关键字
C++ supports a second keyword, struct, that can be used to define class types. The struct keyword is inherited from C.
C++ 支持另一个关键字 struct,它也可以定义类类型。struct 关键字是从 C 语言中继承过来的。
If we define a class using the class keyword, then any members defined before the first access label are implicitly private; ifwe usethe struct keyword, then those members are public. Whether we define a class using the class keyword or the struct keyword affects only the default initial access level.
如果使用 class 关键字来定义类,那么定义在第一个访问标号前的任何成员都隐式指定为 private;如果使用 struct 关键字,那么这些成员都是 public。使用 class 还是 struct 关键字来定义类,仅仅影响默认的初始访问级别。
We could have defined our Sales_item equivalently by writing
可以等效地定义 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;
     };

There are only two differences between this class definition and our initial class definition: Here we use the struct keyword, and we eliminate the use of public keyword immediately following the opening curly brace. Members of a struct are public, unless otherwise specified, so there is no need for the public label.
本例的类定义和前面的类定义只有两个区别:这里使用了关键字 struct,并且没有在花括号后使用关键字 public。struct 的成员都是 public,除非有其他特殊的声明,所以就没有必要添加 public 标号。
 The only difference between a class defined with the class keyword or the struct keyword is the default access level: By default, members in a struct are public; those in a class are private.
用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。

Exercises Section 2.8
Exercise 2.28:Compile the following program to determine whether your compiler warns about a missing semicolon after a class definition:
编译以下程序,确定你的编译器是否会警告遗漏了类定义后面的分号。
     class Foo  {
           // empty
     } // Note: no semicolon
     int main()
     {
         return 0;
     }

If the diagnostic is confusing, remember the message for future reference.
如果编译器的诊断结果难以理解,记住这些信息以备后用。
Exercise 2.29: Distinguish between the public and private sections of a class.
区分类中的 public 部分和 private 部分。
Exercise 2.30: Define the data members of classes to represent the following types:
定义表示下列类型的类的数据成员:
     (a) a phone number            (b) an address
     (c) an employee or a company  (d) a student at a university





      

2.9. Writing Our Own Header Files
2.9. 编写自己的头文件
We know from Section 1.5(p. 20)that ordinarily class definitions go into a header file. In this section we'll see how to define a header file for the Sales_item class.
我们已经从第 1.5 节了解到,一般类定义都会放入头文件。在本节中我们将看到怎样为 Sales_item 类定义头文件。
In fact, C++ programs use headers to contain more than class definitions. Recall that every name must be declared or defined before it is used. The programs we've written so far handle this requirement by putting all their code into a single file. As long as each entity precedes the code that uses it, this strategy works. However, few programs are so simple that they can be written in a single file. Programs made up of multiple files need a way to link the use of a name and its declaration. In C++ that is done through header files.
事实上,C++ 程序使用头文件包含的不仅仅是类定义。回想一下,名字在使用前必须先声明或定义。到目前为止,我们编写的程序是把代码放到一个文件里来处理这个要求。只要每个实体位于使用它的代码之前,这个策略就有效。但是,很少有程序简单到可以放置在一个文件中。由多个文件组成的程序需要一种方法连接名字的使用和声明,在 C++ 中这是通过头文件实现的。
To allow programs to be broken up into logical parts, C++ supports what is commonly known as separate compilation. Separate compilation lets us compose a program from several files. To support separate compilation, we'll put the definition of Sales_item in a header file. The member functions for Sales_item, which we'll define in Section 7.7 (p. 258), will go in a separate source file. Functions such as main that use Sales_item objects are in other source files. Each of the source files that use Sales_item must include our Sales_item.h header file.
为了允许把程序分成独立的逻辑块,C++ 支持所谓的分别编译。这样程序可以由多个文件组成。为了支持分别编译,我们把 Sales_item 的定义放在一个头文件里面。我们后面在第 7.7 节中定义的 Sales_item 成员函数将放在单独的源文件中。像 main 这样使用 Sales_item 对象的函数放在其他的源文件中,任何使用 Sales_item 的源文件都必须包含 Sales_item.h 头文件。
2.9.1. Designing Our Own Headers
2.9.1. 设计自己的头文件
A header provides a centralized location for related declarations. Headers normally contain class definitions, extern variable declarations, and function declarations, about which we'll learn in Section 7.4 (p. 251). Files that use or define these entities include the appropriate header(s).
头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、extern 变量的声明和函数的声明。函数的声明将在第 7.4 节介绍。使用或定义这些实体的文件要包含适当的头文件。
Proper use of header files can provide two benefits: All files are guaranteed to use the same declaration for a given entity; and should a declaration require change, only the header needs to be updated.
头文件的正确使用能够带来两个好处:保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。
Some care should be taken in designing headers. The declarations in a header should logically belong together. A header takes time to compile. If it is too large programmers may be reluctant to incur the compile-time cost of including it.
设计头文件还需要注意以下几点:头文件中的声明在逻辑上应该是统一的。编译头文件需要一定的时间。如果头文件太大,程序员可能不愿意承受包含该头文件所带来的编译时代价。
To reduce the compile time needed to process headers, some C++ implementations support precompiled header files. For more details, consult the reference manual of your C++ implementation.
为了减少处理头文件的编译时间,有些 C++ 的实现支持预编译头文件。欲进一步了解详细情况,请参考你的 C++ 实现的手册。

Compiling and Linking Multiple Source Files
编译和链接多个源文件
To produce an executable file, we must tell the compiler not only where to find our main function but also where to find the definition of the member functions defined by the Sales_item class. Let's assume that we have two files: main.cc, which contains the definition of main, and Sales_item.cc, which contains the Sales_item member functions. We might compile these files as follows:
要产生可执行文件,我们不但要告诉编译器到哪里去查找 main 函数,而且还要告诉编译器到哪里去查找 Sales_item 类所定义的成员函数的定义。假设我们有两个文件:main.cc 含有 main 函数的定义,Sales_item.cc 含有 Sales_item 的成员函数。我们可以按以下方式编译这两个文件:
     $ CC -c main.cc Sales_item.cc # by default generates a.exe
                                   # some compilers generate a.out

     # puts the executable in main.exe
     $ CC -c main.cc Sales_item.cc -o main

where $ is our system prompt and # begins a command-line comment. We can now run the executable file, which will run our main program.
其中 $ 是我们的系统提示符,# 开始命令行注释。现在我们可以运行可执行文件,它将运行我们的 main 程序。
If we have only changed one of our .cc source files, it is more efficient to recompile only the file that actually changed. Most compilers provide a way to separately compile each file. This process usually yields a .o file, where the .o extension implies that the file contains object code.
如果我们只是修改了一个 .cc 源文件,较有效的方法是只重新编译修改过的文件。大多数编译器都提供了分别编译每一个文件的方法。通常这个过程产生 .o 文件,.o 扩展名暗示该文件含有目标代码。
The compiler lets us link object files together to form an executable. On the system we use, in which the compiler is invoked by a command named CC, we would compile our program as follows:
编译器允许我们把目标文件链接在一起以形成可执行文件。我们所使用的系统可以通过命令名 CC 调用编译。因此可以按以下方式编译程序:
     $ CC -c main.cc              # generates main.o
     $ CC -c Sales_item.cc        # generates Sales_item.o
     $ CC main.o Sales_item.o     # by default generates a.exe;
                                  # some compilers generate a.out

     # puts the executable in main.exe
     $ CC main.o Sales_item.o -o main

You'll need to check with your compiler's user's guide to understand how to compile and execute programs made up of multiple source files.
你需要检查所用编译器的用户手册,了解如何编译和执行由多个源文件组成的程序。
Many compilers offer an option to enhance the error detection of the compiler. Check your compiler's user's guide to see what additional checks are available.
许多编译器提供了增强其错误检测能力的选项。查看所用编译器的用户指南,了解有哪些额外的检测方法。


Headers Are for Declarations, Not Definitions
头文件用于声明而不是用于定义
When designing a header it is essential to remember the difference between definitions, which may only occur once, and declarations, which may occur multiple times (Section 2.3.5, p. 52). The following statements are definitions and therefore should not appear in a header:
当设计头文件时,记住定义和声明的区别是很重要的。定义只可以出现一次,而声明则可以出现多次(第 2.3.5 节)。下列语句是一些定义,所以不应该放在头文件里:
     extern int ival = 10;      // initializer, so it's a definition
     double fica_rate;          // no extern, so it's a definition

Although ival is declared extern, it has an initializer, which means this statement is a definition. Similarly, the declaration of fica_rate, although it does not have an initializer, is a definition because the extern keyword is absent. Including either of these definitions in two or more files of the same program will result in a linker error complaining about multiple definitions.
虽然 ival 声明为 extern,但是它有初始化式,代表这条语句是一个定义。类似地,fica_rate 的声明虽然没有初始化式,但也是一个定义,因为没有关键字 extern。同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。
Because headers are included in multiple source files, they should not contain definitions of variables or functions.
因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。

There are three exceptions to the rule that headers should not contain definitions: classes, const objects whose value is known at compile time, and inline functions (Section 7.6 (p. 256) covers inline functions) are all defined in headers. These entities may be defined in more than one source file as long as the definitions in each file are exactly the same.
对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数(第 7.6 节介绍 inline 函数)。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。
These entities are defined in headers because the compiler needs their definitions (not just declarations) to generate code. For example, to generate code that defines or uses objects of a class type, the compiler needs to know what data members make up that type. It also needs to know what operations can be performed on these objects. The class definition provides the needed information. That const objects are defined in a header may require a bit more explanation.
在头文件中定义这些实体,是因为编译器需要它们的定义(不只是声明)来产生代码。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还需要知道能够在这些对象上执行的操作。类定义提供所需要的信息。在头文件中定义 const 对象则需要更多的解释。
Some const Objects Are Defined in Headers
一些 const 对象定义在头文件中
Recall that by default a const variable (Section 2.4, p. 57) is local to the file in which it is defined. As we shall now see, the reason for this default is to allow const variables to be defined in header files.
回想一下,const 变量(第 2.4 节)默认时是定义该变量的文件的局部变量。正如我们现在所看到的,这样设置默认情况的原因在于允许 const 变量定义在头文件中。
In C++ there are places where constant expression (Section 2.7, p. 62) is required. For example, the initializer of an enumerator must be a constant expression. We'll see other cases that require constant expressions in later chapters.
在 C++ 中,有些地方需要放置常量表达式(第 2.7 节)。例如,枚举成员的初始化式必须是常量表达式。在以后的章节中将会看到其他需要常量表达式的例子。
Generally speaking, a constant expression is an expression that the compiler can evaluate at compile-time. A const variable of integral type may be a constant expression when it is itself initialized from a constant expression. However, for the const to be a constant expression, the initializer must be visible to the compiler. To allow multiple files to use the same constant value, the const and its initializer must be visible in each file. To make the initializer visible, we normally define such consts inside a header file. That way the compiler can see the initializer whenever the const is used.
一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当 const 整型变量通过常量表达式自我初始化时,这个 const 整型变量就可能是常量表达式。而 const 变量要成为常量表达式,初始化式必须为编译器可见。为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的 const 变量定义在头文件中。那样的话,无论该 const 变量何时使用,编译器都能够看见其初始化式。
However, there can be only one definition (Section 2.3.5, p. 52) for any variable in a C++ program. A definition allocates storage; all uses of the variable must refer to the same storage. Because, by default, const objects are local to the file in which they are defined, it is legal to put their definition in a header file.
但是,C++ 中的任何变量都只能定义一次(第 2.3.5 节)。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。因为 const 对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件中是合法的。
There is one important implication of this behavior. When we define a const in a header file, every source file that includes that header has its own const variable with the same name and value.
这种行为有一个很重要的含义:当我们在头文件中定义了 const 变量后,每个包含该头文件的源文件都有了自己的 const 变量,其名称和值都一样。
When the const is initialized by a constant expression, then we are guaranteed that all the variables will have the same value. Moreover, in practice, most compilers will replace any use of such const variables by their corresponding constant expression at compile time. So, in practice, there won't be any storage used to hold const variables that are initialized by constant expressions.
当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些 const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的 const 变量。
When a const is initialized by a value that is not a constant expression, then it should not be defined in header file. Instead, as with any other variable, the const should be defined and initialized in a source file. An extern declaration for that const should be made in the header, enabling multiple files to share that variable.
如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加 extern 声明,以使其能被多个文件共享。
Exercises Section 2.9.1
Exercise 2.31:Identify which of the following statements are declarations and which ones are definitions. Explain why they are declarations or definitions.
判别下列语句哪些是声明,哪些是定义,请解释原因。
     (a) extern int ix = 1024;
     (b) int iy;
     (c) extern int iz;
     (d) extern const int &ri;



Exercise 2.32:Which of the following declarations and definitions would you put in a header? In a source file? Explain why.
下列声明和定义哪些应该放在头文件中?哪些应该放在源文件中?请解释原因。
     (a) int var;
     (b) const double pi = 3.1416;
     (c) extern int total = 255;
     (d) const double sq2 = sqrt(2.0);



Exercise 2.33:Determine what options your compiler offers for increasing the warning level. Recompile selected earlier programs using this option to see whether additional problems are reported.
确定你的编译器提供了哪些提高警告级别的选项。使用这些选项重新编译以前选择的程序,查看是否会报告新的问题。


2.9.2. A Brief Introduction to the Preprocessor
2.9.2. 预处理器的简单介绍
Now that we know what we want to put in our headers, our next problem is to actually write a header. We know that to use a header we have to #include it in our source file. In order to write our own headers, we need to understand a bit more about how a #include directive works. The #include facility is a part of the C++ preprocessor. The preprocessor manipulates the source text of our programs and runs before the compiler. C++ inherits a fairly elaborate preprocessor from C. Modern C++ programs use the preprocessor in a very restricted fashion.
既然已经知道了什么应该放在头文件中,那么我们下一个问题就是真正地编写头文件。我们知道要使用头文件,必须在源文件中#include该头文件。为了编写头文件,我们需要进一步理解 #include 指示是怎样工作的。#include 设施是C++ 预处理器的一部分。预处理器处理程序的源代码,在编译器之前运行。C++ 继承了 C 的非常精细的预处理器。现在的 C++ 程序以高度受限的方式使用预处理器。
A #include directive takes a single argument: the name of a header. The pre-processor replaces each #include by the contents of the specified header. Our own headers are stored in files. System headers may be stored in a compiler-specific format that is more efficient. Regardless of the form in which a header is stored, it ordinarily contains class definitions and declarations of the variables and functions needed to support separate compilation.
#include 指示只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个 #include。我们自己的头文件存储在文件中。系统的头文件可能用特定于编译器的更高效的格式保存。无论头文件以何种格式保存,一般都含有支持分别编译所需的类定义及变量和函数的声明。
Headers Often Need Other Headers
头文件经常需要其他头文件
Headers often #include other headers. The entities that a header defines often use facilities from other headers. For example, the header that defines our Sales_item class must include the string library. The Sales_item class has a string data member and so must have access to the string header.
头文件经常 #include 其他头文件。头文件定义的实体经常使用其他头文件的设施。例如,定义 Sales_item 类的头文件必须包含 string 库。Sales_item 类含有一个 string 类型的数据成员,因此必须可以访问 string 头文件。
Including other headers is so common that it is not unusual for a header to be included more than once in the same source file. For example, a program that used the Sales_item header might also use the string library. That program wouldn'tindeed shouldn'tknow that our Sales_item header uses the string library. In this case, the string header would be included twice: once by the program itself and once as a side-effect of including our Sales_item header.
包含其他头文件是如此司空见惯,甚至一个头文件被多次包含进同一源文件也不稀奇。例如,使用 Sales_item 头文件的程序也可能使用 string 库。该程序不会(也不应该)知道 Sales_item 头文件使用了 string 库。在这种情况下,string 头文件被包含了两次:一次是通过程序本身直接包含,另一次是通过包含 Sales_item 头文件而间接包含。
Accordingly, it is important to design header files so that they can be included more than once in a single source file. We must ensure that including a header file more than once does not cause multiple definitions of the classes and objects that the header file defines. A common way to make headers safe uses the preprocessor to define a header guard. The guard is used to avoid reprocessing the contents of a header file if the header has already been seen.
因此,设计头文件时,应使其可以多次包含在同一源文件中,这一点很重要。我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。使得头文件安全的通用做法,是使用预处理器定义头文件保护符。头文件保护符用于避免在已经见到头文件的情况下重新处理该头文件的内容。
Avoiding Multiple Inclusions
避免多重包含
Before we write our own header, we need to introduce some additional preprocessor facilities. The preprocessor lets us define our own variables.
在编写头文件之前,我们需要引入一些额外的预处理器设施。预处理器允许我们自定义变量。
Names used for preprocessor variables must be unique within the program. Any uses of a name that matches a preprocessor variable is assumed to refer to the preprocessor variable.
预处理器变量 的名字在程序中必须是唯一的。任何与预处理器变量相匹配的名字的使用都关联到该预处理器变量。

To help avoid name clashes, preprocessor variables usually are written in all uppercase letters.
为了避免名字冲突,预处理器变量经常用全大写字母表示。
A preprocessor variable has two states: defined or not yet defined. Various preprocessor directives define and test the state of preprocessor variables. The #define directive takes a name and defines that name as a preprocessor variable. The #ifndef directive tests whether the specified preprocessor variable has not yet been defined. If it hasn't, then everything following the #ifndef is processed up to the next #endif.
预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理器指示不同。#define 指示接受一个名字并定义该名字为预处理器变量。#ifndef 指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现 #endif。
We can use these facilities to guard against including a header more than once:
可以使用这些设施来预防多次包含同一头文件:
     #ifndef SALESITEM_H
     #define SALESITEM_H
     // Definition of Sales_itemclass and related functions goes here
     #endif

The conditional directive
条件指示
     #ifndef SALESITEM_H

tests whether the SALESITEM_H preprocessor variable has not been defined. If SALESITEM_H has not been defined, the #ifndef succeeds and all the lines following #ifndef until the #endif is found are processed. Conversely, if the variable SALESITEM_H has been defined, then the #ifndef directive is false. The lines between it and the #endif directive are ignored.
测试 SALESITEM_H 预处理器变量是否未定义。如果 SALESITEM_H 未定义,那么 #ifndef 测试成功,跟在 #ifndef 后面的所有行都被执行,直到发现 #endif。相反,如果 SALESITEM_H 已定义,那么 #ifndef 指示测试为假,该指示和 #endif 指示间的代码都被忽略。
To guarantee that the header is processed only once in a given source file, we start by testing the #ifndef. The first time the header is processed, this test will succeed, because SALESITEM_H will not yet have been defined. The next statement defines SALESITEM_H. That way, if the file we are compiling happens to include this header a second time, the #ifndef directive will discover that SALESITEM_H is defined and skip the rest of the header file.
为了保证头文件在给定的源文件中只处理过一次,我们首先检测 #ifndef。第一次处理头文件时,测试会成功,因为 SALESITEM_H 还未定义。下一条语句定义了 SALESITEM_H。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef 指示会发现 SALESITEM_H 已经定义,并且忽略该头文件的剩余部分。
Headers should have guards, even if they aren't included by another header. Header guards are trivial to write and can avoid mysterious compiler errors if the header subsequently is included more than once.
头文件应该含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。

This strategy works well provided that no two headers define and use a pre-processor constant with the same name. We can avoid problems with duplicate preprocessor variables by naming them for an entity, such as a class, that is defined inside the header. A program can have only one class named Sales_item. By using the class name to compose the name of the header file and the preprocessor variable, we make it pretty likely that only one file will use this preprocessor variable.
当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。我们可以为定义在头文件里的实体(如类)命名预处理器变量来避免预处理器变量重名的问题。一个程序只能含有一个名为 Sales_item 的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。
Using Our Own Headers
使用自定义的头文件
The #include directive takes one of two forms:
#include 指示接受以下两种形式:
     #include <standard_header>
     #include "my_file.h"

If the header name is enclosed by angle brackets (< >), it is presumed to be a standard header. The compiler will look for it in a predefined set of locations, which can be modified by setting a search path environment variable or through a command line option. The search methods used vary significantly across compilers. We recommend you ask a colleague or consult your compiler's user's guide for further information. If the header name is enclosed by a pair of quotation marks, the header is presumed to be a nonsystem header. The search for nonsystem headers usually begins in the directory in which the source file is located.
如果头文件名括在尖括号(< >)里,那么认为该头文件是标准头文件。编译器将会在预定义的位置集查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。使用的查找方法因编译器的不同而差别迥异。建议你咨询同事或者查阅编译器用户指南来获得更多的信息。如果头文件名括在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。
       

Chapter 3. Library Types
第三章 标准库类型
CONTENTS
Section 3.1 Namespace using Declarations78
Section 3.2 Library string Type80
Section 3.3 Library vector Type90
Section 3.4 Introducing Iterators95
Section 3.5 Library bitset Type101
Chapter Summary107
Defined Terms107

In addition to the primitive types covered in Chapter 2, C++ defines a rich library of abstract data types. Among the most important library types are string and vector, which define variable-sized character strings and collections, respectively. Associated with string and vector are companion types known as iterators, which are used to access the characters in a string or the elements in a vector. These library types are abstractions of more primitive typesarrays and pointersthat are part of the language.
除第二章介绍的基本数据类型外,C++ 还定义了一个内容丰富的抽象数据类型标准库。其中最重要的标准库类型是 string 和 vector,它们分别定义了大小可变的字符串和集合。string 和 vector 往往将迭代器用作配套类型(companion type),用于访问 string 中的字符,或者 vector 中的元素。这些标准库类型是语言组成部分中更基本的那些数据类型(如数组和指针)的抽象。
Another library type, bitset, provides an abstract way to manipulate a collection of bits. This class provides a more convenient way of dealing with bits than is offered by the built-in bitwise operators on values of integral type.
另一种标准库类型 bitset,提供了一种抽象方法来操作位的集合。与整型值上的内置位操作符相比,bitset 类类型提供了一种更方便的处理位的方式。
This chapter introduces the library vector, string, and bitset types. The next chapter covers arrays and pointers, and Chapter 5 looks at built-in bitwise operators.
本章将介绍标准库中的 vector、string 和 bitset 类型。第四章将讨论数组和指针,第五章将讲述内置位操作符。
The types that we covered in Chapter 2 are all low-level types: They represent abstractions such as numbers or characters and are defined in terms of how they are represented on the machine.
第二章所涉及的类型都是低层数据类型:这些类型表示数值或字符的抽象,并根据其具体机器表示来定义。
In addition to the types defined in the language, the standard library defines a number of higher level abstract data types. These library types are higher-level in that they mirror more complex concepts. They are abstract because when we use them we don't need to care about how the types are represented. We need to know only what operations they support.
除了这些在语言中定义的类型外,C++ 标准库还定义了许多更高级的抽象数据类型之所以说这些标准库类型是更高级的,是因为其中反映了更复杂的概念;之所以说它们是抽象的,是因为我们在使用时不需要关心它们是如何表示的,只需知道这些抽象数据类型支持哪些操作就可以了。
Two of the most important library types are string and vector. The string type supports variable-length character strings. The vector type holds a sequence of objects of a specified type. These types are important because they offer improvements over more primitive types defined by the language. Chapter 4 looks at the language-level constructs that are similar to, but less flexible and more error-prone than, the library string and vector types.
两种最重要的标准库类型是 string 和 vector。string 类型支持长度可变的字符串,vector 可用于保存一组指定类型的对象。说它们重要,是因为它们在 C++ 定义的基本类型基础上作了一些改进。第四章还将学习类似于标准库中 string 和 vector 类型的语言级构造,但标准库的 string 和 vector 类型可能更灵活,且不易出错。
Another library type that offers a more convenient and reasonably efficient abstraction of a language level facility is the bitset class. This class lets us treat a value as a collection of bits. It provides a more direct way of operating on bits than do the bitwise operators that we cover in Section 5.3 (p. 154).
另一种标准库类型提供了更方便和合理有效的语言级的抽象设施,它就是 bitset 类。通过这个类可以把某个值当作们的集合来处理。与 第 5.3 节介绍的位操作符相比,bitset 类提供操作位更直接的方法。
Before continuing our exploration of the library types, we'll look at a mechanism for simplifying access to the names defined in the library.
在继续探究标准库类型之前,我们先看一种机制,这种机制能够简化对标准库中所定义名字的访问。

      

3.1. Namespace using Declarations
3.1. 命名空间的 using 声明
The programs we've seen so far have referred to names from the library by explicitly noting that the name comes from the std namespace. For example, when we want to read from the standard input, we write std::cin. Such names use the :: operator, which is the scope operator (Section 1.2.2, p. 8). This operator says that we should look for the name of the right-hand operand in the scope of the left-hand operand. Thus, std::cin says that we want the name cin that is defined in the namespace std. Referring to library names through this notation can be cumbersome.
在本章之前看到的程序,都是通过直接说明名字来自 std 命名空间,来引用标准库中的名字。例如,需要从标准输入读取数据时,就用 std::cin。这些名字都用了:: 操作符,该操作符是作用域操作符(第 1.2.2 节)。它的含义是右操作数的名字可以在左操作数的作用域中找到。因此,std::cin 的意思是说所需要名字 cin 是在命名空间 std 中定义的。显然,通过这种符号引用标准库名字的方式是非常麻烦的。
Fortunately, there are easier ways to use namespace members. In this section we'll cover the safest mechanism: using declarations. We will see other ways to simplify the use of names from a namespace in Section 17.2 (p. 712).
幸运的是,C++ 提供了更简洁的方式来使用命名空间成员。本节将介绍一种最安全的机制:using 声明。关于其他简化使用命名空间中名字的方法将在第 17.2 节中介绍
A using declaration allows us to access a name from a namespace without the prefix namespace_name::. A using declaration has the following form:
使用 using 声明可以在不需要加前缀 namespace_name:: 的情况下访问命名空间中的名字。using 声明的形式如下:
     using namespace::name;

Once the using declaration has been made, we can access name directly without reference to its namespace:
一旦使用了 using 声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间。
     #include <string>
     #include <iostream>
     // using declarations states our intent to use these names from the namespace std
     using std::cin;
     using std::string;
     int main()
     {
      string s;       // ok: string is now a synonym for std::string
      cin >> s;       // ok: cin is now a synonym for std::cin
      cout << s;      // error: no using declaration; we must use full name
      std::cout << s; // ok: explicitly use cout from namepsace std
     }

Using the unqualified version of a namespace name without a using declaration is an error, although some compilers may fail to detect this error.
没有 using 声明,而直接使用命名空间中名字的未限定版本是错误的,尽管有些编译器也许无法检测出这种错误。
A Separate Using Declaration Is Required for Each Name
每个名字都需要一个 using 声明
A using declaration introduces only one namespace member at a time. It allows us to be very specific regarding which names are used in our programs. If we want to use several names from stdor any other namespacethen we must issue a using declaration for each name that we intend to use. For example, we could rewrite the addition program from page 6 as follows:
一个 using 声明一次只能作用于一个命名空间成员。using 声明可用来明确指定在程序中用到的命名空间中的名字,如果希望使用 std(或其他的命名空间)中的几个名字,则必须为要用到的每个名字都提供一个 using 声明。例如,利用 using 声明可以这样重新编写第 1.2.2 节中的加法程序:
     #include <iostream>
     // using declarations for names from the standard library
     using std::cin;
     using std::cout;
     using std::endl;
     int main()
     {
         cout << "Enter two numbers:" << endl;
         int v1, v2;
         cin >> v1 >> v2;
         cout << "The sum of " << v1
              << " and " << v2
              << " is " << v1 + v2 << endl;
         return 0;
     }



The using declarations for cin, cout, and endl mean that we can use those names without the std:: prefix, making the code easier to read.
对 cin,cout 和 endl 进行 using 声明,就意味着以后可以省前缀 std::,直接使用命名空间中的名字,这样代码可以更易读。
From this point on, our examples will assume that using declarations have been provided for the names we use from the standard library. Thus, we will refer to cin, not std::cin, in the text and in code examples. To keep the code examples short, we won't show the using declarations that are needed to compile the examples. Similarly, our code examples will not show the necessary #include directives. Table A.1 (p. 810) in Appendix A lists the library names and corresponding headers for standard-library names we use in this primer.
从这里开始,假定本书所有例子中所用到的标准库中的名字都已提供了 using 声明。这样,无论是在文档还是在代码实例中引用 cin, 我们都不再写为前缀形式 std::cin,为了使代码实例简短,我们还省略了编译时所必需的 using 声明。同样的,程序实例也会省略必需的 #include 指示。本书附录 A中的表 A.1 列出了本书中用到的标准为名字的库名和相应的头文件。
Readers should be aware that they must add appropriate #include and using declarations to our examples before compiling them.
在编译我们提供的实例程序前,读者一定要注意在程序中添加适当的 #include 和 using 声明。




Class Definitions that Use Standard Library Types
使用标准库类型的类定义
There is one case in which we should always use the fully qualified library names: inside header files. The reason is that the contents of a header are copied into our program text by the preprocessor. When we #include a file, it is as if the exact text of the header is part of our file. If we place a using declaration within a header, it is equivalent to placing the same using declaration in every program that includes the header whether that program wants the using declaration or not.
有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。用 #include 包含文件时,相当于头文件中的文本将成为我们编写的文件的一部分。如果在头文件中放置 using 声明,就相当于在包含该头文件 using 的每个程序中都放置了同一 using,不论该程序是否需要 using 声明。
In general, it is good practice for headers to define only what is strictly necessary.
通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。



Exercises Section 3.1
Exercise 3.1:Rewrite the program from Section 2.3 (p. 43) that calculated the result of raising a given number to a given power to use appropriate using declarations rather than accessing library names through a std:: prefix.
用适当的 using 声明,而不用 std::,访问标准库中名字的方法,重新编写第 2.3 节的程序,计算一给定数的给定次幂的结果。



      

3.2. Library string Type
3.2. 标准库 string 类型
The string type supports variable-length character strings. The library takes care of managing the memory associated with storing the characters and provides various useful operations. The library string type is intended to be efficient enough for general use.
string 类型支持长度可变的字符串,C++ 标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作。标准库 string 类型的目的就是满足对字符串的一般应用。
As with any library type, programs that use strings must first include the associated header. Our programs will be shorter if we also provide an appropriate using declaration:
与其他的标准库类型一样,用户程序要使用 string 类型对象,必须包含相关头文件。如果提供了合适的 using 声明,那么编写出来的程序将会变得简短些:
     #include <string>
     using std::string;

3.2.1. Defining and Initializing strings
3.2.1. string 对象的定义和初始化
The string library provides several constructors (Section 2.3.3, p. 49). A constructor is a special member function that defines how objects of that type can be initialized. Table 3.1 on the facing page lists the most commonly used string constructors. The default constructor (Section 2.3.4, p. 52) is used "by default" when no initializer is specified.
string 标准库支持几个构造函数(第 2.3.3 节)。构造函数是一个特殊成员函数,定义如何初始化该类型的对象。表 3.1 列出了几个 string 类型常用的构造函数。当没有明确指定对象初始化式时,系统将使用默认构造函数(第 2.3.4 节)。
Table 3.1. Ways to Initialize a string
表 3.1. 几种初始化 string 对象的方式
string s1;Default constructor; s1 is the empty string
 默认构造函数 s1 为空串
string s2(s1);Initialize s2 as a copy of s1
 将 s2 初始化为 s1 的一个副本
string s3("value");Initialize s3 as a copy of the string literal
 将 s3 初始化为一个字符串字面值副本
string s4(n, 'c');Initialize s4 with n copies of the character 'c'
 将 s4 初始化为字符 'c' 的 n 个副本

Caution: Library string Type and String Literals
警告:标准库 string 类型和字符串字面值
For historical reasons, and for compatibility with C, character string literals are not the same type as the standard library string type. This fact can cause confusion and is important to keep in mind when using a string literal or the string data type.
因为历史原因以及为了与 C 语言兼容,字符串字面值与标准库 string 类型不是同一种类型。这一点很容易引起混乱,编程时一定要注意区分字符串字面值和 string 数据类型的使用,这很重要。

Exercises Section 3.2.1
Exercise 3.2:What is a default constructor?
什么是默认构造函数?
Exercise 3.3:Name the three ways to initialize a string.
列举出三种初始化 string 对象的方法。
Exercise 3.4:What are the values of s and s2?
s 和 s2 的值分别是什么?
     string s;
     int main() {
      string s2;
     }





3.2.2. Reading and Writing strings
3.2.2. string 对象的读写
我们已在第一章学习了用 iostream 标准库来读写内置类型的值,如 int double 等。同样地,也可以用 iostream 和 string 标准库,使用标准输入输出操作符来读写 string 对象:
     // Note: #include and using declarations must be added to compile this code
     int main()
     {
         string s;          // empty string
         cin >> s;          // read whitespace-separated string into s
         cout << s << endl; // write s to the output
         return 0;
     }

This program begins by defining a string named s. The next line,
以上程序首先定义命名为 s 的 string 第二行代码:
     cin >> s;        // read whitespace-separated string into s

reads the standard input storing what is read into s. The string input operator:
从标准输入读取 string 并将读入的串存储在 s 中。string 类型的输入操作符:
Reads and discards any leading whitespace (e.g., spaces, newlines, tabs)
读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
It then reads characters until the next whitespace character is encountered
读取字符直至再次遇到空白字符,读取终止。
So, if the input to this program is "Hello World!", (note leading and trailing spaces) then the output will be "Hello" with no extra spaces.
如果给定和上一个程序同样的输入,则输出的结果是"Hello World!"(注意到开头和结尾的空格),则屏幕上将输出"Hello",而不含任何空格。
The input and output operations behave similarly to the operators on the builtin types. In particular, the operators return their left-hand operand as their result. Thus, we can chain together multiple reads or writes:
输入和输出操作的行为与内置类型操作符基本类似。尤其是,这些操作符返回左操作数作为运算结果。因此,我们可以把多个读操作或多个写操作放在一起:
     string s1, s2;
     cin >> s1 >> s2; // read first input into s1, second into s2
     cout << s1 << s2 << endl; // write both strings

If we give this version of the program the same input as in the previous paragraph, our output would be
如果给定和上一个程序同样的输入,则输出的结果将是:
     HelloWorld!

To compile this program, you must add #include directives for both the iostream and string libraries and must issue using declarations for all the names used from the library: string, cin, cout, and endl.
对于上例,编译时必须加上 #include 来标示 iostream 和 string 标准库,以及给出用到的所有标准库中的名字(如 string,cin,cout,endl)的 using 声明。

The programs presented from this point on will assume that the needed #include and using declarations have been made.
从本例开始的程序均假设程序中所有必须 #include 和 using 声明已给出。
Reading an Unknown Number of strings
读入未知数目的 string 对象
Like the input operators that read built-in types, the string input operator returns the stream from which it read. Therefore, we can use a string input operation as a condition, just as we did when reading ints in the program on page 18. The following program reads a set of strings from the standard input and writes what it has read, one string per line, to the standard output:
和内置类型的输入操作一样,string 的输入操作符也会返回所读的数据流。因此,可以把输入操作作为判断条件,这与我们在 1.4.4 节读取整型数据的程序做法是一样的。下面的程序将从标准输入读取一组 string 对象,然后在标准输出上逐行输出:
     int main()
     {
         string word;
         // read until end-of-file, writing each word to a new line
         while (cin >> word)
             cout << word << endl;
         return 0;
     }

In this case, we read into a string using the input operator. That operator returns the istream from which it read, and the while condition tests the stream after the read completes. If the stream is validit hasn't hit end-of-file or encountered an invalid inputthen the body of the while is executed and the value we read is printed to the standard output. Once we hit end-of-file, we fall out of the while.
上例中,用输入操作符来读取 string 对象。该操作符返回所读的 istream 对象,并在读取结束后,作为 while 的判断条件。如果输入流是有效的,即还未到达文件尾且未遇到无效输入,则执行 while 循环体,并将读取到的字符串输出到标准输出。如果到达了文件尾,则跳出 while 循环。
Using getline to Read an Entire Line
使用 getline 读取整行文本
There is an additional useful string IO operation: getline. This is a function that takes both an input stream and a string. The getline function reads the next line of input from the stream and stores what it read, not including the newline, in its string argument. Unlike the input operator, getline does not ignore leading newlines. Whenever getline encounters a newline, even if it is the first character in the input, it stops reading the input and returns. The effect of encountering a newline as the first character in the input is that the string argument is set to the empty string.
另外还有一个有用的 string IO 操作:getline。这个函数接受两个参数:一个输入流对象和一个 string 对象。getline 函数从输入流的下一行读取,并保存读取的内容到不包括换行符。和输入操作符不一样的是,getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符,getline 也将停止读入并返回。如果第一个字符就是换行符,则 string 参数将被置为空 string。
The getline function returns its istream argument so that, like the input operator, it can be used as a condition. For example, we could rewrite the previous program that wrote one word per line to write a line at a time instead:
getline 函数将 istream 参数作为返回值,和输入操作符一样也把它用作判断条件。例如,重写前面那段程序,把每行输出一个单词改为每次输出一行文本:
     int main()
     {
         string line;
         // read line at time until end-of-file
         while (getline(cin, line))
             cout << line << endl;
         return 0;
     }

Because line does not contain a newline, we must write our own if we want the strings written one to a line. As usual, we use endl to write a newline and flush the output buffer.
由于 line 不含换行符,若要逐行输出需要自行添加。照常,我们用 endl 来输出一个换行符并刷新输出缓冲区。
The newline that causes getline to return is discarded; it does not get stored in the string.
由于 getline 函数返回时丢弃换行符,换行符将不会存储在 string 对象中。

Exercises Section 3.2.2
Exercise 3.5:Write a program to read the standard input a line at a time. Modify your program to read a word at a time.
编写程序实现从标准输入每次读入一行文本。然后改写程序,每次读入一个单词。
Exercise 3.6:Explain how whitespace characters are handled in the string input operator and in the getline function.
解释 string 类型的输入操作符和 getline 函数分别如何处理空白字符。


3.2.3. Operations on strings
3.2.3. string 对象的操作
Table 3.2 on the next page lists the most commonly used string operations.
表 3.2 列出了常用的 string 操作。
Table 3.2. string Operations
s.empty()Returns true if s is empty; otherwise returns false
如果 s 为空串,则返回 true,否则返回 false。
s.size()Returns number of characters in s
返回 s 中字符的个数
s[n]Returns the character at position n in s; positions start at 0.
返回 s 中位置为 n 的字符,位置从 0 开始计数
s1 + s2Returns a string equal to the concatenation of s1 and s2
把 s1 和s2 连接成一个新字符串,返回新生成的字符串
s1 = s2Replaces characters in s1 by a copy of s2
把 s1 内容替换为 s2 的副本
v1 == v2Returns true if v1 and v2 are equal; false otherwise
比较 v1 与 v2的内容,相等则返回 true,否则返回 false
!=, <, <=, >, and >=Have their normal meanings
保持这些操作符惯有的含义

The string size and empty Operations
string 的 size 和 empty 操作
The length of a string is the number of characters in the string. It is returned by the size operation:
string 对象的长度指的是 string 对象中字符的个数,可以通过 size 操作获取:
     int main()
     {
         string st("The expense of spirit\n");
         cout << "The size of " << st << "is " << st.size()
              << " characters, including the newline" << endl;
         return 0;
     }

If we compile and execute this program it yields
编译并运行这个程序,得到的结果为:
     The size of The expense of spirit
     is 22 characters, including the newline

Often it is useful to know whether a string is empty. One way we could do so would be to compare size with 0:
了解 string 对象是否空是有用的。一种方法是将 size 与 0 进行比较:
     if (st.size() == 0)
          // ok: empty

In this case, we don't really need to know how many characters are in the string; we are only interested in whether the size is zero. We can more directly answer this question by using the empty member:
本例中,程序员并不需要知道 string 对象中有多少个字符,只想知道 size 是否为 0。用 string 的成员函数 empty() 可以更直接地回答这个问题:
     if (st.empty())
          // ok: empty

The empty function returns the bool (Section 2.1, p. 34) value true if the string contains no characters; otherwise, it returns false.
empty() 成员函数将返回 bool(2.1 节),如果 string 对象为空则返回 true 否则返回 false。
string::size_type
string::size_type 类型
It might be logical to expect that size returns an int, or, thinking back to the note on page 38, an unsigned. Instead, the size operation returns a value of type string::size_type. This type requires a bit of explanation.
从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或如 2.2 节“建议”中所述的无符号整数。但事实上,size 操作返回的是 string::size_type 类型的值。我们需要对这种类型做一些解释。
 
The string classand many other library typesdefines several companion types. These companion types make it possible to use the library types in a machine-independent manner. The type size_type is one of these companion types. It is defined as a synonym for an unsigned typeeither unsigned int or unsigned longthat is guaranteed to be big enough to hold the size of any string. To use the size_type defined by string, we use the scope operator to say that the name size_type is defined in the string class.
string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。size_type 就是这些配套类型中的一种。它定义为与 unsigned 型(unsigned int 或 unsigned long)具有相同的含义,而且可以保证足够大能够存储任意 string 对象的长度。为了使用由 string 类型定义的 size_type 类型是由 string 类定义。
Any variable used to store the result from the string size operation ought to be of type string::size_type. It is particularly important not to assign the return from size to an int.
任何存储 string 的 size 操作结果的变量必须为 string::size_type 类型。特别重要的是,还要把 size 的返回值赋给一个 int 变量。

Although we don't know the precise type of string::size_type, wedo know that it is an unsigned type (Section 2.1.1, p. 34). We also know that for a given type, the unsigned version can hold a positive value twice as large as the corresponding signed type can hold. This fact implies that the largest string could be twice as large as the size an int can hold.
虽然我们不知道 string::size_type 的确切类型,但可以知道它是 unsigned 型(2.1.1 节)。对于任意一种给定的数据类型,它的 unsigned 型所能表示的最大正数值比对应的 signed 型要大倍。这个事实表明 size_type 存储的 string 长度是 int 所能存储的两倍。
Another problem with using an int is that on some machines the size of an int is too small to hold the size of even plausibly large strings. For example, if a machine has 16-bit ints, then the largest string an int could represent would have 32,767 characters. A string that held the contents of a file could easily exceed this size. The safest way to hold the size of a string is to use the type the library defines for this purpose, which is string::size_type.
使用 int 变量的另一个问题是,有些机器上 int 变量的表示范围太小,甚至无法存储实际并不长的 string 对象。如在有 16 位 int 型的机器上,int 类型变量最大只能表示 32767 个字符的 string 个字符的 string 对象。而能容纳一个文件内容的 string 对象轻易就会超过这个数字。因此,为了避免溢出,保存一个 stirng 对象 size 的最安全的方法就是使用标准库类型 string::size_type。
 
The string Relational Operators
string 关系操作符
The string class defines several operators that compare two string values. Each of these operators works by comparing the characters from each string.
string 类定义了几种关系操作符用来比较两个 string 值的大小。这些操作符实际上是比较每个 string
string comparisons are case-sensitivethe upper- and lowercase versions of a letter are different characters. On most computers, the uppercase letters come first: Every uppercase letter is less than any lowercase letter.
string 对象比较操作是区分大小写的,即同一个字符的大小写形式被认为是两个不同的字符。在多数计算机上,大写的字母位于小写之前:任何一个大写之母都小于任意的小写字母。

The equality operator compares two strings, returning true if they are equal. Two strings are equal if they are the same length and contain the same characters. The library also defines != to test whether two strings are unequal.
== 操作符比较两个 string 对象,如果它们相等,则返回 true。两个 string 对象相等是指它们的长度相同,且含有相同的字符。标准库还定义了 != 操作符来测试两个 string 对象是否不等。
The relational operators <, <=, >, >= test whether one string is less than, less than or equal, greater than, or greater than or equal to another:
关系操作符 <,<=,>,>= 分别用于测试一个 string 对象是否小于、小于或等于、大于、大于或等于另一个 string 对象:
 
     string big = "big", small = "small";
     string s1 = big;    // s1 is a copy of big
     if (big == small)   // false
         // ...
     if (big <= s1)      // true, they're equal, so big is less than or equal to s1
         // ...

The relational operators compare strings using the same strategy as in a (case-sensitive) dictionary:
关系操作符比较两个 string 对象时采用了和(大小写敏感的)字典排序相同的策略:
If two strings have different lengths and if every character in the shorter string is equal to the corresponding character of the longer string, then the shorter string is less than the longer one.
如果两个 string 对象长度不同,且短的 string 对象与长的 string 对象的前面部分相匹配,则短的 string 对象小于长的 string 对象。
If the characters in two strings differ, then we compare them by comparing the first character at which the strings differ.
如果 string 对象的字符不同,则比较第一个不匹配的字符。string
As an example, given the strings
举例来说,给定 string 对象;
     string substr = "Hello";
     string phrase = "Hello World";
     string slang  = "Hiya";

then substr is less than phrase, and slang is greater than either substr or phrase.
则 substr 小于 phrase,而 slang 则大于 substr 或 phrase
Assignment for strings
string 对象的赋值
In general the library types strive to make it as easy to use a library type as it is to use a built-in type. To this end, most of the library types support assignment. In the case of strings, we can assign one string object to another:
总体上说,标准库类型尽量设计得和基本数据类型一样方便易用。因此,大多数库类型支持赋值操作。对 string 对象来说,可以把一个 string 对象赋值给另一个 string 对象;
     // st1 is an empty string, st2 is a copy of the literal
     string st1, st2 = "The expense of spirit";
     st1 = st2; // replace st1 by a copy of st2

After the assignment, st1 contains a copy of the characters in st2.
赋值操作后,st1 就包含了 st2 串所有字符的一个副本。
Most string library implementations go to some trouble to provide efficient implementations of operations such as assignment, but it is worth noting that conceptually, assignment requires a fair bit of work. It must delete the storage containing the characters associated with st1, allocate the storage needed to contain a copy of the characters associated with st2, and then copy those characters from st2 into this new storage.
大多数 string 库类型的赋值等操作的实现都会遇到一些效率上的问题,但值得注意的是,从概念上讲,赋值操作确实需要做一些工作。它必须先把 st1 占用的相关内存释放掉,然后再分配给 st2 足够存放 st2 副本的内存空间,最后把 st2 中的所有字符复制到新分配的内存空间。
 
Adding Two strings
两个 string 对象相加
Addition on strings is defined as concatenation. That is, it is possible to concatenate two or more strings through the use of either the plus operator (+) or the compound assignment operator (+=) (Section 1.4.1, p. 13). Given the two strings
string 对象的加法被定义为连接(concatenation)。也就是说,两个(或多个)string 对象可以通过使用加操作符 + 或者复合赋值操作符 +=(1.4.1 节)连接起来。给定两个 string 对象:
 
     string s1("hello, ");
     string s2("world\n");

we can concatenate the two strings to create a third string as follows:
下面把两个 string 对象连接起来产生第三个 string 对象:
     string s3 = s1 + s2;   // s3 is hello, world\n

If we wanted to append s2 to s1 directly, then we would use +=:
如果要把 s2 直接追加到 s1 的末尾,可以使用 += 操作符:
     s1 += s2;   // equivalent to s1 = s1 + s2

Adding Character String Literals and strings
和字符串字面值的连接
The strings s1 and s2 included punctuation directly. We could achieve the same result by mixing string objects and string literals as follows:
上面的字符串对象 s1 和 s2 直接包含了标点符号。也可以通过将 string 对象和字符串字面值混合连接得到同样的结果:
     string s1("hello");
     string s2("world");

     string s3 = s1 + ", " + s2 + "\n";

When mixing strings and string literals, at least one operand to each + operator must be of string type:
当进行 string 对象和字符串字面值混合连接操作时,+ 操作符的左右操作数必须至少有一个是 string 类型的:
     string s1 = "hello";   // no punctuation
     string s2 = "world";
     string s3 = s1 + ", ";           // ok: adding a string and a literal
     string s4 = "hello" + ", ";      // error: no string operand
     string s5 = s1 + ", " + "world"; // ok: each + has string operand
     string s6 = "hello" + ", " + s2; // error: can't add string literals

The initializations of s3 and s4 involve only a single operation. In these cases, it is easy to determine that the initialization of s3 is legal: We initialize s3 by adding a string and a string literal. The initialization of s4 attempts to add two string literals and is illegal.
s3 和 s4 的初始化只用了一个单独的操作。在这些例子中,很容易判断 s3 的初始化是合法的:把一个 string 对象和一个字符串字面值连接起来。而 s4 的初始化试图将两个字符串字面值相加,因此是非法的。
The initialization of s5 may appear surprising, but it works in much the same way as when we chain together input or output expressions (Section 1.2, p. 5). In this case, the string library defines addition to return a string. Thus, when we initialize s5, the subexpression s1 + ", " returns a string, which can be concatenated with the literal "world\n". It is as if we had written
s5 的初始化方法显得有点不可思议,但这种用法和标准输入输出的串联效果是一样的(1.2 节)。本例中,string 标准库定义加操作返回一个 string 对象。这样,在对 s5 进行初始化时,子表达式 s1 + ", " 将返回一个新 string 对象,后者再和字面值 "world\n"连接。整个初始化过程可以改写为:
 
     string tmp = s1 + ", "; // ok: + has a string operand
     s5 = tmp + "world";     // ok: + has a string operand

On the other hand, the initialization of s6 is illegal. Looking at each subexpression in turn, we see that the first subexpression adds two string literals. There is no way to do so, and so the statement is in error.
而 s6 的初始化是非法的。依次来看每个子表达式,则第一个子表达式试图把两个字符串字面值连接起来。这是不允许的,因此这个语句是错误的。
Fetching a Character from a string
从 string 对象获取字符
The string type uses the subscript ([ ]) operator to access the individual characters in the string. The subscript operator takes a size_type value that denotes the character position we wish to fetch. The value in the subscript is often called "the subscript" or "an index."
string 类型通过下标操作符([ ])来访问 string 对象中的单个字符。下标操作符需要取一个 size_type 类型的值,来标明要访问字符的位置。这个下标中的值通常被称为“下标”或“索引”(index)
 
Subscripts for strings start at zero; if s is a string, then if s isn't empty, s[0] is the first character in the string, s[1] is the second if there is one, and the last character is in s[s.size() - 1].
string 对象的下标从 0 开始。如果 s 是一个 string 对象且 s 不空,则 s[0] 就是字符串的第一个字符, s[1] 就表示第二个字符(如果有的话),而 s[s.size() - 1] 则表示 s 的最后一个字符。

It is an error to use an index outside this range.
引用下标时如果超出下标作用范围就会引起溢出错误。
We could use the subscript operator to print each character in a string on a separate line:
可用下标操作符分别取出 string 对象的每个字符,分行输出:
     string str("some string");
     for (string::size_type ix = 0; ix != str.size(); ++ix)
         cout << str[ix] << endl;

On each trip through the loop we fetch the next character from str, printing it followed by a newline.
每次通过循环,就从 str 对象中读取下一个字符,输出该字符并换行。
Subscripting Yields an Lvalue
下标操作可用作左值
Recall that a variable is an lvalue (Section 2.3.1, p. 45), and that the left-hand side of an assignment must be an lvalue. Like a variable, the value returned by the subscript operator is an lvalue. Hence, a subscript can be used on either side of an assignment. The following loop sets each character in str to an asterisk:
前面说过,变量是左值(2.3.1 节),且赋值操作的左操作的必须是左值。和变量一样,string 对象的下标操作返回值也是左值。因此,下标操作可以放于赋值操作符的左边或右边。通过下面循环把 str 对象的每一个字符置为 ‘*’:
     for (string::size_type ix = 0; ix != str.size(); ++ix)
         str[ix] = '*';

Computing Subscript Values
计算下标值
Any expression that results in an integral value can be used as the index to the subscript operator. For example, assuming someval and someotherval are integral objects, we could write
任何可产生整型值的表达式可用作下标操作符的索引。例如,假设 someval 和 someotherval 是两个整形对象,可以这样写:
     str[someotherval * someval] = someval;

Although any integral type can be used as an index, the actual type of the index is string::size_type, which is an unsigned type.
虽然任何整型数值都可作为索引,但索引的实际数据类型却是类型 unsigned 类型 string::size_type。
The same reasons to use string::size_type as the type for a variable that holds the return from size apply when defining a variable to serve as an index. A variable used to index a string should have type string::size_type.
前面讲过,应该用 string::size_type 类型的变量接受 size 函数的返回值。在定义用作索引的变量时,出于同样的道理,string 对象的索引变量最好也用 string::size_type 类型。

When we subscript a string, we are responsible for ensuring that the index is "in range." By in range, we mean that the index is a number that, when assigned to a size_type, is a value in the range from 0 through the size of the string minus one. By using a string::size_type or another unsigned type as the index, we ensure that the subscript cannot be less than zero. As long as our index is an unsigned type, we need only check that it is less than the size of the string.
在使用下标索引 string 对象时,必须保证索引值“在上下界范围内”。“在上下界范围内”就是指索引值是一个赋值为 size_type 类型的值,其取值范围在 0 到 string 对象长度减 1 之间。使用 string::size_type 类型或其他 unsigned 类型,就只需要检测它是否小于 string 对象的长度。
The library is not required to check the value of the index. Using an index that is out of range is undefined and usually results in a serious run-time error.
标准库不要求检查索引值,所用索引的下标越界是没有定义的,这样往往会导致严重的运行时错误。

3.2.4. Dealing with the Characters of a string
3.2.4. string 对象中字符的处理
Often we want to process the individual characters of a string. For example, we might want to know if a particular character is a whitespace character or whether the character is alphabetic or numeric. Table 3.3 on the facing page lists the functions that can be used on the characters in a string (or on any other char value). These functions are defined in the cctype header.
我们经常要对 string 对象中的单个字符进行处理,例如,通常需要知道某个特殊字符是否为空白字符、字母或数字。表 3.3 列出了各种字符操作函数,适用于 string 对象的字符(或其他任何 char 值)。这些函数都在 cctype 头文件中定义。
Table 3.3. cctype Functions
isalnum(c)True if c is a letter or a digit.
如果 c 是字母或数字,则为 True。
isalpha(c)true if c is a letter.
如果 c 是字母,则为 true。
iscntrl(c)true if c is a control character.
如果 c 是控制字符,则为 true
isdigit(c)true if c is a digit.
如果 c 是数字,则为 true。
isgraph(c)true if c is not a space but is printable.
如果 c 不是空格,但可打印,则为 true。
islower(c)true if c is a lowercase letter.
如果 c 是小写字母,则为 true。
isprint(c)True if c is a printable character.
如果 c 是可打印的字符,则为 true。
ispunct(c)True if c is a punctuation character.
如果 c 是标点符号,则 true。
isspace(c)true if c is whitespace.
如果 c 是空白字符,则为 true。
isupper(c)True if c is an uppercase letter.
如果 c 是大写字母,则 true。
isxdigit(c)true if c is a hexadecimal digit.
如果是 c 十六进制数,则为 true。
tolower(c)If c is an uppercase letter, returns its lowercase equivalent; otherwise returns c unchanged.
如果 c 大写字母,返回其小写字母形式,否则直接返回 c。
toupper(c)If c is a lowercase letter, returns its uppercase equivalent; otherwise returns c unchanged.
如果 c 是小写字母,则返回其大写字母形式,否则直接返回 c。

These functions mostly test the given character and return an int, which acts as a truth value. Each function returns zero if the test fails; otherwise, they return a (meaningless) nonzero value indicating that the character is of the requested kind.
表中的大部分函数是测试一个给定的字符是否符合条件,并返回一个 int 作为真值。如果测试失败,则该函数返回 0 ,否则返回一个(无意义的)非 0 ,表示被测字符符合条件。
For these functions, a printable character is a character with a visible representation; whitespace is one of space, tab, vertical tab, return, newline, and formfeed; and punctuation is a printable character that is not a digit, a letter, or (printable) whitespace character such as space.
表中的这些函数,可打印的字符是指那些可以表示的字符,空白字符则是空格、制表符、垂直制表符、回车符、换行符和进纸符中的任意一种;标点符号则是除了数字、字母或(可打印的)空白字符(如空格)以外的其他可打印字符。
As an example, we could use these functions to print the number of punctuation characters in a given string:
这里给出一个例子,运用这些函数输出一给定 string 对象中标点符号的个数:
     string s("Hello World!!!");
     string::size_type punct_cnt = 0;
     // count number of punctuation characters in s
     for (string::size_type index = 0; index != s.size(); ++index)
         if (ispunct(s[index]))
             ++punct_cnt;
     cout << punct_cnt
          << " punctuation characters in " << s << endl;

The output of this program is
这个程序的输出结果是:
     3 punctuation characters in Hello World!!!

Rather than returning a truth value, the tolower and toupper functions return a charactereither the argument unchanged or the lower- or uppercase version of the character. We could use tolower to change s to lowercase as follows:
和返回真值的函数不同的是,tolower 和 toupper 函数返回的是字符,返回实参字符本身或返回该字符相应的大小写字符。我们可以用 tolower 函数把 string 对象 s 中的字母改为小写字母,程序如下:
     // convert s to lowercase
     for (string::size_type index = 0; index != s.size(); ++index)
         s[index] = tolower(s[index]);
     cout << s << endl;

which generates
得到的结果为:
     hello world!!!

Advice: Use the C++ Versions of C Library Headers
建议:采用 C 标准库头文件的 C++ 版本
In addition to facilities defined specifically for C++, the C++ library incorporates the C library. The cctype header makes available the C library functions defined in the C header file named ctype.h.
C++ 标准库除了定义了一些选定于 C++ 的设施外,还包括 C 标准库。C++ 中的头文件 cctype 其实就是利用了 C 标准库函数,这些库函数就定义在 C 标准库的 ctype.h 头文件中。
The standard C headers names use the form name.h. The C++ versions of these headers are named cnamethe C++ versions remove the .h suffix and precede the name by the letter c. Thec indicates that the header originally comes from the C library. Hence, cctype has the same contents as ctype.h, but in a form that is appropriate for C++ programs. In particular, the names defined in the cname headers are defined inside the std namespace, whereas those defined in the .h versions are not.
C 标准库头文件命名形式为 name 而 C++ 版本则命名为 cname ,少了后缀,.h 而在头文件名前加了 c 表示这个头文件源自 C 标准库。因此,cctype 与 ctype.h 文件的内容是一样的,只是采用了更适合 C++程序的形式。特别地,cname 头文件中定义的名字都定义在命名空间 std 内,而 .h 版本中的名字却不是这样。
Ordinarily, C++ programs should use the cname versions of headers and not the name.h versions. That way names from the standard library are consistently found in the std namespace. Using the .h headers puts the burden on the programmer to remember which library names are inherited from C and which are unique to C++.
通常,C++ 程序中应采用 cname 这种头文件的版本,而不采用 name.h 版本,这样,标准库中的名字在命名空间 std 中保持一致。使用 .h 版本会给程序员带来负担,因为他们必须记得哪些标准库名字是从 C 继承来的,而哪些是 C++ 所特有的。
Exercises Section 3.2.4
Exercise 3.7:Write a program to read two strings and report whether the strings are equal. If not, report which of the two is the larger. Now, change the program to report whether the strings have the same length and if not report which is longer.
编一个程序读入两个 string 对象,测试它们是否相等。若不相等,则指出两个中哪个较大。接着,改写程序测试它们的长度是否相等,若不相等指出哪个较长。
Exercise 3.8:Write a program to read strings from the standard input, concatenating what is read into one large string. Print the concatenated string. Next, change the program to separate adjacent input strings by a space.
编一个程序,从标准输入读取多个 string 对象,把它们连接起来存放到一个更大的 string 对象中。并输出连接后的 string 对象。接着,改写程序,将连接后相邻 string 对象以空格隔开。
Exercise 3.9:What does the following program do? Is it valid? If not, why not?
下列程序实现什么功能?实现合法?如果不合法,说明理由。
     string s;
     cout << s[0] << endl;


Exercise 3.10:Write a program to strip the punctuation from a string. The input to the program should be a string of characters including punctuation; the output should be a string in which the punctuation is removed.
编一个程序,从 string 对象中去掉标点符号。要求输入到程序的字符串必须含有标点符号,输出结果则是去掉标点符号后的 string 对象。

 

     

3.3. Library vector Type
3.3. 标准库 vector 类型
A vector is a collection of objects of a single type, each of which has an associated integer index. As with strings, the library takes care of managing the memory associated with storing the elements. We speak of a vector as a container because it contains other objects. All of the objects in a container must have the same type. We'll have much more to say about containers in Chapter 9.
vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和 string 对象一样,标准库将负责管理与存储元素相关的内存。我们把 vector 称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种类型的。我们将在第九章更详细地介绍容器。
To use a vector, we must include the appropriate header. In our examples, we also assume an appropriate using declaration is made:
使用 vector 之前,必须包含相应的头文件。本书给出的例子,都是假设已作了相应的 using 声明:
     #include <vector>
     using std::vector;



A vector is a class template. Templates let us write a single class or function definition that can be used on a variety of types. Thus, we can define a vector that holds strings, or a vector to hold ints, or one to hold objects of our own class types, such as Sales_items. We'll see how to define our own class templates in Chapter 16. Fortunately, we need to know very little about how templates are defined in order to use them.
vector 是一个类模板(class template)。使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存 string 对象的 vector,或保存 int 值的 vector,又或是保存自定义的类类型对象(如 Sales_items 对象)的 vector。将在第十六章介绍如何定义程序员自己的类模板。幸运的是,使用类模板时只需要简单了解类模板是如何定义的就可以了。
To declare objects of a type generated from a class template, we must supply additional information. The nature of this information depends on the template. In the case of vector, we must say what type of objects the vector will contain. We specify the type by putting it between a pair of angle brackets following the template's name:
声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以 vector 为例,必须说明 vector 保存何种对象的类型,通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:
     vector<int> ivec;               // ivec holds objects of type int
     vector<Sales_item> Sales_vec;   // holds Sales_items



As in any variable definition, we specify a type and a list of one or more variables. In the first of these definitions, the type is vector<int>, which is a vector that holds objects of type int. The name of the variable is ivec. In the second, we define Sales_vec to hold Sales_item objects.
和其他变量定义一样,定义 vector 对象要指定类型和一个变量的列表。上面的第一个定义,类型是 vector<int>,该类型即是含有若干 int 类型对象的 vector,变量名为 ivec。第二个定义的变量名是 Sales_vec,它所保存的元素是 Sales_item 类型的对象。
vector is not a type; it is a template that we can use to define any number of types. Each of vector type specifies an element type. Hence, vector<int> and vector<string> are types.
vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector<int> 和 vector<string> 都是数据类型。




3.3.1. Defining and Initializing vectors
3.3.1. vector 对象的定义和初始化
The vector class defines several constructors (Section 2.3.3, p. 49), which we use to define and initialize vector objects. The constructors are listed in Table 3.4.
vector 类定义了好几种构造函数(2.3.3 节),用来定义和初始化 vector 对象。表 3.4 列出了这些构造函数。
Table 3.4. Ways to Initialize a vectorvector<T> v1;vector that holds objects of type T;
 Default constructor v1 is empty
 vector 保存类型为 T 对象。
 默认构造函数 v1 为空。
vector<T> v2(v1);v2 is a copy of v1
v2 是 v1 的一个副本。
vector<T> v3(n, i);v3 has n elements with value i
v3 包含 n 个值为 i 的元素。
vector<T> v4(n);v4 has n copies of a value-initialized object
v4 含有值初始化的元素的 n 个副本。



Creating a Specified Number of Elements
创建确定个数的元素
When we create a vector that is not empty, we must supply value(s) to use to initialize the elements. When we copy one vector to another, each element in the new vector is initialized as a copy of the corresponding element in the original vector. The two vectors must hold the same element type:
若要创建非空的 vector 对象,必须给出初始化元素的值。当把一个 vector 对象复制到另一个 vector 对象时,新复制的 vector 中每一个元素都初始化为原 vectors 中相应元素的副本。但这两个 vector 对象必须保存同一种元素类型:
     vector<int> ivec1;           // ivec1 holds objects of type int
     vector<int> ivec2(ivec1);    // ok: copy elements of ivec1 into ivec2
     vector<string> svec(ivec1);  // error: svec holds strings, not ints



We can initialize a vector from a count and an element value. The constructor uses the count to determine how many elements the vector should have and uses the value to specify the value each of those elements will have:
可以用元素个数和元素值对 vector 对象进行初始化。构造函数用元素个数来决定 vector 对象保存元素的个数,元素值指定每个元素的初始值:
     vector<int> ivec4(10, -1);       // 10 elements, each initialized to -1
     vector<string> svec(10, "hi!");  // 10 strings, each initialized to "hi!"



Key Concept: vectorS Grow Dynamically
关键概念:vector 对象动态增长
A central property of vectors (and the other library containers) is that they are required to be implemented so that it is efficient to add elements to them at run time. Because vectors grow efficiently, it is usually best to let the vector grow by adding elements to it dynamically as the element values are known.
vector 对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。因为 vector 增长的效率高,在元素值已知的情况下,最好是动态地添加元素。
As we'll see in Chapter 4, this behavior is distinctly different from that of built-in arrays in C and for that matter in most other languages. In particular, readers accustomed to using C or Java might expect that because vector elements are stored contiguously, it would be best to preallocate the vector at its expected size. In fact, the contrary is the case, for reasons we'll explore in Chapter 9.
正如第四章将介绍的,这种增长方式不同于 C 语言中的内置数据类型,也不同于大多数其他编程语言的数据类型。具体而言,如果读者习惯了 C 或 Java 的风格,由于 vector 元素连续存储,可能希望最好是预先分配合适的空间。但事实上,为了达到连续性,C++ 的做法恰好相反,具体原因将在第九章探讨。
 Although we can preallocate a given number of elements in a vector, it is usually more efficient to define an empty vector and add elements to it (as we'll learn how to do shortly).
虽然可以对给定元素个数的 vector 对象预先分配内存,但更有效的方法是先初始化一个空 vector 对象,然后再动态地增加元素(我们随后将学习如何进行这样的操作)。





Value Initialization
值初始化
When we do not specify an element initializer, then the library creates a value initialized element initializer for us. This library-generated value is used to initialize each element in the container. The value of the element initializer depends on the type of the elements stored in the vector.
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(value initializationd)。这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在 vector 中元素的数据类型。
If the vector holds elements of a built-in type, such as int, then the library creates an element initializer with a value of 0:
如果 vector 保存内置类型(如 int 类型)的元素,那么标准库将用 0 值创建元素初始化式:
     vector<string> fvec(10); // 10 elements, each initialized to 0



If the vector holds elements of a class type, such as string, that defines its own constructors, then the library uses the value type's default constructor to create the element initializer:
如果 vector 保存的是含有构造函数的类类型(如 string)的元素,标准库将用该类型的默认构造函数创建元素初始化式:
     vector<string> svec(10); // 10 elements, each an empty string



As we'll see in Chapter 12, some classes that define their own constructors do not define a default constructor. We cannot initialize a vector of such a type by specifying only a size; we must also specify an initial element value.
第十二章将介绍一些有自定义构造函数但没有默认构造函数的类,在初始化这种类型的 vector 对象时,程序员就不能仅提供元素个数,还需要提供元素初始值。




There is a third possibility: The element type might be of a class type that does not define any constructors. In this case, the library still creates a value-initialized object. It does so by value-initializing each member of that object.
还有第三种可能性:元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。
Exercises Section 3.3.1
Exercise 3.11:Which, if any, of the following vector definitions are in error?
下面哪些 vector 定义不正确?
     (a) vector< vector<int> > ivec;
     (b) vector<string> svec = ivec;
     (c) vector<string> svec(10, "null");



Exercise 3.12:How many elements are there in each of the following vectors? What are the values of the elements?
下列每个 vector 对象中元素个数是多少?各元素的值是什么?
     (a) vector<int> ivec1;
     (b) vector<int> ivec2(10);
     (c) vector<int> ivec3(10, 42);
     (d) vector<string> svec1;
     (e) vector<string> svec2(10);
     (f) vector<string> svec3(10, "hello");







3.3.2. Operations on vectors
3.3.2. vector 对象的操作
The vector library provides various operations, many of which are similar to operations on strings. Table 3.5 lists the most important vector operations.
vector 标准库提供了许多类似于 string 对象的操作,表 3.5 列出了几种最重要的 vector 操作。
Table 3.5. vector Operationsv.empty()Returns true if v is empty; otherwise returns false
如果 v 为空,则返回 true,否则返回 false。
v.size()Returns number of elements in v
返回 v 中元素的个数。
v.push_back(t)Adds element with value t to end of v
在 v 的末尾增加一个值为 t 的元素。
v[n]Returns element at position n in v
返回 v 中位置为 n 的元素。
v1 = v2Replaces elements in v1 by a copy of elements in v2
把 v1 的元素替换为 v2 中元素的副本。
v1 == v2Returns true if v1 and v2 are equal
如果 v1 与 v2 相等,则返回 true。
!=, <, <=,
>, and >=Have their normal meanings
保持这些操作符惯有的含义。



The size of a vector
vector 对象的 size
The empty and size operations are similar to the corresponding string operations (Section 3.2.3, p. 83). The size member returns a value of the size_type defined by the corresponding vector type.
empty 和 size 操作类似于 string 的相关操作(3.2.3 节)。成员函数 size 返回相应 vector 类定义的 size_type 的值。
 To use size_type, we must name the type in which it is defined. A vector type always includes the element type of the vector:
使用 size_type 类型时,必须指出该类型是在哪里定义的。vector 类型总是包括总是包括 vector 的元素类型:


     vector<int>::size_type        // ok
     vector::size_type            // error





Adding Elements to a vector
向 vector 添加元素
The
push_back operation takes an element value and adds that value as a new element at the back of a vector. In effect it "pushes" an element onto the "back" of the vector:
push_back 操作接受一个元素值,并将它作为一个新的元素添加到 vector 对象的后面,也就是“插入(push)”到 vector 对象的“后面(back)”:
     // read words from the standard input and store them as elements in a vector
     string word;
     vector<string> text;    // empty vector
     while (cin >> word) {
         text.push_back(word);     // append word to text
     }



This loop reads a sequence of strings from the standard input, appending them one at a time onto the back of the vector. We start by defining text as an initially empty vector. Each trip through the loop adds a new element to the vector and gives that element the value of whatever word was read from the input. When the loop completes, text will have as many elements as were read.
该循环从标准输入读取一系列 string 对象,逐一追加到 vector 对象的后面。首先定义一个空的 vector 对象 text。每循环一次就添加一个新元素到 vector 对象,并将从输入读取的 word 值赋予该元素。当循环结束时,text 就包含了所有读入的元素。
Subscripting a vector
vector 的下标操作
Objects in the vector are not named. Instead, they can be accessed by their position in the vector. We can fetch an element using the subscript operator. Subscripting a vector is similar to subscripting a string (Section 3.2.3, p. 87).
vector 中的对象是没有命名的,可以按 vector 中对象的位置来访问它们。通常使用下标操作符来获取元素。vector 的下标操作类似于 string 类型的下标操作(3.2.3 节)。.
The vector subscript operator takes a value and returns the element at that position in the vector. Elements in a vector are numbered beginning with 0. The following example uses a for loop to reset each element in the vector to 0:
vector 的下标操作符接受一个值,并返回 vector 中该对应位置的元素。vector 元素的位置从 0 开始。下例使用 for 循环把 vector 中的每个元素值都重置为 0:
     // reset the elements in the vector to zero
     for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
         ivec[ix] = 0;



Like the string subscript operator, the vector subscript yields an lvalue so that we may write to it, which we do in the body of the loop. Also, as we do for strings, we use the size_type of the vector as the type for the subscript.
和 string 类型的下标操作符一样,vector 下标操作的结果为左值,因此可以像循环体中所做的那样实现写入。另外,和 string 对象的下标操作类似,这里用 size_type 类型作为 vector 下标的类型。
 Even if ivec is empty, this for loop executes correctly. If ivec is empty, the call to size returns 0 and the test in the for compares ix to 0. Because ix is itself 0 on the first trip, the test would fail and the loop body would not be executed even once.
在上例中,即使 ivec 为空,for 循环也会正确执行。ivec 为空则调用 size 返回 0,并且 for 中的测试比较 ix 和 0。第一次循环时,由于 ix 本身就是 0 就是 0,则条件测试失败, for 循环体一次也不执行。



Subscripting Does Not Add Elements
下标操作不添加元素
Programmers new to C++ sometimes think that subscripting a vector adds elements; it does not:
初学 C++ 的程序员可能会认为 vector 的下标操作可以添加元素,其实不然:
     vector<int> ivec;   // empty vector
     for (vector<int>::size_type ix = 0; ix != 10; ++ix)
         ivec[ix] = ix; // disaster: ivec has no elements



Key Concept: Safe, Generic Programming
关键概念:安全的泛型编程
Programmers coming to C++ from C or Java might be surprised that our loop used != rather than < to test the index against the size of the vector. C programmers are probably also suprised that we call the size member in the for rather than calling it once before the loop and remembering its value.
习惯于 C 或 Java 编程的 C++ 程序员可能会觉得难以理解,for 循环的判断条件用 != 而不是用 < 来测试 vector 下标值是否越界。C 程序员难以理解的还有,上例中没有在 for 循环之前就调用 size 成员函数并保存其返回的值,而是在 for 语句头中调用 size 成员函数。
C++ programmers tend to write loops using != in preference to < as a matter of habit. In this case, there is no particular reason to choose one operator or the other. We'll understand the rationale for this habit once we cover generic programming in Part II.
C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。在上例中,选用或不用某种操作符并没有特别的取舍理由。学习完本书第二部分的泛型编程后,你将会明白这种习惯的合理性。
Calling size rather than remembering its value is similarly unnecessary in this case but again reflects a good habit. In C++, data structures such as vector can grow dynamically. Our loop only reads elements; it does not add them. However, a loop could easily add new elements. If the loop did add elements, then testing a saved value of size would failour loop would not account for the newly added elements. Because a loop might add elements, we tend to write our loops to test the current size on each pass rather than store a copy of what the size was when we entered the loop.
调用 size 成员函数而不保存它返回的值,在这个例子中同样不是必需的,但这反映了一种良好的编程习惯。在 C++ 中,有些数据结构(如 vector)可以动态增长。上例中循环仅需要读取元素,而不需要增加新的元素。但是,循环可以容易地增加新元素,如果确实增加了新元素的话,那么测试已保存的 size 值作为循环的结束条件就会有问题,因为没有将新加入的元素计算在内。所以我们倾向于在每次循环中测试 size 的当前值,而不是在进入循环前,存储 size 值的副本。
As we'll see in Chapter 7, in C++ functions can be declared to be inline. When it can do so, the compiler will expand the code for an inline function directly rather than actually making a function call. Tiny library functions such as size are almost surely defined to be inline, so we expect that there is little run-time cost in making this call on each trip through the loop.
我们将在第七章学习到,C++ 中有些函数可以声明为内联(inline)函数。编译器遇到内联函数时就会直接扩展相应代码,而不是进行实际的函数调用。像 size 这样的小库函数几乎都定义为内联函数,所以每次循环过程中调用它的运行时代价是比较小的。



This code intended to insert new 10 elements into ivec, giving the elements the values from 0 through 9. However, ivec is an empty vector and subscripts can only be used to fetch existing elements.
上述程序试图在 ivec 中插入 10 个新元素,元素值依次为 0 到 9 的整数。但是,这里 ivec 是空的 vector 对象,而且下标只能用于获取已存在的元素。
The right way to write this loop would be
这个循环的正确写法应该是:
     for (vector<int>::size_type ix = 0; ix != 10; ++ix)
         ivec.push_back(ix);  // ok: adds new element with value ix



 An element must exist in order to subscript it; elements are not added when we assign through a subscript.
必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加任何元素。




Caution: Only Subscript Elements that Are Known to Exist!
警告:仅能对确知已存在的元素进行下标操作
It is crucially important to understand that we may use the subscript operator, (the [] operator), to fetch only elements that actually exist. For example,
对于下标操作符([] 操作符)的使用有一点非常重要,就是仅能提取确实已存在的元素,例如:
     vector<int> ivec;      // empty vector
     cout << ivec[0];       // Error: ivec has no elements!

     vector<int> ivec2(10); // vector with 10 elements
     cout << ivec[10];      // Error: ivec has elements 0...9



Attempting to fetch an element that doesn't exist is a run-time error. As with most such errors, there is no assurance that the implementation will detect it. The result of executing the program is uncertain. The effect of fetching a nonexisting element is undefinedwhat happens will vary by implementation, but the program will almost surely fail in some interesting way at run time.
试图获取不存在的元素必须产生运行时错误。和大多数同类错误一样,不能确保执行过程可以捕捉到这类错误,运行程序的结果是不确定的。由于取不存在的元素的结果标准没有定义,因而不同的编译器实现会导致不同的结果,但程序运行时几乎肯定会以某种有趣的方式失败。
This caution applies any time we use a subscript, such as when subscripting a string and, as we'll see shortly, when subscripting a built-in array.
本警告适用于任何使用下标操作的时候,如 string 类型的下标操作,以及将要简要介绍的内置数组的下标操作。
Attempting to subscript elements that do not exist is, unfortunately, an extremely common and pernicious programming error. So-called "buffer overflow" errors are the result of subscripting elements that don't exist. Such bugs are the most common cause of security problems in PC and other applications.
不幸的是,试图对不存在的元素进行下标操作是程序设计过程中经常会犯的严重错误。所谓的“缓冲区溢出”错误就是对不存在的元素进行下标操作的结果。这样的缺陷往往导致 PC 机和其他应用中最常见的安全问题。



Exercises Section 3.3.2
Exercise 3.13:Read a set of integers into a vector. Calculate and print the sum of each pair of adjacent elements in the vector. If there is an odd number, tell the user and print the value of the last element without summing it. Now change your program so that it prints the sum of the first and last elements, followed by the sum of the second and second-to-last and so on.
读一组整数到 vector 对象,计算并输出每对相邻元素的和。如果读入元素个数为奇数,则提示用户最后一个元素没有求和,并输出其值。然后修改程序:头尾元素两两配对(第一个和最后一个,第二个和倒数第二个,以此类推),计算每对元素的和,并输出。
Exercise 3.14:Read some text into a vector, storing each word in the input as an element in the vector. transform each word into uppercase letters. Print the transformed elements from the vector, printing eight words to a line.
读入一段文本到 vector 对象,每个单词存储为 vector 中的一个元素。把 vector 对象中每个单词转化为大写字母。输出 vector 对象中转化后的元素,每八个单词为一行输出。
Exercise 3.15:Is the following program legal? If not, how might you fix it?
下面程序合法吗?如果不合法,如何更正?
     vector<int> ivec;
     ivec[0] = 42;



Exercise 3.16: List three ways to define a vector and give it 10 elements, each with the value 42. Indicate whether there is a preferred way to do so and why.
列出三种定义 vector 对象的方法,给定 10 个元素,每个元素值为 42。指出是否还有更好的实现方法,并说明为什么。




    

3.4. Introducing Iterators
3.4. 迭代器简介
While we can use subscripts to access the elements in a vector, the library also gives us another way to examine elements: We can use an iterator. An iterator is a type that lets us examine the elements in a container and navigate from one element to another.
除了使用下标来访问 vector 对象的元素外,标准库还提供了另一种访问元素的方法:使用迭代器(iterator)。迭代器是一种检查容器内元素并遍历元素的数据类型。
The library defines an iterator type for each of the standard containers, including vector. Iterators are more general than subscripts: All of the library containers define iterator types, but only a few of them support subscripting. Because iterators are common to all containers, modern C++ programs tend to use iterators rather than subscripts to access container elements, even on types such as vector that support subscripting.
标准库为每一种标准容器(包括 vector)定义了一种迭代器类型。迭代器类型提供了比下标操作更通用化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也是这样。
The details of how iterators work are discussed in Chapter 11, but we can use them without understanding them in their full complexity.
第十一章将详细讨论迭代器的工作原理,但使用迭代器并不需要完全了解它复杂的实现细节。
Container iterator Type
容器的 iterator 类型
Each of the container types, such as vector, defines its own iterator type:
每种容器类型都定义了自己的迭代器类型,如 vector:
     vector<int>::iterator iter;



This statement defines a variable named iter, whose type is the type named iterator defined by vector<int>. Each of the library container types defines a member named iterator that is a synonym for the actual type of its iterator.
这符语句定义了一个名为 iter 的变量,它的数据类型是 vector<int> 定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。
Terminology: Iterators and Iterator Types
术语:迭代器和迭代器类型
When first encountered, the nomenclature around iterators can be confusing. In part the confusion arises because the same term, iterator, is used to refer to two things. We speak generally of the concept of an iterator, and we speak specifically of a concrete iterator type defined by a container, such as vector<int>.
程序员首次遇到有关迭代器的术语时可能会困惑不解,原因之一是由于同一个术语 iterator 往往表示两个不同的事物。一般意义上指的是迭代器的概念;而具体而言时指的则是由容器定义的具体的 iterator 类型,如 vector<int>。
What's important to understand is that there is a collection of types that serve as iterators. These types are related conceptually. We refer to a type as an iterator if it supports a certain set of actions. Those actions let us navigate among the elements of a container and let us access the value of those elements.
重点要理解的是,有许多用作迭代器的类型,这些类型在概念上是相关的。若一种类型支持一组确定的操作(这些操作可用来遍历容器内的元素,并访问这些元素的值),我们就称这种类型为迭代器。
Each container class defines its own iterator type that can be used to access the elements in the container. That is, each container defines a type named iterator, and that type supports the actions of an (conceptual) iterator.
各容器类都定义了自己的 iterator 类型,用于访问容器内的元素。换句话说,每个容器都定义了一个名为 iterator 的类型,而这种类型支持(概念上的)迭代器的各种操作。



The begin and end Operations
begin 和 end 操作
Each container defines a pair of functions named begin and end that return iterators. The iterator returned by begin refers to the first element, if any, in the container:
每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。如果容器中有元素的话,由 begin 返回的迭代器指向第一个元素:
     vector<int>::iterator iter = ivec.begin();



This statement initializes iter to the value returned by the vector operation named begin. Assuming the vector is not empty, after this initialization, iter refers to the same element as ivec[0].
上述语句把 iter 初始化为由名为 vector 操作返回的值。假设 vector 不空,初始化后,iter 即指该元素为 ivec[0]。
The iterator returned by the end operation is an iterator positioned "one past the end" of the vector. It is often referred to as the off-the-end iterator indicating that it refers to a nonexistent element "off the end" of the vector. If the vector is empty, the iterator returned by begin is the same as the iterator returned by end.
由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”。“超出末端迭代器”(off-the-end iterator)。表明它指向了一个不存在的元素。如果 vector 为空,begin 返回的迭代器与 end 返回的迭代器相同。
 The iterator returned by the end operation does not denote an actual element in the vector. Instead, it is used as a sentinel indicating when we have processed all the elements in the vector.
由 end 操作返回的迭代器并不指向 vector 中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。




Dereference and Increment on vector Iterators
vector 迭代器的自增和解引用运算
The operations on iterator types let us retrieve the element to which an iterator refers and let us move an iterator from one element to another.
迭代器类型定义了一些操作来获取迭代器所指向的元素,并允许程序员将迭代器从一个元素移动到另一个元素。
Iterator types use the dereference operator (the * operator) to access the element to which the iterator refers:
迭代器类型可使用解引用操作符(dereference operator)(*)来访问迭代器所指向的元素:
     *iter = 0;



The dereference operator returns the element that the iterator currently denotes. Assuming iter refers to the first element of the vector, then *iter is the same element as ivec[0]. The effect of this statement is to assign 0 to that element.
解引用操作符返回迭代器当前所指向的元素。假设 iter 指向 vector 对象 ivec 的第一元素,那么 *iter 和 ivec[0] 就是指向同一个元素。上面这个语句的效果就是把这个元素的值赋为 0。
Iterators use the increment operator (++) (Section 1.4.1, p. 13) to advance an iterator to the next element in the container. Incrementing an iterator is a logically similar operation to the increment operator when applied to int objects. In the case of ints, the effect is to "add one" to the int's value. In the case of iterators, the effect is to "advance the iterator by one position" in the container. So, if iter refers to the first element, then ++iter denotes the second element.
迭代器使用自增操作符(1.4.1 节)向前移动迭代器指向容器中下一个元素。从逻辑上说,迭代器的自增操作和 int 型对象的自增操作类似。对 int 对象来说,操作结果就是把 int 型值“加 1”,而对迭代器对象则是把容器中的迭代器“向前移动一个位置”。因此,如果 iter 指向第一个元素,则 ++iter 指向第二个元素。
 Because the iterator returned from end does not denote an element, it may not be incremented or dereferenced.
由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。




Other Iterator Operations
迭代器的其他操作
Another pair of useful operations that we can perform on iterators is comparison: Two iterators can be compared using either == or !=. Iterators are equal if they refer to the same element; they are unequal otherwise.
另一对可执行于迭代器的操作就是比较:用 == 或 != 操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。
A Program that Uses Iterators
迭代器应用的程序示例
Assume we had a vector<int> named ivec and we wanted to reset each of its elements to zero. We might do so by using a subscript:
假设已声明了一个 vector<int> 型的 ivec 变量,要把它所有元素值重置为 0,可以用下标操作来完成:
     // reset all the elements in ivec to 0
     for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
             ivec[ix] = 0;



This program uses a for loop to iterate through the elements in ivec. The for defines an index, which it increments on each iteration. The body of the for sets each element in ivec to zero.
上述程序用 for 循环遍历 ivec 的元素,for 循环定义了一个索引 ix ,每循环迭代一次 ix 就自增 1。for 循环体将 ivec 的每个元素赋值为 0。
A more typical way to write this loop would use iterators:
更典型的做法是用迭代器来编写循环:
     // equivalent loop using iterators to reset all the elements in ivec to 0
     for (vector<int>::iterator iter = ivec.begin();
                                iter != ivec.end(); ++iter)
         *iter = 0;  // set element to which iter refers to 0



The for loop starts by defining iter and initializing it to refer to the first element in ivec. The condition in the for tests whether iter is unequal to the iterator returned by the end operation. Each iteration increments iter. The effect of this for is to start with the first element in ivec and process in sequence each element in the vector. Eventually, iter will refer to the last element in ivec. After we process the last element and increment iter, it will become equal to the value returned by end. At that point, the loop stops.
for 循环首先定义了 iter,并将它初始化为指向 ivec 的第一个元素。for 循环的条件测试 iter 是否与 end 操作返回的迭代器不等。每次迭代 iter 都自增 1,这个 for 循环的效果是从 ivec 第一个元素开始,顺序处理 vector 中的每一元素。最后, iter 将指向 ivec 中的最后一个元素,处理完最后一个元素后,iter 再增加 1,就会与 end 操作的返回值相等,在这种情况下,循环终止。
The statement in the for body uses the dereference operator to access the value of the current element. As with the subscript operator, the value returned by the dereference operator is an lvalue. We can assign to this element to change its value. The effect of this loop is to assign the value zero to each element in ivec.
for 循环体内的语句用解引用操作符来访问当前元素的值。和下标操作符一样,解引用操作符的返回值是一个左值,因此可以对它进行赋值来改变它的值。上述循环的效果就是把 ivec 中所有元素都赋值为 0。
Having walked through the code in detail, we can see that this program has exactly the same effect as the version that used subscripts: We start at the first element in the vector and set each element in the vector to zero.
通过上述对代码的详细分析,可以看出这段程序与用下标操作符的版本达到相同的操作效果:从 vector 的第一个元素开始,把 vector 中每个元素都置为 0。
 This program, like the one on page 94, is safe if the vector is empty. If ivec is empty, then the iterator returned from begin does not denote any element; it can't, because there are no elements. In this case, the iterator returned from begin is the same as the one returned from end, so the test in the for fails immediately.
本节给出的例子程序和 3.3.2 节 vector 的下标操作的程序一样,如果 vector 为空,程序是安全的。如果 ivec 为空,则 begin 返回的迭代器不指向任何元素——由于没有元素,所以它不能指向任何元素。在这种情况下,从 begin 操作返回的迭代器与从 end 操作返回的迭代器的值相同,因此 for 语句中的测试条件立即失败。




const_iterator
The previous program used a vector::iterator to change the values in the vector. Each container type also defines a type named const_iterator, which should be used when reading, but not writing to, the container elements.
前面的程序用 vector::iterator 改变 vector 中的元素值。每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。
When we dereference a plain iterator, we get a nonconst reference (Section 2.5, p. 59) to the element. When we dereference a const_iterator, the value returned is a reference to a const (Section 2.4, p. 56) object. Just as with any const variable, we may not write to the value of this element.
当我们对普通 iterator 类型解引用时,得到对某个元素的非 const(2.5 节)。而如果我们对 const_iterator 类型解引用时,则可以得到一个指向 const 对象的引用(2.4 节),如同任何常量一样,该对象不能进行重写。
For example, if text is a vector<string>, we might want to traverse it, printing each element. We could do so as follows:
例如,如果 text 是 vector<string> 类型,程序员想要遍历它,输出每个元素,可以这样编写程序:
     // use const_iterator because we won't change the elements
     for (vector<string>::const_iterator iter = text.begin();
                                   iter != text.end(); ++iter)
         cout << *iter << endl; // print each element in text



This loop is similar to the previous one, except that we are reading the value from the iterator, not assigning to it. Because we read, but do not write, through the iterator, we define iter to be a const_iterator. When we dereference a const_iterator, the value returned is const. We may not assign to an element using a const_iterator:
除了是从迭代器读取元素值而不是对它进行赋值之外,这个循环与前一个相似。由于这里只需要借助迭代器进行读,不需要写,这里把 iter 定义为 const_iterator 类型。当对 const_iterator 类型解引用时,返回的是一个 const 值。不允许用 const_iterator: 进行赋值
     for (vector<string>::const_iterator iter = text.begin();
                                  iter != text.end(); ++ iter)
         *iter = " ";     // error: *iter is const



When we use the const_iterator type, we get an iterator whose own value can be changed but that cannot be used to change the underlying element value. We can increment the iterator and use the dereference operator to read a value but not to assign to that value.
使用 const_iterator 类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。可以对迭代器进行自增以及使用解引用操作符来读取值,但不能对该元素赋值。
A const_iterator should not be confused with an iterator that is const. When we declare an iterator as const we must initialize the iterator. Once it is initialized, we may not change its value:
不要把 const_iterator 对象与 const 的 iterator 对象混淆起来。声明一个 const 迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值:
     vector<int> nums(10);  // nums is nonconst
     const vector<int>::iterator cit = nums.begin();
     *cit = 1;               // ok: cit can change its underlying element
     ++cit;                  // error: can't change the value of cit



A const_iterator may be used with either a const or nonconst vector, because it cannot write an element. An iterator that is const is largely useless: Once it is initialized, we can use it to write the element it refers to, but cannot make it refer to any other element.
const_iterator 对象可以用于 const vector 或非 const vector,因为不能改写元素值。const 迭代器这种类型几乎没什么用处:一旦它被初始化后,只能用它来改写其指向的元素,但不能使它指向任何其他元素。
     const vector<int> nines(10, 9);  // cannot change elements in nines
     // error: cit2 could change the element it refers to and nines is const
     const vector<int>::iterator cit2 = nines.begin();
     // ok: it can't change an element value, so it can be used with a const vector<int>
     vector<int>::const_iterator it = nines.begin();
     *it = 10; // error: *it is const
     ++it;     // ok: it isn't const so we can change its value



 


     // an iterator that cannot write elements
     vector<int>::const_iterator
     // an iterator whose value cannot change
     const vector<int>::iterator





Exercises Section 3.4
Exercise 3.17:Redo the exercises from Section 3.3.2 (p. 96), using iterators rather than subscripts to access the elements in the vector.
重做 3.3.2 节 的习题,用迭代器而不是下标操作来访问 vector 中的元素。
Exercise 3.18:Write a program to create a vector with 10 elements. Using an iterator, assign each element a value that is twice its current value.
编写程序来创建有 10 个元素的 vector 对象。用迭代器把每个元素值改为当前值的 2 倍。
Exercise 3.19:Test your previous program by printing the vector.
验证习题 3.18 的程序,输出 vector 的所有元素。
Exercise 3.20: Explain which iterator you used in the previous programs, and why.
解释一下在上几个习题的程序实现中你用了哪种迭代器,并说明原因。
Exercise 3.21:When would you use an iterator that is const? When would you use a const_iterator. Explain the difference between them.
何时使用 const 迭代器的?又在何时使用 const_iterator?解释两者的区别。




3.4.1. Iterator Arithmetic
3.4.1. 迭代器的算术操作
In addition to the increment operator, which moves an iterator one element at a time, vector iterators (but few of the other library container iterators) also support other arithmetic operations. These operations are referred to as iterator arithmetic, and include:
除了一次移动迭代器的一个元素的增量操作符外,vector 迭代器(其他标准库容器迭代器很少)也支持其他的算术操作。这些操作称为迭代器算术操作(iterator arithmetic),包括:
iter + n
iter - n
We can add or subtract an integral value to an iterator. Doing so yields a new iterator positioned n elements ahead of (addition) or behind (subtraction) the element to which iter refers. The result of the addition or subtraction must refer to an element in the vector to which iter refers or to one past the end of that vector. The type of the value added or subtracted ought ordinarily to be the vector's size_type or difference_type (see below).
可以对迭代器对象加上或减去一个整形值。这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是 vector 末端的后一个元素。加上或减去的值的类型应该是 vector 的 size_type 或 difference_type 类型(参考下面的解释)。
iter1 - iter2
Computes the difference between two iterators as a value of a signed integral type named difference_type, which, like size_type, is defined by vector. The type is signed because subtraction might have a negative result. This type is guaranteed to be large enough to hold the distance between any two iterators. Both iter1 and iter2 must refer to elements in the same vector or the element one past the end of that vector.
该表达式用来计算两个迭代器对象的距离,该距离是名为 difference_type 的 signed 类型 size_type 的值,这里的 difference_type 是 signed 类型,因为减法运算可能产生负数的结果。该类型可以保证足够大以存储任何两个迭代器对象间的距离。iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向 vector 末端之后的下一个元素。
We can use iterator arithmetic to move an iterator to an element directly. For example, we could locate the middle of a vector as follows:
可以用迭代器算术操作来移动迭代器直接指向某个元素,例如,下面语句直接定位于 vector 中间元素:
     vector<int>::iterator mid = vi.begin() + vi.size() / 2;



This code initializes mid to refer to the element nearest to the middle of ivec. It is more efficient to calculate this iterator directly than to write an equivalent program that increments the iterator one by one until it reaches the middle element.
上述代码用来初始化 mid 使其指向 vi 中最靠近正中间的元素。这种直接计算迭代器的方法,与用迭代器逐个元素自增操作到达中间元素的方法是等价的,但前者的效率要高得多。
Any operation that changes the size of a vector makes existing iterators invalid. For example, after calling push_back, you should not rely on the value of an iterator into the vector.
任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。




Exercises Section 3.4.1
Exercise 3.22:What happens if we compute mid as follows:
如果采用下面的方法来计算 mid 会产生什么结果?
     vector<int>::iterator mid = (vi.begin() + vi.end()) / 2;







    

3.5. Library bitset Type
3.5. 标准库 bitset
Some programs deal with ordered collections of bits. Each bit can contain either a 0 (off) or a 1 (on) value. Using bits is a compact way to keep yes/no information (sometimes called flags) about a set of items or conditions. The standard library makes it easy to deal with bits through the bitset class. To use a bitset we must include its associated header file. In our examples, we also assume an appropriate using declaration for std::bitset is made:
有些程序要处理二进制位的有序集,每个位可能包含 0(关)1(开)值。位是用来保存一组项或条件的 yes/no 信息(有时也称标志)的简洁方法。标准库提供的 bitset 类简化了位集的处理。要使用 bitset 类就必须包含相关的头文件。在本书提供的例子中,假设都使用 std::bitset 的 using 声明:
     #include <bitset>
     using std::bitset;



3.5.1. Defining and Initializing bitsets
3.5.1. bitset 对象的定义和初始化
Table 3.6 lists the constructors for bitset. Like vector, the bitset class is a class template. Unlike vector, objects of type bitset differ by size rather than by type. When we define a bitset, we say how many bits the bitset will contain, which we do by specifying the size between a pair of angle brackets.
表 3.6 列出了 bitset 的构造函数。类似于 vector,bitset 类是一种类模板;而与 vector 不一样的是 bitset 类型对象的区别仅在其长度而不在其类型。在定义 bitset 时,要明确 bitset 含有多少位,须在尖括号内给出它的长度值:
Table 3.6. Ways to Initialize a bitset表 3.6. 初始化 bitset 对象的方法bitset<n> b;b has n bits, each bit is 0
b 有 n 位,每位都 0
bitset<n> b(u);b is a copy of the unsigned long value u
b 是 unsigned long 型 u 的一个副本
bitset<n> b(s);b is a copy of the bits contained in string s
b 是 string 对象 s 中含有的位串的副本
bitset<n> b(s, pos, n);b is a copy of the bits in n characters from s starting from position pos
b 是 s 中从位置 pos 开始的&nbps;n 个位的副本。



     bitset<32> bitvec; // 32 bits, all zero



The size must be a constant expression (Section 2.7, p. 62). It might be defined, as we did here, using an integral literal constant or using a const object of integral type that is initialized from a constant.
给出的长度值必须是常量表达式(2.7 节)。正如这里给出的,长度值值必须定义为整型字面值常量或是已用常量值初始化的整型的 const 对象。
This statement defines bitvec as a bitset that holds 32 bits. Just as with the elements of a vector, the bits in a bitset are not named. Instead, we refer to them positionally. The bits are numbered starting at 0. Thus, bitvec has bits numbered 0 through 31. The bits starting at 0 are referred to as the low-order bits, and those ending at 31 are referred to as high-order bits.
这条语句把 bitvec 定义为含有 32 个位的 bitset 对象。和 vector 的元素一样,bitset 中的位是没有命名的,程序员只能按位置来访问。位集合的位置编号从 0 开始,因此,bitvec 的位序是从 0 到 31。以 0 位开始的位串是低阶位(low-order),以 31 位结束的位串是高阶位(high-order)。
Initializing a bitset from an unsigned Value
用 unsigned 值初始化 bitset 对象
When we use an unsigned long value as an initializer for a bitset, that value is treated as a bit pattern. The bits in the bitset are a copy of that pattern. If the size of the bitset is greater than the number of bits in an unsigned long, then the remaining high-order bits are set to zero. If the size of the bitset is less than that number of bits, then only the low-order bits from the unsigned value are used; the high-order bits beyond the size of the bitset object are discarded.
当用 unsigned long 值作为 bitset 对象的初始值时,该值将转化为二进制的位模式。而 bitset 对象中的位集作为这种位模式的副本。如果 bitset 类型长度大于 unsigned long 值的二进制位数,则其余的高阶位将置为 0;如果 bitset 类型长度小于 unsigned long 值的二进制位数,则只使用 unsigned 值中的低阶位,超过 bistset 类型长度的高阶位将被丢弃。
On a machine with 32-bit unsigned longs, the hexadecimal value 0xffff is represented in bits as a sequence of 16 ones followed by 16 zeroes. (Each 0xf digit is represented as 1111.) We can initialize a bitset from 0xffff:
在 32 位 unsigned long 的机器上,十六进制值 0xffff 表示为二进制位就是十六个 1 和十六个 0(每个 0xf 可表示为 1111)。可以用 0xffff 初始化 bitset 对象:
     // bitvec1 is smaller than the initializer
     bitset<16> bitvec1(0xffff);          // bits 0 ... 15 are set to 1
     // bitvec2 same size as initializer
     bitset<32> bitvec2(0xffff);          // bits 0 ... 15 are set to 1; 16 ... 31 are 0
     // on a 32-bit machine, bits 0 to 31 initialized from 0xffff
     bitset<128> bitvec3(0xffff);         // bits 32 through 127 initialized to zero



In all three cases, the bits 0 to 15 are set to one. For bitvec1, the high-order bits in the initializer are discarded; bitvec1 has fewer bits than an unsigned long. bitvec2 is the same size as an unsigned long, so all the bits are used to initialize that object. bitvec3 is larger than an unsigned long, so its high-order bits above 31 are initialized to zero.
上面的三个例子中,0 到 15 位都置为 1。由于 bitvec1 位数少于 unsigned long 的位数,因此 bitvec1 的初始值的高阶被丢弃。bitvec2 和 unsigned long 长度相同,因此所有位正好放置了初始值。bitvec3 长度大于 32,31 位以上的高阶位就被置为 0。
Initializing a bitset from a string
用 string 对象初始化 bitset 对象
When we initialize a bitset from a string, the string represents the bit pattern directly. The bits are read from the string from right to left:
当用 string 对象初始化 bitset 对象时,string 对象直接表示为位模式。从 string 对象读入位集的顺序是从右向左(from right to left):
     string strval("1100");
     bitset<32> bitvec4(strval);



The bit pattern in bitvec4 has bit positions 2 and 3 set to 1, while the remaining bit positions are 0. If the string contains fewer characters than the size of the bitset, the high-order bits are set to zero.
bitvec4 的位模式中第 2 和 3 的位置为 1,其余位置都为 0。如果 string 对象的字符个数小于 bitset 类型的长度,则高阶位置为 0。
 The numbering conventions of strings and bitsets are inversely related: The rightmost character in the stringthe one with the highest subscriptis used to initialize the low-order bit in the bitsetthe bit with subscript 0. When initializing a bitset from a string, it is essential to remember this difference.
string 对象和 bitsets 对象之间是反向转化的:string 对象的最右边字符(即下标最大的那个字符)用来初始化 bitset 对象的低阶位(即下标为 0 的位)。当用 string 对象初始化 bitset 对象时,记住这一差别很重要。




We need not use the entire string as the initial value for the bitset. Instead, we can use a substring as the initializer:
不一定要把整个 string 对象都作为 bitset 对象的初始值。相反,可以只用某个子串作为初始值:
     string str("1111111000000011001101");
     bitset<32> bitvec5(str, 5, 4); // 4 bits starting at str[5], 1100
     bitset<32> bitvec6(str, str.size() - 4);     // use last 4 characters



Here bitvec5 is initialized by a substring of str starting at str[5] and continuing for four positions. As usual, we start at the rightmost end of this substring when initializing the bitset, meaning that bitvec5 is initialized with bit positions 0 through 3 set to 1100 while its remaining bit positions are set to 0. Leaving off the third parameter says to use characters from the starting position to the end of the string. In this case, the characters starting four from the end of str are used to initialize the lower four bits of bitvec6. The remainder of the bits in bitvec6 are initialized to zero. We can view these initializations as
这里用 str 从 str[5] 开始包含四个字符的子串来初始化 bitvec5。照常,初始化 bitset 对象时总是从子串最右边结尾字符开始的,bitvec5 的从 3 到 0 的二进制位置为 1100 ,其他二进制位都置为 0。如果省略第三个参数则意味着取从开始位置一直到 string 末尾的所有字符。本例中,取出 str 末尾的四位来对 bitvec6 的低四位进行初始化。bitvec6 其余的位初始化为 0。这些初始化过程的图示如下:

3.5.2. Operations on bitsets
3.5.2. bitset 对象上的操作
The bitset operations (Table 3.7) define various operations to test or set one or more bits in the bitset.
多种 bitset 操作(表 3.7)用来测试或设置 bitset 对象中的单个或多个二进制位。
Table 3.7. bitset Operations表 3.7. bitset 操作b.any()Is any bit in b on?
b 中是否存在置为 1 的二进制位?
b.none()Are no bits in b on?
b.count()Number of bits in b that are on
b 中不存在置为 1 的二进制位吗?
b.size()Number of bits in b
b 中置为 1 的二进制位的个数
b[pos]Access bit in b at position pos
访问 b 中在 pos 处二进制位
b.test(pos)Is bit in b in position pos on?
b 中在 pos 处的二进制位置为 1
b.set()Turn on all bits in b把 b 中所有二进制位都置为 1
b.set(pos)Turn on the bit in b at position pos
把 b 中在 pos 处的二进制位置为 1
b.reset()Turn off all bits in b
把 b 中所有二进制位都置为 0
b.reset(pos)Turn off the bit in b at position pos
把 b 中在 pos 处的二进制位置为 0
b.flip()Change the state of each bit in b
把 b 中所有二进制位逐位取反
b.flip(pos)Reverse value of the bit in b in position pos
把 b 中在 pos 处的二进制位取反
b.to_ulong()Returns an unsigned long with the same bits as in b
用 b 中同样的二进制位返回一个 unsigned long 值
os << bPrints the bits in b to the stream os
把 b 中的位集输出到 os 流



Testing the Entire bitset
测试整个 bitset 对象
The any operation returns true if one or more bits of the bitset object are turned onthat is, are equal to 1. Conversely, the operation none returns true if all the bits of the object are set to zero.
如果 bitset 对象中有一个或几个二进制位置为 1,则 any 操作返回 true,也就是说,其返回值等于 1;相反,如果 bitset 对象中二进制位全为 0,则 none 操作返回 true。
     bitset<32> bitvec; // 32 bits, all zero
     bool is_set = bitvec.any();            // false, all bits are zero
     bool is_not_set = bitvec.none();       // true, all bits are zero



If we need to know how many bits are set, we can use the count operation, which returns the number of bits that are set:
如果需要知道置为 1 的二进制位的个数,可以使用 count 操作,该操作返回置为 1 的二进制位的个数:
     size_t bits_set = bitvec.count(); // returns number of bits that are on



The return type of the count operation is a library type named size_t. The size_t type is defined in the cstddef header, which is the C++ version of the stddef.h header from the C library. It is a machine-specific unsigned type that is guaranteed to be large enough to hold the size of an object in memory.
count 操作的返回类型是标准库中命名为 size_t 类型。size_t 类型定义在 cstddef 头文件中,该文件是 C 标准库的头文件 stddef.h 的 C++ 版本。它是一个与机器相关的 unsigned 类型,其大小足以保证存储内在中对象的大小。
The size operation, like the one in vector and string, returns the total number of bits in the bitset. The value returned has type size_t:
与 vector 和 string 中的 size 操作一样,bitset 的 size 操作返回 bitset 对象中二进制位的个数,返回值的类型是 size_t::
     size_t sz = bitvec.size(); // returns 32



Accessing the Bits in a bitset
访问 bitset 对象中的位
The subscript operator lets us read or write the bit at the indexed position. As such, we can use it to test the value of a given bit or to set that value:
可以用下标操作符来读或写某个索引位置的二进制位,同样地,也可以用下标操作符测试给定二进制位的值或设置某个二进制们的值:
     // assign 1 to even numbered bits
     for (int index = 0; index != 32; index += 2)
                 bitvec[index] = 1;



This loop turns on the even-numbered bits of bitvec.
上面的循环把 bitvec 中的偶数下标的位都置为 1。
As with the subscript operator, we can use the set, test, and reset operations to test or set a given bit value:
除了用下标操作符,还可以用 set;、test 和 reset 操作来测试或设置给定二进制位的值:
     // equivalent loop using set operation
     for (int index = 0; index != 32; index += 2)
                 bitvec.set(index);



To test whether a bit is on, we can use the test operation or test the value returned from the subscript operator:
为了测试某个二进制位是否为 1,可以用 test 操作或者测试下标操作符的返回值:
     if (bitvec.test(i))
          // bitvec[i] is on
     // equivalent test using subscript
     if (bitvec[i])
          // bitvec[i] is on



The result of testing the value returned from a subscript is true if the bit is 1 or false if the bit is 0.
如果下标操作符测试的二进制位为 1,则返回的测试值的结果为 true,否则返回 false。
Setting the Entire bitset
对整个 bitset 对象进行设置
The set and reset operations can also be used to turn on or turn off the entire bitset object, respectively:
set 和 reset 操作分别用来对整个 bitset 对象的所有二进制位全置 1 和全置 0:
     bitvec.reset(); // set all the bits to 0.
     bitvec.set();   // set all the bits to 1



The flip operation reverses the value of an individual bit or the entire bitset:
flip 操作可以对 bitset 对象的所有位或个别位取反:
     bitvec.flip(0);   // reverses value of first bit
     bitvec[0].flip(); // also reverses the first bit
     bitvec.flip();    // reverses value of all bits



Retrieving the Value of a bitset
获取 bitset 对象的值
The to_ulong operation returns an unsigned long that holds the same bit pattern as the bitset object. We can use this operation only if the size of the bitset is less than or equal to the size of an unsigned long:
to_ulong 操作返回一个 unsigned long 值,该值与 bitset 对象的位模式存储值相同。仅当 bitset 类型的长度小于或等于 unsigned long 的长度时,才可以使用 to_ulong 操作:
     unsigned long ulong = bitvec3.to_ulong();
     cout << "ulong = " << ulong << endl;



The to_ulong operation is intended to be used when we need to pass a bitset to a C or pre-Standard C++ program. If the bitset contains more bits than the size of an unsigned long, a run-time exception is signaled. We'll introduce exceptions in Section 6.13 (p. 215) and look at them in more detail in Section 17.1 (p. 688).
to_ulong 操作主要用于把 bitset 对象转到 C 风格或标准 C++ 之前风格的程序上。如果 bitset 对象包含的二进制位数超过 unsigned long 长度,将会产生运行时异常。本书将在 6.13 节介绍异常(exception),并在 17.1 节中详细地讨论它。
Printing the Bits
输出二进制位
We can use the output operator to print the bit pattern in a bitset object:
可以用输出操作符输出 bitset 对象中的位模式:
     bitset<32> bitvec2(0xffff); // bits 0 ... 15 are set to 1; 16 ... 31 are 0
     cout << "bitvec2: " << bitvec2 << endl;



will print
输出结果为:
     bitvec2: 00000000000000001111111111111111



Using the Bitwise Operators
使用位操作符
The bitset class also supports the built-in bitwise operators. As defined by the language, these operators apply to integral operands. They perform operations similar to the bitset operations described in this section. Section 5.3 (p. 154) describes these operators.
bitset 类也支持内置的位操作符。C++ 定义的这些操作符都只适用于整型操作数,它们所提供的操作类似于本节所介绍的 bitset 操作。5.3 节将介绍这些操作符。
Exercises Section 3.5.2
Exercise 3.23: Explain the bit pattern each of the following bitset objects contains:
解释下面每个 bitset 对象包含的位模式:
     (a) bitset<64> bitvec(32);
     (b) bitset<32> bv(1010101);
     (c) string bstr; cin >> bstr; bitset<8>bv(bstr);



Exercise 3.24:Consider the sequence 1,2,3,5,8,13,21. Initialize a bitset<32> object that has a one bit in each position corresponding to a number in this sequence. Alternatively, given an empty bitset, write a small program to turn on each of the appropriate bits.
考虑这样的序列 1,2,3,5,8,13,21,并初始化一个将该序列数字所对应的位置置为 1 的 bitset<32> 对象。然后换个方法,给定一个空的 bitset,编写一小段程序把相应的数位设置为 1。




    

Chapter Summary
The library defines several higher-level abstract data types, including strings and vectors. The string class provides variable-length character strings, and the vector type manages a collection of objects of a single type.
C++ 标准库定义了几种更高级的抽象数据类型,包括 string 和 vector 类型。string 类型提供了变长的字符串,而 vector 类型则可用于管理同一类型的对象集合。
Iterators allow indirect access to objects stored in a container. Iterators are used to access and navigate between the elements in strings and vectors.
迭代器实现了对存储于容器中对象的间接访问。迭代器可以用于访问和遍历 string 类型和 vectors 类型的元素。
In the next chapter we'll cover arrays and pointers, which are types built into the language. These types provide low-level analogs to the vector and string libraries. In general, the library classes should be used in preference to low-level array and pointer alternatives built into the language.
下一章将介绍 C++ 的内置数据类型:数组和指针。这两种类型提供了类似于 vector 和 string 标准库类型的低级抽象类型。总的来说,相对于 C++ 内置数据类型的数组和指针而言,程序员应优先使用标准库类类型。
     

Defined Terms
术语
abstract data type(抽象数据类型)
A type whose representation is hidden. To use an abstract type, we need know only what operations the type supports.
隐藏其实现的数据类型。使用抽象数据类型时,只需要了解该类型所支持的操作。
bitset
Standard library class that holds a collection of bits and provides operations to test and set the bits in the collection.
一种标准库类型,用于保存位置,并提供地各个位的测试和置位操作。
cctype header(cctype 头文件)
Header inherited from C that contains routines to test character values. See page 88 for a listing of the most common routines.
从 C 标准库继承而来的头文件,包含一组测试字符值的例程。第 8.3.4 节的表 3.3 列出了常用的例程。
class template(类模板)
A blueprint from which many potential class types can be created. To use a class template, we must specify what actual type(s) or value(s) to use. For example, a vector is a template that holds objects of a given type. When we create a vector, we must say what type this vector will hold. vector<int> holds ints, vector<string> holds strings, and so on.
一个可创建许多潜在类类型的蓝图。使用类模板时,必须给出实际的类型和值。例如,vector 类型是保存给定类型对象的模板。创建一个 vector 对象是,必须指出这个 vector 对象所保存的元素的类型。vector<int> 保存 int 的对象,而 vector<string> 则保存 string 对象,以此类推。
container(容器)
A type whose objects hold a collection of objects of a given type.
一种类型,其对象保存一组给定类型的对象的集合。
difference_type
A signed integral type defined by vector that is capable of holding the distance between any two iterators.
一种由 vector 类型定义的 signed 整型,用于存储任意两个迭代器间的距离。
empty
Function defined by the string and vector types. empty returns a bool indicating whether the string has any characters or whether the vector has any elements. Returns TRue if size is zero; false otherwise.
由string类型和vector类型定义的成员函数。empty返回布尔值,用于检测string是否有字符或vector是否有元素。如果string或vector的size为0,则返回true,否则返回false。
getline
Function defined in the string header that takes an istream and a string. The function reads the stream up to the next newline, storing what it read into the string, and returns the istream. The newline is read and discarded.
string头文件中定义的函数,该函数接受一个istream对象和一个string对象,读取输入流直到下一个换行符,存储读入的输入流到string对象中,并返回istream对象。换行符被读入并丢弃。
high-order(高阶)
Bits in a bitset with the largest indices.
bitset对象中索引值最大的位。
index(索引)
Value used in the subscript operator to denote the element to retrieve from a string or vector.
下标操作符所使用的值,用于表示从string对象或vector对象中获取的元素。也称“下标”。
iterator(迭代器)
A type that can be used to examine the elements of a container and to navigate between them.
用于对容器类型的元素进行检查和遍历的数据类型。
iterator arithmetic(迭代器的算术操作)
The arithmetic operations that can be applied to some, but not all, iterator types. An integral type can be added to or subtracted from an iterator, resulting in an iterator positioned that many elements ahead of or behind the original iterator. Two iterators can be subtracted, yielding the distance between the iterators. Iterator arithmetic is valid only on iterators that refer to elements in the same container or the off-the-end iterator of the container.
应用于一些(并非全部)迭代器类型的算术操作。迭代器对象可以加上或减去一个整型数值,结果迭代器指向处于原迭代器之前或之后若干个元素的位置。两个迭代器对象可以相减,得到的结果是它们之间的距离。迭代器算术操作只适用于指向同一容器中的元素或指向容器末端的下一元素迭代器。
low-order(低阶)
Bits in a bitset with the lowest indices.
bitset对象中索引值最小的位。
off-the-end iterator(超出末端的迭代器)
The iterator returned by end. It is an iterator that refers to a nonexistent element one past the end of a container.
由end操作返回的迭代器,是一种指向容器末端之后的不存在元素的迭代器。
push_back
Function defined by vector that appends elements to the back of a vector.
由vector类型定义的成员函数,用于把元素追加到vector对象的尾部。
sentinel(哨兵)
Programming technique that uses a value as a guard to control processing. In this chapter, we showed the use of the iterator returned by end as a guard to stop processing elements in a vector once we had processed every element in the vector.
一种程序设计技术,使用一个值来控制处理过程。在本章中使用由end操作返回的迭代器作为保护符,当处理完vector对象中的所有元素后,用它来停止处理vector中的元素。
size
Function defined by the library types string, vector, and bitset that returns the number of characters, elements, or bits respectively. The string and vector functions return a value of the size_type for the type. For example, size of a string returns a string::size_type. The bitset operation returns a value of type size_t.
由库类型string、vector和bitset定义的函数,分别用于返回此三个类型的字符个数、元素个素、二进制位的个数。string和vector类的size成员函数返回size_type类型的值(例如,string对象的size操作返回string::size_type类型值)。bitset对象的size操作返回size_t类型值。
size_t
Machine-dependent unsigned integral type defined in cstddef header that is large enough to hold the size of the largest possible array.
在cstddef头文件中定义的机器相关的无符号整型,该类型足以保存最大数组的长度。
在cstddef头文件中定义的机器相关的无符号整型,该类型足以保存最大数组的长度。
size_type
Type defined by the string and vector classes that is capable of containing the size of any string or vector, respectively. Library classes that define size_type define it as an unsigned type.
由string类类型和vector类类型定义的类型,用以保存任意string对象或vecotr对象的长度。标准库类型将size_type定义为unsigned类型。
using declarations(using声明)
Make a name from a namespace accessible directly.
使命名空间的名字可以直接引用。比如:
     using namespace::name;



makes name accessible without the namespace:: prefix.
可以直接访问name而无须前缀namespace::。
value initialization(值初始化)
Initialization that happens for container elements when the container size is specified but there is no explicit element initializer. The elements are initialized as a copy of a compiler-generated value. If the container holds built-in types, then the value copied into the elements is zero. For class types, the value is generated by running the class's default constructor. Container elements that are of class type can be value-initialized only if the class has a default constructor.
当给定容器的长度,但没有显式提供元素的初始式时,对容器元素进行的初始化。元素被初始化为一个编译器产生的值的副本。如果容器保存内置类型变量,则元素的初始值将置为0。如果容器用于保存类对象,则元素的初始值由类的默认构造函数产生。只有类提供了默认构造函数时,类类型的容器元素才能进行值初始化。
++ operator(++操作符)
The iterator types define the increment operator to "add one" by moving the iterator to refer to the next element.
迭代器类型定义的自增操作符,通过“加1”移动迭代器指向下一个元素。
:: operator(::操作符)
The scope operator. It finds the name of its right-hand operand in the scope of its left-hand operand. Used to access names in a namespace, such as std::cout, which represents the name cout from the namespace std. Similarly, used to obtain names from a class, such as string::size_type, which is the size_type defined by the string class.
作用域操作符。::操作符在其左操作数的作用域内找到其右操作数的名字。用于访问某个命名空间中的名字,如std::cout,表明名字cout来自命名空间std。同样地,可用来从某个类取名字,如string::size_type,表明size_type是由string类定义的。
[] operator([]操作符)
An overloaded operator defined by the string, vector, and bitset types. It takes two operands: The left-hand operand is the name of the object and the right-hand operand is an index. It fetches the element whose position matches the index. Indices count from zerothe first element is element 0 and the last is element indexed by obj.size() - 1. Subscript returns an lvalue, so we may use a subscript as the left-hand operand of an assignment. Assigning to the result of a subscript assigns a new value to the indexed element.
由string, vector和bitset类型定义的重载操作符。它接受两个操作数:左操作数是对象名字,右操作数是一个索引。该操作符用于取出位置与索引相符的元素,索引计数从0开始,即第一个元素的索引为0,最后一个元素的索引为obj.size() -1。下标操作返回左值,因此可将下标操作作为赋值操作的左操作数。对下标操作的结果赋值是赋一个新值到相应的元素。
* operator(*操作符)
The iterator types define the dereference operator to return the object to which the iterator refers. Dereference returns an lvalue, so we may use a dereference operator as the left-hand operand of an assignment. Assigning to the result of a dereference assigns a new value to the indexed element.
迭代器类型定义了解引用操作符来返回迭代器所指向的对象。解引用返回左值,因此可将解引用操作符用作赋值操作的左操作数。对解引用操作的结果赋值是赋一个新值到相应的元素。
<< operator(<< 操作符)
The string and bitset library types define an output operator. The string operator prints the characters in a string. The bitset operator prints the bit pattern in the bitset.
标准库类型string和bitset定义了输出操作符。string类型的输出操作符将输出string对象中的字符。bitset类型的输出操作符则输出bitset对象的位模式。
>> operator(>> 操作符)
The string and bitset library types define an input operator. The string operator reads whitespace delimited chunks of characters, storing what is read into the right-hand (string) operand. The bitset operator reads a bit sequence into its bitset operand.
标准库类型string和bitset定义了输入操作符。string类型的输入操作符读入以空白字符为分隔符的字符串,并把读入的内容存储在右操作数(string对象)中。bitset类型的输入操作符则读入一个位序列到其bitset操作数中。
      

Chapter 4. Arrays and Pointers
第四章 数组和指针
CONTENTS
Section 4.1 Arrays110
Section 4.2 Introducing Pointers114
Section 4.3 C-Style Character Strings130
Section 4.4 Multidimensioned Arrays141
Chapter Summary145
Defined Terms145


The language defines two lower-level compound typesarrays and pointersthat are similar to vectors and iterators. Like a vector, an array holds a collection of objects of some type. Unlike vectors, arrays are fixed size; once an array is created, new elements cannot be added. Like iterators, pointers can be used to navigate among and examine the elements in an array.
C++ 语言提供了两种类似于 vector 和迭代器类型的低级复合类型——数组和指针。与 vector 类型相似,数组也可以保存某种类型的一组对象;而它们的区别在于,数组的长度是固定的。数组一经创建,就不允许添加新的元素。指针则可以像迭代器一样用于遍历和检查数组中的元素。
Modern C++ programs should almost always use vectors and iterators in preference to the lower-level arrays and pointers. Well-designed programs use arrays and pointers only in the internals of class implementations where speed is essential.
现代 C++ 程序应尽量使用 vector 和迭代器类型,而避免使用低级的数组和指针。设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针。
Arrays are data structures that are similar to library vectors but are built into the language. Like a vector, an array is a container of objects of a single data type. The individual objects are not named; rather, each one is accessed by its position in the array.
数组是 C++ 语言中类似于标准库 vector 类型的内置数据结构。与 vector 类似,数组也是一种存储单一数据类型对象的容器,其中每个对象都没有单独的名字,而是通过它在数组中的位置对它进行访问。
Arrays have significant drawbacks compared to vectors: They are fixed size, and they offer no help to the programmer in keeping track of how big a given array is. There is no size operation on arrays. Similarly, there is no push_back to automatically add elements. If the array size needs to change, then the programmer must allocate a new, larger array and copy the elements into that new space.
与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的长度。数组没有获取其容量大小的 size 操作,也不提供 push_back 操作在其中自动添加元素。如果需要更改数组的长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间中去。
Programs that rely on built-in arrays rather than using the standard vector are more error-prone and harder to debug.
与使用标准 vector 类型的程序相比,依赖于内置数组的程序更容易出错而且难于调试。

Prior to the advent of the standard library, C++ programs made heavy use of arrays to hold collections of objects. Modern C++ programs should almost always use vectors instead of arrays. Arrays should be restricted to the internals of programs and used only where performance testing indicates that vectors cannot provide the necessary speed. However, there will be a large body of existing C++ code that relies on arrays for some time to come. Hence, all C++ programmers must know a bit about how arrays work.
在出现标准库之前,C++ 程序大量使用数组保存一组对象。而现代的 C++ 程序则更多地使用 vector 来取代数组,数组被严格限制于程序内部使用,只有当性能测试表明使用 vector 无法达到必要的速度要求时,才使用数组。然而,在将来一段时间之内,原来依赖于数组的程序仍大量存在,因此,C++ 程序员还是必须掌握数组的使用方法。

      

4.1. Arrays
4.1. 数组
An array is a compound type (Section 2.5, p. 58) that consists of a type specifier, an identifier, and a dimension. The type specifier indicates what type the elements stored in the array will have. The dimension specifies how many elements the array will contain.
数组是由类型名、标识符和维数组成的复合数据类型(第 2.5 节),类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。
The type specifier can denote a built-in data or class type. With the exception of references, the element type can also be any compound type. There are no arrays of references.
数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。




4.1.1. Defining and Initializing Arrays
4.1.1. 数组的定义和初始化
The dimension must be a constant expression (Section 2.7, p. 62) whose value is greater than or equal to one. A constant expression is any expression that involves only integral literal constants, enumerators (Section 2.7, p. 62), or const objects of integral type that are themselves initialized from constant expressions. A nonconst variable, or a const variable whose value is not known until run time, cannot be used to specify the dimension of an array.
数组的维数必须用值大于等于1的常量表达式定义(第 2.7 节)。此常量表达式只能包含整型字面值常量、枚举常量(第 2.7 节)或者用常量表达式初始化的整型 const 对象。非 const 变量以及要到运行阶段才知道其值的 const 变量都不能用于定义数组的维数。
The dimension is specified inside a [] bracket pair:
数组的维数必须在一对方括号 [] 内指定:
          // both buf_size and max_files are const
          const unsigned buf_size = 512, max_files = 20;
          int staff_size = 27;            // nonconst
          const unsigned sz = get_size();  // const value not known until run time
          char input_buffer[buf_size];     // ok: const variable
          string fileTable[max_files + 1]; // ok: constant expression
          double salaries[staff_size];     // error: non const variable
          int test_scores[get_size()];     // error: non const expression
          int vals[sz];                    // error: size not known until run time



Although staff_size is initialized with a literal constant, staff_size itself is a nonconst object. Its value can be known only at run time, so it is illegal as an array dimension. Even though size is a const object, its value is not known until get_size is called at run time. Therefore, it may not be used as a dimension. On the other hand, the expression
虽然 staff_size 是用字面值常量进行初始化,但 staff_size 本身是一个非 const 对象,只有在运行时才能获得它的值,因此,使用该变量来定义数组维数是非法的。而对于 sz,尽管它是一个 const 对象,但它的值要到运行时调用 get_size 函数后才知道,因此,它也不能用于定义数组维数。
          max_files + 1



is a constant expression because max_files is a const variable. The expression can be and is evaluated at compile time to a value of 21.
另一方面,由于 max_files 是 const 变量,因此表达式是常量表达式,编译时即可计算出该表达式的值为21。
Explicitly Initializing Array Elements
显式初始化数组元素
When we define an array, we can provide a comma-separated list of initializers for its elements. The initializer list must be enclosed in braces:
在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:
          const unsigned array_size = 3;
          int ia[array_size] = {0, 1, 2};

If we do not supply element initializers, then the elements are initialized in the same way that variables are initialized (Section 2.3.4, p. 50).
如果没有显式提供元素初值,则数组元素会像普通变量一样初始化(第 2.3.4 节):
Elements of an array of built-in type defined outside the body of a function are initialized to zero.
在函数体外定义的内置数组,其元素均初始化为 0。
Elements of an array of built-in type defined inside the body of a function are uninitialized.
在函数体内定义的内置数组,其元素无初始化。
Regardless of where the array is defined, if it holds elements of a class type, then the elements are initialized by the default constructor for that class if it has one. If the class does not have a default constructor, then the elements must be explicitly initialized.
不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。
Unless we explicitly supply element initializers, the elements of a local array of built-in type are uninitialized. Using these elements for any purpose other than to assign a new value is undefined.
除非显式地提供元素初值,否则内置类型的局部数组的元素没有初始化。此时,除了给元素赋值外,其他使用这些元素的操作没有定义。

An explicitly initialized array need not specify a dimension value. The compiler will infer the array size from the number of elements listed:
显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:
          int ia[] = {0, 1, 2}; // an array of dimension 3

If the dimension size is specified, the number of elements provided must not exceed that size. If the dimension size is greater than the number of listed elements, the initializers are used for the first elements. The remaining elements are initialized to zero if the elements are of built-in type or by running the default constructor if they are of class type:
如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素;剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化:
          const unsigned array_size = 5;
          // Equivalent to ia = {0, 1, 2, 0, 0}
          // ia[3] and ia[4] default initialized to 0
          int ia[array_size] = {0, 1, 2};
          // Equivalent to str_arr = {"hi", "bye", "", "", ""}
          // str_arr[2] through str_arr[4] default initialized to the empty string
          string str_arr[array_size] = {"hi", "bye"};

Character Arrays Are Special
特殊的字符数组
A character array can be initialized with either a list of comma-separated character literals enclosed in braces or a string literal. Note, however, that the two forms are not equivalent. Recall that a string literal (Section 2.2, p. 40) contains an additional terminating null character. When we create a character array from a string literal, the null is also inserted into the array:
字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值(第 2.2 节)包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符:
          char ca1[] = {'C', '+', '+'};                // no null
          char ca2[] = {'C', '+', '+', '\0'};         // explicit null
          char ca3[] = "C++";     // null terminator added automatically

The dimension of ca1 is 3; the dimension of ca2 and ca3 is 4. It is important to remember the null-terminator when initializing an array of characters to a literal. For example, the following is a compile-time error:
ca1 的维数是 3,而 ca2 和 ca3 的维数则是 4。使用一组字符字面值初始化字符数组时,一定要记得添加结束字符串的空字符。例如,下面的初始化将导致编译时的错误:
          const char ch3[6] = "Daniel"; // error: Daniel is 7 elements

While the literal contains only six explicit characters, the required array size is sevensix to hold the literal and one for the null.
上述字符串字面值包含了 6 个显式字符,存放该字符串的数组则必须有 7 个元素——6 个用于存储字符字面值,而 1 个用于存放空字符 null。
No Array Copy or Assignment
不允许数组直接复制和赋值
Unlike a vector, it is not possible to initialize an array as a copy of another array. Nor is it legal to assign one array to another:
与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组,这些操作都是非法的:
          int ia[] = {0, 1, 2}; // ok: array of ints
          int ia2[](ia);        // error: cannot initialize one array with another

          int main()
          {
              const unsigned array_size = 3;
              int ia3[array_size]; // ok: but elements are uninitialized!

              ia3 = ia;           //  error: cannot assign one array to another
              return 0;
          }

Some compilers allow array assignment as a compiler extension. If you intend to run a given program on more than one compiler, it is usually a good idea to avoid using nonstandard compiler-specific features such as array assignment.
一些编译器允许将数组赋值作为编译器扩展。但是如果希望编写的程序能在不同的编译器上运行,则应该避免使用像数组赋值这类依赖于编译器的非标准功能。

Caution: Arrays Are Fixed Size
警告:数组的长度是固定的
Unlike the vector type, there is no push_back or other operation to add elements to the array. Once we define an array, we cannot add elements to it.
与 vector 类型不同,数组不提供 push_back 或者其他的操作在数组中添加新元素,数组一经定义,就不允许再添加新元素。
If we must add elements to the array, then we must manage the memory ourselves. We have to ask the system for new storage to hold the larger array and copy the existing elements into that new storage. We'll see how to do so in Section 4.3.1 (p. 134).
如果必须在数组中添加新元素,程序员就必须自己管理内存:要求系统重新分配一个新的内存空间用于存放更大的数组,然后把原数组的所有元素复制到新分配的内存空间中。我们将会在第 4.3.1 节学习如何去实现。
Exercises Section 4.1.1
Exercise 4.1:Assuming get_size is a function that takes no arguments and returns an int value, which of the following definitions are illegal? Explain why.
假设 get_size 是一个没有参数并返回 int 值的函数,下列哪些定义是非法的?为什么?
          unsigned buf_size = 1024;

          (a) int ia[buf_size];
          (b) int ia[get_size()];
          (c) int ia[4 * 7 - 14];
          (d) char st[11] = "fundamental";



Exercise 4.2:What are the values in the following arrays?
下列数组的值是什么?
          string sa[10];
          int ia[10];
          int main() {
              string sa2[10];
              int    ia2[10];
          }



Exercise 4.3:Which, if any, of the following definitions are in error?
下列哪些定义是错误的?
          (a) int ia[7] = { 0, 1, 1, 2, 3, 5, 8 };
          (b) vector<int> ivec = { 0, 1, 1, 2, 3, 5, 8 };
          (c) int ia2[ ] = ia1;
          (d) int ia3[ ] = ivec;



Exercise 4.4:How can you initialize some or all the elements of an array?
如何初始化数组的一部分或全部元素?
Exercise 4.5:List some of the drawbacks of using an array instead of a vector.
列出使用数组而不是 vector 的缺点。



4.1.2. Operations on Arrays
4.1.2. 数组操作
Array elements, like vector elements, may be accessed using the subscript operator (Section 3.3.2, p. 94). Like the elements of a vector, the elements of an array are numbered beginning with 0. For an array of ten elements, the correct index values are 0 through 9, not 1 through 10.
与vector元素一样,数组元素可用下标操作符(第 3.3.2 节)来访问,数组元素也是从 0 开始计数。对于一个包含 10 个元素的数组,正确的下标值是从 0 到 9,而不是从 1 到 10。
When we subscript a vector, we use vector::size_type as the type for the index. When we subscript an array, the right type to use for the index is size_t (Section 3.5.2, p. 104).
在用下标访问元素时,vector 使用 vector::size_type 作为下标的类型,而数组下标的正确类型则是 size_t(第 3.5.2 节)。
In the following example, a for loop steps through the 10 elements of an array, assigning to each the value of its index:
在下面的例子中,for 循环遍历数组的 10 个元素,并以其下标值作为各个元素的初始值:
          int main()
          {
              const size_t array_size = 10;
              int ia[array_size]; // 10 ints, elements are uninitialized

              // loop through array, assigning value of its index to each element
              for (size_t ix = 0; ix != array_size; ++ix)
                    ia[ix] = ix;
              return 0;
          }



Using a similar loop, we can copy one array into another:
使用类似的循环,可以实现把一个数组复制给另一个数组:
          int main()
          {
              const size_t array_size = 7;
              int ia1[] = { 0, 1, 2, 3, 4, 5, 6 };
              int ia2[array_size]; // local array, elements uninitialized

              // copy elements from ia1 into ia2
              for (size_t ix = 0; ix != array_size; ++ix)
                    ia2[ix] = ia1[ix];
              return 0;
          }



Checking Subscript Values
检查数组下标值
As with both strings and vectors, the programmer must guarantee that the subscript value is in rangethat the array has an element at the index value.
正如 string 和 vector 类型,程序员在使用数组时,也必须保证其下标值在正确范围之内,即数组在该下标位置应对应一个元素。
Nothing stops a programmer from stepping across an array boundary except attention to detail and thorough testing of the code. It is not inconceivable for a program to compile and execute and still be fatally wrong.
除了程序员自己注意细节,并彻底测试自己的程序之外,没有别的办法可防止数组越界。通过编译并执行的程序仍然存在致命的错误,这并不是不可能的。
By far, the most common causes of security problems are so-called "buffer overflow" bugs. These bugs occur when a subscript is not checked and reference is made to an element outside the bounds of an array or other similar data structure.
导致安全问题的最常见原因是所谓“缓冲区溢出(buffer overflow)”错误。当我们在编程时没有检查下标,并且引用了越出数组或其他类似数据结构边界的元素时,就会导致这类错误。



 

Exercises Section 4.1.2
Exercise 4.6: This code fragment intends to assign the value of its index to each array element. It contains a number of indexing errors. Identify them.
下面的程序段企图将下标值赋给数组的每个元素,其中在下标操作上有一些错误,请指出这些错误。
          const size_t array_size = 10;
          int ia[array_size];
          for (size_t ix = 1; ix <= array_size; ++ix)
                ia[ix] = ix;



Exercise 4.7: Write the code necessary to assign one array to another. Now, change the code to use vectors. How might you assign one vector to another?
编写必要的代码将一个数组赋给另一个数组,然后把这段代码改用 vector 实现。考虑如何将一个 vector 赋给另一个 vector。
Exercise 4.8: Write a program to compare two arrays for equality. Write a similar program to compare two vectors.
编写程序判断两个数组是否相等,然后编写一段类似的程序比较两个 vector。
Exercise 4.9: Write a program to define an array of 10 ints. Give each element the same value as its position in the array.
编写程序定义一个有 10 个 int 型元素的数组,并以其在数组中的位置作为各元素的初值。



      

4.2. Introducing Pointers
4.2. 指针的引入
Just as we can traverse a vector either by using a subscript or an iterator, we can also traverse an array by using either a subscript or a pointer. A pointer is a compound type; a pointer points to an object of some other type. Pointers are iterators for arrays: A pointer can point to an element in an array. The dereference and increment operators, when applied to a pointer that points to an array element, have similar behavior as when applied to an iterator. When we dereference a pointer, we obtain the object to which the pointer points. When we increment a pointer, we advance the pointer to denote the next element in the array. Before we write programs using pointers, we need to know a bit more about them.
vector 的遍历可使用下标或迭代器实现,同理,也可用下标或指针来遍历数组。指针是指向某种类型对象的复合数据类型,是用于数组的迭代器:指向数组中的一个元素。在指向数组元素的指针上使用解引用操作符 *(dereference operator)和自增操作符 ++(increment operator),与在迭代器上的用法类似。对指针进行解引用操作,可获得该指针所指对象的值。而当指针做自增操作时,则移动指针使其指向数组中的下一个元素。在使用指针编写程序之前,我们需进一步了解一下指针。
4.2.1. What Is a Pointer?
4.2.1. 什么是指针
For newcomers, pointers are often hard to understand. Debugging problems due to pointer errors bedevil even experienced programmers. However, pointers are an important part of most C programs and to a much lesser extent remain important in many C++ programs.
对初学者来说,指针通常比较难理解。而由指针错误引起的调试问题连富有经验的程序员都感到头疼。然而,指针是大多数C程序的重要部分,而且在许多 C++ 程序中仍然受到重用。
Conceptually, pointers are simple: A pointer points at an object. Like an iterator, a pointer offers indirect access to the object to which it points. However, pointers are a much more general construct. Unlike iterators, pointers can be used to point at single objects. Iterators are used only to access elements in a container.
指针的概念很简单:指针用于指向对象。与迭代器一样,指针提供对其所指对象的间接访问,只是指针结构更通用一些。与迭代器不同的是,指针用于指向单个对象,而迭代器只能用于访问容器内的元素。
Specifically, a pointer holds the address of another object:
具体来说,指针保存的是另一个对象的地址:
          string s("hello world");
          string *sp = &s; // sp holds the address of s

The second statement defines sp as a pointer to string and initializes sp to point to the string object named s. The * in *sp indicates that sp is a pointer. The & operator in &s is the address-of operator. It returns a value that when dereferenced yields the original object. The address-of operator may be applied only to an lvalue (Section 2.3.1, p. 45). Because a variable is an lvalue, we may take its address. Similarly, the subscript and dereference operators, when applied to a vector, string, or built-in array, yield lvalues. Because these operators yield lvalues, we may apply the address-of to the result of the subscript or dereference operator. Doing so gives us the address of a particular element.
第二条语句定义了一个指向 string 类型的指针 sp,并初始化 sp 使其指向 string 类型的对象s。*sp 中的 * 操作符表明 sp 是一个指针变量,&s 中的 & 符号是取地址操作符,当此操作符用于一个对象上时,返回的是该对象的存储地址。取地址操作符只能用于左值(第 2.3.1 节),因为只有当变量用作左值时,才能取其地址。同样地,由于用于 vector 类型、string 类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做取地址操作,这样即可获取某一特定对象的存储地址。
Advice: Avoid Pointers and Arrays
建议:尽量避免使用指针和数组
Pointers and arrays are surprisingly error-prone. Part of the problem is conceptual: Pointers are used for low-level manipulations and it is easy to make bookkeeping mistakes. Other problems arise because of the syntax, particularly the declaration syntax used with pointers.
指针和数组容易产生不可预料的错误。其中一部分是概念上的问题:指针用于低级操作,容易产生与繁琐细节相关的(bookkeeping)错误。其他错误则源于使用指针的语法规则,特别是声明指针的语法。
Many useful programs can be written without needing to use arrays or pointers. Instead, modern C++ programs should use vectors and iterators to replace general arrays and strings to replace C-style array-based character strings.
许多有用的程序都可不使用数组或指针实现,现代C++程序采用vector类型和迭代器取代一般的数组、采用string类型取代C风格字符串。



4.2.2. Defining and Initializing Pointers
4.2.2. 指针的定义和初始化
Every pointer has an associated type. The type of a pointer determines the type of the objects to which the pointer may point. A pointer to int, for example, may only point to an object of type int.
每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。例如,一个 int 型指针只能指向 int 型对象。
Defining Pointer Variables
指针变量的定义
We use the * symbol in a declaration to indicate that an identifier is a pointer:
C++ 语言使用 * 符号把一个标识符声明为指针:
          vector<int>   *pvec;      // pvec can point to a vector<int>
          int           *ip1, *ip2; // ip1 and ip2 can point to an int
          string        *pstring;   // pstring can point to a string
          double        *dp;        // dp can point to a double

 When attempting to understand pointer declarations, read them from right to left.
理解指针声明语句时,请从右向左阅读。




Reading the definition of pstring from right to left, we see that
从右向左阅读 pstring 变量的定义,可以看到
          string *pstring;

defines pstring as a pointer that can point to string objects. Similarly,
语句把 pstring 定义为一个指向 string 类型对象的指针变量。类似地,语句
          int *ip1, *ip2; // ip1 and ip2 can point to an int

defines ip2 as a pointer and ip1 as a pointer. Both pointers point to ints.
把 ip1 和 ip2 都定义为指向 int 型对象的指针。
The * can come anywhere in a list of objects of a given type:
在声明语句中,符号 * 可用在指定类型的对象列表的任何位置:
          double dp, *dp2; // dp2 is a ponter, dp is an object: both type double



defines dp2 as a pointer and dp as an object, both of type double.
该语句定义了一个 double 类型的 dp 对象以及一个指向 double 类型对象的指针dp2。
A Different Pointer Declaration Style
另一种声明指针的风格
The * symbol may be separated from its identifier by a space. It is legal to write:
在定义指针变量时,可用空格将符号 * 与其后的标识符分隔开来。下面的写法是合法的:
          string* ps; // legal but can be misleading

which says that ps is a pointer to string.
也就是说,该语句把 ps 定义为一个指向 string 类型对象的指针。
We say that this definition can be misleading because it encourages the belief that string* is the type and any variable defined in the same definition is a pointer to string. However,
这种指针声明风格容易引起这样的误解:把 string* 理解为一种数据类型,认为在同一声明语句中定义的其他变量也是指向 string 类型对象的指针。然而,语句
          string* ps1, ps2; // ps1 is a pointer to string,  ps2 is a string

defines ps1 as a pointer, but ps2 is a plain string. If we want to define two pointers in a single definition, we must repeat the * on each identifier:
实际上只把 ps1 定义为指针,而 ps2 并非指针,只是一个普通的 string 对象而已。如果需要在一个声明语句中定义两个指针,必须在每个变量标识符前再加符号 * 声明:
          string* ps1, *ps2; // both ps1 and ps2 are pointers to string

Multiple Pointer Declarations Can Be Confusing
连续声明多个指针易导致混淆
There are two common styles for declaring multiple pointers of the same type. One style requires that a declaration introduce only a single name. In this style, the * is placed with the type to emphasize that the declaration is declaring a pointer:
连续声明同一类型的多个指针有两种通用的声明风格。其中一种风格是一个声明语句只声明一个变量,此时,符号 * 紧挨着类型名放置,强调这个声明语句定义的是一个指针:
          string* ps1;
          string* ps2;

The other style permits multiple declarations in a single statement but places the * adjacent to the identifier. This style emphasizes that the object is a pointer:
另一种风格则允许在一条声明语句中声明多个指针,声明时把符号 * 靠近标识符放置。这种风格强调对象是一个指针:
          string *ps1, *ps2;

 As with all questions of style, there is no single right way to declare pointers. The important thing is to choose a style and stick with it.
关于指针的声明,不能说哪种声明风格是唯一正确的方式,重要的是选择一种风格并持续使用。




In this book we use the second style and place the * with the pointer variable name.
在本书中,我们将采用第二种声明风格:将符号 * 紧贴着指针变量名放置。
Possible Pointer Values
指针可能的取值
A valid pointer has one of three states: It can hold the address of a specific object, it can point one past the end of an object, or it can be zero. A zero-valued pointer points to no object. An uninitialized pointer is invalid until it is assigned a value. The following definitions and assignments are all legal:
一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。下列定义和赋值都是合法的:
          int ival = 1024;
          int *pi = 0;       // pi initialized to address no object
          int *pi2 = & ival; // pi2 initialized to address of ival
          int *pi3;          // ok, but dangerous, pi3 is uninitialized
          pi = pi2;          // pi and pi2 address the same object, e.g. ival
          pi2 = 0;           // pi2 now addresses no object

Avoid Uninitialized Pointers
避免使用未初始化的指针
Uninitialized pointers are a common source of run-time errors.
很多运行时错误都源于使用了未初始化的指针。

As with any other uninitialized variable, what happens when we use an uninitialized pointer is undefined. Using an uninitialized pointer almost always results in a run-time crash. However, the fact that the crash results from using an uninitialized pointer can be quite hard to track down.
就像使用其他没有初始化的变量一样,使用未初始化的指针时的行为C++标准中并没有定义使用未初始化的指针,它几乎总会导致运行时崩溃。然而,导致崩溃的这一原因很难发现。
Under most compilers, if we use an uninitialized pointer the effect will be to use whatever bits are in the memory in which the pointer resides as if it were an address. Using an uninitialized pointer uses this supposed address to manipulate the underlying data at that supposed location. Doing so usually leads to a crash as soon as we attempt to dereference the uninitialized pointer.
对大多数的编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容。使用未初始化的指针相当于操纵这个不确定地址中存储的基础数据。因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃。
It is not possible to detect whether a pointer is uninitialized. There is no way to distinguish a valid address from an address formed from the bits that are in the memory in which the pointer was allocated. Our recommendation to initialize all variables is particularly important for pointers.
C++ 语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间中存放的二进制位形成的地址。建议程序员在使用之前初始化所有的变量,尤其是指针。
If possible, do not define a pointer until the object to which it should point has been defined. That way, there is no need to define an uninitialized pointer.
如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针。


If you must define a pointer separately from pointing it at an object, then initialize the pointer to zero. The reason is that a zero-valued pointer can be tested and the program can detect that the pointer does not point to an object.
如果必须分开定义指针和其所指向的对象,则将指针初始化为 0。因为编译器可检测出 0 值的指针,程序可判断该指针并未指向一个对象。


Constraints on Initialization of and Assignment to Pointers
指针初始化和赋值操作的约束
There are only four kinds of values that may be used to initialize or assign to a pointer:
对指针进行初始化或赋值只能使用以下四种类型的值:
A constant expression (Section 2.7, p. 62) with value 0 (e.g., a const integral object whose value is zero at compile time or a literal constant 0)
0 值常量表达式(第 2.7 节),例如,在编译时可获得 0 值的整型 const 对象或字面值常量 0。
An address of an object of an appropriate type
类型匹配的对象的地址。
The address one past the end of another object
另一对象末的下一地址。
Another valid pointer of the same type
同类型的另一个有效指针。
It is illegal to assign an int to a pointer, even if the value of the int happens to be 0. It is okay to assign the literal 0 or a const whose value is known to be 0 at compile time:
把 int 型变量赋给指针是非法的,尽管此 int 型变量的值可能为 0。但允许把数值 0 或在编译时可获得 0 值的 const 量赋给指针:
          int ival;
          int zero = 0;
          const int c_ival = 0;
          int *pi = ival; // error: pi initialized from int value of ival
          pi = zero;      // error: pi assigned int value of zero
          pi = c_ival;    // ok: c_ival is a const with compile-time value of 0
          pi = 0;         // ok: directly initialize to literal constant 0

In addition to using a literal 0 or a const with a compile-time value of 0, we can also use a facility that C++ inherits from C. The cstdlib header defines a preprocessor variable (Section 2.9.2, p. 69) named NULL, which is defined as 0. When we use a preprocessor variable in our code, it is automatically replaced by its value. Hence, initializing a pointer to NULL is equivalent to initializing it to 0:
除了使用数值0或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL(第 2.9.2 节),该变量在 cstdlib 头文件中定义,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值:
          // cstdlib #defines NULL to 0
          int *pi = NULL; // ok: equivalent to int *pi = 0;

As with any preprocessor variable (Section 2.9.2, p. 71) we should not use the name NULL for our own variables.
正如其他的预处理器变量一样(第 2.9.2 节),不可以使用 NULL 这个标识符给自定义的变量命名。
 Preprocessor variables are not defined in the std namespace and hence the name is NULL, not std::NULL.
预处理器变量不是在 std 命名空间中定义的,因此其名字应为 NULL,而非 std::NULL。




With two exceptions, which we cover in Sections 4.2.5 and 15.3, we may only initialize or assign a pointer from an address or another pointer that has the same type as the target pointer:
除了将在第 4.2.5 节和第 15.3 节介绍的两种例外情况之外,指针只能初始化或赋值为同类型的变量地址或另一指针:
          double dval;
          double *pd = &dval;   // ok: initializer is address of a double
          double *pd2 = pd;     // ok: initializer is a pointer to double

          int *pi = pd;   // error: types of pi and pd differ
          pi = &dval;     // error: attempt to assign address of a double to int *

The reason the types must match is that the type of the pointer is used to determine the type of the object that it addresses. Pointers are used to indirectly access an object. The operations that the pointer can perform are based on the type of the pointer: A pointer to int treats the underlying object as if it were an int. If that pointer actually addressed an object of some other type, such as double, then any operations performed by the pointer would be in error.
由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配。指针用于间接访问对象,并基于指针的类型提供可执行的操作,例如,int 型指针只能把其指向的对象当作 int 型数据来处理,如果该指针确实指向了其他类型(如 double 类型)的对象,则在指针上执行的任何操作都有可能出错。
void* Pointers
void* 指针
The type void* is a special pointer type that can hold an address of any object:
C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:
          double obj = 3.14;
          double *pd = &obj;
          // ok: void* can hold the address value of any data pointer type
          void *pv = &obj;       // obj can be an object of any type
          pv = pd;               // pd can be a pointer to any type

A void* indicates that the associated value is an address but that the type of the object at that address is unknown.
void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。
There are only a limited number of actions we can perform on a void* pointer: We can compare it to another pointer, we can pass or return it from a function, and we can assign it to another void* pointer. We cannot use the pointer to operate on the object it addresses. We'll see in Section 5.12.4 (p. 183) how we can retrieve the address stored in a void* pointer.
void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递 void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用 void* 指针操纵它所指向的对象。我们将在第 5.12.4 节讨论如何重新获取存储在 void* 指针中的地址。
Exercises Section 4.2.2
Exercise 4.10: Explain the rationale for preferring the first form of pointer declaration:
下面提供了两种指针声明的形式,解释宁愿使用第一种形式的原因:
          int *ip; // good practice
          int* ip; // legal but misleading


Exercise 4.11: Explain each of the following definitions. Indicate whether any are illegal and if so why.
解释下列声明语句,并指出哪些是非法的,为什么?
          (a) int* ip;
          (b) string s, *sp = 0;
          (c) int i; double* dp = &i;
          (d) int* ip, ip2;
          (e) const int i = 0, *p = i;
          (f) string *p = NULL;


Exercise 4.12: Given a pointer, p, can you determine whether p points to a valid object? If so, how? If not, why not?
已知一指针 p,你可以确定该指针是否指向一个有效的对象吗?如果可以,如何确定?如果不可以,请说明原因。
Exercise 4.13: Why is the first pointer initialization legal and the second illegal?
下列代码中,为什么第一个指针的初始化是合法的,而第二个则不合法?
          int i = 42;
          void *p = &i;
          long *lp = &i;






4.2.3. Operations on Pointers
4.2.3. 指针操作
Pointers allow indirect manipulation of the object to which the pointer points. We can access the object by dereferencing the pointer. Dereferencing a pointer is similar to dereferencing an iterator (Section 3.4, p. 98). The * operator (the dereference operator) returns the object to which the pointer points:
指针提供间接操纵其所指对象的功能。与对迭代器进行解引用操作(第 3.4 节)一样,对指针进行解引用可访问它所指的对象,* 操作符(解引用操作符)将获取指针所指的对象:
          string s("hello world");
          string *sp = &s; // sp holds the address of s
          cout  <<*sp;     // prints hello world

When we dereference sp, we fetch the value of s. We hand that value to the output operator. The last statement, therefore, prints the contents of sthat is, hello world.
对 sp 进行解引用将获得 s 的值,然后用输出操作符输出该值,于是最后一条语句输出了 s 的内容 hello world。
Dereference Yields an Lvalue
生成左值的解引用操作
The dereference operator returns the lvalue of the underlying object, so we can use it to change the value of the object to which the pointer points:
解引用操作符返回指定对象的左值,利用这个功能可修改指针所指对象的值:
          *sp = "goodbye"; // contents of s now changed

Because we assign to *sp, this statement leaves sp pointing to s and changes the value of s.
因为 sp 指向 s,所以给 *sp 赋值也就修改了 s 的值。
We can also assign a new value to sp itself. Assigning to sp causes sp to point to a different object:
也可以修改指针 sp 本身的值,使 sp 指向另外一个新对象:
          string s2 = "some value";
          sp = &s2;  // sp now points to s2

We change the value of a pointer by assigning to it directlywithout dereferencing the pointer.
给指针直接赋值即可修改指针的值——不需要对指针进行解引用。
Key Concept: Assigning TO or THROUGH a Pointer
关键概念:给指针赋值或通过指针进行赋值
When first using pointers, the difference in whether an assignment is to the pointer or through the pointer to the value pointed to can be confusing. The important thing to keep in mind is that if the left-hand operand is dereferenced, then the value pointed to is changed. If there is no dereference, then the pointer itself is being changed. A picture can sometimes help:
对于初学指针者,给指针赋值和通过指针进行赋值这两种操作的差别确实让人费解。谨记区分的重要方法是:如果对左操作数进行解引用,则修改的是指针所指对象的值;如果没有使用解引用操作,则修改的是指针本身的值。如图所示,帮助理解下列例子:



Comparing Pointers and References
指针和引用的比较
While both references and pointers are used to indirectly access another value, there are two important differences between references and pointers. The first is that a reference always refers to an object: It is an error to define a reference without initializing it. The behavior of assignment is the second important difference: Assigning to a reference changes the object to which the reference is bound; it does not rebind the reference to another object. Once initialized, a reference always refers to the same underlying object.
虽然使用引用(reference)和指针都可间接访问另一个值,但它们之间有两个重要区别。第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。
Consider these two program fragments. In the first, we assign one pointer to another:
考虑以下两个程序段。第一个程序段将一个指针赋给另一指针:
          int ival = 1024, ival2 = 2048;
          int *pi = &ival, *pi2 = &ival2;
          pi = pi2;    // pi now points to ival2

After the assignment, ival, the object addressed by pi remains unchanged. The assignment changes the value of pi, making it point to a different object. Now consider a similar program that assigns two references:
赋值结束后,pi 所指向的 ival 对象值保持不变,赋值操作修改了 pi 指针的值,使其指向另一个不同的对象。现在考虑另一段相似的程序,使用两个引用赋值:
          int &ri = ival, &ri2 = ival2;
          ri = ri2;    // assigns ival2 to ival

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.
这个赋值操作修改了 ri 引用的值 ival 对象,而并非引用本身。赋值后,这两个引用还是分别指向原来关联的对象,此时这两个对象的值相等。
Pointers to Pointers
指向指针的指针
Pointers are themselves objects in memory. They, therefore, have addresses that we can store in a pointer:
指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。下面程序段:
          int ival = 1024;
          int *pi = &ival; // pi points to an int
          int **ppi = &pi; // ppi points to a pointer to int

which yields a pointer to a pointer. We designate a pointer to a pointer by using **. We might represent these objects as
定义了指向指针的指针。C++ 使用 ** 操作符指派一个指针指向另一指针。这些对象可表示为:

As usual, dereferencing ppi yields the object to which ppi points. In this case, that object is a pointer to an int:
对 ppi 进行解引用照常获得 ppi 所指的对象,在本例中,所获得的对象是指向 int 型变量的指针 pi:
          int *pi2 = *ppi; // ppi points to a pointer

To actually access ival, we need to dereference ppi twice:
为了真正地访问到 ival 对象,必须对 ppi 进行两次解引用:
          cout << "The value of ival\n"
               << "direct value: " << ival << "\n"
               << "indirect value: " << *pi << "\n"
               << "doubly indirect value: " << **ppi
               << endl;

This program prints the value of ival three different ways. First, by direct reference to the variable. Then, through the pointer to int in pi, and finally, by dereferencing ppi twice to get to the underlying value in ival.
这段程序用三种不同的方式输出 ival 的值。首先,采用直接引用变量的方式输出;然后使用指向 int 型对象的指针 pi 输出;最后,通过对 ppi 进行两次解引用获得 ival 的特定值。
Exercises Section 4.2.3
Exercise 4.14: Write code to change the value of a pointer. Write code to change the value to which the pointer points.
编写代码修改指针的值;然后再编写代码修改指针所指对象的值。
Exercise 4.15: Explain the key differences between pointers and references.
解释指针和引用的主要区别。
Exercise 4.16: What does the following program do?
下列程序段实现什么功能?
          int i = 42, j = 1024;
          int *p1 = &i, *p2 = &j;
          *p2 = *p1 * *p2;
          *p1 *= *p1;






4.2.4. Using Pointers to Access Array Elements
4.2.4. 使用指针访问数组元素
Pointers and arrays are closely intertwined in C++. In particular, when we use the name of an array in an expression, that name is automatically converted into a pointer to the first element of the array:
C++ 语言中,指针和数组密切相关。特别是在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针:
          int ia[] = {0,2,4,6,8};
          int *ip = ia; // ip points to ia[0]

If we want to point to another element in the array, we could do so by using the subscript operator to locate the element and then applying the address-of operator to find its location:
如果希望使指针指向数组中的另一个元素,则可使用下标操作符给某个元素定位,然后用取地址操作符 & 获取该元素的存储地址:
          ip = &ia[4];    // ip points to last element in ia

Pointer Arithmetic
指针的算术操作
Rather than taking the address of the value returned by subscripting, we could use pointer arithmetic. Pointer arithmetic works the same way (and has the same constraints) as iterator arithmetic (Section 3.4.1, p. 100). Using pointer arithmetic, we can compute a pointer to an element by adding (or subtracting) an integral value to (or from) a pointer to another element in the array:
与其使用下标操作,倒不如通过指针的算术操作来获取指定内容的存储地址。指针的算术操作和迭代器的算术操作(第 3.4.1 节)以相同的方式实现(也具有相同的约束)。使用指针的算术操作在指向数组某个元素的指针上加上(或减去)一个整型数值,就可以计算出指向数组另一元素的指针值:
          ip = ia;            // ok: ip points to ia[0]
          int *ip2 = ip + 4;  // ok: ip2 points to ia[4], the last element in ia

When we add 4 to the pointer ip, we are computing a new pointer. That new pointer points to the element four elements further on in the array from the one to which ip currently points.
在指针 ip 上加 4 得到一个新的指针,指向数组中 ip 当前指向的元素后的第 4 个元素。
More generally, when we add (or subtract) an integral value to a pointer, the effect is to compute a new pointer. The new pointer points to the element as many elements as that integral value ahead of (or behind) the original pointer.
通常,在指针上加上(或减去)一个整型数值 n 等效于获得一个新指针,该新指针指向指针原来指向的元素之后(或之前)的第 n 个元素。
 Pointer arithmetic is legal only if the original pointer and the newly calculated pointer address elements of the same array or an element one past the end of that array. If we have a pointer to an object, we can also compute a pointer that points just after that object by adding one to the pointer.
指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。如果指针指向一对象,我们还可以在指针上加1从而获取指向相邻的下一个对象的指针。




Given that ia has 4 elements, adding 10 to ia would be an error:
假设数组 ia 只有 4 个元素,则在 ia 上加 10 是错误的:
          // error: ia has only 4 elements, ia + 10 is an invalid address
          int *ip3 = ia + 10;

We can also subtract two pointers as long as they point into the same array or to an element one past the end of the array:
只要两个指针指向同一数组或有一个指向该数组末端的下一单元,C++ 还支持对这两个指针做减法操作:
          ptrdiff_t n = ip2 - ip; // ok: distance between the pointers

The result is four, the distance between the two pointers, measured in objects. The result of subtracting two pointers is a library type named ptrdiff_t. Like size_t, the ptrdiff_t type is a machine-specific type and is defined in the cstddef header. The size_t type is an unsigned type, whereas ptrdiff_t is a signed integral type.
结果是 4,这两个指针所指向的元素间隔为 4 个对象。两个指针减法操作的结果是标准库类型(library type)ptrdiff_t 的数据。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。size_t 是 unsigned 类型,而 ptrdiff_t 则是 signed 整型。
The difference in type reflects how these two types are used: size_t is used to hold the size of an array, which must be a positive value. The ptrdiff_t type is guaranteed to be large enough to hold the difference between any two pointers into the same array, which might be a negative value. For example, had we subtracted ip2 from ip, the result would be -4.
这两种类型的差别体现了它们各自的用途:size_t 类型用于指明数组长度,它必须是一个正数;ptrdiff_t 类型则应保证足以存放同一数组中两个指针之间的差距,它有可能是负数。例如,ip 减去 ip2,结果为 -4。
It is always possible to add or subtract zero to a pointer, which leaves the pointer unchanged. More interestingly, given a pointer that has a value of zero, it is also legal to add zero to that pointer. The result is another zero-valued pointer. We can also subtract two pointers that have a value of zero. The result of subtracting two zero-valued pointers is zero.
允许在指针上加减 0,使指针保持不变。更有趣的是,如果一指针具有 0 值(空指针),则在该指针上加 0 仍然是合法的,结果得到另一个值为 0 的指针。也可以对两个空指针做减法操作,得到的结果仍是 0。
Interaction between Dereference and Pointer Arithmetic
解引用和指针算术操作之间的相互作用
The result of adding an integral value to a pointer is itself a pointer. We can dereference the resulting pointer directly without first assigning it to another pointer:
在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:
          int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]

This expression calculates the address four elements past ia and dereferences that pointer. It is equivalent to writing ia[4].
这个表达式计算出 ia 所指向元素后面的第 4 个元素的地址,然后对该地址进行解引用操作,等价于 ia[4]。
 The parentheses around the addition are essential. Writing
加法操作两边用圆括号括起来是必要的。如果写为:


          last = *ia + 4;     // ok: last = 4, equivalent to ia[0]+4

means dereference ia and add four to the dereferenced value.
意味着对 ia 进行解引用,获得 ia 所指元素的值 ia[0],然后加 4。


The parentheses are required due to the precedence of the addition and dereference operators. We'll learn more about precedence in Section 5.10.1 (p. 168). Simply put, precedence stipulates how operands are grouped in expressions with multiple operators. The dereference operator has a higher precedence than the addition operator.
由于加法操作和解引用操作的优先级不同,上述表达式中的圆括号是必要的。我们将在第 5.10.1 节讨论操作符的优先级。简单地说,优先级决定了有多个操作符的表达式如何对操作数分组。解引用操作符的优先级比加法操作符高。
The operands to operators with higher precedence are grouped more tightly than those of lower precedence. Without the parentheses, the dereference operator would use ia as its operand. The expression would be evaluated by dereferencing ia and adding four to the value of the element at the beginning of ia.
与低优先级的操作符相比,优先级高的操作符的操作数先被组合起来操作。如果没有圆括号,解引用操作符的操作数是 ia,该表达式先对 ia 解引用,获得 ia 数组中的第一个元素,并将该值与 4 相加。
By parenthesizing the expression, we override the normal precedence rules and effectively treat (ia + 4) as a single operand. That operand is an address of an element four past the one to which ia points. That new address is dereferenced.
如果表达式加上圆括号,则不管一般的优先级规则,将 (ia + 4) 作为单个操作数,这是 ia 所指向的元素后面第4个元素的地址,然后对这个新地址进行解引用。
Subscripts and Pointers
下标和指针
We have already seen that when we use an array name in an expression, we are actually using a pointer to the first element in the array. This fact has a number of implications, which we shall point out as they arise.
我们已经看到,在表达式中使用数组名时,实际上使用的是指向数组第一个元素的指针。这种用法涉及很多方面,当它们出现时我们会逐一指出来。
One important implication is that when we subscript an array, we are really subscripting a pointer:
其中一个重要的应用是使用下标访问数组时,实际上是使用下标访问指针:
          int ia[] = {0,2,4,6,8};
          int i = ia[0]; // ia points to the first element in ia

When we write ia[0], that is an expression that uses the name of an array. When we subscript an array, we are really subscripting a pointer to an element in that array. We can use the subscript operator on any pointer, as long as that pointer points to an element in an array:
ia[0] 是一个使用数组名的表达式。在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。只要指针指向数组元素,就可以对它进行下标操作:
          int *p = &ia[2];     // ok: p points to the element indexed by 2
          int j = p[1];        // ok: p[1] equivalent to *(p + 1),
                               //    p[1] is the same element as ia[3]
          int k = p[-2];       // ok: p[-2] is the same element as ia[0]

Computing an Off-the-End Pointer
计算数组的超出末端指针
When we use a vector, the end operation returns an iterator that refers just past the end of the vector. We often use this iterator as a sentinel to control loops that process the elements in the vector. Similarly, we can compute an off-the-end pointer value:
vector 类型提供的 end 操作将返回指向超出 vector 末端位置的一个迭代器。这个迭代器常用作哨兵,来控制处理 vector 中元素的循环。类似地,可以计算数组的超出末端指针的值:
          const size_t arr_size = 5;
          int arr[arr_size] = {1,2,3,4,5};
          int *p = arr;           // ok: p points to arr[0]
          int *p2 = p + arr_size; // ok: p2 points one past the end of arr
                                  //    use caution -- do not dereference!

In this case, we set p to point to the first element in arr. We then calculate a pointer one past the end of arr by adding the size of arr to the pointer value in p. When we add 5 to p, the effect is to calculate the address of that is five ints away from pin other words, p + 5 points just past the end of arr.
本例中,p 指向数组 arr 的第一个元素,在指针 p 上加数组长度即可计算出数组 arr 的超出末端指针。p 加 5 即得 p 所指向的元素后面的第五个 int 元素的地址——换句话说,p + 5指向数组的超出末端的位置。
 It is legal to compute an address one past the end of an array or object. It is not legal to dereference a pointer that holds such an address. Nor is it legal to compute an address more than one past the end of an array or an address before the beginning of an array.
C++ 允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。而计算数组超出末端位置之后或数组首地址之前的地址都是不合法的。




The address we calculated and stored in p2 acts much like the iterator returned from the end operation on vectors. The iterator we obtain from end denotes "one past the end" of the vector. We may not dereference that iterator, but we may compare it to another iterator value to see whether we have processed all the elements in the vector. Similarly, the value we calculated for p2 can be used only to compare to another pointer value or as an operand in a pointer arithmetic expression. If we attempt to dereference p2, the most likely result is that it would yield some garbage value. Most compilers, would treat the result of dereferencing p2 as an int, using whatever bits happened to be in memory at the location just after the last element in arr.
计算并存储在p2中的地址,与在 vector 上做 end 操作所返回的迭代器具有相同的功能。由 end 返回的迭代器标志了该 vector 对象的“超出末端位置”,不能进行解引用运算,但是可将它与别的迭代器比较,从而判断是否已经处理完 vector 中所有的元素。同理,p2 也只能用来与其他指针比较,或者用做指针算术操作表达式的操作数。对 p2 进行解引用将得到无效值。对大多数的编译器来说,会把对 p2 进行解引用的结果(恰好存储在 arr 数组的最后一个元素后面的内存中的二进制位)视为一个 int 型数据。
Printing the Elements of an Array
输出数组元素
Now we are ready to write a program that uses pointers:
用指针编写以下程序:
          const size_t arr_sz = 5;
          int int_arr[arr_sz] = { 0, 1, 2, 3, 4 };
          // pbegin points to first element, pend points just after the last
          for (int *pbegin = int_arr, *pend = int_arr + arr_sz;
                    pbegin != pend; ++pbegin)
              cout << *pbegin << ' '; // print the current element

This program uses a feature of the for loop that we have not yet used: We may define multiple variables inside the init-statement (Section 1.4.2, p. 14) of a for as long as the variables are defined using the same type. In this case, we're defining two int pointers named pbegin and pend.
这段程序使用了一个我们以前没有用过的 for 循环性质:只要定义的多个变量具有相同的类型,就可以在 for 循环的初始化语句(第 1.4.2 节)中同时定义它们。本例在初始化语句中定义了两个 int 型指针 pbegin 和 pend。
We use these pointers to traverse the array. Like other built-in types, arrays have no member functions. Hence, there are no begin and end operations on arrays. Instead, we must position pointers to denote the first and one past the last elements ourselves. We do so in the initialization of our two pointers. We initialize pbegin to address the first element of int_arr and pend to one past the last element in the array:
C++ 允许使用指针遍历数组。和其他内置类型一样,数组也没有成员函数。因此,数组不提供 begin 和 end 操作,程序员只能自己给指针定位,使之分别标志数组的起始位置和超出末端位置。可在初始化中实现这两个指针的定位:初始化指针 pbegin 指向 int_arr 数组的第一个元素,而指针 pend 则指向该数组的超出末端的位置:

The pointer pend serves as a sentinel, allowing the for loop to know when to stop. Each iteration of the for loop increments pbegin to address the next element. On the first trip through the loop, pbegin denotes the first element, on the second iteration, the second element, and so on. After processing the last element in the array, pbegin will be incremented once more and will then equal pend. At that point we know that we have iterated across the entire array.
指针 pend 是标志 for 循环结束的哨兵。for 循环的每次迭代都会使 pbegin 递增 1 以指向数组的下一个元素。第一次执行 for 循环时,pbegin 指向数组中的第一个元素;第二次循环,指向第二个元素;这样依次类推。当处理完数组的最后一个元素后,pbegin 再加 1 则与 pend 值相等,表示整个数组已遍历完毕。
Pointers Are Iterators for Arrays
指针是数组的迭代器
Astute readers will note that this program is remarkably similar to the program on page 99, which traversed and printed the contents of a vector of strings. The loop in that program
聪明的读者可能已经注意到这段程序与第 3.4 节的一段程序非常相像,该程序使用下面的循环遍历并输出一个 string 类型的 vector 的内容:
          // equivalent loop using iterators to reset all the elements in ivec to 0
          for (vector<int>::iterator iter = ivec.begin();
                                     iter != ivec.end(); ++iter)
              *iter = 0; // set element to which iter refers to 0

used iterators in much the same way that pointers are used in the program to print the contents of the array. This similarity is not a coincidence. In fact, the built-in array type has many of the properties of a library container, and pointers, when we use them in conjunction with arrays, are themselves iterators. We'll have much more to say about containers and iterators in Part II.
这段程序使用迭代器的方式就像上个程序使用指针实现输出数组内容一样。指针和迭代器的这个相似之处并不是巧合。实际上,内置数组类型具有标准库容器的许多性质,与数组联合使用的指针本身就是迭代器。在第二部分中,我们还会详细介绍容器和迭代器类型。
Exercises Section 4.2.4
Exercise 4.17: Given that p1 and p2 point to elements in the same array, what does the following statement do?
已知 p1 和 p2 指向同一个数组中的元素,下面语句实现什么功能?
          p1 += p2 - p1;

Are there any values of p1 or p2 that could make this code illegal?
当 p1 和 p2 具有什么值时这个语句是非法的?
Exercise 4.18: Write a program that uses pointers to set the elements in an array of ints to zero.
编写程序,使用指针把一个 int 型数组的所有元素设置为0。




4.2.5. Pointers and the const Qualifier
4.2.5. 指针和 const 限定符
There are two kinds of interactions between pointers and the const qualifier discussed in Section 2.4 (p. 56): We can have pointers to const objects and pointers that are themselves const. This section discusses both kinds of pointers.
第 2.4 节介绍了指针和 const 限定符之间的两种交互类型:指向 const 对象的指针和 const 指针。我们在本节中详细讨论这两类指针。
Pointers to const Objects
指向 const 对象的指针
The pointers we've seen so far can be used to change the value of the objects to which they point. But if we have a pointer to a const object, we do not want to allow that pointer to change the underlying, const value. The language enforces this property by requiring that pointers to const objects must take the constness of their target into account:
到目前为止,我们使用指针来修改其所指对象的值。但是如果指针指向 const 对象,则不允许用指针来改变其所指的 const 值。为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:
          const double *cptr;  // cptr may point to a double that is const

Here cptr is a pointer to an object of type const double. The const qualifies the type of the object to which cptr points, not cptr itself. That is, cptr itself is not const. We need not initialize it and can assign a new value to it if we so desire. What we cannot do is use cptr to change the value to which it points:
这里的 cptr 是一个指向 double 类型 const 对象的指针,const 限定了 cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是 const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值:
          *cptr = 42;   // error: *cptr might be const

It is also a compile-time error to assign the address of a const object to a plain, nonconst pointer:
把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误:
          const double pi = 3.14;
          double *ptr = &pi;        // error: ptr is a plain pointer
          const double *cptr = &pi; // ok: cptr is a pointer to const

We cannot use a void* pointer (Section 4.2.2, p. 119) to hold the address of a const object. Instead, we must use the type const void* to hold the address of a const object:
不能使用 void* 指针(第 4.2.2 节)保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:
          const int universe = 42;
          const void *cpv = &universe; // ok: cpv is const
          void *pv = &universe;        // error: universe is const

A pointer to a const object can be assigned the address of a nonconst object, such as
允许把非 const 对象的地址赋给指向 const 对象的指针,例如:
          double dval = 3.14; // dval is a double; its value can be changed
          cptr = &dval;       // ok: but can't change dval through cptr

Although dval is not a const, any attempt to modify its value through cptr results in a compile-time error. When we declared cptr, we said that it would not change the value to which it points. The fact that it happens to point to a nonconst object is irrelevant.
尽管 dval 不是 const 对象,但任何企图通过指针 cptr 修改其值的行为都会导致编译时的错误。cptr 一经定义,就不允许修改其所指对象的值。如果该指针恰好指向非 const 对象时,同样必须遵循这个规则。
 We cannot use a pointer to const to change the underlying object. However, if the pointer addresses a nonconst object, it is possible that some other action will change the object to which the pointer points.
不能使用指向 const 对象的指针修改基础对象,然而如果该指针指向的是一个非 const 对象,可用其他方法修改其所指的对象。


The fact that values to which a const pointer points can be changed is subtle and can be confusing. Consider:
事实是,可以修改 const 指针所指向的值,这一点常常容易引起误会。考虑:
          dval = 3.14159;       // dval is not const
          *cptr = 3.14159;      // error: cptr is a pointer to const
          double *ptr = &dval;  // ok: ptr points at non-const double
          *ptr = 2.72;          // ok: ptr is plain pointer
          cout << *cptr;        // ok: prints 2.72



In this case, cptr is defined as a pointer to const but it actually points at a nonconst object. Even though the object to which it points is nonconst, we cannot use cptr to change the object's value. Essentially, there is no way for cptr to know whether the object it points to is const, and so it treats all objects to which it might point as const.
在此例题中,指向 const 的指针 cptr 实际上指向了一个非 const 对象。尽管它所指的对象并非 const,但仍不能使用 cptr 修改该对象的值。本质上来说,由于没有方法分辩 cptr 所指的对象是否为 const,系统会把它所指的所有对象都视为 const。
When a pointer to const does point to a nonconst, it is possible that the value of the object might change: After all, that value is not const. We could either assign to it directly or, as here, indirectly through another, plain nonconst pointer. It is important to remember that there is no guarantee that an object pointed to by a pointer to const won't change.
如果指向 const 的指针所指的对象并非 const,则可直接给该对象赋值或间接地利用普通的非 const 指针修改其值:毕竟这个值不是 const。重要的是要记住:不能保证指向 const 的指针所指对象的值一定不可修改。
 It may be helpful to think of pointers to const as "pointers that think they point to const."
如果把指向 const 的指针理解为“自以为指向 const 的指针”,这可能会对理解有所帮助。



In real-world programs, pointers to const occur most often as formal parameters of functions. Defining a parameter as a pointer to const serves as a contract guaranteeing that the actual object being passed into the function will not be modified through that parameter.
在实际的程序中,指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。
const Pointers
const 指针
In addition to pointers to const, we can also have const pointersthat is, pointers whose own value we may not change:
除指向 const 对象的指针外,C++ 语言还提供了 const 指针——本身的值不能修改:
          int errNumb = 0;
          int *const curErr = &errNumb; // curErr is a constant pointer

Reading this definition from right to left, we see that "curErr is a constant pointer to an object of type int." As with any const, we may not change the value of the pointerthat is, we may not make it point to any other object. Any attempt to assign to a constant pointereven assigning the same value back to curErris flagged as an error during compilation:
我们可以从右向左把上述定义语句读作“curErr 是指向 int 型对象的 const 指针”。与其他 const 量一样,const 指针的值不能修改,这就意味着不能使 curErr 指向其他对象。任何企图给 const 指针赋值的行为(即使给 curErr 赋回同样的值)都会导致编译时的错误:
          curErr = curErr; // error: curErr is const

As with any const, we must initialize a const pointer when we create it.
与任何 const 量一样,const 指针也必须在定义时初始化。
The fact that a pointer is itself const says nothing about whether we can use the pointer to change the value to which it points. Whether we can change the value pointed to depends entirely on the type to which the pointer points. For example, curErr addresses a plain, nonconst int. We can use curErr to change the value of errNumb:
指针本身是 const 的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。例如,curErr 指向一个普通的非常量 int 型对象 errNumb,则可使用 curErr 修改该对象的值:
          if (*curErr) {
              errorHandler();
              *curErr = 0; // ok: reset value of the object to which curErr is bound
          }

const Pointer to a const Object
指向 const 对象的 const 指针
We can also define a constant pointer to a constant object as follows:
还可以如下定义指向 const 对象的 const 指针:
          const double pi = 3.14159;
          // pi_ptr is const and points to a const object
          const double *const pi_ptr = &pi;

In this case, neither the value of the object addressed by pi_ptr nor the address itself can be changed. We can read its definition from right to left as "pi_ptr is a constant pointer to an object of type double defined as const."
本例中,既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向(即 pi_ptr 中存放的地址值)。可从右向左阅读上述声明语句:“pi_ptr 首先是一个 const 指针,指向 double 类型的 const 对象”。
Pointers and Typedefs
指针和 typedef
The use of pointers in typedefs (Section 2.6, p. 61) often leads to surprising results. Here is a question almost everyone answers incorrectly at least once. Given the following,
在 typedef(第 2.6 节)中使用指针往往会带来意外的结果。下面是一个几乎所有人刚开始时都会答错的问题。假设给出以下语句:
          typedef string *pstring;
          const pstring cstr;

what is the type of cstr? The simple answer is that it is a pointer to const pstring. The deeper question is: what underlying type does a pointer to const pstring represent? Many think that the actual type is
请问 cstr 变量是什么类型?简单的回答是 const pstring 类型的指针。进一步问:const pstring 指针所表示的真实类型是什么?很多人都认为真正的类型是:
          const string *cstr; // wrong interpretation of const pstring cstr

That is, that a const pstring would be a pointer to a constant string. But that is incorrect.
也就是说,const pstring 是一种指针,指向 string 类型的 const 对象,但这是错误的。
The mistake is in thinking of a typedef as a textual expansion. When we declare a const pstring, the const modifies the type of pstring, which is a pointer. Therefore, this definition declares cstr to be a const pointer to string. The definition is equivalent to
错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把 cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:
          // cstr is a const pointer to string
          string *const cstr; // equivalent to const pstring cstr

Advice: Understanding Complicated const Type Declarations
建议:理解复杂的 const 类型的声明
Part of the problem in reading const declarations arises because the const can go either before or after the type:
阅读 const 声明语句产生的部分问题,源于 const 限定符既可以放在类型前也可以放在类型后:
          string const s1;   // s1 and s2 have same type,
          const string s2;   // they're both strings that are const

When writing const definitions using typedefs, the fact that the const can precede the type can lead to confusion as to the actual type being defined:
用 typedef 写 const 类型定义时,const 限定符加在类型名前面容易引起对所定义的真正类型的误解:
[View full width]
          string s;
          typedef string *pstring;
          const pstring cstr1 = &s; // written this way the type
 is obscured
          pstring const cstr2 = &s; // all three decreations are
 the same type
          string *const cstr3 = &s; // they're all const pointers
 to string



Putting the const after pstring and reading the declaration from right to left makes it clearer that cstr2 is a const pstring, which in turn is a const pointer to string.
把 const 放在类型 pstring 之后,然后从右向左阅读该声明语句就会非常清楚地知道 cstr2 是 const pstring 类型,即指向 string 对象的 const 指针。
Unfortunately, most readers of C++ programs expect to see the const before the type. As a result, it is probably a good idea to put the const first, respecting common practice. But it can be helpful in understanding declarations to rewrite them to put the const after the type.
不幸的是,大多数人在阅读 C++ 程序时都习惯看到 const 放在类型前面。于是为了遵照惯例,只好建议编程时把 const 放在类型前面。但是,把声明语句重写为置 const 于类型之后更便于理解。


Exercises Section 4.3
Exercise 4.19:Explain the meaning of the following five definitions. Identify any illegal definitions.
解释下列 5 个定义的含义,指出其中哪些定义是非法的:
          (a) int i;
          (b) const int ic;
          (c) const int *pic;
          (d) int *const cpi;
          (e) const int *const cpic;



Exercise 4.20:Which of the following initializations are legal? Explain why.
下列哪些初始化是合法的?为什么?
          (a) int i = -1;
          (b) const int ic = i;
          (c) const int *pic = &ic;
          (d) int *const cpi = &ic;
          (e) const int *const cpic = &ic;



Exercise 4.21:Based on the definitions in the previous exercise, which of the following assignments are legal? Explain why.
根据上述定义,下列哪些赋值运算是合法的?为什么?
          (a) i = ic;
          (b) pic = &ic;
          (c) cpi = pic;
          (d) pic = cpic;
          (e) cpic = &ic;
          (f) ic = *cpic;





      

4.3. C-Style Character Strings
4.3. C 风格字符串
Although C++ supports C-style strings, they should not be used by C++ programs. C-style strings are a surprisingly rich source of bugs and are the root cause of many, many security problems.
尽管 C++ 支持 C 风格字符串,但不应该在 C++ 程序中使用这个类型。C 风格字符串常常带来许多错误,是导致大量安全问题的根源。

In Section 2.2 (p. 40) we first used string literals and learned that the type of a string literal is array of constant characters. We can now be more explicit and note that the type of a string literal is an array of const char. A string literal is an instance of a more general construct that C++ inherits from C: C-style character strings. C-style strings are not actually a type in either C or C++. Instead, C-style strings are null-terminated arrays of characters:
在第 2.2 节中我们第一次使用了字符串字面值,并了解字符串字面值的类型是字符常量的数组,现在可以更明确地认识到:字符串字面值的类型就是 const char 类型的数组。C++ 从 C 语言继承下来的一种通用结构是C 风格字符串,而字符串字面值就是该类型的实例。实际上,C 风格字符串既不能确切地归结为 C 语言的类型,也不能归结为 C++ 语言的类型,而是以空字符 null 结束的字符数组:
          char ca1[] = {'C', '+', '+'};        // no null, not C-style string
          char ca2[] = {'C', '+', '+', '\0'};  // explicit null
          char ca3[] = "C++";     // null terminator added automatically
          const char *cp = "C++"; // null terminator added automatically
          char *cp1 = ca1;   // points to first element of a array, but not C-style string
          char *cp2 = ca2;   // points to first element of a null-terminated char array

Neither ca1 nor cp1 are C-style strings: ca1 is a character array, but the array is not null-terminated. cp1, which points to ca1, therefore, does not point to a null-terminated array. The other declarations are all C-style strings, remembering that the name of an array is treated as a pointer to the first element of the array. Thus, ca2 and ca3 are pointers to the first elements of their respective arrays.
ca1 和 cp1 都不是 C 风格字符串:ca1 是一个不带结束符 null 的字符数组,而指针 cp1 指向 ca1,因此,它指向的并不是以 null 结束的数组。其他的声明则都是 C 风格字符串,数组的名字即是指向该数组第一个元素的指针。于是,ca2 和 ca3 分别是指向各自数组第一个元素的指针。
Using C-style Strings
C 风格字符串的使用
C-style strings are manipulated through (const) char* pointers. One frequent usage pattern uses pointer arithmetic to traverse the C-style string. The traversal tests and increments the pointer until we reach the terminating null character:
C++ 语言通过(const)char*类型的指针来操纵 C 风格字符串。一般来说,我们使用指针的算术操作来遍历 C 风格字符串,每次对指针进行测试并递增 1,直到到达结束符 null 为止:
          const char *cp = "some value";
          while (*cp) {
              // do something to *cp
              ++cp;
          }

The condition in the while dereferences the const char* pointer cp and the resulting character is tested for its true or false value. A true value is any character other than the null. So, the loop continues until it encounters the null character that terminates the array to which cp points. The body of the while does whatever processing is needed and concludes by incrementing cp to advance the pointer to address the next character in the array.
while 语句的循环条件是对 const char* 类型的指针 cp 进行解引用,并判断 cp 当前指向的字符是 true 值还是 false 值。真值表明这是除 null 外的任意字符,则继续循环直到 cp 指向结束字符数组的 null 时,循环结束。while 循环体做完必要的处理后,cp 加1,向下移动指针指向数组中的下一个字符。
This loop will fail if the array that cp addresses is not null-terminated. If this case, the loop is apt to read characters starting at cp until it encounters a null character somewhere in memory.
如果 cp 所指向的字符数组没有 null 结束符,则此循环将会失败。这时,循环会从 cp 指向的位置开始读数,直到遇到内存中某处 null 结束符为止。

C Library String Functions
C 风格字符串的标准库函数
The Standard C library provides a set of functions, listed in Table 4.1, that operate on C-style strings. To use these functions, we must include the associated C header file
表4-1列出了 C 语言标准库提供的一系列处理 C 风格字符串的库函数。要使用这些标准库函数,必须包含相应的 C 头文件:
which is the C++ version of the string.h header from the C library.
cstring 是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。
These functions do no checking on their string parameters.
这些标准库函数不会检查其字符串参数。




Table 4.1. C-Style Character String Functions
表 4.1. 操纵 C 风格字符串的标准库函数
strlen(s)Returns the length of s, not counting the null.
返回 s 的长度,不包括字符串结束符 null
strcmp(s1, s2)Compares s1 and s2 for equality. Returns 0 if s1 == s2, positive value if s1 > s2, negative value if s1 < s2.
比较两个字符串 s1 和 s2 是否相同。若 s1 与 s2 相等,返回 0;若 s1 大于 s2,返回正数;若 s1 小于 s2,则返回负数
strcat(s1, s2)Appends s2 to s1. Returns s1.
将字符串 s2 连接到 s1 后,并返回 s1
strcpy(s1, s2)Copies s2 into s1. Returns s1.
将 s2 复制给 s1,并返回 s1
strncat(s1, s2,n)Appends n characters from s2 onto s1. Returns s1.
将 s2 的前 n 个字符连接到 s1 后面,并返回 s1
strncpy(s1, s2, n)Copies n characters from s2 into s1. Returns s1.
将 s2 的前 n 个字符复制给 s1,并返回 s1


          #include <cstring>



The pointer(s) passed to these routines must be nonzero and each pointer must point to the initial character in a null-terminated array. Some of these functions write to a string they are passed. These functions assume that the array to which they write is large enough to hold whatever characters the function generates. It is up to the programmer to ensure that the target string is big enough.
传递给这些标准库函数例程的指针必须具有非零值,并且指向以 null 结束的字符数组中的第一个元素。其中一些标准库函数会修改传递给它的字符串,这些函数将假定它们所修改的字符串具有足够大的空间接收本函数新生成的字符,程序员必须确保目标字符串必须足够大。
When we compare library strings, we do so using the normal relational operators. We can use these operators to compare pointers to C-style strings, but the effect is quite different; what we're actually comparing is the pointer values, not the strings to which they point:
C++ 语言提供普通的关系操作符实现标准库类型 string 的对象的比较。这些操作符也可用于比较指向C风格字符串的指针,但效果却很不相同:实际上,此时比较的是指针上存放的地址值,而并非它们所指向的字符串:
          if (cp1 < cp2) // compares addresses, not the values pointed to

Assuming cp1 and cp2 point to elements in the same array (or one past that array), then the effect of this comparison is to compare the address in cp1 with the address in cp2. If the pointers do not address the same array, then the comparison is undefined.
如果 cp1 和 cp2 指向同一数组中的元素(或该数组的溢出位置),上述表达式等效于比较在 cp1 和 cp2 中存放的地址;如果这两个指针指向不同的数组,则该表达式实现的比较没有定义。
To compare the strings, we must use strcmp and interpret the result:
字符串的比较和比较结果的解释都须使用标准库函数 strcmp 进行:
          const char *cp1 = "A string example";
          const char *cp2 = "A different string";
          int i = strcmp(cp1, cp2);    // i is positive
          i = strcmp(cp2, cp1);        // i is negative
          i = strcmp(cp1, cp1);        // i is zero

The strcmp function returns three possible values: 0 if the strings are equal; or a positive or negative value, depending on whether the first string is larger or smaller than the second.
标准库函数 strcmp 有 3 种可能的返回值:若两个字符串相等,则返回 0 值;若第一个字符串大于第二个字符串,则返回正数,否则返回负数。
Never Forget About the Null-Terminator
永远不要忘记字符串结束符 null
When using the C library string functions it is essential to remember the strings must be null-terminated:
在使用处理 C 风格字符串的标准库函数时,牢记字符串必须以结束符 null 结束:
          char ca[] = {'C', '+', '+'}; // not null-terminated
          cout << strlen(ca) << endl; // disaster: ca isn't null-terminated

In this case, ca is an array of characters but is not null-terminated. What happens is undefined. The strlen function assumes that it can rely on finding a null character at the end of its argument. The most likely effect of this call is that strlen will keep looking through the memory that follows wherever ca happens to reside until it encounters a null character. In any event, the return from strlen will not be the correct value.
在这个例题中,ca 是一个没有 null 结束符的字符数组,则计算的结果不可预料。标准库函数 strlen 总是假定其参数字符串以 null 字符结束,当调用该标准库函数时,系统将会从实参 ca 指向的内存空间开始一直搜索结束符,直到恰好遇到 null 为止。strlen 返回这一段内存空间中总共有多少个字符,无论如何这个数值不可能是正确的。
Caller Is Responsible for Size of a Destination String
调用者必须确保目标字符串具有足够的大小
The array that we pass as the first argument to strcat and strcpy must be large enough to hold the generated string. The code we show here, although a common usage pattern, is frought with the potential for serious error:
传递给标准库函数 strcat 和 strcpy 的第一个实参数组必须具有足够大的空间存放新生成的字符串。以下代码虽然演示了一种通常的用法,但是却有潜在的严重错误:
          // Dangerous: What happens if we miscalculate the size of largeStr?
          char largeStr[16 + 18 + 2];         // will hold cp1 a space and cp2
          strcpy(largeStr, cp1);              // copies cp1 into largeStr
          strcat(largeStr, " ");              // adds a space at end of largeStr
          strcat(largeStr, cp2);              // concatenates cp2 to largeStr
          // prints A string example A different string
          cout << largeStr << endl;



The problem is that we could easily miscalculate the size needed in largeStr. Similarly, if we later change the sizes of the strings to which either cp1 or cp2 point, then the calculated size of largeStr will be wrong. Unfortunately, programs similar to this code are widely distributed. Programs with such code are error-prone and often lead to serious security leaks.
问题在于我们经常会算错 largeStr 需要的大小。同样地,如果 cp1 或 cp2 所指向的字符串大小发生了变化,largeStr 所需要的大小则会计算错误。不幸的是,类似于上述代码的程序应用非常广泛,这类程序往往容易出错,并导致严重的安全漏洞。
When Using C-Style Strings, Use the strn Functions
使用 strn 函数处理C风格字符串
If you must use C-style strings, it is usually safer to use the strncat and strncpy functions instead of strcat and strcpy:
如果必须使用 C 风格字符串,则使用标准库函数 strncat 和 strncpy 比 strcat 和 strcpy 函数更安全:
          char largeStr[16 + 18 + 2]; // to hold cp1 a space and cp2
          strncpy(largeStr, cp1, 17); // size to copy includes the null
          strncat(largeStr, " ", 2);  // pedantic, but a good habit
          strncat(largeStr, cp2, 19); // adds at most 18 characters, plus a null

The trick to using these versions is to properly calculate the value to control how many characters get copied. In particular, we must always remember to account for the null when copying or concatenating characters. We must allocate space for the null because that is the character that terminates largeStr after each call. Let's walk through these calls in detail:
使用标准库函数 strncat 和 strncpy 的诀窍在于可以适当地控制复制字符的个数。特别是在复制和串连字符串时,一定要时刻记住算上结束符 null。在定义字符串时要切记预留存放 null字符的空间,因为每次调用标准库函数后都必须以此结束字符串 largeStr。让我们详细分析一下这些标准库函数的调用:
On the call to strncpy, we ask to copy 17 characters: all the characters in cp1 plus the null. Leaving room for the null is necessary so that largeStr is properly terminated. After the strncpy call, largeStr has a strlen value of 16. Remember, strlen counts the characters in a C-style string, not including the null.
调用 strncpy 时,要求复制 17 个字符:字符串 cp1 中所有字符,加上结束符 null。留下存储结束符 null 的空间是必要的,这样 largeStr 才可以正确地结束。调用 strncpy 后,字符串 largeStr 的长度 strlen 值是 16。记住:标准库函数 strlen 用于计算 C 风格字符串中的字符个数,不包括 null结束符。
When we call strncat, we ask to copy two characters: the space and the null that terminates the string literal. After this call, largeStr has a strlen of 17. The null that had ended largeStr is overwritten by the space that we appended. A new null is written after that space.
调用 strncat 时,要求复制 2 个字符:一个空格和结束该字符串字面值的 null。调用结束后,字符串 largeStr 的长度是 17,原来用于结束 largeStr 的 null 被新添加的空格覆盖了,然后在空格后面写入新的结束符 null。
When we append cp2 in the second call, we again ask to copy all the characters from cp2, including the null. After this call, the strlen of largeStr would be 35: 16 characters from cp1, 18 from cp2, and 1 for the space that separates the two strings.
第二次调用 strncat 串接 cp2 时,要求复制 cp2 中所有字符,包括字符串结束符 null。调用结束后,字符串 largeStr 的长度是 35:cp1 的 16 个字符和 cp2 的 18 个字符,再加上分隔这两个字符串的一个空格。
The array size of largeStr remains 36 throughout.
整个过程中,存储 largeStr 的数组大小始终保持为 36(包括结束符)。
These operations are safer than the simpler versions that do not take a size argument as long as we calculate the size argument correctly. If we ask to copy or concatenate more characters than the size of the target array, we will still overrun that array. If the string we're copying from or concatenating is bigger than the requested size, then we'll inadvertently truncate the new version. Truncating is safer than overrunning the array, but it is still an error.
只要可以正确计算出 size 实参的值,使用 strn 版本要比没有 size 参数的简化版本更安全。但是,如果要向目标数组复制或串接比其 size 更多的字符,数组溢出的现象仍然会发生。如果要复制或串接的字符串比实际要复制或串接的 size 大,我们会不经意地把新生成的字符串截短了。截短字符串比数组溢出要安全,但这仍是错误的。
Whenever Possible, Use Library strings
尽可能使用标准库类型 string
None of these issues matter if we use C++ library strings:
如果使用 C++ 标准库类型 string,则不存在上述问题:
          string largeStr = cp1; // initialize large Str as a copy of cp1
          largeStr += " ";       // add space at end of largeStr
          largeStr += cp2;       // concatenate cp2 onto end of largeStr

Now the library handles all memory management, and we need no longer worry if the size of either string changes.
此时,标准库负责处理所有的内存管理问题,我们不必再担心每一次修改字符串时涉及到的大小问题。
For most applications, in addition to being safer, it is also more efficient to use library strings rather than C-style strings.
对大部分的应用而言,使用标准库类型 string,除了增强安全性外,效率也提高了,因此应该尽量避免使用 C 风格字符串。


Exercises Section 4.3
Exercise 4.22:Explain the difference between the following two while loops:
解释下列两个 while 循环的差别:
          const char *cp = "hello";
          int cnt;
          while (cp) { ++cnt; ++cp; }
          while (*cp) { ++cnt; ++cp; }



Exercise 4.23:What does the following program do?
下列程序实现什么功能?
          const char ca[] = {'h', 'e', 'l', 'l', 'o'};
          const char *cp = ca;
          while (*cp) {
              cout << *cp << endl;
              ++cp;
          }



Exercise 4.24:Explain the differences between strcpy and strncpy. What are the advantages of each? The disadvantages?
解释 strcpy 和 strncpy 的差别在哪里,各自的优缺点是什么?
Exercise 4.25:Write a program to compare two strings. Now write a program to compare the value of two C-style character strings.
编写程序比较两个 string 类型的字符串,然后编写另一个程序比较两个C风格字符串的值。
Exercise 4.26:Write a program to read a string from the standard input. How might you write a program to read from the standard input into a C-style character string?
编写程序从标准输入设备读入一个 string 类型的字符串。考虑如何编程实现从标准输入设备读入一个 C 风格字符串。


4.3.1. Dynamically Allocating Arrays
4.3.1. 创建动态数组
A variable of array type has three important limitations: Its size is fixed, the size must be known at compile time, and the array exists only until the end of the block in which it was defined. Real-world programs usually cannot live with these restrictionsthey need a way to allocate an array dynamically at run time. Although all arrays have fixed size, the size of a dynamically allocated array need not be fixed at compile time. It can be (and usually is) determined at run time. Unlike an array variable, a dynamically allocated array continues to exist until it is explicitly freed by the program.
数组类型的变量有三个重要的限制:数组长度固定不变,在编译时必须知道其长度,数组只在定义它的块语句内存在。实际的程序往往不能忍受这样的限制——它们需要在运行时动态地分配数组。虽然数组长度是固定的,但动态分配的数组不必在编译时知道其长度,可以(通常也是)在运行时才确定数组长度。与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。
Every program has a pool of available memory it can use during program execution to hold dynamically allocated objects. This pool of available memory is referred to as the program's free store or heap. C programs use a pair of functions named malloc and free to allocate space from the free store. In C++ we use new and delete expressions.
每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。C 语言程序使用一对标准库函数 malloc 和 free 在自由存储区中分配存储空间,而 C++ 语言则使用 new 和 delete 表达式实现相同的功能。
Defining a Dynamic Array
动态数组的定义
When we define an array variable, we specify a type, a name, and a dimension. When we dynamically allocate an array, we specify the type and size but do not name the object. Instead, the new expression returns a pointer to the first element in the newly allocated array:
数组变量通过指定类型、数组名和维数来定义。而动态分配数组时,只需指定类型和数组长度,不必为数组对象命名,new 表达式返回指向新分配数组的第一个元素的指针:
          int *pia = new int[10]; // array of 10 uninitialized ints

This new expression allocates an array of ten ints and returns a pointer to the first element in that array, which we use to initialize pia.
此 new 表达式分配了一个含有 10 个 int 型元素的数组,并返回指向该数组第一个元素的指针,此返回值初始化了指针 pia。
A new expression takes a type and optionally an array dimension specified inside a bracket-pair. The dimension can be an arbitrarily complex expression. When we allocate an array, new returns a pointer to the first element in the array. Objects allocated on the free store are unnamed. We use objects on the heap only indirectly through their address.
new 表达式需要指定指针类型以及在方括号中给出的数组维数,该维数可以是任意的复杂表达式。创建数组后,new 将返回指向数组第一个元素的指针。在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。
Initializing a Dynamically Allocated Array
初始化动态分配的数组
When we allocate an array of objects of a class type, then that type's default constructor (Section 2.3.4, p. 50) is used to initialize each element. If the array holds elements of built-in type, then the elements are uninitialized:
动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数(第 2.3.4 节)实现初始化;如果数组元素是内置类型,则无初始化:
          string *psa = new string[10]; // array of 10 empty strings
          int *pia = new int[10];       // array of 10 uninitialized ints

Each of these new expressions allocates an array of ten objects. In the first case, those objects are strings. After allocating memory to hold the objects, the default string constructor is run on each element of the array in turn. In the second case, the objects are a built-in type; memory to hold ten ints is allocated, but the elements are uninitialized.
这两个 new 表达式都分配了含有 10 个对象的数组。其中第一个数组是 string 类型,分配了保存对象的内存空间后,将调用 string 类型的默认构造函数依次初始化数组中的每个元素。第二个数组则具有内置类型的元素,分配了存储 10 个 int 对象的内存空间,但这些元素没有初始化。
Alternatively, we can value-initialize (Section 3.3.1, p. 92) the elements by following the array size by an empty pair of parentheses:
也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化(第 3.3.1 节):
          int *pia2 = new int[10] (); // array of 10 uninitialized ints

The parentheses are effectively a request to the compiler to value-initialize the array, which in this case sets its elements to 0.
圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为0。
The elements of a dynamically allocated array can be initialized only to the default value of the element type. The elements cannot be initialized to separate values as can be done for elements of an array variable.
对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。

Dynamic Arrays of const Objects
const 对象的动态数组
If we create an array of const objects of built-in type on the free store, we must initialize that array: The elements are const, there is no way to assign values to the elements. The only way to initialize the elements is to value-initialize the array:
如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:
          // error: uninitialized const array
          const int *pci_bad = new const int[100];
          // ok: value-initialized const array
          const int *pci_ok = new const int[100]();

It is possible to have a const array of elements of a class type that provides a default constructor:
C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数:
          // ok: array of 100 empty strings
          const string *pcs = new const string[100];

In this case, the default constructor is used to initialize the elements of the array.
在这里,将使用 string 类的默认构造函数初始化数组元素。
Of course, once the elements are created, they may not be changedwhich means that such arrays usually are not very useful.
当然,已创建的常量元素不允许修改——因此这样的数组实际上用处不大。
It Is Legal to Dynamically Allocate an Empty Array
允许动态分配空数组
When we dynamically allocate an array, we often do so because we don't know the size of the array at compile time. We might write code such as
之所以要动态分配数组,往往是由于编译时并不知道数组的长度。我们可以编写如下代码
          size_t n = get_size(); // get_size returns number of elements needed
          int* p = new int[n];
          for (int* q = p; q != p + n; ++q)
               /* process the array */ ;

to figure out the size of the array and then allocate and process the array.
计算数组长度,然后创建和处理该数组。
An interesting question is: What happens if get_size returns 0? The answer is that our code works fine. The language specifies that a call to new to create an array of size zero is legal. It is legal even though we could not create an array variable of size 0:
有趣的是,如果 get_size 返回 0 则会怎么样?答案是:代码仍然正确执行。C++ 虽然不允许定义长度为 0 的数组变量,但明确指出,调用 new 动态创建长度为 0 的数组是合法的:
          char arr[0];            // error: cannot define zero-length array
          char *cp = new char[0]; // ok: but cp can't be dereferenced

When we use new to allocate an array of zero size, new returns a valid, nonzero pointer. This pointer will be distinct from any other pointer returned by new. The pointer cannot be dereferencedafter all, it points to no element. The pointer can be compared and so can be used in a loop such as the preceeding one. It is also legal to add (or subtract) zero to such a pointer and to subtract the pointer from itself, yielding zero.
用 new 动态创建长度为 0 的数组时,new 返回有效的非零指针。该指针与 new 返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。而允许的操作包括:比较运算,因此该指针能在循环中使用;在该指针上加(减)0;或者减去本身,得 0 值。
In our hypothetical loop, if the call to get_size returned 0, then the call to new would still succeed. However, p would not address any element; the array is empty. Because n is zero, the for loop effectively compares q to p. These pointers are equal; q was initialized to p, so the condition in the for fails and the loop body is not executed.
在上述例题中,如果 get_size 返回 0,则仍然可以成功调用 new,但是 p 并没有指向任何对象,数组是空的。因为 n 为 0,所以 for 循环实际比较的是 p 和 q,而 q 是用 p 初始化的,两者具有相等的值,因此 for 循环条件不成立,循环体一次都没有执行。
Freeing Dynamic Memory
动态空间的释放
When we allocate memory, we must eventually free it. Otherwise, memory is gradually used up and may be exhausted. When we no longer need the array, we must explicitly return its memory to the free store. We do so by applying the delete [] expression to a pointer that addresses the array we want to release:
动态分配的内存最后必须进行释放,否则,内存最终将会逐渐耗尽。如果不再需要使用动态创建的数组,程序员必须显式地将其占用的存储空间返还给程序的自由存储区。C++ 语言为指针提供 delete [] 表达式释放指针所指向的数组空间:
          delete [] pia;

deallocates the array pointed to by pia, returning the associated memory to the free store. The empty bracket pair between the delete keyword and the pointer is necessary: It indicates to the compiler that the pointer addresses an array of elements on the free store and not simply a single object.
该语句回收了 pia 所指向的数组,把相应的内存返还给自由存储区。在关键字 delete 和指针之间的空方括号对是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。
If the empty bracket pair is omitted, it is an error, but an error that the compiler is unlikely to catch; the program may fail at run time.
如果遗漏了空方括号对,这是一个编译器无法发现的错误,将导致程序在运行时出错。




The least serious run-time consequence of omitting brackets when freeing an array is that too little memory will be freed, leading to a memory leak. On some systems and/or for some element types, more serious run-time problems are possible. It is essential to remember the bracket-pair when deleting pointers to arrays.
理论上,回收数组时缺少空方括号对,至少会导致运行时少释放了内存空间,从而产生内存泄漏(memory leak)。对于某些系统和/或元素类型,有可能会带来更严重的运行时错误。因此,在释放动态数组时千万别忘了方括号对。
Contrasting C-Style Strings and C++ Library strings
C 风格字符串与 C++ 的标准库类型 string 的比较
The following two programs illustrate the differences in using C-style character strings versus using the C++ library string type. The string version is shorter, easier to understand, and less error-prone:
以下两段程序反映了使用 C 风格字符串与 C++ 的标准库类型 string 的不同之处。使用 string 类型的版本更短、更容易理解,而且出错的可能性更小:


[View full width]

          // C-style character string implementation
             const char *pc = "a very long literal string";
             const size_t len = strlen(pc +1);      // space to
 allocate
             // performance test on string allocation and copy
             for (size_t ix = 0; ix != 1000000; ++ix) {
                 char *pc2 = new char[len + 1]; // allocate the space
                 strcpy(pc2, pc);               // do the copy
                 if (strcmp(pc2, pc))           // use the new string
                     ;   // do nothing
                 delete [] pc2;                 // free the memory
          }
          // string implementation
             string str("a very long literal string");
             // performance test on string allocation and copy
             for (int ix = 0; ix != 1000000; ++ix) {
                 string str2 = str; // do the copy, automatically
 allocated
                 if (str != str2)           // use the new string
                       ;  // do nothing
          }
                                            // str2 is
 automatically freed


These programs are further explored in the exercises to Section 4.3.1 (p. 139).
这些程序将在4.3.1节的习题中做进一步探讨。




Using Dynamically Allocated Arrays
动态数组的使用
A common reason to allocate an array dynamically is if its dimension cannot be known at compile time. For example, char* pointers are often used to refer to multiple C-style strings during the execution of a program. The memory used to hold the various strings typically is allocated dynamically during program execution based on the length of the string to be stored. This technique is considerably safer than allocating a fixed-size array. Assuming we correctly calculate the size needed at run time, we no longer need to worry that a given string will overflow the fixed size of an array variable.
通常是因为在编译时无法知道数组的维数,所以才需要动态创建该数组。例如,在程序执行过程中,常常使用char*指针指向多个C风格字符串,于是必须根据每个字符串的长度实时地动态分配存储空间。采用这种技术要比建立固定大小的数组安全。如果程序员能够准确计算出运行时需要的数组长度,就不必再担心因数组变量具有固定的长度而造成的溢出问题。
Suppose we have the following C-style strings:
假设有以下C风格字符串:
          const char *noerr = "success";
          // ...
          const char *err189 = "Error: a function declaration must "
                               "specify a function return type!";

We might want to copy one or the other of these strings at run time to a new character array. We could calculate the dimension at run time, as follows:
我们想在运行时把这两个字符串中的一个复制给新的字符数组,于是可以用以下程序在运行时计算维数:
    const char *errorTxt;
    if (errorFound)
        errorTxt = err189;
    else
        errorTxt = noerr;
    // remember the 1 for the terminating null
    int dimension = strlen(errorTxt) + 1;
    char *errMsg = new char[dimension];
    // copy the text for the error into errMsg
    strncpy (errMsg, errorTxt, dimension);

Recall that strlen returns the length of the string not including the null. It is essential to remember to add 1 to the length returned from strlen to accommodate the trailing null.
别忘记标准库函数 strlen 返回的是字符串的长度,并不包括字符串结束符,在获得的字符串长度上必须加 1 以便在动态分配时预留结束符的存储空间。
Exercises Section 4.3.1
Exercise 4.27:Given the following new expression, how would you delete pa?
假设有下面的 new 表达式,请问如何释放 pa?
     int *pa = new int[10];


Exercise 4.28:Write a program to read the standard input and build a vector of ints from values that are read. Allocate an array of the same size as the vector and copy the elements from the vector into the array.
编写程序由从标准输入设备读入的元素数据建立一个 int 型 vector 对象,然后动态创建一个与该 vector 对象大小一致的数组,把 vector 对象的所有元素复制给新数组。
Exercise 4.29:Given the two program fragments in the highlighted box on page 138,
对本小节第 5 条框中的两段程序:
Explain what the programs do.
解释这两个程序实现什么功能?
As it happens, on average, the string class implementation executes considerably faster than the C-style string functions. The relative average execution times on our more than five-year-old PC are as follows:
平均来说,使用 string 类型的程序执行速度要比用 C 风格字符串的快很多,在我们用了五年的 PC 机上其平均执行速度分别是:
          user       0.47    # string class
          user       2.55    # C-style character string



Did you expect that? How would you account for it?
你预计的也一样吗?请说明原因。
Exercise 4.30:Write a program to concatenate two C-style string literals, putting the result in a C-style string. Write a program to concatenate two library strings that have the same value as the literals used in the first program.
编写程序连接两个C风格字符串字面值,把结果存储在一个C风格字符串中。然后再编写程序连接两个 string 类型字符串,这两个 string 类型字符串与前面的C风格字符串字面值具有相同的内容。


4.3.2. Interfacing to Older Code
4.3.2. 新旧代码的兼容
Many C++ programs exist that predate the standard library and so do not yet use the string and vector types. Moreover, many C++ programs interface to existing C programs that cannot use the C++ library. Hence, it is not infrequent to encounter situations where a program written in modern C++ must interface to code that uses arrays and/or C-style character strings. The library offers facilities to make the interface easier to manage.
许多 C++ 程序在有标准类之前就已经存在了,因此既没有使用标准库类型 string 也没有使用 vector。而且,许多 C++ 程序为了兼容现存的 C 程序,也不能使用 C++ 标准库。因此,现代的 C++ 程序经常必须兼容使用数组和/或 C 风格字符串的代码,标准库提供了使兼容界面更容易管理的手段。
Mixing Library strings and C-Style Strings
混合使用标准库类 string 和 C 风格字符串
As we saw on page 80 we can initialize a string from a string literal:
正如第 3.2.1 节中显示的,可用字符串字面值初始化 string 类对象:
          string st3("Hello World");  // st3 holds Hello World

More generally, because a C-style string has the same type as a string literal and is null-terminated in the same way, we can use a C-style string anywhere that a string literal can be used:
通常,由于 C 风格字符串与字符串字面值具有相同的数据类型,而且都是以空字符 null 结束,因此可以把 C 风格字符串用在任何可以使用字符串字面值的地方:
We can initialize or assign to a string from a C-style string.
可以使用 C 风格字符串对 string 对象进行初始化或赋值。
We can use a C-style string as one of the two operands to the string addition or as the right-hand operand to the compound assignment operators.
string 类型的加法操作需要两个操作数,可以使用 C 风格字符串作为其中的一个操作数,也允许将 C 风格字符串用作复合赋值操作的右操作数。
The reverse functionality is not provided: there is no direct way to use a library string when a C-style string is required. For example, there is no way to initialize a character pointer from a string:
反之则不成立:在要求C风格字符串的地方不可直接使用标准库 string 类型对象。例如,无法使用 string 对象初始化字符指针:
          char *str = st2; // compile-time type error

There is, however, a string member function named c_str that we can often use to accomplish what we want:
但是,string 类提供了一个名为 c_str 的成员函数,以实现我们的要求:
          char *str = st2.c_str(); // almost ok, but not quite

The name c_str indicates that the function returns a C-style character string. Literally, it says, "Get me the C-style string representation"that is, a pointer to the beginning of a null-terminated character array that holds the same data as the characters in the string.
c_str 函数返回 C 风格字符串,其字面意思是:“返回 C 风格字符串的表示方法”,即返回指向字符数组首地址的指针,该数组存放了与 string 对象相同的内容,并且以结束符 null 结束。
This initialization fails because c_str returns a pointer to an array of const char. It does so to prevent changes to the array. The correct initialization is:
如果 c_str 返回的指针指向 const char 类型的数组,则上述初始化失败,这样做是为了避免修改该数组。正确的初始化应为:
          const char *str = st2.c_str(); // ok

The array returned by c_str is not guaranteed to be valid indefinitely. Any subsequent use of st2 that might change the value of st2 can invalidate the array. If a program needs continuing access to the data, then the program must copy the array returned by c_str.
c_str 返回的数组并不保证一定是有效的,接下来对 st2 的操作有可能会改变 st2 的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制 c_str 函数返回的数组。

Using an Array to Initialize a vector
使用数组初始化 vector 对象
On page 112 we noted that it is not possible to initialize an array from another array. Instead, we have to create the array and then explicitly copy the elements from one array into the other. It turns out that we can use an array to initialize a vector, although the form of the initialization may seem strange at first. To initialize a vector from an array, we specify the address of the first element and one past the last element that we wish to use as initializers:
第 4.1.1 节提到不能用一个数组直接初始化另一数组,程序员只能创建新数组,然后显式地把源数组的元素逐个复制给新数组。这反映 C++ 允许使用数组初始化 vector 对象,尽管这种初始化形式起初看起来有点陌生。使用数组初始化 vector 对象,必须指出用于初始化式的第一个元素以及数组最后一个元素的下一位置的地址:
          const size_t arr_size = 6;
          int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
          // ivec has 6 elements: each a copy of the corresponding element in int_arr
          vector<int> ivec(int_arr, int_arr + arr_size);

The two pointers passed to ivec mark the range of values with which to initialize the vector. The second pointer points one past the last element to be copied. The range of elements marked can also represent a subset of the array:
传递给 ivec 的两个指针标出了 vector 初值的范围。第二个指针指向被复制的最后一个元素之后的地址空间。被标出的元素范围可以是数组的子集:
          // copies 3 elements: int_arr[1], int_arr[2], int_arr[3]
          vector<int> ivec(int_arr + 1, int_arr + 4);

This initialization creates ivec with three elements. The values of these elements are copies of the values in int_arr[1] through int_arr[3].
这个初始化创建了含有三个元素的 ivec,三个元素的值分别是 int_arr[1] 到 int_arr[3] 的副本。
Exercises Section 4.3.2
Exercise 4.31:Write a program that reads a string into a character array from the standard input. Describe how your program handles varying size inputs. Test your program by giving it a string of data that is longer than the array size you've allocated.
编写程序从标准输入设备读入字符串,并把该串存放在字符数组中。描述你的程序如何处理可变长的输入。提供比你分配的数组长度长的字符串数据测试你的程序。
Exercise 4.32:Write a program to initialize a vector from an array of ints.
编写程序用 int 型数组初始化 vector 对象。
Exercise 4.33:Write a program to copy a vector of ints into an array of ints.
编写程序把 int 型 vector 复制给 int 型数组。
Exercise 4.34:Write a program to read strings into a vector. Now, copy that vector into an array of character pointers. For each element in the vector, allocate a new character array and copy the data from the vector element into that character array. Then insert a pointer to the character array into the array of character pointers.
编写程序读入一组 string 类型的数据,并将它们存储在 vector 中。接着,把该 vector 对象复制给一个字符指针数组。为 vector 中的每个元素创建一个新的字符数组,并把该 vector 元素的数据复制到相应的字符数组中,最后把指向该数组的指针插入字符指针数组。
Exercise 4.35:Print the contents of the vector and the array created in the previous exercise. After printing the array, remember to delete the character arrays.
输出习题 4.34中建立的 vector 对象和数组的内容。输出数组后,记得释放字符数组。

 
      

4.4. Multidimensioned Arrays
4.4. 多维数组
Strictly speaking, there are no multidimensioned arrays in C++. What is commonly referred to as a multidimensioned array is actually an array of arrays:
严格地说,C++ 中没有多维数组,通常所指的多维数组其实就是数组的数组:


          // array of size 3, each element is an array of ints of size 4
          int ia[3][4];



It can be helpful to keep this fact in mind when using what appears to be a multidimensioned array.
在使用多维数组时,记住这一点有利于理解其应用。


An array whose elements are an array is said to have two dimensions. Each dimension is referred to by its own subscript:
如果数组的元素又是数组,则称为二维数组,其每一维对应一个下标:
     ia[2][3] // fetches last element from the array in the last row



The first dimension is often referred to as the row and the second as the column. In C++ there is no limit on how many subscripts are used. That is, we could have an array whose elements are arrays of elements that are arrays, and so on.
第一维通常称为行(row),第二维则称为列(column)。C++ 中并未限制可用的下标个数,也就是说,我们可以定义元素是数组(其元素又是数组,如此类推)的数组。
Initializing the Elements of a Multidimensioned Array
多维数组的初始化
As with any array, we can initialize the elements by providing a bracketed list of initializers. Multidimensioned arrays may be initialized by specifying bracketed values for each row:
和处理一维数组一样,程序员可以使用由花括号括起来的初始化式列表来初始化多维数组的元素。对于多维数组的每一行,可以再用花括号指定其元素的初始化式:
     int ia[3][4] = {     /*  3 elements, each element is an array of size 4 */
         {0, 1, 2, 3} ,   /*  initializers for row indexed by 0 */
         {4, 5, 6, 7} ,   /*  initializers for row indexed by 1 */
         {8, 9, 10, 11}   /*  initializers for row indexed by 2 */
     };

The nested braces, which indicate the intended row, are optional. The following initialization is equivalent, although considerably less clear.
其中用来标志每一行的内嵌的花括号是可选的。下面的初始化尽管有点不清楚,但与前面的声明完全等价:
     // equivalent initialization without the optional nested braces for each row
     int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

As is the case for single-dimension arrays, elements may be left out of the initializer list. We could initialize only the first element of each row as follows:
与一维数组一样,有些元素将不使用初始化列表提供的初始化式进行初始化。下面的声明只初始化了每行的第一个元素:
     // explicitly initialize only element 0 in each row
     int ia[3][4] = {{ 0 } , { 4 } , { 8 } };

The values of the remaining elements depend on the element type and follow the rules descibed on page 112.
其余元素根据其元素类型用第 4.1.1 节描述的规则初始化。
If the nested braces were omitted, the results would be very different:
如果省略内嵌的花括号,结果会完全不同:
     // explicitly initialize row 0
     int ia[3][4] = {0, 3, 6, 9};

initializes the elements of the first row. The remaining elements are initialized to 0.
该声明初始化了第一行的元素,其余元素都被初始化为 0。
Subscripting a Multidimensioned Array
多维数组的下标引用
Indexing a multidimensioned array requires a subscript for each dimension. As an example, the following pair of nested for loops initializes a two-dimensioned array:
为了对多维数组进行索引,每一维都需要一个下标。例如,下面的嵌套 for 循环初始化了一个二维数组:
     const size_t rowSize = 3;
     const size_t colSize = 4;
     int ia [rowSize][colSize];   // 12 uninitialized elements
     // for each row
     for (size_t i = 0; i != rowSize; ++i)
         // for each column within the row
         for (size_t j = 0; j != colSize; ++j)
             // initialize to its positional index
             ia[i][j] = i * colSize + j;

When we want to access a particular element of the array, we must supply both a row and column index. The row index specifies which of the inner arrays we intend to access. The column index selects an element from that inner array. Remembering this fact can help in calculating proper subscript values and in understanding how multidimensioned arrays are initialized.
当需要访问数组中的特定元素时,必须提供其行下标和列下标。行下标指出需要哪个内部数组,列下标则选取该内部数组的指定元素。了解多维数组下标引用策略有助于正确计算其下标值,以及理解多维数组如何初始化。
If an expression provides only a single index, then the result is the inner-array element at that row index. Thus, ia[2] fetches the array that is the last row in ia. It does not fetch any element from that array; it fetches the array itself.
如果表达式只提供了一个下标,则结果获取的元素是该行下标索引的内层数组。如 ia[2] 将获得ia 数组的最后一行,即这一行的内层数组本身,而并非该数组中的任何元素。
4.4.1. Pointers and Multidimensioned Arrays
4.4.1. 指针和多维数组
As with any array, when we use the name of a multidimensioned array, it is automatically converted to a pointer to the first element in the array.
与普通数组一样,使用多维数组名时,实际上将其自动转换为指向该数组第一个元素的指针。
When defining a pointer to a multidimensioned array, it is essential to remember that what we refer to as a multidimensioned array is really an array of arrays.
定义指向多维数组的指针时,千万别忘了该指针所指向的多维数组其实是数组的数组。

Because a multidimensioned array is really an array of arrays, the pointer type to which the array converts is a pointer to the first inner array. Although conceptually straightforward, the syntax for declaring such a pointer can be confusing:
因为多维数组其实就是数组的数组,所以由多维数组转换而成的指针类型应是指向第一个内层数组的指针。尽管这个概念非常明了,但声明这种指针的语法还是不容易理解:
     int ia[3][4];      // array of size 3, each element is an array of ints of size 4
     int (*ip)[4] = ia; // ip points to an array of 4 ints
     ip = &ia[2];       // ia[2] is an array of 4 ints



We define a pointer to an array similarly to how we would define the array itself: We start by declaring the element type followed by a name and a dimension. The trick is that the name is a pointer, so we must prepend * to the name. We can read the definition of ip from the inside out as saying that *ip has type int[4] that is, ip is a pointer to an int array of four elements.
定义指向数组的指针与如何定义数组本身类似:首先声明元素类型,后接(数组)变量名字和维数。窍门在于(数组)变量的名字其实是指针,因此需在标识符前加上 *。如果从内向外阅读 ip 的声明,则可理解为:*ip 是 int[4] 类型——即 ip 是一个指向含有 4 个元素的数组的指针。
The parentheses in this declaration are essential:
在下面的声明中,圆括号是必不可少的:

     int *ip[4]; // array of pointers to int
     int (*ip)[4]; // pointer to an array of 4 ints

Typedefs Simplify Pointers to Multidimensioned Arrays
用 typedef 简化指向多维数组的指针
Typedefs (Section 2.6, p. 61) can help make pointers to elements in multidimensioned arrays easier to write, read, and understand. We might write a typedef for the element type of ia as
typedef 类型定义(第 2.6 节)可使指向多维数组元素的指针更容易读、写和理解。以下程序用 typedef 为 ia 的元素类型定义新的类型名:
     typedef int int_array[4];
     int_array *ip = ia;

We might use this typedef to print the elements of ia:
可使用 typedef 类型输出 ia 的元素:
     for (int_array *p = ia; p != ia + 3; ++p)
         for (int *q = *p; q != *p + 4; ++q)
              cout << *q << endl;

The outer for loop starts by initializing p to point to the first array in ia. That loop continues until we've processed all three rows in ia. The increment, ++p, has the effect of moving p to point to the next row (e.g., the next element) in ia.
外层的 for 循环首先初始化 p 指向 ia 的第一个内部数组,然后一直循环到 ia 的三行数据都处理完为止。++p 使 p 加 1,等效于移动指针使其指向 ia 的下一行(例如:下一个元素)。
The inner for loop actually fetches the int values stored in the inner arrays. It starts by making q point to the first element in the array to which p points. When we dereference p, we get an array of four ints. As usual, when we use an array, it is converted automatically to a pointer to its first element. In this case, that first element is an int, and we point q at that int. The inner for loop runs until we've processed every element in the inner array. To obtain a pointer just off the end of the inner array, we again dereference p to get a pointer to the first element in that array. We then add 4 to that pointer to process the four elements in each inner array.
内层的 for 循环实际上处理的是存储在内部数组中的 int 型元素值。首先让 q 指向 p 所指向的数组的第一个元素。对 p 进行解引用获得一个有 4 个 int 型元素的数组,通常,使用这个数组时,系统会自动将它转换为指向该数组第一个元素的指针。在本例中,第一个元素是int型数据,q指向这个整数。系统执行内层的 for 循环直到处理完当前 p 指向的内部数组中所有的元素为止。当 q 指针刚达到该内部数组的超出末端位置时,再次对 p 进行解引用以获得指向下一个内部数组第一个元素的指针。在 p 指向的地址上加 4 使得系统可循环处理每一个内部数组的 4 个元素。
Exercises Section 4.4.1
Exercise 4.36:Rewrite the program to print the contents of the array ia without using a typedef for the type of the pointer in the outer loop.
重写程序输出 ia 数组的内容,要求在外层循环中不能使用 typedef 定义的类型。


 
      

Chapter Summary
小结
This chapter covered arrays and pointers. These facilities provide functionality similar to that provided by the library vector and string types and their companion iterators. The vector type can be thought of as a more flexible, easier to manage array. Similarly, strings are a great improvement on C-style strings that are implemented as null-terminated character arrays.
本章介绍了数组和指针。数组和指针所提供的功能类似于标准库的 vector 类与 string 类和相关的迭代器所提供。我们可以把 vector 类型理解为更灵活、更容易管理的数组,同样,string 是 C 风格字符串的改进类型,而 C 风格字符串是以空字符结束的字符数组。
Iterators and pointers allow indirect access to objects. Iterators are used to examine elements and navigate between the elements in vectors. Pointers provide similar access to array elements. Although conceptually simple, pointers are notoriously hard to use in practice.
迭代器和指针都能用于间接地访问所指向的对象。vector 类型所包含的元素通过迭代器来操纵,类似地,指针则用于访问数组元素。尽管道理都很简单,但在实际应用中,指针的难用是出了名的。
Pointers and arrays can be necessary for certain low-level tasks, but they should be avoided because they are error-prone and hard to debug. In general, the library abstractions should be used in preference to low-level array and pointer alternatives built into the language. This advice is especially applicable to using strings instead of C-style null-terminated character arrays. Modern C++ programs should not use C-style strings.
某些低级任务必须使用指针和数组,但由于使用指针和数组容易出错而且难以调试,应尽量避免使用。一般而言,应该优先使用标准库抽象类而少用语言内置的低级数组和指针。尤其是应该使用 string 类型取代 C 风格以空字符结束的字符数组。现代 C++ 程序不应使用C风格字符串。

      

Defined Terms
术语
C-style strings(C 风格字符串)
C programs treat pointers to null-terminated character arrays as strings. In C++, string literals are C-style strings. The C library defines a set of functions that operate on such strings, which C++ makes available in the cstring header. C++ programs should use C++ library strings in preference to C-style strings, which are inherently error-prone. A sizeable majority of security holes in networked programs are due to bugs related to using C-style strings and arrays.
C 程序把指向以空字符结束的字符数组的指针视为字符串。在 C++ 中,字符串字面值就是 C 风格字符串。C 标准库定义了一系列处理这种字符串的库函数,C++ 中将这些标准库函数放在 cstring 头文件中。由于 C 风格字符串本质上容易出错,C++ 程序应该优先使用 C++ 标准库类 string 而少用 C 风格字符串。网络程序中大量的安全漏洞都源于与使用 C 风格字符串和数组相关的缺陷。
compiler extension(编译器扩展)
Feature that is added to the language by a particular compiler. Programs that rely on compiler extensions cannot be moved easily to other compilers.
特定编译器为语言添加的特性。依赖于编译器扩展的程序很难移植到其他的编译器。
compound type(复合类型)
Type that is defined in terms of another type. Arrays, pointers, and references are compound types.
使用其他类型定义的类型。数组、指针和引用都是复合类型。
const void*
A pointer type that can point to any const type. See void*.
可以指向任意 const 类型的指针类型,参见 void *。
delete expression(delete 表达式)
A delete expression frees memory that was allocated by new:
delete 表达式用于释放由 new 动态分配的内存:
     delete [] p;

where p must be a pointer to the first element in a dynamically allocated array. The bracket pair is essential: It indicates to the compiler that the pointer points at an array, not at a single object. In C++ programs, delete replaces the use of the C library free function.
在此表达式中,p 必须是指向动态创建的数组中第一个元素的指针,其中方括号必不可少:它告诉编译器该指针指向数组,而非单个对象。C++ 程序使用 delete 取代 C 语言的标准库函数 free。
dimension(维数)
The size of an array.
数组大小。
dynamically allocated(动态分配的)
An object that is allocated on the program's free store. Objects allocated on the free store exist until they are explicitly deleted.
在程序自由存储区中建立的对象。该对象一经创建就一直存在,直到显式释放为止。
free store(自由存储区)
Memory pool available to a program to hold dynamically allocated objects.
程序用来存储动态创建对象的内存区域。
heap(堆)
Synonym for free store.
自由存储区的同义词。
new expression(new表达式)
Allocates dynamic memory. We allocate an array of n elements as follows:
用于分配动态内存的表达式。下面的语句分配了一个有 n 个元素的数组:
     new type[n];



The array holds elements of the indicated type. new returns a pointer to the first element in the array. C++ programs use new in place of the C library malloc function.
该数组存放 type 类型的元素。new 返回指向该数组第一个元素的指针。C++ 程序使用 new 取代 C 语言的标准库函数 malloc。
pointer(指针)
An object that holds the address of an object.
存放对象地址的对象。
pointer arithmetic(指针算术操作)
The arithmetic operations that can be applied to pointers. An integral type can be added to or subtracted from a pointer, resulting in a pointer positioned that many elements ahead or behind the original pointer. Two pointers can be subtracted, yielding the difference between the pointers. Pointer arithmetic is valid only on pointers that denote elements in the same array or an element one past the end of that array.
可用于指针的算术操作。允许在指针上做加上或减去整型值的操作,以获得当前指针之前或之后若干个元素处的地址。两个指针可做减法操作,得到它们之间的差值。只有当指针指向同一个数组或其超出末端的位置时,指针的算术操作才有意义。
precedence(优先级)
Defines the order in which operands are grouped with operators in a compound expression.
在复杂的表达式中,优先级确定了操作数分组的次序。
ptrdiff_t
Machine-dependent signed integral type defined in cstddef header that is large enough to hold the difference between two pointers into the largest possible array.
在 cstddef 头文件中定义的与机器相关的有符号整型,该类型具有足够的大小存储两个指针的差值,这两个指针指向同一个可能的最大数组。
size_t
Machine-dependent unsigned integral type defined in cstddef header that is large enough to hold the size of the largest possible array.
在 cstddef 头文件中定义的与机器相关的无符号整型,它具有足够的大小存储一个可能的最大数组。
* operator(* 操作符)
Dereferencing a pointer yields the object to which the pointer points. The dereference operator returns an lvalue; we may assign to the value returned from the dereference operator, which has the effect of assigning a new value to the underlying element.
对指针进行解引用操作获得该指针所指向的对象。解引用操作符返回左值,因此可为其结果赋值,等效于为该指针所指向的特定对象赋新值。
++ operator(++ 操作符)
When used with a pointer, the increment operator "adds one" by moving the pointer to refer to the next element in an array.
用于指针时,自增操作符给指针“加 1”,移动指针使其指向数组的下一个元素。
[] operator([] 操作符)
The subscript operator takes two operands: a pointer to an element of an array and an index. Its result is the element that is offset from the pointer by the index. Indices count from zerothe first element in an array is element 0, and the last is element size of the array minus 1. The subscript operator returns an lvalue; we may use a subscript as the left-hand operand of an assignment, which has the effect of assigning a new value to the indexed element.
下标操作符接受两个操作数:一个是指向数组元素的指针,一个是下标 n。该操作返回偏离指针当前指向 n 个位置的元素值。数组下标从 0 开始计数——数组第一个元素的下标为 0,最后一个元素的下标是数组长度减 1。下标操作返回左值,可用做赋值操作的左操作数,等效于为该下标引用的元素赋新值。
& operator(& 操作符)
The address-of operator. Takes a single argument that must be an lvalue. Yields the address in memory of that object.
取地址操作符需要一个操作数,其唯一的操作数必须是左值对象,该操作返回操作数对象在内存中的存储地址。
void*
A pointer type that can point to any nonconst type. Only limited operations are permitted on void* pointers. They can be passed or returned from functions and they can be compared with other pointers. They may not be dereferenced.
可以指向任何非 const 对象的指针类型。void* 指针只提供有限的几种操作:可用作函数形参类型或返回类型,也可与其他指针做比较操作,但是不能进行解引用操作。
      

Chapter 5. Expressions
第五章 表达式
CONTENTS
Section 5.1 Arithmetic Operators149
Section 5.2 Relational and Logical Operators152
Section 5.3 The Bitwise Operators154
Section 5.4 Assignment Operators159
Section 5.5 Increment and Decrement Operators162
Section 5.6 The Arrow Operator164
Section 5.7 The Conditional Operator165
Section 5.8 The sizeof Operator167
Section 5.9 Comma Operator168
Section 5.10 Evaluating Compound Expressions168
Section 5.11 The new and delete Expressions174
Section 5.12 Type Conversions178
Chapter Summary188
Defined Terms188


C++ provides a rich set of operators and defines what these operators do when applied to operands of built-in type. It also allows us to define meanings for the operators when applied to class types. This facility, known as operator overloading, is used by the library to define the operators that apply to the library types.
C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义。标准库正是使用这种功能定义用于库类型的操作符。
In this chapter our focus is on the operators as defined in the language and applied to operands of built-in type. We will also look at some of the operators defined by the library. Chapter 14 shows how we can define our own overloaded operators.
本章重点介绍 C++ 语言定义的操作符,它们使用内置类型的操作数;本章还会介绍一些标准库定义的操作符。第十四章将学习如何定义自己的重载操作符。
An expression is composed of one or more operands that are combined by operators. The simplest form of an expression consists of a single literal constant or variable. More complicated expressions are formed from an operator and one or more operands.
表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。
Every expression yields a result. In the case of an expression with no operator, the result is the operand itself, e.g., a literal constant or a variable. When an object is used in a context that requires a value, then the object is evaluated by fetching the object's value. For example, assuming ival is an int object,
每个表达式都会产生一个结果。如果表达式中没有操作符,则其结果就是操作数本身(例如,字面值常量或变量)的值。当一个对象用在需要使用其值的地方,则计算该对象的值。例如,假设ival是一个int型对象:
     if (ival)            // evaluate ival as a condition
         // ....



we could use ival as an expression in the condition of an if. The condition succeeds if the value of ival is not zero and fails otherwise.
上述语句将 ival 作为 if 语句的条件表达式。当 ival 为非零值时, if 条件成立;否则条件不成立。
The result of expressions that involve operators is determined by applying each operator to its operand(s). Except when noted otherwise, the result of an expression is an rvalue (Section 2.3.1, p. 45). We can read the result but cannot assign to it.
对于含有操作符的表达式,它的值通过对操作数做指定操作获得。除了特殊用法外,表达式的结果是右值(第 2.3.1 节),可以读取该结果值,但是不允许对它进行赋值。
The meaning of an operatorwhat operation is performed and the type of the resultdepends on the types of its operands.
操作符的含义——该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。




Until one knows the type of the operand(s), it is not possible to know what a particular expression means. The expression
除非已知道操作数的类型,否则无法确定一个特定表达式的含义。下面的表达式
     i + j



might mean integer addition, concatenation of strings, floating-point addition, or something else entirely. How the expression is evaluated depends on the types of i and j.
既可能是整数的加法操作、字符串的串接或者浮点数的加法操作,也完全可能是其他的操作。如何计算该表达式的值,完全取决于 i 和 j 的数据类型。
There are both unary operators and binary operators. Unary operators, such as address-of (&) and dereference (*), act on one operand. Binary operators, such as addition (+) and subtraction (-), act on two operands. There is also one ternary operator that takes three operands. We'll look at this operator in Section 5.7 (p. 165).
C++提供了一元操作符和二元操作符两种操作符。作用在一个操作数上的操作符称为一元操作符,如取地址操作符(&)和解引用操作符(*);而二元操作符则作用于两个操作数上,如加法操作符(+)和减法操作符(-)。除此之外,C++ 还提供了一个使用三个操作数的三元操作符(ternary operator),我们将在第 5.7 节介绍它。
Some symbols, such as *, are used to represent both a unary and a binary operator. The * symbol is used as the (unary) dereference operator and as the (binary) multiplication operator. The uses of the symbol are independent; it can be helpful to think of them as two different symbols. The context in which an operator symbol is used always determines whether the symbol represents a unary or binary operator.
有些符号(symbols)既可表示一元操作也可表示二元操作。例如,符号 * 既可以作为(一元)解引用操作符,也可以作为(二元)乘法操作符,这两种用法相互独立、各不相关,如果将其视为两个不同的符号可能会更容易理解些。对于这类操作符,需要根据该符号所处的上下文来确定它代表一元操作还是二元操作。
Operators impose requirements on the type(s) of their operand(s). The language defines the type requirements for the operators when applied to built-in or compound types. For example, the dereference operator, when applied to an object of built-in type, requires that its operand be a pointer type. Attempting to dereference an object of any other built-in or compound type is an error.
操作符对其操作数的类型有要求,如果操作符应用于内置或复合类型的操作数,则由C++语言定义其类型要求。例如,用于内置类型对象的解引用操作符要求其操作数必须是指针类型,对任何其他内置类型或复合类型对象进行解引用将导致错误的产生。
The binary operators, when applied to operands of built-in or compound type, usually require that the operands be the same type, or types that can be converted to a common type. We'll look at conversions in Section 5.12 (p. 178). Although the rules can be complex, for the most part conversions happen in expected ways. For example, we can convert an integer to floating-point, and vice versa, but we cannot convert a pointer type to floating-point.
对于操作数为内置或复合类型的二元操作符,通常要求它的两个操作数具有相同的数据类型,或者其类型可以转换为同一种数据类型。关于类型转换,我们将在第 5.12 节学习。尽管规则可能比较复杂,但大部分的类型转换都可按预期的方式进行。例如,整型可转换为浮点类型,反之亦然,但不能将指针类型转换为浮点类型。
Understanding expressions with multiple operators requires understanding operator precedence, associativity, and the order of evaluation of the operands. For example, the expression
要理解由多个操作符组成的表达式,必须先理解操作符的优先级、结合性和操作数的求值顺序。例如,表达式
     5 + 10 * 20/2;

uses addition, multiplication, and division. The result of this expression depends on how the operands are grouped to the operators. For example, the operands to the * operator could be 10 and 20, or 10 and 20/2, or 15 and 20 or 15 and 20/2. Associativity and precedence rules specify the grouping of operators and their operands. In C++ this expression evaluates to 105, which is the result of multiplying 10 and 20, dividing that result by 2, and then adding 5.
使用了加法、乘法和除法操作。该表达式的值取决于操作数与操作符如何结合。例如,乘法操作符 * 的操作数可以是 10 和 20,也可以是 10 和 20 /2,或者 15 和 20 、 15 和 20/2。结合性和优先级规则规定了操作数与操作符的结合方式。在 C++ 语言中,该表达式的值应是 105,10 和 20 先做乘法操作,然后其结果除以 2,再加 5 即为最后结果。
Knowing how operands and operators are grouped is not always sufficient to determine the result. It may also be necessary to know in what order the operands to each operator are evaluated. Each operator controls what assumptions, if any, can be made as to the order in which the operands will be evaluatedthat is, whether we can assume that the left-hand operand is always evaluated before the right or not. Most operators do not guarantee a particular order of evaluation. We will cover these topics in Section 5.10 (p. 168).
求解表达式时,仅了解操作数和操作符如何结合是不足够的,还必须清楚操作符上每一个操作数的求值顺序。每个操作符都控制了其假定的求值顺序,即,我们是否可以假定左操作数总是先于右操作数求值。大部分的操作符无法保证某种特定的求值次序,我们将于第 5.10 节讨论这个问题。
      

5.1. Arithmetic Operators
5.1. 算术操作符
Unless noted otherwise, these operators may be applied to any of the arithmetic types (Section 2.1, p. 34), or any type that can be converted to an arithmetic type.
除非特别说明,表5-1所示操作符可用于任意算术类型(第 2.1 节)或者任何可转换为算术类型的数据类型。
The table groups the operators by their precedencethe unary operators have the highest precedence, then the multiplication and division operators, and then the binary addition and subtraction operators. Operators of higher precedence group more tightly than do operators with lower precedence. These operators are all left associative, meaning that they group left to right when the precedence levels are the same.
表 5.1 按优先级来对操作符进行分组——一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。
Table 5.1. Arithmetic Operators
表 5.1. 算术操作符
Operator
操作符Function
功能Use
用法
+unary plus(一元正号)+ expr
-unary minus(一元负号)- expr
*multiplication(乘法)expr * expr
/division(除法)expr / expr
%remainder(求余)expr % expr
+addition(加法)expr + expr
-subtraction(减法)expr - expr


Applying precedence and associativity to the previous expression:
对于前述表达式
     5 + 10 * 20/2;



we can see that the operands to the multiplication operator (*) are 10 and 20. The result of that expression and 2 are the operands to the division operator (/). The result of that division and 5 are the operands to the addition operator (+).
考虑优先级与结合性,可知该表达式先做乘法( *)操作,其操作数为 10 和 20,然后以该操作的结果和 2 为操作数做除法(/)操作,其结果最后与操作数 5做加法( +)操作。
The unary minus operator has the obvious meaning. It negates its operand:
一元负号操作符具有直观的含义,它对其操作数取负:
     int i = 1024;
     int k = -i; //  negates the value of its operand



Unary plus returns the operand itself. It makes no change to its operand.
一元正号操作符则返回操作数本身,对操作数不作任何修改。
Caution: Overflow and Other Arithmetic Exceptions
警告:溢出和其他算术异常
The result of evaluating some arithmetic expressions is undefined. Some expressions are undefined due to the nature of mathematicsfor example, division by zero. Others are undefined due to the nature of computerssuch as overflow, in which a value is computed that is too large for its type.
某些算术表达式的求解结果未定义,其中一部分由数学特性引起,例如除零操作;其他则归咎于计算机特性,如溢出:计算出的数值超出了其类型的表示范围。
Consider a machine on which shorts are 16 bits. In that case, the maximum short is 32767. Given only 16 bits, the following compound assignment overflows:
考虑某台机器,其 short 类型为 16 位,能表示的最大值是 32767。假设 short 类型只有 16 位,下面的复合赋值操作将会溢出:
     // max value if shorts are 8 bits
     short short_value = 32767;
     short ival = 1;
     // this calculation overflows
     short_value += ival;
     cout << "short_value: " << short_value << endl;

Representing a signed value of 32768 requires 17 bits, but only 16 are available. On many systems, there is no compile-time or run-time warning when an overflow might occur. The actual value put into short_value varies across different machines. On our system the program completes and writes
表示 32768 这个有符号数需 17 位的存储空间,但是这里仅有 16 位,于是导致溢出现象的发生,此时,许多系统都不会给出编译时或运行时的警告。对于不同的机器,上述例子的 short_value 变量真正获得的值不尽相同。在我们的系统上执行该程序后将得到:
     short_value: -32768

The value "wrapped around:" The sign bit, which had been 0, was set to 1, resulting in a negative value. Because the arithmetic types have limited size, it is always possible for some calculations to overflow. Adhering to the recommendations from the "Advice" box on page 38 can help avoid such problems.
其值“截断(wrapped around)”,将符号位的值由 0 设为 1,于是结果变为负数。因为算术类型具有有限的长度,因此计算后溢出的现象常常发生。遵循第 2.2 节建议框中给出的建议将有助于避免此类问题。


The binary + and - operators may also be applied to pointer values. The use of these operators with pointers was described in Section 4.2.4 (p. 123).
二元 +、 - 操作符也可用于指针值,对指针使用这些操作符的用法将在第 4.2.4 节介绍。
The arithmetic operators, +, -, *, and / have their obvious meanings: addition, subtraction, multiplication, and division. Division between integers results in an integer. If the quotient contains a fractional part, it is truncated:
算术操作符 +、-、* 和 / 具有直观的含义:加法、减法、乘法和除法。对两个整数做除法,结果仍为整数,如果它的商包含小数部分,则小数部分会被截除:
     int ival1 = 21/6;  //  integral result obtained by truncating the remainder
     int ival2 = 21/7;  //  no remainder, result is an integral value

Both ival1 and ival2 are initialized with a value of 3.
ival1 和 ival2 均被初始化为 3。
The % operator is known as the "remainder" or the "modulus" operator. It computes the remainder of dividing the left-hand operand by the right-hand operand. This operator can be applied only to operands of the integral types: bool, char, short, int, long, and their associated unsigned types:
操作符 % 称为“求余(remainder)”或“求模(modulus)”操作符,用于计算左操作数除以右操作数的余数。该操作符的操作数只能为整型,包括 bool、char、short 、int 和 long 类型,以及对应的 unsigned 类型:
     int ival = 42;
     double dval = 3.14;
     ival % 12;   //  ok: returns 6
     ival % dval; //  error: floating point operand

For both division (/) and modulus(%), when both operands are positive, the result is positive (or zero). If both operands are negative, the result of division is positive (or zero) and the result of modulus is negative (or zero). If only one operand is negative, then the value of the result is machine-dependent for both operators. The sign is also machine-dependent for modulus; the sign is negative (or zero) for division:
如果两个操作数为正,除法(/)和求模(%)操作的结果也是正数(或零);如果两个操作数都是负数,除法操作的结果为正数(或零),而求模操作的结果则为负数(或零);如果只有一个操作数为负数,这两种操作的结果取决于机器;求模结果的符号也取决于机器,而除法操作的值则是负数(或零):
     21 % 6;   //  ok: result is 3
     21 % 7;   //  ok: result is 0
     -21 % -8; //  ok: result is -5
     21 % -5;  //  machine-dependent: result is 1 or -4
     21 / 6;   //  ok: result is 3
     21 / 7;   //  ok: result is 3
     -21 / -8; //  ok: result is 2
     21 / -5;  //  machine-dependent: result -4 or -5

When only one operand is negative, the sign and value of the result for the modulus operator can follow either the sign of the numerator or of the denominator. On a machine where modulus follows the sign of the numerator then the value of division truncates toward zero. If modulus matches the sign of the denominator, then the result of division truncates toward minus infinity.
当只有一个操作数为负数时,求模操作结果值的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的结果随分子的符号,则除出来的值向零一侧取整;如果求模与分母的符号匹配,则除出来的值向负无穷一侧取整。
Exercises Section 5.1
Exercise 5.1:Parenthesize the following expression to indicate how it is evaluated. Test your answer by compiling the expression and printing its result.
在下列表达式中,加入适当的圆括号以标明其计算顺序。编译该表达式并输出其值,从而检查你的回答是否正确。
     12 / 3 * 4 + 5 * 15 + 24 % 4 / 2


Exercise 5.2:Determine the result of the following expressions and indicate which results, if any, are machine-dependent.
计算下列表达式的值,并指出哪些结果值依赖于机器?
     -30 * 3 + 21 / 5
     -30 + 3 * 21 / 5
     30 / 3 * 21 % 5
     -30 / 3 * 21 % 4



Exercise 5.3:Write an expression to determine whether an int value is even or odd.
编写一个表达式判断一个 int 型数值是偶数还是奇数。
Exercise 5.4:Define the term overflow. Show three expressions that will overflow.
定义术语“溢出”的含义,并给出导致溢出的三个表达式。



      

5.10. Evaluating Compound Expressions
5.10. 复合表达式的求值
An expression with two or more operators is a compound expression. In a compound expression, the way in which the operands are grouped to the operators may determine the result of the overall expression. If the operands group in one way, the result differs from what it would be if they grouped another way.
含有两个或更多操作符的表达式称为复合表达式。在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值。表达式的结果会因为操作符和操作数的分组结合方式的不同而不同。
Precedence and associativity determine how the operands are grouped. That is, precedence and associativity determine which part of the expression is the operand for each of the operators in the expression. Programmers can override these rules by parenthesizing compound expressions to force a particular grouping.
操作数的分组结合方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。如果程序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特殊的分组。
Precedence specifies how the operands are grouped. It says nothing about the order in which the operands are evaluated. In most cases, operands may be evaluated in whatever order is convenient.
优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序。在大多数情况下,操作数一般以最方便的次序求解。

5.10.1. Precedence
5.10.1. 优先级
The value of an expression depends on how the subexpressions are grouped. For example, in the following expression, a purely left-to-right evaluation yields 20:
表达式的值取决于其子表达式如何分组。例如,下面的表达式,如果纯粹从左向右计算,结果为 20:
     6 + 3 * 4 / 2 + 2;

Other imaginable results include 9, 14, and 36. In C++, the result is 14.
想像中其他可能的结果包括 9、14 和 36。在 C++ 中,该表达式的值应为 14。
Multiplication and division have higher precedence than addition. Their operands are bound to the operator in preference to the operands to addition. Multiplication and division have the same precedence as each other. Operators also have associativity, which determines how operators at the same precedence level are grouped. The arithmetic operators are left associative, which means they group left to right. We now can see that our expression is equivalent to
乘法和除法的优先级高于加法操作,于是它们的操作数先于加法操作的操作数计算。但乘法和除法的优先级相同。当操作符的优先级相同时,由其结合性决定求解次序。算术操作具有左结合性,这意味着它们从左向右结合。因此上面表达式等效于:
     int temp = 3 * 4;           // 12
     int temp2 = temp / 2;       // 6
     int temp3 = temp2 + 6;      // 12
     int result = temp3 + 2;     // 14

Parentheses Override Precedence
圆括号凌驾于优先级之上
We can override precedence with parentheses. Parenthesized expressions are evaluated by treating each parenthesized subexpression as a unit and otherwise applying the normal precedence rules. For example, we can use parentheses on our initial expression to force the evaluation to result in any of the four possible values:
我们可使用圆括号推翻优先级的限制。使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先计算,其他部分则以普通的优先级规则处理。例如,下面的程序在前述表达式上添加圆括号,强行更改其操作次序,可能得到四种结果:
     // parentheses on this expression match default precedence/associativity
     cout << ((6 + ((3 * 4) / 2)) + 2) << endl; // prints 14
     // parentheses result in alternative groupings
     cout << (6 + 3) * (4 / 2 + 2) << endl;     // prints 36
     cout << ((6 + 3) * 4) / 2 + 2 << endl;     // prints 20
     cout << 6 + 3 * 4 / (2 + 2) << endl;       // prints 9

We have already seen examples where precedence rules affect the correctness of our programs. For example, consider the expression described in the "Advice" box on page 164:
我们已经通过前面的例子了解了优先级规则如何影响程序的正确性。例如,考虑第 5.5 节第二个建议框中描述的表达式:
     *iter++;

Precedence says that ++ has higher precedence than *. That means that iter++ is grouped first. The operand of *, therefore, is the result of applying the increment operator to iter. If we wanted to increment the value that iter denotes, we'd have to use parentheses to force our intention:
其中,++ 的优先级高于*操作符,这就意味着 iter++ 先结合。而操作符 * 的操作数是 iter 做了自增操作后的结果。如果我们希望对 iter 所指向的值做自增操作,则必须使用圆括号强制实现我们的目的:
     (*iter)++; // increment value to which iter refers and yield unincremented value

The parentheses specify that the operand of * is iter. The expression now uses *iter as the operand to ++.
圆括号指明操作符 * 的操作数是 iter,然后表达式以 *iter 作为 ++操作符的操作数。
As another example, recall the condition in the while on page 161:
另一个例子,回顾一下第 5.4.2 节中的 while 循环条件:
     while ((i = get_value()) != 42) {

The parentheses around the assignment were necessary to implement the desired operation, which was to assign to i the value returned from get_value and then test that value to see whether it was 42. Had we failed to parenthesize the assignment, the effect would be to test the return value to see whether it was 42. The true or false value of that test would then be assigned to i, meaning that i would either be 1 or 0.
赋值操作上的圆括号是必需的,这样才能实现预期的操作:将 get_value 的返回值赋给 i,然后检查刚才赋值的结果是否为42。如果赋值操作上没有加圆括号,结果将是先判断 get_value 的返回值是否为 42,然后将判断结果 true 或 false 值赋给 i,这意味着 i 的值只能是 1 或 0。
5.10.2. Associativity
5.10.2. 结合性
Associativity specifies how to group operators at the same precedence level. We have also seen cases where associativity matters. As one example, the assignment operator is right associative. This fact allows concatenated assignments:
结合性规定了具有相同优先级的操作符如何分组。我们已经遇到过涉及结合性的例子。其中之一使用了赋值操作的右结合性,这个特性允许将多个赋值操作串接起来:
     ival = jval = kval = lval       // right associative
     (ival = (jval = (kval = lval))) // equivalent, parenthesized version

This expression first assigns lval to kval, then the result of that to jval, and finally the result of that to ival.
该表达式首先将 lval 赋给 kval ,然后将 kval 的值赋给 jval ,最后将 jval 的值再赋给 ival。
The arithmetic operators, on the other hand, are left associative. The expression
另一方面,算术操作符为左结合。表达式
     ival * jval / kval * lval       // left associative
     (((ival * jval) / kval) * lval) // equivalent, parenthesized version

multiplies ival and jval, then divides that result by kval, and finally multiplies the result of the division by lval.
先对 ival 和 jval 做乘法操作,然后乘积除以 kval,最后再将其商与 lval 相乘。
Table 5.4 presents the full set of operators ordered by precedence. The table is organized into segments separated by double lines. Operators in each segment have the same precedence, and have higher precedence than operators in sub-sequent segments. For example, the prefix increment and dereference operators share the same precedence and have higher precedence than the arithmetic or relational operators. We have seen most of these operators, although a few will not be defined until later chapters.
表 5.4 按照优先级顺序列出了 C++ 的全部操作符。该表以双横线分割成不同的段,每段内各个操作符的优先级相同,且都高于后面各段中的操作符。例如,前自增操作符和解引用操作符的优先级相同,它们的优先级都比算术操作符或关系操作符高。此表中大部分操作符已经介绍过,而少数未介绍的操作符将在后续章节中学习。
Table 5.4. Operator Precedence
表 5.4. 操作符的优先级
Associativity
and Operator
操作符及其结合性Function
功能Use
用法See
Page
参见页码
L::global scope(全局作用域):: namep. 450
L::class scope(类作用域)class :: namep. 85
L::namespace scope(名字空间作用域)namespace :: namep. 78

L.member selectors(成员选择)object . memberp. 25
L->member selectors(成员选择)pointer -> memberp. 164
L[]subscript(下标)variable [ expr ]p. 113
L()function call(函数调用)name (expr_list)p. 25
L()type construction(类型构造)type (expr_list)p. 460

R++postfix increment(后自增操作)lvalue++p. 162
R--postfix decrement(后自减操作)lvalue--p. 162
Rtypeidtype ID(类型 ID)typeid (type)p. 775
Rtypeidrun-time type ID(运行时类型 ID)typeid (expr)p. 775
Rexplicit cast(显式强制类型转换)type conversion(类型转换)cast_name <type>(expr)p. 183

Rsizeofsize of object(对象的大小)sizeof exprp. 167
Rsizeofsize of type(类型的大小)sizeof(type)p. 167
R++prefix increment(前自增操作)++ lvaluep. 162
R--prefix decrement(前自减操作)-- lvaluep. 162
R~bitwise NOT(位求反)~exprp. 154
R!logical NOT(逻辑非)!exprp. 152
R-unary minus(一元负号)-exprp. 150
R+unary plus(一元正号)+exprp. 150
R*dereference(解引用)*exprp. 119
R&address-of(取地址)&exprp. 115
R()type conversion(类型转换)(type) exprp. 186
Rnewallocate object(创建对象)new typep. 174
Rdeletedeallocate object(释放对象)delete exprp. 176
Rdelete[]deallocate array(释放数组)delete[] exprp. 137

L->*ptr to member select(指向成员操作的指针)ptr ->* ptr_to_memberp. 783
L.*ptr to member select(指向成员操作的指针)obj .*ptr_to_memberp. 783

L*multiply(乘法)expr * exprp. 149
L/divide(除法)expr / exprp. 149
L%modulo (remainder)(求模(求余))expr % exprp. 149

L+add(加法)expr + exprp. 149
L-subtract(减法)expr - exprp. 149

L<<bitwise shift left(位左移)expr << exprp. 154
L>>bitwise shift right(位右移)expr >> exprp. 154

L<less than(小于)expr < exprp. 152
L<=less than or equal(小于或等于)expr <= exprp. 152
L>greater than(大于)expr > exprp. 152
L>=greater than or equal(大于或等于)expr >= exprp. 152

L==equality(相等)expr == exprp. 152
L!=inequality(不等)expr != exprp. 152

L&bitwise AND(位与)expr & exprp. 154

L^bitwise XOR(位异或)expr ^ exprp. 154

L|bitwise OR(位或)expr | exprp. 154

L&&logical AND(逻辑与)expr && exprp. 152

L||logical OR(逻辑或)expr || exprp. 152

R?:conditional(条件操作)expr ? expr : exprp. 165

R=assignment(赋值操作)lvalue = exprp. 159
R*=, /=, %=,compound assign(复合赋值操作)lvalue += expr, etc.p. 159
R+=, -=,  p. 159
R<<=, >>=,  p. 159
R&=,|=, ^=  p. 159

Rthrowthrow exception(抛出异常)throw exprp. 216

L,comma(逗号)expr , exprp. 168

Exercises Section 5.10.2
Exercise 5.25:Using Table 5.4 (p. 170), parenthesize the following expressions to indicate the order in which the operands are grouped:
根据表 5.4 的内容,在下列表达式中添加圆括号说明其操作数分组的顺序(即计算顺序):
     (a)  ! ptr == ptr->next
     (b)  ch = buf[ bp++ ] != '\n'



Exercise 5.26:The expressions in the previous exercise evaluate in an order that is likely to be surprising. Parenthesize these expressions to evaluate in an order you imagine is intended.
习题 5.25 中的表达式的计算次序与你的意图不同,给它们加上圆括号使其以你所希望的操作次序求解。
Exercise 5.27:The following expression fails to compile due to operator precedence. Using Table 5.4 (p. 170), explain why it fails. How would you fix it?
由于操作符优先级的问题,下列表达式编译失败。请参照表 5.4 解释原因,应该如何改正?
     string s = "word";
     // add an 's' to the end, if the word doesn't already end in 's'
     string pl = s + s[s.size() - 1] == 's' ? "" : "s" ;




5.10.3. Order of Evaluation
5.10.3. 求值顺序
In Section 5.2 (p. 152) we saw that the && and || operators specify the order in which their operands are evaluated: In both cases the right-hand operand is evaluated if and only if doing so might affect the truth value of the overall expression. Because we can rely on this property, we can write code such as
在第 5.2 节中,我们讨论了 && 和 || 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。根据这个原则,可编写如下代码:
     // iter only dereferenced if it isn't at end
     while (iter != vec.end() && *iter != some_val)

The only other operators that guarantee the order in which operands are evaluated are the conditional (?:) and comma operators. In all other cases, the order is unspecified.
C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。
For example, in the expression
例如,表达式
     f1() * f2();

we know that both f1 and f2 must be called before the multiplication can be done. After all, their results are what is multiplied. However, we have no way to know whether f1 will be called before f2 or vice versa.
在做乘法操作之前,必须调用 f1 函数和 f2 函数,毕竟其调用结果要相乘。然而,我们却无法知道到底是先调用 f1 还是先调用 f2。
The order of operand evaluation often, perhaps even usually, doesn't matter. It can matter greatly, though, if the operands refer to and change the same objects.
其实,以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果。

The order of operand evaluation matters if one subexpression changes the value of an operand used in another subexpression:
如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:
     // oops! language does not define order of evaluation
     if (ia[index++] < ia[index])

The behavior of this expression is undefined. The problem is that the left- and right-hand operands to the < both use the variable index. However, the left-hand operand involves changing the value of that variable. Assuming index is zero, the compiler might evaluate this expression in one of the following two ways:
此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了 index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:
     if (ia[0] < ia[0]) // execution if rhs is evaluated first
     if (ia[0] < ia[1]) // execution if lhs is evaluated first

We can guess that the programmer intended that the left operand be evaluated, thereby incrementing index. If so, the comparison would be between ia[0] and ia[1]. The language, however, does not guarantee a left-to-right evaluation order. In fact, an expression like this is undefined. An implementation might evaluate the right-hand operand first, in which case ia[0] is compared to itself. Or the implementation might do something else entirely.
可以假设程序员希望先求左操作数的值,因此 index 的值加 1。如果是这样的话,比较 ia[0] 和 ia[1] 的值。然而,C++ 语言不能确保从左到右的计算次序。事实上,这类表达式的行为没有明确定义。一种实现可能是先计算右操作数,于是 ia[0] 与自己做比较,要不然就是做完全不同的操作。
Advice: Managing Compound Expressions
建议:复合表达式的处理
Beginning C and C++ programmers often have difficulties understanding order of evaluation and the rules of precedence and associativity. Misunderstanding how expressions and operands are evaluated is a rich source of bugs. Moreover, the resulting bugs are difficult to find because reading the program does not reveal the error unless the programmer already understands the rules.
初学 C 和 C++ 的程序员一般很难理解求值顺序、优先级和结合性规则。误解表达式和操作数如何求解将导致大量的程序错误。此外,除非程序员已经完全理解了相关规则,否则这类错误很难发现,因为仅靠阅读程序是无法排除这些错误的。
Two rules of thumb can be helpful:
下面两个指导原则有助于处理复合表达式:
When in doubt, parenthesize expressions to force the grouping that the logic of your program requires.
如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
If you change the value of an operand, don't use that operand elsewhere in the same statement. If you need to use the changed value, then break the expression up into separate statements in which the operand is changed in one statement and then used in a subsequent statement.
如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。
An important exception to the second rule is that subexpressions that use the result of the subexpression that changes the operand are safe. For example, in *++iter the increment changes the value of iter, and the (changed) value of iter is then used as the operand to *. In this, and similar, expressions, order of evaluation of the operand isn't an issue. To evaluate the larger expression, the subexpression that changes the operand must first be evaluated. Such usage poses no problems and is quite common.
第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的。例如,*++iter 表达式的自增操作修改了 iter 的值,然后将 iter(修改后)的值用作 * 操作符的操作数。对于这个表达式或其他类似的表达式,其操作数的计算次序无关紧要。而为了计算更复杂的表达式,改变操作数值的子表达式必须首先计算。这种方法很常用,不会产生什么问题。

Do not use an increment or decrement operator on the same object in more than two subexpressions of the same expression.
一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。

One safe and machine-independent way to rewrite the previous comparison of two array elements is
以一种安全而且独立于机器的方式重写上述比较两个数组元素的程序:
     if (ia[index] < ia[index + 1]) {
         // do whatever
     }
     ++index;

Now neither operand can affect the value of the other.
现在,两个操作数的值不会相互影响。
Exercises Section 5.10.3
Exercise 5.28:With the exception of the logical AND and OR, the order of evaluation of the binary operators is left undefined to permit the compiler freedom to provide an optimal implementation. The trade-off is between an efficient implementation and a potential pitfall in the use of the language by the programmer. Do you consider that an acceptable trade-off? Why or why not?
除了逻辑与和逻辑或外,C++ 没有明确定义二元操作符的求解次序,编译器可自由地提供最佳的实现方式。只能在“实现效率”和程序语言使用中“潜在的缺陷”之间寻求平衡。你认为这可以接受吗?说出你的理由。
Exercise 5.29:Given that ptr points to a class with an int member named ival, vec is a vector holding ints, and that ival, jval, and kval are also ints, explain the behavior of each of these expressions. Which, if any, are likely to be incorrect? Why? How might each be corrected?
假设 ptr 指向类类型对象,该类拥有一个名为 ival 的 int 型数据成员, vec 是保存 int 型元素的 vector 对象,而 ival、 jval 和 kval 都是 int 型变量。请解释下列表达式的行为,并指出哪些(如果有的话)可能是不正确的,为什么?如何改正?
     (a) ptr->ival != 0            (b) ival != jval < kval
     (c) ptr != 0 && *ptr++        (d) ival++ && ival
     (e) vec[ival++] <= vec[ival]





      

5.11. The new and delete Expressions
5.11. new 和 delete 表达式
In Section 4.3.1 (p. 134) we saw how to use new and delete expressions to dynamically allocate and free arrays. We can also use new and delete to dynamically allocate and free single objects.
第 4.3.1 节介绍了如何使用 new 和 delete 表达式动态创建和释放数组,这两种表达式也可用于动态创建和释放单个对象。
When we define a variable, we specify a type and a name. When we dynamically allocate an object, we specify a type but do not name the object. Instead, the new expression returns a pointer to the newly allocated object; we use that pointer to access the object:
定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:
     int i;              // named, uninitialized int variable
     int *pi = new int;  // pi points to dynamically allocated,
                         // unnamed, uninitialized int

This new expression allocates one object of type int from the free store and returns the address of that object. We use that address to initialize the pointer pi.
这个 new 表达式在自由存储区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针 pi。
Initializing Dynamically Allocated Objects
动态创建对象的初始化
Dynamically allocated objects may be initialized, in much the same way as we initialize variables:
动态创建的对象可用初始化变量的方式实现初始化:
     int i(1024);              // value of i is 1024
     int *pi = new int(1024);  // object to which pi points is 1024
     string s(10, '9');                   // value of s is "9999999999"
     string *ps = new string(10, '9');    // *ps is "9999999999"

We must use the direct-initialization syntax (Section 2.3.3, p. 48) to initialize dynamically allocated objects. When an initializer is present, the new expression allocates the required memory and initializes that memory using the given initializer(s). In the case of pi, the newly allocated object is initialized to 1024. The object pointed to by ps is initialized to a string of 10 nines.
C++ 使用直接初始化(direct-initialization)语法规则(第 2.3.3 节)初始化动态创建的对象。如果提供了初值,new 表达式分配到所需要的内存后,用给定的初值初始化该内存空间。在本例中,pi 所指向的新创建对象将被初始化为 1024,而 ps 所指向的对象则初始化为十个9的字符串。
Default Initialization of Dynamically Allocated Objects
动态创建对象的默认初始化
If we do not explicitly state an initializer, then a dynamically allocated object is initialized in the same way as is a variable that is defined inside a function. (Section 2.3.4, p. 50) If the object is of class type, it is initialized using the default constructor for the type; if it is of built-in type, it is uninitialized.
如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同(第 2.3.4 节)。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
     string *ps = new string; // initialized to empty string
     int *pi = new int;       // pi points to an uninitialized int

As usual, it is undefined to use the value associated with an uninitialized object in any way other than to assign a good value to it.
通常,除了对其赋值之外,对未初始化的对象所关联的值的任何使用都是没有定义的。
Just as we (almost) always initialize the objects we define as variables, it is (almost) always a good idea to initialize dynamically allocated objects.
正如我们(几乎)总是要初始化定义为变量的对象一样,在动态创建对象时,(几乎)总是对它做初始化也是一个好办法。

We can also value-initialize (Section 3.3.1, p. 92) a dynamically allocated object:
同样也可对动态创建的对象做值初始化(value-initialize)(第 3.3.1 节):
     string *ps = new string();  // initialized to empty string
     int *pi = new int();  // pi points to an int value-initialized to 0
     cls *pc = new cls();  // pc points to a value-initialized object of type cls

We indicate that we want to value-initialize the newly allocated object by following the type name by a pair of empty parentheses. The empty parentheses signal that we want initialization but are not supplying a specific initial value. In the case of class types (such as string) that define their own constructors, requesting value-initialization is of no consequence: The object is initialized by running the default constructor whether we leave it apparently uninitialized or ask for value-initialization. In the case of built-in types or types that do not define any constructors, the difference is significant:
以上表明程序员想通过在类型名后面使用一对内容为空的圆括号对动态创建的对象做值初始化。内容为空的圆括号表示虽然要做初始化,但实际上并未提供特定的初值。对于提供了默认构造函数的类类型(例如 string),没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采用不同初始化方式则有显著的差别:
     int *pi = new int;         // pi points to an uninitialized int
     int *pi = new int();       // pi points to an int value-initialized to 0

In the first case, the int is uninitialized; in the second case, the int is initialized to zero.
第一个语句的 int 型变量没有初始化,而第二个语句的 int 型变量则被初始化为0。
The () syntax for value initialization must follow a type name, not a variable. As we'll see in Section 7.4 (p. 251)
值初始化的 () 语法必须置于类型名后面,而不是变量后。正如我们将要学习的第 7.4 节的例子:

     int x(); // does not value initialize x



declares a function named x with no arguments that returns an int.
这个语句声明了一个名为 x、没有参数而且返回 int 值的函数。


Memory Exhaustion
耗尽内存
Although modern machines tend to have huge memory capacity, it is always possible that the free store will be exhausted. If the program uses all of available memory, then it is possible for a new expression to fail. If the new expression cannot acquire the requested memory, it throws an exception named bad_alloc. We'll look at how exceptions are thrown in Section 6.13 (p. 215).
尽管现代机器的内存容量越来越大,但是自由存储区总有可能被耗尽。如果程序用完了所有可用的内存,new 表达式就有可能失败。如果 new 表达式无法获取需要的内存空间,系统将抛出名为 bad_alloc 的异常。我们将在第 6.13 节介绍如何抛出异常。
Destroying Dynamically Allocated Objects
撤销动态创建的对象
When our use of the object is complete, we must explicitly return the object's memory to the free store. We do so by applying the delete expression to a pointer that addresses the object we want to release.
动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间。
     delete pi;

frees the memory associated with the int object addressed by pi.
该命令释放 pi 指向的 int 型对象所占用的内存空间。
It is illegal to apply delete to a pointer that addresses memory that was not allocated by new.
如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。

The effect of deleting a pointer that addresses memory that was not allocated by new is undefined. The following are examples of safe and unsafe delete expressions:
C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针。下面提供了一些安全的和不安全的 delete expressions 表达式。
     int i;
     int *pi = &i;
     string str = "dwarves";
     double *pd = new double(33);
     delete str; // error: str is not a dynamic object
     delete pi;  // error: pi refers to a local
     delete pd;  // ok

It is worth noting that the compiler might refuse to compile the delete of str. The compiler knows that str is not a pointer and so can detect this error at compile-time. The second error is more insidious: In general, compilers cannot tell what kind of object a pointer addresses. Most compilers will accept this code, even though it is in error.
值得注意的是:编译器可能会拒绝编译 str 的 delete 语句。编译器知道 str 并不是一个指针,因此会在编译时就能检查出这个错误。第二个错误则比较隐蔽:通常来说,编译器不能断定一个指针指向什么类型的对象,因此尽管这个语句是错误的,但在大部分编译器上仍能通过。
delete of a Zero-Valued Pointer
零值指针的删除
It is legal to delete a pointer whose value is zero; doing so has no effect:
如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义:
     int *ip = 0;
     delete ip; // ok: always ok to delete a pointer that is equal to 0

The language guarantees that deleting a pointer that is equal to zero is safe.
C++ 保证:删除 0 值的指针是安全的。
Resetting the Value of a Pointer after a delete
在 delete 之后,重设指针的值
When we write
执行语句
     delete p;

p becomes undefined. Although p is undefined, on many machines, p still contains the address of the object to which it pointed. However, the memory to which p points was freed, so p is no longer valid.
后,p 变成没有定义。在很多机器上,尽管 p 没有定义,但仍然存放了它之前所指向对象的地址,然而 p 所指向的内存已经被释放,因此 p 不再有效。
After deleting a pointer, the pointer becomes what is referred to as a dangling pointer. A dangling pointer is one that refers to memory that once held an object but does so no longer. A dangling pointer can be the source of program errors that are difficult to detect.
删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。
Setting the pointer to 0 after the object it refers to has been deleted makes it clear that the pointer points to no object.
一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。




Dynamic Allocation and Deallocation of const Objects
const 对象的动态分配和回收
It is legal to dynamically create const objects:
C++ 允许动态创建 const 对象:
     // allocate and initialize a const object
     const int *pci = new const int(1024);

Like any const, a dynamically created const must be initialized when it is created and once initialized cannot be changed. The value returned from this new expression is a pointer to const int. Like the address of any other const object, the return from a new that allocates a const object may only be assigned to a pointer to const.
与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改。上述 new 表达式返回指向 int 型 const 对象的指针。与其他 const 对象的地址一样,由于 new 返回的地址上存放的是 const 对象,因此该地址只能赋给指向 const 的指针。
A const dynamic object of a class type that defines a default constructor may be initialized implicitly:
对于类类型的 const 动态对象,如果该类提供了默认的构造函数,则此对象可隐式初始化:
     // allocate default initialized const empty string
     const string *pcs = new const string;

This new expression does not explicitly initialize the object pointed to by pcs. Instead, the object to which pcs points is implicitly initialized to the empty string. Objects of built-in type or of a class type that does not provide a default constructor must be explicitly initialized.
new 表达式没有显式初始化 pcs 所指向的对象,而是隐式地将 pcs 所指向的对象初始化为空的 string 对象。内置类型对象或未提供默认构造函数的类类型对象必须显式初始化。
Caution: Managing Dynamic Memory Is Error-Prone
警告:动态内存的管理容易出错
The following three common program errors are associated with dynamic memory allocation:
下面三种常见的程序错误都与动态内存分配相关:
Failing to delete a pointer to dynamically allocated memory, thus preventing the memory from being returned to the free store. Failure to delete dynamically allocated memory is spoken of as a "memory leak." Testing for memory leaks is difficult because they often do not appear until the application is run for a test period long enough to actually exhaust memory.
删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
Reading or writing to the object after it has been deleted. This error can sometimes be detected by setting the pointer to 0 after deleting the object to which the pointer had pointed.
读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
Applying a delete expression to the same memory location twice. This error can happen when two pointers address the same dynamically allocated object. If delete is applied to one of the pointers, then the object's memory is returned to the free store. If we subsequently delete the second pointer, then the free store may be corrupted.
对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。
These kinds of errors in manipulating dynamically allocated memory are considerably easier to make than they are to track down and fix.
操纵动态分配的内存时,很容易发生上述错误,但这些错误却难以跟踪和修正。


Deleting a const Object
删除 const 对象
Although the value of a const object cannot be modified, the object itself can be destroyed. As with any other dynamic object, a const dynamic object is freed by deleting a pointer that points to it:
尽管程序员不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的:
     delete pci; // ok: deletes a const object

Even though the operand of the delete expression is a pointer to const int, the delete expression is valid and causes the memory to which pci refers to be deallocated.
即使 delete 表达式的操作数是指向 int 型 const 对象的指针,该语句同样有效地回收 pci 所指向的内容。
Exercises Section 5.11
Exercise 5.30:Which of the following, if any, are illegal or in error?
下列语句哪些(如果有的话)是非法的或错误的?
     (a) vector<string> svec(10);
     (b) vector<string> *pvec1 = new vector<string>(10);
     (c) vector<string> **pvec2 = new vector<string>[10];
     (d) vector<string> *pv1 = &svec;
     (e) vector<string> *pv2 = pvec1;

     (f) delete svec;
     (g) delete pvec1;
     (h) delete [] pvec2;
     (i) delete pv1;
     (j) delete pv2;






      

5.12. Type Conversions
5.12. 类型转换
The type of the operand(s) determine whether an expression is legal and, if the expression is legal, determines the meaning of the expression. However, in C++ some types are related to one another. When two types are related, we can use an object or value of one type where an operand of the related type is expected. Two types are related if there is a conversion between them.
表达式是否合法取决于操作数的类型,而且合法的表达式其含义也由其操作数类型决定。但是,在 C++ 中,某些类型之间存在相关的依赖关系。若两种类型相关,则可在需要某种类型的操作数位置上,使用该类型的相关类型对象或值。如果两个类型之间可以相互转换,则称这两个类型相关。
As an example, consider
考虑下列例子:
     int ival = 0;
     ival = 3.541 + 3; // typically compiles with a warning

which assigns 6 to ival.
ival 的值为 6。
The operands to the addition operator are values of two different types: 3.541 is a literal of type double, and 3 is a literal of type int. Rather than attempt to add values of the two different types, C++ defines a set of conversions to transform the operands to a common type before performing the arithmetic. These conversions are carried out automatically by the compiler without programmer interventionand sometimes without programmer knowledge. For that reason, they are referred to as implicit type conversions.
首先做加法操作,其操作数是两个不同类型的值:3.541 是 double 型的字面值常量,而 3 则是 int 型的字面值常量。C++ 并不是把两个不同类型的值直接加在一起,而是提供了一组转换规则,以便在执行算术操作之前,将两个操作数转换为同一种数据类型。这些转换规则由编译器自动执行,无需程序员介入——有时甚至不需要程序员了解。因此,它们也被称为隐式类型转换。
The built-in conversions among the arithmetic types are defined to preserve precision, if possible. Most often, if an expression has both integral and floating-point values, the integer is converted to floating-point. In this addition, the integer value 3 is converted to double. Floating-point addition is performed and the result, 6.541, is of type double.
C++ 定义了算术类型之间的内置转换以尽可能防止精度损失。通常,如果表达式的操作数分别为整型和浮点型,则整型的操作数被转换为浮点型。本例中,整数3被转换为 double 类型,然后执行浮点类型的加法操作,得 double 类型的结果 6.541。
The next step is to assign that double value to ival, which is an int. In the case of assignment, the type of the left-hand operand dominates, because it is not possible to change the type of the object on the left-hand side. When the left- and right-hand types of an assignment differ, the right-hand side is converted to the type of the left-hand side. Here the double is converted to int. Converting a double to an int TRuncates the value; the decimal portion is discarded. 6.541 becomes 6, which is the value assigned to ival. Because the conversion of a double to int may result in a loss of precision, most compilers issue a warning. For example, the compiler we used to check the examples in this book warns us:
下一步是将 double 类型的值赋给 int 型变量 ival。在赋值操作中,因为不可能更改左操作数对象的类型,因此左操作数的类型占主导地位。如果赋值操作的左右操作数类型不相同,则右操作数会被转换为左边的类型。本例中,double 型的加法结果转换为 int 型。double 向 int 的转换自动按截尾形式进行,小数部分被舍弃。于是 6.541 变成 6,然后赋给 ival。因为从 double 到 int 的转换会导致精度损失,因此大多数编译器会给出警告。例如,本书所用的测试例程的编译器给出如下警告:
     warning: assignment to 'int' from 'double'

To understand implicit conversions, we need to know when they occur and what conversions are possible.
为了理解隐式类型转换,我们需要知道它们在什么时候发生,以及可能出现什么类型的转换。
5.12.1. When Implicit Type Conversions Occur
5.12.1. 何时发生隐式类型转换
The compiler applies conversions for both built-in and class type objects as necessary. Implicit type conversions take place in the following situations:
编译器在必要时将类型转换规则应用到内置类型和类类型的对象上。在下列情况下,将发生隐式类型转换:
In expressions with operands of mixed types, the types are converted to a common type:
在混合类型的表达式中,其操作数被转换为相同的类型:
     int ival;
     double dval;
     ival >= dval // ival converted to double

An expression used as a condition is converted to bool:
用作条件的表达式被转换为 bool 类型:
     int ival;
     if (ival)   // ival converted to bool
     while (cin) // cin converted to bool

Conditions occur as the first operand of the conditional (?:) operator and as the operand(s) to the logical NOT (!), logical AND (&&), and logical OR (||) operators. Conditions also appear in the if, while, for, and do while statements. (We cover the do while in Chapter 6)
条件操作符(?:)中的第一个操作数以及逻辑非(!)、逻辑与(&&)和逻辑或(||)的操作数都是条件表达式。出现在 if、while、for 和 do while 语句中的同样也是条件表达式(其中 do while将在第六章中学习)。
An expression used to initialize or assign to a variable is converted to the type of the variable:
用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型:
     int ival = 3.14; // 3.14 converted to int
     int *ip;
     ip = 0; // the int 0 converted to a null pointer of type int *

In addition, as we'll see in Chapter 7, implicit conversions also occur during function calls.
另外,在函数调用中也可能发生隐式类型转换,我们将在第七章学习这方面的内容。
5.12.2. The Arithmetic Conversions
5.12.2. 算术转换
The language defines a set of conversions among the built-in types. Among these, the most common are the arithmetic conversions, which ensure that the two operands of a binary operator, such as an arithmetic or logical operator, are converted to a common type before the operator is evaluated. That common type is also the result type of the expression.
C++ 语言为内置类型提供了一组转换规则,其中最常用的是算术转换。算术转换保证在执行操作之前,将二元操作符(如算术或逻辑操作符)的两个操作数转换为同一类型,并使表达式的值也具有相同的类型。
The rules define a hierarchy of type conversions in which operands are converted to the widest type in the expression. The conversion rules are defined so as to preserve the precision of the values involved in a multi-type expression. For example, if one operand is of type long double, then the other is converted to type long double regardless of what the second type is.
算术转换规则定义了一个类型转换层次,该层次规定了操作数应按什么次序转换为表达式中最宽的类型。在包含多种类型的表达式中,转换规则要确保计算值的精度。例如,如果一个操作数的类型是 long double,则无论另一个操作数是什么类型,都将被转换为 long double。
The simplest kinds of conversion are integral promotions. Each of the integral types that are smaller than int char, signed char, unsigned char, short, and unsigned shortis promoted to int if all possible values of that type fit in an int. Otherwise, the value is promoted to unsigned int. When bool values are promoted to int, a false value promotes to zero and true to one.
最简单的转换为整型提升:对于所有比 int 小的整型,包括 char、signed char、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为 unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1。
Conversions between Signed and Unsigned Types
有符号与无符号类型之间的转换
When an unsigned value is involved in an expression, the conversion rules are defined to preserve the value of the operands. Conversions involving unsigned operands depend on the relative sizes of the integral types on the machine. Hence, such conversions are inherently machine dependent.
若表达式中使用了无符号( unsigned )数值,所定义的转换规则需保护操作数的精度。unsigned 操作数的转换依赖于机器中整型的相对大小,因此,这类转换本质上依赖于机器。
In expressions involving shorts and ints, values of type short are converted to int. Expressions involving unsigned short are converted to int if the int type is large enough to represent all the values of an unsigned short. Otherwise, both operands are converted to unsigned int. For example, if shorts are a half word and ints a word, then any unsigned value will fit inside an int. On such a machine, unsigned shorts are converted to int.
包含 short 和 int 类型的表达式, short 类型的值转换为 int 。如果 int 型足够表示所有 unsigned short 型的值,则将 unsigned short 转换为 int,否则,将两个操作数均转换为 unsigned int 。例如,如果 short 用半字表示而 int 用一个字表示,则所有 unsigned 值都能包容在 int 内,在这种机器上, unsigned short 转换为 int。
The same conversion happens among operands of type long and unsigned int. The unsigned int operand is converted to long if type long on the machine is large enough to represent all the values of the unsigned int. Otherwise, both operands are converted to unsigned long.
long 和 unsigned int 的转换也是一样的。只要机器上的 long 型足够表示 unsigned int 型的所有值,就将 unsigned int 转换为 long 型,否则,将两个操作数均转换为 unsigned long 。
On a 32-bit machine, long and int are typically represented in a word. On such machines, expressions involving unsigned ints and longs are converted to unsigned long.
在 32 位的机器上,long 和 int 型通常用一个字长表示,因此当表达式包含 unsigned int 和 long 两种类型,其操作数都应转换为 unsigned long 型。
Conversions for expressions involving signed and unsigned int can be surprising. In these expressions the signed value is converted to unsigned. For example, if we compare a plain int and an unsigned int, the int is first converted to unsigned. If the int happens to hold a negative value, the result will be converted as described in Section 2.1.1 (p. 36), with all the attendant problems discussed there.
对于包含 signed 和 unsigned int 型的表达式,其转换可能出乎我们的意料。表达式中的 signed 型数值会被转换为 unsigned 型。例如,比较 int 型和 unsigned int 型的简单变量,系统首先将 int 型数值转换为 unsigned int 型,如果 int 型的值恰好为负数,其结果将以第 2.1.1 节介绍的方法转换,并带来该节描述的所有副作用。
Understanding the Arithmetic Conversions
理解算术转换
The best way to understand the arithmetic conversions is to study lots of examples. In most of the following examples, either the operands are converted to the largest type involved in the expression or, in the case of assignment expressions, the right-hand operand is converted to the type of the left-hand operand:
研究大量例题是帮助理解算术转换的最好方法。下面大部分例题中,要么是将操作数转换为表达式中的最大类型,要么是在赋值表达式中将右操作数转换为左操作数的类型。
     bool      flag;         char           cval;
     short     sval;         unsigned short usval;
     int       ival;         unsigned int   uival;
     long      lval;         unsigned long  ulval;
     float     fval;         double         dval;
     3.14159L + 'a'; // promote 'a' to int, then convert to long double
     dval + ival;    // ival converted to double
     dval + fval;    // fval converted to double
     ival = dval;    // dval converted (by truncation) to int
     flag = dval;    // if dval is 0, then flag is false, otherwise true
     cval + fval;    // cval promoted to int, that int converted to float
     sval + cval;    // sval and cval promoted to int
     cval + lval;    // cval converted to long
     ival + ulval;   // ival converted to unsigned long
     usval + ival;   // promotion depends on size of unsigned short and int
     uival + lval;   // conversion depends on size of unsigned int and long

In the first addition, the character constant lowercase 'a' has type char, which as we know from Section 2.1.1 (p. 34) is a numeric value. The numeric value that 'a' represents depends on the machine's character set. On our ASCII machine, 'a' represents the number 97. When we add 'a' to a long double, the char value is promoted to int and then that int value is converted to a long double. That converted value is added to the long double literal. The other interesting cases are the last two expressions involving unsigned values.
第一个加法操作的小写字母 'a' 是一个 char 类型的字符常量,正如我们在第 2.1.1 节介绍的,它是一个数值。字母 'a' 表示的数值取决于机器字符集。在 ASCII 机器中,字母 'a' 的值为 97。将 'a' 与 long double 型数据相加时,char 型的值被提升为 int 型,然后将 int 型转换为 long double 型,转换后的值再与 long double 型字面值相加。另一个有趣的现象是最后两个表达式都包含 unsigned 数值。
5.12.3. Other Implicit Conversions
5.12.3. 其他隐式转换
Pointer Conversions
指针转换
In most cases when we use an array, the array is automatically converted to a pointer to the first element:
在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:
     int ia[10];    // array of 10 ints
     int* ip = ia;  // convert ia to pointer to first element

The exceptions when an array is not converted to a pointer are: as the operand of the address-of (&) operator or of sizeof, or when using the array to initialize a reference to the array. We'll see how to define a reference (or pointer) to an array in Section 7.2.4 (p. 240).
不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。我们将在第 7.2.4 节学习如何定义指向数组的引用(或指针)。
There are two other pointer conversions: A pointer to any data type can be converted to a void*, and a constant integral value of 0 can be converted to any pointer type.
C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为 void* 类型;整型数值常量 0 可转换为任意指针类型。
Conversions to bool
转换为 bool 类型
Arithmetic and pointer values can be converted to bool. If the pointer or arithmetic value is zero, then the bool is false; any other value converts to true:
算术值和指针值都可以转换为 bool 类型。如果指针或算术值为 0,则其 bool 值为 false ,而其他值则为 true:
     if (cp) /* ... */     // true if cp is not zero
     while (*cp) /* ... */ // dereference cp and convert resulting char to bool

Here, the if converts any nonzero value of cp to TRue. The while dereferences cp, which yields a char. The null character has value zero and converts to false. All other char values convert to true.
这里,if 语句将 cp 的非零值转换为 true。 while 语句则对 cp 进行解引用,操作结果产生一个 char 型的值。空字符( null )具有 0 值,被转换为 false,而其他字符值则转换为 true。
Arithmetic Type and bool Conversions
算术类型与 bool 类型的转换
Arithmetic objects can be converted to bool and bool objects can be converted to int. When an arithmetic type is converted to bool, zero converts as false and any other value converts as true. When a bool is converted to an arithmetic type, true becomes one and false becomes zero:
可将算术对象转换为 bool 类型,bool 对象也可转换为 int 型。将算术类型转换为 bool 型时,零转换为 false ,而其他值则转换为 true 。将 bool 对象转换为算术类型时,true 变成 1,而 false 则为 0:
     bool b = true;
     int ival = b;   // ival == 1
     double pi = 3.14;
     bool b2 = pi;   // b2 is true
     pi = false;     // pi == 0

Conversions and Enumeration Types
转换与枚举类型
Objects of an enumeration type (Section 2.7, p. 62) or an enumerator can be automatically converted to an integral type. As a result, they can be used where an integral value is requiredfor example, in an arithmetic expression:
C++ 自动将枚举类型(第 2.7 节)的对象或枚举成员( enumerator )转换为整型,其转换结果可用于任何要求使用整数值的地方。例如,用于算术表达式:
     // point2d is 2, point2w is 3, point3d is 3, point3w is 4
     enum Points { point2d = 2, point2w,
                   point3d = 3, point3w };
     const size_t array_size = 1024;
     // ok: pt2w promoted to int
     int chunk_size = array_size * pt2w;
     int array_3d = array_size * point3d;

The type to which an enum object or enumerator is promoted is machine-defined and depends on the value of the largest enumerator. Regardless of that value, an enum or enumerator is always promoted at least to int. If the largest enumerator does not fit in an int, then the promotion is to the smallest type larger than int (unsigned int, long or unsigned long) that can hold the enumerator value.
将 enum 对象或枚举成员提升为什么类型由机器定义,并且依赖于枚举成员的最大值。无论其最大值是什么, enum 对象或枚举成员至少提升为 int 型。如果 int 型无法表示枚举成员的最大值,则提升到能表示所有枚举成员值的、大于 int 型的最小类型( unsigned int、long 或 unsigned long)。
Conversion to const
转换为 const 对象
A nonconst object can be converted to a const object, which happens when we use a nonconst object to initialize a reference to const object. We can also convert the address of a nonconst object (or convert a nonconst pointer) to a pointer to the related const type:
当使用非 const 对象初始化 const 对象的引用时,系统将非 const 对象转换为 const 对象。此外,还可以将非 const 对象的地址(或非 const 指针)转换为指向相关 const 类型的指针:
     int i;
     const int ci = 0;
     const int &j = i;   // ok: convert non-const to reference to const int
     const int *p = &ci; // ok: convert address of non-const to address of a const

Conversions Defined by the Library Types
由标准库类型定义的转换
Class types can define conversions that the compiler will apply automatically. Of the library types we've used so far, there is one important conversion that we have used. When we read from an istream as a condition
类类型可以定义由编译器自动执行的类型转换。迄今为止,我们使用过的标准库类型中,有一个重要的类型转换。从 istream 中读取数据,并将此表达式作为 while 循环条件:
     string s;
     while (cin >> s)

we are implicitly using a conversion defined by the IO library. In a condition such as this one, the expression cin >> s is evaluated, meaning cin is read. Whether the read succeeds or fails, the result of the expression is cin.
这里隐式使用了 IO 标准库定义的类型转换。在与此类似的条件中,求解表达式 cin >> s,即读 cin。无论读入是否成功,该表达式的结果都是 cin。
The condition in the while expects a value of type bool, but it is given a value of type istream. That istream value is converted to bool. The effect of converting an istream to bool is to test the state of the stream. If the last attempt to read from cin succeeded, then the state of the stream will cause the conversion to bool to be truethe while test will succeed. If the last attempt failedsay because we hit end-of-filethen the conversion to bool will yield false and the while condition will fail.
while 循环条件应为 bool 类型的值,但此时给出的却是 istream 类类型的值,于是 istream 类型的值应转换为 bool 类型。将 istream 类型转换为 bool 类型意味着要检验流的状态。如果最后一次读 cin 的尝试是成功的,则流的状态将导致上述类型转换为 bool 类型后获得 true 值——while 循环条件成立。如果最后一次尝试失败,比如说已经读到文件尾了,此时将 istream 类型转换为 bool 类型后得 false,while 循环条件不成立。
Exercises Section 5.12.3
Exercise 5.31:Given the variable definitions on page 180, explain what conversions take place when evaluating the following expressions:
记住,你可能需要考虑操作符的结合性,以便在表达式含有多个操作符的情况下确定答案。
     (a) if (fval)
     (b) dval = fval + ival;
     (c) dval + ival + cval;



Remember that you may need to consider associativity of the operators in order to determine the answer in the case of expressions involving more than one operator.
记住,你可能需要考虑操作符的结合性,以便 在表达式含有多个操作符的情况下确定答案。

5.12.4. Explicit Conversions
5.12.4. 显式转换
An explicit conversion is spoken of as a cast and is supported by the following set of named cast operators: static_cast, dynamic_cast, const_cast, and reinterpret_cast.
显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。
Although necessary at times, casts are inherently dangerous constructs.
虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。

5.12.5. When Casts Might Be Useful
5.12.5. 何时需要强制类型转换
One reason to perform an explicit cast is to override the usual standard conversions. The following compound assignment
因为要覆盖通常的标准转换,所以需显式使用强制类型转换。下面的复合赋值:
     double dval;
     int ival;
     ival *= dval; // ival = ival * dval

converts ival to double in order to multiply it by dval. That double result is then truncated to int in order to assign it to ival. We can eliminate the unnecessary conversion of ival to double by explicitly casting dval to int:
为了与 dval 做乘法操作,需将 ival 转换为 double 型,然后将乘法操作的 double 型结果截尾为 int 型,再赋值给 ival。为了去掉将 ival 转换为 double 型这个不必要的转换,可通过如下强制将 dval 转换为 int 型:
     ival *= static_cast<int>(dval); // converts dval to int

Another reason for an explicit cast is to select a specific conversion when more than one conversion is possible. We will look at this case more closely in Chapter 14.
显式使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。我们将在第 14 章中详细讨论这种情况。
5.12.6. Named Casts
5.12.6. 命名的强制类型转换
The general form for the named cast notation is the following:
命名的强制类型转换符号的一般形式如下:
     cast-name<type>(expression);



cast-name may be one of static_cast, const_cast, dynamic_cast, or reinterpret_cast. type is the target type of the conversion, and expression is the value to be cast. The type of cast determines the specific kind of conversion that is performed on the expression.
其中 cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。
dynamic_cast
A dynamic_cast supports the run-time identification of objects addressed either by a pointer or reference. We cover dynamic_cast in Section 18.2 (p. 772).
dynamic_cast 支持运行时识别指针或引用所指向的对象。对 dynamic_cast 的讨论将在第 18.2 节中进行。
const_cast
A const_cast, as its name implies, casts away the constness of its expression. For example, we might have a function named string_copy that we are certain reads, but does not write, its single parameter of type char*. If we have access to the code, the best alternative would be to correct it to take a const char*. If that is not possible, we could call string_copy on a const value using a const_cast:
const_cast ,顾名思义,将转换掉表达式的 const 性质。例如,假设有函数 string_copy,只有唯一的参数,为 char* 类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char* 类型的参数。如果不行,可通过 const_cast 用一个 const 值调用 string_copy 函数:
     const char *pc_str;
     char *pc = string_copy(const_cast<char*>(pc_str));

Only a const_cast can be used to cast away constness. Using any of the other three forms of cast in this case would result in a compile-time error. Similarly, it is a compile-time error to use the const_cast notation to perform any type conversion other than adding or removing const.
只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除 const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。
static_cast
Any type conversion that the compiler performs implicitly can be explicitly requested by using a static_cast:
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
     double d = 97.0;
     // cast specified to indicate that the conversion is intentional
     char ch = static_cast<char>(d);

Such casts are useful when assigning a larger arithmetic type to a smaller type. The cast informs both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision. Compilers often generate a warning for assignments of a larger arithmetic type to a smaller type. When we provide the explicit cast, the warning message is turned off.
当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会产生警告。当我们显式地提供强制类型转换时,警告信息就会被关闭。
A static_cast is also useful to perform a conversion that the compiler will not generate automatically. For example, we can use a static_cast to retrieve a pointer value that was stored in a void* pointer (Section 4.2.2, p. 119):
如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值(第 4.2.2 节):
     void* p = &d; // ok: address of any data object can be stored in a void*
     // ok: converts void* back to the original pointer type
     double *dp = static_cast<double*>(p);

When we store a pointer in a void* and then use a static_cast to cast the pointer back to its original type, we are guaranteed that the pointer value is preserved. That is, the result of the cast will be equal to the original address value.
可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。也就是说,强制转换的结果应与原来的地址值相等。
reinterpret_cast
A reinterpret_cast generally performs a low-level reinterpretation of the bit pattern of its operands.
reinterpret_cast 通常为操作数的位模式提供较低层次的重新解释。
A reinterpret_cast is inherently machine-dependent. Safely using reinterpret_cast requires completely understanding the types involved as well as the details of how the compiler implements the cast.
reinterpret_cast 本质上依赖于机器。为了安全地使用 reinterpret_cast,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。

As an example, in the following cast
例如,对于下面的强制转换:
     int *ip;
     char *pc = reinterpret_cast<char*>(ip);

the programmer must never forget that the actual object addressed by pc is an int, not a character array. Any use of pc that assumes it's an ordinary character pointer is likely to fail at run time in interesting ways. For example, using it to initialize a string object such as
程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:
     string str(pc);

is likely to result in bizarre run-time behavior.
它可能会引起运行时的怪异行为。
The use of pc to initialize str is a good example of why explicit casts are dangerous. The problem is that types are changed, yet there are no warnings or errors from the compiler. When we initialized pc with the address of an int, there is no error or warning from the compiler because we explicitly said the conversion was okay. Any subsequent use of pc will assume that the value it holds is a char*. The compiler has no way of knowing that it actually holds a pointer to an int. Thus, the initialization of str with pc is absolutely correctalbeit in this case meaningless or worse! Tracking down the cause of this sort of problem can prove extremely difficult, especially if the cast of ip to pc occurs in a file separate from the one in which pc is used to initialize a string.
用 pc 初始化 str 这个例子很好地说明了显式强制转换是多么的危险。问题源于类型已经改变时编译器没有提供任何警告或错误提示。当我们用 int 型地址初始化 pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是 char* 型对象的地址,编译器确实无法知道 pc 实际上是指向 int 型对象的指针。因此用 pc 初始化 str 是完全正确的——虽然实际上是无意义的或是错误的。查找这类问题的原因相当困难,特别是如果 ip 到 pc 的强制转换和使用 pc 初始化 string 对象这两个应用发生在不同文件中的时候。
Advice: Avoid Casts
建议:避免使用强制类型转换
By using a cast, the programmer turns off or dampens normal type-checking (Section 2.3, p. 44). We strongly recommend that programmers avoid casts and believe that most well-formed C++ programs can be written without relying on casts.
强制类型转换关闭或挂起了正常的类型检查(第 2.3 节)。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++ 程序。
This advice is particularly important regarding use of reinterpret_casts. Such casts are always hazardous. Similarly, use of const_cast almost always indicates a design flaw. Properly designed systems should not need to cast away const. The other casts, static_cast and dynamic_cast, have their uses but should be needed infrequently. Every time you write a cast, you should think hard about whether you can achieve the same result in a different way. If the cast is unavoidable, errors can be mitigated by limiting the scope in which the cast value is used and by documenting all assumptions about the types involved.
这个建议在如何看待 reinterpret_cast 的使用时非常重要。此类强制转换总是非常危险的。相似地,使用 const_cast 也总是预示着设计缺陷。设计合理的系统应不需要使用强制转换抛弃 const 特性。其他的强制转换,如 static_cast 和 dynamic_cast,各有各的用途,但都不应频繁使用。每次使用强制转换前,程序员应该仔细考虑是否还有其他不同的方法可以达到同一目的。如果非强制转换不可,则应限制强制转换值的作用域,并且记录所有假定涉及的类型,这样能减少错误发生的机会。

5.12.7. Old-Style Casts
5.12.7. 旧式强制类型转换
Prior to the introduction of named cast operators, an explicit cast was performed by enclosing a type in parentheses:
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
     char *pc = (char*) ip;

The effect of this cast is the same as using the reinterpret_cast notation. However, the visibility of this cast is considerably less, making it even more difficult to track down the rogue cast.
效果与使用 reinterpret_cast 符号相同,但这种强制转换的可视性比较差,难以跟踪错误的转换。
Standard C++ introduced the named cast operators to make casts more visible and to give the programmer a more finely tuned tool to use when casts are necessary. For example, nonpointer static_casts and const_casts tend to be safer than reinterpret_casts. As a result, the programmer (as well as readers and tools operating on the program) can clearly identify the potential risk level of each explicit cast in code.
标准 C++ 为了加强类型转换的可视性,引入命名的强制转换操作符,为程序员在必须使用强制转换时提供了更好的工具。例如,非指针的 static_cast 和 const_cast 要比 reinterpret_cast 更安全。结果使程序员(以及读者和操纵程序的工具)可清楚地辨别代码中每个显式的强制转换潜在的风险级别。
Although the old-style cast notation is supported by Standard C++, we recommend it be used only when writing code to be compiled either under the C language or pre-Standard C++.
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。

The old-style cast notation takes one of the following two forms:
旧式强制转换符号有下列两种形式:
     type (expr); // Function-style cast notation
     (type) expr; // C-language-style cast notation

Depending on the types involved, an old-style cast has the same behavior as a const_cast, a static_cast, ora reinterpret_cast. When used where a static_cast or a const_cast would be legal, an old-style cast does the same conversion as the respective named cast. If neither is legal, then an old-style cast performs a reinterpret_cast. For example, we might rewrite the casts from the previous section less clearly using old-style notation:
旧式强制转换依赖于所涉及的数据类型,具有与 const_cast、 static_cast 和 reinterpret_cast 一样的行为。在合法使用 static_cast 或 const_cast 的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。如果这两种强制转换均不合法,则旧式强制转换执行 reinterpret_cast 功能。例如,我们可用旧式符号重写上一节的强制转换:
     int ival; double dval;
     ival += int (dval); // static_cast: converts double to int
     const char* pc_str;
     string_copy((char*)pc_str); // const_cast: casts away const
     int *ip;
     char *pc = (char*)ip; // reinterpret_cast: treats int* as char*

The old-style cast notation remains supported for backward compatibility with programs written under pre-Standard C++ and to maintain compatibility with the C language.
支持旧式强制转换符号是为了对“在标准 C++ 之前编写的程序”保持向后兼容性,并保持与 C 语言的兼容性。
Exercises Section 5.12.7
Exercise 5.32:Given the following set of definitions,
给定下列定义:
     char cval;  int ival;   unsigned int ui;
     float fval;             double dval;



identify the implicit type conversions, if any, taking place:
指出可能发生的(如果有的话)隐式类型转换:
     (a) cval = 'a' + 3;        (b) fval = ui - ival * 1.0;
     (c) dval = ui * fval;      (d) cval = ival + fval + dval;



Exercise 5.33:Given the following set of definitions,
给定下列定义:
     int ival;                         double dval;
     const string *ps;    char *pc;    void *pv;



rewrite each of the following using a named cast notation:
用命名的强制类型转换符号重写下列语句:
     (a) pv = (void*)ps;     (b) ival = int(*pc);
     (c) pv = &dval;         (d) pc = (char*) pv;






      

Chapter Summary
小结
C++ provides a rich set of operators and defines their meaning when applied to values of the built-in types. Additionally, the language supports operator overloading, which allows us to define the meaning of the operators for class types. We'll see in Chapter 14 how to define operators for our own types.
C++ 提供了丰富的操作符,并定义了用于内置类型值时操作符的含义。除此之外,C++ 还支持操作符重载,允许由程序员自己来定义用于类类型时操作符的含义。我们将在第十四章中学习如何重载自定义的操作符。
To understand compound expressionsexpressions involving more than one operatorit is necessary to understand precedence, associativity, and order of operand evaluation. Each operator has a precedence level and associativity. Precedence determines how operators are grouped in a compound expression. Associativity determines how operators at the same precedence level are grouped.
要理解复合表达式(即含有多个操作符的表达式)就必须先了解优先级、结合性以及操作数的求值次序。每一个操作符都有自己的优先级别和结合性。优先级规定复合表达式中操作符结合的方式,而结合性则决定同一个优先级的操作符如何结合。
Most operators do not specify the order in which operands are evaluated: The compiler is free to evaluate either the left- or right-hand operand first. Often, the order of operand evaluation has no impact on the result of the expression. However, if both operands refer to the same object and one of the operands changes that object, then the program has a serious bugand a bug that may be hard to find.
大多数操作符没有规定其操作数的求值顺序:由编译器自由选择先计算左操作数还是右操作数。通常,操作数的求值顺序不会影响表达式的结果。但是,如果操作符的两个操作数都与同一个对象相关,而且其中一个操作数改变了该对象的值,则程序将会因此而产生严重的错误——而且这类错误很难发现。
Finally, it is possible to write an expression that is given one type but where a value of another type is required. In such cases, the compiler will automatically apply a conversion (either built-in or defined for a class type) to transform the given type into the type that is required. Conversions can also be requested explicitly by using a cast.
最后,可以使用某种类型编写表达式,而实际需要的是另一类型的值。此时,编译器自动实现类型转换(既可以是内置类型也可以是为类类型而定义的),将特定类型转换为所需的类型。C++ 还提供了强制类型转换显式地将数值转换为所需的数据类型。
      

Defined Terms
术语
arithmetic conversion(算术转换)
A conversion from one arithmetic type to another. In the context of the binary arithmetic operators, arithmetic conversions usually attempt to preserve precision by converting a smaller type to a larger type (e.g., small integral types, such as char and short, are converted to int).
算术类型之间的转换。在使用二元算术操作符的地方,算术转换通常将较小的类型转换为较大的类型,以确保精度(例如,将小的整型 char 型和 short 型转换为 int 型)。
associativity(结合性)
Determines how operators of the same precedence are grouped. Operators can be either right associative (operators are grouped from right to left) or left associative (operators are grouped from left to right).
决定同一优先级的操作符如何结合。C++ 的操作符要么是左结合(操作符从左向右结合)要么是右结合(操作符从右向左结合)。
binary operators(二元操作符)
Operators that take two operands.
有两个操作数的操作符。
cast(强制类型转换)
An explicit conversion.
显式的类型转换。
compound expression(复合表达式)
An expression involving more than one operator.
含有多个操作符的表达式。
const_cast
A cast that converts a const object to the corresponding nonconst type.
将 const 对象转换为相应的非 const 类型的强制转换。
conversion(类型转换)
Process whereby a value of one type is transformed into a value of another type. The language defines conversions among the built-in types. Conversions to and from class types are also possible.
将某种类型的值转换为另一种类型值的处理方式。C++ 语言定义了内置类型之间的类型转换,也允许将某种类型转换为类类型或将类类型转换为某种类型。
dangling pointer(悬垂指针)
A pointer that refers to memory that once had an object but no longer does. Dangling pointers are the source of program errors that are quite difficult to detect.
指向曾经存在的对象的指针,但该对象已经不再存在了。悬垂指针容易导致程序错误,而且这种错误很难检测出来。
delete expression(delete表达式)
A delete expression frees memory that was allocated by new. There are two forms of delete:
delete 表达式用于释放由 new 动态分配的内存。delete 有两种语法形式:
     delete p;      // delete object
     delete [] p;    // delete array



In the first case, p must be a pointer to a dynamically allocated object; in the second, p must point to the first element in a dynamically allocated array. In C++ programs, delete replaces the use of the C library free function.
第一种形式的 p 必须是指向动态创建对象的指针;第二种形式的 p 则应指向动态创建数组的第一个元素。C++ 程序使用 delete 取代 C 语言的标准库函数 free。
dynamic_cast
Used in combination with inheritance and run-time type identification. See Section 18.2 (p. 772).
用于结合继承和运行时类型识别。参见第 18.2 节。
expression(表达式)
The lowest level of computation in a C++ program. Expressions generally apply an operator to one or more operands. Each expression yields a result. Expressions can be used as operands, so we can write compound expressions requiring the evaluation of multiple operators.
C++程序中的最低级的计算。表达式通常将一个操作符用于一个或多个操作数。每个表达式产生一个结果。表达式也可用作操作数,因此可用多个操作符编写复合表达式。
implicit conversion(隐式类型转换)
A conversion that is automatically generated by the compiler. Given an expression that needs a particular type but has an operand of a differing type, the compiler will automatically convert the operand to the desired type if an appropriate conversion exists.
编译器自动实现的类型转换。假设表达式需要某种特定类型的数值,但其操作数却是其他不同的类型,此时如果系统定义了适当的类型转换,编译器会自动根据转换规则将该操作数转换为需要的类型。
integral promotions(整型提升)
Subset of the standard conversions that take a smaller integral type to its most closely related larger type. Integral types (e.g. short, char, etc.) are promoted to int or unsigned int.
整型提升是标准类型转换规则的子集,它将较小的整型转换为最接近的较大数据类型。整型(如 short、char 等)被提升为 int 型或 unsigned int 型。
new expression(new表达式)
A new expression allocates memory at run time from the free store. This chapter looked at the form that allocates a single object:
new 表达式用于运行时从自由存储区中分配内存空间。本章使用 new 创建单个对象,其语法形式为:
     new type;
     new type(inits);

allocates an object of the indicated type and optionally initializes that object using the initializers in inits. Returns a pointer to the object. In C++ programs, new replaces use of the C library malloc function.
new 表达式创建指定 type 类型的对象,并且可选择在创建时使用 inits 初值初始化该对象,然后返回指向该对象的指针。C++ 程序使用 new 取代 C 语言的标准库函数 malloc。
operands(操作数)
Values on which an expression
表达式操纵的值。
operator(操作符)
Symbol that determines what action an expression performs. The language defines a set of operators and what those operators mean when applied to values of built-in type. The language also defines the precedence and associativity of each operator and specifies how many operands each operator takes. Operators may be overloaded and applied to values of class type.
决定表达式执行什么功能的符号。C++ 语言定义了一组操作符以及将它们用于内置类型时的含义,还定义了每个操作符的优先级和结合性以及它们所需要的操作数个数。C++ 语言允许重载操作符,以使它们能用于类类型的对象。
operator overloading(操作符重载)
The ability to redefine an operator to apply to class types. We'll see in Chapter 14 how to define overloaded versions of operators.
对操作符的功能重定义以用于类类型。我们将在第十四章中学习如何重载不同的操作符版本。
order of evaluation(求值顺序)
Order, if any, in which the operands to an operator are evaluated. In most cases in C++ the compiler is free to evaluate operands in any order.
操作符的操作数计算顺序(如果有的话)。大多数情况下,C++ 编译器可自由选择操作数求解的次序。
precedence(优先级)
Defines the order in which different operators in a compound expression are grouped. Operators with higher precedence are grouped more tightly than operators with lower precedence.
定义了复合表达式中不同操作符的结合方式。高优先级的操作符要比低优先级操作符结合得更紧密。
reinterpret_cast
Interprets the contents of the operand as a different type. Inherently machine-dependent and dangerous.
将操作数内容解释为另一种不同的类型。这类强制转换本质上依赖于机器,而且是非常危险的。
result(结果)
The value or object obtained by evaluating an expression.
计算表达式所获得的值或对象。
static_cast
An explicit request for a type conversion that the compiler would do implicitly. Often used to override an implicit conversion that the compiler would otherwise perform.
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成。我们常常使用 static_cast 取代由编译器实现的隐式转换。
unary operators(一元操作符)
Operators that take a single operand.
只有一个操作数的操作符。
~ operator
The bitwise NOT operator. Inverts the bits of its operand.
逐位求反操作符,将其操作数的每个位都取反。
, operator
The comma operator. Expressions separated by a comma are evaluated left to right. Result of a comma expression is the value of the right-most expression.
逗号操作符。用逗号隔开的表达式从左向右计算,整个逗号表达式的结果为其最右边的表达式的值。
^ operator
The bitwise exclusive or operator. Generates a new integral value in which each bit position is 1 if either but not both operands contain a 1 in that bit position; otherwise, the bit is 0.
位异或操作符。在做位异或操作时,如果两个操作数对应位置上的位只有一个(注意不是两个)为 1,则操作结果中该位为 1,否则为 0,位异或操作产生一个新的整数值。
| operator
The bitwise OR operator. Generates a new integral value in which each bit position is 1 if either operand has a 1 in that position; otherwise the bit is 0.
位或操作符。在做位或操作时,如果两个操作数对应位置上的位至少有一个为 1,则操作结果中该位为 1,否则为 0,位或操作产生一个新的整数值。
++ operator
The increment operator. The increment operator has two forms, prefix and postfix. Prefix increment yields an lvalue. It adds one to the operand and returns the changed value of the operand. Postfix increment yields an rvalue. It adds one to the operand and returns the original, unchanged value of the operand.
自增操作符。自增操作符有两种形式:前置操作和后置操作。前自增操作生成左值,在给操作数加1后返回改变后的操作数值。后自增操作生成右值,给操作数加1但返回未改变的操作数原值。
-- operator
The decrement operator. has two forms, prefix and postfix. Prefix decrement yields an lvalue. It subtracts one from the operand and returns the changed value of the operand. Postfix decrement yields an rvalue. It subtracts one from the operand and returns the original, unchanged value of the operand.
自减操作符。自减操作符也有两种形式:前置操作和后置操作。前自减操作生成左值,在给操作数减1后返回改变后的操作数值。后自减操作生成右值,给操作数减1但返回未改变的操作数原值。
<< operator
The left-shift operator. Shifts bits in the left-hand operand to the left. Shifts as many bits as indicated by the right-hand operand. The right-hand operand must be zero or positive and strictly less than the number of bits in the left-hand operand.
左移操作符。左移操作符将其左操作数的各个位向左移动若干个位,移动的位数由其右操作数指定。左移操作符的右操作数必须是0值或正整数,而且它的值必须严格小于左操作数的位数。
>> operator
The right-shift operator. Like the left-shift operator except that bits are shifted to the right. The right-hand operand must be zero or positive and strictly less than the number of bits in the left-hand operand.
右移操作符。与左移操作符类似,右移操作符将其左操作数的各个位向右移动,其右操作数必须是0值或正整数,而且它的值必须严格小于左操作数的位数。
      

5.2. Relational and Logical Operators
5.2. 关系操作符和逻辑操作符
The relational and logical operators take operands of arithmetic or pointer type and return values of type bool.
关系操作符和逻辑操作符(表 5.2)使用算术或指针类型的操作数,并返回 bool 类型的值。
Table 5.2. Relational and Logical Operators
表 5.2. 关系操作符和逻辑操作符
Each of these operators yields bool
下列操作符都产生 bool 值
Operator
操作符Function
功能Use
用法
!logical NOT(逻辑非)!expr
<less than(小于)expr < expr
<=less than or equal(小于等于)expr <= expr
>greater than(大于)expr > expr
>=greater than or equal(大于等于)expr >= expr
==equality(相等)expr == expr
!=inequality(不等)expr != expr
&&logical AND(逻辑与)expr && expr
||logical OR(逻辑或)expr || expr


Logical AND and OR Operators
逻辑与、逻辑或操作符
The logical operators treat their operands as conditions (Section 1.4.1, p. 12). The operand is evaluated; if the result is zero the condition is false, otherwise it is true. The overall result of the AND operator is TRue if and only if both its operands evaluate to TRue. The logical OR (||) operator evaluates to true if either of its operands evaluates to true. Given the forms
逻辑操作符将其操作数视为条件表达式(第 1.4.1 节):首先对操作数求值;若结果为 0,则条件为假(false),否则为真(true)。仅当逻辑与(&&)操作符的两个操作数都为 true,其结果才得 true 。对于逻辑或(||)操作符,只要两个操作数之一为 true,它的值就为 true。给定以下形式:
     expr1 && expr2 // logical AND
     expr1 || expr2 // logical OR



expr2 is evaluated if and only if expr1 does not by itself determine the result. In other words, we're guaranteed that expr2 will be evaluated if and only if
仅当由 expr1 不能确定表达式的值时,才会求解 expr2。也就是说,当且仅当下列情况出现时,必须确保 expr2 是可以计算的:
In a logical AND expression, expr1 evaluates to TRue. If expr1 is false, then the expression will be false regardless of the value of expr2. When expr1 is true, it is possible for the expression to be true if expr2 is also TRue.
在逻辑与表达式中,expr1 的计算结果为 true。如果 expr1 的值为 false,则无论 expr2 的值是什么,逻辑与表达式的值都为 false 。当 expr1 的值为 true 时,只有 expr2 的值也是 true ,逻辑与表达式的值才为 true。
In a logical OR expression, expr1 evaluates to false; if expr1 is false, then the expression depends on whether expr2 is true.
在逻辑或表达式中,expr1 的计算结果为 false。如果 expr1 的值为 false,则逻辑或表达式的值取决于 expr2 的值是否为 true。
The logical AND and OR operators always evaluate their left operand before the right. The right operand is evaluated only if the left operand does not determine the result. This evaluation strategy is often referred to as "short-circuit evaluation."
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值(short-circuit evaluation)”。




A valuable use of the logical AND operator is to have expr1 evaluate to false in the presence of a boundary condition that would make the evaluation of expr2 dangerous. As an example, we might have a string that contains the characters in a sentence and we might want to make the first word in the sentence all uppercase. We could do so as follows:
对于逻辑与操作符,一个很有价值的用法是:如果某边界条件使 expr2 的计算变得危险,则应在该条件出现之前,先让 expr1 的计算结果为 false。例如,编写程序使用一个 string 类型的对象存储一个句子,然后将该句子的第一个单词的各字符全部变成大写,可如下实现:
     string s("Expressions in C++ are composed...");
     string::iterator it = s.begin();
     // convert first word in s to uppercase
     while (it != s.end() && !isspace(*it)) {
         *it = toupper(*it); // toupper covered in section 3.2.4 (p. 88)
         ++it;
     }

In this case, we combine our two tests in the condition in the while. First we test whether it has reached the end of the string. If not, it refers to a character in s. Only if that test succeeds is the right-hand operand evaluated. We're guaranteed that it refers to an actual character before we test to see whether the character is a space or not. The loop ends either when a space is encountered or, if there are no spaces in s, when we reach the end of s.
在这个例子中,while 循环判断了两个条件。首先检查 it 是否已经到达 string 类型对象的结尾,如果不是,则 it 指向 s 中的一个字符。只有当该检验条件成立时,系统才会计算逻辑与操作符的右操作数,即在保证it确实指向一个真正的字符之后,才检查该字符是否为空格。如果遇到空格,或者 s 中没有空格而已经到达 s 的结尾时,循环结束。
Logical NOT Operator
逻辑非操作符
The logical NOT operator (!) treats its operand as a condition. It yields a result that has the opposite truth value from its operand. If the operand evaluates as nonzero, then ! returns false. For example, we might determine that a vector has elements by applying the logical NOT operator to the value returned by empty:
逻辑非操作符(!)将其操作数视为条件表达式,产生与其操作数值相反的条件值。如果其操作数为非零值,则做 ! 操作后的结果为 false。例如,可如下在 vector 类型对象的 empty 成员函数上使用逻辑非操作符,根据函数返回值判断该对象是否为空:
     // assign value of first element in vec to x if there is one
     int x = 0;
     if (!vec.empty())
         x = *vec.begin();

The subexpression
如果调用 empty 函数返回 false,则子表达式
     !vec.empty()

evaluates to true if the call to empty returns false.
的值为 true。
The Relational Operators Do Not Chain Together
不应该串接使用关系操作符
The relational operators (<, <=, >, <=) are left associative. The fact that they are left associative is rarely of any use because the relational operators return bool results. If we do chain these operators together, the result is likely to be surprising:
关系操作符(<、<=、>、<=)具有左结合特性。事实上,由于关系操作符返回bool类型的结果,因此很少使用其左结合特性。如果把多个关系操作符串接起来使用,结果往往出乎预料:
     // oops! this condition does not determine if the 3 values are unequal
     if (i < j < k) { /* ... */ }

As written, this expression will evaluate as true if k is greater than one! The reason is that the left operand of the second less-than operator is the TRue/ false result of the firstthat is, the condition compares k to the integer values of 0 or 1. To accomplish the test we intended, we must rewrite the expression as follows:
这种写法只要 k 大于 1,上述表达式的值就为 true。这是因为第二个小于操作符的左操作数是第一个小于操作符的结果:true 或 false。也就是,该条件将 k 与整数 0 或 1 做比较。为了实现我们想要的条件检验,应重写上述表达式如下:
     if (i < j && j < k) { /* ... */ }

Equality Tests and the bool Literals
相等测试与 bool 字面值
As we'll see in Section 5.12.2 (p. 180) a bool can be converted to any arithmetic typethe bool value false converts to zero and true converts to one.
正如第 5.12.2 节将介绍的,bool 类型可转换为任何算术类型——bool 值 false 用 0 表示,而 true 则为 1。
Because bool converts to one, is almost never right to write an equality test that tests against the bool literal true:
由于 true 转换为 1,因此要检测某值是否与 bool 字面值 true 相等,其等效判断条件通常很难正确编写:

     if (val == true) { /* ... */ }



Either val is itself a bool or it is a type to which a bool can be converted. If val is a bool, then this test is equivalent to writing
val 本身是 bool 类型,或者 val 具有可转换为 bool 类型的数据类型。如果 val 是 bool 类型,则该判断条件等效于:
     if (val) { /* ... */ }

which is shorter and more direct (although admittedly when first learning the language this kind of abbreviation can be perplexing).
这样的代码更短而且更直接(尽管对初学者来说,这样的缩写可能会令人费解)。
More importantly, if val is not a bool, then comparing val with true is equivalent to writing
更重要的是,如果 val 不是 bool 值,val 和 true 的比较等效于:
     if (val == 1) { /* ... */ }

which is very different from
这与下面的条件判断完全不同:
     // condition succeeds if val is any nonzero value
     if (val) { /* ... */ }

in which any nonzero value in val is true. If we write the comparison explicitly, then we are saying that the condition will succeed only for the specific value 1.
此时,只要 val 为任意非零值,条件判断都得 true。如果显式地书写条件比较,则只有当 val 等于指定的 1 值时,条件才成立。
Exercises Section 5.2
Exercise 5.5:Explain when operands are evaluated in the logical AND operator, logical OR operator, and equality operator.
解释逻辑与操作符、逻辑或操作符以及相等操作符的操作数在什么时候计算。
Exercise 5.6:Explain the behavior of the following while condition:
解释下列 while 循环条件的行为:
     char *cp = "Hello World";
     while (cp && *cp)


Exercise 5.7:Write the condition for a while loop that would read ints from the standard input and stop when the value read is equal to 42.
编写 while 循环条件从标准输入设备读入整型(int)数据,当读入值为 42 时循环结束。
Exercise 5.8:Write an expression that tests four values, a, b, c, and d, and ensures that a is greater than b, which is greater than c, which is greater than d.
编写表达式判断四个值 a、b、c 和 d 是否满足 a 大于 b、b 大于 c 而且 c 大于 d 的条件。

 
      

5.3. The Bitwise Operators
5.3. 位操作符
The bitwise operators take operands of integral type. These operators treat their integral operands as a collection of bits, providing operations to test and set individual bits. In addition, these operators may be applied to bitset (Section 3.5, p. 101) operands with the behavior as described here for integral operands.
位操作符(表5-3)使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于 bitset 类型(第 3.5 节)的操作数,该类型具有这里所描述的整型操作数的行为。
Table 5.3. Bitwise Operators
表 5.3. 位操作符
Operator
操作符Function
功能Use
用法
~bitwise NOT(位求反)~expr
<<left shift(左移)expr1 << expr2
>>right shift(右移)expr1 >> expr2
&bitwise AND(位与)expr1 & expr2
^bitwise XOR(位异或)expr1 ^ expr2
|bitwise OR(位或)expr1 | expr2


The type of an integer manipulated by the bitwise operators can be either signed or unsigned. If the value is negative, then the way that the "sign bit" is handled in a number of the bitwise operations is machine-dependent. It is, therefore, likely to differ across implementations; programs that work under one implementation may fail under another.
位操作符操纵的整数的类型可以是有符号的也可以是无符号的。如果操作数为负数,则位操作符如何处理其操作数的符号位依赖于机器。于是它们的应用可能不同:在一个应用环境中实现的程序可能无法用于另一应用环境。
Because there are no guarantees for how the sign bit is handled, we strongly recommend using an unsigned type when using an integral value with the bitwise operators.
对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。

In the following examples we assume that an unsigned char has 8 bits. The bitwise NOT operator (~) is similar in behavior to the bitset flip (Section 3.5.2, p. 105) operation: It generates a new value with the bits of its operand inverted. Each 1 bit is set to 0; each 0 bit is set to 1:
在下面的例子中,假设 unsigned char 类型有 8 位。位求反操作符(~)的功能类似于 bitset 的 flip 操作(第 3.5.2 节):将操作数的每一个二进制位取反:将 1 设置为 0、0 设置为 1,生成一个新值:
unsigned char bits = 0227;
bits = ~bits;


The <<, >> operators are the bitwise shift operators. These operators use their right-hand operand to indicate by how many bits to shift. They yield a value that is a copy of the left-hand operand with the bits shifted as directed by the right-hand operand. The bits are shifted left (<<) or right (>>), discarding the bits that are shifted off the end.
<< 和 >> 操作符提供移位操作,其右操作数标志要移动的位数。这两种操作符将其左操作数的各个位向左(<<)或向右(>>)移动若干个位(移动的位数由其右操作数指定),从而产生新的值,并丢弃移出去的位。
unsigned char bits = 1;
bits << 1; // left shift
bits << 2; // left shift
bits >> 3; // right shift


The left shift operator (<<) inserts 0-valued bits in from the right. The right shift operator (>>) inserts 0-valued bits in from the left if the operand is unsigned. If the operand is signed, it can either insert copies of the sign bit or insert 0-valued bits; which one it uses is implementation defined. The right-hand operand must not be negative and must be a value that is strictly less than the number of bits in the left-hand operand. Otherwise, the effect of the operation is undefined.
左移操作符(<<)在右边插入 0 以补充空位。对于右移操作符(>>),如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值,如何选择需依据具体的实现而定。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。
The bitwise AND operator (&) takes two integral operands. For each bit position, the result is 1 if both operands contain 1; otherwise, the result is 0.
位与操作(&)需要两个整型操作数,在每个位的位置,如果两个操作数对应的位都为 1,则操作结果中该位为 1,否则为 0。
It is a common error to confuse the bitwise AND operator (&) with the logical AND operator (&&) (Section 5.2, p. 152). Similarly, it is common to confuse the bitwise OR operator (|) and the logical OR operator(||).
常犯的错误是把位与操作(&)和逻辑与操作(&&)(第 5.2节)混淆了。同样地,位或操作(|)和逻辑或操作(||)也很容易搞混。




Here we illustrate the result of bitwise AND of two unsigned char values, each of which is initialized by an octal literal:
下面我们用图解的方法说明两个 unsigned char类型值的位与操作,这两个操作数均用八进制字面常量初始化:
unsigned char b1 = 0145;
unsigned char b2 = 0257;
unsigned char result = b1 & b2;


The bitwise XOR (exclusive or) operator (^) also takes two integral operands. For each bit position, the result is 1 if either but not both operands contain 1; otherwise, the result is 0.
位异或(互斥或,exclusive or)操作符(^)也需要两个整型操作数。在每个位的位置,如果两个操作数对应的位只有一个(不是两个)为 1,则操作结果中该位为 1,否则为 0。
result = b1 ^ b2;


The bitwise OR (inclusive or) operator (|) takes two integral operands. For each bit position, the result is 1 if either or both operands contain 1; otherwise, the result is 0.
位或(包含或,inclusive or)操作符(|)需要两个整型操作数。在每个位的位置,如果两个操作数对应的位有一个或者两个都为 1,则操作结果中该位为 1,否则为 0。
result = b1 | b2;

5.3.1. Using bitset Objects or Integral Values
5.3.1. bitset 对象或整型值的使用
We said that the bitset class was easier to use than the lower-level bitwise operations on integral values. Let's look at a simple example and show how we might solve a problem using either a bitset or the bitwise operators. Assume that a teacher has 30 students in a class. Each week the class is given a pass/fail quiz. We'll track the results of each quiz using one bit per student to represent the pass or fail grade on a given test. We might represent each quiz in either a bitset or as an integral value:
bitset 类比整型值上的低级位操作更容易使用。观察下面简单的例子,了解如何使用 bitset 类型或者位操作来解决问题。假设某老师带了一个班,班中有 30 个学生,每个星期在班上做一次测验,只有及格和不及格两种测验成绩,对每个学生用一个二进制位来记录一次测试及格或不及格,以方便我们跟踪每次测验的结果,这样就可以用一个bitset对象或整数值来代表一次测验:
     bitset<30> bitset_quiz1;     //  bitset solution
     unsigned long int_quiz1 = 0; // simulated collection of bits

In the bitset case we can define bitset_quiz1 to be exactly the size we need. By default each of the bits is set to zero. In the case where we use a built-in type to hold our quiz results, we define int_quiz1 as an unsigned long, meaning that it will have at least 32 bits on any machine. Finally, we explicitly initialize int_quiz1 to ensure that the bits start out with well-defined values.
使用 bitset 类型时,可根据所需要的大小明确地定义 bitset_quiz1,它的每一个位都默认设置为 0 值。如果使用内置类型来存放测验成绩,则应将变量 int_quiz1 定义为 unsigned long 类型,这种数据类型在所有机器上都至少拥有32位的长度。最后,显式地初始化 int_quiz1 以保证该变量在使用前具有明确定义的值。
The teacher must be able to set and test individual bits. For example, assuming that the student represented by position 27 passed, we'd like to be able to set that bit appropriately:
老师可以设置和检查每个位。例如,假设第27位所表示的学生及格了,则可以使用下面的语句适当地设置对应的位:
     bitset_quiz1.set(27);   //  indicate student number 27 passed
     int_quiz1 |= 1UL<<27;   //  indicate student number 27 passed

In the bitset case we do so directly by passing the bit we want turned on to set. The unsigned long case will take a bit more explanation. The way we'll set a specific bit is to OR our quiz data with another integer that has only one bitthe one we wantturned on. That is, we need an unsigned long where bit 27 is a one and all the other bits are zero. We can obtain such a value by using the left shift operator and the integer constant 1:
如果使用 bitset 实现,可直接传递要置位的位给 set 函数。而用 unsigned long 实现时,实现的方法则比较复杂。设置指定位的方法是:将测验数据与一个整数做位或操作,该整数只有一个指定的位为 1。也就是说,我们需要一个只有第 27 位为 1 其他位都为0的无符号长整数(unsigned long),这样的整数可用左移操作符和整型常量 1 生成:
     1UL << 27;  //  generate a value with only bit number 27 set

Now when we bitwise OR this value with int_quiz1, all the bits except bit 27 will remain unchanged. That bit will be turned on. We use a compound assignment (Section 1.4.1, p. 13) to OR this value into int_quiz1. This operator, |=, executes in the same way that += does. It is equivalent to the more verbose:
然后让这个整数与 int_quiz1 做位或操作,操作后,除了第 27 位外其他所有位的值都保持不变,而第 27 位则被设置为 1。这里,使用复合赋值操作(第 1.4.1 节)将位或操作的结果赋给 int_quiz1,该操作符 |= 操作的方法与 += 相同。于是,上述功能等效于下面更详细的形式:
     //  following assignment is equivalent to int_quiz1 |= 1UL << 27;
     int_quiz1 = int_quiz1 | 1UL << 27;

Imagine that the teacher reexamined the quiz and discovered that student 27 actually had failed the test. The teacher must now turn off bit 27:
如果老师重新复核测验成绩,发现第 27 个学生实际上在该次测验中不及格,这时老师应把第 27 位设置为 0:
     bitset_quiz1.reset(27);   // student number 27 failed
     int_quiz1 &= ~(1UL<<27);  // student number 27 failed

Again, the bitset version is direct. We reset the indicated bit. For the simulated case, we need to do the inverse of what we did to set the bit: This time we'll need an integer that has bit 27 turned off and all the other bits turned on. We'll bitwise AND this value with our quiz data to turn off just that bit. We can obtain a value with all but bit 27 turned on by inverting our previous value. Applying the bitwise NOT to the previous integer will turn on every bit except the 27th. When we bitwise AND this value with int_quiz1, all except bit 27 will remain unchanged.
使用 bitset 的版本可直接实现该功能,只要复位(reset)指定的位即可。而对于另一种情况,则需通过反转左移操作后的结果来实现设置:此时,我们需要一个只有第 27 位为 0 而其他位都为 1 的整数。然后将这个整数与测验数据做位与操作,把指定的位设置为 0。位求反操作使得除了第 27 位外其他位都设置为 1,然后此值和 int_quiz1 做位与操作,保证了除第 27 位外所有的位都保持不变。
Finally, we might want to know how the student at position 27 fared. To do so, we could write
最后,可通过以下代码获知第 27 个学生是否及格:
     bool status;
     status = bitset_quiz1[27];       // how did student number 27 do?
     status = int_quiz1 & (1UL<<27);  // how did student number 27 do?

In the bitset case we can fetch the value directly to determine how that student did. In the unsigned long case, the first step is to set the 27th bit of an integer to 1. The bitwise AND of this value with int_quiz1 evaluates to nonzero if bit 27 of int_quiz1 is also on; otherwise, it evaluates to zero.
使用 bitset 的版本中,可直接读取其值判断他是否及格。使用 unsigned long 时,首先要把一个整数的第 27 位设置为 1,然后用该整数和 int_quiz1 做位与操作,如果 int_quiz1 的第 27 位为 1,则结果为非零值,否则,结果为零。
In general, the library bitset operations are more direct, easier to read, easier to write, and more likely to be used correctly. Moreover, the size of a bitset is not limited by the number of bits in an unsigned. Ordinarily bitset should be used in preference to lower-level direct bit manipulation of integral values.
一般而言,标准库提供的 bitset 操作更直接、更容易阅读和书写、正确使用的可能性更高。而且,bitset 对象的大小不受 unsigned 数的位数限制。通常来说,bitset 优于整型数据的低级直接位操作。



Exercises Section 5.3.1
Exercise 5.9:Assume the following two definitions:
假设有下面两个定义
     unsigned long ul1 = 3, ul2 = 7;

What is the result of each of the following expressions?
下列表达式的结果是什么?
     (a) ul1 & ul2     (c)  ul1 | ul2
     (b) ul1 && ul2    (d)  ul1 || ul2


Exercise 5.10:Rewrite the bitset expressions that set and reset the quiz results using a subscript operator.
重写 bitset 表达式:使用下标操作符对测验结果进行置位(置 1)和复位(置 0)。


5.3.2. Using the Shift Operators for IO
5.3.2. 将移位操作符用于IO
The IO library redefines the bitwise >> and << operators to do input and output. Even though many programmers never need to use the bitwise operators directly, most programs do make extensive use of the overloaded versions of these operators for IO. When we use an overloaded operator, it has the same precedence and associativity as is defined for the built-in version of the operator. Therefore, programmers need to understand the precedence and associativity of these operators even if they never use them with their built-in meaning as the shift operators.
输入输出标准库(IO library)分别重载了位操作符 >> 和 << 用于输入和输出。即使很多程序员从未直接使用过位操作符,但是相当多的程序都大量用到这些操作符在IO标准库中的重载版本。重载的操作符与该操作符的内置类型版本有相同的优先级和结合性。因此,即使程序员从不使用这些操作符的内置含义来实现移位操作,但是还是应该先了解这些操作符的优先级和结合性。
The IO Operators Are Left Associative
IO 操作符为左结合
Like the other binary operators, the shift operators are left associative. These operators group from left to right, which accounts for the fact that we can concatenate input and output operations into a single statement:
像其他二元操作符一样,移位操作符也是左结合的。这类操作符从左向右地结合,正好说明了程序员为什么可以把多个输入或输出操作连接为单个语句:
     cout << "hi" << " there" << endl;

executes as:
执行为:
     ( (cout << "hi") << " there" ) << endl;

In this statement, the operand "hi" is grouped with the first << symbol. Its result is grouped with the second, and then that result is grouped to the third.
在这个语句中,操作数"hi"与第一个 << 符号结合,其计算结果与第二个 << 符号结合,第二个 << 符号操作后,其结果再与第三个 << 符号结合。
The shift operators have midlevel precedence: lower precedence than the arithmetic operators but higher than the relational, assignment, or conditional operators. These relative precedence levels affect how we write IO expressions involving operands that use operators with lower precedence. We often need to use parentheses to force the right grouping:
移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。若 IO 表达式的操作数包含了比IO操作符优先级低的操作符,相关的优先级别将影响书写该表达式的方式。通常需使用圆括号强制先实现右结合:
     cout << 42 + 10;   // ok, + has higher precedence, so the sum is printed
     cout << (10 < 42); // ok: parentheses force intended grouping; prints 1
     cout << 10 < 42;   // error: attempt to compare cout to 42!

The second cout is interpreted as
第二个cout语句解释为:
     (cout << 10) < 42;

this expression says to "write 10 onto cout and then compare the result of that operation (e.g., cout) to 42."
该表达式说“将 10 写到 cout,然后用此操作(也就是 cout)的结果与 42 做比较”。
      

5.4. Assignment Operators
5.4. 赋值操作符
The left-hand operand of an assignment operator must be a nonconst lvalue. Each of these assignments is illegal:
赋值操作符的左操作数必须是非 const 的左值。下面的赋值语句是不合法的:
     int i, j, ival;
     const int ci = i;  // ok: initialization not assignment
     1024 = ival;       // error: literals are rvalues
     i + j = ival;      // error: arithmetic expressions are rvalues
     ci = ival;         // error: can't write to ci

Array names are nonmodifiable lvalues: An array cannot be the target of an assignment. Both the subscript and dereference operators return lvalues. The result of dereference or subscript, when applied to a nonconst array, can be the left-hand operand of an assignment:
数组名是不可修改的左值:因此数组不可用作赋值操作的目标。而下标和解引用操作符都返回左值,因此当将这两种操作用于非 const 数组时,其结果可作为赋值操作的左操作数:
     int ia[10];
     ia[0] = 0;    // ok: subscript is an lvalue
     *ia = 0;      // ok: dereference also is an lvalue

The result of an assignment is the left-hand operand; the type of the result is the type of the left-hand operand.
赋值表达式的值是其左操作数的值,其结果的类型为左操作数的类型。
The value assigned to the left-hand operand ordinarily is the value that is in the right-hand operand. However, assignments where the types of the left and right operands differ may require conversions that might change the value being assigned. In such cases, the value stored in the left-hand operand might differ from the value of the right-hand operand:
通常,赋值操作将其右操作数的值赋给左操作数。然而,当左、右操作数的类型不同时,该操作实现的类型转换可能会修改被赋的值。此时,存放在左、右操作数里的值并不相同:
     ival = 0;        // result: type int value 0
     ival = 3.14159;  // result: type int value 3

Both these assignments yield values of type int. In the first case the value stored in ival is the same value as in its right-hand operand. In the second case the value stored in ival is different from the right-hand operand.
上述两个赋值语句都产生int类型的值,第一个语句中 ival 的值与右操作数的值相同;但是在第二个语句中,ival 的值则与右操作数的值不相同。
5.4.1. Assignment Is Right Associative
5.4.1. 赋值操作的右结合性
Like the subscript and dereference operators, assignment returns an lvalue. As such, we can perform multiple assignments in a single expression, provided that each of the operands being assigned is of the same general type:
与下标和解引用操作符一样,赋值操作也返回左值。同理,只要被赋值的每个操作数都具有相同的通用类型,C++语言允许将这多个赋值操作写在一个表达式中:
     int ival, jval;
     ival = jval = 0; // ok: each assigned 0

Unlike the other binary operators, the assignment operators are right associative. We group an expression with multiple assignment operators from right to left. In this expression, the result of the rightmost assignment (i.e., jval) is assigned to ival. The types of the objects in a multiple assignment either must be the same type or of types that can be converted (Section 5.12, p. 178) to one another:
与其他二元操作符不同,赋值操作具有右结合特性。当表达式含有多个赋值操作符时,从右向左结合。上述表达式,将右边赋值操作的结果(也就是 jval)赋给 ival。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换(第 5.12 节)为同一类型的数据类型:
     int ival; int *pval;
     ival = pval = 0; // error: cannot assign the value of a pointer to an int
     string s1, s2;
     s1 = s2 = "OK";  // ok: "OK" converted to string

The first assignment is illegal because ival and pval are objects of different types. It is illegal even though zero happens to be a value that could be assigned to either object. The problem is that the result of the assignment to pval is a value of type int*, which cannot be assigned to an object of type int. On the other hand, the second assignment is fine. The string literal is converted to string, and that string is assigned to s2. The result of that assignment is s2, which is then assigned to s1.
第一个赋值语句是不合法的,因为 ival 和 pval 是不同类型的对象。虽然0值恰好都可以赋给这两个对象,但该语句仍然错误。因为问题在于给 pval 赋值的结果是一个 int* 类型的值,不能将此值赋给 int 类型的对象。另一方面,第二个赋值语句则是正确的。字符串字面值可以转换为 string 类型,string 类型的值可赋给 s2 变量。右边赋值操作的结果为 s2,再将此结果值赋给 s1。
5.4.2. Assignment Has Low Precedence
5.4.2. 赋值操作具有低优先级
Inside a condition is another common place where assignment is used as a part of a larger expression. Writing an assignment in a condition can shorten programs and clarify the programmer's intent. For example, the following loop uses a function named get_value, which we assume returns int values. We can test those values until we obtain some desired valuesay, 42:
另一种通常的用法,是将赋值操作写在条件表达式中,把赋值操作用作长表达式的一部分。这种做法可缩短程序代码并阐明程序员的意图。例如,下面的循环调用函数 get_value,假设该函数返回 int 数值,通过循环检查这些返回值,直到获得需要的值为止——这里是 42:
     int i = get_value();  // get_value returns an int
     while (i != 42) {
         // do something ...
         i = get_value(); }

The program begins by getting the first value and storing it in i. Then it establishes the loop, which tests whether i is 42, and if not, does some processing. The last statement in the loop gets a value from get_value(), and the loop repeats. We can write this loop more succinctly as
首先,程序将所获得的第一个值存储在 i 中,然后建立循环检查i的值是否为 42,如果不是,则做某些处理。循环中的最后一条语句调用 get_value() 返回一个值,然后继续循环。该循环可更简洁地写为:
     int i;
     while ((i = get_value()) != 42) {
         // do something ...
     }

The condition now more clearly expresses our intent: We want to continue until get_value returns 42. The condition executes by assigning the result returned by get_value to i and then comparing the result of that assignment with 42.
现在,循环条件更清晰地表达了程序员的意图:持续循环直到 get_value 返回 42 为止。在循环条件中,将 get_value 返回的值赋给 i,然后判断赋值的结果是否为 42。
The additional parentheses around the assignment are necessary because assignment has lower precedence than inequality.
在赋值操作上加圆括号是必需的,因为赋值操作符的优先级低于不等操作符。




Without the parentheses, the operands to != would be the value returned from calling get_value and 42. The true or false result of that test would be assigned to iclearly not what we intended!
如果没有圆括号,操作符 != 的操作数则是调用 get_value 返回的值和 42,然后将该操作的结果 true 或 false 赋给 i—— 显然这并不是我们想要的。
Beware of Confusing Equality and Assignment Operators
谨防混淆相等操作符和赋值操作符
The fact that we can use assignment in a condition can have surprising effects:
可在条件表达式中使用赋值操作,这个事实往往会带来意外的效果:
     if (i = 42)

This code is legal: What happens is that 42 is assigned to i and then the result of the assignment is tested. In this case, 42 is nonzero, which is interpreted as a true value. The author of this code almost surely intended to test whether i was 42:
此代码是合法的:将 42 赋给 i,然后检验赋值的结果。此时,42 为非零值,因此解释为 true。其实,程序员的目的显然是想判断i的值是否为 42:
     if (i == 42)

Bugs of this sort are notoriously difficult to find. Some, but not all, compilers are kind enough to warn about code such as this example.
这种类型的程序错误很难发现。有些(并非全部)编译器会为类似于上述例子的代码提出警告。
Exercises Section 5.4.2
Exercise 5.11:What are the values of i and d after the each assignment:
请问每次赋值操作完成后,i 和 d 的值分别是多少?
     int i;   double d;
     d = i = 3.5;
     i = d = 3.5;


Exercise 5.12:Explain what happens in each of the if tests:
解释每个 if 条件判断产生什么结果?
     if (42 = i)   // . . .
     if (i = 42)   // . . .




5.4.3. Compound Assignment Operators
5.4.3. 复合赋值操作符
We often apply an operator to an object and then reassign the result to that same object. As an example, consider the sum program from page 14:
我们常常在对某个对象做某种操作后,再将操作结果重新赋给该对象。例如,考虑第 1.4.2 节的求和程序:
     int sum = 0;
     // sum values from 1 up to 10 inclusive
     for (int val = 1; val <= 10; ++val)
         sum += val; // equivalent to sum = sum + val

This kind of operation is common not just for addition but for the other arithmetic operators and the bitwise operators. There are compound assignments for each of these operators. The general syntactic form of a compound assignment operator is
C++ 语言不仅对加法,而且还对其他算术操作符和位操作符提供了这种用法,称为复合赋值操作。复合赋值操作符的一般语法格式为:
     a op= b;

where op= may be one of the following ten operators:
其中,op= 可以是下列十个操作符之一:
     +=   -=   *=   /=   %=   // arithmetic operators
     <<= >>=   &=   ^=   |=   // bitwise operators

Each compound operator is essentially equivalent to
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。
     a = a op b;

There is one important difference: When we use the compound assignment, the left-hand operand is evaluated only once. If we write the similar longer version, that operand is evaluated twice: once as the right-hand operand and again as the left. In many, perhaps most, contexts this difference is immaterial aside from possible performance consequences.
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。
Exercises Section 5.4.3
Exercise 5.13:The following assignment is illegal. Why? How would you correct it?
下列赋值操作是不合法的,为什么?怎样改正?
     double dval; int ival; int *pi;
     dval = ival = pi = 0;


Exercise 5.14:Although the following are legal, they probably do not behave as the programmer expects. Why? Rewrite the expressions as you think they should be.
虽然下列表达式都是合法的,但并不是程序员期望的操作,为什么?怎样修改这些表达式以使其能反映程序员的意图?
     (a) if (ptr = retrieve_pointer() != 0)
     (b) if (ival = 1024)
     (c) ival += ival + 1;





      

5.5. Increment and Decrement Operators
5.5. 自增和自减操作符
The increment (++) and decrement (--) operators provide a convenient notational shorthand for adding or subtracting 1 from an object. There are two forms of these operators: prefix and postfix. So far, we have used only the prefix increment, which increments its operand and yields the changed value as its result. The prefix decrement operates similarly, except that it decrements its operand. The postfix versions of these operators increment (or decrement) the operand but yield a copy of the original, unchanged value as its result:
自增(++)和自减(--)操作符为对象加1或减1操作提供了方便简短的实现方式。它们有前置和后置两种使用形式。到目前为止,我们已经使用过前自增操作,该操作使其操作数加1,操作结果是修改后的值。同理,前自减操作使其操作数减 1。这两种操作符的后置形式同样对其操作数加 1(或减 1),但操作后产生操作数原来的、未修改的值作为表达式的结果:
     int i = 0, j;
     j = ++i; // j = 1, i = 1: prefix yields incremented value
     j = i++; // j = 1, i = 2: postfix yields unincremented value

Because the prefix version returns the incremented value, it returns the object itself as an lvalue. The postfix versions return an rvalue.
因为前置操作返回加1后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。
Advice: Use Postfix Operators Only When Necessary
建议:只有在必要时才使用后置操作符
Readers from a C background might be surprised that we use the prefix increment in the programs we've written. The reason is simple: The prefix version does less work. It increments the value and returns the incremented version. The postfix operator must store the original value so that it can return the unincremented value as its result. For ints and pointers, the compiler can optimize away this extra work. For more complex iterator types, this extra work potentially could be more costly. By habitually favoring the use of the prefix versions, we do not have to worry if the performance difference matters.
有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。对于 int 型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。


Postfix Operators Return the Unincremented Value
后置操作符返回未加1的值
The postfix version of ++ and -- is used most often when we want to use the current value of a variable and increment it in a single compound expression:
当我们希望在单个复合表达式中使用变量的当前值,然后再加1时,通常会使用后置的 ++ 和 -- 操作:
     vector<int> ivec;           // empty vector
     int cnt = 10;
     // add elements 10...1 to ivec
     while (cnt > 0)
         ivec.push_back(cnt--);  // int postfix decrement

This program uses the postfix version of -- to decrement cnt. We want to assign the value of cnt to the next element in the vector and then decrement cnt before the next iteration. Had the loop used the prefix version, then the decremented value of cnt would be used when creating the elements in ivec and the effect would be to add elements from 9 down to 0.
这段程序使用了后置的 -- 操作实现 cnt 减 1。我们希望把 cnt 的值赋给 vector 对象的下一个元素,然后在下次迭代前 cnt 的值减 1。如果在循环中使用前置操作,则是用 cnt 减 1 后的值创建 ivec 的新元素,结果是将 9 至 0 十个元素依次添加到 ivec 中。
Combining Dereference and Increment in a Single Expression
在单个表达式中组合使用解引用和自增操作
The following program, which prints the contents of ivec, represents a very common C++ programming pattern:
下面的程序使用了一种非常通用的 C++ 编程模式输出 ivec 的内容:
     vector<int>::iterator iter = ivec.begin();
     // prints 10 9 8 ... 1
     while (iter != ivec.end())
         cout << *iter++ << endl; // iterator postfix increment

The expression *iter++ is usually very confusing to programmers new to both C++ and C.
如果程序员对 C++ 和 C 语言都不太熟悉,则常常会弄不清楚表达式 *iter++ 的含义。

The precedence of postfix increment is higher than that of the dereference operator, so *iter++ is equivalent to *(iter++). The subexpression iter++ increments iter and yields a copy of the previous value of iter as its result. Accordingly, the operand of * is a copy of the unincremented value of iter.
由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于 *(iter++)。子表达式 iter++ 使 iter 加 1,然后返回 iter 原值的副本作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本。
This usage relies on the fact that postfix increment returns a copy of its original, unincremented operand. If it returned the incremented value, we'd dereference the incremented value, with disastrous results: The first element of ivec would not get written. Worse, we'd attempt to dereference one too many elements!
这种用法的根据在于后自增操作返回其操作数原值(没有加 1)的副本。如果返回的是加 1 后的值,则解引用该值将导致错误的结果:ivec 的第一个元素没有输出,并企图对一个多余的元素进行解引用。
Advice: Brevity Can Be a Virtue
建议:简洁即是美
Programmers new to C++ who have not previously programmed in a C-based language often have trouble with the terseness of some expressions. In particular, expressions such as *iter++ can be bewilderingat first. Experienced C++ programmers value being concise. They are much more likely to write
没有 C 语言基础的 C++ 新手,时常会因精简的表达式而苦恼,特别是像 *iter++ 这类令人困惑的表达式。有经验的 C++程序员非常重视简练,他们更喜欢这么写:
     cout << *iter++ << endl;

than the more verbose equivalent
而不采用下面这种冗长的等效代码:
     cout << *iter << endl;
     ++iter;

For programmers new to C++, the second form is clearer because the action of incrementing the iterator and fetching the value to print are kept separate. However, the first version is much more natural to most C++ programmers.
对于初学 C++ 的程序员来说,第二种形式更清晰,因为给迭代器加 1 和获取输出值这两个操作是分开来实现的。但是更多的 C++ 程序员更习惯使用第一种形式。
It is worthwhile to study examples of such code until their meanings are immediately clear. Most C++ programs use succinct expressions rather than more verbose equivalents. Therefore, C++ programmers must be comfortable with such usages. Moreover, once these expressions are familiar, you will find them less error-prone.
要不断地研究类似的代码,最后达到一目了然的地步。大部分的 C++ 程序员更喜欢使用简洁的表达式而非冗长的等效表达式。因此,C++ 程序员必须熟悉这种用法。而且,一旦熟悉了这类表达式,我们会发现使用起来更不容易出错。
Exercises Section 5.5
Exercise 5.15:Explain the difference between prefix and postfix increment.
解释前自增操作和后自增操作的差别。
Exercise 5.16:Why do you think C++ wasn't named ++C?
你认为为什么C++不叫做++C?
Exercise 5.17:What would happen if the while loop that prints the contents of a vector used the prefix increment operator?
如果输出vector内容的while循环使用前自增操作符,那会怎么样?



      

5.6. The Arrow Operator
5.6. 箭头操作符
The arrow operator (->) provides a synonym for expressions involving the dot and dereference operators. The dot operator (Section 1.5.2, p. 25) fetches an element from an object of class type:
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符(第 1.5.2 节)用于获取类类型对象的成员:
     item1.same_isbn(item2); // run the same_isbn member of item1

If we had a pointer (or iterator) to a Sales_item, we would have to dereference the pointer (or iterator) before applying the dot operator:
如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符前,需对该指针(或迭代器)进行解引用:
     Sales_item *sp = &item1;
     (*sp).same_isbn(item2); // run same_isbn on object to which sp points

Here we dereference sp to get the underlying Sales_item. Then we use the dot operator to run same_isbn on that object. We must parenthesize the dereference because dereference has a lower precedence than dot. If we omit the parentheses, this code means something quite different:
这里,对 sp 进行解引用以获得指定的 Sales_item 对象。然后使用点操作符调用指定对象的 same_isbn 成员函数。在上述用法中,注意必须用圆括号把解引用括起来,因为解引用的优先级低于点操作符。如果漏掉圆括号,则这段代码的含义就完全不同了:
     // run the same_isbn member of sp then dereference the result!
     *sp.same_isbn(item2); // error: sp has no member named same_isbn

This expression attempts to fetch the same_isbn member of the object sp. It is equivalent to
这个表达式企图获得 sp 对象的 same_isbn 成员。等价于:
     *(sp.same_isbn(item2));  // equivalent to *sp.same_isbn(item2);

However, sp is a pointer, which has no members; this code will not compile.
然而,sp是一个没有成员的指针;这段代码无法通过编译。
Because it is easy to forget the parentheses and because this kind of code is a common usage, the language defines the arrow operator as a synonym for a dereference followed by the dot operator. Given a pointer (or iterator) to an object of class type, the following expressions are equivalent:
因为编程时很容易忘记圆括号,而且这类代码又经常使用,所以 C++ 为在点操作符后使用的解引用操作定义了一个同义词:箭头操作符(->)。假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
     (*p).foo; // dereference p to get an object and fetch its member named foo
     p->foo;   // equivalent way to fetch the foo from the object to which p points

More concretely, we can rewrite the call to same_isbn as
具体地,可将 same_isbn 的调用重写为:
     sp->same_isbn(item2); // equivalent to (*sp).same_isbn(item2)

Exercises Section 5.6
Exercise 5.18:Write a program that defines a vector of pointers to strings. Read the vector, printing each string and its corresponding size.
编写程序定义一个 vector 对象,其每个元素都是指向 string 类型的指针,读取该 vector 对象,输出每个 string 的内容及其相应的长度。
Exercise 5.19:Assuming that iter is a vector<string>::iterator, indicate which, if any, of the following expressions is legal. Explain the behavior of the legal expressions.
假设 iter 为 vector<string>::iterator 类型的变量,指出下面哪些表达式是合法的,并解释这些合法表达式的行为。
     (a) *iter++;         (b) (*iter)++;
     (c) *iter.empty()    (d) iter->empty();
     (e) ++*iter;         (f) iter++->empty();



 
      

5.7. The Conditional Operator
5.7. 条件操作符
The conditional operator is the only ternary operator in C++. It allows us to embed simple if-else tests inside an expression. The conditional operator has the following syntactic form
条件操作符是 C++ 中唯一的三元操作符,它允许将简单的 if-else 判断语句嵌入表达式中。条件操作符的语法格式为:
     cond ? expr1 : expr2;

where cond is an expression that is used as a condition (Section 1.4.1, p. 12). The operator executes by evaluating cond. If cond evaluates to 0, then the condition is false; any other value is true. cond is always evaluated. If it is true, then expr1 is evaluated; otherwise, expr2 is evaluated. Like the logical AND and OR (&& and ||) operators, the conditional operator guarantees this order of evaluation for its operands. Only one of expr1 or expr2 is evaluated. The following program illustrates use of the conditional operator:
其中,cond 是一个条件判断表达式(第 1.4.1 节)。条件操作符首先计算 cond 的值,如果 cond 的值为 0,则条件为 false;如果 cond 非 0,则条件为 true。无论如何,cond 总是要被计算的。然后,条件为 true 时计算 expr1 ,否则计算 expr2 。和逻辑与、逻辑或(&& 和 ||)操作符一样,条件操作符保证了上述操作数的求解次序。expr1 和 expr2 中只有一个表达式被计算。下面的程序说明了条件操作符的用法:
     int i = 10, j = 20, k = 30;
     // if i > j then maxVal = i else maxVal = j
     int maxVal = i > j ? i : j;

Avoid Deep Nesting of the Conditional Operator
避免条件操作符的深度嵌套
We could use a set of nested conditional expressions to set max to the largest of three variables:
可以使用一组嵌套的条件操作符求出三个变量的最大值,并将最大值赋给 max:
     int max = i > j
                   ? i > k ? i : k
                   : j > k ? j : k;

We could do the equivalent comparison in the following longer but simpler way:
我们也可以用下面更长却更简单的比较语句实现相同的功能:
     int max = i;
     if (j > max)
         max = j;
     if (k > max)
         max = k;

Using a Conditional Operator in an Output Expression
在输出表达式中使用条件操作符
The conditional operator has fairly low precedence. When we embed a conditional expression in a larger expression, we usually must parenthesize the conditional subexpression. For example, the conditional operator is often used to print one or another value, depending on the result of a condition. Incompletely parenthesized uses of the conditional operator in an output expression can have surprising results:
条件操作符的优先级相当低。当我们要在一个更大的表达式中嵌入条件表达式时,通常必须用圆括号把条件表达式括起来。例如,经常使用条件操作符根据一定的条件输出一个或另一个值,在输出表达式中,如果不严格使用圆括号将条件操作符括起来,将会得到意外的结果:
     cout << (i < j ? i : j);  // ok: prints larger of i and j
     cout << (i < j) ? i : j;  // prints 1 or 0!
     cout << i < j ? i : j;    // error: compares cout to int

The second expression is the most interesting: It treats the comparison between i and j as the operand to the << operator. The value 1 or 0 is printed, depending on whether i < j is true or false. The << operator returns cout, which is tested as the condition for the conditional operator. That is, the second expression is equivalent to
第二个表达式比较有趣:它将i和j的比较结果视为 << 操作符的操作数,输出 1 或 0。 << 操作符返回 cout 值,然后将返回结果作为条件操作符的判断条件。也就是,第二个表达式等效于:
     cout << (i < j); // prints 1 or 0
     cout ? i : j;    // test cout and then evaluate i or j
                      // depending on whether cout evaluates to true or false

Exercises Section 5.7
Exercise 5.20:Write a program to prompt the user for a pair of numbers and report which is smaller.
编写程序提示用户输入两个数,然后报告哪个数比较小。
Exercise 5.21:Write a program to process the elements of a vector<int>. Replace each element with an odd value by twice that value.
编写程序处理 vector<int> 对象的元素:将每个奇数值元素用该值的两倍替换。



      

5.8. The sizeof Operator
5.8. sizeof 操作符
The sizeof operator returns a value of type size_t (Section 3.5.2, p. 104) that is the size, in bytes (Section 2.1, p. 35), of an object or type name. The result of sizeof expression is a compile-time constant. The sizeof operator takes one of the following forms:
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为 size_t(第 3.5.2 节),长度的单位是字节(第 2.1 节)。size_t 表达式的结果是编译时常量,该操作符有以下三种语法形式:
     sizeof (type name);
     sizeof (expr);
     sizeof expr;

Applying sizeof to an expr returns the size of the result type of that expression:
将 sizeof 应用在表达式 expr 上,将获得该表达式的结果的类型长度:
     Sales_item item, *p;
     // three ways to obtain size required to hold an object of type Sales_item
     sizeof(Sales_item); // size required to hold an object of type Sales_item
     sizeof item; // size of item's type, e.g., sizeof(Sales_item)
     sizeof *p;   // size of type to which p points, e.g., sizeof(Sales_item)

Evaluating sizeof expr does not evaluate the expression. In particular, in sizeof *p, the pointer p may hold an invalid address, because p is not dereferenced.
将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof *p 中,指针 p 可以持有一个无效地址,因为不需要对 p 做解引用操作。
The result of applying sizeof depends in part on the type involved:
使用 sizeof 的结果部分地依赖所涉及的类型:
sizeof char or an expression of type char is guaranteed to be 1
对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。
sizeof a reference type returns the size of the memory necessary to contain an object of the referenced type
对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间大小。
sizeof a pointer returns the size needed hold a pointer; to obtain the size of the object to which the pointer points, the pointer must be dereferenced
对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获取该指针所指向对象的大小,则必须对指针进行引用。
sizeof an array is equivalent to taking the sizeof the element type times the number of elements in the array
对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数。
Because sizeof returns the size of the entire array, we can determine the number of elements by dividing the sizeof the array by the sizeof an element:
因为 sizeof 返回整个数组在内存中的存储长度,所以用 sizeof 数组的结果除以 sizeof 其元素类型的结果,即可求出数组元素的个数:
     // sizeof(ia)/sizeof(*ia) returns the number of elements in ia
     int sz = sizeof(ia)/sizeof(*ia);

Exercises Section 5.8
Exercise 5.22:Write a program to print the size of each of the built-in types.
编写程序输出的每种内置类型的长度。
Exercise 5.23:Predict the output of the following program and explain your reasoning. Now run the program. Is the output what you expected? If not, figure out why.
预测下列程序的输出是,并解释你的理由。然后运行该程序,输出的结果和你的预测的一样吗?如果不一样,为什么?
     int x[10];   int *p = x;
     cout << sizeof(x)/sizeof(*x) << endl;
     cout << sizeof(p)/sizeof(*p) << endl;





      

5.9. Comma Operator
5.9. 逗号操作符
A comma expression is a series of expressions separated by commas. The expressions are evaluated from left to right. The result of a comma expression is the value of the rightmost expression. The result is an lvalue if the rightmost operand is an lvalue. One common use for the comma operator is in a for loop.
逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。此类表达式通常用于for循环:
     int cnt = ivec.size();
     // add elements from size... 1 to ivec
     for(vector<int>::size_type ix = 0;
                     ix != ivec.size(); ++ix, --cnt)
         ivec[ix] = cnt;

This loop increments ix and decrements cnt in the expression in the for header. Both ix and cnt are changed on each trip through the loop. As long as the test of ix succeeds, we reset the next element to the current value of cnt.
上述的 for 语句在循环表达式中使 ix 自增 1 而 cnt 自减 1。每次循环均要修改 ix 和 cnt 的值。当检验 ix 的条件判断成立时,程序将下一个元素重新设置为 cnt 的当前值。
Exercises Section 5.9
Exercise 5.24:The program in this section is similar to the program on page 163 that added elements to a vector. Both programs decremented a counter to generate the element values. In this program we used the prefix decrement and the earlier one used postfix. Explain why we used prefix in one and postfix in the other.
本节的程序与第 5.5 节在 vector 对象中添加元素的程序类似。两段程序都使用递减的计数器生成元素的值。本程序中,我们使用了前自减操作,而第 5.5 节的程序则使用了后自减操作。解释为什么一段程序中使用前自减操作而在另一段程序中使用后自减操作。

 
      

Chapter 6. Statements
第六章 语句
CONTENTS
Section 6.1 Simple Statements192
Section 6.2 Declaration Statements193
Section 6.3 Compound Statements (Blocks)193
Section 6.4 Statement Scope194
Section 6.5 The if Statement195
Section 6.6 The switch Statement199
Section 6.7 The while Statement204
Section 6.8 The for Loop Statement207
Section 6.9 The do while Statement210
Section 6.10 The break Statement212
Section 6.11 The continue Statement214
Section 6.12 The goto Statement214
Section 6.13 try Blocks and Exception Handling215
Section 6.14 Using the Preprocessor for Debugging220
Chapter Summary223
Defined Terms223


Statements are analogous to sentences in a natural language. In C++ there are simple statements that execute a single task and compound statements that consist of a block of statements that execute as a unit. Like most languages, C++ provides statements for conditional execution and loops that repeatedly execute the same body of code. This chapter looks in detail at the statements supported by C++.
语句类似于自然语言中的句子。C++ 语言既有只完成单一任务的简单语句,也有作为一个单元执行的由一组语句组成的复合语句。和大多数语言一样,C++ 也提供了实现条件分支结构的语句以及重复地执行同一段代码的循环结构。本章将详细讨论 C++ 所支持的语句。
By default, statements are executed sequentially. Except for the simplest programs, sequential execution is inadequate. Therefore, C++ also defines a set of flow-of-control statements that allow statements to be executed conditionally or repeatedly. The if and switch statements support conditional execution. The for, while, and do while statements support repetitive execution. These latter statements are often referred to as loops or iteration statements.
通常情况下,语句是顺序执行的。但是,除了最简单的程序外,只有顺序执行往往并不足够。为此,C++ 定义了一组控制流语句,允许有条件地执行或者重复地执行某部分功能。if 和 switch 语句提供了条件分支结构,而 for、while 和 do while 语句则支持重复执行的功能。后几种语句常称为循环或者迭代语句。
 
      

6.1. Simple Statements
6.1. 简单语句
Most statements in C++ end with a semicolon. An expression, such as ival + 5, becomes an expression statement by following it with a semicolon. Expression statements cause the expression to be evaluated. In the case of
C++ 中,大多数语句以分号结束。例如,像 ival + 5 这样的表达式,在后面加上分号,就是一条表达式语句。表达式语句用于计算表达式。但执行下面的语句
     ival + 5;    // expression statement



evaluating this expression is useless: The result is calculated but not assigned or otherwise used. More commonly, expression statements contain expressions that when evaluated affect the program's state. Examples of such expressions are those that use assignment, increment, input, or output operators.
却没有任何意义:因为计算出来的结果没有用于赋值或其他用途。通常,表达式语句所包含的表达式在计算时会影响程序的状态,使用赋值、自增、输入或输出操作符的表达式就是很好的例子。
Null Statements
空语句
The simplest form of statement is the empty, or null statement. It takes the following form (a single semicolon):
程序语句最简单的形式是空语句,它使用以下的形式(只有一个单独的分号):
     ;  // null statement



A null statement is useful where the language requires a statement but the program's logic does not. Such usage is most common when a loop's work can be done within the condition. For example, we might want to read an input stream, ignoring everything we read until we encounter a particular value:
如果在程序的某个地方,语法上需要一个语句,但逻辑上并不需要,此时应该使用空语句。这种用法常见于在循环条件判断部分就能完成全部循环工作的情况。例如,下面程序从输入流中读取数据,在获得某个特殊值前无需作任何操作:
     // read until we hit end-of-file or find an input equal to sought
     while (cin >> s && s != sought)
         ; // null statement



The condition reads a value from the standard input and implicitly tests cin to see whether the read was successful. Assuming the read succeeded, the second part of the condition tests whether the value we read is equal to the value in sought. If we found the value we want, then the while loop is exited; otherwise, the condition is tested again, which involves reading another value from cin.
循环条件从标准输入中读入一个值并检验 cin 的读入是否成功。如果成功读取数据,循环条件紧接着检查该值是否等于 sought。如果找到了需要的值,则退出 while 循环;否则,循环条件再次从 cin 里读入另一个值继续检验。
A null statement should be commented, so that anyone reading the code can see that the statement was omitted intentionally.
使用空语句时应该加上注释,以便任何读这段代码的人都知道该语句是有意省略的。




Because a null statement is a statement, it is legal anywhere a statement is expected. For this reason, semicolons that might appear illegal are often nothing more than null statements:
由于空语句也是一个语句,因此可用在任何允许使用语句的地方。由于这个原因,那些看似非法的分号往往只不过是一个空语句而已:
     // ok: second semicolon is superfluous null statement
     ival = v1 + v2;;



This fragment is composed of two statements: the expression statement and the null statement.
这个程序段由两条语句组成:一条表达式语句和一条空语句。
Extraneous null statements are not always harmless.
无关的空语句并非总是无害的。




An extra semicolon following the condition in a while or if can drastically alter the programmer's intent:
在 while 或 if 条件后面额外添加分号,往往会彻底改变程序员的意图:
     // disaster: extra semicolon: loop body is this null statement
     while (iter != svec.end()) ; // null statement--while body is empty!
         ++iter;     // increment is not part of the loop



This program will loop indefinitely. Contrary to the indentation, the increment is not part of the loop. The loop body is a null statement caused by the extra semicolon following the condition.
这个程序将会无限次循环。与缩进的意义相反,此自增语句并不是循环的一部分。由于循环条件后面多了一个分号,因此循环体为空语句。
 
      

6.10. The break Statement
6.10. break 语句
A break statement terminates the nearest enclosing while, do while, for, or switch statement. Execution resumes at the statement immediately following the terminated statement. For example, the following loop searches a vector for the first occurrence of a particular value. Once it's found, the loop is exited:
break 语句用于结束最近的 while、do while、for 或 switch 语句,并将程序的执行权传递给紧接在被终止语句之后的语句。例如,下面的循环在 vector 中搜索某个特殊值的第一次出现。一旦找到,则退出循环:
     vector<int>::iterator iter = vec.begin();
     while (iter != vec.end()) {
        if (value == *iter)
             break; // ok: found it!
        else
             ++iter; // not found: keep looking
     }// end of while
     if (iter != vec.end()) // break to here ...
         // continue processing



In this example, the break terminates the while loop. Execution resumes at the if statement immediately following the while.
本例中,break 终止了 while 循环。执行权交给紧跟在 while 语句后面的 if 语句,程序继续执行。
A break can appear only within a loop or switch statement or in a statement nested inside a loop or switch. A break may appear within an if only when the if is inside a switch or a loop. A break occurring outside a loop or switch is a compile-time error. When a break occurs inside a nested switch or loop statement, the enclosing loop or switch statement is unaffected by the termination of the inner switch or loop:
break 只能出现在循环或 switch 结构中,或者出现在嵌套于循环或 switch 结构中的语句里。对于 if 语句,只有当它嵌套在 switch 或循环里面时,才能使用 break。break 出现在循环外或者 switch 外将会导致编译时错误。当 break 出现在嵌套的 switch 或者循环语句中时,将会终止里层的 switch 或循环语句,而外层的 switch 或者循环不受影响:
     string inBuf;
     while (cin >> inBuf && !inBuf.empty()) {
         switch(inBuf[0]) {
         case '-':
             // process up to the first blank
             for (string::size_type ix = 1;
                         ix != inBuf.size(); ++ix) {
                   if (inBuf[ix] == ' ')
                        break; // #1, leaves the for loop
                   // ...
             }
             // remaining '-' processing: break #1 transfers control here
             break; // #2, leaves the switch statement
         case '+':
             // ...
         } // end switch
         // end of switch: break #2 transfers control here
     }  // end while



The break labeled #1 terminates the for loop within the hyphen case label. It does not terminate the enclosing switch statement and in fact does not even terminate the processing for the current case. Processing continues with the first statement following the for, which might be additional code to handle the hyphen case or the break that completes that section.
#1 标记的 break 终止了连字符('-')case 标号内的 for 循环,但并没有终止外层的 switch 语句,而且事实上也并没有结束当前 case 语句的执行。接着程序继续执行 for 语句后面的第一个语句,即处理连字符 case 标号下的其他代码,或者执行结束这个 case 的 break 语句。
The break labeled #2 terminates the switch statement after handling the hyphen case but does not terminate the enclosing while loop. Processing continues after that break by executing the condition in the while, which reads the next string from the standard input.
#2 标记的 break 终止了处理连字符情况的 switch 语句,但没有终止 while 循环。程序接着执行 break 后面的语句,即求解 while 的循环条件,从标准输入读入下一个 string 对象。
Exercises Section 6.10
Exercise 6.19:The first program in this section could be written more succinctly. In fact, its action could be contained entirely in the condition in the while. Rewrite the loop so that it has an empty body and does the work of finding the element in the condition.
本节的第一个程序可以写得更简洁。事实上,该程序的所有工作可以全部包含在 while 的循环条件中。重写这个循环,使得它的循环体为空,并找出满足条件的元素。
Exercise 6.20:Write a program to read a sequence of strings from standard input until either the same word occurs twice in succession or all the words have been read. Use a while loop to read the text one word at a time. Use the break statement to terminate the loop if a word occurs twice in succession. Print the word if it occurs twice in succession, or else print a message saying that no word was repeated.
编写程序从标准输入读入一系列 string 对象,直到同一个单词连续出现两次,或者所有的单词都已读完,才结束读取。请使用 while 循环,每次循环读入一个单词。如果连续出现相同的单词,便以 break 语句结束循环,此时,请输出这个重复出现的单词;否则输出没有任何单词连续重复出现的信息。


 
      

6.11. The continue Statement
6.11. continue 语句
A continue statement causes the current iteration of the nearest enclosing loop to terminate. Execution resumes with the evaluation of the condition in the case of a while or do while statement. In a for loop, execution continues by evaluating the expression inside the for header.
continue 语句导致最近的循环语句的当次迭代提前结束。对于 while 和 do while 语句,继续求解循环条件。而对于 for 循环,程序流程接着求解 for 语句头中的 expression 表达式。
For example, the following loop reads the standard input one word at a time. Only words that begin with an underscore will be processed. For any other value, we terminate the current iteration and get the next input:
例如,下面的循环每次从标准输入中读入一个单词,只有以下划线开头的单词才做处理。如果是其他的值,终止当前循环,接着读取下一个单词:
     string inBuf;
     while (cin >> inBuf && !inBuf.empty()) {
             if (inBuf[0] != '_')
                  continue; // get another input
             // still here? process string ...
     }



A continue can appear only inside a for, while, or do while loop, including inside blocks nested inside such loops.
continue 语句只能出现在 for、while 或者 do while 循环中,包括嵌套在这些循环内部的块语句中。
Exercises Section 6.11
Exercise 6.21:Revise the program from the last exercise in Section 6.10 (p. 213) so that it looks only for duplicated words that start with an uppercase letter.
修改第 6.10 节最后一个习题的程序,使得它只寻找以大写字母开头的连续出现的单词。


 
      

6.12. The goto Statement
6.12. goto 语句
A goto statement provides an unconditional jump from the goto to a labeled statement in the same function.
goto 语句提供了函数内部的无条件跳转,实现从 goto 语句跳转到同一函数内某个带标号的语句。
Use of gotos has been discouraged since the late 1960s. gotos make it difficult to trace the control flow of a program, making the program hard to understand and hard to modify. Any program that uses a goto can be rewritten so that it doesn't need the goto.
从上世纪 60 年代后期开始,不主张使用 goto 语句。goto 语句使跟踪程序控制流程变得很困难,并且使程序难以理解,也难以修改。所有使用 goto 的程序都可以改写为不用 goto 语句,因此也就没有必要使用 goto 语句了。




The syntactic form of a goto statement is
goto 语句的语法规则如下:
     goto label;



where label is an identifier that identifies a labeled statement. A labeled statement is any statement that is preceded by an identifier followed by a colon:
其中 label 是用于标识带标号的语句的标识符。在任何语句前提供一个标识符和冒号,即得带标号的语句:
     end: return; // labeled statement, may be target of a goto



The identifier that forms the label may be used only as the target of a goto. For this reason, label identifiers may be the same as variable names or other identifiers in the program without interfering with other uses of those identifiers. The goto and the labeled statement to which it transfers control must be in the same function.
形成标号的标识符只能用作 goto 的目标。因为这个原因,标号标识符可以与变量名以及程序里的其他标识符一样,不与别的标识符重名。goto 语句和获得所转移的控制权的带标号的语句必须位于于同一个函数内。
A goto may not jump forward over a variable definition:
goto 语句不能跨越变量的定义语句向前跳转:
     // ...
     goto end;
     int ix = 10; // error: goto bypasses declaration statement
 end:
     // error: code here could use ix but the goto bypassed its declaration
     ix = 42;



If definitions are needed between a goto and its corresponding label, the definitions must be enclosed in a block:
如果确实需要在 goto 和其跳转对应的标号之间定义变量,则定义必须放在一个块语句中:
         // ...
         goto end; // ok: jumps to a point where ix is not defined
         {
            int ix = 10;
            // ... code using ix
         }
     end: // ix no longer visible here



A jump backward over an already executed definition is okay. Why? Jumping over an unexecuted definition would mean that a variable could be used even though it was never defined. Jumping back to a point before a variable is defined destroys the variable and constructs it again:
向后跳过已经执行的变量定义语句则是合法的。为什么?向前跳过未执行的变量定义语句,意味着变量可能在没有定义的情况下使用。向后跳回到一个变量定义之前,则会使系统撤销这个变量,然后再重新创建它:
     // backward jump over declaration statement ok
       begin:
         int sz = get_size();
         if (sz <= 0) {
               goto begin;
         }



Note that sz is destroyed when the goto executes and is defined and initialized anew when control jumps back to begin:.
注意:执行 goto 语句时,首先撤销变量 sz,然后程序的控制流程跳转到带 begin: 标号的语句继续执行,再次重新创建和初始化 sz 变量。
Exercises Section 6.12
Exercise 6.22:The last example in this section that jumped back to begin could be better written using a loop. Rewrite the code to eliminate the goto.
对于本节的最后一个例子,跳回到 begin 标号的功能可以用循环更好地实现。请不使用 goto 语句重写这段代码。



      

6.13. try Blocks and Exception Handling
6.13. try 块和异常处理
Handling errors and other anomalous behavior in programs can be one of the most difficult parts of designing any system. Long-lived, interactive systems such as communication switches and routers can devote as much as 90 percent of their code to error detection and error handling. With the proliferation of Web-based applications that run indefinitely, attention to error handling is becoming more important to more and more programmers.
在设计各种软件系统的过程中,处理程序中的错误和其他反常行为是困难的部分之一。像通信交换机和路由器这类长期运行的交互式系统必须将 90% 的程序代码用于实现错误检测和错误处理。随着基于 Web 的应用程序在运行时不确定性的增多,越来越多的程序员更加注重错误的处理。
Exceptions are run-time anomalies, such as running out of memory or encountering unexpected input. Exceptions exist outside the normal functioning of the program and require immediate handling by the program.
异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。异常存在于程序的正常功能之外,并要求程序立即处理。
In well-designed systems, exceptions represent a subset of the program's error handling. Exceptions are most useful when the code that detects a problem cannot handle it. In such cases, the part of the program that detects the problem needs a way to transfer control to the part of the program that can handle the problem. The error-detecting part also needs to be able to indicate what kind of problem occurred and may want to provide additional information.
在设计良好的系统中,异常是程序错误处理的一部分。当程序代码检查到无法处理的问题时,异常处理就特别有用。在这些情况下,检测出问题的那部分程序需要一种方法把控制权转到可以处理这个问题的那部分程序。错误检测程序还必须指出具体出现了什么问题,并且可能需要提供一些附加信息。
Exceptions support this kind of communication between the error-detecting and error-handling parts of a program. In C++ exception handling involves:
异常机制提供程序中错误检测与错误处理部分之间的通信。C++ 的异常处理中包括:
throw expressions, which the error-detecting part uses to indicate that it encountered an error that it cannot handle. We say that a throw raises an exceptional condition.
throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw 引发了异常条件。
try blocks, which the error-handling part uses to deal with an exception. A try block starts with keyword try and ends with one or more catch clauses. Exceptions thrown from code executed inside a try block are usually handled by one of the catch clauses. Because they "handle" the exception, catch clauses are known as handlers.
try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出(throw)的异常,通常会被其中一个 catch 子句处理。由于它们“处理”异常,catch 子句也称为处理代码。
A set of exception classes defined by the library, which are used to pass the information about an error between a throw and an associated catch.
由标准库定义的一组异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。
In the remainder of this section we'll introduce these three components of exception handling. We'll have more to say about exceptions in Section 17.1 (p. 688).
在本节接下来的部分将要介绍这三种异常处理的构成。而第 17.1 节将会进一步了解异常的相关内容。
6.13.1. A throw Expression
6.13.1 throw 表达式
An exception is thrown using a throw expression, which consists of the keyword throw followed by an expression. A throw expression is usually followed by a semicolon, making it into an expression statement. The type of the expression determines what kind of exception is thrown.
系统通过 throw 表达式抛出异常。throw 表达式由关键字 throw 以及尾随的表达式组成,通常以分号结束,这样它就成为了表达式语句。throw 表达式的类型决定了所抛出异常的类型。
As a simple example, recall the program on page 24 that added two objects of type Sales_item. That program checked whether the records it read referred to the same book. If not, it printed a message and exited.
回顾第 1.5.2 节将两个 Sales_item 类型对象相加的程序,就是一个简单的例子。该程序检查读入的记录是否来自同一本书。如果不是,就输出一条信息然后退出程序。
     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
     }



In a less simple program that used Sales_items, the part that adds the objects might be separated from the part that manages the interaction with a user. In this case, we might rewrite the test to throw an exception instead:
在使用 Sales_items 的更简单的程序中,把将对象相加的部分和负责跟用户交互的部分分开。在这个例子中,用 throw 抛出异常来改写检测代码:
     // first check that data is for the same item
     if (!item1.same_isbn(item2))
         throw runtime_error("Data must refer to same ISBN");
     // ok, if we're still here the ISBNs are the same
     std::cout << item1 + item2 << std::endl;



In this code we check whether the ISBNs differ. If so, we discontinue execution and transfer control to a handler that will know how to handle this error.
这段代码检查 ISBN 对象是否不相同。如果不同的话,停止程序的执行,并将控制转移给处理这种错误的处理代码。
A throw takes an expression. In this case, that expression is an object of type runtime_error. The runtime_error type is one of the standard library exception types and is defined in the stdexcept header. We'll have more to say about these types shortly. We create a runtime_error by giving it a string, which provides additional information about the kind of problem that occurred.
throw 语句使用了一个表达式。在本例中,该表达式是 runtime_error 类型的对象。runtime_error 类型是标准库异常类中的一种,在 stdexcept 头文件中定义。在后续章节中很快就会更详细地介绍这些类型。我们通过传递 string 对象来创建 runtime_error 对象,这样就可以提供更多关于所出现问题的相关信息。
6.13.2. The try Block
6.13.2. try 块
The general form of a try block is
try 块的通用语法形式是:
     try {
         program-statements
     } catch (exception-specifier) {
         handler-statements
     } catch (exception-specifier) {
         handler-statements
     } //...



A try block begins with the keyword try followed by a block enclosed in braces. Following the try block is a list of one or more catch clauses. A catch clause consists of three parts: the keyword catch, the declaration of a single type or single object within parentheses (referred to as an exception specifier), and a block, which as usual must be enclosed in curly braces. If the catch clause is selected to handle an exception, the associated block is executed. Once the catch clause finishes, execution continues with the statement immediately following the last catch clause.
try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。每个 catch 子句包括三部分:关键字 catch,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。
The program-statements inside the try constitute the normal logic of the program. They can contain any C++ statement, including declarations. Like any block, a try block introduces a local scope, and variables declared within a try block cannot be referred to outside the try, including within the catch clauses.
try 语句内的 program-statements 形成程序的正常逻辑。这里面可以包含任意 C++ 语句,包括变量声明。与其他块语句一样,try 块引入局部作用域,在 try 块中声明的变量,包括 catch 子句声明的变量,不能在 try 外面引用。
Writing a Handler
编写处理代码
In the preceeding example we used a throw to avoid adding two Sales_items that represented different books. We imagined that the part of the program that added to Sales_items was separate from the part that communicated with the user. The part that interacts with the user might contain code something like the following to handle the exception that was thrown:
在前面的例子中,使用了 throw 来避免将两个表示不同书的 Sales_items 对象相加。想象一下将 Sales_items 对象相加的那部分程序与负责与用户交流的那部分是分开的,则与用户交互的部分也许会包含下面的用于处理所捕获异常的代码:
     while (cin >> item1 >> item2) {
         try {
             // execute code that will add the two Sales_items
             // if the addition fails, the code throws a runtime_error exception
         } catch (runtime_error err) {
             // remind the user that ISBN must match and prompt for another pair
             cout << err.what()
                  << "\nTry Again? Enter y or n" << endl;
             char c;
             cin >> c;
             if (cin && c == 'n')
                 break;     // break out of the while loop
         }
     }



Following the try keyword is a block. That block would invoke the part of the program that processes Sales_item objects. That part might throw an exception of type runtime_error.
关键字 try 后面是一个块语句。这个块语句调用处理 Sales_item 对象的程序部分。这部分也可能会抛出 runtime_error 类型的异常。
This try block has a single catch clause, which handles exceptions of type runtime_error. The statements in the block following the catch define the actions that will be executed if code inside the try block throws a runtime_error. Our catch handles the error by printing a message and asking the user to indicate whether to continue. If the user enters an 'n', then we break out of the while. Otherwise the loop continues by reading two new Sales_items.
上述 try 块提供单个 catch 子句,用来处理 runtime_error 类型的异常。在执行 try 块代码的过程中,如果在 try 块中的代码抛出 runtime_error 类型的异常,则处理这类异常的动作在 catch 后面的块语句中定义。本例中,catch 输出信息并且询问用户是否继续进行异常处理。如果用户输入'n',则结束 while;否则继续循环,读入两个新的 Sales_items 对象。
The prompt to the user prints the return from err.what(). We know that err has type runtime_error, so we can infer that what is a member function (Section 1.5.2, p. 24) of the runtime_error class. Each of the library exception classes defines a member function named what. This function takes no arguments and returns a C-style character string. In the case of runtime_error, the C-style string that what returns is a copy of the string that was used to initialize the runtime_error. If the code described in the previous section threw an exception, then the output printed by this catch would be
通过输出 err.what() 的返回值提示用户。大家都知道 err 返回 runtime_error 类型的值,因此可以推断出 what 是 runtime_error 类的一个成员函数(1.5.2 节)。每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回 C 风格字符串。在出现 runtime_error 的情况下,what 返回的 C 风格字符串,是用于初始化 runtime_error 的 string 对象的副本。如果在前面章节描述的代码抛出异常,那么执行这个 catch 将输出。
     Data must refer to same ISBN
     Try Again? Enter y or n



functions are exited during the search for a handler
函数在寻找处理代码的过程中退出
in complex systems the execution path of a program may pass through multiple try blocks before encountering code that actually throws an exception. for example, a try block might call a function that contains a try, that calls another function with its own try, and so on.
在复杂的系统中,程序的执行路径也许在遇到抛出异常的代码之前,就已经经过了多个 try 块。例如,一个 try 块可能调用了包含另一 try 块的函数,它的 try 块又调用了含有 try 块的另一个函数,如此类推。
the search for a handler reverses the call chain. when an exception is thrown, the function that threw the exception is searched first. if no matching catch is found, the function terminates, and the function that called the one that threw is searched for a matching catch. if no handler is found, then that function also exits and the function that called it is searched; and so on back up the execution path until a catch of an appropriate type is found.
寻找处理代码的过程与函数调用链刚好相反。抛出一个异常时,首先要搜索的是抛出异常的函数。如果没有找到匹配的 catch,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的 catch。如果仍然找到相应的处理代码,该函数同样要终止,搜索调用它的函数。如此类推,继续按执行路径回退,直到找到适当类型的 catch 为止。
If no catch clause capable of handling the exception exists, program execution is transferred to a library function named terminate, which is defined in the exception header. The behavior of that function is system dependent, but it usually aborts the program.
如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为 terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
Exceptions that occur in programs that define no try blocks are handled in the same manner: After all, if there are no try blocks, there can be no handlers for any exception that might be thrown. If an exception occurs, then terminate is called and the program (ordinarily) is aborted.
在程序中出现的异常,如果没有经 try 块定义,则都以相同的方式来处理:毕竟,如果没有任何 try 块,也就没有捕获异常的处理代码(catch 子句)。此时,如果发生了异常,系统将自动调用 terminate 终止程序的执行。
Exercises Section 6.13.2
Exercise 6.23:The bitset operation to_ulong tHRows an overflow_error exception if the bitset is larger than the size of an unsigned long. Write a program that generates this exception.
bitset 类提供 to_ulong 操作,如果 bitset 提供的位数大于 unsigned long 的长度时,抛出一个 overflow_error 异常。编写产生这种异常的程序。
Exercise 6.24:Revise your program to catch this exception and print a message.
修改上述的程序,使它能捕获这种异常并输出提示信息。



6.13.3. Standard Exceptions
6.13.3. 标准异常
The C++ library defines a set of classes that it uses to report problems encountered in the functions in the standard library. These standard exception classes are also intended to be used in the programs we write. Library exception classes are defined in four headers:
C++ 标准库定义了一组类,用于报告在标准库中的函数遇到的问题。程序员可在自己编写的程序中使用这些标准异常类。标准库异常类定义在四个头文件中:
The exception header defines the most general kind of exception class named exception. It communicates only that an exception occurs but provides no additional information.
exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。
The stdexcept header defines several general purpose exception classes. These types are listed in Table 6.1 on the following page.
stdexcept 头文件定义了几种常见的异常类,这些类型在表 6.1 中列出。
Table 6.1. Standard Exception Classes Defined in <stdexcept>
表 6.1 在 <stdexcept> 头文件中定义的标准异常类
exceptionThe most general kind of problem.
最常见的问题。
runtime_errorProblem that can be detected only at run time.
运行时错误:仅在运行时才能检测到问题
range_errorRun-time error: result generated outside the range of values that are meaningful.
运行时错误:生成的结果超出了有意义的值域范围
overflow_errorRun-time error: computation that overflowed.
运行时错误:计算上溢
underflow_errorRun-time error: computation that underflowed.
运行时错误:计算下溢
logic_errorProblem that could be detected before run time.
逻辑错误:可在运行前检测到问题
domain_errorLogic error: argument for which no result exists.
逻辑错误:参数的结果值不存在
invalid_argumentLogic error: inappropriate argument.
逻辑错误:不合适的参数
length_errorLogic error: attempt to create an object larger than the maximum size for that type.
逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_rangeLogic error: used a value outside the valid range.
逻辑错误:使用一个超出有效范围的值


The new header defines the bad_alloc exception type, which is the exception thrown by new (Section 5.11, p. 174) if it cannot allocate memory.
new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new(第 5.11 节)抛出的异常。
The type_info header defines the bad_cast exception type, which we will discuss in Section 18.2 (p. 772).
type_info 头文件定义了 bad_cast 异常类型,这种类型将第 18.2 节讨论。
Standard Library Exception Classes
标准库异常类
The library exception classes have only a few operations. We can create, copy, and assign objects of any of the exception types. The exception, bad_alloc, and bad_cast types define only a default constructor (Section 2.3.4, p. 50); it is not possible to provide an initializer for objects of these types. The other exception types define only a single constructor that takes a string initializer. When we define any of these other exception types, we must supply a string argument. That string initializer is used to provide additional information about the error that occurred.
标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。 exception、bad_alloc 以及 bad_cast 类型只定义了默认构造函数(第 2.3.4 节),无法在创建这些类型的对象时为它们提供初值。其他的异常类型则只定义了一个使用 string 初始化式的构造函数。当需要定义这些异常类型的对象时,必须提供一想 string 参数。string 初始化式用于为所发生的错误提供更多的信息。
The exception types define only a single operation named what. That function takes no arguments and returns a const char*. The pointer it returns points to a C-style character string (Section 4.3, p. 130). The purpose of this C-style character string is to provide some sort of textual description of the exception thrown.
异常类型只定义了一个名为 what 的操作。这个函数不需要任何参数,并且返回 const char* 类型值。它返回的指针指向一个 C 风格字符串(第 4.3 节)。使用 C 风格字符串的目的是为所抛出的异常提出更详细的文字描述。
The contents of the C-style character array to which what returns a pointer depends on the type of the exception object. For the types that take a string initializer, the what function returns that string as a C-style character array. For the other types, the value returned varies by compiler.
what 函数所返回的指针指向 C 风格字符数组的内容,这个数组的内容依赖于异常对象的类型。对于接受 string 初始化式的异常类型,what 函数将返回该 string 作为 C 风格字符数组。对于其他异常类型,返回的值则根据编译器的变化而不同。
 
      

6.14. Using the Preprocessor for Debugging
6.14. 使用预处理器进行调试
In Section 2.9.2 (p. 71) we learned how to use preprocessor variables to prevent header files being included more than once. C++ programmers sometimes use a technique similar to header guards to conditionally execute debugging code. The idea is that the program will contain debugging code that is executed only while the program is being developed. When the application is completed and ready to ship, the debugging code is turned off. We can write conditional debugging code using the NDEBUG preprocessor variable:
第 2.9.2 节介绍了如何使用预处理变量来避免重复包含头文件。C++ 程序员有时也会使用类似的技术有条件地执行用于调试的代码。这种想法是:程序所包含的调试代码仅在开发过程中执行。当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用 NDEBUG 预处理变量实现有条件的调试代码:
     int main()
     {
     #ifndef NDEBUG
     cerr << "starting main" << endl;
     #endif
     // ...



If NDEBUG is not defined, then the program writes the message to cerr. If NDEBUG is defined, then the program executes without ever passing through the code between the #ifndef and the #endif.
如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef 和 #endif 之间的代码。
By default, NDEBUG is not defined, meaning that by default, the code inside the #ifndef and #endif is processed. When the program is being developed, we leave NDEBUG undefined so that the debugging statements are executed. When the program is built for delivery to customers, these debugging statements can be (effectively) removed by defining the NDEBUG preprocessor variable. Most compilers provide a command line option that defines NDEBUG:
默认情况下,NDEBUG 未定义,这也就意味着必须执行 #ifndef 和 #endif 之间的代码。在开发程序的过程中,只要保持 NDEBUG 未定义就会执行其中的调试语句。开发完成后,要将程序交付给客户时,可通过定义 NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG 命令行选项:
     $ CC -DNDEBUG main.C



has the same effect as writing #define NDEBUG at the beginning of main.C.
这样的命令行行将于在 main.c 的开头提供 #define NDEBUG 预处理命令。
The preprocessor defines four other constants that can be useful in debugging:
预处理器还定义了其余四种在调试时非常有用的常量:
__FILE__ name of the file.
__FILE__ 文件名
__LINE__ current line number.
__LINE__ 当前行号
__TIME__ time the file was compiled.
__TIME__ 文件被编译的时间
__DATE__ date the file was compiled.
__DATE__ 文件被编译的日期
We might use these constants to report additional information in error messages:
可使用这些常量在错误消息中提供更多的信息:
     if (word.size() < threshold)
         cerr << "Error: " << _ _FILE_ _
              << " : line " << _ _LINE_ _ << endl
              << "       Compiled on " << _ _DATE_ _
              << " at " << _ _TIME_ _ << endl
              << "      Word read was " << word
              << ": Length too short" << endl;



If we give this program a string that is shorter than the threshold, then the following error message will be generated:
如果给这个程序提供一个比 threshold 短的 string 对象,则会产生下面的错误信息:
     Error: wdebug.cc : line 21
               Compiled on Jan 12 2005 at 19:44:40
               Word read was "foo": Length too short



Another common debugging technique uses the NDEBUG preprocessor variable and the assert preprocessor macro. The assert macro is defined in the cassert header, which we must include in any file that uses assert.
另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。
A preprocessor macro acts something like a function call. The assert macro takes a single expression, which it uses as a condition:
预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:
     assert(expr)



As long as NDEBUG is not defined, the assert macro evaluates the condtion and if the result is false, then assert writes a message and terminates the program. If the expression has a nonzero (e.g., true) value, then assert does nothing.
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。
Unlike exceptions, which deal with errors that a program expects might happen in production, programmers use assert to test conditions that "cannot happen." For example, a program that does some manipulation of input text might know that all words it is given are always longer than a threshold. That program might contain a statement such as:
与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用 assert 来测试“不可能发生”的条件。例如,对于处理输入文本的程序,可以预测全部给出的单词都比指定的阈值长。那么程序可以包含这样一个语句:
     assert(word.size() > threshold);



During testing the assert has the effect of verifying that the data are always of the expected size. Once development and test are complete, the program is built and NDEBUG is defined. In production code, assert does nothing, so there is no run-time cost. Of course, there is also no run-time check. assert should be used only to verify things that truly should not be possible. It can be useful as an aid in getting a program debugged but should not be used to substitute for run-time logic checks or error checking that the program should be doing.
在测试过程中,assert 等效于检验数据是否总是具有预期的大小。一旦开发和测试工作完成,程序就已经建立好,并且定义了 NDEBUG。在成品代码中,assert 语句不做任何工作,因此也没有任何运行时代价。当然,也不会引起任何运行时检查。assert 仅用于检查确实不可能的条件,这只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。
Exercises Section 6.14
Exercise 6.25:Revise the program you wrote for the exercise in Section 6.11 (p. 214) to conditionally print information about its execution. For example, you might print each word as it is read to let you determine whether the loop correctly finds the first duplicated word that begins with an uppercase letter. Compile and run the program with debugging turned on and again with it turned off.
修改第 6.11 节习题所编写的程序,使其可以有条件地输出运行时的信息。例如,可以输出每一个读入的单词,用来判断循环是否正确地找到第一个连续出现的以大写字母开头的单词。分别在打开和关闭调试器的情况下编译和运行这个程序。
Exercise 6.26:What happens in the following loop:
下面循环会导致什么现象的发生:
     string s;
     while (cin >> s) {
        assert(cin);
        // process s
     }



Explain whether this usage seems like a good application of the assert macro.
解释这种用法是否是 assert 宏的一种恰当应用。
Exercise 6.27:Explain this loop:
解释下面的循环:
     string s;
     while (cin >> s && s != sought) { } // empty body
     assert(cin);
     // process s






      

Chapter Summary
小结
C++ provides a fairly limited number of statements. Most of these affect the flow of control within a program:
C++ 提供了种类相当有限的语句,其中大多数都会影响程序的控制流:
while, for, and do while statements, which implement iterative loops
while、for 以及 do while 语句,实现反复循环;
if and switch, which provide conditional execution
if 和 switch,提供条件分支结构;
continue, which stops the current iteration of a loop
continue,终止当次循环;
break, which exits a loop or switch statement
break,退出一个循环或 switch 语句;
goto, which transfers control to a labeled statement
goto,将控制跳转到某个标号语句;
try, catch, which define a try block enclosing a sequence of statements that might throw an exception. The catch clause(s) are intended to handle the exception(s) that the enclosed code might throw.
try、catch 语句,实现 try 块的定义,该语句包含一个可能抛出异常的语句序列,catch 子句则用来处理在 try 块里抛出的异常;
throw expressions, which exit a block of code, transferring control to an associated catch clause
throw 表达式,用于退出代码块的执行,将控制转移给相关的 catch 子句。
There is also a return statement, which will be covered in Chapter 7.
当然还有将在第七章介绍的 return 语句。
In addition, there are expression statements and declaration statements. An expression statement causes the subject expression to be evaluated. Declarations and definitions of variables were described in Chapter 2.
此外,C++ 还提供表达式语句和声明语句。表达式语句用于求解表达式。变量的声明和定义则已在第二章讲述过了。
      

Defined Terms
术语
assert
Preprocessor macro that takes a single expression, which it uses as a condition. If the preprocessor variable NDEBUG is not defined, then assert evaluates the condition. If the condition is false, assert writes a message and terminates the program.
一种预处理宏,使用单个表达式作为断言条件。如果预处理变量 NDEBUG 没有定义,则 assert 将求解它的条件表达式。若条件为 false,assert 输出信息并终止程序的执行。
block(块)
A sequence of statements enclosed in curly braces. A block is a statement, so it can appear anywhere a statement is expected.
包含在一对花括号里的语句序列。在语法上,块就是单语句,可出现在任何单语句可以出现的地方。
break statement(break 语句)
Terminates the nearest enclosing loop or switch statement. Execution transfers to the first statement following the terminated loop or switch.
一种语句,能够终止最近的循环或者 switch 语句的执行,将控制权交给被终止的循环或者 switch 后的第一条语句。
case label(case 标号)
Integral constant value that follows the keyword case in a switch statement. No two case labels in the same switch statement may have the same value. If the value in the switch condition is equal to that in one of the case labels, control transfers to the first statement following the matched label. Execution continues from that point until a break is encountered or it flows off the end of the switch statement.
switch 语句中跟在关键字 case 后的整型常量值。在同一个 switch 结构中不能有任何两个标号拥有相同的常量值。如果 switch 条件表达式的值与其中某个标号的值相等,则控制权转移到匹配标号后面的第一条语句,从这种语句开始依次继续各个语句,直到遇到 break 或者到达 switch 结尾为止。
catch clause(catch 子句)
The catch keyword, an exception specifier in parentheses, and a block of statements. The code inside a catch clause does whatever is necessary to handle an exception of the type defined in its exception specifier.
一种语句,包括关键字 catch、圆括号内的异常说明符以及一个块语句。catch 子句中的代码实现某种异常的处理,该异常的处理,该异常由圆括号内的异常说明符定义。
compound statement(复合语句)
Synonym for block.
块的同义词。
continue statement(continue 语句)
Terminates the current iteration of the nearest enclosing loop. Execution transfers to the loop condition in a while or do or to the expression in the for header.
一种语句,能够结束最近的循环结构的当次循环迭代,将控制流转移到 while 或 do 的循环条件表达式,或者 for 语句头中第三个表达式。
dangling else(悬垂 else)
Colloquial term used to refer to the problem of how to process nested if statements in which there are more ifs than elses. In C++, an else is always paired with the closest preceding unmatched if. Note that curly braces can be used to effectively hide an inner if so that the programmer can control with which if a given else should be matched.
一个通俗术语,指出如何处理嵌套 if 语句中 if 多于 else 时发生的二义性问题。C++ 中,else 总是与最近的未匹配的 if 配对。注意使用花括号能有效地隐藏内层 if,使程序员可以控制给定的 else 与哪个 if 相匹配。
declaration statement(声明语句)
A statement that defines or declares a variable. Declarations were covered in Chapter 2.
定义或者声明变量的语句。声明已在第二章中介绍。
default label(default 标号)
The switch case label that matches any otherwise unmatched value computed in the switch condition.
switch 语句中的一种标号,当计算 switch 条件所得的值与所有 case 标号的值都不匹配时,则执行 default 标号关联的语句。
exception classes(异常类)
Set of classes defined by the standard library to be used to represent errors. Table 6.1 (p. 220) lists the general purpose exceptions.
标准库定义的一组描述程序错误的类。表 6.1 列出了常见的异常。
exception handler(异常处理代码)
Code that deals with an exception raised in another part of the program. Synonym for catch clause.
一段代码,用于处理程序某个部分引起的异常。是 catch 子句的同义词。
exception specifier(异常说明符)
The declaration of an object or a type that indicates the kind of exceptions a catch clause can handle.
对象或类型的声明,用于指出当前的 catch 能处理的异常类型。
expression statement(表达式语句)
An expression followed by a semicolon. An expression statement causes the expression to be evaluated.
一种语句,由后接分号的表达式构成。表达式语句用于表达式的求解。
flow of control(控制流)
Execution path through a program.
程序的执行路径。
goto statement(goto 语句)
Statement that causes an unconditional transfer of control to a specified labeled statement elsewhere in the program. gotos obfuscate the flow of control within a program and should be avoided.
一种语句,能够使程序控制流程无条件跳转到指定标号语句。goto 扰乱了程序内部的控制流,应尽可能避免使用。
if else statement(if else 语句)
Conditional execution of code following the if or the else, depending on the truth value of the condition.
一种语句,有条件地执行 if 或 else 后的代码,如何执行取决于条件表达式的真值。
if statement(if 语句)
Conditional execution based on the value of the specified condition. If the condition is true, then the if body is executed. If not, control flows to the statement following the if.
基于指定条件值的条件分支语句。如果条件为真,则执行 if 语句体;否则,控制流转到 if 后面的语句。
labeled statement(带标号的语句)
A statement preceded by a label. A label is an identifier followed by a colon.
以标号开头的语句。标号是后面带一个冒号的标识符。
null statement(空语句)
An empty statement. Indicated by a single semicolon.
空白的语句。其语法形式为单个分号。
preprocessor macro(预处理宏)
Function like facility defined by the preprocessor. assert is a macro. Modern C++ programs make very little use of the preprocessor macros.
与预处理器定义的设施相似的函数。assert 是一个宏。现代 C++ 程序很少使用预处理宏。
raise(引发)
Often used as a synonym for throw. C++ programmers speak of "throwing" or "raising" an exception interchangably.
常用作 throw 的同义词。C++ 程序员所说的“抛出(throwing)”或者“引发(raising)”异常表示一样的含义。
switch statement(switch 语句)
A conditional execution statement that starts by evaluating the expression that follows the switch keyword. Control passes to the labeled statement with a case label that matches the value of the expression. If there is no matching label, execution either branches to the default label, if there is one, or falls out of the switch if there is no default label.
从计算关键字 switch 后面的表达式开始执行的条件分支语句。程序的控制流转跳到与表达式值匹配的 case 标号所标记的标号语句。如果没有匹配的标号,则执行 default 标号标记的分支,如果没有提供 default 分支则结束 switch 语句的执行。
terminate
Library function that is called if an exception is not caught. Usually aborts the program.
异常未被捕获时调用的标准库函数。通常会终止程序的执行。
throw expression(throw 表达式)
Expression that interrupts the current execution path. Each throw tHRows an object and transfers control to the nearest enclosing catch clause that can handle the type of exception that is thrown.
中断当前执行路径的表达式。每个 throw 都会抛出一个对象,并将控制转换到最近的可处理该类型异常的 catch 子句。
try block(try 块)
A block enclosed by the keyword try and one or more catch clauses. If the code inside the try block raises an exception and one of the catch clauses matches the type of the exception, then the exception is handled by that catch. Otherwise, the exception is handled by an enclosing try block or the program terminates.
跟在关键字 try 后面的块,以及一个或多个 catch 子句。如果 try 块中的代码产生了异常,而且该异常类型与其中某个 catch 子句匹配,则执行这个 catch 子句的语句处理这个异常。否则,异常将由外围 try 块处理,或者终止程序。
while loop(while 循环)
Control statement that executes its target statement as long as a specified condition is true. The statement is executed zero or more times, depending on the truth value of the condition.
当指定条件为 true 时,执行目标代码的控制语句。根据条件的真值,目标代码可能执行零次或多次。
      

6.2. Declaration Statements
6.2. 声明语句
Defining or declaring an object or a class is a statement. Definition statements are usually referred to as declaration statements although definition statement might be more accurate. We covered definitions and declarations of variables in Section 2.3 (p. 43). Class definitions were introduced in Section 2.8 (p. 63) and will be covered in more detail in Chapter 12.
在 C++ 中,对象或类的定义或声明也是语句。尽管定义语句这种说法也许更准确些,但定义语句经常被称为声明语句。第 2.3 节介绍了变量的定义和声明。第 2.8 节介绍了类的定义,相关内容将在第十二章进一步探讨。

      

6.3. Compound Statements (Blocks)
6.3. 复合语句(块)
A compound statement, usually referred to as a block, is a (possibly empty) sequence of statements surrounded by a pair of curly braces. A block is a scope. Names introduced within a block are accessible only from within that block or from blocks nested inside the block. As usual, a name is visible only from its point of definition until the end of the enclosing block.
复合语句,通常被称为块,是用一对花括号括起来的语句序列(也可能是空的)。块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。通常,一个名字只从其定义处到该块的结尾这段范围内可见。
Compound statements can be used where the rules of the language require a single statement, but the logic of our program needs to execute more than one. For example, the body of a while or for loop must be a single statement. Yet, we often need to execute more than one statement in the body of a loop. We can do so by enclosing the statements in a pair of braces, thus turning the sequence of statements into a block.
复合语句用在语法规则要求使用单个语句但程序逻辑却需要不止一个语句的地方。例如,while 或 for 语句的循环体必须是单个语句。然而,大多数情况都需要在循环体里执行多个语句。因而可使用一对花括号将语句序列括起来,使其成为块语句。
As an example, recall the while loop from our solution to the bookstore problem on page 26:
回顾一下第 1.6 节处理书店问题的程序,以其中用到的 while 循环为例:
     // if so, read the transaction records
     while (std::cin >> trans)
         if (total.same_isbn(trans))
             // match: update the running total
             total = total + trans;
        else {
             // no match: print & assign to total
             std::cout << total << std::endl;
             total = trans;
     }



In the else branch, the logic of our program requires that we print total and then reset it from trans. An else may be followed by only a single statement. By enclosing both statements in curly braces, we transform them into a single (com-pound) statement. This statement satisfies the rules of the language and the needs of our program.
在 else 分支中,程序逻辑需要输出 total 的值,然后用 trans 重置 total。但是,else 分支只能后接单个语句。于是,用一对花括号将上述两条语句括起来,使其在语法上成为单个语句(复合语句)。这个语句既符合语法规则又满足程序的需要。
Unlike most other statements, a block is not terminated by a semicolon.
与其他大多数语句不同,块并不是以分号结束的。




Just as there is a null statement, we also can define an empty block. We do so by using a pair of curlies with no statements:
像空语句一样,程序员也可以定义空块,用一对内部没有语句的花括号实现:
     while (cin >> s && s != sought)
         { } // empty block


Exercises Section 6.3
Exercise 6.1:What is a null statement? Give an example of when you might use a null statement.
什么是空语句?请给出一个使用空语句的例子。
Exercise 6.2:What is a block? Give an example of when you might use a block.
什么是块语句?请给出一个使用块的例子。
Exercise 6.3:Use the comma operator (Section 5.9, p. 168) to rewrite the else branch in the while loop from the bookstore problem so that it no longer requires a block. Explain whether this rewrite improves or diminishes the readability of this code.
使用逗号操作符(第 5.9 节)重写书店问题中 while 循环里的 else 分支,使它不再需要用块实现。解释一下重写后是提高还是降低了该段代码的可读性。
Exercise 6.4:In the while loop that solved the bookstore problem, what effect, if any, would removing the curly brace following the while and its corresponding close curly have on the program?
在解决书店问题的 while 循环中,如果删去 while 后面的左花括号及相应的右花括号,将会给程序带来什么影响?



      

6.4. Statement Scope
6.4. 语句作用域
Some statements permit variable definitions within their control structure:
有些语句允许在它们的控制结构中定义变量:
     while (int i = get_num())
         cout << i << endl;
     i = 0; // error: i is not accessible outside the loop



Variables defined in a condition must be initialized. The value tested by the condition is the value of the initialized object.
在条件表达式中定义的变量必须初始化,该条件检验的就是初始化对象的值。




Variables defined as part of the control structure of a statement are visible only until the end of the statement in which they are defined. The scope of such variables is limited to the statement body. Often the statement body itself is a block, which in turn may contain other blocks. A name introduced in a control structure is local to the statement and the scopes nested inside the statement:
在语句的控制结构中定义的变量,仅在定义它们的块语句结束前有效。这种变量的作用域限制在语句体内。通常,语句体本身就是一个块语句,其中也可能包含了其他的块。一个在控制结构里引入的名字是该语句的局部变量,其作用域局限在语句内部。
     // index is visible only within the for statement
     for (vector<int>::size_type index = 0;
                     index != vec.size(); ++index)
     { // new scope, nested within the scope of this for statement
         int square = 0;
         if (index % 2)                      // ok: index is in scope
             square = index * index;
         vec[index] = square;
     }
     if (index != vec.size()) // error: index is not visible here



If the program needs to access the value of a variable used in the control statement, then that variable must be defined outside the control structure:
如果程序需要访问某个控制结构中的变量,那么这个变量必须在控制语句外部定义。
     vector<int>::size_type index = 0;
     for ( /* empty */ ; index != vec.size(); ++index)
         // as before
     if  (index != vec.size()) // ok: now index is in scope
         // as before



Earlier versions of C++ treated the scope of variables defined inside a for differently: Variables defined in the for header were treated as if they were defined just before the for. Older C++ programs may have code that relies on being able to access these control variables outside the scope of the for.
早期的 C++ 版本以不同的方式处理 for 语句中定义的变量的作用域:将 for 语句头定义的变量视为在 for 语句之前定义。有些更旧式的 C++ 程序代码允许在 for 语句作用域外访问控制变量。




One advantage of limiting the scope of variables defined within a control statement to that statement is that the names of such variables can be reused without worrying about whether their current value is correct at each use. If the name is not in scope, then it is impossible to use that name with an incorrect, leftover value.
对于在控制语句中定义的变量,限制其作用域的一个好处是,这些变量名可以重复使用而不必担心它们的当前值在每一次使用时是否正确。对于作用域外的变量,是不可能用到其在作用域内的残留值的。
      

6.5. The if Statement
6.5. if 语句
An if statement conditionally executes another statement based on whether a specified expression is true. There are two forms of the if: one with an else branch and one without. The syntactic form of the plain if is the following:
if 语句根据特定表达式是否为真来有条件地执行另一个语句。if 语句有两种形式:其中一种带 else 分支而另一种则没有。根据语法结构,最简单的 if 语句是这样的:
     if (condition)
          statement



The condition must be enclosed in parentheses. It can be an expression, such as
其中的 condition 部分必须用圆括号括起来。它可以是一个表达式,例如:
     if (a + b > c) {/* ... */}



or an initialized declaration, such as
或者一个初始化声明,例如:
     // ival only accessible within the if statement
     if (int ival = compute_value()) {/* ... */}



As usual, statement could be a compound statementthat is, a block of statements enclosed in curly braces.
通常,statement 部分可以是复合语句,即用花括号括起来的块语句。
When a condition defines a variable, the variable must be initialized. The value of the initialized variable is converted to bool (Section 5.12.3, p. 181) and the resulting bool determines the value of the condition. The variable can be of any type that can be converted to bool, which means it can be an arithmetic or pointer type. As we'll see in Chapter 14, whether a class type can be used in a condition depends on the class. Of the types we've used so far, the IO types can be used in a condition, but the vector and string types may not be used as a condition.
如果在条件表达式中定义了变量,那么变量必须初始化。将已初始化的变量值转换为 bool 值(第 5.12.3 节)后,该 bool 值决定条件是否成立。变量类型可以是任何可转换为 bool 型的类型,这意味着它可以是算术类型或指针类型。正如第十四章要提及的,一个类类型能否用在条件表达式中取决于类本身。迄今为止,在所有用过的类类型中,IO 类型可以用作条件,但 vector 类型和 string 类型一般不可用作条件。
To illustrate the use of the if statement, we'll find the smallest value in a vector<int>, keeping a count of how many times that minimum value occurs. To solve this problem, we'll need two if statements: one to determine whether we have a new minimum and the other to increment a count of the number of occurrences of the current minimum value:
为了说明 if 语句的用法,下面程序用于寻找 vector<int> 对象中的最小值,并且记录这个最小值出现的次数。为了解决这个问题,需要两个 if 语句:一个判断是否得到一个新的最小值,而另一个则用来增加当前最小值的数目。
     if (minVal > ivec[i])  { /* process new minVal */ }
     if (minVal == ivec[i]) { /* increment occurrence count */ }



Statement Block as Target of an if
语句块用作 if 语句的对象
We'll start by considering each if in isolation. One of these if statements will determine whether there is a new minimum and, if so, reset the counter and update minVal:
现在单独考虑上述例子中的每个 if 语句。其中一个 if 语句将要决定是否出现了一个新的最小值,如果是的话,则要重置计数器并更新最小值:
     if (minVal > ivec[i]) { // execute both statements if condition is true
          minVal = ivec[i];
          occurs = 1;
     }



The other conditionally updates the counter. This if needs only one statement, so it need not be enclosed in curlies:
另一个 if 语句则有条件地更新计数器,它只需要一个语句,因此不必用花括号起来:
     if (minVal == ivec[i])
          ++occurs;



It is a somewhat common error to forget the curly braces when multiple statements must be executed as a single statement.
当多个语句必须作为单个语句执行时,比较常见的错误是漏掉了花括号。




In the following program, contrary to the indentation and intention of the programmer, the assignment to occurs is not part of the if statement:
在下面的程序中,与程序员缩进目的相反,对 occurs 的赋值并不是 if 语句的一部分:
     // error: missing curly brackets to make a block!
     if (minVal > ivec[i])
          minVal = ivec[i];
          occurs = 1; // executed unconditionally: not part of the if



Written this way, the assignment to occurs will be executed unconditionally. Uncovering this kind of error can be very difficult because the text of the program looks correct.
这样写的话,对 occurs 的赋值将会无条件地执行。这种错误很难发现,因为程序代码看起来是正确的。
Many editors and development environments have tools to automatically indent source code to match its structure. It is a good idea to use such tools if they are available.
很多编辑器和开发环境都是提供工具自动根据语句结构缩排源代码。有效地利用这些工具将是一种很好的编程方法。




6.5.1. The if Statement else Branch
6.5.1. if 语句的 else 分支
Our next task is to put these if statements together into an execution sequence. The order of the if statements is significant. If we use the following order
紧接着,我们要考虑如何将那些 if 语句放在一起形成一个执行语句序列。这些 if 语句的排列顺序非常重要。如果采用下面的顺序:
     if (minVal > ivec[i]) {
          minVal = ivec[i];
          occurs = 1;
     }
     // potential error if minVal has just been set to ivec[i]
     if (minVal == ivec[i])
          ++occurs;



our count will always be off by 1. This code double-counts the first occurrence of the minimum.
那么计数器将永远得不到 1。这段代码只是对第一次出现的最小值重复计数。
Not only is the execution of both if statements on the same value potentially dangerous, it is also unnecessary. The same element cannot be both less than minVal and equal to it. If one condition is true, the other condition can be safely ignored. The if statement allows for this kind of either-or condition by providing an else clause.
这样两个 if 语句不但在值相同时执行起来有潜在的危险,而且还是没必要的。同一个元素不可能既小于 minVal 又等于它。如果其中一个条件是真的,那么另一个条件就可以安全地忽略掉。if 语句为这种只能二选一的条件提供了 else 子句。
The syntactic form of the if else statement is
if else 语句的语法形式为:
     if (condition)
          statement1
     else
          statement2



If condition is true, then statement1 is executed; otherwise, statement2 is executed:
如果 condition 为真,则执行 statement1;否则,执行 statement2:
     if (minVal == ivec[i])
          ++occurs;
     else if (minVal > ivec[i]) {
              minVal = ivec[i];
              occurs = 1;
     }



It is worth noting that statement2 can be any statement or a block of statements enclosed in curly braces. In this example, statement2 is itself an if statement.
值得注意的是,statement2 既可以是任意语句,也可以是用花括号起来的块语句。在这个例子里,statement2 本身是一个 if 语句。
Dangling else
悬垂 else
There is one important complexity in using if statements that we have not yet covered. Notice that neither if directly handles the case where the current element is greater than minVal. Logically, ignoring these elements is finethere is nothing to do if the element is greater than the minimum we've found so far. However, it is often the case that an if needs to do something on all three cases: Unique steps may be required if one value is greater than, less than, or equal to some other value. We've rewritten our loop to explicitly handle all three cases:
对于 if 语句的使用,还有一个重要的复杂问题没有考虑。上述例子中,注意到没有一个 if 分支能直接处理元素值大于 minVal 的情况。从逻辑上来说,可以忽略这些元素——如果该元素比当前已找到的最小值大,那就应该没什么要做的。然而,通常需要使用 if 语句为三种不同情况提供执行的内容,即如果一个值大于、小于或等于其他值时,可能都需要执行特定的步骤。为此重写循环部分,显式地处理这三种情况:
     // note: indented to make clear how the else branches align with the corresponding if
     if (minVal < ivec[i])
         { }                       // empty block
     else if (minVal == ivec[i])
             ++occurs;
     else {                        // minVal > ivec[i]
         minVal = ivec[i];
         occurs = 1;
     }



This three-way test handles each case correctly. However, a simple rewrite that collapses the first two tests into a single, nested if runs into problems:
上述的三路测试精确地控制了所有的情况。然而,简单地把前两个情况用一个嵌套 if 语句实现将会产生问题:
     // oops: incorrect rewrite: This code won't work!
     if (minVal <= ivec[i])
          if (minVal == ivec[i])
               ++occurs;
     else {      // this else goes with the inner if, not the outer one!
          minVal = ivec[i];
          occurs = 1;
     }



This version illustrates a source of potential ambiguity common to if statements in all languages. The problem, usually referred to as the dangling-else problem, occurs when a statement contains more if clauses than else clauses. The question then arises: To which if does each else clause belong?
这个版本表明了所有语言的 if 语句都普通存在着潜在的二义性。这种情况往往称为悬垂 else 问题,产生于一个语句包含的 if 子句多于 else 子句时:对于每一个 else,究竟它们归属哪个 if 语句?




The indentation in our code indicates the expectation that the else should match up with the outer if clause. In C++, however, the dangling-else ambiguity is resolved by matching the else with the last occurring unmatched if. In this case, the actual evaluation of the if else statement is as follows:
在上述的代码中,缩进的用法表明 else 应该与外层的 if 子句匹配。然而,C++ 中悬垂 else 问题带来的二义性,通过将 else 匹配给最后出现的尚未匹配的 if 子句来解决。在这个情况下,这个 if else 语句实际上等价于下面的程序:
     // oops: still wrong, but now the indentation matches execution path
     if (minVal <= ivec[i])
         // indented to match handling of dangling-else
         if (minVal == ivec[i])
              ++occurs;
         else {
             minVal = ivec[i];
             occurs = 1;
         }



We can force an else to match an outer if by enclosing the inner if in a compound statement:
可以通过用花括号将内层的 if 语句括起来成为复合语句,从而迫使这个 else 子句与外层的 if 匹配。
     if (minVal <= ivec[i]) {
         if (minVal == ivec[i])
               ++occurs;
     } else {
         minVal = ivec[i];
         occurs = 1;
     }



Some coding styles recommend always using braces after any if. Doing so avoids any possible confusion and error in later modifications of the code. At a minimum, it is nearly always a good idea to use braces after an if (or while) when the statement in the body is anything other than a simple expression statement, such as an assignment or output expression.
有些编程风格建议总是在 if 后面使用花括号。这样做可以避免日后修改代码时产生混乱和错误。至少,无论 if(或者 while)后面是简单语句,例如赋值和输出语句,还是其他任意语句,使用花括号都是一个比较好的做法。



Exercises Section 6.5.1
Exercise 6.5:Correct each of the following:
改正下列代码:
     (a) if (ival1 != ival2)
              ival1 = ival2
         else ival1 = ival2 = 0;

     (b) if (ival < minval)
              minval = ival;  // remember new minimum
              occurs = 1;     // reset occurrence counter

     (c) if (int ival = get_value())
              cout << "ival = " << ival << endl;
         if (!ival)
              cout << "ival = 0\n";

     (d) if (ival = 0)
              ival = get_value();



Exercise 6.6:What is a "dangling else"? How are else clauses resolved in C++?
什么是“悬垂 else”?C++ 是如何匹配 else 子句的?


 
      

6.6. The switch Statement
6.6. switch 语句
Deeply nested if else statements can often be correct syntactically and yet not correctly reflect the programmer's logic. For example, mistaken else if matchings are more likely to pass unnoticed. Adding a new condition and associated logic or making other changes to the statements is also hard to get right. A switch statement provides a more convenient way to write deeply nested if/else logic.
深层嵌套的 if else 语句往往在语法上是正确的,但逻辑上去却没有正确地反映程序员的意图。例如,错误的 else if 匹配很容易被忽略。添加新的条件和逻辑关系,或者对语句做其他修改,都很难保证正确。switch 语句提供了一种更方便的方法来实现深层嵌套的 if/else 逻辑。
Suppose that we have been asked to count how often each of the five vowels appears in some segment of text. Our program logic is as follows:
假设要统计五个元音在文本里分别出现的次数,程序逻辑结构如下:
Read each character until there are no more characters to read
按顺序读入每个字符直到读入完成为止。
Compare each character to the set of vowels
把每个字符与元音字符集做比较。
If the character matches one of the vowels, add 1 to that vowel's count
如果该字符与某个元音匹配,则该元音的计数器加 1。
Display the results
显示结果。
The program was used to analyze this chapter. Here is the output:
使用此程序分析本章的英文版,得到以下输出结果:
     Number of vowel a: 3499
     Number of vowel e: 7132
     Number of vowel i: 3577
     Number of vowel o: 3530
     Number of vowel u: 1185



6.6.1. Using a switch
6.6.1. 使用 switch
We can solve our problem most directly using a switch statement:
直接使用 switch 语句解决上述问题:
     char ch;
     // initialize counters for each vowel
     int aCnt = 0, eCnt = 0, iCnt = 0,
         oCnt = 0, uCnt = 0;
     while (cin >> ch) {
         // if ch is a vowel, increment the appropriate counter
         switch (ch) {
             case 'a':
                 ++aCnt;
                 break;
             case 'e':
                 ++eCnt;
                 break;
             case 'i':
                 ++iCnt;
                 break;
             case 'o':
                 ++oCnt;
                 break;
             case 'u':
                 ++uCnt;
                 break;
         }
     }
     // print results
     cout  << "Number of vowel a: \t" << aCnt << '\n'
           << "Number of vowel e: \t" << eCnt << '\n'
           << "Number of vowel i: \t" << iCnt << '\n'
           << "Number of vowel o: \t" << oCnt << '\n'
           << "Number of vowel u: \t" << uCnt << endl;



A switch statement executes by evaluating the parenthesized expression that follows the keyword switch. That expression must yield an integral result. The result of the expression is compared with the value associated with each case. The case keyword and its associated value together are known as the case label. Each case label's value must be a constant expression (Section 2.7, p. 62). There is also a special case label, the default label, which we cover on page 203.
通过对圆括号内表达式的值与其后列出的关键字做比较,实现 switch 语句的功能。表达式必须产生一个整数结果,其值与每个 case 的值比较。关键字 case 和它所关联的值称为 case 标号。每个 case 标号的值都必须是一个常量表达式(第 2.7 节)。除此之外,还有一个特殊的 case 标号——default 标号,我们将在第 6.6.3 节介绍它。
If the expression matches the value of a case label, execution begins with the first statement following that label. Execution continues normally from that statement through the end of the switch or until a break statement. If no match is found, (and if there is no default label), execution falls through to the first statement following the switch. In this program, the switch is the only statement in the body of a while. Here, falling through the switch returns control to the while condition.
如果表达式与其中一个 case 标号的值匹配,则程序将从该标号后面的第一个语句开始依次执行各个语句,直到 switch 结束或遇到 break 语句为止。如果没有发现匹配的 case 标号(并且也没有 default 标号),则程序从 switch 语句后面的第一条继续执行。在这个程序中,switch 语句是 while 循环体中唯一的语句,于是,switch 语句匹配失败后,将控制流返回给 while 循环条件。
We'll look at break statements in Section 6.10 (p. 212). Briefly, a break statement interrupts the current control flow. In this case, the break transfers control out of the switch. Execution continues at the first statement following the switch. In this example, as we already know, transferring control to the statement following the switch returns control to the while.
第 6.10 节将讨论 break 语句。简单地说,break 语句中断当前的控制流。对于 switch 的应用,break 语句将控制跳出 switch,继续执行 switch 语句后面的第一个语句。在这个例子中,正如大家所知道的,将会把控制转移到 switch 后面的下一语句,即交回给 while。
6.6.2. Control Flow within a switch
6.6.2. switch 中的控制流
It is essential to understand that execution flows across case labels.
了解 case 标号的执行流是必要的。
It is a common misunderstanding to expect that only the statements associated with the matched case label are executed. However, execution continues across case boundaries until the end of the switch statement or a break is encountered.
存在一个普遍的误解:以为程序只会执行匹配的 case 标号相关联的语句。实际上,程序从该点开始执行,并跨越 case 边界继续执行其他语句,直到 switch 结束或遇到 break 语句为止。




Sometimes this behavior is indeed correct. We want to execute the code for a particular label as well as the code for following labels. More often, we want to execute only the code particular to a given label. To avoid executing code for subsequent cases, the programmer must explicitly tell the compiler to stop execution by specifying a break statement. Under most conditions, the last statement before the next case label is break. For example, here is an incorrect implementation of our vowel-counting switch statement:
有时候,这种行为的确是正确的。程序员也许希望执行完某个特定标号的代码后,接着执行后续标号关联的语句。但更常见的是,我们只需要执行某个特定标号对应的代码。为了避免继续执行其后续 case 标号的内容,程序员必须利用 break 语句清楚地告诉编译器停止执行 switch 中的语句。大多数情况下,在下一个 case 标号之前的最后一条语句是 break。例如,下面统计元音出现次数的 switch 语句是不正确的:
     // warning: deliberately incorrect!
     switch (ch) {
          case 'a':
               ++aCnt;   // oops: should have a break statement
          case 'e':
               ++eCnt;   // oops: should have a break statement
          case 'i':
               ++iCnt;   // oops: should have a break statement
          case 'o':
               ++oCnt;   // oops: should have a break statement
          case 'u':
               ++uCnt;   // oops: should have a break statement
     }



To understand what happens, we'll trace through this version assuming that value of ch is 'i'. Execution begins following case 'i'thus incrementing iCnt. Execution does not stop there but continues across the case labels incrementing oCnt and uCnt as well. If ch had been 'e', then eCnt, iCnt, oCnt, and uCnt would all be incremented.
为了搞清楚该程序导致了什么结果,假设 ch 的值是 'i' 来跟踪这个版本的代码。程序从 case 'i' 后面的语句开始执行,iCnt 的值加 1。但是,程序的执行并没有在这里停止,而是越过 case 标号继续执行,同时将 oCnt 和 uCnt 的值都加了 1.如果 ch 是 'e' 的话,那么 eCnt、iCnt、oCnt 以及 uCnt 的值都会加 1。
Forgetting to provide a break is a common source of bugs in switch statements.
对于 switch 结构,漏写 break 语句是常见的程序错误。




Although it is not strictly necessary to specify a break statement after the last label of a switch, the safest course is to provide a break after every label, even the last. If an additional case label is added later, then the break is already in place.
尽管没有严格要求在 switch 结构的最后一个标号之后指定 break 语句,但是,为了安全起见,最好在每个标号后面提供一个 break 语句,即使是最后一个标号也一样。如果以后在 switch 结构的末尾又需要添加一个新的 case 标号,则不用再在前面加 break 语句了。




break Statements Aren't Always Appropriate
慎用 break 语句,它并不总是恰当的
There is one common situation where the programmer might wish to omit a break statement from a case label, allowing the program to fall through multiple case labels. That happens when two or more values are to be handled by the same sequence of actions. Only a single value can be associated with a case label. To indicate a range, therefore, we typically stack case labels following one another. For example, if we wished only to count vowels seen rather than count the individual vowels, we might write the following:
有这么一种常见的情况,程序员希望在 case 标号后省略 break 语句,允许程序向下执行多个 case 标号。这时,两个或多个 case 值由相同的动作序列来处理。由于系统限定一个 case 标号只能与一个值相关联,于是为了指定一个范围,典型的做法是,把 case 标号依次排列。例如,如果只希望计算文本中元音的总数,而不是每一个元音的个数,则可以这样写:
     int vowelCnt = 0;
     // ...
     switch (ch)
     {
         // any occurrence of a,e,i,o,u increments vowelCnt
         case 'a':
         case 'e':
         case 'i':
         case 'o':
         case 'u':
             ++vowelCnt;
             break;
     }



Case labels need not appear on a new line. We could emphasize that the cases represent a range of values by listing them all on a single line:
每个 case 标号不一定要另起一行。为了强调这些 case 标号表示的是一个要匹配的范围,可以将它们全部在一行中列出:
     switch (ch)
     {
         // alternative legal syntax
         case 'a': case 'e': case 'i': case 'o': case 'u':
             ++vowelCnt;
             break;
     }



Less frequently, we deliberately omit a break because we want to execute code for one case and then continue into the next case, executing that code as well.
比较少见的用法是,为了执行某个 case 的代码后继续执行下一个 case 的代码,故意省略 break 语句。
Deliberately omitting a break at the end of a case happens rarely enough that a comment explaining the logic should be provided.
故意省略 case 后面的 break 语句是很罕见的,因此应该提供一些注释说明其逻辑。




6.6.3. The default Label
6.6.3. default 标号
The default label provides the equivalent of an else clause. If no case label matches the value of the switch expression and there is a default label, then the statements following the default are executed. For example, we might add a counter to track how many nonvowels we read. We'll increment this counter, which we'll name otherCnt, in the default case:
default 标号提供了相当于 else 子句的功能。如果所有的 case 标号与 switch 表达式的值都不匹配,并且 default 标号存在,则执行 default 标号后面的语句。例如,在上述例子中添加一个计数器 otherCnt 统计读入多少个辅音字母,为 switch 结构增加 default 标号,其标志的分支实现 otherCnt 的自增:
         // if ch is a vowel, increment the appropriate counter
         switch (ch) {
             case 'a':
                 ++aCnt;
                 break;
             // remaining vowel cases as before
             default:
                 ++otherCnt;
                 break;
         }
     }



In this version, if ch is not a vowel, execution will fall through to the default label, and we'll increment otherCnt.
在这个版本的代码中,如果 ch 不是元音,程序流程将执行 default 标号的相关语句,使 otherCnt 的值加 1。
It can be useful always to define a default label even if there is no processing to be done in the default case. Defining the label indicates to subsequent readers that the case was considered but that there is no work to be done.
哪怕没有语句要在 default 标号下执行,定义 default 标号仍然是有用的。定义 default 标号是为了告诉它的读者,表明这种情况已经考虑到了,只是没什么要执行的。




A label may not stand alone; it must precede a statement. If a switch ends with the default case in which there is no work to be done, then the default label must be followed by a null statement.
一个标号不能独立存在,它必须位于语句之前。如果 switch 结构以 default 标号结束,而且 default 分支不需要完成任何任务,那么该标号后面必须有一个空语句。
6.6.4. switch Expression and Case Labels
6.6.4. switch 表达式与 case 标号
The expression evaluated by a switch can be arbitrarily complex. In particular, the expression can define and intialize a variable:
switch 求解的表达式可以非常复杂。特别是,该表达式也可以定义和初始化一个变量:
     switch(int ival = get_response())



In this case, ival is initialized, and the value of ival is compared with each case label. The variable ival exists throughout the entire switch statement but not outside it.
在这个例子中,ival 被初始化为 get_response 函数的调用结果,其值将要与每个 case 标号作比较。变量 ival 始终存在于整个 switch 语句中,在 switch 结构外面该变量就不再有效了。
Case labels must be constant integral expressions (Section 2.7, p. 62). For example, the following labels result in compile-time errors:
case 标号必须是整型常量表达式(第 2.7 节)。例如,下面的标号将导致编译时的错误:
     // illegal case label values
     case 3.14:  // noninteger
     case ival:  // nonconstant



It is also an error for any two case labels to have the same value.
如果两个 case 标号具有相同的值,同样也会导致编译时的错误。
6.6.5. Variable Definitions inside a switch
6.6.5. switch 内部的变量定义
Variables can be defined following only the last case or default label:
对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量:
     case true:
          // error: declaration precedes a case label
          string file_name = get_file_name();
          break;
     case false:
          // ...



The reason for this rule is to prevent code that might jump over the definition and initialization of a variable.
制定这个规则是为避免出现代码跳过变量的定义和初始化的情况。
Recall that a variable can be used from its point of definition until the end of the block in which it is defined. Now, consider what would happen if we could define a variable between two case labels. That variable would continue to exist until the end of the enclosing block. It could be used by code in case labels following the one in which it was defined. If the switch begins executing in one of these subsequent case labels, then the variable might be used even though it had not been defined.
回顾变量的作用域,变量从它的定义点开始有效,直到它所在块结束为止。现在考虑如果在两个 case 标号之间定义变量会出现什么情况。该变量会在块结束之前一直存在。对于定义该变量的标号后面的其他 case 标号,它们所关联的代码都可以使用这个变量。如果 switch 从那些后续 case 标号开始执行,那么这个变量可能还未定义就要使用了。
If we need to define a variable for a particular case, we can do so by defining the variable inside a block, thereby ensuring that the variable can be used only where it is guaranteed to have been defined and initialized:
在这种情况下,如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
     case true:
         {
             // ok: declaration statement within a statement block
             string file_name = get_file_name();
             // ...
         }
       break;
         case false:
             // ...


 
Exercises Section 6.6.5
Exercise 6.7:There is one problem with our vowel-counting program as we've implemented it: It doesn't count capital letters as vowels. Write a program that counts both lower- and uppercase letters as the appropriate vowelthat is, your program should count both 'a' and 'A' as part of aCnt, and so forth.
前面已实现的统计元音的程序存在一个问题:不能统计大写的元音字母。编写程序统计大小写的元音,也就是说,你的程序计算出来的 aCnt,既包括 'a' 也包括 'A' 出现的次数,其他四个元音也一样。
Exercise 6.8:Modify our vowel-count program so that it also counts the number of blank spaces, tabs, and newlines read.
修改元音统计程序使其可统计出读入的空格、制表符和换行符的个数。
Exercise 6.9:Modify our vowel-count program so that it counts the number of occurrences of the following two-character sequences: ff, fl, and fi.
修改元音统计程序使其可统计以下双字符序列出现的次数:ff、fl 以及 fi。
Exercise 6.10:Each of the programs in the highlighted text on page 206 contains a common programming error. Identify and correct each error.
下面每段代码都暴露了一个常见编程错误。请指出并修改之。
Code for Exercises in Section 6.6.5
     (a) switch (ival) {
             case 'a': aCnt++;
             case 'e': eCnt++;
             default: iouCnt++;
         }

     (b) switch (ival) {
             case 1:
                 int ix = get_value();
                 ivec[ ix ] = ival;
                 break;
             default:
                 ix = ivec.size()-1;
                 ivec[ ix ] = ival;
         }

     (c) switch (ival) {
             case 1, 3, 5, 7, 9:
                 oddcnt++;
                 break;
             case 2, 4, 6, 8, 10:
                 evencnt++;
                 break;
         }

     (d) int ival=512 jval=1024, kval=4096;
         int bufsize;
         // ...
         switch(swt) {
             case ival:
                 bufsize = ival * sizeof(int);
                 break;
             case jval:
                 bufsize = jval * sizeof(int);
                 break;
             case kval:
                 bufsize = kval * sizeof(int);
                 break;
         }







      

6.7. The while Statement
6.7. while 语句
A while statement repeatedly executes a target statement as long as a condition is true. Its syntactic form is
当条件为真时,while 语句反复执行目标语句。它的语法形式如下:
     while (condition)
              statement



The statement (which is often a block) is executed as long as the condition evaluates as true. The condition may not be empty. If the first evaluation of condition yields false, statement is not executed.
只要条件 condition 的值为 true,执行语句 statement(通常是一个块语句)。condition 不能为空。如果第一次求解 condition 就产生 false 值,则不执行 statement。
The condition can be an expression or an initialized variable definition:
循环条件 condition 可以是一个表达式,或者是提供初始化的变量定义。
     bool quit = false;
     while (!quit) {                  // expression as condition
         quit = do_something();
     }
     while (int loc = search(name)) { // initialized variable as condition
             // do something
     }



Any variable defined in the condition is visible only within the block associated with the while. On each trip through the loop, the initialized value is converted to bool (Section 5.12.3, p. 182). If the value evaluates as true, the while body is executed. Ordinarily, the condition itself or the loop body must do something to change the value of the expression. Otherwise, the loop might never terminate.
在循环条件中定义的任意变量都只在与 while 关联的块语句中可见。每一次循环都将该变量的初值转换为 bool(第 5.12.3 节)。如果求得的值为 true,则执行 while 的循环体。通常,循环条件自身或者在循环体内必须做一些相关操作来改变循环条件表达式的值。否则,循环可能永远不会结束。
Variables defined in the condition are created and destroyed on each trip through the loop.
在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程。




Using a while Loop
while 循环的使用
We have already seen a number of while loops, but for completeness, here is an example that copies the contents of one array into another:
前面的章节已经用过很多 while 循环,但为更完整地了解该结构,考虑下面将一个数组的内容复制到另一个数组的例子:
     // arr1 is an array of ints
     int *source = arr1;
     size_t sz = sizeof(arr1)/sizeof(*arr1); // number of elements
     int *dest = new int[sz];                // uninitialized elements
     while (source != arr1 + sz)
         *dest++ = *source++; //  copy element and increment pointers



We start by initializing source and dest to point to the first element of their respective arrays. The condition in the while tests whether we've reached the end of the array from which we are copying. If not, we execute the body of the loop. The body contains only a single statement, which copies the element and increments both pointers so that they point to the next element in their corresponding arrays.
首先初始化 source 和 dest,并使它们各自指向所关联的数组的第一个元素。while 循环条件判断是否已经到达要复制的数组的末尾。如果没有,继续执行循环。循环体只有单个语句,实现元素的复制,并对两个指针做自增操作,使它们指向对应数组的下一个元素。
As we saw in the "Advice" box on page 164, C++ programmers tend to write terse expressions. The statement in the body of the while
正如 第 5.5. 节提出的关于“简洁即是美”的建议,C++ 程序员应尝试编写简洁的表达式。while 循环体中的语句:
     *dest++ = *source++;



is a classic example. This expression is equivalent to
是一个经典的例子。这个表达式等价于:
     {
         *dest = *source; // copy element
         ++dest;  // increment the pointers
         ++source;
     }



The assignment in the while loop represents a very common usage. Because such code is widespread, it is important to study this expression until its meaning is immediately clear.
while 循环内的赋值操作是一种常见的用法。因为这类代码广为流传,所以学习这种表达式非常重要,要一眼就能看出其含义来。



Exercises Section 6.7
Exercise 6.11:Explain each of the following loops. Correct any problems you detect.
解释下面的循环,更正你发现的问题。
     (a) string bufString, word;
         while (cin >> bufString >> word) { /* ... */ }

     (b) while (vector<int>::iterator iter != ivec.end())
         {/*... */ }

     (c) while (ptr = 0)
             ptr = find_a_value();

     (d) while (bool status = find(word))
         { word = get_next_word(); }
         if (!status)
              cout << "Did not find any words\n";



Exercise 6.12:Write a small program to read a sequence of strings from standard input looking for duplicated words. The program should find places in the input where one word is followed immediately by itself. Keep track of the largest number of times a single repetition occurs and which word is repeated. Print the maximum number of duplicates, or else print a message saying that no word was repeated. For example, if the input is
编写一个小程序,从标准输入读入一系列 string 对象,寻找连续重复出现的单词。程序应该找出满足以下条件的单词的输入位置:该单词的后面紧跟着再次出现自己本身。跟踪重复次数最多的单词及其重复次数。输出重复次数的最大值,若没有单词重复则输出说明信息。例如,如果输入是:
     how, now now now brown cow cow



the output should indicate that the word "now" occurred three times.
则输出应表明“now”这个单词出现了三次。
Exercise 6.13:Explain in detail how the statement in the while loop is executed:
详细解释下面 while 循环中的语句是如何执行的:
     *dest++ = *source++;





      

6.8. The for Loop Statement
6.8. for 循环语句
The syntactic form of a for statement is
for 语句的语法形式是:
     for (init-statement condition; expression)
           statement



The init-statement must be a declaration statement, an expression statement, or a null statement. Each of these statements is terminated by a semicolon, so the syntactic form can also be thought of as
init-statement 必须是声明语句、表达式语句或空语句。这些语句都以分号结束,因此其语法形式也可以看成:
     for (initializer; condition; expression)
           statement



although technically speaking, the semicolon after the initializer is part of the statement that begins the for header.
当然,从技术上说,在 initializer 后面的分号是 for 语句头的一部分。
In general, the init-statement is used to initialize or assign a starting value that is modified over the course of the loop. The condition serves as the loop control. As long as condition evaluates as true, statement is executed. If the first evaluation of condition evaluates to false, statement is not executed. The expression usually is used to modify the variable(s) initialized in init-statement and tested in condition. It is evaluated after each iteration of the loop. If condition evaluates to false on the first iteration, expression is never executed. As usual, statement can be either a single or a compound statement.
一般来说,init-statement 用于对每次循环过程中都要修改的变量进行初始化,或者赋给一个起始值。而 condition 则是用来控制循环的。当 condition 为 true 时,循环执行 statement。如果第一次求解 condition 就得 false 值,则不执行 statement。expression 通常用于修改在 init-statement 中初始化并在 condition 中检查的变量。它在每次循环迭代后都要求解。如果第一次求解 condition 就得 false 值,则始终不执行 expression。通常,statement 既可以是单个语句也可以是复合语句。
Using a for Loop
for 循环的使用
Given the following for loop, which prints the contents of a vector,
假设有下面的 for 循环,用于输出一个 vector 对象的内容:
     for (vector<string>::size_type ind = 0;
                   ind != svec.size(); ++ind) {
         cout << svec[ind]; // print current element
         // if not the last element, print a space to separate from the next one
         if (ind + 1 != svec.size())
            cout << " ";
     }



the order of evaluation is as follows:
它的计算顺序如下:
1. The init-statement is executed once at the start of the loop. In this example, ind is defined and initialized to zero.

循环开始时,执行一次 init-statement。在这个例子中,定义了 ind,并将它初始化为 0。

2. Next, condition is evaluated. If ind is not equal to svec.size(), then the for body is executed. Otherwise, the loop terminates. If the condition is false on the first trip, then the for body is not executed.

接着,求解 condition。如果 ind 不等于 svec.size(),则执行 for 循环体。否则,循环结束。如果在第一次循环时,条件就为 flase,则不执行 for 循环体。

3. If the condition is true, the for body executes. In this case, the for body prints the current element and then tests whether this element is the last one. If not, it prints a space to separate it from the next element.

如果条件为 true,则执行 for 循环体。本例中,for 循环体输出当前元素值,并检验这个元素是否是最后一个。如果不是,则输出一个空格,用于分隔当前元素和下一个元素。

4. Finally, expression is evaluated. In this example, ind is incremented by 1.

最后,求解 expression。本例中,ind 自增 1。


These four steps represent the first iteration of the for loop. Step 2 is now repeated, followed by steps 3 and 4, until the condition evaluates to falsethat is, when ind is equal to svec.size().
这四步描述了 for 循环的第一次完整迭代。接着重复第 2 步,然后是和 3、4 步,直到 condition 的值为 false,即 ind 等于 svec.size() 为止。
It is worth remembering that the visibility of any object defined within the for header is limited to the body of the for loop. Thus, in this example, ind is inaccessible after the for completes.
应该谨记:在 for 语句头定义的任何对象只限制在 for 循环体里可见。因此,对本例而言,在执行完 for 语句后,ind 不再有效(即不可访问)。




6.8.1. Omitting Parts of the for Header
6.8.1. 省略 for 语句头的某些部分
A for header can omit any (or all) of init-statement, condition, or expression.
for 语句头中,可以省略 init-statement、condition 或者 expression(表达式)中的任何一个(或全部)。
The init-statement is omitted if an initialization is unnecessary or occurs elsewhere. For example, if we rewrote the program to print the contents of a vector using iterators instead of subscripts, we might, for readability reasons, move the initialization outside the loop:
如果不需要初始化或者初始化已经在别处实现了,则可以省略 init-statement。例如,使用迭代器代替下标重写输出 vector 对象内容的程序,为了提高易读性,可将初始化移到循环外面:
     vector<string>::iterator iter = svec.begin();
     for( /* null */ ; iter != svec.end(); ++iter) {
         cout << *iter; // print current element
         // if not the last element, print a space to separate from the next one
         if (iter+1 != svec.end())
             cout << " ";
     }



Note that the semicolon is necessary to indicate the absence of the init-statement more precisely, the semicolon represents a null init-statement.
注意此时必须要有一个分号表明活力了 init-statement——更准确地说,分号代表一个空的 init-statement。
If the condition is omitted, then it is equivalent to having written true as the condition:
省略 condition,则等效于循环条件永远为 true:
     for (int i = 0; /* no condition */ ; ++i)



It is as if the program were written as
相当于程序写为:
     for (int i = 0; true ; ++i)



It is essential that the body of the loop contain a break or return statement. Otherwise the loop will execute until it exhausts the system resources. Similarly, if the expression is omitted, then the loop must exit through a break or return or the loop body must arrange to change the value tested in the condition:
这么一来,循环体内就必须包含一个 break 或者 return 语句。否则,循环会一直执行直到耗尽系统的资源为止。同样地,如果省略 expression,则必须利用 break 或 return 语句跳出循环,或者在循环体内安排语句修改 condition 所检查的变量值。
     for (int i = 0; i != 10; /* no expression */ ) {
        // body must change i or the loop won't terminate
     }



If the body doesn't change the value of i, then i remains 0 and the test will always succeed.
如果循环体不修改 i 的值,则 i 始终为 0,循环条件永远成立。
6.8.2. Multiple Definitions in the for Header
6.8.2. for 语句头中的多个定义
Multiple objects may be defined in the init-statement; however, only one statement may appear, so all the objects must be of the same general type:
可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型:
     const int size = 42;
     int val = 0, ia[size];
     // declare 3 variables local to the for loop:
     // ival is an int, pi a pointer to int, and ri a reference to int
     for (int ival = 0, *pi = ia, &ri = val;
           ival != size;
           ++ival, ++pi, ++ri)
                   // ...


Exercises Section 6.8.2
Exercise 6.14:Explain each of the following loops. Correct any problems you detect.
解释下面每个循环,更正你发现的任何问题。
     (a) for (int *ptr = &ia, ix = 0;
               ix != size && ptr != ia+size;
               ++ix, ++ptr)   { /* ... */ }
     (b) for (; ;) {
               if (some_condition) return;
               // ...
         }
     (c) for (int ix = 0; ix != sz; ++ix) { /* ... */ }
         if (ix != sz)
              // ...
     (d) int ix;
         for (ix != sz; ++ix) { /* ... */ }
     (e) for (int ix = 0; ix != sz; ++ix, ++ sz) { /* ... */ }



Exercise 6.15:The while loop is particularly good at executing while some condition holds; for example, while the end-of-file is not reached, read a next value. The for loop is generally thought of as a step loop: An index steps through a range of values in a collection. Write an idiomatic use of each loop and then rewrite each using the other loop construct. If you were able to program with only one loop, which construct would you choose? Why?
while 循环特别擅长在某个条件保持为真时反复地执行;例如,当未到达文件尾时,一直读取下一个值。一般认为 for 循环是一种按步骤执行的循环;使用下标依次遍历集合中一定范围内的元素。按每种循环的习惯用法编写程序,然后再用另外一种结构重写。如果只能用一种循环来编写程序,你会选择哪种结构?为什么?
Exercise 6.16:Given two vectors of ints, write a program to determine whether one vectors is a prefix of the other. For vectors of unequal length, compare the number of elements of the smaller vector. For example, given the vectors (0,1,1,2) and (0,1,1,2,3,5,8), your program should return true.
给出两个 int 型的 vector 对象,编写程序判断一个对象是否是另一个对象的前缀。如果两个 vector 对象的长度不相同,假设较短的 vector 对象长度为 n,则只对这两个对象的前面 n 个元素做比较。例如,对于 (0, 1, 1, 2) 和 (0, 1, 1, 2, 3, 5, 8) 这两个 vector,你的程序应该返回 true。


 
      

6.9. The do while Statement
6.9. do while 语句
We might want to write a program that interactively performs some calculation for its user. As a simple example, we might want to do sums for the user: Our program prompts the user for a pair of numbers and produces their sum. Having generated one sum, we'd like the program to give the user the option to repeat the process and generate another.
在实际应用中,可能会要求程序员编写一个交互程序,为用户实现某种计算。一个简单的例子是:程序提示用户输入两个数,然后输出读入数之和。在输出和值后,程序可以让用户选择是否重复这个过程计算下一个和。
The body of this program is pretty easy. We'll need to write a prompt, then read a pair of values and print the sum of the values we read. After we print the sum, we'll ask the user whether to continue.
程序的实现相当简单。只需输出一个提示,接着读入两个数,然后输出读入数之和。输出结果后,询问用户是否继续。
The hard part is deciding on a control structure. The problem is that we want to execute the loop until the user asks to exit. In particular, we want to do a sum even on the first iteration. The do while loop does exactly what we need. It guarantees that its body is always executed at least once. The syntactic form is as follows:
关键在于控制结构的选择。问题是要到用户要求退出时,才中止循环的执行。尤其是,在第一次循环时就要求一次和。do while 循环正好满足这样的需要。它保证循环体至少执行一次。
     do
             statement
     while   (condition);



Unlike a while statement, a do-while statement always ends with a semicolon.
与 while 语句不同。do-while 语句总是以分号结束。




The statement in a do is executed before condition is evaluated. The condition cannot be empty. If condition evaluates as false, then the loop terminates; otherwise, the loop is repeated. Using a do while, we can write our program:
在求解 condition 之前,先执行了 do 里面的 statement。condition 不能为空。如果 condition 的值为假,则循环结束,否则循环重复执行。使用 do while 循环,可以编写程序如下:
     // repeatedly ask user for pair of numbers to sum
     string rsp; // used in the condition; can't be defined inside the do
     do {
        cout << "please enter two values: ";
        int val1, val2;
        cin  >> val1 >> val2;
        cout << "The sum of " << val1 << " and " << val2
             << " = " << val1 + val2 << "\n\n"
             << "More? [yes][no] ";
        cin  >> rsp;
     } while (!rsp.empty() && rsp[0] != 'n');



The body of this loop is similar to others we've written and so should be easy to follow. What might be a bit surprising is that we defined rsp before the do rather than defining it inside the loop. Had we defined rsp inside the do, then rsp would go out of scope at the close curly brace before the while. Any variable referenced inside the condition must exist before the do statement itself.
循环体与之前编写的其他循环语句相似,因此很容易理解。奇怪的是此代码把 rsp 定义在 do 之前而不是在循环体内部。如果把 rsp 定义在 do 内部,那么 rsp 的作用域就被限制在 while 前的右花括号之前了。任何在循环条件中引用变量都必须在 do 语句之前就已经存在。
Because the condition is not evaluated until after the statement or block is executed, the do while loop does not allow variable definitions:
因为要到循环语句或者语句块执行之后,才求解循环条件,因此 do while 循环不可以采用如下方式定义变量:
     // error: declaration statement within do condition is not supported
     do {
         // ...
         mumble(foo);
     } while (int foo = get_foo()); // error: declaration in do condition



If we could define variables in the condition, then any use of the variable would happen before the variable was defined!
如果可以在循环条件中定义变量的话,则对变量的任何使用都将发生在变量定义之前!
Exercises Section 6.9
Exercise 6.17:Explain each of the following loops. Correct any problems you detect.
解释下列的循环。更正你发现的问题。
     (a) do
              int v1, v2;
              cout << "Please enter two numbers to sum:" ;
              cin >> v1 >> v2;
              if (cin)
                  cout << "Sum is: "
                       << v1 + v2 << endl;
         while (cin);

     (b) do {
             // ...
         } while (int ival = get_response());

     (c) do {
             int ival = get_response();
             if (ival == some_value())
                  break;
         } while (ival);
          if (!ival)
              // ...



Exercise 6.18:Write a small program that requests two strings from the user and reports which string is lexicographically less than the other (that is, comes before the other alphabetically). Continue to solicit the user until the user requests to quit. Use the string type, the string less-than operator, and a do while loop.
编写一个小程序,由用户输入两个 string 对象,然后报告哪个 string 对象按字母排列次序而言比较小(也就是说,哪个的字典序靠前)。继续要求用户输入,直到用户请求退出为止。请使用 string 类型、string 类型的小于操作符以及 do while 循环实现。


 
      

Chapter 7. Functions
第七章 函数
CONTENTS
Section 7.1 Defining a Function226
Section 7.2 Argument Passing229
Section 7.3 The return Statement245
Section 7.4 Function Declarations251
Section 7.5 Local Objects254
Section 7.6 Inline Functions256
Section 7.7 Class Member Functions258
Section 7.8 Overloaded Functions265
Section 7.9 Pointers to Functions276
Chapter Summary280
Defined Terms280


This chapter describes how to define and declare functions. We'll cover how arguments are passed to and values are returned from a function. We'll then look at three special kinds of functions: inline functions, class member functions, and overloaded functions. The chapter closes with a more advanced topic: function pointers.
本章将介绍函数的定义和声明。其中讨论了如何给函数传递参数以及如何从函数返回值。然后具体分析三类特殊的函数:内联(inline)函数、类成员函数和重载函数。最后以一个更高级的话题“函数指针”来结束全章。
A function can be thought of as a programmer-defined operation. Like the built-in operators, each function performs some computation and (usually) yields a result. Unlike the operators, functions have names and may take an unlimited number of operands. Like operators, functions can be overloaded, meaning that the same name may refer to multiple different functions.
函数可以看作程序员定义的操作。与内置操作符相同的是,每个函数都会实现一系列的计算,然后(大多数时候)生成一个计算结果。但与操作符不同的是,函数有自己的函数名,而且操作数没有数量限制。与操作符一样,函数可以重载,这意味着同样的函数名可以对应多个不同的函数。
      

7.1. Defining a Function
7.1. 函数的定义
A function is uniquely represented by a name and a set of operand types. Its operands, referred to as parameters, are specified in a comma-separated list enclosed in parentheses. The actions that the function performs are specified in a block, referred to as the function body. Every function has an associated return type.
函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都有一个相关联的返回类型。
As an example, we could write the following function to find the greatest common divisor of two ints:
考虑下面的例子,这个函数用来求出两个 int 型数的最大公约数:
     // return the greatest common divisor
     int gcd(int v1, int v2)
     {
         while (v2) {
             int temp = v2;
             v2 = v1 % v2;
             v1 = temp;
         }
         return v1;
     }



Here we define a function named gcd that returns an int and has two int parameters. To call gcd, we must supply two int values and we get an int in return.
这里,定义了一个名为 gcd 的函数,该函数返回一个 int 型值,并带有两个 int 型形参。调用 gcd 函数时,必须提供两个 int 型值传递给函数,然后将得到一个 int 型的返回值。
Calling a Function
函数的调用
To invoke a function we use the call operator, which is a pair of parentheses. As with any operator, the call operator takes operands and yields a result. The operands to the call operator are the name of the function and a (possibly empty) comma-separated list of arguments. The result type of a call is the return type of the called function, and the result itself is the value returned by the function:
C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。正如其他操作符一样,调用操作符需要操作数并产生一个结果。调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参。函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值:
     // get values from standard input
     cout << "Enter two values: \n";
     int i, j;
     cin >> i >> j;
     // call gcd on arguments i and j
     // and print their greatest common divisor
     cout << "gcd: " << gcd(i, j) << endl;



If we gave this program 15 and 123 as input, the output would be 3.
如果给定 15 和 123 作为程序的输入,程序将输出 3。
Calling a function does two things: It initializes the function parameters from the corresponding arguments and transfers control to the function being invoked. Execution of the calling function is suspended and execution of the called function begins. Execution of a function begins with the (implicit) definition and initialization of its parameters. That is, when we invoke gcd, the first thing that happens is that variables of type int named v1 and v2 are created. These variables are initialized with the values passed in the call to gcd. In this case, v1 is initialized by the value of i and v2 by the value of j.
函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用函数。主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。也就是说,当我们调用 gcd 时,第一件事就是创建名为 v1 和 v2 的 int 型变量,并将这两个变量初始化为调用 gcd 时传递的实参值。在上例中,v1 的初值为 i,而 v2 则初始化为 j 的值。
Function Body Is a Scope
函数体是一个作用域
The body of a function is a statement block, which defines the function's operation. As usual, the block is enclosed by a pair of curly braces and hence forms a new scope. As with any block, the body of a function can define variables. Names defined inside a function body are accessible only within the function itself. Such variables are referred to as local variables. They are "local" to that function; their names are visible only in the scope of the function. They exist only while the function is executing. Section 7.5 (p. 254) covers local variables in more detail.
函数体是一个语句块,定义了函数的具体操作。通常,这个块语句包含在一对花括号中,形成了一个新的作用域。和其他的块语句一样,在函数体中可以定义变量。在函数体内定义的变量只在该函数中才可以访问。这种变量称为局部变量,它们相对于定义它们的函数而言是“局部”的,其名字只能在该函数的作用域中可见。这种变量只在函数运行时存在。第 7.5 节将详细讨论局部变量。
Execution completes when a return statement is encountered. When the called function finishes, it yields as its result the value specified in the return statement. After the return is executed, the suspended, calling function resumes execution at the point of the call. It uses the return value as the result of evaluating the call operator and continues processing whatever remains of the statement in which the call was performed.
当执行到 return 语句时,函数调用结束。被调用的函数完成时,将产生一个在 return 语句中指定的结果值。执行 return 语句后,被挂起的主调函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理在执行调用的语句中所剩余的工作。
Parameters and Arguments
形参和实参
Like local variables, the parameters of a function provide named, local storage for use by the function. The difference is that parameters are defined inside the function's parameter list and are initialized by arguments passed to the function when the function is called.
类似于局部变量,函数的形参为函数提供了已命名的局部存储空间。它们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。
An argument is an expression. It might be a variable, a literal constant or an expression involving one or more operators. We must pass exactly the same number of arguments as the function has parameters. The type of each argument must match the corresponding parameter in the same way that the type of an initializer must match the type of the object it initializes: The argument must have the same type or have a type that can be implicitly converted (Section 5.12, p. 178) to the parameter type. We'll cover how arguments match a parameter in detail in Section 7.8.2 (p. 269).
An argument is an expression. It might be a variable, a literal constant or an expression involving one or more operators. We must pass exactly the same number of arguments as the function has parameters. The type of each argument must match the corresponding parameter in the same way that the type of an initializer must match the type of the object it initializes: The argument must have the same type or have a type that can be implicitly converted (Section 5.12, p. 178) to the parameter type. We'll cover how arguments match a parameter in detail in Section 7.8.2 (p. 269).
实参则是一个表达式。它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。在调用函数时,所传递的实参个数必须与函数的形参个数完全相同。与初始化式的类型必须与初始化对象的类型匹配一样,实参的类型也必须与其对应形参的类型完全匹配:实参必须具有与形参类型相同、或者能隐式转换(第 5.12 节)为形参类型的数据类型。本章第 7.8.2 节将详细讨论实参与形参的匹配。
7.1.1. Function Return Type
7.1.1. 函数返回类型
The return type of a function can be a built-in type, such as int or double, a class type, or a compound type, such as int& or string*. A return type also can be void, which means that the function does not return a value. The following are example definitions of possible function return types:
函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如 int& 或 string*),还可以是 void 类型,表示该函数不返回任何值。下面的例子列出了一些可能的函数返回类型:
     bool is_present(int *, int);       // returns bool
     int count(const string &, char);   // returns int
     Date &calendar(const char*);       // returns reference to Date
     void process();                    // process does not return a value



A function may not return another function or a built-in array type. Instead, the function may return a pointer to the function or to a pointer to an element in the array:
函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针:
     // ok: pointer to first element of the array
     int *foo_bar() { /* ... */ }



This function returns a pointer to int and that pointer could point to an element in an array.
这个函数返回一个 int 型指针,该指针可以指向数组中的一个元素。
We'll learn about function pointers in Section 7.9 (p. 276).
第 7.9 节将介绍有关函数指针的内容。
Functions Must Specify a Return Type
函数必须指定返回类型
It is illegal to define or declare a function without an explicit return type:
在定义或声明函数时,没有显式指定返回类型是不合法的:
     // error: missing return type
     test(double v1, double v2) { /* ... */ }



Eariler versions of C++ would accept this program and implicitly define the return type of test as an int. Under Standard C++, this program is an error.
早期的 C++ 版本可以接受这样的程序,将 test 函数的返回类型隐式地定义为 int 型。但在标准 C++ 中,上述程序则是错误的。
In pre-Standard C++, a function without an explicit return type was assumed to return an int. C++ programs compiled under earlier, non-standard compilers may still contain functions that implicitly return int.
在 C++ 标准化之前,如果缺少显式返回类型,函数的返回值将被假定为 int 型。早期未标准化的 C++ 编译器所编译的程序可能依然含有隐式返回 int 型的函数。




7.1.2. Function Parameter List
7.1.2. 函数形参表
The parameter list of a function can be empty but cannot be omitted. A function with no parameters can be written either with an empty parameter list or a parameter list containing the single keyword void. For example, the following declarations of process are equivalent:
函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。例如,下面关于 process 的声明是等价的:
     void process() { /* ... */ }      // implicit void parameter list

     void process(void){ /* ... */ }  // equivalent declaration



A parameter list consists of a comma-separated list of parameter types and (optional) parameter names. Even when the types of two parameters are the same, the type must be repeated:
形参表由一系列用逗号分隔的参数类型和(可选的)参数名组成。如果两个参数具有相同的类型,则其类型必须重复声明:
     int manip(int v1, v2) { /* ... */ }      // error
     int manip(int v1, int v2) { /* ... */ }  // ok



No two parameters can have the same name. Similarly, a variable local to a function may not use the same name as the name of any of the function's parameters.
参数表中不能出现同名的参数。类似地,局部于函数的变量也不能使用与函数的任意参数相同的名字。
Names are optional, but in a function definition, normally all parameters are named. A parameter must be named to be used.
参数名是可选的,但在函数定义中,通常所有参数都要命名。参数必须在命名后才能使用。
Parameter Type-Checking
参数类型检查
C++ is a statically typed language (Section 2.3, p. 44). The arguments of every call are checked during compilation.
C++ 是一种静态强类型语句(第 2.3 节),对于每一次的函数调用,编译时都会检查其实参。




When we call a function, the type of each argument must be either the same type as the corresponding parameter or a type that can be converted (Section 5.12, p. 178) to that type. The function's parameter list provides the compiler with the type information needed to check the arguments. For example, the function gcd, which we defined on page 226, takes two parameters of type int:
调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换(第 5.12 节)为该形参类型的类型。函数的形参表为编译器提供了检查实参需要的类型信息。例如,第 7.1 节定义的 gcd 函数有两个 int 型的形参:
     gcd("hello", "world"); // error: wrong argument types
     gcd(24312);            // error: too few arguments
     gcd(42, 10, 0);        // error: too many arguments



Each of these calls is a compile-time error. In the first call, the arguments are of type const char*. There is no conversion from const char* to int, so the call is illegal. In the second and third calls, gcd is passed the wrong number of arguments. The function must be called with two arguments; it is an error to call it with any other number.
以上所有的调用都会导致编译时的错误。在第一个调用中,实参的类型都是 const char*,这种类型无法转换为 int 型,因此该调用不合法。而第二和第三个调用传递的实参数量有误。在调用该函数时必须提供两个实参,实参数太多或太少都是不合法的。
But what happens if the call supplies two arguments of type double? Is this call legal?
如果两个实参都是 double 类型,又会怎样呢?调用是否合法?
     gcd(3.14, 6.29);      // ok: arguments are converted to int



In C++, the answer is yes; the call is legal. In Section 5.12.1 (p. 179) we saw that a value of type double can be converted to a value of type int. This call involves such a conversionwe want to use double values to initialize int objects. Therefore, flagging the call as an error would be too severe. Rather, the arguments are implicitly converted to int (tHRough truncation). Because this conversion might lose precision, most compilers will issue a warning. In this case, the call becomes
在 C++中,答案是肯定的:该调用合法!正如第 5.12.1 节所示,double 型的值可以转换为 int 型的值。本例中的函数调用正涉及了这种转换——用 double 型的值来初始化 int 型对象。因此,把该调用标记为不合法未免过于严格。更确切地说,(通过截断)double 型实参被隐式地转换为 int 型。由于这样的转换可能会导致精度损失,大多数编译器都会给出警告。对于本例,该调用实际上变为:
     gcd(3, 6);



and returns a value of 3.
返回值是 3。
A call that passes too many arguments, omits an argument, or passes an argument of the wrong type almost certainly would result in serious run-time errors. Catching these so-called interface errors at compile time greatly reduces the compile-debug-test cycle for large programs.
调用函数时传递过多的实参、忽略某个实参或者传递错误类型的实参,几乎肯定会导致严重的运行时错误!对于大程序,在编译时检查出这些所谓的接口错误(interface error),将会大大地缩短“编译-调试-测试”的周期。
Exercises Section 7.1.2
Exercise 7.1:What is the difference between a parameter and an argument?
形参和实参有什么区别?
Exercise 7.2:Indicate which of the following functions are in error and why. Suggest how you might correct the problems.
下列哪些函数是错误的?为什么?请给出修改意见。
     (a) int f() {
             string s;
             // ...
             return s;
         }
     (b) f2(int i) { /* ... */ }
     (c) int calc(int v1, int v1) /* ... */ }
     (d) double square(double x) return x * x;



Exercise 7.3:Write a program to take two int parameters and generate the result of raising the first parameter to the power of the second. Write a program to call your function passing it two ints. Verify the result.
编写一个带有两个 int 型形参的函数,产生第一个参数的第二个参数次幂的值。编写程序传递两个 int 数值调用该函数,请检验其结果。
Exercise 7.4:Write a program to return the absolute value of its parameter.
编写一个函数,返回其形参的绝对值。


      

Chapter Summary
小结
Functions are named units of computation and are essential to structuring even modest programs. They are defined by specifying a return type, a name, a (possibly empty) list of parameters, and a function body. The function body is a block that is executed when the function is called. When a function is called, the arguments passed to the function must be compatible with the types of the corresponding parameters.
函数是有名字的计算单元,对程序(就算是小程序)的结构化至关重要。函数的定义由返回类型、函数名、形参表(可能为空)以及函数体组成。函数体是调用函数时执行的语句块。在调用函数时,传递给函数的实参必须与相应的形参类型兼容。
Passing an argument to a function follows the same rules as initializing a variable. Each parameter that has nonreference type is initialized as a copy of the corresponding argument. Any changes made to a (nonreference) parameter are made to the local copy, not to the argument itself.
给函数传递实参遵循变量初始化的规则。非引用类型的形参以相应实参的副本初始化。对(非引用)形参的任何修改仅作用于局部副本,并不影响实参本身。
Copying large, complex values can be expensive. To avoid the overhead of passing a copy, parameters can be specified as references. Changes made to reference parameters are reflected in the argument itself. A reference parameter that does not need to change its argument should be const reference.
复制庞大而复杂的值有昂贵的开销。为了避免传递副本的开销,可将形参指定为引用类型。对引用形参的任何修改会直接影响实参本身。应将不需要修改相应实参的引用形参定义为 const 引用。
In C++, functions may be overloaded. The same name may be used to define different functions as long as the number or types of the parameters in the functions differ. The compiler automatically figures out which function to call based the arguments in a call. The process of selecting the right function from a set of overloaded functions is referred to as function matching.
在 C++ 中,函数可以重载。只要函数中形参的个数或类型不同,则同一个函数名可用于定义不同的函数。编译器将根据函数调用时的实参确定调用哪一个函数。在重载函数集合中选择适合的函数的过程称为函数匹配。
C++ provides two special kinds of functions: inline and member functions. Specifying inline on a function is a hint to the compiler to expand the function into code directly at the call point. Inline functions avoid the overhead associated with calling a function. Member functions are just that: class members that are functions. This chapter introduced simple member functions. Chapter 12 will cover member functions in more detail.
C++ 提供了两种特殊的函数:内联函数和成员函数。将函数指定为内联是建议编译器在调用点直接把函数代码展开。内联函数避免了调用函数的代价。成员函数则是身为类成员的函数。本章介绍了简单的成员函数,在第十二章将会更详细地介绍成员函数。
      

Defined Terms
术语
ambiguous call(有二义性的调用)
Compile-time error that results when there is not a single best match for a call to an overloaded function.
一种编译错误,当调用重载函数,找不到唯一的最佳匹配时产生。
arguments(实参)
Values supplied when calling a function. These values are used to initialize the corresponding parameters in the same way that variables of the same type are initialized.
调用函数时提供的值。这些值用于初始化相应的形参,其方式类似于初始化同类型变量的方法。
automatic objects(自动对象)
Objects that are local to a function. Automatic objects are created and initialized anew on each call and are destroyed at the end of the block in which they are defined. They no longer exist once the function terminates.
局部于函数的对象。自动对象会在每一次函数调用时重新创建和初始化,并在定义它的函数块结束时撤销。一旦函数执行完毕,这些对象就不再存在了。
best match(最佳匹配)
The single function from a set of overloaded functions that has the best match for the arguments of a given call.
在重载函数集合里找到的与给定调用的实参达到最佳匹配的唯一函数。
call operator(调用操作符)
The operator that causes a function to be executed. The operator is a pair of parentheses and takes two operands: The name of the function to call and a (possibly empty) comma-separated list of arguments to pass to the function.
使函数执行的操作符。该操作符是一对圆括号,并且有两个操作数:被调用函数的名字,以及由逗号分隔的(也可能为空)形参表。
candidate functions(候选函数)
The set of functions that are considered when resolving a function call. The candidate functions are all the functions with the name used in the call for which a declaration is in scope at the time of the call.
在解析函数调用时考虑的函数集合。候选函数包括了所有在该调用发生的作用域中声明的、具有该调用所使用的名字的函数。
const member function(常量成员函数)
Function that is member of a class and that may be called for const objects of that type. const member functions may not change the data members of the object on which they operate.
类的成员函数,并可以由该类类型的常量对象调用。常量成员函数不能修改所操纵的对象的数据成员。
constructor(构造函数)
Member function that has the same name as its class. A constructor says how to initialize objects of its class. Constructors have no return type. Constructors may be overloaded.
与所属类同名的类成员函数。构造函数说明如何初始化本类的对象。构造函数没有返回类型,而且可以重载。
constructor initializer list(构造函数初始化列表)
List used in a constructor to specify initial values for data members. The initializer list appears in the definition of a constructor between the parameter list and the constructor body. The list consists of a colon followed by a comma-separated list of member names, each of which is followed by that member's initial value in parentheses.
在构造函数中用于为数据成员指定初值的表。初始化列表出现在构造函数的定义中,位于构造函数体与形参表之间。该表由冒号和冒号后面的一组用逗号分隔的成员名组成,每一个成员名后面跟着用圆括号括起来的该成员的初值。
default constructor(默认构造函数)
The constructor that is used when no explicit initializer is supplied. The compiler will synthesize a default constructor if the class defines no other constructors.
在没有显式提供初始化式时调用的构造函数。如果类中没有定义任何构造函数,编译器会自动为这个类合成默认构造函数。
function(函数)
A callable unit of computation.
可调用的计算单元。
function body(函数体)
Block that defines the actions of a function.
定义函数动作的语句块。
function matching(函数匹配)
Compiler process by which a call to an overloaded function is resolved. Arguments used in the call are compared to the parameter list of each overloaded function.
确定重载函数调用的编译器过程。调用时使用的实参将与每个重载函数的形参表作比较。
function prototype(函数原型)
Synonym for function declaration. The name, return type, and parameter types of a function. To call a function, its prototype must have been declared before the point of call.
函数声明的同义词。包括了函数的名字、返回类型和形参类型。调用函数时,必须在调用点之前声明函数原型。
inline function(内联函数)
Function that is expanded at the point of call, if possible. Inline functions avoid the normal function-calling overhead by replacing the call by the function's code.
如果可能的话,将在调用点展开的函数。内联函数直接以函数代码替代了函数调用点展开的函数。内联函数直接以函数代码替代了函数调用语句,从而避免了一般函数调用的开销。
local static objects(局部静态对象)
Local object that is created and initialized once before the function is first called and whose value persists across invocations of the function.
在函数第一次调用前就已经创建和初始化的局部对象,其值在函数的调用之间保持有效。
local variables(局部变量)
Variables defined inside a function. Local variables are accessible only within the function body.
在函数内定义的变量,仅能在函数体内访问。
object lifetime(对象生命期)
Every object has an associated lifetime. Objects that are defined inside a block exist from when their definition is encountered until the end of the block in which they are defined. Local static objects and global objects defined outside any function are created during program startup and are destroyed when the main function ends. Dynamically created objects that are created through a new expression exist until the memory in which they were created is freed through a corresponding delete.
每个对象皆有与之关联的生命期。在块中定义的对象从定义时开始存在,直到它的定义所在的语句块结束为止。静态局部对象和函数外定义的全局变量则在程序开始执行时创建,当 main 函数结束时撤销。动态创建的对象由 new 表达式创建,从此开始存在,直到由相应的 delete 表达式释放所占据的内存空间为止。
overload resolution(重载确定)
A synonym for function matching.
函数匹配的同义词。
overloaded function(重载函数)
A function that has the same name as at least one other function. Overloaded functions must differ in the number or type of their parameters.
和至少一个其他函数同名的函数。重载函数必须在形参的个数或类型上有所不同。
parameters(形参)
Variables local to a function whose initial values are supplied when the function is called.
函数的局部变量,其初值由函数调用提供。
recursive function(递归函数)
Function that calls itself directly or indirectly.
直接或间接调用自己的函数。
return type(返回类型)
The type of the value returned from a function.
函数返回值的类型。
synthesized default constructor(合成默认构造函数)
If there are no constructors defined by a class, then the compiler will create (synthesize) a default constructor. This constructor default initializes each data member of the class.
如果类没有定义任何构造函数,则编译器会为这个类创建(合成)一个默认构造函数。该函数以默认的方式初始化类中的所有数据成员。
temporary object(临时对象)
Unnamed object automatically created by the compiler in the course of evaluating an expression. The phrase temporary object is usually abreviated as temporary. A temporary persists until the end of the largest expression that encloses the expression for which it was created.
在求解表达式的过程中由编译器自动创建的没有名字的对象。“临时对象”这个术语通常简称为“临时”。临时对象一直存在直到最大表达式结束为止,最大表达式指的是包含创建该临时对象的表达式的最大范围内的表达式。
this pointer(this 指针)
Implicit parameter of a member function. this points to the object on which the function is invoked. It is a pointer to the class type. In a const member function the pointer is a pointer to const.
成员函数的隐式形参。this 指针指向调用该函数的对象,是指向类类型的指针。在 const 成员函数中,该指针也指向 const 对象。
viable functions(可行函数)
The subset of overloaded functions that could match a given call. Viable functions have the same number of parameters as arguments to the call and each argument type can potentially be converted to the corresponding parameter type.
重载函数中可与指定的函数调用匹配的子集。可行函数的形参个数必须与该函数调用的实参个数相同,而且每个实参类型都可潜在地转换为相应形参的类型。
 
      

7.2. Argument Passing
7.2. 参数传递
Each parameter is created anew on each call to the function. The value used to initialize a parameter is the corresponding argument passed in the call.
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
Parameters are initialized the same way that variables are. If the parameter has a nonreference type, then the argument is copied. If the parameter is a reference (Section 2.5, p. 58), then the parameter is just another name for the argument.
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型(第 2.5 节),则它只是实参的别名。




7.2.1. Nonreference Parameters
7.2.1. 非引用形参
Parameters that are plain, nonreference types are initialized by copying the corresponding argument. When a parameter is initialized with a copy, the function has no access to the actual arguments of the call. It cannot change the arguments. Let's look again at the definition of gcd:
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。下面再次观察 gcd 这个函数的定义:
     // return the greatest common divisor
     int gcd(int v1, int v2)
     {
         while (v2) {
             int temp = v2;
             v2 = v1 % v2;
             v1 = temp;
         }
         return v1;
     }



Inside the body of the while, we change the values of both v1 and v2. However, these changes are made to the local parameters and are not reflected in the arguments used to call gcd. Thus, when we call
while 循环体虽然修改了 v1 与 v2 的值,但这些变化仅限于局部参数,而对调用 gcd 函数使用的实参没有任何影响。于是,如果有函数调用
     gcd(i, j)



the values i and j are unaffected by the assignments performed inside gcd.
则 i 与 j 的值不受 gcd 内执行的赋值操作的影响。
Nonreference parameters represent local copies of the corresponding argument. Changes made to the parameter are made to the local copy. Once the function terminates, these local values are gone.
非引用形参表示对应实参的局部副本。对这类形参的修改仅仅改变了局部副本的值。一旦函数执行结束,这些局部变量的值也就没有了。




Pointer Parameters
指针形参
A parameter can be a pointer (Section 4.2, p. 114), in which case the argument pointer is copied. As with any nonreference type parameter, changes made to the parameter are made to the local copy. If the function assigns a new pointer value to the parameter, the calling pointer value is unchanged.
函数的形参可以是指针(第 4.2 节),此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
Recalling the discussion in Section 4.2.3 (p. 121), the fact that the pointer is copied affects only assignments to the pointer. If the function takes a pointer to a nonconst type, then the function can assign through the pointer and change the value of the object to which the pointer points:
回顾第 4.2.3 节的讨论,事实上被复制的指针只影响对指针的赋值。如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值:
     void reset(int *ip)
     {
         *ip = 0; // changes the value of the object to which ip points
         ip = 0;   // changes only the local value of ip; the argument is unchanged
     }



After a call to reset, the argument is unchanged but the object to which the argument points will be 0:
调用 reset 后,实参依然保持原来的值,但它所指向的对象的值将变为 0:
     int i = 42;
     int *p = &i;
     cout << "i: " << *p << '\n';   // prints i: 42
     reset(p);                      // changes *p but not p
     cout << "i: " << *p << endl;   // ok: prints i: 0



If we want to prevent changes to the value to which the pointer points, then the parameter should be defined as a pointer to const:
如果保护指针指向的值,则形参需定义为指向 const 对象的指针:
     void use_ptr(const int *p)
     {
          // use_ptr may read but not write to *p
     }



Whether a pointer parameter points to a const or nonconst type affects the arguments that we can use to call the function. We can call use_ptr on either an int* or a const int*; we can pass only on an int* to reset. This distinction follows from the initialization rules for pointers (Section 4.2.5, p. 126). We may initialize a pointer to const to point to a nonconst object but may not use a pointer to nonconst to point to a const object.
指针形参是指向 const 类型还是非 const 类型,将影响函数调用所使用的实参。我们既可以用 int* 也可以用 const int* 类型的实参调用 use_ptr 函数;但仅能将 int* 类型的实参传递给 reset 函数。这个差别来源于指针的初始化规则(第 4.2.5 节)。可以将指向 const 对象的指针初始化为指向非 const 对象,但不可以让指向非 const 对象的指针向 const 对象。
const Parameters
const 形参
We can call a function that takes a nonreference, nonconst parameter passing either a const or nonconst argument. For example, we could pass two const ints to our gcd function:
在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参。例如,可以传递两个 int 型 const 对象调用 gcd:
     const int i = 3, j = 6;
     int k = rgcd(3, 6);   // ok: k initialized to 3



This behavior follows from the normal initialization rules for const objects (Section 2.4, p. 56). Because the initialization copies the value of the initializer, we can initialize a nonconst object from a const object, or vice versa.
这种行为源于 const 对象的标准初始化规则(第 2.4 节)。因为初始化复制了初始化式的值,所以可用 const 对象初始化非 const 对象,反之亦然。
If we make the parameter a const nonreference type:
如果将形参定义为非引用的 const 类型:
     void fcn(const int i) { /* fcn can read but not write to i */ }



then the function cannot change its local copy of the argument. The argument is still passed as a copy so we can pass fcn either a const or nonconst object.
则在函数中,不可以改变实参的局部副本。由于实参仍然是以副本的形式传递,因此传递给 fcn 的既可以是 const 对象也可以是非 const 对象。
What may be surprising, is that although the parameter is a const inside the function, the compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int:
令人吃惊的是,尽管函数的形参是 const,但是编译器却将 fcn 的定义视为其形码被声明为普通的 int 型:
     void fcn(const int i) { /* fcn can read but not write to i */ }
     void fcn(int i) { /* ... */ }            // error: redefines fcn(int)



This usage exists to support compatibility with the C language, which makes no distinction between functions taking const or nonconst parameters.
这种用法是为了支持对 C 语言的兼容,因为在 C 语言中,具有 const 形参或非 const 形参的函数并无区别。
Limitations of Copying Arguments
复制实参的局限性
Copying an argument is not suitable for every situation. Cases where copying doesn't work include:
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
When we want the function to be able to change the value of an argument.
当需要在函数中修改实参的值时。
When we want to pass a large object as an argument. The time and space costs to copy the object are often too high for real-world applications.
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
When there is no way to copy the object.
当没有办法实现对象的复制时。
In these cases we can instead define the parameters as references or pointers.
对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。
Exercises Section 7.2.1
Exercise 7.5:Write a function that takes an int and a pointer to an int and returns the larger of the int value of the value to which the pointer points. What type should you use for the pointer?
编写一个函数,该函数具有两个形参,分别为 int 型和指向 int 型的指针,并返回这两个 int 值之中较大的数值。考虑应将其指针形参定义为什么类型?
Exercise 7.6:Write a function to swap the values pointed to by two pointers to int. Test the function by calling it and printing the swapped values.
编写函数交换两个 int 型指针所指向的值,调用并检验该函数,输出交换后的值。



7.2.2. Reference Parameters
7.2.2. 引用形参
As an example of a situation where copying the argument doesn't work, consider a function to swap the values of its two arguments:
考虑下面不适宜复制实参的例子,该函数希望交换两个实参的值:
      // incorrect version of swap: The arguments are not changed!
     void swap(int v1, int v2)
     {
         int tmp = v2;
         v2 = v1;    // assigns new value to local copy of the argument
         v1 = tmp;
     }               // local objects v1 and v2 no longer exist



In this case, we want to change the arguments themselves. As defined, though, swap cannot affect those arguments. When it executes, swap exchanges the local copies of its arguments. The arguments passed to swap are unchanged:
这个例子期望改变实参本身的值。但对于上述的函数定义,swap 无法影响实参本身。执行 swap 时,只交换了其实参的局部副本,而传递 swap 的实参并没有修改:
     int main()
     {
         int i = 10;
         int j = 20;
         cout << "Before swap():\ti: "
              << i << "\tj: " << j << endl;
         swap(i, j);
         cout << "After swap():\ti: "
              << i << "\tj: " << j << endl;
         return 0;
     }



Compiling and executing this program results in the following output:
编译并执行程序,产生如下输出结果:
     Before swap(): i: 10 j: 20
     After  swap(): i: 10 j: 20



For swap to work as intended and swap the values of its arguments, we need to make the parameters references:
为了使 swap 函数以期望的方式工作,交换实参的值,需要将形参定义为引用类型:
     // ok: swap acts on references to its arguments
     void swap(int &v1, int &v2)
     {
         int tmp = v2;
         v2 = v1;
         v1 = tmp;
     }



Like all references, reference parameters refer directly to the objects to which they are bound rather than to copies of those objects. When we define a reference, we must initialize it with the object to which the reference will be bound. Reference parameters work exactly the same way. Each time the function is called, the reference parameter is created and bound to its corresponding argument. Now, when we call swap
与所有引用一样,引用形参直接关联到其所绑定的圣贤,而并非这些对象的副本。定义引用时,必须用与该引用绑定的对象初始化该引用。引用形参完全以相同的方式工作。每次调用函数,引用形参被创建并与相应实参关联。此时,当调用 swap
     swap(i, j);



the parameter v1 is just another name for the object i and v2 is another name for j. Any change to v1 is actually a change to the argument i. Similarly, changes to v2 are actually made to j. If we recompile main using this revised version of swap, we can see that the output is now correct:
形参 v1 只是对象 i 的另一个名字,而 v2 则是对象 j 的另一个名字。对 v1 的任何修改实际上也是对 i 的修改。同样地,v2 上的任何修改实际上也是对 j 的修改。重新编译使用 swap 的这个修订版本的 main 函数后,可以看到输出结果是正确的:
     Before swap(): i: 10 j: 20
     After  swap(): i: 20 j: 10



Programmers who come to C++ from a C background are used to passing pointers to obtain access to the argument. In C++ it is safer and more natural to use reference parameters.
从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。




Using Reference Parameters to Return Additional Information
使用引用形参返回额外的信息
We've seen one example, swap, in which reference parameters were used to allow the function to change the value of its arguments. Another use of reference parameters is to return an additional result to the calling function.
通过对 swap 这个例子的讨论,了解了如何利用引用形参让函数修改实参的值。引用形参的另一种用法是向主调函数返回额外的结果。
Functions can return only a single value, but sometimes a function has more than one thing to return. For example, let's define a function named find_val that searches for a particular value in the elements of a vector of integers. It returns an iterator that refers to the element, if the element was found, or to the end value if the element isn't found. We'd also like the function to return an occurrence count if the value occurs more than once. In this case the iterator returned should be to the first element that has the value for which we're looking.
函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如,定义一个 find_val 函数。在一个整型 vector 对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器;否则返回一个迭代器,指向该 vector 对象的 end 操作返回的元素。此外,如果该值出现了不止一次,我们还希望函数可以返回其出现的次数。在这种情况下,返回的迭代器应该指向具有要寻找的值的第一个元素。
How can we define a function that returns both an iterator and an occurrence count? We could define a new type that contains an iterator and a count. An easier solution is to pass an additional reference argument that find_val can use to return a count of the number of occurrences:
如何定义既返回一个迭代器又返回出现次数的函数?我们可以定义一种包含一个迭代器和一个计数器的新类型。而更简便的解决方案是给 find_val 传递一个额外的引用实参,用于返回出现次数的统计结果:
     // returns an iterator that refers to the first occurrence of value
     // the reference parameter occurs contains a second return value
     vector<int>::const_iterator find_val(
         vector<int>::const_iterator beg,             // first element
         vector<int>::const_iterator end,             // one past last element
         int value,                                    // the value we want
         vector<int>::size_type &occurs)              // number of times it occurs
     {
         // res_iter will hold first occurrence, if any
         vector<int>::const_iterator res_iter = end;
         occurs = 0; // set occurrence count parameter
         for ( ; beg != end; ++beg)
             if (*beg == value) {
                 // remember first occurrence of value
                 if (res_iter == end)
                    res_iter = beg;
                 ++occurs; // increment occurrence count
             }
         return res_iter;  // count returned implicitly in occurs
     }



When we call find_val, we have to pass four arguments: a pair of iterators that denote the range of elements (Section 9.2.1, p. 314) in the vector in which to look, the value to look for, and a size_type (Section 3.2.3, p. 84) object to hold the occurrence count. Assuming ivec is a vector<int>, it is an iterator of the right type, and ctr is a size_type, we could call find_val as follows:
调用 find_val 时,需传递四个实参:一对标志 vector 对象中要搜索的元素范围(第 9.2.1 节)的迭代器,所查找的值,以及用于存储出现次数的 size_type 类型(第 3.2.3 节)对象。假设 ivec 是 vector<int>, it 类型的对象,it 是一个适当类型的迭代器,而 ctr 则是 size_type 类型的变量,则可如此调用该函数:
     it = find_val(ivec.begin(), ivec.end(), 42, ctr);



After the call, the value of ctr will be the number of times 42 occurs, and it will refer to the first occurrence if there is one. Otherwise, it will be equal to ivec.end() and ctr will be zero.
调用后,ctr 的值将是 42 出现的次数,如果 42 在 ivec 中出现了,则 it 将指向其第一次出现的位置;否则,it 的值为 ivec.end(),而 ctr 则为 0。
Using (const) References to Avoid Copies
利用 const 引用避免复制
The other circumstance in which reference parameters are useful is when passing a large object to a function. Although copying an argument is okay for objects of built-in data types and for objects of class types that are small in size, it is (often) too inefficient for objects of most class types or large arrays. Moreover, as we'll learn in Chapter 13, some class types cannot be copied. By using a reference parameter, the function can access the object directly without copying it.
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;此外,我们将在第十三章学习到,某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。
As an example, we'll write a function that compares the length of two strings. Such a function needs to access the size of each string but has no need to write to the strings. Because strings can be long, we'd like to avoid copying them. Using const references we can avoid the copy:
编写一个比较两个 string 对象长度的函数作为例子。这个函数需要访问每个 string 对象的 size,但不必修改这些对象。由于 string 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:
     // compare the length of two strings
     bool isShorter(const string &s1, const string &s2)
     {
         return s1.size() < s2.size();
     }



Each parameter is a reference to const string. Because the parameters are references the arguments are not copied. Because the parameters are const references, isShorter may not use the references to change the arguments.
其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。
When the only reason to make a parameter a reference is to avoid copying the argument, the parameter should be const reference.
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。




References to const Are More Flexible
更灵活的指向 const 的引用
It should be obvious that a function that takes a plain, nonconst reference may not be called on behalf of a const object. After all, the function might change the object it is passed and thus violate the constness of the argument.
如果函数具有普通的非 const 引用形参,则显然不能通过 const 对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的 const 特性。
What may be less obvisous is that we also cannot call such a function with an rvalue (Section 2.3.1, p. 45) or with an object of a type that requires a conversion:
但比较容易忽略的是,调用这样的函数时,传递一个右值(第 2.3.1 节)或具有需要转换的类型的对象同样是不允许的:
     // function takes a non-const reference parameter
     int incr(int &val)
     {
         return ++val;
     }
     int main()
     {
         short v1 = 0;
         const int v2 = 42;
         int v3 = incr(v1);   // error: v1 is not an int
         v3 = incr(v2);       // error: v2 is const
         v3 = incr(0);        // error: literals are not lvalues
         v3 = incr(v1 + v2);  // error: addition doesn't yield an lvalue
         int v4 = incr(v3);   // ok: v3 is a non const object type int
     }



The problem is that a nonconst reference (Section 2.5, p. 59) may be bound only to nonconst object of exactly the same type.
问题的关键是非 const 引用形参(第 2.5 节)只能与完全同类型的非 const 对象关联。
Parameters that do not change the value of the corresponding argument should be const references. Defining such parameters as nonconst references needlessly restricts the usefulness of a function. As an example, we might write a program to find a given character in a string:
应该将不修改相应实参的形参定义为 const 引用。如果将这样的形参定义为非 const 引用,则毫无必要地限制了该函数的使用。例如,可编写下面的程序在一个 string 对象中查找一个指定的字符:
     // returns index of first occurrence of c in s or s.size() if c isn't in s
     // Note: s doesn't change, so it should be a reference to const
     string::size_type find_char(string &s, char c)
     {
         string::size_type i = 0;
         while (i != s.size() && s[i] != c)
             ++i;                   // not found, look at next character
         return i;
     }



This function takes its string argument as a plain (nonconst) reference, even though it doesn't modify that parameter. One problem with this definition is that we cannot call it on a character string literal:
这个函数将其 string 类型的实参当作普通(非 const)的引用,尽管函数并没有修改这个形参的值。这样的定义带来的问题是不能通过字符串字面值来调用这个函数:
     if (find_char("Hello World", 'o')) // ...



This call fails at compile time, even though we can convert the literal to a string.
虽然字符串字面值可以转换为 string 对象,但上述调用仍然会导致编译失败。
Such problems can be surprisingly pervasive. Even if our program has no const objects and we only call find_char on behalf of string objects (as opposed to on a string literal or an expression that yields a string), we can encounter compile-time problems. For example, we might have another function is_sentence that wants to use find_char to determine whether a string represents a sentence:
继续将这个问题延伸下去会发现,即使程序本身没有 const 对象,而且只使用 string 对象(而并非字符串字面值或产生 string 对象的表达式)调用 find_char 函数,编译阶段的问题依然会出现。例如,可能有另一个函数 is_sentence 调用 find_char 来判断一个 string 对象是否是句子:
     bool is_sentence (const string &s)
     {
          // if there's a period and it's the last character in s
          // then s is a sentence
          return (find_char(s, '.') == s.size() - 1);
     }



As written, the call to find_char from inside is_sentence is a compile-time error. The parameter to is_sentence is a reference to const string and cannot be passed to find_char, which expects a reference to a nonconst string.
如上代码,函数 is_sentence 中 find_char 的调用是一个编译错误。传递进 is_sentence 的形参是指向 const string 对象的引用,不能将这种类型的参数传递给 find_char,因为后者期待得到一个指向非 const string 对象的引用。
Reference parameters that are not changed should be references to const. Plain, nonconst reference parameters are less flexible. Such parameters may not be initialized by const objects, or by arguments that are literals or expressions that yield rvalues.
应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。




Passing a Reference to a Pointer
传递指向指针的引用
Suppose we want to write a function that swaps two pointers, similar to the program we wrote earlier that swaps two integers. We know that we use * to define a pointer and & to define a reference. The question here is how to combine these operators to obtain a reference to a pointer. Here is an example:
假设我们想编写一个与前面交换两个整数的 swap 类似的函数,实现两个指针的交换。已知需用 * 定义指针,用 & 定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:
     // swap values of two pointers to int
     void ptrswap(int *&v1, int *&v2)
     {
         int *tmp = v2;
         v2 = v1;
         v1 = tmp;
     }



The parameter
形参
     int *&v1



should be read from right to left: v1 is a reference to a pointer to an object of type int. That is, v1 is just another name for whatever pointer is passed to ptrswap.
的定义应从右至左理解:v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进 ptrswap 函数的任意指针的别名。
We could rewrite the main function from page 233 to use ptrswap and swap pointers to the values 10 and 20:
重写第 7.2.2 节的 main 函数,调用 ptrswap 交换分别指向值 10 和 20 的指针:
     int main()
     {
         int i = 10;
         int j = 20;
         int *pi = &i;  // pi points to i
         int *pj = &j; // pj points to j
         cout << "Before ptrswap():\t*pi: "
              << *pi << "\t*pj: " << *pj << endl;
         ptrswap(pi, pj); // now pi points to j; pj points to i
         cout << "After ptrswap():\t*pi: "
              << *pi << "\t*pj: " << *pj << endl;
         return 0;
     }



When compiled and executed, the program generates the following output:
编译并执行后,该程序产生如下结果:
     Before ptrswap(): *pi: 10 *pj: 20
     After ptrswap():  *pi: 20 *pj: 10



What happens is that the pointer values are swapped. When we call ptrswap, pi points to i and pj points to j. Inside ptrswap the pointers are swapped so that after ptrswap, pi points to the object pj had addressed. In other words, pi now points to j. Similarly, pj points to i.
即指针的值被交换了。在调用 ptrswap 时,pi 指向 i,而 pj 则指向 j。在 ptrswap 函数中,指针被交换,使得调用 ptrswap 结束后,pi 指向了原来 pj 所指向的对象。换句话说,现在 pi 指向 j,而 pj 则指向了 i。
Exercises Section 7.2.2
Exercise 7.7:Explain the difference in the following two parameter declarations:
解释下面两个形参声明的不同之处:
     void f(T);
     void f(T&);



Exercise 7.8:Give an example of when a parameter should be a reference type. Give an example of when a parameter should not be a reference.
举一个例子说明什么时候应该将形参定义为引用类型。再举一个例子说明什么时候不应该将形参定义为引用。
Exercise 7.9:Change the declaration of occurs in the parameter list of find_val (defined on page 234) to be a nonreference argument type and rerun the program. How does the behavior of the program change?
将第 7.2.2 节定义的 find_val 函数的形参表中 occurs 的声明修改为非引用参数类型,并重新执行这个程序,该函数的行为发生了什么改变?
Exercise 7.10:The following program, although legal, is less useful than it might be. Identify and correct the limitation on this program:
下面的程序虽然是合法的,但可用性还不够好,指出并改正该程序的局限:
     bool test(string& s) { return s.empty(); }



Exercise 7.11:When should reference parameters be const? What problems might arise if we make a parameter a plain reference when it could be a const reference?
何时应将引用形参定义为 const 对象?如果在需要 const 引用时,将形参定义为普通引用,则会出现什么问题?



7.2.3. vector and Other Container Parameters
7.2.3. vector 和其他容器类型的形参
Ordinarily, functions should not have vector or other library container parameters. Calling a function that has a plain, nonreference vector parameter will copy every element of the vector.
通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。




In order to avoid copying the vector, we might think that we'd make the parameter a reference. However, for reasons that will be clearer after reading Chapter 11, in practice, C++ programmers tend to pass containers by passing iterators to the elements we want to process:
从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。然而,看过第十一章后我们会知道,事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器:
     // pass iterators to the first and one past the last element to print
     void print(vector<int>::const_iterator beg,
                vector<int>::const_iterator end)
     {
         while (beg != end) {
             cout << *beg++;
             if (beg != end) cout << " "; // no space after last element
         }
         cout << endl;
     }



This function prints the elements starting with one referred to by beg up to but not including the one referred to by end. We print a space after each element but the last.
这个函数将输出从 beg 指向的元素开始到 end 指向的元素(不含)为止的范围内所有的元素。除了最后一个元素外,每个元素后面都输出一个空格。
7.2.4. Array Parameters
7.2.4. 数组形参
Arrays have two special properties that affect how we define and use functions that operate on arrays: We cannot copy an array (Section 4.1.1, p. 112) and when we use the name of an array it is automatically converted to a pointer to the first element (Section 4.2.4, p. 122). Because we cannot copy an array, we cannot write a function that takes an array type parameter. Because arrays are automatically converted to pointers, functions that deal with arrays usually do so indirectly by manipulating pointers to elements in the array.
数组有两个特殊的性质,影响我们定义和使用作用在数组上的函数:一是不能复制数组(第 4.1.1 节);二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针(第 4.2.4 节)。因为数组不能复制,所以无法编写使用数组类型形参的函数。因为数组会被自动转化为指针,所以处理数组的函数通常通过操纵指向数组指向数组中的元素的指针来处理数组。
Defining an Array Parameter
数组形参的定义
Let's assume that we want to write a function that will print the contents of an array of ints. We could specify the array parameter in one of three ways:
如果要编写一个函数,输出 int 型数组的内容,可用下面三种方式指定数组形参:
     // three equivalent definitions of printValues
     void printValues(int*) { /* ... */ }
     void printValues(int[]) { /* ... */ }
     void printValues(int[10]) { /* ... */ }



Even though we cannot pass an array directly, we can write a function parameter that looks like an array. Despite appearances, a parameter that uses array syntax is treated as if we had written a pointer to the array element type. These three definitions are equivalent; each is interpreted as taking a parameter of type int*.
虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。上面的三种定义是等价的,形参类型都是 int*。
It is usually a good idea to define array parameters as pointers, rather than using the array syntax. Doing so makes it clear that what is being operated on is a pointer to an array element, not the array itself. Because an array dimension is ignored, including a dimension in a parameter definition is particularly misleading.
通常,将数组形参直接定义为指针要比使用数组语法定义更好。这样就明确地表示,函数操纵的是指向数组元素的指针,而不是数组本身。由于忽略了数组长度,形参定义中如果包含了数组长度则特别容易引起误解。




Parameter Dimensions Can Be Misleading
形参的长度会引起误解
The compiler ignores any dimension we might specify for an array parameter. Relying, incorrectly, on the dimension, we might write printValues as
编译器忽略为任何数组形参指定的长度。根据数组长度(权且这样说),可将函数 printValues 编写为:
     // parameter treated as const int*, size of array is ignored
     void printValues(const int ia[10])
     {
          // this code assumes array has 10 elements;
          // disaster if argument has fewer than 10 elements!
          for (size_t i = 0; i != 10; ++i)
          {
              cout << ia[i] << endl;
          }
     }



Although this code assumes that the array it is passed has at least 10 elements, nothing in the language enforces that assumption. The following calls are all legal:
尽管上述代码假定所传递的数组至少含有 10 个元素,但 C++ 语言没有任何机制强制实现这个假设。下面的调用都是合法的:
     int main()
     {
         int i = 0, j[2] = {0, 1};
         printValues(&i);      // ok: &i is int*; probable run-time error
         printValues(j);      // ok: j is converted to pointer to 0th
                              // element; argument has type int*;
                              // probable run-time error
         return 0;
     }



Even though the compiler issues no complaints, both calls are in error, and probably will fail at run time. In each case, memory beyond the array will be accessed because printValues assumes that the array it is passed has at least 10 elements. Depending on the values that happen to be in that memory, the program will either produce spurious output or crash.
虽然编译没有问题,但是这两个调用都是错误的,可能导致运行失败。在这两个调用中,由于函数 printValues 假设传递进来的数组至少含有 10 个元素,因此造成数组内在的越界访问。程序的执行可能产生错误的输出,也可能崩溃,这取决于越界访问的内存中恰好存储的数值是什么。
When the compiler checks an argument to an array parameter, it checks only that the argument is a pointer and that the types of the pointer and the array elements match. The size of the array is not checked.
当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。




Array Arguments
数组实参
As with any other type, we can define an array parameter as a reference or nonreference type. Most commonly, arrays are passed as plain, nonreference types, which are quietly converted to pointers. As usual, a nonreference type parameter is initialized as a copy of its corresponding argument. When we pass an array, the argument is a pointer to the first element in the array. That pointer value is copied; the array elements themselves are not copied. The function operates on a copy of the pointer, so it cannot change the value of the argument pointer. The function can, however, use that pointer to change the element values to which the pointer points. Any changes through the pointer parameter are made to the array elements themselves.
和其他类型一样,数组形参可定义为引用或非引用类型。大部分情况下,数组以普通的非引用类型传递,此时数组会悄悄地转换为指针。一般来说,非引用类型的形参会初始化为其相应实参的副本。而在传递数组时,实参是指向数组第一个元素的指针,形参复制的是这个指针的值,而不是数组元素本身。函数操纵的是指针的副本,因此不会修改实参指针的值。然而,函数可通过该指针改变它所指向的数组元素的值。通过指针形参做的任何改变都在修改数组元素本身。
Functions that do not change the elements of their array parameter should make the parameter a pointer to const:
不需要修改数组形参的元素时,函数应该将形参定义为指向 const 对象的指针:


     // f won't change the elements in the array
     void f(const int*) { /* ... */ }





Passing an Array by Reference
通过引用传递数组
As with any type, we can define an array parameter as a reference to the array. If the parameter is a reference to the array, then the compiler does not convert an array argument into a pointer. Instead, a reference to the array itself is passed. In this case, the array size is part of the parameter and argument types. The compiler will check that the size of the array argument matches the size of the parameter:
和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:
     // ok: parameter is a reference to an array; size of array is fixed
     void printValues(int (&arr)[10]) { /* ... */ }
     int main()
     {
         int i = 0, j[2] = {0, 1};
         int k[10] = {0,1,2,3,4,5,6,7,8,9};
         printValues(&i); // error: argument is not an array of 10 ints
         printValues(j);  // error: argument is not an array of 10 ints
         printValues(k);  // ok: argument is an array of 10 ints
         return 0;
     }



This version of printValues may be called only for arrays of exactly 10 ints, limiting which arrays can be passed. However, because the parameter is a reference, it is safe to rely on the size in the body of the function:
这个版本的 printValues 函数只严格地接受含有 10 个 int 型数值的数组,这限制了哪些数组可以传递。然而,由于形参是引用,在函数体中依赖数组的大小是安全的:
     // ok: parameter is a reference to an array; size of array is fixed
     void printValues(int (&arr)[10])
     {
         for (size_t i = 0; i != 10; ++i) {
             cout << arr[i] << endl;
         }
     }



The parentheses around &arr are necessary because of the higher precedence of the subscript operator:
&arr 两边的圆括号是必需的,因为下标操作符具有更高的优先级:


     f(int &arr[10])     // error: arr is an array of references
     f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints





We'll see in Section 16.1.5 (p. 632) how we might write this function in a way that would allow us to pass a reference parameter to an array of any size.
在第 16.1.5 节将会介绍如何重新编写此函数,允许传递指向任意大小的数组的引用形参。
Passing a Multidimensioned Array
多维数组的传递
Recall that there are no multidimensioned arrays in C++ (Section 4.4, p. 141). Instead, what appears to be a multidimensioned array is an array of arrays.
回顾前面我们说过在 C++ 中没有多维数组(第 4.4 节)。所谓多维数组实际是指数组的数组。
As with any array, a multidimensioned array is passed as a pointer to its zeroth element. An element in a multidimenioned array is an array. The size of the second (and any subsequent dimensions) is part of the element type and must be specified:
和其他数组一样,多维数组以指向 0 号元素的指针方式传递。多维数组的元素本身就是数组。除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定:
     // first parameter is an array whose elements are arrays of 10 ints
     void printValues(int (matrix*)[10], int rowSize);



declares matrix as a pointer to an array of ten ints.
上面的语句将 matrix 声明为指向含有 10 个 int 型元素的数组的指针。
Again, the parentheses around *matrix are necessary:
再次强调,*matrix 两边的圆括号是必需的:


     int *matrix[10];   // array of 10 pointers
     int (*matrix)[10]; // pointer to an array of 10 ints





We could also declare a multidimensioned array using array syntax. As with a single-dimensioned array, the compiler ignores the first dimension and so it is best not to include it:
我们也可以用数组语法定义多维数组。与一维数组一样,编译器忽略第一维的长度,所以最好不要把它包括在形参表内:
     // first parameter is an array whose elements are arrays of 10 ints
     void printValues(int matrix[][10], int rowSize);



declares matrix to be what looks like a two-dimensioned array. In fact, the parameter is a pointer to an element in an array of arrays. Each element in the array is itself an array of ten ints.
这条语句把 matrix 声明为二维数组的形式。实际上,形参是一个指针,指向数组的数组中的元素。数组中的每个元素本身就是含有 10 个 int 型对象的数组。
7.2.5. Managing Arrays Passed to Functions
7.2.5. 传递给函数的数组的处理
As we've just seen, type checking for a nonreference array parameter confirms only that the argument is a pointer of the same type as the elements in the array. Type checking does not verify that the argument actually points to an array of a specified size.
就如刚才所见的,非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。
It is up to any program dealing with an array to ensure that the program stays within the bounds of the array.
任何处理数组的程序都要确保程序停留在数组的边界内。




There are three common programming techniques to ensure that a function stays within the bounds of its array argument(s). The first places a marker in the array itself that can be used to detect the end of the array. C-style character strings are an example of this approach. C-style strings are arrays of characters that encode their termination point with a null character. Programs that deal with C-style strings use this marker to stop processing elements in the array.
有三种常见的编程技巧确保函数的操作不超出数组实参的边界。第一种方法是在数组本身放置一个标记来检测数组的结束。C 风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符 null 作为结束的标记。处理 C 风格字符串的程序就是使用这个标记停止数组元素的处理。
Using the Standard Library Conventions
使用标准库规范
A second approach is to pass pointers to the first and one past the last element in the array. This style of programming is inspired by techniques used in the standard library. We'll learn more about this style of programming in Part II.
第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针。这种编程风格由标准库所使用的技术启发而得,在第二部分将会进一步介绍这种编程风格。
Using this approach, we could rewrite printValues and call the new version as follows:
使用这种方法重写函数 printValues 并调用该函数,如下所示:
     void printValues(const int *beg, const int *end)
     {
         while (beg != end) {
             cout << *beg++ << endl;
          }
     }
     int main()
     {
         int j[2] = {0, 1};
         // ok: j is converted to pointer to 0th element in j
         //     j + 2 refers one past the end of j
         printValues(j, j + 2);
         return 0;
     }



The loop inside printValues looks like other programs we've written that used vector iterators. We march the beg pointer one element at a time through the array. We stop the loop when beg is equal to the end marker, which was passed as the second parameter to the function.
printValues 中的循环很像用 vector 迭代器编写的程序。每次循环都使 beg 指针指向下一个元素,从而实现数组的遍历。当 beg 指针等于结束标记时,循环结束。结束标记就是传递给函数的第二个形参。
When we call this version, we pass two pointers: one to the first element we want to print and one just past the last element. The program is safe, as long as we correctly calculate the pointers so that they denote a range of elements.
调用这个版本的函数需要传递两个指针:一个指向要输出的第一个元素,另一个则指向最后一个元素的下一个位置。只要正确计算指针,使它们标记一段有效的元素范围,程序就会安全。
Explicitly Passing a Size Parameter
显式传递表示数组大小的形参
A third approach, which is common in C programs and pre-Standard C++ programs, is to define a second parameter that indicates the size of the array.
第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。
Using this approach, we could rewrite printValues one more time. The new version and a call to it looks like:
用这种方法再次重写函数 printValues,新版本及其调用如下所示:
     // const int ia[] is equivalent to const int* ia
     // size is passed explicitly and used to control access to elements of ia
     void printValues(const int ia[], size_t size)
     {
          for (size_t i = 0; i != size; ++i) {
              cout << ia[i] << endl;
          }
     }
     int main()
     {
         int j[] = { 0, 1 }; // int array of size 2
         printValues(j, sizeof(j)/sizeof(*j));
         return 0;
     }



This version uses the size parameter to determine how many elements there are to print. When we call printValues, we must pass an additional parameter. The program executes safely as long as the size passed is no greater than the actual size of the array.
这个版本使用了形参 size 来确定要输出的元素的个数。调用 printValues 时,要额外传递一个形参。只要传递给函数的 size 值不超过数组的实际大小,程序就能安全运行。
Exercises Section 7.2.5
Exercise 7.12:When would you use a parameter that is a pointer? When would you use a parameter that is a reference? Explain the advantages and disadvantages of each.
什么时候应使用指针形参?什么时候就使用引用形参?解释两者的优点和缺点。
Exercise 7.13:Write a program to calculate the sum of the elements in an array. Write the function three times, each one using a different approach to managing the array bounds.
编写程序计算数组元素之和。要求编写函数三次,每次以不同的方法处理数组边界。
Exercise 7.14:Write a program to sum the elements in a vector<double>.
编写程序求 vector<double> 对象中所有元素之和。



7.2.6. main: Handling Command-Line Options
7.2.6. main: 处理命令行选项
It turns out that main is a good example of how C programs pass arrays to functions. Up to now, we have defined main with an empty parameter list:
主函数 main 是演示 C 程序如何将数组传递给函数的好例子。直到现在,我们所定义的主函数都只有空的形参表:
     int main() { ... }



However, we often need to pass arguments to main. TRaditionally, such arguments are options that determine the operation of the program. For example, assuming our main program was in an executable file named prog, we might pass options to the program as follows:
但是,我们通常需要给 main 传递实参。传统上,主函数的实参是可选的,用来确定程序要执行的操作。比如,假设我们的主函数 main 位于名为 prog 的可执行文件中,可如下将实参选项传递给程序:
     prog -d -o ofile data0



The way this usage is handled is that main actually defines two parameters:
这种用法的处理方法实际上是在主函数 main 中定义了两个形参:
     int main(int argc, char *argv[]) { ... }



The second parameter, argv, is an array of C-style character strings. The first parameter, argc, passes the number of strings in that array. Because the second parameter is an array, we might alternatively define main as
第二个形参 argv 是一个 C 风格字符串数组。第一个形参 argc 则用于传递该数组中字符串的个数。由于第二个参数是一个数组,主函数 main 也可以这样定义:
     int main(int argc, char **argv) { ... }



indicating that argv points to a char*.
表示 argv 是指向 char* 的指针。
When arguments are passed to main, the first string in argv, if any, is always the name of the program. Subsequent elements pass additional optional strings to main. Given the previous command line, argc would be set to 5, and argv would hold the following C-style character strings:
当将实参传递给主函数 main 时,argv 中的第一个字符串(如果有的话)通常是程序的名字。接下来的元素将额外的可选字符串传递给主函数 main。以前面的命令行为例,argc 应设为 5,argv 会保存下面几个 C 风格字符串:
     argv[0] = "prog";
     argv[1] = "-d";
     argv[2] = "-o";
     argv[3] = "ofile";
     argv[4] = "data0";


Exercises Section 7.2.6
Exercise 7.15:Write a main function that takes two values as arguments and print their sum.
编写一个主函数 main,使用两个值作为实参,并输出它们的和。
Exercise 7.16:Write a program that could accept the options presented in this section. Print the values of the arguments passed to main.
编写程序使之可以接受本节介绍的命令行选项,并输出传递给 main 的实参值。



7.2.7. Functions with Varying Parameters
7.2.7. 含有可变形参的函数
Ellipsis parameters are in C++ in order to compile C programs that use varargs. See your C compiler documentation for how to use varargs. Only simple data types from the C++ program should be passed to functions with ellipses parameters. In particular, objects of most class types are not copied properly when passed to ellipses parameters.
C++ 中的省略符形参是为了编译使用了 varargs 的 C 语言程序。关于如何使用 varargs,请查阅所用 C 语言编译器的文档。对于 C++ 程序,只能将简单数据类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制。




Ellipses parameters are used when it is impossible to list the type and number of all the arguments that might be passed to a function. Ellipses suspend type checking. Their presence tells the compiler that when the function is called, zero or more arguments may follow and that the types of the arguments are unknown. Ellipses may take either of two forms:
在无法列举出传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以有 0 或多个实参,而实参的类型未知。省略符形参有下列两种形式:
     void foo(parm_list, ...);
     void foo(...);



The first form provides declarations for a certain number of parameters. In this case, type checking is performed when the function is called for the arguments that correspond to the parameters that are explicitly declared, whereas type checking is suspended for the arguments that correspond to the ellipsis. In this first form, the comma following the parameter declarations is optional.
第一种形式为特定数目的形参提供了声明。在这种情况下,当函数被调用时,对于与显示声明的形参相对应的实参进行类型检查,而对于与省略符对应的实参则暂停类型检查。在第一种形式中,形参声明后面的逗号是可选的。
Most functions with an ellipsis use some information from a parameter that is explicitly declared to obtain the type and number of optional arguments provided in a function call. The first form of function declaration with ellipsis is therefore most commonly used.
大部分带有省略符形参的函数都利用显式声明的参数中的一些信息,来获取函数调用中提供的其他可选实参的类型和数目。因此带有省略符的第一种形式的函数声明是最常用的。
 
      

7.3. The return Statement
7.3. return 语句
A return statement terminates the function that is currently executing and returns control to the function that called the now-terminated function. There are two forms of return statements:
return 语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。return 语句有两种形式:
     return;
     return expression;



7.3.1. Functions with No Return Value
7.3.1. 没有返回值的函数
A return with no value may be used only in a function that has a return type of void. Functions that return void are not required to contain a return statement. In a void function, an implicit return takes place after the function's final statement.
不带返回值的 return 语句只能用于返回类型为 void 的函数。在返回类型为 void 的函数中,return 返回语句不是必需的,隐式的 return 发生在函数的最后一个语句完成时。
Typically, a void function uses a return to cause premature termination of the function. This use of return parallels the use of the break (Section 6.10, p. 212) statement inside a loop. For example, we could rewrite our swap program to avoid doing any work if the values are identical:
一般情况下,返回类型是 void 的函数使用 return 语句是为了引起函数的强制结束,这种 return 的用法类似于循环结构中的 break 语句(第 6.10 节)的作用。例如,可如下重写 swap 程序,使之在输入的两个数值相同时不执行任何工作:
     // ok: swap acts on references to its arguments
     void swap(int &v1, int &v2)
     {
          // if values already the same, no need to swap, just return
          if (v1 == v2)
              return;
          // ok, have work to do
          int tmp = v2;
          v2 = v1;
          v1 = tmp;
          // no explicit return necessary
     }



This function first checks if the values are equal and if so exits the function. If the values are unequal, the function swaps them. An implicit return occurs after the last assignment statement.
这个函数首先检查两个值是否相等,如果相等则退出函数;如果不相等,则交换这两个值,隐式的 return 发生在最后一个赋值语句后。
A function with a void return type ordinarily may not use the second form of the return statement. However, a void function may return the result of calling another function that returns void:
返回类型为 void 的函数通常不能使用第二种形式的 return 语句,但是,它可以返回另一个返回类型同样是 void 的函数的调用结果:
     void do_swap(int &v1, int &v2)
     {
         int tmp = v2;
         v2 = v1;
         v1 = tmp;
         // ok: void function doesn't need an explicit return
     }
     void swap(int &v1, int &v2)
     {
         if (v1 == v2)
             return false; // error: void function cannot return a value
         return do_swap(v1, v2); // ok: returns call to a void function

     }



Attempting to return any other expression is a compile-time error.
返回任何其他表达式的尝试都会导致编译时的错误。
7.3.2. Functions that Return a Value
7.3.2. 具有返回值的函数
The second form of the return statement provides the function's result. Every return in a function with a return type other than void must return a value. The value returned must have the same type as the function return type, or must have a type that can be implicitly converted to that type.
return 语句的第二种形式提供了函数的结果。任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。
Although C++ cannot guarantee the correctness of a result, it can guarantee that every return from a function returns a result of the appropriate type. The following program, for example, won't compile:
尽管 C++ 不能确保结果的正确性,但能保证函数每一次 return 都返回适当类型的结果。例如,下面的程序就不能通过编译:
     // Determine whether two strings are equal.
     // If they differ in size, determine whether the smaller
     // one holds the same characters as the larger one
     bool str_subrange(const string &str1, const string &str2)
     {
         // same sizes: return normal equality test
         if (str1.size() == str2.size())
             return str1 == str2;    // ok, == returns bool
         // find size of smaller string
         string::size_type size = (str1.size() < str2.size())
                                  ? str1.size() : str2.size();
         string::size_type i = 0;
         // look at each element up to size of smaller string
         while (i != size) {
             if (str1[i] != str2[i])
                 return;   // error: no return value
         }
         // error: control might flow off the end of the function without a return
         // the compiler is unlikely to detect this error
      }



The return from within the while loop is an error because it fails to return a value. The compiler should detect this error.
while 循环中的 return 语句是错误的,因为它没有返回任何值,编译器将检查出这个错误。
The second error occurs because the function fails to provide a return after the while loop. If we call this function with one string that is a subset of the other, execution would fall out of the while. There should be are turn to handle this case. The compiler may or may not detect this error. If a program is generated, what happens at run time is undefined.
第二个错误源于函数没有在 while 循环后提供 return 语句。调用这个函数时,如果一个 string 是另一个 string 的子集,执行会退出 while 循环。这里应该有一个 return 语句来处理这种情况。编译器有可能检查出也有可能检查不出这种错误。执行程序时,不确定在运行阶段会出现什么问题。
Failing to provide a return after a loop that does contain a return is particularly insidious because many compilers will not detect it. The behavior at run time is undefined.
在含有 return 语句的循环后没有提供 return 语句是很危险的,因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。




Return from main
主函数 main 的返回值
There is one exception to the rule that a function with a return type other than void must return a value: The main function is allowed to terminate without a return. If control reaches the end of main and there is no return, then the compiler implicitly inserts a return of 0.
返回类型不是 void 的函数必须返回一个值,但此规则有一个例外情况:允许主函数 main 没有返回值就可结束。如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。
Another way in which the return from main is special is how its returned value is treated. As we saw in Section 1.1 (p. 2), the value returned from main is treated as a status indicator. A zero return indicates success; most other values indicate failure. A nonzero value has a machine-dependent meaning. To make return values machine-independent, the cstdlib header defines two preprocessor variables (Section 2.9.2, p. 69) that we can use to indicate success or failure:
关于主函数 main 返回的另一个特别之处在于如何处理它的返回值。在第 1.1 节已知,可将主函数 main 返回的值视为状态指示器。返回 0 表示程序运行成功,其他大部分返回值则表示失败。非 0 返回值的意义因机器不同而不同,为了使返回值独立于机器,cstdlib 头文件定义了两个预处理变量(第 2.9.2 节),分别用于表示程序运行成功和失败:
     #include <cstdlib>
     int main()
     {
         if (some_failure)
             return EXIT_FAILURE;
         else
             return EXIT_SUCCESS;
     }



Our code no longer needs to use the precise machine-dependent values. Instead, those values are defined in cstdlib, and our code need not change.
我们的代码不再需要使用那些依赖于机器的精确返回值。相应地,这些值都在 cstdlib 库中定义,我们的代码不需要做任何修改。
Returning a Nonreference Type
返回非引用类型
The value returned by a function is used to initialize a temporary object created at the point at which the call was made. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression. C++ programmers usually use the term "temporary" as an abreviation of "temporary object."
函数的返回值用于初始化在调用函数处创建的临时对象。在求解表达式时,如果需要一个地方储存其运算结果,编译器会创建一个没有命名的对象,这就是临时对象。在英语中,C++ 程序员通常用 temporary 这个术语来代替 temporary object。
The temporary is initialized by the value returned by a function in much the same way that parameters are initialized by their arguments. If the return type is not a reference, then the return value is copied into the temporary at the call site. The value returned when a function returns a nonreference type can be a local object or the result of evaluating an expression.
用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。
As an example, we might want to write a function that, given a counter, a word, and an ending, gives us back the plural version of the word if the counter is greater than one:
例如,下面的程序提供了一个计数器、一个单词 word 和单词结束字符串 ending,当计数器的值大于 1 时,返回该单词的复数版本:
     // return plural version of word if ctr isn't 1
     string make_plural(size_t ctr, const string &word,
                                    const string &ending)
     {
         return (ctr == 1) ? word : word + ending;
     }



We might use such a function to print a message with either a plural or singular ending.
我们可以使用这样的函数来输出单词的单数或复数形式。
This function either returns a copy of its parameter named word or it returns an unnamed temporary string that results from adding word and ending. In either case, the return copies that string to the call site.
这个函数要么返回其形参 word 的副本,要么返回一个未命名的临时 string 对象,这个临时对象是由字符串 word 和 ending 的相加而产生的。这两种情况下,return 都在调用该函数的地方复制了返回的 string 对象。
Returning a Reference
返回引用
When a function returns a reference type, the return value is not copied. Instead, the object itself is returned. As an example, consider a function that returns a reference to the shorter of its two string parameters:
当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。例如,考虑下面的函数,此函数返回两个 string 类型形参中较短的那个字符串的引用:
     // find longer of two strings
     const string &shorterString(const string &s1, const string &s2)
     {
         return s1.size() < s2.size() ? s1 : s2;
     }



The parameters and return type are references to const string. The strings are not copied either when calling the function or when returning the result.
形参和返回类型都是指向 const string 对象的引用,调用函数和返回结果时,都没有复制这些 string 对象。
Never Return a Reference to a Local Object
千万不要返回局部对象的引用
There's one crucially important thing to understand about returning a reference: Never return a reference to a local variable.
理解返回引用至关重要的是:千万不能返回局部变量的引用。




When a function completes, the storage in which the local objects were allocated is freed. A reference to a local object refers to undefined memory after the function terminates. Consider the following function:
当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。考虑下面的程序:
     // Disaster: Function returns a reference to a local object
     const string &manip(const string& s)
     {
          string ret = s;
          // transform ret in some way
          return ret; // Wrong: Returning reference to a local object!
     }



This function will fail at run time because it returns a reference to a local object. When the function ends, the storage in which ret resides is freed. The return value refers to memory that is no longer available to the program.
这个函数会在运行时出错,因为它返回了局部对象的引用。当函数执行完毕,字符串 ret 占用的储存空间被释放,函数返回值指向了对于这个程序来说不再有效的内存空间。
One good way to ensure that the return is safe is to ask: To what pre-existing object is the reference referring?
确保返回引用安全的一个好方法是:请自问,这个引用指向哪个在此之前存在的对象?




Reference Returns Are Lvalues
引用返回左值
A function that returns a reference returns an lvalue. That function, therefore, can be used wherever an lvalue is required:
返回引用的函数返回一个左值。因此,这样的函数可用于任何要求使用左值的地方:
     char &get_val(string &str, string::size_type ix)
     {
         return str[ix];
     }
     int main()
     {
         string s("a value");
         cout << s << endl;   // prints a value
         get_val(s, 0) = 'A'; // changes s[0] to A

         cout << s << endl;   // prints A value
         return 0;
     }



It may be surprising to assign to the return of a function, but the return is a reference. As such, it is just a synonym for the element returned.
给函数返回值赋值可能让人惊讶,由于函数返回的是一个引用,因此这是正确的,该引用是被返回元素的同义词。
If we do not want the reference return to be modifiable, the return value should be declared as const:
如果不希望引用返回值被修改,返回值应该声明为 const:
     const char &get_val(...



Never Return a Pointer to a Local Object
千万不要返回指向局部对象的指针
The return type for a function can be most any type. In particular, it is possible for a function to return a pointer. For the same reasons that it is an error to return a reference to a local object, it is also an error to return a pointer to a local object. Once the function completes, the local objects are freed. The pointer would be a dangling pointer (Section 5.11, p. 176) that refers to a nonexistent object.
函数的返回类型可以是大多数类型。特别地,函数也可以返回指针类型。和返回局部对象的引用一样,返回指向局部对象的指针也是错误的。一旦函数结束,局部对象被释放,返回的指针就变成了指向不再存在的对象的悬垂指针(第 5.11 节)。
Exercises Section 7.3.2
Exercise 7.17:When is it valid to return a reference? A const reference?
什么时候返回引用是正确的?而什么时候返回 const 引用是正确的?
Exercise 7.18:What potential run-time problem does the following function have?
下面函数存在什么潜在的运行时问题?
     string &processText() {
         string text;
         while (cin >> text) { /* ... */ }
         // ....
         return text;
     }



Exercise 7.19:Indicate whether the following program is legal. If so, explain what it does; if not, make it legal and then explain it:
判断下面程序是否合法;如果合法,解释其功能;如果不合法,更正它并解释原因。
     int &get(int *arry, int index) { return arry[index]; }
     int main() {
         int ia[10];
         for (int i = 0; i != 10; ++i)
              get(ia, i) = 0;
     }






7.3.3. Recursion
7.3.3. 递归
A function that calls itself, either directly or indirectly, is a recursive function. An example of a simple recursive function is one that computes the factorial of a number. The factorial of a number n is the product of the numbers from 1 to n. The factorial of 5, for example, is 120.
直接或间接调用自己的函数称为递归函数。一个简单的递归函数例子是阶乘的计算。数 n 阶乘是从 1 到 n 的乘积。例如,5 的阶乘就是 120。
     1 * 2 * 3 * 4 * 5 = 120



A natural way to solve this problem is recursively:
解决这个问题的自然方法就是递归:
     // calculate val!, which is 1*2 *3 ... * val
     int factorial(int val)
     {
         if (val > 1)
             return factorial(val-1) * val;
          return 1;
     }



A recursive function must always define a stopping condition; otherwise, the function will recurse "forever," meaning that the function will continue to call itself until the program stack is exhausted. This is sometimes called an "infinite recursion error." In the case of factorial, the stopping condition occurs when val is 1.
递归函数必须定义一个终止条件;否则,函数就会“永远”递归下去,这意味着函数会一直调用自身直到程序栈耗尽。有时候,这种现象称为“无限递归错误”。对于函数 factorial,val 为 1 是终止条件。
As another example, we can define a recursive function to find the greatest common divisor:
另一个例子是求最大公约数的递归函数:
     // recursive version greatest common divisor program
     int rgcd(int v1, int v2)
     {
         if (v2 != 0)                // we're done once v2 gets to zero
             return rgcd(v2, v1%v2); // recurse, reducing v2 on each call
         return v1;
     }



In this case the stopping condition is a remainder of 0. If we call rgcd with the arguments (15, 123), then the result is three. Table 7.1 on the next page traces the execution.
这个例子中,终止条件是余数为 0。如果用实参 (15, 123) 来调用 rgcd 函数,结果为 3。表 7.1 跟踪了它的执行过程。
Table 7.1. Trace of rgcd(15,123)
表 7.1. rgcd(15, 123) 的跟踪过程
v1v2Return
15123rgcd(123, 15)
12315rgcd(15, 3)
153rgcd(3, 0)
303


The last call,
最后一次调用:
     rgcd(3,0)



satisfies the stopping condition. It returns the greatest common denominator, 3. This value successively becomes the return value of each prior call. The value is said to percolate upward until the execution returns to the function that called rgcd in the first place.
满足了终止条件,它返回最大公约数 3。该值依次成为前面每个调用的返回值。这个过程称为此值向上回渗(percolate),直到执行返回到第一次调用 rgcd 的函数。
The main function may not call itself.
主函数 main 不能调用自身。



Exercises Section 7.3.3
Exercise 7.20:Rewrite factorial as an iterative function.
将函数 factorial 重写为迭代函数(即非递归函数)。
Exercise 7.21:What would happen if the stopping condition in factorial were:
如是函数 factorial 的终止条件为:
     if (val != 0)



会出现什么问题?



      

7.4. Function Declarations
7.4. 函数声明
Just as variables must be declared before they are used, a function must be declared before it is called. As with a variable definition (Section 2.3.5, p. 52), we can declare a function separately from its definition; a function may be defined only once but may be declared multiple times.
正如变量必须先声明后使用一样,函数也必须在被调用之前先声明。与变量的定义(第 2.3.5 节)类似,函数的声明也可以和函数的定义分离;一个函数只能定义一次,但是可声明多次。
A function declaration consists of a return type, the function name, and parameter list. The parameter list must contain the types of the parameters but need not name them. These three elements are referred to as the function prototype. A function prototype describes the interface of the function.
函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。
Function prototypes provide the interface between the programmer who defines the function and programmers who use it. When we use a function, we program to the function's prototype.
函数原型为定义函数的程序员和使用函数的程序员之间提供了接口。在使用函数时,程序员只对函数原型编程即可。




Parameter names in a function declaration are ignored. If a name is given in a declaration, it should serve as a documentation aid:
函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档:
     void print(int *array, int size);



Function Declarations Go in Header Files
在头文件中提供函数声明
Recall that variables are declared in header files (Section 2.9, p. 67) and defined in source files. For the same reasons, functions should be declared in header files and defined in source files.
回顾前面章节,变量可在头文件中声明(第 2.9 节),而在源文件中定义。同理,函数也应当在头文件中声明,并在源文件中定义。
It may be temptingand would be legalto put a function declaration directly in each source file that uses the function. The problem with this approach is that it is tedious and error-prone. By putting function declarations into header files, we can ensure that all the declarations for a given function agree. If the interface to the function changes, only one declaration must be changed.
把函数声明直接放到每个使用该函数的源文件中,这可能是大家希望的方式,而且也是合法的。但问题在于这种用法比较呆板而且容易出错。解决的方法是把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发生变化,则只要修改其唯一的声明即可。
The source file that defines the function should include the header that declares the function.
定义函数的源文件应包含声明该函数的头文件。




Including the header that contains a function's declaration in the same file that defines the function lets the compiler check that the definition and declaration are the same. In particular, if the definition and declaration agree as to parameter list but differ as to return type, the compiler will issue a warning or error message indicating the discrepancy.
将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。特别地,如果函数定义和函数声明的形参列表一致,但返回类型不一致,编译器会发出警告或出错信息来指出这种差异。
Exercises Section 7.4
Exercise 7.22:Write the prototypes for each of the following functions:
编写下面函数的原型:
A function named compare with two parameters that are references to a class named matrix and with a return value of type bool.
函数名为 compare,有两个形参,都是名为 matrix 的类的引用,返回 bool 类型的值。
A function named change_val that returns a vector<int> iterator and takes two parameters: one is an int and the other is an iterator for a vector<int>.
函数名为 change_val,返回 vector<int> 类型的迭代器,有两个形参:一个是 int 型形参,另一个是 vector<int> 类型的迭代器。
Hint: When you write these prototypes, use the name of the function as an indicator as to what the function does. How does this hint affect the types you use?
提示:写函数原型时,函数名应当暗示函数的功能。考虑这个提示会如何影响你用的类型?
Exercise 7.23:Given the following declarations, determine which calls are legal and which are illegal. For those that are illegal, explain why.
给出下面函数,判断哪些调用是合法的,哪些是不合法的。对于那些不合法的调用,解释原因。
     double calc(double);
     int count(const string &, char);
     int sum(vector<int>::iterator, vector<int>::iterator, int);
     vector<int> vec(10);

     (a) calc(23.4, 55.1);
     (b) count("abcda", 'a');
     (c) calc(66);
     (d) sum(vec.begin(), vec.end(), 3.8);






7.4.1. Default Arguments
7.4.1. 默认实参
A default argument is a value that, although not universally applicable, is the argument value that is expected to be used most of the time. When we call the function, we may omit any argument that has a default. The compiler will supply the default value for any argument we omit.
默认实参是一种虽然并不普遍、但在多数情况下仍然适用的实参值。调用函数时,可以省略有默认值的实参。编译器会为我们省略的实参提供默认值。
A default argument is specified by providing an explicit initializer for the parameter in the parameter list. We may define defaults for one or more parameters. However, if a parameter has a default argument, all the parameters that follow it must also have default arguments.
默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。
For example, a function to create and initialize a string intended to simulate a window can provide default arguments for the height, width, and background character of the screen:
例如,下面的函数创建并初始化了一个 string 对象,用于模拟窗口屏幕。此函数为窗口屏幕的高、宽和背景字符提供了默认实参:
     string screenInit(string::size_type height = 24,
                       string::size_type width = 80,
                       char background = ' ' );



A function that provides a default argument for a parameter can be invoked with or without an argument for this parameter. If an argument is provided, it overrides the default argument value; otherwise, the default argument is used. Each of the following calls of screenInit is correct:
调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。下面的函数 screenInit 的调用都是正确的:
     string screen;
     screen = screenInit();       // equivalent to screenInit (24,80,' ')
     screen = screenInit(66);     // equivalent to screenInit (66,80,' ')
     screen = screenInit(66, 256);       // screenInit(66,256,' ')
     screen = screenInit(66, 256, '#');



Arguments to the call are resolved by position, and default arguments are used to substitute for the trailing arguments of a call. If we want to specify an argument for background, we must also supply arguments for height and width:
函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。例如,如果要给 background 提供实参,那么也必须给 height 和 width 提供实参:
     screen = screenInit(, , '?'); // error, can omit only trailing arguments
     screen = screenInit( '?');    // calls screenInit('?',80,' ')



Note that the second call, which passes a single character value, is legal. Although legal, it is unlikely to be what the programmer intended. The call is legal because '?' is a char, and a char can be promoted to the type of the leftmost parameter. That parameter is string::size_type, which is an unsigned integral type. In this call, the char argument is implicitly promoted to string::size_type, and passed as the argument to height.
注意第二个调用,只传递了一个字符值,虽然这是合法的,但是却并不是程序员的原意。因为 '?' 是一个 char,char 可提升为最左边形参的类型,所以这个调用是合法的。最左边的形参具有 string::size_type 类型,这是 unsigned 整型。在这个调用中,char 实参隐式地提升为 string::size_type 类型,并作为实参传递给形参 height。
Because char is an integral type (Section 2.1.1, p. 34), it is legal to pass a char to an int parameter and vice versa. This fact can lead to various kinds of confusion, one of which arises in functions that take both char and int parametersit can be easy for callers to pass the arguments in the wrong order. Using default arguments can compound this problem.
因为 char 是整型(第 2.1.1 节),因此把一个 char 值传递给 int 型形参是合法的,反之亦然。这个事实会导致很多误解。例如,如果函数同时含有 char 型和 int 型形参,则调用者很容易以错误的顺序传递实参。如果使用默认实参,则这个问题会变得更加复杂。




Part of the work of designing a function with default arguments is ordering the parameters so that those least likely to use a default value appear first and those most likely to use a default appear last.
设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。
Default Argument Initializers
默认实参的初始化式
A default argument can be any expression of an appropriate type:
默认实参可以是任何适当类型的表达式:
     string::size_type screenHeight();
     string::size_type screenWidth(string::size_type);
     char screenDefault(char = ' ');
     string screenInit(
         string::size_type height = screenHeight(),
         string::size_type width = screenWidth(screenHeight()),
         char background = screenDefault());



When the default argument is an expression, and the default is used as the argument, then the expression is evaluated at the time the function is called. For example, screenDefault is called to obtain a value for background every time screenInit is called without a third argument.
如果默认实参是一个表达式,而且默认值用作实参,则在调用函数时求解该表达式。例如,每次不带第三个实参调用函数 screenInit 时,编译器都会调用函数 screenDefault 为 background 获得一个值。
Constraints on Specifying Default Arguments
指定默认实参的约束
We can specify default argument(s) in either the function definition or declaration. However, a parameter can have its default argument specified only once in a file. The following is an error:
既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:
     // ff.h
     int ff(int = 0);

     // ff.cc
     #include "ff.h"
     int ff(int i = 0) { /* ... */ } // error



Default arguments ordinarily should be specified with the declaration for the function and placed in an appropriate header.
通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。




If a default argument is provided in the parameter list of a function definition, the default argument is available only for function calls in the source file that contains the function definition.
如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。
Exercises Section 7.4.1
Exercise 7.24:Which, if any, of the following declarations are errors? Why?
如果有的话,指出下面哪些函数声明是错误的?为什么?
     (a) int ff(int a, int b = 0, int c = 0);
     (b) char *init(int ht = 24, int wd, char bckgrnd);



Exercise 7.25:Given the following function declarations and calls, which, if any, of the calls are illegal? Why? Which, if any, are legal but unlikely to match the programmer's intent? Why?
假设有如下函数声明和调用,指出哪些调用是不合法的?为什么?哪些是合法的但可能不符合程序员的原意?为什么?
     // declarations
     char *init(int ht, int wd = 80, char bckgrnd = ' ');

     (a) init();
     (b) init(24,10);
     (c) init(14, '*');



Exercise 7.26:Write a version of make_plural with a default argument of 's'. Use that version to print singular and plural versions of the words "success" and "failure".
用字符 's' 作为默认实参重写函数 make_plural。利用这个版本的函数输出单词“success”和“failure”的单数和复数形式。


      

7.5. Local Objects
7.5. 局部对象
In C++, names have scope, and objects have lifetimes. To understand how functions operate, it is important to understand both of these concepts. The scope of a name is the part of the program's text in which that name is known. The lifetime of an object is the time during the program's execution that the object exists.
在 C++ 语言中,每个名字都有作用域,而每个对象都有生命期。要弄清楚函数是怎么运行的,理解这两个概念十分重要。名字的作用域指的是知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。
The names of parameters and variables defined within a function are in the scope of the function: The names are visible only within the function body. As usual, a variable's name can be used from the point at which it is declared or defined until the end of the enclosing scope.
在函数中定义的形参和变量的名字只位于函数的作用域中:这些名字只在函数体中可见。通常,变量名从声明或定义的地方开始到包围它的作用域结束处都是可用的。
7.5.1. Automatic Objects
7.5.1. 自动对象
By default, the lifetime of a local variable is limited to the duration of a single execution of the function. Objects that exist only while a function is executing are known as automatic objects. Automatic objects are created and destroyed on each call to a function.
默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。
The automatic object corresponding to a local variable is created when the function control path passes through the variable's definition. If the definition contains an initializer, then the object is given an initial value each time the object is created. Uninitialized local variables of built-in type have undefined values. When the function terminates, the automatic objects are destroyed.
局部变量所对应的自动对象在函数控制经过变量定义语句时创建。如果在定义时提供了初始化式,那么每次创建对象时,对象都会被赋予指定的初值。对于未初始化的内置类型局部变量,其初值不确定。当函数调用结束时,自动对象就会撤销。
Parameters are automatic objects. The storage in which the parameters reside is created when the function is called and is freed when the function terminates.
形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。
Automatic objects, including parameters, are destroyed at the end of the block in which they were defined. Parameters are defined in the function's block and so are destroyed when the function terminates. When a function exits, its local storage is deallocated. After the function exits, the values of its automatic objects and parameters are no longer accessible.
自动对象,包括形参,都在定义它们的块语句结束时撤销。形参在函数块中定义,因此当函数的执行结束时撤销。当函数结束时,会释放它的局部存储空间。在函数结束后,自动对象和形参的值都不能再访问了。
7.5.2. Static Local Objects
7.5.2. 静态局部对象
It is can be useful to have a variable that is in the scope of a function but whose lifetime persists across calls to the function. Such objects are defined as static.
一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。
A static local object is guaranteed to be initialized no later than the first time that program execution passes through the object's definition. Once it is created, it is not destroyed until the program terminates; local statics are not destroyed when the function ends. Local statics continue to exist and hold their value across calls to the function. As a trivial example, consider a function that counts how often it is called:
static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。考虑下面的小例子,这个函数计算了自己被调用的次数:
     size_t count_calls()
     {
          static size_t ctr = 0; // value will persist across calls
          return ++ctr;
     }
     int main()
     {
         for (size_t i = 0; i != 10; ++i)
             cout << count_calls() << endl;
         return 0;
     }



This program will print the numbers from 1 through 10 inclusive.
这个程序会依次输出 1 到 10(包含 10)的整数。
Before count_calls is called for the first time, ctr is created and given an initial value of 0. Each call increments ctr and returns its current value. Whenever count_calls is executed, the variable ctr already exists and has whatever value was in the variable the last time the function exited. Thus, on the second invocation, the value is 1, on the third it is 2, and soon.
在第一次调用函数 count_calls 之前,ctr 就已创建并赋予初值 0。每次函数调用都使加 1,并且返回其当前值。在执行函数 count_calls 时,变量 ctr 就已经存在并且保留上次调用该函数时的值。因此,第二次调用时,ctr 的值为 1,第三次为 2,依此类推。
Exercises Section 7.5.2
Exercise 7.27:Explain the differences between a parameter, a local variable and a static local variable. Give an example of a program in which each might be useful.
解释形参、局部变量和静态局部变量的差别。并给出一个有效使用了这三种变量的程序例子。
Exercise 7.28:Write a function that returns 0 when it is first called and then generates numbers in sequence each time it is called again.
编写函数,使其在第一次调用时返回 0,然后再次调用时按顺序产生正整数(即返回其当前的调用次数)。



      

7.6. Inline Functions
7.6. 内联函数
Recall the function we wrote on page 248 that returned a reference to the shorter of its two string parameters:
回顾在第 7.3.2 节编写的那个返回两个 string 形参中较短的字符串的函数:
     // find longer of two strings
     const string &shorterString(const string &s1, const string &s2)
     {
         return s1.size() < s2.size() ? s1 : s2;
     }



The benefits of defining a function for such a small operation include:
为这样的小操作定义一个函数的好处是:
It is easier to read and understand a call to shorterString than it would be to read and interpret an expression that used the equivalent conditional expression in place of the function call.
阅读和理解函数 shorterString 的调用,要比读一条用等价的条件表达式取代函数调用表达式并解释它的含义要容易得多。
If a change needs to be made, it is easier to change the function than to find and change every occurrence of the equivalent expression.
如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多。
Using a function ensures uniform behavior. Each test is guaranteed to be implemented in the same manner.
使用函数可以确保统一的行为,每个测试都保证以相同的方式实现。
The function can be reused rather than rewritten for other applications.
函数可以重用,不必为其他应用重写代码。
There is, however, one potential drawback to making shorterString a function: Calling a function is slower than evaluating the equivalent expression. On most machines, a function call does a lot of work: registers are saved before the call and restored after the return; the arguments are copied; and the program branches to a new location.
但是,将 shorterString 写成函数有一个潜在的缺点:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作;调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行。
inline Functions Avoid Function Call Overhead
inline 函数避免函数调用的开销
A function specified as inline (usually) is expanded "in line" at each point in the program in which it is invoked. Assuming we made shorterString an inline function, then this call
将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。假设我们将 shorterString 定义为内联函数,则调用:
         cout << shorterString(s1, s2) << endl;



would be expanded during compilation into something like
在编译时将展开为:
         cout << (s1.size() < s2.size() ? s1 : s2)
              << endl;



The run-time overhead of making shorterString a function is thus removed.
从而消除了把 shorterString 写成函数的额外执行开销。
We can define shorterString as an inline function by specifying the keyword inline before the function's return type:
从而消除了把 shorterString 写成函数的额外执行开销。
     // inline version: find longer of two strings
     inline const string &
     shorterString(const string &s1, const string &s2)
     {
             return s1.size() < s2.size() ? s1 : s2;
     }



The inline specification is only a request to the compiler. The compiler may choose to ignore this request.
inline 说明对于编译器来说只是一个建议,编译器可以选择忽略这个。




In general, the inline mechanism is meant to optimize small, straight-line functions that are called frequently. Many compilers will not inline a recursive function. A 1,200-line function is also not likely to be explanded inline.
一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。大多数的编译器都不支持递归函数的内联。一个 1200 行的函数也不太可能在调用点内联展开。
Put inline Functions in Header Files
把 inline 函数放入头文件
Unlike other function definitions, inlines should be defined in header files.
内联函数应该在头文件中定义,这一点不同于其他函数。




To expand the code of an inline function at the point of call, the compiler must have access to the function definition. The function prototype is insufficient.
inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。
An inline function may be defined more than once in a program as long as the definition appears only once in a given source file and the definition is exactly the same in each source file. By putting inline functions in headers, we ensure that the same definition is used whenever the function is called and that the compiler has the function definition available at the point of call.
inline 函数可能要在程序中定义不止一次,只要 inline 函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的。把 inline 函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。
Whenever an inline function is added to or changed in a header file, every source file that uses that header must be recompiled.
在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。



Exercises Section 7.6
Exercise 7.29:Which one of the following declarations and definitions would you put in a header? In a program text file? Explain why.
对于下面的声明和定义,你会将哪个放在头文件,哪个放在程序文本文件呢?为什么?
     (a) inline bool eq(const BigInt&, const BigInt&) {...}
     (b) void putValues(int *arr, int size);



Exercise 7.30:Rewrite the is Shorter function from page 235 as an inline function.
第 7.2.2 节的函数 is Shorter 改写为 inline 函数。



      

7.7. Class Member Functions
7.7. 类的成员函数
In Section 2.8 (p. 63) we began the definition of the Sales_item class used in solving the bookstore problem from Chapter 1. Now that we know how to define ordinary functions, we can continue to fill in our class by defining the member functions of this class.
第 2.8 节开始定义类 Sales_item,用于解决第一章的书店问题。至此,我们已经了解了如何定义普通函数,现在来定义类的成员函数,以继续完善这个类。
We define member functions similarly to how we define ordinary functions. As with any function, a member function consists of four parts:
成员函数的定义与普通函数的定义类似。和任何函数一样,成员函数也包含下面四个部分:
A return type for the function
函数返回类型。
The function name
函数名。
A (possibly empty) comma-separated list of parameters
用逗号隔开的形参表(也可能是空的)。
The function body, which is contained between a pair of curly braces
包含在一对花括号里面的函数体。
As we know, the first three of these parts constitute the function prototype. The function prototype defines all the type information related to the function: what its return type is, the function name, and what types of arguments may be passed to it. The function prototype must be defined within the class body. The body of the function, however, may be defined within the class itself or outside the class body.
正如我们知道的,前面三部分组成函数原型。函数原型定义了所有和函数相关的类型信息:函数返回类型是什么、函数的名字、应该给这个函数传递什么类型的实参。函数原型必须在类中定义。但是,函数体则既可以在类中也可以在类外定义。
With this knowledge, let's look at our expanded class definition, to which we've added two new members: the member functions avg_price and same_isbn. The avg_price function has an empty parameter list and returns a value of type double. The same_isbn function returns a bool and takes a single parameter of type reference to const Sales_item.
知道这些后,观察下面扩展的类定义,我们为这个类增加了两个新成员:成员函数 avg_price 和 same_isbn。其中 avg_price 函数的形参表是空的,返回 double 类型的值。而 same_isbn 函数则返回 bool 对象,有一个 const Sales_item 类型的引用形参。
     class Sales_item {
     public:
         // operations on Sales_item objects
         double avg_price() const;
         bool same_isbn(const Sales_item &rhs) const
              { return isbn == rhs.isbn; }
     // private members as before
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };



We'll explain the meaning of the const that follows the parameter lists shortly, but first we need to explain how member functions are defined.
在解释跟在形参表后面的 const 之前,必须先说明成员函数是如何定义的。
7.7.1. Defining the Body of a Member Function
7.7.1. 定义成员函数的函数体
We must declare all the members of a class within the curly braces that delimit the class definition. There is no way to subsequently add any members to the class. Members that are functions must be defined as well as declared. We can define a member function either inside or outside of the class definition. In Sales_item, we have one example of each: same_isbn is defined inside the Sales_item class, whereas avg_price is declared inside the class but defined elsewhere.
类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。在类 Sales_item 中,这两种情况各有一例说明:函数 same_isbn 在类内定义,而函数 avg_price 则在类内声明,在类外定义。
A member function that is defined inside the class is implicitly treated as an inline function (Section 7.6, p. 256).
编译器隐式地将在类内定义的成员函数当作内联函数(第 7.6 节)。
Let's look in more detail at the definition of same_isbn:
再详细观察函数 same_isbn 的定义:
     bool same_isbn(const Sales_item &rhs) const
         { return isbn == rhs.isbn; }



As with any function, the body of this function is a block. In this case, the block contains a single statement that returns the result of comparing the value of the isbn data members of two Sales_item objects.
与任何函数一样,该函数的函数体也是一个块。在这个函数中,块中只有一个语句,比较两个 Sales_item 对象的数据成员 isbn 的值,并返回比较结果。
The first thing to note is that the isbn member is private. Even though these members are private, there is no error.
首先要注意的是:成员 isbn 是 private 的。尽管如此,上述语句却没有任何错误。
A member function may access the private members of its class.
类的成员函数可以访问该类的 private 成员。




More interesting is understanding from which Sales_item objects does the function get the values that it compares. The function refers both to isbn and rhs.isbn. Fairly clearly, rhs.isbn uses the isbn member from the argument passed to the function. The unqualified use of isbn is more interesting. As we shall see, the unqualified isbn refers to the isbn member of the object on behalf of which the function is called.
更有意思的是,函数从哪个 Sales_item 类对象得到这个用于比较的值?函数涉及到 isbn 和 rhs.isbn。很明显,rhs.isbn 使用的是传递给此函数的实参的 isbn 成员。没有前缀的 isbn 的用法更加有意思。正如我们所看见的,这个没有前缀的 isbn 指的是用于调用函数的对象的 isbn 成员。
Member Functions Have an Extra, Implicit Parameter
成员函数含有额外的、隐含的形参
When we call a member function, we do so on behalf of an object. For example, when we called same_isbn in the bookstore program on page 26, we executed the same_isbn member on the object named total:
调用成员函数时,实际上是使用对象来调用的。例如,调用 第 1.6 节书店程序中的函数 same_isbn,是通过名为 total 的对象来执行 same_isbn 函数的:
     if (total.same_isbn(trans))



In this call, we pass the object trans. As part of executing the call, the object trans is used to initialize the parameter rhs. Thus, in this call, rhs.isbn is a reference to trans.isbn.
在这个调用中,传递了对象 trans。作为执行调用的一部分,使用对象 trans 初始化形参 rhs。于是,rhs.isbn 是 trans.isbn 的引用。
The same argument-binding process is used to bind the unqualified use of isbn to the object named total. Each member function has an extra, implicit parameter that binds the function to the object on which the function was called. When we call same_isbn on the object named total, that object is also passed to the function. When same_isbn refers to isbn, it is implicitly referring to the isbn member of the object on which the function was called. The effect of this call is to compare total.isbn with Trans.isbn.
而没有前缀的 isbn 使用了相同的实参绑定过程,使之与名为 total 的对象绑定起来。每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。当调用名为 total 的对象的 same_isbn 时,这个对象也传递给了函数。而 same_isbn 函数使用 isbn 时,就隐式地使用了调用该函数的对象的 isbn 成员。这个函数调用的效果是比较 total.isbn 和 trans.isbn 两个值。
Introducing this
this 指针的引入
Each member function (except for static member functions, which we cover in Section 12.6 (p. 467)) has an extra, implicit parameter named this. When a member function is called, the this parameter is initialized with the address of the object on which the function was invoked. To understand a member function call, we might think that when we write
每个成员函数(除了在第 12.6 节介绍的 static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。为了理解成员函数的调用,可考虑下面的语句:
     total.same_isbn(trans);



it is as if the compiler rewrites the call as
就如编译器这样重写这个函数调用:
     // pseudo-code illustration of how a call to a member function is translated
     Sales_item::same_isbn(&total, trans);



In this call, the data member isbn inside same_isbn is bound to the one belonging to total.
在这个调用中,函数 same_isbn 中的数据成员 isbn 属于对象 total。
Introducing const Member Functions
const 成员函数的引入
We now can understand the role of the const that follows the parameter lists in the declarations of the Sales_item member functions: That const modifies the type of the implicit this parameter. When we call total.same_isbn(trans), the implicit this parameter will be a const Sales_Item* that points to total. It is as if the body of same_isbn were written as
现在,可以理解跟在 Sales_item 成员函数声明的形参表后面的 const 所起的作用了:const 改变了隐含的 this 形参的类型。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的 const Sales_Item* 类型的指针。就像如下编写 same_isbn 的函数体一样:
     // pseudo-code illustration of how the implicit this pointer is used
     // This code is illegal: We may not explicitly define the this pointer ourselves
     // Note that this is a pointer to const because same_isbn is a const member
     bool Sales_item::same_isbn(const Sales_item *const this,
                               const Sales_item &rhs) const
     { return (this->isbn == rhs.isbn); }



A function that uses const in this way is called a const member function. Because this is a pointer to const, a const member function cannot change the object on whose behalf the function is called. Thus, avg_price and same_isbn may read but not write to the data members of the objects on which they are called.
用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。因此,函数 avg_price 和函数 same_isbn 只能读取而不能修改调用它们的对象的数据成员。
A const object or a pointer or reference to a const object may be used to call only const member functions. It is an error to try to call a nonconst member function on a const object or through a pointer or reference to a const object.
const 对象、指向 const 对象的指针或引用只能用于调用其 const 成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的。




Using the this Pointer
this 指针的使用
Inside a member function, we need not explicitly use the this pointer to access the members of the object on which the function was called. Any unqualified reference to a member of our class is assumed to be a reference through this:
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用:
     bool same_isbn(const Sales_item &rhs) const
         { return isbn == rhs.isbn; }



The uses of isbn in this function are as if we had written this->units_sold or this->revenue.
在这个函数中 isbn 的用法与 this->units_sold 或 this->revenue 的用法一样。
The this parameter is defined implicitly, so it is unnecessary and in fact illegal to include the this pointer in the function's parameter list. However, in the body of the function we can refer to the this pointer explicitly. It is legal, although unnecessary, to define same_isbn as follows:
由于 this 指针是隐式定义的,因此不需要在函数的形参表中包含 this 指针,实际上,这样做也是非法的。但是,在函数体中可以显式地使用 this 指针。如下定义函数 same_isbn 尽管没有必要,但是却是合法的:
     bool same_isbn(const Sales_item &rhs) const
         { return this->isbn == rhs.isbn; }



7.7.2. Defining a Member Function Outside the Class
7.7.2. 在类外定义成员函数
Member functions defined outside the class definition must indicate that they are members of the class:
在类的定义外面定义成员函数必须指明它们是类的成员:
     double Sales_item::avg_price() const
     {
         if (units_sold)
             return revenue/units_sold;
         else
             return 0;
     }



This definition is like the other functions we've seen: It has a return type of double and an empty parameter list enclosed in parentheses after the function name. What is new is the const following the parameter list and the form of the function name. The function name
上述定义和其他函数一样:该函数返回类型为 double,在函数名后面的圆括号起了一个空的形参表。新的内容则包括跟在形参表后面的 const 和函数名的形式。函数名:
     Sales_item::avg_price



uses the scope operator (Section 1.2.2, p. 8) to say that we are defining the function named avg_price that is defined in the scope of the Sales_item class.
使用作用域操作符(第 1.2.2 节)指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。
The const that follows the parameter list reflects the way we declared the member funcion inside the Sales_item header. In any definition, the return type and parameter list must match the declaration, if any, of the function. In the case of a member function, the declaration is as it appears in the class definition. If the function is declared to be a const member function, then the const after the parameter list must be included in the definition as well.
形参表后面的 const 则反映了在类 Sales_item 中声明成员函数的形式。在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。对于成员函数,函数声明必须与其定义一致。如果函数被声明为 const 成员函数,那么函数定义时形参表后面也必须有 const。
We can now fully understand the first line of this code: It says we are defining the avg_price function from the Sales_item class and that the function is a const member. The function takes no (explicit) parameters and returns a double.
现在可以完全理解第一行代码了:这行代码说明现在正在定义类 Sales_item 的函数 avg_price,而且这是一个 const 成员函数,这个函数没有(显式的)形参,返回 double 类型的值。
The body of the function is easier to understand: It tests whether units_sold is nonzero and, if so, returns the result of dividing revenue by units_sold. If units_sold is zero, we can't safely do the divisiondividing by zero has undefined behavior. In this program, we return 0, indicating that if there were no sales the average price would be zero. Depending on the sophistication of our error-handling strategy, we might instead throw an exception (Section 6.13, p. 215).
函数体更加容易理解:检查 units_sold 是否为 0,如果不为 0,返回 revenue 除以 units_sold 的结果;如果 units_sold 是 0,不能安全地进行除法运算——除以 0 是未定义的行为。此时程序返回 0,表示没有任何销售时平均售价为 0。根据异常错误处理策略,也可以抛出异常来代替刚才的处理(第 6.13 节)。
7.7.3. Writing the Sales_item Constructor
7.7.3. 编写 Sales_item 类的构造函数
There's one more member that we need to write: a constructor. As we learned in Section 2.8 (p. 65), class data members are not initialized when the class is defined. Instead, data members are initialized through a constructor.
还必须编写一个成员,那就是构造函数。正如在第 2.8 节所学习的,在定义类时没有初始化它的数据成员,而是通过构造函数来初始化其数据成员。
Constructors Are Special Member Functions
构造函数是特殊的成员函数
A constructor is a special member function that is distinguished from other member functions by having the same name as its class. Unlike other member functions, constructors have no return type. Like other member functions they take a (possibly empty) parameter list and have a function body. A class can have multiple constructors. Each constructor must differ from the others in the number or types of its parameters.
构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。
The constructor's parameters specify the initializers that may be used when creating objects of the class type. Ordinarily these initializers are used to initialize the data members of the newly created object. Constructors usually should ensure that every data member is initialized.
构造函数的形参指定了创建类类型对象时使用的初始化式。通常,这些初始化式会用于初始化新创建对象的数据成员。构造函数通常应确保其每个数据成员都完成了初始化。
The Sales_item class needs to explicitly define only one constructor, the default constructor, which is the one that takes no arguments. The default constructor says what happens when we define an object but do not supply an (explicit) initializer:
Sales_item 类只需要显式定义一个构造函数:没有形参的默认构造函数。默认构造函数说明当定义对象却没有为它提供(显式的)初始化式时应该怎么办:
     vector<int> vi;       // default constructor: empty vector
     string s;             // default constructor: empty string
     Sales_item item;      // default constructor: ???



We know the behavior of the string and vector default constructors: Each of these constructors initializes the object to a sensible default state. The default string constructor generates an empty string, the one that is equal to "". The default vector constructor generates a vector with no elements.
我们知道 string 和 vector 类默认构造函数的行为:这些构造函数会将对象初始化为合理的默认状态。string 的默认构造函数会产生空字符串上,相当于 ""。vector 的默认构造函数则生成一个没有元素的 vector 向量对象。
Similarly, we'd like the default constructor for Sales_items to generate an empty Sales_item. Here "empty" means an object in which the isbn is the empty string and the units_sold and revenue members are initialized to zero.
同样地,我们希望类 Sales_items 的默认构造函数为它生成一个空的 Sales_item 对象。这里的“空”意味着对象中的 isbn 是空字符串,units_sold 和 revenue 则初始化为 0。
Defining a Constructor
构造函数的定义
Like any other member function, a constructor is declared inside the class and may be defined there or outside the class. Our constructor is simple, so we will define it inside the class body:
和其他成员函数一样,构造函数也必须在类中声明,但是可以在类中或类外定义。由于我们的构造函数很简单,因此在类中定义它:
     class Sales_item {
     public:
         // operations on Sales_item objects
         double avg_price() const;
         bool same_isbn(const Sales_item &rhs) const
             { return isbn == rhs.isbn; }
         // default constructor needed to initialize members of built-in type
         Sales_item(): units_sold(0), revenue(0.0) { }
     // private members as before
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };



Before we explain the constructor definition, note that we put the constructor in the public section of the class. Ordinarily, and certainly in this case, we want the constructor(s) to be part of the interface to the class. After all, we want code that uses the Sales_item type to be able to define and initialize Sales_item objects. Had we made the constructor private, it would not be possible to define Sales_item objects, which would make the class pretty useless.
在解释任何构造函数的定义之前,注意到构造函数是放在类的 public 部分的。通常构造函数会作为类的接口的一部分,这个例子也是这样。毕竟,我们希望使用类 Sales_item 的代码可以定义和初始化类 Sales_item 的对象。如果将构造函数定义为 private 的,则不能定义类 Sales_item 的对象,这样的话,这个类就没有什么用了。
As to the definition itself
对于定义本身:
     // default constructor needed to initialize members of built-in type
     Sales_item(): units_sold(0), revenue(0.0) { }



it says that we are defining a constructor for the Sales_item class that has an empty parameter list and an empty function body. The interesting part is the colon and the code between it and the curly braces that define the (empty) function body.
上述语句说明现在正在定义类 Sales_item 的构造函数,这个构造函数的形参表和函数体都为空。令人感兴趣的是冒号和冒号与定义(空)函数体的花括号之间的代码。
Constructor Initialization List
构造函数和初始化列表
The colon and the following text up to the open curly is the constructor initializer list. A constructor initializer list specifies initial values for one or more data members of the class. It follows the constructor parameter list and begins with a colon. The constructor initializer is a list of member names, each of which is followed by that member's initial value in parentheses. Multiple member initializations are separated by commas.
在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
This initializer list says that both the units_sold and revenue members should be initialized to 0. Whenever a Sales_item object is created, these members will start out as 0. We need not specify an initial value for the isbn member. Unless we say otherwise in the constructor initializer list, members that are of class type are automatically initialized by that class' default constructor. Hence, isbn is initialized by the string default constructor, meaning that isbn initially is the empty string. Had we needed to, we could have specified a default value for isbn in the initializer list as well.
上述例题的初始化列表表明 units_sold 和 revenue 成员都应初始化为 0。每当创建 Sales_item 对象时,它的这两个成员都以初值 0 出现。而 isbn 成员可以不必准确指明其初值。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。于是,isbn 由 string 类的默认构造函数初始化为空串。当然,如果有必要的话,也可以在初始化列表中指明 isbn 的默认初值。
Having explained the initializer list, we can now understand the constructor: Its parameter list and the function body are both empty. The parameter list is empty because we are defining the constructor that is run by default, when no initializer is present. The body is empty because there is no work to do other than initializing units_sold and revenue. The initializer list explicitly initializes units_sold and revenue to zero and implicitly initializes isbn to the empty string. Whenever we create a Sales_item object, the data members will start out with these values.
解释了初始化列表后,就可以深入地了解这个构造函数了:它的形参表和函数体都为空。形参表为空是因为正在定义的构造函数是默认调用的,无需提供任何初值。函数体为空是因为除了初始化 units_sold 和 revenue 成员外没有其他工作可做了。初始化列表显式地将 units_sold 和 revenue 初始化为 0,并隐式地将 isbn 初始化为空串。当创建新 Sales_item 对象时,数据成员将以这些值出现。
Synthesized Default Constructor
合成的默认构造函数
If we do not explicitly define any constructors, then the compiler will generate the default constructor for us.
如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。




The compiler-created default constructor is known as a synthesized default constructor. It initializes each member using the same rules as are applied for variable initializations (Section 2.3.4, p. 50). Members that are of class type, such as isbn, are initialized by using the default constructor of the member's own class. The initial value of members of built-in type depend on how the object is defined. If the object is defined at global scope (outside any function) or is a local static object, then these members will be initialized to 0. If the object is defined at local scope, these members are uninitialized. As usual, using an uninitialized member for any purpose other than giving it a value is undefined.
由编译器创建的默认构造函数通常称为默认构造函数,它将依据如同变量初始化(第 2.3.4 节)的规则初始化类中所有成员。对于具有类类型的成员,如 isbn,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。
The synthesized default constructor often suffices for classes that contain only members of class type. Classes with members of built-in or compound type should usually define their own default constructors to initialize those members.
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。




Because the synthesized constructor does not automatically initialize members of built-in type, we had to define the Sales_item default constructor explicitly.
由于合成的默认构造函数不会自动初始化内置类型的成员,所以必须明确定义 Sales_item 类的默认构造函数。
7.7.4. Organizing Class Code Files
7.7.4. 类代码文件的组织
As we saw in Section 2.9 (p. 67), class declarations ordinarily are placed in headers. Usually, member functions defined outside the class are put in ordinary source files. C++ programmers tend to use a simple naming convention for headers and the associated class definition code. The class definition is put in a file named type .h or type .H, where type is the name of the class defined in the file. Member function definitions usually are stored in a source file whose name is the name of the class. Following this convention we put the Sales_item class definition in a file named Sales_item.h. Any program that wants to use the class must include that header. We should put the definition of our Sales_item functions in a file named Sales_item.cc. That file, like any other file that uses the Sales_item type, would include the Sales_item.h header.
正如在第 2.9 节提及的,通常将类的声明放置在头文件中。大多数情况下,在类外定义的成员函数则置于源文件中。C++ 程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为 type.h 或 type.H 的文件中,type 指在该文件中定义的类的名字。成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类 Sales_item 放在名为 Sales_item.h 的文件中定义。任何需使用这个类的程序,都必须包含这个头文件。而 Sales_item 的成员函数的定义则应该放在名为 Sales_item.cc 的文件中。这个文件同样也必须包含 Sales_item.h 头文件。
Exercises Section 7.7.4
Exercise 7.31:Write your own version of the Sales_item class, adding two new public members to read and write Sales_item objects. These functions should operate similarly to the input and output operators used in Chapter 1. Transactions should look like the ones defined in that chapter as well. Use this class to read and write a set of transactions.
编写你自己的 Sales_item 类,添加两个公用(public)成员用于读和写 Sales_item 对象。这两个成员函数的功能应类似于第一章介绍的输入输出操作符。交易也应类似于那一章所定义的。利用这个类读入并输出一组交易。
Exercise 7.32:Write a header file to contain your version of the Sales_item class. Use ordinary C++ conventions to name the header and any associated file needed to hold non-inline functions defined outside the class.
编写一个头文件,包含你自己的 Sales_item 类。使用通用的 C++ 规则给这个头文件以及任何相关的文件命名,这些文件用于存储在类外定义的非内联函数。
Exercise 7.33:Add a member that adds two Sales_items. Use the revised class to reimplement your solution to the average price problem presented in Chapter 1.
在 Sales_item 类中加入一个成员,用于添加两个 Sales_item 对象。使用修改后的类重新解决第一章给出的平均价格问题。


 
      

7.8. Overloaded Functions
7.8. 重载函数
Two functions that appear in the same scope are overloaded if they have the same name but have different parameter lists.
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
If you have written an arithmetic expression in a programming language, you have used an overloaded function. The expression
使用某种程序设计语言编写过算术表达式的程序员都肯定使用过重载函数。表达式
     1 + 3



invokes the addition operation for integer operands, whereas the expression
调用了针对整型操作数加法操作符,而表达式
     1.0 + 3.0



invokes a different operation that adds floating-point operands. It is the compiler's responsibility, not the programmer's, to distinguish between the different operations and to apply the appropriate operation depending on the operands' types.
调用了另外一个专门处理浮点操作数的不同的加法操作。根据操作数的类型来区分不同的操作,并应用适当的操作,是编译器的责任,而不是程序员的事情。
Similarly, we may define a set of functions that perform the same general action but that apply to different parameter types. These functions may be called without worrying about which function is invoked, much as we can add ints or doubles without worrying whether integer arithmetic or floating-point arithmetic is performed.
类似地,程序员可以定义一组函数,它们执行同样的一般性动作,但是应用在不同的形参类型上,调用这些函数时,无需担心调用的是哪个函数,就像我们不必操心执行的是整数算术操作还是浮点数自述操作就可以实现 int 型加法或 double 型加法一样。
Function overloading can make programs easier to write and to understand by eliminating the need to inventand remembernames that exist only to help the compiler figure out which function to call. For example, a database application might well have several lookup functions that could do the lookup based on name, phone number, account number, and so on. Function overloading allows us to define a collection of functions, each named lookup, that differ in terms of what values they use to do the search. We can call lookup passing a value of any of several types:
通过省去为函数起名并记住函数名字的麻烦,函数重载简化了程序的实现,使程序更容易理解。函数名只是为了帮助编译器判断调用的是哪个函数而已。例如,一个数据库应用可能需要提供多个 lookup 函数,分别实现基于姓名、电话号码或账号之类的查询功能。函数重载使我们可以定义一系列的函数,它们的名字都是 lookup,不同之处在于用于查询的值不相同。如此可传递几种类型中的任一种值调用 lookup 函数:
     Record lookup(const Account&);  // find by Account
     Record lookup(const Phone&);    // find by Phone
     Record lookup(const Name&);     // find by Name
     Record r1, r2;
     r1 = lookup(acct);                  // call version that takes an Account
     r2 = lookup(phone);                 // call version that takes a Phone



Here, all three functions share the same name, yet they are three distinct functions. The compiler uses the argument type(s) passed in the call to figure out which function to call.
这里的三个函数共享同一个函数名,但却是三个不同的函数。编译器将根据所传递的实参类型来判断调用的是哪个函数。
To understand function overloading, we must understand how to define a set of overloaded functions and how the compiler decides which function to use for a given call. We'll review these topics in the remainder of this section.
要理解函数重载,必须理解如何定义一组重载函数和编译器如何决定对某一调用使用哪个函数。本节的其余部分将会回顾这些主题。
There may be only one instance of main in any program. The main function may not be overloaded.
任何程序都仅有一个 main 函数的实例。main 函数不能重载。




Distinguishing Overloading from Redeclaring a Function
函数重载和重复声明的区别
If the return type and parameter list of two functions declarations match exactly, then the second declaration is treated as a redeclaration of the first. If the parameter lists of two functions match exactly but the return types differ, then the second declaration is an error:
如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的:
     Record lookup(const Account&);
     bool lookup(const Account&); // error: only return type is different



Functions cannot be overloaded based only on differences in the return type.
函数不能仅仅基于不同的返回类型而实现重载。
Two parameter lists can be identical, even if they don't look the same:
有些看起来不相同的形参表本质上是相同的:
     // each pair declares the same function
     Record lookup(const Account &acct);
     Record lookup(const Account&); // parameter names are ignored
     typedef Phone Telno;
     Record lookup(const Phone&);
     Record lookup(const Telno&); // Telno and Phone are the same type
     Record lookup(const Phone&, const Name&);
     // default argument doesn't change the number of parameters
     Record lookup(const Phone&, const Name& = "");
     // const is irrelevent for nonreference parameters
     Record lookup(Phone);
     Record lookup(const Phone); // redeclaration



In the first pair, the first declaration names its parameter. Parameter names are only a documentation aid. They do not change the parameter list.
在第一对函数声明中,第一个声明给它的形参命了名。形参名只是帮助文档,并没有修改形参表。
In the second pair, it looks like the types are different, but Telno is not a new type; it is a synonym for Phone. A typedef name provides an alternative name for an existing data type; it does not create a new data type. Therefore, two parameters that differ only in that one uses a typedef and the other uses the type to which the typedef corresponds are not different.
在第二对函数声明中,看似形参类型不同,但注意到 Telno 其实并不是新类型,只是 Phone 类型的同义词。typedef 给已存在的数据类型提供别名,但并没有创建新的数据类型。所以,如果两个形参的差别只是一个使用 typedef 定义的类型名,而另一个使用 typedef 对应的原类型名,则这两个形参并无不同。
In the third pair, the parameter lists differ only in their default arguments. A default argument doesn't change the number of parameters. The function takes two arguments, whether they are supplied by the user or by the compiler.
在第三对中,形参列表只有默认实参不同。默认实参并没有改变形参的个数。无论实参是由用户还是由编译器提供的,这个函数都带有两个实参。
The last pair differs only as to whether the parameter is const. This difference has no effect on the objects that can be passed to the function; the second declaration is treated as a redeclaration of the first. The reason follows from how arguments are passed. When the parameter is copied, whether the parameter is const is irrelevantthe function executes on a copy. Nothing the function does can change the argument. As a result, we can pass a const object to either a const or nonconst parameter. The two parameters are indistinguishable.
最后一对的区别仅在于是否将形参定义为 const。这种差异并不影响传递至函数的对象;第二个函数声明被视为第一个的重复声明。其原因在于实参传递的方式。复制形参时并不考虑形参是否为 const——函数操纵的只是副本。函数的无法修改实参。结果,既可将 const 对象传递给 const 形参,也可传递给非 const 形参,这两种形参并无本质区别。
It is worth noting that the equivalence between a parameter and a const parameter applies only to nonreference parameters. A function that takes a const reference is different from on that takes a nonconst reference. Similarly, a function that takes a pointer to a const type differs from a function that takes a pointer to the nonconst object of the same type.
值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。
Advice: When Not to Overload a Function Name
建议:何时不重载函数名
Although overloading can be useful in avoiding the necessity to invent (and remember) names for common operations, it is easy to take this advantage too far. There are some cases where providing different function names adds information that makes the program easier to understand. Consider a set of member functions for a Screen class that move Screen's cursor.
虽然,对于通常的操作,重载函数能避免不必要的函数命名(和名字记忆),但很容易就会过分使用重载。在一些情况下,使用不同的函数名能提供较多的信息,使程序易于理解。考虑下面 Screen 类的一组用于移动屏幕光标的成员函数:
     Screen& moveHome();
     Screen& moveAbs(int, int);
     Screen& moveRel(int, int, char *direction);



It might at first seem better to overload this set of functions under the name move:
乍看上去,似乎把这组函数重载为名为 move 的函数更好一些:
     Screen& move();
     Screen& move(int, int);
     Screen& move(int, int, *direction);



However, by overloading these functions we've lost information that was inherent in the function names and by doing so may have rendered the program more obscure.
其实不然,重载过后的函数失去了原来函数名所包含的信息,如此一来,程序变得晦涩难懂了。
Although cursor movement is a general operation shared by all these functions, the specific nature of that movement is unique to each of these functions. moveHome, for example, represents a special instance of cursor movement. Which of the two calls is easier to understand for a reader of the program? Which of the two calls is easier to remember for a programmer using the Screen class?
虽则这几个函数共享的一般性动作都是光标移动,但特殊的移动性质却互不相同。例如,moveHome 表示的是光标移动的一个特殊实例。对于程序的读者,下面两种调用中,哪种更易于理解?而对于使用 Screen 类的程序员,哪一个调用又更容易记忆呢?
     // which is easier to understand?
     myScreen.home(); // we think this one!
     myScreen.move();





7.8.1. Overloading and Scope
7.8.1. 重载与作用域
We saw in the program on page 54 that scopes in C++ nest. A name declared local to a function hides the same name declared in the global scope (Section 2.3.6, p. 54). The same is true for function names as for variable names:
第 2.3.6 节的程序演示了 C++ 作用域的嵌套。在函数中局部声明的名字将屏蔽在全局作用域(第 2.3.6 节)内声明的同名名字。这个关于变量名字的性质对于函数名同样成立:
     /* Program for illustration purposes only:
      * It is bad style for a function to define a local variable
      * with the same name as a global name it wants to use
      */
     string init(); // the name init has global scope
     void fcn()
     {
         int init = 0;        // init is local and hides global init
         string s = init();   // error: global init is hidden
     }

Normal scoping rules apply to names of overloaded functions. If we declare a function locally, that function hides rather than overloads the same function declared in an outer scope. As a consequence, declarations for every version of an overloaded function must appear in the same scope.
一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
In general, it is a bad idea to declare a function locally. Function declarations should go in header files.
一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。

To explain how scope interacts with overloading we will violate this practice and use a local function declaration.
但为了解释作用域与重载的相互作用,我们将违反上述规则而使用局部函数声明。
As an example, consider the following program:
作为例子,考虑下面的程序:
     void print(const string &);
     void print(double);   // overloads the print function
     void fooBar(int ival)
     {
         void print(int);   // new scope: hides previous instances of print
         print("Value: ");  // error: print(const string &) is hidden
         print(ival); // ok: print(int) is visible
         print(3.14); // ok: calls print(int); print(double) is hidden
     }

The declaration of print(int) in the function fooBar hides the other declarations of print. It is as if there is only one print function available: the one that takes a single int parameter. Any use of the name print at this scopeor a scope nested in this scopewill resolve to this instance.
函数 fooBar 中的 print(int) 声明将屏蔽 print 的其他声明,就像只有一个有效的 print 函数一样:该函数仅带有一个 int 型形参。在这个作用域或嵌套在这个作用域里的其他作用域中,名字 print 的任何使用都将解释为这个 print 函数实例。
When we call print, the compiler first looks for a declaration of that name. It finds the local declaration for print that takes an int. Once the name is found, the compiler does no further checks to see if the name exists in an outer scope. Instead, the compiler assumes that this declaration is the one for the name we are using. What remains is to see if the use of the name is valid
调用 print 时,编译器首先检索这个名字的声明,找到只有一个 int 型形参的 print 函数的局部声明。一旦找到这个名字,编译器将不再继续检查这个名字是否在外层作用域中存在,即编译器将认同找到的这个声明即是程序需要调用的函数,余下的工作只是检查该名字的使用是否有效。
The first call passes a string literal but the function parameter is an int. A string literal cannot be implicitly converted to an int, so the call is an error. The print(const string&) function, which would have matched this call, is hidden and is not considered when resolving this call.
第一个函数调用传递了一个字符串字面值,但是函数的形参却是 int 型的。字符串字面值无法隐式地转换为 int 型,因而该调用是错误的。print(const string&) 函数与这个函数调用匹配,但已被屏蔽,因此不在解释该调用时考虑。
When we call print passing a double, the process is repeated. The compiler finds the local definition of print(int). The double argument can be converted to an int, so the call is legal.
当传递一个 double 数据调用 print 函数时,编译器重复了同样的匹配过程:首先检索到 print(int) 局部声明,然后将 double 型的实参隐式转换为 int 型。因此,该调用合法。
In C++ name lookup happens before type checking.
在 C++ 中,名字查找发生在类型检查之前。

Had we declared print(int) in the same scope as the other print functions, then it would be another overloaded version of print. In that case, these calls would be resolved differently:
另一种情况是,在与其他 print 函数相同的作用域中声明 print(int),这样,它就成为 print 函数的另一个重载版本。此时,所有的调用将以不同的方式解释:
     void print(const string &);
     void print(double); // overloads print function
     void print(int);    // another overloaded instance
     void fooBar2(int ival)
     {
         print("Value: "); // ok: calls print(const string &)
         print(ival);      // ok: print(int)
         print(3.14);      // ok: calls print (double)
     }

Now when the compiler looks for the name print it finds three functions with that name. On each call it selects the version of print that matches the argument that is passed.
现在,编译器在检索名字 print 时,将找到这个名字的三个函数。每一个调用都将选择与其传递的实参相匹配的 print 版本。
Exercises Section 7.8.1
Exercise 7.34:Define a set of overloaded functions named error that would match the following calls:
定义一组名为 error 的重载函数,使之与下面的调用匹配:
     int index, upperBound;
     char selectVal;
     // ...
     error("Subscript out of bounds: ", index, upperBound);
     error("Division by zero");
     error("Invalid selection", selectVal);


Exercise 7.35:Explain the effect of the second declaration in each one of the following sets of declarations. Indicate which, if any, are illegal.
下面提供了三组函数声明,解释每组中第二个声明的效果,并指出哪些(如果有的话)是不合法的。
     (a) int calc(int, int);
         int calc(const int, const int);

     (b) int get();
         double get();

     (c) int *reset(int *);
         double *reset(double *);




7.8.2. Function Matching and Argument Conversions
7.8.2. 函数匹配与实参转换
Function overload resolution (also known as function matching) is the process by which a function call is associated with a specific function from a set of overloaded functions. The compiler matches a call to a function automatically by comparing the actual arguments used in the call with the parameters offered by each function in the overload set. There are three possible outcomes:
函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。匹配结果有三种可能:
The compiler finds one function that is a best match for the actual arguments and generates code to call that function.
编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
There is no function with parameters that match the arguments in the call, in which case the compiler indicates a compile-time error.
找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
There is more than one function that matches and none of the matches is clearly best. This case is also an error; the call is ambiguous.
存在多个与实参匹配的函数,但没有一个是明显的最佳选择。这种情况也是,该调用具有二义性。
Most of the time it is straghtforward to determine whether a particular call is legal and if so, which function will be invoked by the compiler. Often the functions in the overload set differ in terms of the number of arguments, or the types of the arguments are unrelated. Function matching gets tricky when multiple functions have parameters that are related by conversions (Section 5.12, p. 178). In these cases, programmers need to have a good grasp of the process of function matching.
大多数情况下,编译器都可以直接明确地判断一个实际的调用是否合法,如果合法,则应该调用哪一个函数。重载集合中的函数通常有不同个数的参数或无关联的参数类型。当多个函数的形参具有可通过隐式转换(第 5.12 节)关联起来的类型,则函数匹配将相当灵活。在这种情况下,需要程序员充分地掌握函数匹配的过程。
7.8.3. The Three Steps in Overload Resolution
7.8.3. 重载确定的三个步骤
Consider the following set of functions and function call:
考虑下面的这组函数和函数调用:
     void f();
     void f(int);
     void f(int, int);
     void f(double, double = 3.14);
     f(5.6);  // calls void f(double, double)



Candidate Functions
候选函数
The first step of function overload resolution identifies the set of overloaded functions considered for the call. The functions in this set are the candidate functions. A candidate function is a function with the same name as the function that is called and for which a declaration is visible at the point of the call. In this example, there are four candidate functions named f.
函数重载确定的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。在这个例子中,有四个名为 f 的候选函数。
Determining the Viable Functions
选择可行函数
The second step selects the functions from the set of candidate functions that can be called with the arguments specified in the call. The selected functions are the viable functions. To be viable, a function must meet two tests. First, the function must have the same number of parameters as there are arguments in the call. Second, the type of each argument must matchor be convertible tothe type of its corresponding parameter.
第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
When a function has default arguments (Section 7.4.1, p. 253), a call may appear to have fewer arguments than it actually does. Default arguments are arguments and are treated the same way as any other argument during function matching.
如果函数具有默认实参(第 7.4.1 节),则调用该函数时,所用的实参可能比实际需要的少。默认实参也是实参,在函数匹配过程中,它的处理方式与其他实参一样。

For the call f(5.6), we can eliminate two of our candidate functions because of a mismatch on number of arguments. The function that has no parameters and the one that has two int parameters are not viable for this call. Our call has only one argument, and these functions have zero and two parameters, respectively.
对于函数调用 f(5.6),可首先排除两个实参个数不匹配的候选函数。没有形参的 f 函数和有两个 int 型形参的 f 函数对于这个函数调用来说都不可行。例中的调用只有一个实参,而这些函数分别带有零个和两个形参。
On the other hand, the function that takes two doubles might be viable. A call to a function declaration that has a default argument (Section 7.4.1, p. 253) may omit that argument. The compiler will automatically supply the default argument value for the omitted argument. Hence, a given call might have more arguments than appear explicitly.
另一方面,有两个 double 型参数的 f 函数可能是可行的。调用带有默认实参(第 7.4.1 节)的函数时可忽略这个实参。编译器自动将默认实参的值提供给被忽略的实参。因此,某个调用拥有的实参可能比显式给出的多。
Having used the number of arguments to winnow the potentially viable functions, we must now look at whether the argument types match those of the parameters. As with any call, an argument might match its parameter either because the types match exactly or because there is a conversion from the argument type to the type of the parameter. In the example, both of our remaining functions are viable.
根据实参个数选出潜在的可行函数后,必须检查实参的类型是否与对应的形参类型匹配。与任意函数调用一样,实参必须与它的形参匹配,它们的类型要么精确匹配,要么实参类型能够转换为形参类型。在这个例子中,余下的两个函数都是是可行的。
f(int) is a viable function because a conversion exists that can convert the argument of type double to the parameter of type int.
f(int) 是一个可行函数,因为通过隐式转换可将函数调用中的 double 型实参转换为该函数唯一的 int 型形参。
f(double, double) is a viable function because a default argument is provided for the function's second parameter and its first parameter is of type double, which exactly matches the type of the parameter.
f(double, double) 也是一个可行函数,因为该函数为其第二个形参提供了默认实参,而且第一个形参是 double 类型,与实参类型精确匹配。
If there are no viable functions, then the call is in error.
如果没有找到可行函数,则该调用错误。

Finding the Best Match, If Any
寻找最佳匹配(如果有的话)
The third step of function overload resolution determines which viable function has the best match for the actual arguments in the call. This process looks at each argument in the call and selects the viable function (or functions) for which the corresponding parameter best matches the argument. The details of "best" here will be explained in the next section, but the idea is that the closer the types of the argument and parameter are to each other, the better the match. So, for example, an exact type match is better than a match that requires a conversion from the argument type to the parameter type.
函数重载确定的第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这里所谓“最佳”的细节将在下一节中解释,其原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。
In our case, we have only one explicit argument to consider. That argument has type double. To call f(int), the argument would have to be converted from double to int. The other viable function, f(double, double), is an exact match for this argument. Because an exact match is better than a match that requires a conversion, the compiler will resolve the call f(5.6) as a call to the function that has two double parameters.
在上述例子中,只需考虑一个 double 类型的显式实参。如果调用 f(int),实参需从 double 型转换为 int 型。而另一个可行函数 f(double, double) 则与该实参精确匹配。由于精确匹配优于需要类型转换的匹配,因此编译器将会把函数调用 f(5.6) 解释为对带有两个 double 形参的 f 函数的调用。
Overload Resolution with Multiple Parameters
含有多个形参的重载确定
Function matching is more complicated if there are two or more explicit arguments. Given the same functions named f, let's analyze the following call:
如果函数调用使用了两个或两个以上的显式实参,则函数匹配会更加复杂。假设有两样的名为 f 的函数,分析下面的函数调用:
     f(42, 2.56);



The set of viable functions is selected in the same way. The compiler selects those functions that have the required number of parameters and for which the argument types match the parameter types. In this case, the set of viable functions are f(int, int) and f(double, double). The compiler then determines argument by argument which function is (or functions are) the best match. There is a match if there is one and only one function for which
可行函数将以同样的方式选出。编译器将选出形参个数和类型都与实参匹配的函数。在本例中,可行函数是 f(int, int) 和 f(double, double)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:
The match for each argument is no worse than the match required by any other viable function.
其每个实参的匹配都不劣于其他可行函数需要的匹配。
There is at least one argument for which the match is better than the match provided by any other viable function.
至少有一个实参的匹配优于其他可行函数提供的匹配。
If after looking at each argument there is no single function that is preferable, then the call is in error. The compiler will complain that the call is ambiguous.
如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
In this call, when we look only at the first argument, we find that the function f(int, int) is an exact match. To match the second function, the int argument 42 must be converted to a double. A match through a built-in conversion is "less good" than one that is exact. So, considering only this parameter, the function that takes two ints is a better match than the function that takes two doubles.
在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个 int 型形参的函数比带有两个 double 型形参的函数匹配更佳。
However, when we look at the second argument, then the function that takes two doubles is an exact match to the argument 2.56. Calling the version of f that takes two ints would require that 2.56 be converted from double to int. When we consider only the second parameter, then the function f(double, double) is the better match.
但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
This call is therefore ambiguous: Each viable function is a better match on one of the arguments to the call. The compiler will generate an error. We could force a match by explicitly casting one of our arguments:
因此,这个调用有二义性:每个可行函数都对函数调用的一个实参实现更好的匹配。编译器将产生错误。解决这样的二义性,可通过显式的强制类型转换强制函数匹配:
     f(static_cast<double>(42), 2.56);  // calls f(double, double)
     f(42, static_cast<int>(2.56));     // calls f(int, int)

In practice, arguments should not need casts when calling over-loaded functions: The need for a cast means that the parameter sets are designed poorly.
在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:需要使用强制类型转换意味着所设计的形参集合不合理。

Exercises Section 7.8.3
Exercise 7.36:What is a candidate function? What is a viable function?
什么是候选函数?什么是可行函数?
Exercise 7.37:Given the declarations for f, determine whether the following calls are legal. For each call list the viable functions, if any. If the call is illegal, indicate whether there is no match or why the call is ambiguous. If the call is legal, indicate which function is the best match.
已知本节所列出的 f 函数的声明,判断下面哪些函数调用是合法的。如果有的话,列出每个函数调用的可行函数。如果调用非法,指出是没有函数匹配还是该调用存在二义性。如果调用合法,指出哪个函数是最佳匹配。
     (a) f(2.56, 42);
     (b) f(42);
     (c) f(42, 0);
     (d) f(2.56, 3.14);





7.8.4. Argument-Type Conversions
7.8.4. 实参类型转换
In order to determine the best match, the compiler ranks the conversions that could be used to convert each argument to the type of its corresponding parameter. Conversions are ranked in descending order as follows:
为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
An exact match. The argument and parameter types are the same.
精确匹配。实参与形参类型相同。
Match through a promotion (Section 5.12.2, p. 180).
通过类型提升实现的匹配(第 5.12.2 节)。
Match through a standard conversion (Section 5.12.3, p. 181).
通过标准转换实现的匹配(第 5.12.3 节)。
Match through a class-type conversion. (Section 14.9 (p. 535) covers these conversions.)
通过类类型转换实现的匹配(第 14.9 节将介绍这类转换)。
Promotions and conversions among the built-in types can yield surprising results in the context of function matching. Fortunately, well-designed systems rarely include functions with parameters as closely related as those in the following examples.
内置类型的提升和转换可能会使函数匹配产生意想不到的结果。但幸运的是,设计良好的系统很少会包含与下面例子类似的形参类型如此接近的函数。

These examples bear study to cement understanding both of function matching in particular and of the relationships among the built-in types in general.
通过这些例子,学习并加深了解特殊的函数匹配和内置类型之间的一般关系。
Matches Requiring Promotion or Conversion
需要类型提升或转换的匹配
Promotions or conversions are applied when the type of the argument can be promoted or converted to the appropriate parameter type using one of the standard conversions.
类型提升或转换适用于实参类型可通过某种标准转换提升或转换为适当的形参类型情况。
One important point to realize is that the small integral types promote to int. Given two functions, one of which takes an int and the other a short, the int version will be a better match for a value of any integral type other than short, even though short might appear on the surface to be a better match:
必须注意的一个重点是较小的整型提升为 int 型。假设有两个函数,一个的形参为 int 型,另一个的形参则是 short 型。对于任意整型的实参值,int 型版本都是优于 short 型版本的较佳匹配,即使从形式上看 short 型版本的匹配较佳:
     void ff(int);
     void ff(short);
     ff('a');    // char promotes to int, so matches f(int)



A character literal is type char, and chars are promoted to int. That promoted type matches the type of the parameter of function ff(int). A char could also be converted to short, but a conversion is a "less good" match than a promotion. And so this call will be resolved as a call to ff (int).
字符字面值是 char 类型,char 类型可提升为 int 型。提升后的类型与函数 ff(int) 的形参类型匹配。char 类型同样也可转换为 short 型,但需要类型转换的匹配“劣于”需要类型提升的匹配。结果应将该调用解释为对 ff (int) 的调用。
A conversion that is done through a promotion is preferred to another standard conversion. So, for example, a char is a better match for a function taking an int than it is for a function taking a double. All other standard conversions are treated as equivalent. The conversion from char to unsigned char, for example, does not take precedence over the conversion from char to double. As a concrete example, consider:
通过类型提升实现的转换优于其他标准转换。例如,对于 char 型实参来说,有 int 型形参的函数是优于有 double 型形参的函数的较佳匹配。其他的标准转换也以相同的规则处理。例如,从 char 型到 unsigned char 型的转换的优先级不比从 char 型到 double 型的转换高。再举一个具体的例子,考虑:
     extern void manip(long);
     extern void manip(float);
     manip(3.14);  // error: ambiguous call

The literal constant 3.14 is a double. That type could be converted to either long or float. Because there are two possible standard conversions, the call is ambiguous. No one standard conversion is given precedence over another.
字面值常量 3.14 的类型为 double。这种类型既可转为 long 型也可转为 float 型。由于两者都是可行的标准转换,因此该调用具有二义性。没有哪个标准转换比其他标准转换具有更高的优先级。
Parameter Matching and Enumerations
参数匹配和枚举类型
Recall that an object of enum type may be initialized only by another object of that enum type or one of its enumerators (Section 2.7, p. 63). An integral object that happens to have the same value as an enumerator cannot be used to call a function expecting an enum argument:
回顾枚举类型 enum,我们知道这种类型的对象只能用同一枚举类型的另一个对象或一个枚举成员进行初始化(第 2.7 节)。整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。
     enum Tokens {INLINE = 128, VIRTUAL = 129};
     void ff(Tokens);
     void ff(int);
     int main() {
         Tokens curTok = INLINE;
         ff(128);    // exactly matches ff(int)
         ff(INLINE); // exactly matches ff(Tokens)
         ff(curTok); // exactly matches ff(Tokens)
         return 0;
     }

The call that passes the literal 128 matches the version of ff that takes an int.
传递字面值常量 128 的函数调用与有一个 int 型参数的 ff 版本匹配。
Although we cannot pass an integral value to a enum parameter, we can pass an enum to a parameter of integral type. When we do so, the enum value promotes to int or to a larger integral type. The actual promotion type depends on the values of the enumerators. If the function is overloaded, the type to which the enum promotes determines which function is called:
虽然无法将整型值传递给枚举类型的形参,但可以将枚举值传递给整型形参。此时,枚举值被提升为 int 型或更大的整型。具体的提升类型取决于枚举成员的值。如果是重载函数,枚举值提升后的类型将决定调用哪个函数:
     void newf(unsigned char);
     void newf(int);
     unsigned char uc = 129;
     newf(VIRTUAL); // calls newf(int)
     newf(uc);      // calls newf(unsigned char)

The enum Tokens has only two enumerators, the largest of which has a value of 129. That value can be represented by the type unsigned char, and many compilers would store the enum as an unsigned char. However, the type of VIRTUAL is not unsigned char. Enumerators and values of an enum type, are not promoted to unsigned char, even if the values of the enumerators would fit.
枚举类型 Tokens 只有两个枚举成员,最大的值为 129。这个值可以用 unsigned char 类型表示,很多编译器会将这个枚举类型存储为 unsigned char 类型。然而,枚举成员 VIRTUAL 却并不是 unsigned char 类型。就算枚举成员的值能存储在 unsigned char 类型中,枚举成员和枚举类型的值也不会提升为 unsigned char 类型。
When using overloaded functions with enum parameters, remember: Two enumeration types may behave quite differently during function overload resolution, depending on the value of their enumeration constants. The enumerators determine the type to which they promote. And that type is machine-dependent.
在使用有枚举类型形参的重载函数时,请记住:由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器。

Overloading and const Parameters
重载和 const 形参
Whether a parameter is const only matters when the parameter is a reference or pointer.
仅当形参是引用或指针时,形参是否为 const 才有影响。

We can overload a function based on whether a reference parameter refers to a const or nonconst type. Overloading on const for a reference parameter is valid because the compiler can use whether the argument is const to determine which function to call:
可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数:
     Record lookup(Account&);
     Record lookup(const Account&); // new function
     const Account a(0);
     Account b;
     lookup(a);   // calls lookup(const Account&)
     lookup(b);   // calls lookup(Account&)

If the parameter is a plain reference, then we may not pass a const object for that parameter. If we pass a const object, then the only function that is viable is the version that takes a const reference.
如果形参是普通的引用,则不能将 const 对象传递给这个形参。如果传递了 const 对象,则只有带 const 引用形参的版本才是该调用的可行函数。
When we pass a nonconst object, either function is viable. We can use a nonconst object to initializer either a const or nonconst reference. However, initializing a const reference to a nonconst object requires a conversion, whereas initializing a nonconst parameter is an exact match.
如果传递的是非 const 对象,则上述任意一种函数皆可行。非 const 对象既可用于初始化 const 引用,也可用于初始化非 const 引用。但是,将 const 引用初始化为非 const 对象,需通过转换来实现,而非 const 形参的初始化则是精确匹配。
Pointer parameters work in a similar way. We may pass the address of a const object only to a function that takes a pointer to const. We may pass a pointer to a nonconst object to a function taking a pointer to a const or nonconst type. If two functions differ only as to whether a pointer parameter points to const or nonconst, then the parameter that points to the nonconst type is a better match for a pointer to a nonconst object. Again, the compiler can distinguish: If the argument is const, it calls the function that takes a const*; otherwise, if the argument is a nonconst, the function taking a plain pointer is called.
对指针形参的相关处理如出一辙。可将 const 对象的地址值只传递给带有指向 const 对象的指针形参的函数。也可将指向非 const 对象的指针传递给函数的 const 或非 const 类型的指针形参。如果两个函数仅在指针形参时是否指向 const 对象上不同,则指向非 const 对象的指针形参对于指向非 const 对象的指针(实参)来说是更佳的匹配。重复强调,编译器可以判断:如果实参是 const 对象,则调用带有 const* 类型形参的函数;否则,如果实参不是 const 对象,将调用带有普通指针形参的函数。
It is worth noting that we cannot overload based on whether the pointer itself is const:
注意不能基于指针本身是否为 const 来实现函数的重载:
     f(int *);
     f(int *const); // redeclaration

Here the const applies to the pointer, not the type to which the pointer points. In both cases the pointer is copied; it makes no difference whether the pointer itself is const. As we noted on page 267, when a parameter is passed as a copy, we cannot overload based on whether that parameter is const.
此时,const 用于修改指针本身,而不是修饰指针所指向的类型。在上述两种情况中,都复制了指针,指针本身是否为 const 并没有带来区别。正如前面第 7.8 节所提到的,当形参以副本传递时,不能基于形参是否为 const 来实现重载。
Exercises Section 7.8.4
Exercise 7.38:Given the following declarations,
给出如下声明:
     void manip(int, int);
     double dobj;

what is the rank (Section 7.8.4, p. 272) of each conversion in the following calls?
对于下面两组函数调用,请指出实参上每个转换的优先级等级(第 7.8.4 节)?
     (a) manip('a', 'z');    (b) manip(55.4, dobj);


Exercise 7.39:Explain the effect of the second declaration in each one of the following sets of declarations. Indicate which, if any, are illegal.
解释以下每组声明中的第二个函数声明所造成的影响,并指出哪些不合法(如果有的话)。
     (a) int calc(int, int);
         int calc(const int&, const int&);

     (b) int calc(char*, char*);
         int calc(const char*, const char*);

     (c) int calc(char*, char*);
         int calc(char* const, char* const);


Exercise 7.40:Is the following function call legal? If not, why is the call in error?
下面的函数调用是否合法?如果不合法,请解释原因。
     enum Stat { Fail, Pass };
     void test(Stat);
     test(0);






      

7.9. Pointers to Functions
7.9. 指向函数的指针
A function pointer is just thata pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type. A function's type is determined by its return type and its parameter list. A function's name is not part of its type:
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关:
     // pf points to function returning bool that takes two const string references
     bool (*pf)(const string &, const string &);

This statement declares pf to be a pointer to a function that takes two const string& parameters and has a return type of bool.
这个语句将 pf 声明为指向函数的指针,它所指向的函数带有两个 const string& 类型的形参和 bool 类型的返回值。
The parentheses around *pf are necessary:
*pf 两侧的圆括号是必需的:


     // declares a function named pf that returns a bool*
     bool *pf(const string &, const string &);

Using Typedefs to Simplify Function Pointer Definitions
用 typedef 简化函数指针的定义
Function pointer types can quickly become unwieldy. We can make function pointers easier to use by defining a synonym for the pointer type using a typedef (Section 2.6, p. 61):
函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函数指针的使用大大简化:(第 2.6 节):
     typedef bool (*cmpFcn)(const string &, const string &);

This definition says that cmpFcn is the name of a type that is a pointer to function. That pointer has the type "pointer to a function that returns a bool and takes two references to const string." When we need to use this function pointer type, we can do so by using cmpFcn, rather than having to write the full type definition each time.
该定义表示 cmpFcn 是一种指向函数的指针类型的名字。该指针类型为“指向返回 bool 类型并带有两个 const string 引用形参的函数的指针”。在要使用这种函数指针类型时,只需直接使用 cmpFcn 即可,不必每次都把整个类型声明全部写出来。
Initializing and Assigning Pointers to Functions
指向函数的指针的初始化和赋值
When we use a function name without calling it, the name is automatically treated as a pointer to a function. Given
在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。假设有函数:
     // compares lengths of two strings
     bool lengthCompare(const string &, const string &);

any use of lengthCompare, except as the left-hand operand of a function call, is treated as a pointer whose type is
除了用作函数调用的左操作数以外,对 lengthCompare 的任何使用都被解释为如下类型的指针:
     bool (*)(const string &, const string &);

We can use a function name to initialize or assign to a function pointer:
可使用函数名对函数指针做初始化或赋值:
     cmpFcn pf1 = 0;             // ok: unbound pointer to function
     cmpFcn pf2 = lengthCompare; // ok: pointer type matches function's type
     pf1 = lengthCompare;        // ok: pointer type matches function's type
     pf2 = pf1;                  // ok: pointer types match

Using the function name is equivalent to applying the address-of operator to the function name:
此时,直接引用函数名等效于在函数名上应用取地址操作符:
     cmpFcn pf1 = lengthCompare;
     cmpFcn pf2 = &lengthCompare;

 A function pointer may be initialized or assigned only by a function or function pointer that has the same type or by a zero-valued constant expression.
函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。

Initializing a function pointer to zero indicates that the pointer does not point to any function.
将函数指针初始化为 0,表示该指针不指向任何函数。
There is no conversion between one pointer to function type and another:
指向不同函数类型的指针之间不存在转换:
     string::size_type sumLength(const string&, const string&);
     bool cstringCompare(char*, char*);
     // pointer to function returning bool taking two const string&
     cmpFcn pf;
     pf = sumLength;      // error: return type differs
     pf = cstringCompare; // error: parameter types differ
     pf = lengthCompare;  // ok: function and pointer types match exactly

Calling a Function through a Pointer
通过指针调用函数
A pointer to a function can be used to call the function to which it refers. We can use the pointer directlythere is no need to use the dereference operator to call the function
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数:
     cmpFcn pf = lengthCompare;
     lengthCompare("hi", "bye"); // direct call
     pf("hi", "bye");            // equivalent call: pf1 implicitly dereferenced
     (*pf)("hi", "bye");         // equivalent call: pf1 explicitly dereferenced

If the pointer to function is uninitialized or has a value of zero, it may not be used in a call. Only pointers that have been initialized or assigned to refer to a function can be safely used to call a function.
如果指向函数的指针没有初始化,或者具有 0 值,则该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,方能安全地用来调用函数。

Function Pointer Parameters
函数指针形参
A function parameter can be a pointer to function. We can write such a parameter in one of two ways:
函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:

[View full width]

     /* useBigger function's third parameter is a pointer to function
      * that function returns a bool and takes two const string references
      * two ways to specify that parameter:
      */
     // third parameter is a function type and is automatically treated as a pointer to
 function
     void useBigger(const string &, const string &,
                    bool(const string &, const string &));
     // equivalent declaration: explicitly define the parameter as a pointer to function
     void useBigger(const string &, const string &,
                    bool (*)(const string &, const string &));

Returning a Pointer to Function
返回指向函数的指针
A function can return a pointer to function, although correctly writing the return type can be a challenge:
函数可以返回指向函数的指针,但是,正确写出这种返回类型相当不容易:
     // ff is a function taking an int and returning a function pointer
     // the function pointed to returns an int and takes an int* and an int
     int (*ff(int))(int*, int);

 The best way to read function pointer declarations is from the inside out, starting with the name being declared.
阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。

We can figure out what this declaration means by observing that
要理解该声明的含义,首先观察:
     ff(int)

says that ff is a function taking one parameter of type int. This function returns
将 ff 声明为一个函数,它带有一个 int 型的形参。该函数返回
     int (*)(int*, int);

a pointer to a function that returns an int and takes two parameters of type int* and an int.
它是一个指向函数的指针,所指向的函数返回 int 型并带有两个分别是 int* 型和 int 型的形参。
Typedefs can make such declarations considerably easier to read:
使用 typedef 可使该定义更简明易懂:
     // PF is a pointer to a function returning an int, taking an int* and an int
     typedef int (*PF)(int*, int);
     PF ff(int);  // ff returns a pointer to function

 We can define a parameter as a function type. A function return type must be a pointer to function; it cannot be a function.
允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。

An argument to a parameter that has a function type is automatically converted to the corresponding pointer to function type. The same conversion does not happen when returning a function:
具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现:
     // func is a function type, not a pointer to function!
     typedef int func(int*, int);
     void f1(func); // ok: f1 has a parameter of function type
     func f2(int);  // error: f2 has a return type of function type
     func *f3(int); // ok: f3 returns a pointer to function type

Pointers to Overloaded Functions
指向重载函数的指针
It is possible to use a function pointer to refer to an overloaded function:
C++ 语言允许使用函数指针指向重载的函数:
     extern void ff(vector<double>);
     extern void ff(unsigned int);

     // which function does pf1 refer to?
     void (*pf1)(unsigned int) = &ff; // ff(unsigned)

The type of the pointer and one of the overloaded functions must match exactly. If no function matches exactly, the initialization or assignment results in a compile-time error:
指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误:
     // error: no match: invalid parameter list
     void (*pf2)(int) = &ff;

     // error: no match: invalid return type
     double (*pf3)(vector<double>);
     pf3 = &ff;



      

Chapter 8. The IO Library
第八章 标准 IO 库
CONTENTS
Section 8.1 An Object-Oriented Library284
Section 8.2 Condition States287
Section 8.3 Managing the Output Buffer290
Section 8.4 File Input and Output293
Section 8.5 String Streams299
Chapter Summary302
Defined Terms302


In C++, input/output is provided through the library. The library defines a family of types that support IO to and from devices such as files and console windows. Additional types allow strings to act like files, which gives us a way to convert data to and from character forms without also doing IO. Each of these IO types defines how to read and write values of the built-in data types. In addition, class designers generally use the library IO facilities to read and write objects of the classes that they define. Class types are usually read and written using the same operators and conventions that the IO library defines for the built-in types.
C++ 的输入/输出(input/output)由标准库提供。标准库定义了一族类型,支持对文件和控制窗口等设备的读写(IO)。还定义了其他一些类型,使 string 对象能够像文件一样操作,从而使我们无须 IO 就能实现数据与字符之间的转换。这些 IO 类型都定义了如何读写内置数据类型的值。此外,一般来说,类的设计者还可以很方便地使用 IO 标准库设施读写自定义类的对象。类类型通常使用 IO 标准库为内置类型定义的操作符和规则来进行读写。
This chapter introduces the fundamentals of the IO library. Later chapters will cover additional capabilities: Chapter 14 will look at how we can write our own input and output operators; Appendix A will cover ways to control formatting and random access to files.
本章将介绍 IO标准库的基础知识,而更多的内容会在后续章节中介绍:第十四章考虑如何编写自己的输入输出操作符:附录 A 则介绍格式控制以及文件的随机访问。
Our programs have already used many IO library facilities:
前面的程序已经使用了多种 IO 标准库提供的工具:
istream (input stream) type, which supports input operations
istream(输入流)类型,提供输入操作。
ostream (output stream) type, which provides output operations
ostream(输出流)类型,提供输出操作。
cin (pronounced see-in) an istream object that reads the standard input.
cin(发音为 see-in):读入标准输入的 istream 对象。
cout (pronounced see-out) an ostream object that writes to the standard output
cout(发音为 see-out):写到标准输出的 ostream 对象。
cerr (pronounced see-err) an ostream object that writes to the standard error. cerr is usually used for program error messages.
cerr(发音为 see-err):输出标准错误的 ostream 对象。cerr 常用于程序错误信息。
operator >>, which is used to read input from an istream object
>> 操作符,用于从 istream 对象中读入输入。
operator <<, which is used to write output to an ostream object
<< 操作符,用于把输出写到 ostream 对象中。
getline function, which takes a reference to an istream and a reference to a string and reads a word from the istream into the string
getline 函数,需要分别取 istream 类型和 string 类型的两个引用形参,其功能是从 istream 对象读取一个单词,然后写入 string 对象中。
This chapter looks briefly at some additional IO operations, and discusses support for reading and writing files and strings. Appendix A covers how to control formatting of IO operations, support for random access to files, and support for unformatted IO. This primer does not describe the entire iostream libraryin particular, we do not cover the system-specific implementation details, nor do we discuss the mechanisms by which the library manages input and output buffers or how we might write our own buffer classes. These topics are beyond the scope of this book. Instead, we'll focus on those portions of the IO library that are most useful in ordinary programs.
本章简要地介绍一些附加的 IO 操作,并讨论文件对象和 string 对象的读写。附录 A 会介绍如何控制 IO 操作的格式、文件的随机访问以及无格式的 IO。本书是初级读本,因此不会详细讨论完整的 iostream 标准库——特别是,我们不但没有涉及系统特定的实现细则,也不讨论标准库管理输入输出缓冲区的机制,以及如何编写自定义的缓冲区类。这些话题已超出了本书的范畴。相对而言,本书把重点放在 IO 标准库对普通程序最有用的部分。
      

8.1. An Object-Oriented Library
8.1. 面向对象的标准库
The IO types and objects we've used so far read and write streams of data and are used to interact with a user's console window. Of course, real programs cannot be limited to doing IO solely to or from a console window. Programs often need to read or write named files. Moreover, it can be quite convenient to use the IO operations to format data in memory, thereby avoiding the complexity and run-time expense of reading or writing to a disk or other device. Applications also may have to read and write languages that require wide-character support.
迄今为止,我们已经使用 IO 类型和对象读写数据流,它们常用于与用户控制窗口的交互。当然,实际的程序不能仅限于对控制窗口的 IO,通常还需要读或写已命名的文件。此外,程序还应该能方便地使用 IO 操作格式化内存中的数据,从而避免读写磁盘或其他设备的复杂性和运行代价。应用程序还需要支持宽字符(wide-character)语言的读写。
Conceptually, neither the kind of device nor the character size affect the IO operations we want to perform. For example, we'd like to use >> to read data regardless of whether we're reading a console window, a disk file, or an in-memory string. Similarly, we'd like to use that operator regardless of whether the characters we read fit in a char or require the wchar_t (Section 2.1.1, p. 34) type.
从概念上看,无论是设备的类型还是字符的大小,都不影响需要执行的 IO 操作。例如,不管我们是从控制窗口、磁盘文件或内存中的字符串读入数据,都可使用 >> 操作符。相似地,无论我们读的是 char 类型的字符还是 wchar_t(第 2.1.1 节)的字符,也都可以使用该操作符。
At first glance, the complexities involved in supporting or using these different kinds of devices and different sized character streams might seem a daunting problem. To manage the complexity, the library uses inheritance to define a set of object-oriented classes. We'll have more to say about inheritance and object-oriented programming in Part IV, but generally speaking, types related by inheritance share a common interface. When one class inherits from another, we (usually) can use the same operations on both classes. More specifically, when two types are related by inheritance, we say that one class "inherits" the behaviorthe interfaceof its parent. In C++ we speak of the parent as the base class and the inheriting class as a derived class.
乍看起来,要同时支持或使用不同类型设备以及不同大小的字符流,其复杂程度似乎相当可怕。为了管理这样的复杂性,标准库使用了继承(inheritance)来定义一组面向对象(object-oriented)类。在本书的第四部分将会更详细地讨论继承和面向对象程序设计,不过,一般而言,通过继承关联起来的类型都共享共同的接口。当一个类继承另一个类时,这两个类通常可以使用相同的操作。更确切地说,如果两种类型存在继承关系,则可以说一个类“继承”了其父类的行为——接口。C++ 中所提及的父类称为基类(base class),而继承而来的类则称为派生类(derived class)。
The IO types are defined in three separate headers: iostream defines the types used to read and write to a console window, fstream defines the types used to read and write named files, and sstream defines the types used to read and write in-memory strings. Each of the types in fstream and sstream is derived from a corresponding type defined in the iostream header. Table 8.1 lists the IO classes and Figure 8.1 on the next page illustrates the inheritance relationships among these types. Inheritance is usually illustrated similarly to how a family tree is displayed. The topmost circle represents a base (or parent) class. Lines connect a base class to its derived (or children) class(es). So, for example, this figure indicates that istream is the base class of ifstream and istringstream. It is also the base class for iostream, which in turn is the base class for sstream and fstream classes.
IO 类型在三个独立的头文件中定义:iostream 定义读写控制窗口的类型,fstream 定义读写已命名文件的类型,而 sstream 所定义的类型则用于读写存储在内存中的 string 对象。在 fstream 和 sstream 里定义的每种类型都是从 iostream 头文件中定义的相关类型派生而来。表 8.1 列出了 C++ 的 IO 类,而图 8.1 则阐明这些类型之间的继承关系。继承关系通常可以用类似于家庭树的图解说明。最顶端的圆圈代表基类(或称“父类”),基类和派生类(或称“子类”)之间用线段连接。因此,图 8.1 所示,istream 是 ifstream 和 istringstream 的基类,同时也是 iostream 的基类,而 iostream 则是 stringstream 和 fstream 的基类。
Table 8.1. IO Library Types and Headers
表 8.1. IO 标准库类型和头文件
HeaderType
iostreamistream reads from a stream
istream 从流中读取
ostream writes to a stream
ostream 写到流中去
iostream reads and writes a stream; derived from istream and ostream,
iostream 对流进行读写;从 istream 和 ostream 派生而来
fstreamifstream, reads from a file; derived from istream
ifstream 从文件中读取;由 istream 派生而来
ofstream writes to a file; derived from ostream
ofstream 写到文件中去;由 ostream 派生而来
fstream, reads and writes a file; derived from iostream
fstream 读写文件;由 iostream 派生而来
sstreamistringstream reads from a string; derived from istream
istringstream 从 string 对象中读取;由 istream 派生而来
ostringstream writes to a string; derived from ostream
ostringstream 写到 string 对象中去;由 ostream 派生而来
stringstream reads and writes a string; derived from iostream
stringstream 对 string 对象进行读写;由 iostream 派生而来


Figure 8.1. Simplified iostream Inheritance Hierarchy
图 8.1. 简单的 iostream 继承层次



Because the types ifstream and istringstream inherit from istream, we already know a great deal about how to use these types. Each program we've written that read an istream could be used to read a file (using the ifstream type) or a string (using the istringstream type). Similarly, programs that did output could use an ofstream or ostringstream instead of ostream. In addition to the istream and ostream types, the iostream header also defines the iostream type. Although our programs have not used this type, we actually know a good bit about how to use an iostream. The iostream type is derived from both istream and ostream. Being derived from both types means that an iostream object shares the interface of both its parent types. That is, we can use an iostream type to do both input and output to the same stream. The library also defines two types that inherit from iostream. These types can be used to read or write to a file or a string.
由于 ifstream 和 istringstream 类型继承了 istream 类,因此已知这两种类型的大量用法。我们曾经编写过的读 istream 对象的程序也可用于读文件(使用 ifstream 类型)或者 string 对象(使用 istringstream 类型)。类似地,提供输出功能的程序同样可用 ofstream 或 ostringstream 取代 ostream 类型实现。除了 istream 和 ostream 类型之外,iostream 头文件还定义了 iostream 类型。尽管我们的程序还没用过这种类型,但事实上可以多了解一些关于 iostream 的用法。iostream 类型由 istream 和 ostream 两者派生而来。这意味着 iostream 对象共享了它的两个父类的接口。也就是说,可使用 iostream 类型在同一个流上实现输入和输出操作。标准库还定义了另外两个继承 iostream 的类型。这些类型可用于读写文件或 string 对象。
Using inheritance for the IO types has another important implication: As we'll see in Chapter 15, when we have a function that takes a reference to a base-class type, we can pass an object of a derived type to that function. This fact means that a function written to operate on istream& can be called with an ifstream or istringstream object. Similarly, a function that takes an ostream& can be called with an ofstream or ostringstream object. Because the IO types are related by inheritance, we can write one function and apply it to all three kinds of streams: console, disk files, or string streams.
对 IO 类型使用继承还有另外一个重要的含义:正如在第十五章可以看到的,如果函数有基类类型的引用形参时,可以给函数传递其派生类型的对象。这就意味着:对 istream& 进行操作的函数,也可使用 ifstream 或者 istringstream 对象来调用。类似地,形参为 ostream& 类型的函数也可用 ofstream 或者 ostringstream 对象调用。因为 IO 类型通过继承关联,所以可以只编写一个函数,而将它应用到三种类型的流上:控制台、磁盘文件或者字符串流(string streams)。
International Character Support
国际字符的支持
The stream classes described thus far read and write streams composed of type char. The library defines a corresponding set of types supporting the wchar_t type. Each class is distinguished from its char counterpart by a "w" prefix. Thus, the types wostream, wistream, and wiostream read and write wchar_t data to or from a console window. The file input and output classes are wifstream, wofstream, and wfstream. The wchar_t versions of string stream input and output are wistringstream, wostringstream, and wstringstream. The library also defines objects to read and write wide characters from the standard input and standard output. These objects are distinguished from the char counterparts by a "w" prefix: The wchar_t standard input object is named wcin; standard output is wcout; and standard error is wcerr.
迄今为止,所描述的流类(stream class)读写的是由 char 类型组成的流。此外,标准库还定义了一组相关的类型,支持 wchar_t 类型。每个类都加上“w”前缀,以此与 char 类型的版本区分开来。于是,wostream、wistream 和 wiostream 类型从控制窗口读写 wchar_t 数据。相应的文件输入输出类是 wifstream、wofstream 和 wfstream。而 wchar_t 版本的 string 输入/输出流则是 wistringstream、wostringstream 和 wstringstream。标准库还定义了从标准输入输出读写宽字符的对象。这些对象加上“w”前缀,以此与 char 类型版本区分:wchar_t 类型的标准输入对象是 wcin;标准输出是 wcout;而标准错误则是 wcerr。
Each of the IO headers defines both the char and wchar_t classes and standard input/output objects. The stream-based wchar_t classes and objects are defined in iostream, the wide character file stream types in fstream, and the wide character stringstreams in sstream.
每一个 IO 头文件都定义了 char 和 wchar_t 类型的类和标准输入/输出对象。基于流的 wchar_t 类型的类和对象在 iostream 中定义,宽字符文件流类型在 fstream 中定义,而宽字符 stringstream 则在 sstream 头文件中定义。
No Copy or Assign for IO Objects
IO 对象不可复制或赋值
For reasons that will be more apparent when we study classes and inheritance in Parts III and IV, the library types do not allow allow copy or assignment:
出于某些原因,标准库类型不允许做复制或赋值操作。其原因将在后面第三部分和第四部分学习类和继承时阐明。
    ofstream out1, out2;
    out1 = out2;   // error: cannot assign stream objects
    // print function: parameter is copied
    ofstream print(ofstream);
    out2 = print(out2);  // error: cannot copy stream objects



This requirement has two particularly important implications. As we'll see in Chapter 9, only element types that support copy can be stored in vectors or other container types. Because we cannot copy stream objects, we cannot have a vector (or other container) that holds stream objects.
这个要求有两层特别重要的含义。正如在第九章看到的,只有支持复制的元素类型可以存储在 vector 或其他容器类型里。由于流对象不能复制,因此不能存储在 vector(或其他)容器中(即不存在存储流对象的 vector 或其他容器)。
The second implication is that we cannot have a parameter or return type that is one of the stream types. If we need to pass or return an IO object, it must be passed or returned as a pointer or reference:
第二个含义是:形参或返回类型也不能为流类型。如果需要传递或返回 IO 对象,则必须传递或返回指向该对象的指针或引用:
    ofstream &print(ofstream&);              // ok: takes a reference, no copy
    while (print(out2)) { /* ... */ } // ok: pass reference to out2



Typically, we pass a stream as a nonconst reference because we pass an IO object intending to read from it or write to it. Reading or writing an IO object changes its state, so the reference must be nonconst.
一般情况下,如果要传递 IO 对象以便对它进行读写,可用非 const 引用的方式传递这个流对象。对 IO 对象的读写会改变它的状态,因此引用必须是非 const 的。
Exercises Section 8.1
Exercise 8.1:Assuming os is an ofstream, what does the following program do?
假设 os 是一个 ofstream 对象,下面程序做了什么?
    os << "Goodbye!" << endl;



What if os is an ostringstream? Whatif os is an ifstream?
如果 os 是 ostringstream 对象呢?或者,os 是 ifstream 呢?
Exercise 8.2:The following declaration is in error. Identify and correct the problem(s):
下面的声明是错误的,指出其错误并改正之:
    ostream print(ostream os);






      

8.2. Condition States
8.2. 条件状态
Before we explore the types defined in fstream and sstream, we need to understand a bit more about how the IO library manages its buffers and the state of a stream. Keep in mind that the material we cover in this section and the next applies equally to plain streams, file streams, or string streams.
在展开讨论 fstream 和 sstream 头文件中定义的类型之前,需要了解更多 IO 标准库如何管理其缓冲区及其流状态的相关内容。谨记本节和下一节所介绍的内容同样适用于普通流、文件流以及 string 流。
Inherent in doing IO is the fact that errors can occur. Some errors are recoverable; others occur deep within the system and are beyond the scope of a program to correct. The IO library manages a set of condition state members that indicate whether a given IO object is in a usable state or has encountered a particular kind of error. The library also defines a set of functions and flags, listed in Table 8.2, that give us access to and let us manipulate the state of each stream.
实现 IO 的继承正是错误发生的根源。一些错误是可恢复的;一些错误则发生在系统底层,位于程序可修正的范围之外。IO 标准库管理一系列条件状态(condition state)成员,用来标记给定的 IO 对象是否处于可用状态,或者碰到了哪种特定的错误。表 8.2 列出了标准库定义的一组函数和标记,提供访问和操纵流状态的手段。
Table 8.2. IO Library Condition State
表 8.2. IO 标准库的条件状态
strm::iostateName of the machine-dependent integral type, defined by each iostream class that is used to define the condition states.
机器相关的整型名,由各个 iostream 类定义,用于定义条件状态
strm::badbitstrm::iostate value used to indicate that a stream is corrupted.
strm::iostate 类型的值,用于指出被破坏的流
strm::failbitstrm::iostate value used to indicate that an IO operation failed.
strm::iostate 类型的值,用于指出失败的 IO 操作
strm::eofbitstrm::iostate value used to indicate the a stream hit end-of-file.
strm::iostate 类型的值,用于指出流已经到达文件结束符
s.eof()true if eofbit in the stream s is set.
如果设置了流 s 的 eofbit 值,则该函数返回 true
s.fail()true if failbit in the stream s is set.
如果设置了流 s 的 failbit 值,则该函数返回 true
s.bad()true if badbit in the stream s is set.
如果设置了流 s 的 badbit 值,则该函数返回 true
s.good()true if the stream s is in a valid state.
如果流 s 处于有效状态,则该函数返回 true
s.clear()Reset all condition values in the stream s to valid state.
将流 s 中的所有状态值都重设为有效状态
s.clear(flag)Set specified condition state(s) in s to valid. Type of flag is strm::iostate.
将流 s 中的某个指定条件状态设置为有效。flag 的类型是 strm::iostate
s.setstate(flag)Add specified condition to s. Type of flag is strm::iostate.
给流 s 添加指定条件。flag 的类型是 strm::iostate
s.rdstate()Returns current condition of s as an strm::iostate value.
返回流 s 的当前条件,返回值类型为 strm::iostate


As an example of an IO error, consider the following code:
考虑下面 IO 错误的例子:
     int ival;
     cin >> ival;



If we enter Borges on the standard input, then cin will be put in an error state following the unsuccessful attempt to read a string of characters as an int. Similarly, cin will be in an error state if we enter an end-of-file. Had we entered 1024, then the read would be successful and cin would be in a good, non-error state.
如果在标准输入设备输入 Borges,则 cin 在尝试将输入的字符串读为 int 型数据失败后,会生成一个错误状态。类似地,如果输入文件结束符(end-of-file),cin 也会进入错误状态。而如果输入 1024,则成功读取,cin 将处于正确的无错误状态。
To be used for input or output, a stream must be in a non-error state. The easiest way to test whether a stream is okay is to test its truth value:
流必须处于无错误状态,才能用于输入或输出。检测流是否用的最简单的方法是检查其真值:
          if (cin)
               // ok to use cin, it is in a valid state

          while (cin >> word)
               // ok: read operation successful ...



The if directly tests the state of the stream. The while does so indirectly by testing the stream returned from the expression in the condition. If that input operation succeeds, then the condition tests true.
if 语句直接检查流的状态,而 while 语句则检测条件表达式返回的流,从而间接地检查了流的状态。如果成功输入,则条件检测为 true。
Condition States
条件状态
Many programs need only know whether a stream is valid. Other programs need more fine-grained access to and control of the state of the stream. Rather than knowing that the stream is in an error state, we might want to know what kind of error was encountered. For example, we might want to distinguish between reaching end-of-file and encountering an error on the IO device.
许多程序只需知道是否有效。而某些程序则需要更详细地访问或控制流的状态,此时,除了知道流处于错误状态外,还必须了解它遇到了哪种类型的错误。例如,程序员也许希望弄清是到达了文件的结尾,还是遇到了 IO 设备上的错误。
Each stream object contains a condition state member that is managed through the setstate and clear operations. This state member has type iostate, which is a machine-dependent integral type defined by each iostream class. It is used as a collection of bits, much the way we used the int_quiz1 variable to represent test scores in the example in Section 5.3.1 (p. 156).
所有流对象都包含一个条件状态成员,该成员由 setstate 和 clear 操作管理。这个状态成员为 iostate 类型,这是由各个 iostream 类分别定义的机器相关的整型。该状态成员以二进制位(bit)的形式使用,类似于第 5.3.1 节的例子中用于记录测验成绩的 int_quiz1 变量。
Each IO class also defines three const values of type iostate that represent particular bit patterns. These const values are used to indicate particular kinds of IO conditions. They can be used with the bitwise operators (Section 5.3, p. 154) to test or set multiple flags in one operation.
每个 IO 类还定义了三个 iostate 类型的常量值,分别表示特定的位模式。这些常量值用于指出特定类型的 IO 条件,可与位操作符(第 5.3 节)一起使用,以便在一次操作中检查或设置多个标志。
The badbit indicates a system level failure, such as an unrecoverable read or write error. It is usually not possible to continue using a stream after such an error. The failbit is set after a recoverable error, such as reading a character when numeric data was expected. It is often possible to correct the problem that caused the failbit to be set. The eofbit is set when an end-of-file is encountered. Hitting end-of-file also sets the failbit.
badbit 标志着系统级的故障,如无法恢复的读写错误。如果出现了这类错误,则该流通常就不能再继续使用了。如果出现的是可恢复的错误,如在希望获得数值型数据时输入了字符,此时则设置 failbit 标志,这种导致设置 failbit 的问题通常是可以修正的。eofbit 是在遇到文件结束符时设置的,此时同时还设置了 failbit。
The state of the stream is revealed by the bad, fail, eof, and good operations. If any of bad, fail, or eof are true, then testing the stream itself will indicate that the stream is in an error state. Similarly, the good operation returns TRue if none of the other conditions is true.
流的状态由 bad、fail、eof 和 good 操作提示。如果 bad、fail 或者 eof 中的任意一个为 true,则检查流本身将显示该流处于错误状态。类似地,如果这三个条件没有一个为 true,则 good 操作将返回 true。
The clear and setstate operations change the state of the condition member. The clear operations put the condition back in its valid state. They are called after we have remedied whatever problem occurred and we want to reset the stream to its valid state. The setstate operation turns on the specified condition to indicate that a problem occurred. setstate leaves the existing state variables unchanged except that it adds the additional indicated state(s).
clear 和 setstate 操作用于改变条件成员的状态。clear 操作将条件重设为有效状态。在流的使用出现了问题并做出补救后,如果我们希望把流重设为有效状态,则可以调用 clear 操作。使用 setstate 操作可打开某个指定的条件,用于表示某个问题的发生。除了添加的标记状态,setstate 将保留其他已存在的状态变量不变。
Interrogating and Controlling the State of a Stream
流状态的查询和控制
We might manage an input operation as follows:
可以如下管理输入操作
    int ival;
    // read cin and test only for EOF; loop is executed even if there are other IO failures
    while (cin >> ival, !cin.eof()) {
        if (cin.bad())         // input stream is corrupted; bail out
            throw runtime_error("IO stream corrupted");
        if (cin.fail()) {                        // bad input
            cerr<< "bad data, try again";        // warn the user
            cin.clear(istream::failbit);         // reset the stream
            continue;                            // get next input
        }
        // ok to process ival
    }



This loop reads cin until end-of-file or an unrecoverable read error occurs. The condition uses a comma operator (Section 5.9, p. 168). Recall that the comma operator executes by evaluating each operand and returns its rightmost operand as its result. The condition, therefore, reads cin and ignores its result. The result of the condition is the result of !cin.eof(). If cin hit end-of-file, the condition is false and we fall out of the loop. If cin did not hit end-of-file, we enter the loop, regardless of any other error the read might have encountered.
这个循环不断读入 cin,直到到达文件结束符或者发生不可恢复的读取错误为止。循环条件使用了逗号操作符(第 5.9 节)。回顾逗号操作符的求解过程:首先计算它的每一个操作数,然后返回最右边操作数作为整个操作的结果。因此,循环条件只读入 cin 而忽略了其结果。该条件的结果是 !cin.eof() 的值。如果 cin 到达文件结束符,条件则为假,退出循环。如果 cin 没有到达文件结束符,则不管在读取时是否发生了其他可能遇到的错误,都进入循环。
Inside the loop, we first check whether the stream is corrupted. If so, we exit by throwing an exception (Section 6.13, p. 215). If the input was invalid, we print a warning, and clear the failbit state. In this case, we execute a continue (Section 6.11, p. 214) to return to the start of the while to read another value into ival. If there were no errors, the rest of the loop can safely use ival.
在循环中,首先检查流是否已破坏。如果是的放,抛出异常并退出循环。如果输入无效,则输出警告并清除 failbit 状态。在本例中,执行 continue 语句(第 6.11 节)回到 while 的开头,读入另一个值 ival。如果没有出现任何错误,那么循环体中余下的部分则可以很安全地使用 ival。
Accessing the Condition State
条件状态的访问
The rdstate member function returns an iostate value that corresponds to the entire current condition state of the stream:
rdstate 成员函数返回一个 iostate 类型值,该值对应于流当前的整个条件状态:
     // remember current state of cin
     istream::iostate old_state = cin.rdstate();
     cin.clear();
     process_input();  // use cin
     cin.clear(old_state); // now reset cin to old state



Dealing with Multiple States
多种状态的处理
Often we need to set or clear multiple state bits. We could do so by making multiple calls to the setstate or clear functions. Alternatively, we could use the bitwise OR (Section 5.3, p. 154) operator to generate a value to pass two or more state bits in a single call. The bitwise OR generates an integral value using the bit patterns of its operands. For each bit in the result, the bit is 1 if the corresponding bit is 1 in either of its operands. For example:
常常会出现需要设置或清除多个状态二进制位的情况。此时,可以通过多次调用 setstate 或者 clear 函数实现。另外一种方法则是使用按位或(OR)操作符(第 5.3 节)在一次调用中生成“传递两个或更多状态位”的值。按位或操作使用其操作数的二进制位模式产生一个整型数值。对于结果中的每一个二进制位,如果其值为 1,则该操作的两个操作数中至少有一个的对应二进制位是 1。例如:
   // sets both the badbit and the failbit
   is.setstate(ifstream::badbit | ifstream::failbit);



tells the object is to turn on both the failbit and the badbit. The argument
将对象 is 的 failbit 和 badbit 位同时打开。实参:
     is.badbit | is.failbit



creates a value in which the bits corresponding to the badbit and to the failbit are both turned onthat is they are both set to 1. All other bits in the value are zero. The call to setstate uses this value to turn on the bits corresponding to badbit and failbit in the stream's condition state member.
生成了一个值,其对应于 badbit 和 failbit 的位都打开了,也就是将这两个位都设置为 1,该值的其他位则都为 0。在调用 setstate 时,使用这个值来开启流条件状态成员中对应的 badbit 和 failbit 位。
Exercises Section 8.2
Exercise 8.3:Write a function that takes and returns an istream&. The function should read the stream until it hits end-of-file. The function should print what it reads to the standard output. Reset the stream so that it is valid and return the stream.
编写一个函数,其唯一的形参和返回值都是 istream& 类型。该个函数应一直读取流直到到达文件结束符为止,还应将读到的内容输出到标准输出中。最后,重设流使其有效,并返回该流。
Exercise 8.4:Test your function by calling it passing cin as an argument.
通过以 cin 为实参实现调用来测试上题编写的函数。
Exercise 8.5:What causes the following while to terminate?
导致下面的 while 终止的原因是什么?
    while (cin >> i) /* . . . */





 
      

8.3. Managing the Output Buffer
8.3. 输出缓冲区的管理
Each IO object manages a buffer, which is used to hold the data that the program reads and writes. When we write
每个 IO 对象管理一个缓冲区,用于存储程序读写的数据。如有下面语句:
    os << "please enter a value: ";



the literal string is stored in the buffer associated with the stream os. There are several conditions that cause the buffer to be flushedthat is, writtento the actual output device or file:
系统将字符串字面值存储在与流 os 关联的缓冲区中。下面几种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:
The program completes normally. All output buffers are emptied as part of the return from main.
程序正常结束。作为 main 返回工作的一部分,将清空所有输出缓冲区。
At some indeterminate time, the buffer can become full, in which case it will be flushed before writing the next value.
在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。
We can flush the buffer explicitly using a manipulator (Section 1.2.2, p. 7) such as endl.
用操纵符(第 1.2.2 节)显式地刷新缓冲区,例如行结束符 endl。
We can use the unitbuf manipulator to set the stream's internal state to empty the buffer after each output operation.
在每次输出操作执行完后,用 unitbuf 操作符设置流的内部状态,从而清空缓冲区。
We can tie the output stream to an input stream, in which case the output buffer is flushed whenever the associated input stream is read.
可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。
Flushing the Output Buffer
输出缓冲区的刷新
Our programs have already used the endl manipulator, which writes a newline and flushes the buffer. There are two other similar manipulators. The first, flush, is used quite frequently. It flushes the stream but adds no characters to the output. The second, ends, is used much less often. It inserts a null character into the buffer and then flushes it:
我们的程序已经使用过 endl 操纵符,用于输出一个换行符并刷新缓冲区。除此之外,C++ 语言还提供了另外两个类似的操纵符。第一个经常使用的 flush,用于刷新流,但不在输出中添加任何字符。第二个则是比较少用的 ends,这个操纵符在缓冲区中插入空字符 null,然后后刷新它:
    cout << "hi!" << flush;      // flushes the buffer; adds no data
    cout << "hi!" << ends;       // inserts a null, then flushes the buffer
    cout << "hi!" << endl;       // inserts a newline, then flushes the buffer



The unitbuf Manipulator
unitbuf 操纵符
If we want to flush every output, it is better to use the unitbuf manipulator. This manipulator flushes the stream after every write:
如果需要刷新所有输出,最好使用 unitbuf 操纵符。这个操纵符在每次执行完写操作后都刷新流:
    cout << unitbuf << "first" << " second" << nounitbuf;



is equivalent to writing
等价于:
    cout << "first" << flush << " second" << flush;



The nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing.
nounitbuf 操纵符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。
Caution: Buffers Are Not Flushed if the Program Crashes
警告:如果程序崩溃了,则不会刷新缓冲区
Output buffers are not flushed if the program terminates abnormally. When attempting to debug a program that has crashed, we often use the last output to help isolate the region of program in which the bug might occur. If the crash is after a particular print statement, then we know that the crash happened after that point in the program.
如果程序不正常结束,输出缓冲区将不会刷新。在尝试调试已崩溃的程序时,通常会根据最后的输出找出程序发生错误的区域。如果崩溃出现在某个特定的输出语句后面,则可知是在程序的这个位置之后出错。
When debugging a program, it is essential to make sure that any output you think should have been written was actually flushed. Because the system does not automatically flush the buffers when the program crashes, it is likely that there is output that the program wrote but that has not shown up on the standard output. It is still sitting in an output buffer waiting to be printed.
调试程序时,必须保证期待写入的每个输出都确实被刷新了。因为系统不会在程序崩溃时自动刷新缓冲区,这就可能出现这样的情况:程序做了写输出的工作,但写的内容并没有显示在标准输出上,仍然存储在输出缓冲区中等待输出。
If you use the last output to help locate the bug, you need to be certain that all the output really did get printed. Making sure that all output operations include an explicit flush or call to endl is the best way to ensure that you are seeing all the output that the program actually processed.
如果需要使用最后的输出给程序错误定位,则必须确定所有要输出的都已经输出。为了确保用户看到程序实际上处理的所有输出,最好的方法是保证所有的输出操作都显式地调用了 flush 或 endl。
Countless hours of programmer time have been wasted tracking through code that appeared not to have executed when in fact the buffer simply had not been flushed. For this reason, we tend to use endl rather than \n when writing output. Using endl means we do not have to wonder whether output is pending when a program crashes.
如果仅因为缓冲区没有刷新,程序员将浪费大量的时间跟踪调试并没有执行的代码。基于这个原因,输出时应多使用 endl 而非 '\n'。使用 endl 则不必担心程序崩溃时输出是否悬而未决(即还留在缓冲区,未输出到设备中)。


Tying Input and Output Streams Together
将输入和输出绑在一起
When an input stream is tied to an output stream, any attempt to read the input stream will first flush the buffer associated output stream. The library ties cout to cin, so the statement
当输入流与输出流绑在一起时,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区。标准库将 cout 与 cin 绑在一起,因此语句:
         cin >> ival;



causes the buffer associated with cout to be flushed.
导致 cout 关联的缓冲区被刷新。
Interactive systems usually should be sure that their input and output streams are tied. Doing so means that we are guaranteed that any output, which might include prompts to the user, has been written before attempting to read.
交互式系统通常应确保它们的输入和输出流是绑在一起的。这样做意味着可以保证任何输出,包括给用户的提示,都在试图读之前输出。




The tie function can be called on either istream or an ostream. It takes a pointer to an ostream and ties the argument stream to the object on which tie was called. When a stream ties itself to an ostream, then any IO operation on the stream that called tie flushes the buffer associated with the argument it passed to tie.
tie 函数可用 istream 或 ostream 对象调用,使用一个指向 ostream 对象的指针形参。调用 tie 函数时,将实参流绑在调用该函数的对象上。如果一个流调用 tie 函数将其本身绑在传递给 tie 的 ostream 实参对象上,则该流上的任何 IO 操作都会刷新实参所关联的缓冲区。
    cin.tie(&cout);   // illustration only: the library ties cin and cout for us
    ostream *old_tie = cin.tie();
    cin.tie(0); // break tie to cout, cout no longer flushed when cin is read
    cin.tie(&cerr);   // ties cin and cerr, not necessarily a good idea!
    // ...
    cin.tie(0);       // break tie between cin and cerr
    cin.tie(old_tie); // restablish normal tie between cin and cout



An ostream object can be tied to only one istream object at a time. To break an existing tie, we pass in an argument of 0.
一个 ostream 对象每次只能与一个 istream 对象绑在一起。如果在调用 tie 函数时传递实参 0,则打破该流上已存在的捆绑。
 
      

8.4. File Input and Output
8.4. 文件的输入和输出
The fstream header defines three types to support file IO:
fstream 头文件定义了三种支持文件 IO 的类型:
ifstream, derived from istream, reads from a file.
ifstream,由 istream 派生而来,提供读文件的功能。
ofstream, derived from ostream, writes to a file.
ofstream,由 ostream 派生而来,提供写文件的功能。
fstream, derived from iostream, reads and writes the same file.
fstream,由 iostream 派生而来,提供读写同一个文件的功能。
The fact that these types are derived from the corresponding iostream types means that we already know most of what we need to know about how to use the fstream types. In particular, we can use the IO operators (<< and >>) to do formatted IO on a file, and the material covered in the previous sections on condition states apply identically to fstream objects.
这些类型都由相应的 iostream 类型派生而来,这个事实意味着我们已经知道使用 fstream 类型需要了解的大部分内容了。特别是,可使用 IO 操作符(<< 和 >> )在文件上实现格式化的 IO,而且在前面章节介绍的条件状态也同样适用于 fstream 对象。
In addition to the behavior that fstream types inherit, they also define two new operations of their ownopen and closealong with a constructor that takes the name of a file to open. These operations can be called on objects of fstream, ifstream, or ofstream but not on the other IO types.
fstream 类型除了继承下来的行为外,还定义了两个自己的新操作—— open 和 close,以及形参为要打开的文件名的构造函数。fstream、ifstream 或 ofstream 对象可调用这些操作,而其他的 IO 类型则不能调用。
8.4.1. Using File Stream Objects
8.4.1. 文件流对象的使用
So far our programs have used the library-defined objects, cin, cout, and cerr. When we want to read or write a file, we must define our own objects, and bind them to the desired files. Assuming that ifile and ofile are strings with the names of the files we want to read and write, we might write code such as
迄今为止,我们的程序已经使用过标准库定义的对象:cin、cout 和 cerr。需要读写文件时,则必须定义自己的对象,并将它们绑定在需要的文件上。假设 ifile 和 ofile 是存储希望读写的文件名的 strings 对象,可如下编写代码:
    // construct an ifstream and bind it to the file named ifile
    ifstream infile(ifile.c_str());
    // ofstream output file object to write file named ofile
    ofstream outfile(ofile.c_str());



to define and open a pair of fstream objects. infile is a stream that we can read and outfile is a stream that we can write. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file.
上述代码定义并打开了一对 fstream 对象。infile 是读的流,而 outfile 则是写的流。为 ifstream 或者 ofstream 对象提供文件名作为初始化式,就相当于打开了特定的文件。
    ifstream infile;    // unbound input file stream
    ofstream outfile;   // unbound output file stream



These definitions define infile as a stream object that will read from a file and outfile as an object that we can use to write to a file. Neither object is as yet bound to a file. Before we use an fstream object, we must also bind it to a file to read or write:
上述语句将 infile 定义为读文件的流对象,将 outfile 定义为写文件的对象。这两个对象都没有捆绑具体的文件。在使用 fstream 对象之前,还必须使这些对象捆绑要读写的文件:
    infile.open("in");   // open file named "in" in the current directory
    outfile.open("out"); // open file named "out" in the current directory



We bind an existing fstream object to the specified file by calling the open member. The open function does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate.
调用 open 成员函数将已存在的 fstream 对象与特定文件绑定。为了实现读写,需要将指定的文件打开并定位,open 函数完成系统指定的所有需要的操作。
Caution: File Names in C++
警告:C++ 中的文件名
For historical reasons, the IO library uses C-style character strings (Section 4.3, p. 130) rather than C++ strings to refer to file names. When we call open or use a file name as the initializer when creating an fstream object, the argument we pass is a C-style string, not a library string. Often our programs obtain file names by reading the standard input. As usual, it is a good idea to read into a string, not a C-style character array. Assuming that the name of the file we wish to use is in a string, we can use the c_str member (Section 4.3.2, p. 139) to obtain a C-style string.
由于历史原因,IO 标准库使用 C 风格字符串(第 4.3 节)而不是 C++ strings 类型的字符串作为文件名。在创建 fstream 对象时,如果调用 open 或使用文件名作初始化式,需要传递的实参应为 C 风格字符串,而不是标准库 strings 对象。程序常常从标准输入获得文件名。通常,比较好的方法是将文件名读入 string 对象,而不是 C 风格字符数组。假设要使用的文件名保存在 string 对象中,则可调用 c_str 成员(第 4.3.2 节)获取 C 风格字符串。


Checking Whether an Open Succeeded
检查文件打开是否成功
After opening a file, it is usually a good idea to verify that the open succeeded:
打开文件后,通常要检验打开是否成功,这是一个好习惯:
    // check that the open succeeded
    if (!infile) {
        cerr << "error: unable to open input file: "
             << ifile << endl;
        return -1;
    }



This condition is similar to those we've used to test whether cin had hit end-of-file or encountered some other error. When we test a stream, the effect is to test whether the object is "okay" for input or output. If the open fails, then the state of the fstream object is that it is not ready for doing IO. When we test the object
这个条件与之前测试 cin 是否到达文件尾或遇到某些其他错误的条件类似。检查流等效于检查对象是否“适合”输入或输出。如果打开(open)失败,则说明 fstream 对象还没有为 IO 做好准备。当测试对象
    if (outfile) // ok to use outfile?



a true return means that it is okay to use the file. Because we want to know if the file is not okay, we invert the return from checking the stream:
返回 true 意味着文件已经可以使用。由于希望知道文件是否未准备好,则对返回值取反来检查流:
    if (!outfile) // not ok to use outfile?



Rebinding a File Stream to a New File
将文件流与新文件重新捆绑
Once an fstream has been opened, it remains associated with the specified file. To associate the fstream with a different file, we must first close the existing file and then open a different file:
fstream 对象一旦打开,就保持与指定的文件相关联。如果要把 fstream 对象与另一个不同的文件关联,则必须先关闭(close)现在的文件,然后打开(open)另一个文件:要点是在尝试打开新文件之前,必须先关闭当前的文件流。open 函数会检查流是否已经打开。如果已经打开,则设置内部状态,以指出发生了错误。接下来使用文件流的任何尝试都会失败。
     ifstream infile("in");      // opens file named "in" for reading
     infile.close();             // closes "in"
     infile.open("next");        // opens file named "next" for reading



Clearing the State of a File Stream
清除文件流的状态
Consider a program that has a vector containing names of files it should open and read, doing some processing on the words stored in each file. Assuming the vector is named files, such a progam might have a loop like the following:
考虑这样的程序,它有一个 vector 对象,包含一些要打开并读取的文件名,程序要对每个文件中存储的单词做一些处理。假设该 vector 对象命名为 files,程序也许会有如下循环:
    // for each file in the vector
    while (it != files.end()) {
        ifstream input(it->c_str());   // open the file;
        // if the file is ok, read and "process" the input
        if (!input)
            break;                  // error: bail out!
        while(input >> s)               // do the work on this file
            process(s);
        ++it;                           // increment iterator to get next file
    }



Each trip through the loop constructs the ifstream named input open to read the indicated file. The initializer in the constructor uses the arrow operator (Section 5.6, p. 164) which dereferences it and fetches the c_str member from the underlying string that it currently denotes. The file is opened by the constructor, and assuming the open succeeded, we read that file until we hit end-of-file or some other error condition. At that point, input is in an error state. Any further attempt to read from input will fail. Because input is local to the while loop, it is created on each iteration. That means that it starts out each iteration in a clean stateinput.good() is true.
每一次循环都构造了名为 input 的 ifstream 对象,打开并读取指定的文件。构造函数的初始化式使用了箭头操作符(第 5.6 节)对 it 进行解引用,从而获取 it 当前表示的 string 对象的 c_str 成员。文件由构造函数打开,并假设打开成功,读取文件直到到达文件结束符或者出现其他的错误条件为止。在这个点上,input 处于错误状态。任何读 input 的尝试都会失败。因为 input 是 while 循环的局部变量,在每次迭代中创建。这就意味着它在每次循环中都以干净的状态即 input.good() 为 true,开始使用。
If we wanted to avoid creating a new stream object on each trip through the while, we might move the definition of input out of the while. This simple change means that we must manage the stream state more carefully. When we encounter end-of-file, or any other error, the internal state of the stream is set so that further reads or writes are not allowed. Closing a stream does not change the internal state of the stream object. If the last read or write operation failed, the state of the object remains in a failure mode until we execute clear to reset the condition of the stream. After the clear, it is as if we had created the object afresh.
如果希望避免在每次 while 循环过程中创建新流对象,可将 input 的定义移到 while 之前。这点小小的改动意味着必须更仔细地管理流的状态。如果遇到文件结束符或其他错误,将设置流的内部状态,以便之后不允许再对该流做读写操作。关闭流并不能改变流对象的内部状态。如果最后的读写操作失败了,对象的状态将保持为错误模式,直到执行 clear 操作重新恢复流的状态为止。调用 clear 后,就像重新创建了该对象一样。
If we wish to reuse an existing stream object, our while loop must remember to close and clear the stream on each trip through the loop:
如果打算重用已存在的流对象,那么 while 循环必须在每次循环进记得关闭(close)和清空(clear)文件流:
    ifstream input;
    vector<string>::const_iterator it = files.begin();
    //   for each file in the vector
    while (it != files.end()) {
        input.open(it->c_str());  // open the file
        // if the file is ok, read and "process" the input
        if (!input)
            break;                    // error: bail out!
        while(input >> s) // do the work on this file
            process(s);
        input.close();        // close file when we're done with it
        input.clear();        // reset state to ok
        ++it;                 // increment iterator to get next file
    }



Had we neglected the call to clear, this loop would read only the first file. To see why, consider what happens in this loop: First we open the indicated file. Assuming open succeeded, we read the file until we hit end-of-file or some other error condition. At that point, input is in an error state. If we close but do not clear the stream, then any subsequent input operation on input will fail. Once we have closed the file, we can open the next one. However, the read of input in the inner while will failafter all, the last read from this stream hit end-of-file. The fact that the end-of-file was on a different file is irrelevant!
如果忽略 clear 的调用,则循环只能读入第一个文件。要了解其原因,就需要考虑在循环中发生了什么:首先打开指定的文件。假设打开成功,则读取文件直到文件结束或者出现其他错误条件为止。在这个点上,input 处于错误状态。如果在关闭(close)该流前没有调用 clear 清除流的状态,接着在 input 上做的任何输入运算都会失败。一旦关闭该文件,再打开 下一个文件时,在内层 while 循环上读 input 仍然会失败——毕竟最后一次对流的读操作到达了文件结束符,事实上该文件结束符对应的是另一个与本文件无关的其他文件。
If we reuse a file stream to read or write more than one file, we must clear the stream before using it to read from another file.
如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用 clear 清除该流的状态。



Exercises Section 8.4.1
Exercise 8.6:Because ifstream inherits from istream, we can pass an ifstream object to a function that takes a reference to an istream. Use the function you wrote for the first exercise in Section 8.2 (p. 291) to read a named file.
由于 ifstream 继承了 istream,因此可将 ifstream 对象传递给形参为 istream 引用的函数。使用第 8.2 节第一个习题编写的函数读取已命名的文件。
Exercise 8.7:The two programs we wrote in this section used a break to exit the while loop if the open failed for any file in the vector. Rewrite these two loops to print a warning message if a file can't be opened and continue processing by getting the next file name from the vector.
本节编写的两个程序,在打开 vector 容器中存放的任何文件失败时,使用 break 跳出 while 循环。重写这两个循环,如果文件无法打开,则输出警告信息,然后从 vector 中获取下一个文件名继续处理。
Exercise 8.8:The programs in the previous exercise can be written without using a continue statement. Write the program with and without using a continue.
上一个习题的程序可以不用 continue 语句实现。分别使用或不使用 continue 语句编写该程序。
Exercise 8.9:Write a function to open a file for input and read its contents into a vector of strings, storing each line as a separate element in the vector.
编写函数打开文件用于输入,将文件内容读入 string 类型的 vector 容器,每一行存储为该容器对象的一个元素。
Exercise 8.10:Rewrite the previous program to store each word in a separate element.
重写上面的程序,把文件中的每个单词存储为容器的一个元素。



8.4.2. File Modes
8.4.2. 文件模式
Whenever we open a fileeither through a call to open or as part of initializing a stream from a file namea file mode is specified. Each fstream class defines a set of values that represent different modes in which the stream could be opened. Like the condition state flags, the file modes are integral constants that we use with the bitwise operators (Section 5.3, p. 154) to set one or more modes when we open a given file. The file stream constructors and open have a default argument (Section 7.4.1, p. 253) to set the file mode. The value of the default varies based on the type of the stream. Alternatively, we can supply the mode in which to open the file. Table 8.3 on the next page lists the file modes and their meanings.
在打开文件时,无论是调用 open 还是以文件名作为流初始化的一部分,都需指定文件模式(file mode)。每个 fstream 类都定义了一组表示不同模式的值,用于指定流打开的不同模式。与条件状态标志一样,文件模式也是整型常量,在打开指定文件时,可用位操作符(第 5.3 节)设置一个或多个模式。文件流构造函数和 open 函数都提供了默认实参(第 7.4.1 节)设置文件模式。默认值因流类型的不同而不同。此外,还可以显式地以模式打开文件。表 8.3 列出了文件模式及其含义。
Table 8.3. File Modes
表 8.3 文件模式
inopen for input
打开文件做读操作
outopen output
打开文件做写操作
appseek to the end before every write
在每次写之前找到文件尾
ateseek to the end immediately after the open
打开文件后立即将文件定位在文件尾
trunctruncate an existing stream when opening it
打开文件时清空已存在的文件流
binarydo IO operations in binary mode
以二进制模式进行 IO 操作


The modes out, trunc, and app may be specifed only for files associated with an ofstream or an fstream; in may be specified only for files associated with either ifstream or fstream. Any file may be opened in ate or binary mode. The ate mode has an effect only at the open: Opening a file in ate mode puts the file at the end-of-file immediately after the open. A stream opened in binary mode processes the file as a sequence of bytes; it does no interpretation of the characters in the stream.
out、trunc 和 app 模式只能用于指定与 ofstream 或 fstream 对象关联的文件;in 模式只能用于指定与 ifstream 或 fstream 对象关联的文件。所有的文件都可以用 ate 或 binary 模式打开。ate 模式只在打开时有效:文件打开后将定位在文件尾。以 binary 模式打开的流则将文件以字节序列的形式处理,而不解释流中的字符。
By default, files associated with an ifstream are opened in in mode, which is the mode that permits the file to be read. Files opened by an ofstream are opened in out mode, which permits the file to be written. A file opened in out mode is truncated: All data stored in the file is discarded.
默认时,与 ifstream 流对象关联的文件将以 in 模式打开,该模式允许文件做读的操作:与 ofstream 关联的文件则以 out 模式打开,使文件可写。以 out 模式打开的文件会被清空:丢弃该文件存储的所有数据。
In effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc.
从效果来看,为 ofstream 对象指定 out 模式等效于同时指定了 out 和 trunc 模式。




The only way to preserve the existing data in a file opened by an ofstream is to specify app mode explicitly:
对于用 ofstream 打开的文件,要保存文件中存在的数据,唯一方法是显式地指定 app 模式打开:
    //  output mode by default; truncates file named "file1"
    ofstream outfile("file1");
    // equivalent effect: "file1" is explicitly truncated
    ofstream outfile2("file1", ofstream::out | ofstream::trunc);
    //  append mode; adds new data at end of existing file named "file2"
    ofstream appfile("file2", ofstream::app);



The definition of outfile2 uses the bitwise OR operator (Section 5.3, p. 154) to open inOut in both out and trunc mode.
outfile2 的定义使用了按位或操作符(第 5.3 节)将相应的文件同时以 out 和 trunc 模式打开。
Using the Same File for Input and Output
对同一个文件作输入和输出运算
An fstream object can both read and write its associated file. How an fstream uses its file depends on the mode specified when we open the file.
fstream 对象既可以读也可以写它所关联的文件。fstream 如何使用它的文件取决于打开文件时指定的模式。
By default, an fstream is opened with both in and out set. A file opened with both in and out mode set is not truncated. If we open the file associated with an fstream with out mode, but not in mode specified, then the file is truncated. The file is also truncated if trunc is specified, regardless of whether in is specified. The following definition opens the file copyOut in both input and output mode:
默认情况下,fstream 对象以 in 和 out 模式同时打开。当文件同时以 in 和 out 打开时不清空。如果打开 fstream 所关联的文件时,只使用 out 模式,而不指定 in 模式,则文件会清空已存在的数据。如果打开文件时指定了 trunc 模式,则无论是否同时指定了 in 模式,文件同样会被清空。下面的定义将 copyOut 文件同时以输入和输出的模式打开:
    // open for input and output
    fstream inOut("copyOut", fstream::in | fstream::out);



Appendix A.3.8 (p. 837) discusses how to use a file that is opened for both input and output.
对于同时以输入和输出的模式打开的文件,附录 A.3.8 将讨论其使用方法。
Mode Is an Attribute of a File, Not a Stream
模式是文件的属性而不是流的属性
The mode is set each time a file is opened:
每次打开文件时都会设置模式
    ofstream outfile;
    // output mode set to out, "scratchpad" truncated
    outfile.open("scratchpad", ofstream::out);
    outfile.close();    // close outfile so we can rebind it
    // appends to file named "precious"
    outfile.open("precious", ofstream::app);
    outfile.close();
    // output mode set by default, "out" truncated
    outfile.open("out");



The first call to open specifies ofstream::out. The file named "scratchpad" in the current directory is opened in output mode; the file will be truncated. When we open the file named "precious," we ask for append mode. Any data in the file remains, and all writes are done at the end of the file. When we opened "out," we did not specify an output mode explicitly. It is opened in out mode, meaning that any data currently in "out" is discarded.
第一次调用 open 函数时,指定的模式是 ofstream::out。当前目录中名为“scratchpad”的文件以输出模式打开并清空。而名为“precious”的文件,则要求以添加模式打开:保存文件里的原有数据,所有的新内容在文件尾部写入。在打开“out”文件时,没有明确指明输出模式,该文件则以 out 模式打开,这意味着当前存储在“out”文件中的任何数据都将被丢弃。
Any time open is called, the file mode is set, either explicitly or implicitly. If a mode is not specified, the default value is used.
只要调用 open 函数,就要设置文件模式,其模式的设置可以是显式的也可以是隐式的。如果没有指定文件模式,将使用默认值。




Valid Combinations for Open Mode
打开模式的有效组合
Not all open modes can be specified at once. Some are nonsensical, such as opening a file setting both in and trunc. That would yield a stream we intend to read but that we have truncated so that there is no data to read. Table 8.4 lists the valid mode combinations and their meanings.
并不是所有的打开模式都可以同时指定。有些模式组合是没有意义的,例如同时以 in 和 trunc 模式打开文件,准备读取所生成的流,但却因为 trunc 操作而导致无数据可读。表 8.4 列出了有效的模式组合及其含义。
Table 8.4. File Mode Combinations
表 8.4 文件模式的组合
outopen for output; deletes existing data in the file
打开文件做写操作,删除文件中已有的数据
out | appopen for output; all writes at end of file
打开文件做写操作,在文件尾写入
out | truncsame as out
与 out 模式相同
inopen for input
打开文件做读操作
in | outopen for both input and output;
positioned to read the beginning of the file
打开文件做读、写操作,并定位于文件开头处

in | out | truncopen for both input and output,
deletes existing data in the file
打开文件做读、写操作,删除文件中已有的数据



Any open mode combination may also include ate. The effect of adding ate to any of these modes changes only the initial position of the file. Adding ate to any of these mode combinations positions the file to the end before the first input or output operation is performed.
上述所有的打开模式组合还可以添加 ate 模式。对这些模式添加 ate 只会改变文件打开时的初始化定位,在第一次读或写之前,将文件定位于文件末尾处。
8.4.3. A Program to Open and Check Input Files
8.4.3. 一个打开并检查输入文件的程序
Several programs in this book open a given file for input. Because we need to do this work in several programs, we'll write a function, named open_file, to perform it. Our function takes references to an ifstream and a string. The string holds the name of a file to associate with the given ifstream:
本书有好几个程序都要打开给定文件用输入。由于需要在多个程序里做这件工作,我们编写一个名为 open_file 的函数实现这个功能。这个函数有两个引用形参,分别是 ifstream 和 string 类型,其中 string 类型的引用形参存储与指定 ifstream 对象关联的文件名:
    // opens in binding it to the given file
    ifstream& open_file(ifstream &in, const string &file)
    {
        in.close();     // close in case it was already open
        in.clear();     // clear any existing errors
        // if the open fails, the stream will be in an invalid state
        in.open(file.c_str()); // open the file we were given
        return in; // condition state is good if open succeeded
    }



Because we do not know what state the stream is in, we start by calling close and clear to put the stream into a valid state. We next attempt to open the given file. If the open fails, the stream's condition state will indicate that the stream is unusable. We finish by returning the stream, which is either bound to the given file and ready to use or is in an error condition.
由于不清楚流 in 的当前状态,因此首先调用 close 和 clear 将这个流设置为有效状态。然后尝试打开给定的文件。如果打开失败,流的条件状态将标志这个流是不可用的。最后返回流对象 in,此时,in 要么已经与指定文件绑定起来了,要么处于错误条件状态。
Exercises Section 8.4.3
Exercise 8.11:In the open_file function, explain why we call clear before the call to open. What would happen if we neglected to make this call? What would happen if we called clear after the open?
对于 open_file 函数,请解释为什么在调用 open 前先调用 clear 函数。如果忽略这个函数调用,会出现什么问题?如果在 open 后面调用 clear 函数,又会怎样?
Exercise 8.12:In the open_file function, explain what the effect would be if the program failed to execute the close.
对于 open_file 函数,请解释如果程序执行 close 函数失败,会产生什么结果?
Exercise 8.13:Write a program similar to open_file that opens a file for output.
编写类似 open_file 的程序打开文件用于输出。
Exercise 8.14:Use open_file and the program you wrote for the first exercise in Section 8.2 (p. 291) to open a given file and read its contents.
使用 open_file 函数以及第 8.2 节第一个习题编写的程序,打开给定的文件并读取其内容。


 
      

8.5. String Streams
8.5. 字符串流
The iostream library supports in-memory input/output, in which a stream is attached to a string within the program's memory. That string can be written to and read from using the iostream input and output operators. The library defines three kinds of string streams:
iostream 标准库支持内存中的输入/输出,只要将流与存储在程序内存中的 string 对象捆绑起来即可。此时,可使用 iostream 输入和输出操作符读写这个 string 对象。标准库定义了三种类型的字符串流:
istringstream, derived from istream, reads from a string.
istringstream,由 istream 派生而来,提供读 string 的功能。
ostringstream, derived from ostream, writes to a string.
ostringstream,由 ostream 派生而来,提供写 string 的功能。
stringstream, derived from iostream, reads and writes a string.
stringstream,由 iostream 派生而来,提供读写 string 的功能。
To use any of these classes, we must include the sstream header.
要使用上述类,必须包含 sstream 头文件。
Like the fstream types, these types are derived from the iostream types, meaning that all the operations on iostreams also apply to the types in sstream. In addition to the operations that the sstream types inherit, these types have a constructor that takes a string. The constructor copies the string argument into the stringstream object. The operations that read and write the stringstream read or write the string in the object. These classes also define a member named str to fetch or set the string value that the stringstream manipulates.
与 fstream 类型一样,上述类型由 iostream 类型派生而来,这意味着 iostream 上所有的操作适用于 sstream 中的类型。sstream 类型除了继承的操作外,还各自定义了一个有 string 形参的构造函数,这个构造函数将 string 类型的实参复制给 stringstream 对象。对 stringstream 的读写操作实际上读写的就是该对象中的 string 对象。这些类还定义了名为 str 的成员,用来读取或设置 stringstream 对象所操纵的 string 值。
Note that although fstream and sstream share a common base class, they have no other interrelationship. In particular, we cannot use open and close on a stringstream, nor can we use str on an fstream.
注意到尽管 fstream 和 sstream 共享相同的基类,但它们没有其他相互关系。特别是,stringstream 对象不使用 open 和 close 函数,而 fstream 对象则不允许使用 str。
Table 8.5. stringstream-Specific Operations
表 8.5. stringstream 特定的操作
stringstream strm;Creates an unbound stringstream.
创建自由的 stringstream 对象
stringstream strm(s);Creates a stringstream that holds a copy of the string s.
创建存储 s 的副本的 stringstream 对象,其中 s 是 string 类型的对象
strm.str()Returns a copy of the string that strm holds.
返回 strm 中存储的 string 类型对象
strm.str(s)Copies the string s into strm. Returns void.
将 string 类型的 s 复制给 strm,返回 void


Using a stringstream
stringstream 对象的和使用
We've seen programs that need to deal with their input a word at a time or a line at a time. The first sort of programs use the string input operator and the second use the getline function. However, some programs need to do both: They have some processing to do on a per-line basis and other work that needs to be done on each word within each line. Using stringstreamslets us do so:
前面已经见过以每次一个单词或每次一行的方式处理输入的程序。第一种程序用 string 输入操作符,而第二种则使用 getline 函数。然而,有些程序需要同时使用这两种方式:有些处理基于每行实现,而其他处理则要操纵每行中每个单词。可用 stringstreams 对象实现:
    string line, word;      // will hold a line and word from input, respectively
    while (getline(cin, line))   {            // read a line from the input into line
       // do per-line processing
       istringstream stream(line);            // bind to stream to the line we read
       while (stream >> word){          // read a word from line
           // do per-word processing
       }
    }



Here we use getline to get an entire line from the input. To get the words in each line, we bind an istringstream to the line that we read. We can then use the normal string input operator to read the words from each line.
这里,使用 getline 函数从输入读取整行内容。然后为了获得每行中的单词,将一个 istringstream 对象与所读取的行绑定起来,这样只需要使用普通的 string 输入操作符即可读出每行中的单词。
stringstreams Provide Conversions and/or Formatting
stringstream 提供的转换和/或格式化
One common use of stringstreams is when we want to obtain automatic formatting across multiple data types. For example, we might have a collection of numeric values but want their string representation or vice versa. The sstream input and output operations automatically convert an arithmetic type into its corresponding string representation or back again:
stringstream 对象的一个常见用法是,需要在多种数据类型之间实现自动格式化时使用该类类型。例如,有一个数值型数据集合,要获取它们的 string 表示形式,或反之。sstream 输入和输出操作可自动地把算术类型转化为相应的 string 表示形式,反过来也可以。
    int val1 = 512, val2 = 1024;
    ostringstream format_message;
    // ok: converts values to a string representation
    format_message << "val1: " << val1 << "\n"
                   << "val2: " << val2 << "\n";



Here we create an empty ostringstream object named format_message and insert the indicated text into that object. What's important is that the int values are automatically converted to their printable string equivalents. The contents of format_message are the characters
这里创建了一个名为 format_message 的 ostringstream 类型空对象,并将指定的内容插入该对象。重点在于 int 型值自动转换为等价的可打印的字符串。format_message 的内容是以下字符:
val1: 512\nval2: 1024



We could retrieve the numeric value by using an istringstream to read from the string. Reading an istringstream automatically converts from the character representation of a numeric value to its corresponding arithmetic value:
相反,用 istringstream 读 string 对象,即可重新将数值型数据找回来。读取 istringstream 对象自动地将数值型数据的字符表示方式转换为相应的算术值。
   // str member obtains the string associated with a stringstream
   istringstream input_istring(format_message.str());
   string dump; // place to dump the labels from the formatted message
   // extracts the stored ascii values, converting back to arithmetic types
   input_istring >> dump >> val1 >> dump >> val2;
   cout << val1 << " " << val2 << endl;  // prints 512 1024



Here we use the str member to obtain a copy of the string associated with the ostringstream we previously created. We bind input_istring to that string. When we read input_istring, the values are converted back to their original numeric representations.
这里使用 。str 成员获取与之前创建的 ostringstream 对象关联的 string 副本。再将 input_istring 与 string 绑定起来。在读 input_istring 时,相应的值恢复为它们原来的数值型表示形式
To read input_string, we must parse the string into its component parts. We want the numeric values; to get them we must read (and ignore) the labels that are interspersed with the data we want.
为了读取 input_string,必须把该 string 对象分解为若干个部分。我们要的是数值型数据;为了得到它们,必须读取(和忽略)处于所需数据周围的标号。




Because the input operator reads typed values, it is essential that the types of the objects into which we read be compatible with the types of the values read from the stringstream. In this case, input_istring had four components: The string value val1: followed by 512 followed by the string val2: followed by 1024. As usual, whenweread strings using the input operator, whitespace is ignored. Thus, when we read the string associated with format_message, we can ignore the newlines that are part of that value.
因为输入操作符读取的是有类型的值,因此读入的对象类型必须和由 stringstream 读入的值的类型一致。在本例中,input_istring 分成四个部分:string 类型的值 val1,接着是 512,然后是 string 类型的值 val2,最后是 1024。一般情况下,使用输入操作符读 string 时,空白符将会忽略。于是,在读与 format_message 关联的 string 时,忽略其中的换行符。
Exercises Section 8.5
Exercise 8.15:Use the function you wrote for the first exercise in Section 8.2 (p. 291) to print the contents of an istringstream object.
使用第 8.2 节第一个习题编写的函数输出 istringstream 对象的内容。
Exercise 8.16:Write a program to store each line from a file in a vector<string>. Now use an istringstream to read each line from the vector a word at a time.
编写程序将文件中的每一行存储在 vector<string> 容器对象中,然后使用 istringstream 从 vector 里以每次读一个单词的形式读取存储的行。


 
      

Chapter Summary
小结
C++ uses library classes to handle input and output:
C++ 使用标准库类处理输入和输出:
The iostream classes handle stream-oriented input and output
iostream 类处理面向流的输入和输出。
The fstream classes handle IO to named files
fstream 类处理已命名文件的 IO。
The stringstream classes do IO to in-memory strings
stringstream 类处理内存中字符串的 IO。
All of these classes are related by inheritance. The input classes inherit from istream and the output classes from ostream. Thus, operations that can be performed on an istream object can also be performed on either an ifstream or an istringstream. Similarly for the output classes, which inherit from ostream.
所有的这些类都是通过继承相互关联的。输入类继承了 istream,而输出类则继承了 ostream。因此,可在 istream 对象上执行的操作同样适用于 ifstream 或 istringstream 对象。而继承 ostream 的输出类也是类似的。
Each IO object maintains a set of condition states that indicate whether IO can be done through this object. If an error is encounteredsuch as hitting end-of-file on an input streamthen the object's state will be such that no further input can be done until the error is rectified. The library provides a set of functions to set and test these states.
所有 IO 对象都有一组条件状态,用来指示是否可以通过该对象进行 IO 操作。如果出现了错误(例如遇到文件结束符)对象的状态将标志无法再进行输入,直到修正了错误为止。标准库提供了一组函数设置和检查这些状态。
 
      

Defined Terms
术语
base class(基类)
A class that is the parent of another class. The base class defines the interface that a derived class inherits.
是其他类的父类。基类定义了派生类所继承的接口。
condition state(条件状态)
Flags and associated functions usable by any of the stream classes that indicate whether a given stream is usable. States and functions to get and set these states are listed in Table 8.2 (p. 288).
流类用于指示给定的流是否用的标志以及相关函数。表 8.2 列出了流的状态以及获取和设置这些状态的函数。
derived class(派生类)
A derived class is one that shares an interface with its parent class.
与父类共享接口的类。
file mode(文件模式)
Flags defined by the fstream classes that are specified when opening a file and control how a file can be used. Listed in Table 8.3 (p. 297).
由 fstream 类定义的标志,在打开文件和控制文件如何使用时指定。表 8.3 列出了所有的文件模式。
fstream
Stream object that reads or writes a named file. In addition to the normal iostream operations, the fstream class also defines open and close members. The open member function takes a C-style character string that names the file to open and an optional open mode argument. By default ifstreams are opened with in mode, ofstreams with out mode, and fstreams with in and out mode set. The close member closes the file to which the stream is attached. It must be called before another file can be opened.
用来读或写已命名文件的流对象。除了普通的 iostream 操作外,fstream 类还定义了 open 和 close 成员。open 成员函数有一个表示打开文件名的 C 风格字符串参数和一个可选的打开模式参数。默认时,ifstream 对象以 in 模式打开,ofstream 对象以 out 模式打开,而 fstream 对象则同时以 in 和 out 模式打开。close 成员关闭流关联的文件,必须在打开另一个文件前调用。
inheritance(继承)
Types that are related by inheritance share a common interface. A derived class inherits properties from its base class. Chapter 15 covers inheritance.
有继承关系的类型共享相同的接口。派生类继承其基类的属性。第十五章将继承。
object-oriented library(面向对象标准库)
A set of classes related by inheritance. Generally speaking, the base class of an object-oriented library defines an interface that is shared by the classes derived from that base class. In the IO library, the istream and ostream classes serve as base classes for the types defined in the fstream and sstream headers. We can use an object of a derived class as if it were an object of the base class. For example, we can use the operations defined for istream on an ifstream object.
有继承关系的类的集合。一般来说,面向对象标准库的基类定义了接口,由继承这个 基类的各个派生类共享。在 IO 标准库中,istream 和 ostream 类是 fstream 和 sstream 头文件中定义的类型的基类。派生类的对象可当做基类对象使用。例如,可在 ifstream 对象上使用 istream 定义的操作。
stringstream
Stream object that reads or writes a string. In addition to the normal iostream operations, it also defines an overloaded member named str. Calling str with no arguments returns the string to which the stringstream is attached. Calling it with a string attaches the stringstream to a copy of that string.
读写字符串的流对象。除了普通的 iostream 操作外,它还定义了名为 str 的重载成员。无实参地调用 str 将返回 stringstream 所关联的 string 值。用 string 对象做实参调用它则将该 stringstream 对象与实参副本相关联。
     

Chapter 9. Sequential Containers
CONTENTS
目录
Section 9.1 Defining a Sequential Container307
Section 9.2 Iterators and Iterator Ranges311
Section 9.3 Sequence Container Operations316
Section 9.4 How a vector Grows330
Section 9.5 Deciding Which Container to Use333
Section 9.6 strings Revisited335
Section 9.7 Container Adaptors348
Chapter Summary353
Defined Terms353



This chapter completes our discussion of the standard-library sequential container types. It expands on the material from Chapter 3, which introduced the most commonly used sequential container, the vector type. Elements in a sequential container are stored and accessed by position. The library also defines several associative containers, which hold elements whose order depends on a key. Associative containers are covered in the next chapter.
第三章介绍了最常用的顺序容器:vector 类型。本章将对第三章的内容进行扩充和完善,继续讨论标准库提供的顺序容器类型。顺序容器内的元素按其位置存储和访问。除顺序容器外,标准库还定义了几种关联容器,其元素按键(key)排序。我们将在下一章讨论它们。
The container classes share a common interface. This fact makes the library easier to learn; what we learn about one type applies to another. Each container type offers a different set of time and functionality tradeoffs. Often a program using one type can be fine-tuned by substituting another container without changing our code beyond the need to change type declarations.
容器类共享公共的接口,这使标准库更容易学习,只要学会其中一种类型就能运用另一种类型。每种容器类型提供一组不同的时间和功能折衷方案。通常不需要修改代码,只需改变类型声明,用一种容器类型替代另一种容器类型,就可以优化程序的性能。
A container holds a collection of objects of a specified type. We've used one kind of container already: the library vector type. It is a sequential container. It holds a collection of elements of a single type, making it a container. Those elements are stored and accessed by position, making it a sequential container. The order of elements in a sequential container is independent of the value of the elements. Instead, the order is determined by the order in which elements are added to the container.
容器容纳特定类型对象的集合。我们已经使用过一种容器类型:标准库 vector 类型,这是一种顺序容器(sequential container)。它将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。
The library defines three kinds of sequential containers: vector, list, and deque (short for "double-ended queue" and pronounced "deck"). These types differ in how elements are accessed and the relative run-time cost of adding or removing elements. The library also provides three container adaptors. Effectively, an adaptor adapts an underlying container type by defining a new interface in terms of the operations provided by the original type. The sequential container adaptors are stack, queue, and priority_queue.
标准库定义了三种顺序容器类型:vector、list 和 deque(是双端队列“double-ended queue”的简写,发音为“deck”)。它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。标准库还提供了三种容器适配器(adaptors)。实际上,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。顺序容器适配器包括 stack、queue 和 priority_queue 类型,见表 9-1。
Containers define only a small number of operations. Many additional operations are provided by the algorithms library, which we'll cover in Chapter 11. For those operations that are defined by the containers, the library imposes a common interface. The containers vary as to which operations they provide, but if two containers provide the same operation, then the interface (name and number of arguments) will be the same for both container types. The set of operations on the container types form a kind of hierarchy:
容器只定义了少量操作。大多数额外操作则由算法库提供,我们将在第十一章学习算法库。标准库为由容器类型定义的操作强加了公共的接口。这些容器类型的差别在于它们提供哪些操作,但是如果两个容器提供了相同的操作,则它们的接口(函数名字和参数个数)应该相同。容器类型的操作集合形成了以下层次结构:
Some operations are supported by all container types.
一些操作适用于所有容器类型。
Other operations are common to only the sequential or only the associative containers.
另外一些操作则只适用于顺序或关联容器类型。
Still others are common to only a subset of either the sequential or associative containers.
还有一些操作只适用于顺序或关联容器类型的一个子集。
In the remainder of this chapter, we look at the sequential container types and their operations in detail.
在本章的后续部分,我们将详细描述顺序容器类型和它们所提供的操作。
Table 9.1. Sequential Container Types表 9.1. 顺序容器类型Sequential Containers
顺序容器
vectorSupports fast random access
支持快速随机访问
listSupports fast insertion/deletion
支持快速插入/删除
dequeDouble-ended queue
双端队列
Sequential Container Adaptors
顺序容器适配器
stackLast in/First out stack
后进先出(LIFO)堆栈
queueFirst in/First out queue
先进先出(FIFO)队列
priority_queuePriority-managed queue
有优先级管理的队列



    

9.1. Defining a Sequential Container
9.1. 顺序容器的定义
We already know a fair bit about how to use the sequential containers based on what we covered in Section 3.3 (p. 90). To define a container object, we must include its associated header file, which is one of
在第 3.3 节中,我们已经了解了一些使用顺序容器类型的知识。为了定义一个容器类型的对象,必须先包含相关的头文件,即下列头文件之一:
     #include <vector>
     #include <list>
     #include <deque>



Each of the containers is a class template (Section 3.3, p. 90). To define a particular kind of container, we name the container followed by angle brackets that enclose the type of the elements the container will hold:
所有的容器都是类模板(第 3.3 节)。要定义某种特殊的容器,必须在容器名后加一对尖括号,尖括号里面提供容器中存放的元素的类型:
     vector<string>    svec;       // empty vector that can hold strings
     list<int>         ilist;      // empty list that can hold ints
     deque<Sales_item> items;      // empty deque that holds Sales_items



Each container defines a default constructor that creates an empty container of the speicfied type. Recall that a default constructor takes no arguments.
所有容器类型都定义了默认构造函数,用于创建指定类型的空容器对象。默认构造函数不带参数。
 For reasons that shall become clear shortly, the most commonly used container constructor is the default constructor. In most programs, using the default constructor gives the best run-time performance and makes using the container easier.
为了使程序更清晰、简短,容器类型最常用的构造函数是默认构造函数。在大多数的程序中,使用默认构造函数能达到最佳运行时性能,并且使容器更容易使用。




9.1.1. Initializing Container Elements
9.1.1. 容器元素的初始化
In addition to defining a default constructor, each container type also supports constructors that allow us to specify initial element values.
除了默认构造函数,容器类型还提供其他的构造函数,使程序员可以指定元素初值,见表 9.2。
Table 9.2. Container Constructors表 9.2. 容器构造函数C<T> c;Create an empty container named c. C is a container name, such as vector, and T is the element type, such as int or string. Valid for all containers.
创建一个名为 c 的空容器。C 是容器类型名,如 vector,T 是元素类型,如 int 或 string 适用于所有容器。
C c(c2);Create c as a copy of container c2; c and c2 must be the same container type and hold values of the same type. Valid for all containers.
创建容器 c2 的副本 c;c 和 c2 必须具有相同的容器类型,并存放相同类型的元素。适用于所有容器。
C c(b, e);Create c with a copy of the elements from the range denoted by iterators b and e. Valid for all containers.
创建 c,其元素是迭代器 b 和 e 标示的范围内元素的副本。适用于所有容器。
C c(n, t);Create c with n elements, each with value t, which must be a value of the element type of C or a type convertible to that type.
用 n 个值为 t 的元素创建容器 c,其中值 t 必须是容器类型 C 的元素类型的值,或者是可转换为该类型的值。
Sequential containers only.
只适用于顺序容器
C c(n);Create c with n value-initialized (Section 3.3.1, p. 92) elements.
创建有 n 个值初始化(第 3.3.1 节)(value-initialized)元素的容器 c。
Sequential containers only.
只适用于顺序容器



Intializing a Container as a Copy of Another Container
将一个容器初始化为另一个容器的副本
When we initialize a sequential container using any constructor other than the default constructor, we must indicate how many elements the container will have. We must also supply initial values for those elements. One way to specify both the size and element values is to initialize a new container as a copy of an existing container of the same type:
当不使用默认构造函数,而是用其他构造函数初始化顺序容器时,必须指出该容器有多少个元素,并提供这些元素的初值。同时指定元素个数和初值的一个方法是将新创建的容器初始化为一个同类型的已存在容器的副本:
     vector<int> ivec;
     vector<int> ivec2(ivec);   // ok: ivec is vector<int>
     list<int>   ilist(ivec);   // error: ivec is not list<int>
     vector<double> dvec(ivec); // error: ivec holds int not double



When we copy one container into another, the types must match exactly: The container type and element type must be the same.
将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。




Initializing as a Copy of a Range of Elements
初始化为一段元素的副本
Although we cannot copy the elements from one kind of container to another directly, we can do so indirectly by passing a pair of iterators (Section 3.4, p. 95). When we use iterators, there is no requirement that the container types be identical. The element types in the containers can differ as long as they are compatible. It must be possible to convert the element we copy into the type held by the container we are constructing.
尽管不能直接将一种容器内的元素复制给另一种容器,但系统允许通过传递一对迭代器(第 3.4 节)间接实现该实现该功能。使用迭代器时,不要求容器类型相同。容器内的元素类型也可以不相同,只要它们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。
The iterators denote a range of elements that we want to copy. These elements are used to initialize the elements of the new container. The iterators mark the first and one past the last element to be copied. We can use this form of initialization to copy a container that we could not copy directly. More importantly, we can use it to copy only a subsequence of the other container:
迭代器标记了要复制的元素范围,这些元素用于初始化新容器的元素。迭代器标记出要复制的第一个元素和最后一个元素。采用这种初始化形式可复制不能直接复制的容器。更重要的是,可以实现复制其他容器的一个子序列:
     // initialize slist with copy of each element of svec
     list<string> slist(svec.begin(), svec.end());

     // find midpoint in the vector
     vector<string>::iterator mid = svec.begin() + svec.size()/2;

     // initialize front with first half of svec: The elements up to but not including *mid
     deque<string> front(svec.begin(), mid);
     // initialize back with second half of svec: The elements *mid through end of svec
     deque<string> back(mid, svec.end());



Recall that pointers are iterators, so it should not be surprising that we can initialize a container from a pair of pointers into a built-in array:
回顾一下指针,我们知道指针就是迭代器,因此允许通过使用内置数组中的一对指针初始化容器也就不奇怪了:
     char *words[] = {"stately", "plump", "buck", "mulligan"};

     // calculate how many elements in words
     size_t words_size = sizeof(words)/sizeof(char *);

     // use entire array to initialize words2
     list<string> words2(words, words + words_size);



Here we use sizeof (Section 5.8, p. 167) to calculate the size of the array. We add that size to a pointer to the first element to get a pointer to a location one past the end of the array. The initializers for words2 are a pointer to the first element in words and a second pointer one past the last element in that array. The second pointer serves as a stopping condition; the location it addresses is not included in the elements to be copied.
这里,使用 sizeof(5.8 节)计算数组的长度。将数组长度加到指向第一个元素的指针上就可以得到指向超出数组末端的下一位置的指针。通过指向第一个元素的指针 words 和指向数组中最后一个元素的下一位置的指针,实现了 words2 的初始化。其中第二个指针提供停止复制的条件,其所指向的位置上存放的元素并没有复制。
Allocating and Initializing a Specified Number of Elements
分配和初始化指定数目的元素
When creating a sequential container, we may specify an explicit size and an (optional) initializer to use for the elements. The size can be either a constant or non-constant expression. The element initializer must be a valid value that can be used to initialize an object of the element type:
创建顺序容器时,可显式指定容器大小和一个(可选的)元素初始化式。容器大小可以是常量或非常量表达式,元素初始化则必须是可用于初始化其元素类型的对象的值:
     const list<int>::size_type list_size = 64;
     list<string> slist(list_size, "eh?"); // 64 strings, each is eh?



This code initializes slist to have 64 elements, each with the value eh?.
这段代码表示 slist 含有 64 个元素,每个元素都被初始化为“eh?”字符串。
As an alternative to specifying the number of elements and an element initializer, we can also specify only the size:
创建容器时,除了指定元素个数,还可选择是否提供元素初始化式。我们也可以只指定容器大小:
list<int> ilist(list_size); // 64 elements, each initialized to 0
// svec has as many elements as the return value from get_word_count
extern unsigned get_word_count(const string &file_name);
vector<string> svec(get_word_count("Chimera"));



When we do not supply an element initializer, the library generates a value-initialized (Section 3.3.1, p. 92) one for us. To use this form of initialization, the element type must either be a built-in or compound type or be a class type that has a default constructor. If the element type does not have a default constructor, then an explicit element initializer must be specified.
不提供元素初始化式时,标准库将为该容器实现值初始化(3.3.1&nbps;节)。采用这种类型的初始化,元素类型必须是内置或复合类型,或者是提供了默认构造函数的类类型。如果元素类型没有默认构造函数,则必须显式指定其元素初始化式。
The constructors that take a size are valid only for sequential containers; they are not supported for the associative containers,
接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。




Exercises Section 9.1.1
Exercise 9.1:Explain the following initializations. Indicate if any are in error, and if so, why.
解释下列初始化,指出哪些是错误的,为什么?
     int ia[7] = { 0, 1, 1, 2, 3, 5, 8 };
     string sa[6] = {
         "Fort Sumter", "Manassas", "Perryville",
         "Vicksburg", "Meridian", "Chancellorsville" };
     (a) vector<string> svec(sa, sa+6);
     (b) list<int> ilist( ia+4, ia+6);
     (c) vector<int> ivec(ia, ia+8);
     (d) list<string> slist(sa+6, sa);



Exercise 9.2: Show an example of each of the four ways to create and initialize a vector. Explain what values each vector contains.
创建和初始化一个 vector 对象有 4 种方式,为每种方式提供一个例子,并解释每个例子生成的 vector 对象包含什么值。
Exercise 9.3: Explain the differences between the constructor that takes a container to copy and the constructor that takes two iterators.
解释复制容器对象的构造函数和使用两个迭代器的构造函数之间的差别。




9.1.2. Constraints on Types that a Container Can Hold
9.1.2. 容器内元素的类型约束
While most types can be used as the element type of a container, there are two constraints that element types must meet:
C++ 语言中,大多数类型都可用作容器的元素类型。容器元素类型必须满足以下两个约束:
The element type must support assignment.
元素类型必须支持赋值运算。
We must be able to copy objects of the element type.
元素类型的对象必须可以复制。
There are additional constraints on the types used as the key in an associative container, which we'll cover in Chapter 10.
此外,关联容器的键类型还需满足其他的约束,我们将在第十章介绍相关内容。
Most types meet these minimal element type requirements. All of the built-in or compound types, with the exception of references, can be used as the element type. References do not support assignment in its ordinary meaning, so we cannot have containers of references.
大多数类型满足上述最低限度的元素类型要求。除了引用类型外,所有内置或复合类型都可用做元素类型。引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器。
With the exception of the IO library types (and the auto_ptr type, which we cover in Section 17.1.9 (p. 702)), all the library types are valid container element types. In particular, containers themselves satisfy these requirements. We can define containers with elements that are themselves containers. Our Sales_item type also satisifes these requirements.
除输入输出(IO)标准库类型(以及第 17.1.9 节介绍的 auto_ptr 类型)之外,所有其他标准库类型都是有效的容器元素类型。特别地,容器本身也满足上述要求,因此,可以定义元素本身就是容器类型的容器。Sales_item 类型也满足上述要求。
The IO library types do not support copy or assignment. Therefore, we cannot have a container that holds objects of the IO types.
IO 库类型不支持复制或赋值。因此,不能创建存放 IO 类型对象的容器。
Container Operations May Impose Additional Requirements
容器操作的特殊要求
The requirement to support copy and assignment is the minimal requirement on element types. In addition, some container operations impose additional requirements on the element type. If the element type doesn't support the additional requirement, then we cannot perform that operation: We can define a container of that type but may not use that particular operation.
支持复制和赋值功能是容器元素类型的最低要求。此外,一些容器操作对元素类型还有特殊要求。如果元素类型不支持这些特殊要求,则相关的容器操作就不能执行:我们可以定义该类型的容器,但不能使用某些特定的操作。
One example of an operation that imposes a type constraint is the constructors that take a single initializer that specifies the size of the container. If our container holds objects of a class type, then we can use this constructor only if the element type has a default constructor. Most types do have a default constructor, although there are some classes that do not. As an example, assume that Foo is a class that does not define a default constructor but that does have a constructor that takes an int argument. Now, consider the following declarations:
其中一种需外加类型要求的容器操作是指定容器大小并提供单个初始化式的构造函数。如果容器存储类类型的对象,那么只有当其元素类型提供默认构造函数时,容器才能使用这种构造函数。尽管有一些类没有提供默认构造函数,但大多数类类型都会有。例如,假设类 Foo 没有默认构造函数,但提供了需要一个 int 型形参的构造函数。现在,考虑下面的声明:
     vector<Foo> empty;     // ok: no need for element default constructor
     vector<Foo> bad(10);   // error: no default constructor for Foo
     vector<Foo> ok(10, 1); // ok: each element initialized to 1



We can define an empty container to hold Foo objects, but we can define one of a given size only if we also specify an initializer for each element.
我们定义一个存放 Foo 类型对象的空容器,但是,只有在同时指定每个元素的初始化式时,才能使用给定容器大小的构造函数来创建同类型的容器对象。
As we describe the container operations, we'll note the constraints, if any, that each container operation places on the element type.
在描述容器操作时,我们应该留意(如果有的话)每个操作对元素类型的约束。
Containers of Containers
容器的容器
Because the containers meet the constraints on element types, we can define a container whose element type is itself a container type. For example, we might define lines as a vector whose elements are a vector of strings:
因为容器受容器元素类型的约束,所以可定义元素是容器类型的容器。例如,可以定义 vector 类型的容器 lines,其元素为 string 类型的 vector 对象:
     // note spacing: use ">>" not ">>" when specifying a container element type
     vector< vector<string> > lines; // vector of vectors



Note the spacing used when specifying a container element type as a container:
注意,在指定容器元素为容器类型时,必须如下使用空格:
     vector< vector<string> > lines; // ok: space required between close >
     vector< vector<string>> lines; // error: >> treated as shift operator



We must separate the two closing > symbols with a space to indicate that these two characters represent two symbols. Without the space, >> is treated as a single symbol, the right shift operator, and results in a compile-time error.必须用空格隔开两个相邻的 > 符号,以示这是两个分开的符号,否则,系统会认为 >> 是单个符号,为右移操作符,并导致编译时错误。




Exercises Section 9.1.2
Exercise 9.4: Define a list that holds elements that are deques that hold ints.
定义一个 list 对象来存储 deque 对象里的元素,该 deque 对象存放 int 型元素。
Exercise 9.5:Why can we not have containers that hold iostream objects?
为什么我们不可以使用容器来存储 iostream 对象?
Exercise 9.6: Given a class type named Foo that does not define a default constructor but does define a constructor that takes int values, define a list of Foo that holds 10 elements.
假设有一个名为 Foo 的类,这个类没有定义默认构造函数,但提供了需要一个 int 型参数的构造函数,定义一个存放 Foo 的 list 对象,该对象有 10 个元素。




    

9.2. Iterators and Iterator Ranges
9.2. 迭代器和迭代器范围
The constructors that take a pair of iterators are an example of a common form used extensively throughout the library. Before we look further at the container operations, we should understand a bit more about iterators and iterator ranges.
在整个标准库中,经常使用形参为一对迭代器的构造函数。在深入探讨容器操作之前,先来了解一下迭代器和迭代器范围。
In Section 3.4 (p. 95), we first encountered vector iterators. Each of the container types has several companion iterator types. Like the containers, the iterators have a common interface: If an iterator provides an operation, then the operation is supported in the same way for each iterator that supplies that operation. For example, all the container iterators let us read an element from a container, and they all do so by providing the dereference operator. Similarly, they all provide increment and decrement operators to allow us to go from one element to the next. Table 9.3 lists the iterator operations supported by the iterators for all of the library containers.
第 3.4 节首次介绍了 vector 类型的迭代器。每种容器类型都提供若干共同工作的迭代器类型。与容器类型一样,所有迭代器具有相同的接口:如果某种迭代器支持某种操作,那么支持这种操作的其他迭代器也会以相同的方式支持这种操作。例如,所有容器迭代器都支持以解引用运算从容器中读入一个元素。类似地,容器都提供自增和自减操作符来支持从一个元素到下一个元素的访问。表 9.3 列出迭代器为所有标准库容器类型所提供的运算。
Table 9.3. Common Iterator Operations表 9.3. 常用迭代器运算*iterReturn a reference to the element referred to by the iterator iter.
返回迭代器 iter 所指向的元素的引用
iter->memDereference iter and fetch the member named mem from the underlying element. Equivalent to (*iter).mem.
对 iter 进行解引用,获取指定元素中名为 mem 的成员。等效于 (*iter).mem
++iter iter++Increment iter to refer to the next element in the container.
给 iter 加 1,使其指向容器里的下一个元素
--iter iter--Decrement iter to refer to the previous element in the container.
给 iter 减 1,使其指向容器里的前一个元素
iter1 == iter2
iter1 != iter2Compare two iterators for equality (inequality). Two iterators are equal if they refer to the same element of the same container or if they are the off-the-end iterator (Section 3.4, p. 97) for the same container.
比较两个迭代器是否相等(或不等)。当两个迭代器指向同一个容器中的同一个元素,或者当它们都指向同一个容器的超出末端的下一位置时,两个迭代器相等



Iterators on vector and deque Support Additional Operations
vector 和 deque 容器的迭代器提供额外的运算
There are two important sets of operations that only vector and deque support: iterator arithmetic (Section 3.4.1, p. 100) and the use of the relational operators (in addition to == and !=) to compare two iterators. These operations are summarized in Table 9.4 on the facing page.
C++ 定义的容器类型中,只有 vector 和 deque 容器提供下面两种重要的运算集合:迭代器算术运算(第 3.4.1 节),以及使用除了 == 和 != 之外的关系操作符来比较两个迭代器(== 和 != 这两种关系运算适用于所有容器)。表 9.4 总结了这些相关的操作符。
Table 9.4. Operations Supported by vector and deque Iterators表 9.4. vector 和 deque 类型迭代器支持的操作iter + n
iter - nAdding (subtracting) an integral value n to (from) an iterator yields an iterator that many elements forward (backward) within the container. The resulting iterator must refer to an element in the container or one past the end of the container.
在迭代器上加(减)整数值 n,将产生指向容器中前面(后面)第 n 个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置
iter1 += iter2
iter1 -= iter2Compound-assignment versions of iterator addition and subtraction. Assigns the value of adding or subtracting iter1 and iter2 into iter1.
这里迭代器加减法的复合赋值运算:将 iter1 加上或减去 iter2 的运算结果赋给 iter1
iter1 - iter2Subtracting two iterators yields the number that when added to the right-hand iterator yields the left-hand iterator. The iterators must refer to elements in the same container or one past the end of the container.
两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器。这两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置
Supported only for vector and deque.
只适用于 vector 和 deque 容器
>, >=, <, <=Relational operators on iterators. One iterator is less than another if it refers to an element whose position in the container is ahead of the one referred to by the other iterator. The iterators must refer to elements in the same container or one past the end of the container.
迭代器的关系操作符。当一个迭代器指向的元素在容器中位于另一个迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。关系操作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置
Supported only for vector and deque.
只适用于 vector 和 deque 容器



The reason that only vector and deque support the relational operators is that only vector and deque offer fast, random access to their elements. These containers are guaranteed to let us efficiently jump directly to an element given its position in the container. Because these containers support random access by position, it is possible for their iterators to efficiently implement the arithmetic and relational operations.
关系操作符只适用于 vector 和 deque 容器,这是因为只有这种两种容器为其元素提供快速、随机的访问。它们确保可根据元素位置直接有效地访问指定的容器元素。这两种容器都支持通过元素位置实现的随机访问,因此它们的迭代器可以有效地实现算术和关系运算。
For example, we could calculate the midpoint of a vector as follows:
例如,下面的语句用于计算 vector 对象的中点位置:
     vector<int>::iterator iter = vec.begin() + vec.size()/2;



On the other hand, this code
另一方面,代码:
     // copy elements from vec into ilist
     list<int> ilist(vec.begin(), vec.end());
     ilist.begin() + ilist.size()/2; // error: no addition on list iterators



is an error. The list iterator does not support the arithmetic operationsaddition or subtractionnor does it support the relational (<=, <, >=, >) operators. It does support pre- and postfix increment and decrement and the equality (inequality) operators.
是错误的。list 容器的迭代器既不支持算术运算(加法或减法),也不支持关系运算(<=, <, >=, >),它只提供前置和后置的自增、自减运算以及相等(不等)运算。
In Chapter 11 we'll see that the operations an iterator supports are fundamental to using the library algorithms.
第十一章中,我们将会了解到迭代器提供运算是使用标准库算法的基础。
Exercises Section 9.2
Exercise 9.7:What is wrong with the following program? How might you correct it?
下面的程序错在哪里?如何改正。
     list<int> lst1;
     list<int>::iterator iter1 = lst1.begin(),
                         iter2 = lst1.end();
     while (iter1 < iter2) /* . . . */



Exercise 9.8:Assuming vec_iter is bound to an element in a vector that holds strings, what does this statement do?
假设 vec_iter 与 vector 对象的一个元素捆绑在一起,该 vector 对象存放 string 类型的元素,请问下面的语句实现什么功能?
     if (vec_iter->empty()) /* . . . */



Exercise 9.9:Write a loop to write the elements of a list in reverse order.
编写一个循环将 list 容器的元素逆序输出。
Exercise 9.10:Which, if any, of the following iterator uses are in error?
下列迭代器的用法哪些(如果有的话)是错误的?
     const vector< int > ivec(10);
     vector< string >    svec(10);
     list< int >         ilist(10);

     (a) vector<int>::iterator    it = ivec.begin();
     (b) list<int>::iterator      it = ilist.begin()+2;
     (c) vector<string>::iterator it = &svec[0];
     (d) for (vector<string>::iterator
                  it = svec.begin(); it != 0; ++it)
                     // ...







9.2.1. Iterator Ranges
9.2.1. 迭代器范围
The concept of an iterator range is fundamental to the standard library.


迭代器范围这个概念是标准库的基础。




An iterator range is denoted by a pair of iterators that refer to two elements, or to one past the last element, in the same container. These two iterators, often referred to as first and last, or beg and end, mark a range of elements from the container.
C++ 语言使用一对迭代器标记迭代器范围(iterator range),这两个迭代器分别指向同一个容器中的两个元素或超出末端的下一位置,通常将它们命名为 first 和 last,或 beg 和 end,用于标记容器中的一段元素范围。
Although the names last and end are common, they are a bit misleading. The second iterator never refers to the last element of the range. Instead, it refers to a point one past the last element. The elements in the range include the element referred to by first and every element from first tHRough the element just before last. If the iterators are equal, then the range is empty.
尽管 last 和 end 这两个名字很常见,但是它们却容易引起误解。其实第二个迭代器从来都不是指向元素范围的最后一个元素,而是指向最后一个元素的下一位置。该范围内的元素包括迭代器 first 指向的元素,以及从 first 开始一直到迭代器 last 指向的位置之前的所有元素。如果两个迭代器相等,则迭代器范围为空。
This element range is called a left-inclusive interval. The standard notation for such a range is
此类元素范围称为左闭合区间(left-inclusive interval),其标准表示方式为:
     // to be read as: includes first and each element up to but not including last
     [ first, last )



indicating that the range begins with first and ends with, but does not include, last. The iterator in last may be equal to the first or may refer to an element that appears after the one referred to by first. The last iterator must not refer to an element ahead of the one referred to by first.
表示范围从 first 开始,到 last 结束,但不包括 last。迭代器 last 可以等于 first,或者指向 first 标记的元素后面的某个元素,但绝对不能指向 first 标记的元素前面的元素。
Requirements on Iterators Forming an Iterator Range
对形成迭代器范围的迭代器的要求
Two iterators, first and last, form an iterator range, if
迭代器 first 和 last 如果满足以下条件,则可形成一个迭代器范围:
They refer to elements of, or one-past-the-end of, the same container.
它们指向同一个容器中的元素或超出末端的下一位置。
If the iterators are not equal, then it must be possible to reach last by repeatedly incrementing first. In other words, last must not precede first in the container.
如果这两个迭代器不相等,则对 first 反复做自增运算必须能够到达 last。换句话说,在容器中,last 绝对不能位于 first 之前。
The compiler cannot itself enforce these requirements. It does not know to which container an iterator is bound, nor does it know how many elements are in a container. Failing to meet these requirements results in undefined run-time behavior.
编译器自己不能保证上述要求。编译器无法知道迭代器所关联的是哪个容器,也不知道容器内有多少个元素。若不能满足上述要求,将导致运行时未定义的行为。






Programming Implications of Using Left-Inclusive Ranges
使用左闭合区间的编程意义
The library uses left-inclusive ranges because such ranges have two convenient properties. Assuming first and last denote a valid iterator range, then
因为左闭合区间有两个方便使用的性质,所以标准库使用此烦区间。假设 first 和 last 标记了一个有效的迭代器范围,于是:
When first equals last, the range is empty.
当 first 与 last 相等时,迭代器范围为空;
When first is not equal to last, there is at least one element in the range, and first refers to the first element in that range. Moreover, we can advance first by incrementing it some number of times until first == last.
当 first 与不相等时,迭代器范围内至少有一个元素,而且 first 指向该区间中的第一元素。此外,通过若干次自增运算可以使 first 的值不断增大,直到 first == last 为止。
These properties mean that we can safely write loops such as the following to process a range of elements by testing the iterators:
这两个性质意味着程序员可以安全地编写如下的循环,通过测试迭代器处理一段元素:
     while (first != last) {
         // safe to use *first because we know there is at least one element
         ++first;
     }



Assuming first and last form a valid iterator range, then we know that either first == last, in which case the loop is exited, or the range is non-empty and first refers to an element. Because the condition in the while handles the case where the range is empty, there is no need for a special case to handle an empty range. When the range is non-empty, the loop will execute at least once. Because the loop body increments first, we know the loop will eventually terminate. Moreover, if we are in the loop, then we know that *first is safe: It must refer to an element in the non-empty range between first and last.
假设 first 和 last 标记了一段有效的迭代器范围,于是我们知道要么 first == last,这是退出循环的情况;要么该区间非空,first 指向其第一个元素。因为 while 循环条件处理了空区间情况,所以对此无须再特别处理。当迭代器范围非空时,循环至少执行一次。由于循环体每次循环就给 first 加 1,因此循环必定会终止。而且在循环内可确保 *first 是安全的:它必然指向 first 和 last 之间非空区间内的某个特定元素。
Exercises Section 9.2.1
Exercise 9.11: What are the constraints on the iterators that form iterator ranges?
要标记出有效的迭代器范围,迭代器需要满足什么约束?
Exercise 9.12:Write a function that takes a pair of iterators and an int value. Look for that value in the range and return a bool indicating whether it was found.
编写一个函数,其形参是一对迭代器和一个 int 型数值,实现在迭代器标记的范围内寻找该 int 型数值的功能,并返回一个 bool 结果,以指明是否找到指定数据。
Exercise 9.13:Rewrite the program that finds a value to return an iterator that refers to the element. Be sure your function works correctly if the element does not exist.
重写程序,查找元素的值,并返回指向找到的元素的迭代器。确保程序在要寻找的元素不存在时也能正确工作。
Exercise 9.14: Using iterators, write a program to read a sequence of strings from the standard input into a vector. Print the elements in the vector.
使用迭代器编写程序,从标准输入设备读入若干 string 对象,并将它们存储在一个 vector 对象中,然后输出该 vector 对象中的所有元素。
Exercise 9.15:Rewrite the program from the previous exercise to use a list. List the changes you needed to change the container type.
用 list 容器类型重写习题 9.14 得到的程序,列出改变了容器类型后要做的修改。




9.2.2. Some Container Operations Invalidate Iterators
9.2.2. 使迭代器失效的容器操作
In the sections that follow, we'll see that some container operations change the internal state of a container or cause the elements in the container to be moved. Such operations invalidate all iterators that refer to the elements that are moved and may invalidate other iterators as well. Using an invalidated iterator is undefined, and likely to lead to the same kinds of problems as using a dangling pointer.
在后面的几节里,我们将看到一些容器操作会修改容器的内在状态或移动容器内的元素。这样的操作使所有指向被移动的元素的迭代器失效,也可能同时使其他迭代器失效。使用无效迭代器是没有定义的,可能会导致与悬垂指针相同的问题。
For example, each container defines one or more erase functions. These functions remove elements from the container. Any iterator that refers to an element that is removed has an invalid value. After all, the iterator was positioned on an element that no longer exists within the container.
例如,每种容器都定义了一个或多个 erase 函数。这些函数提供了删除容器元素的功能。任何指向已删除元素的迭代器都具有无效值,毕竟,该迭代器指向了容器中不再存在的元素。
When writing programs that use iterators, we must be aware of which operations can invalidate the iterators. It is a serious run-time error to use an iterator that has been invalidated.
使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。使用无效迭代器将会导致严重的运行时错误。




There is no way to examine an iterator to determine whether it has been invalidated. There is no test we can perform to detect that it has gone bad. Any use of an invalidated iterator is likely to yield a run-time error, but there is no guarantee that the program will crash or otherwise make it easy to detect the problem.
无法检查迭代器是否有效,也无法通过测试来发现迭代器是否已经失效。任何无效迭代器的使用都可能导致运行时错误,但程序不一定会崩溃,否则检查这种错误也许会容易些。
 When using iterators, it is usually possible to write the program so that the range of code over which an iterator must stay valid is relatively short. It is important to examine each statement in this range to determine whether elements are added or removed and adjust iterator values accordingly.
使用迭代器时,通常可以编写程序使得要求迭代器有效的代码范围相对较短。然后,在该范围内,严格检查每一条语句,判断是否有元素添加或删除,从而相应地调整迭代器的值。


9.3. Sequence Container Operations
9.3. 每种顺序容器都提供了一组有用的类型定义以及以下操作:
Each sequential container defines a set of useful typedefs and supports operations that let us
每种顺序容器都提供了一组有用的类型定义以及以下操作:
Add elements to the container
在容器中添加元素。
Delete elements from the container
在容器中删除元素。
Determine the size of the container
设置容器大小。
Fetch the first and last elements from the container, if any
(如果有的话)获取容器内的第一个和最后一个元素。
9.3.1. Container Typedefs
9.3.1. 容器定义的类型别名
We've used three of the container-defined types: size_type, iterator, and const_iterator. Each container defines these types, along with several others shown in Table 9.5.
在前面的章节里,我们已经使用过三种由容器定义的类型:size_type、iterator 和 const_iterator。所有容器都提供这三种类型以及表 9.5 所列出的其他类型。
Table 9.5. Container-Defined Typedefs
表 9.5. 容器定义的类型别名
size_typeUnsigned integral type large enough to hold size of largest possible container of this container type
无符号整型,足以存储此容器类型的最大可能容器长度
iteratorType of the iterator for this container type
此容器类型的迭代器类型
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值