Team LiB
Previous Section Next Section

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:

编写下面函数的原型:

  1. 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 类型的值。

  2. 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 提供实参,那么也必须给 heightwidth 提供实参:

     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.

注意第二个调用,只传递了一个字符值,虽然这是合法的,但是却并不是程序员的原意。因为 '?' 是一个 charchar 可提升为最左边形参的类型,所以这个调用是合法的。最左边的形参具有 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 时,编译器都会调用函数 screenDefaultbackground 获得一个值。

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”的单数和复数形式。


Team LiB
Previous Section Next Section