Team LiB
Previous Section Next Section

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:

替代这种蛮力型计算的方法包括两部分内容:

  1. Use named objects to perform and print each computation.

    使用已命名对象执行运算并输出每次计算。

  2. 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.

valuepowresultcnt 都是变量,可以对数值进行存储、修改和查询。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++ 的两种表达式:

  1. 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):左值可以出现在赋值语句的左边或右边。

  2. 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++ 关键字

asm

do

if

return

try

auto

double

inline

short

typedef

bool

dynamic_cast

int

signed

typeid

break

else

long

sizeof

typename

case

enum

mutable

static

union

catch

explicit

namespace

static_cast

unsigned

char

export

new

struct

using

class

extern

operator

switch

virtual

const

false

private

template

void

const_cast

float

protected

this

volatile

continue

for

public

throw

wchar_t

default

friend

register

true

while

delete

goto

reinterpret_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++ 操作符替代名

and

bitand

compl

not_eq

or_eq

xor_eq

and_eq

bitor

not

or

xor

 

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,而不写成 IndexINDEX

  • 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_loansalary

  • 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_loanstudentLoan,而不写成 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.

每个定义都是以类型说明符开始,后面紧跟着以逗号分开的含有一个或多个说明符的列表。分号结束定义。类型说明符指定与对象相关联的类型:intdoublestd::stringSales_item 都是类型名。其中 intdouble 是内置类型,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 类型的值。将变量 pricediscount 传递给函数,并且用它的返回值来初始化 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 的函数,以及两个名为 sumval 的变量。名字 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 类型的全局变量 s1string 类型的局部变量 s2int 类型的局部变量 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;

Team LiB
Previous Section Next Section