Team LiB
Previous Section Next Section

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 循环体虽然修改了 v1v2 的值,但这些变化仅限于局部参数,而对调用 gcd 函数使用的实参没有任何影响。于是,如果有函数调用

     gcd(i, j)

the values i and j are unaffected by the assignments performed inside gcd.

ij 的值不受 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 的实参。例如,可以传递两个 intconst 对象调用 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 节)对象。假设 ivecvector<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_sentencefind_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.

大部分带有省略符形参的函数都利用显式声明的参数中的一些信息,来获取函数调用中提供的其他可选实参的类型和数目。因此带有省略符的第一种形式的函数声明是最常用的。

Team LiB
Previous Section Next Section