Team LiB
Previous Section Next Section

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:

函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。匹配结果有三种可能:

  1. The compiler finds one function that is a best match for the actual arguments and generates code to call that function.

    编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。

  2. There is no function with parameters that match the arguments in the call, in which case the compiler indicates a compile-time error.

    找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。

  3. 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)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:

  1. The match for each argument is no worse than the match required by any other viable function.

    其每个实参的匹配都不劣于其他可行函数需要的匹配。

  2. 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.56double 型转换为 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:

为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:

  1. An exact match. The argument and parameter types are the same.

    精确匹配。实参与形参类型相同。

  2. Match through a promotion (Section 5.12.2, p. 180).

    通过类型提升实现的匹配(第 5.12.2 节)。

  3. Match through a standard conversion (Section 5.12.3, p. 181).

    通过标准转换实现的匹配(第 5.12.3 节)。

  4. 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);


Team LiB
Previous Section Next Section