Chapter 2 advanced features of C ++ Functions
Compared with C functions, C ++ adds four new mechanisms: overloaded, inline, const, and virtual. The overload and inline mechanisms can be used for both global functions and class member functions. The const and virtual mechanisms are only used for class member functions.
Heavy Load and inline will certainly have their advantages to be adopted by the C ++ language, but it cannot be abused as a free lunch. This chapter will explore the advantages and limitations of heavy load and inline, and explain under what circumstances should be adopted, should not be used, and should be vigilant against mistakes.
8.1 concept of function overloading 8.1.1 origin of Overload
In natural language, a word can have many different meanings, that is, it is overloaded. People can use context to determine the meaning of a word. "Word overload" can make the language more concise. For example, "eat" has a wide range of meanings, and people do not have to clarify what to eat each time. Do not peat Kong as you have already said that there are four ways to write the Z word.
In C ++ProgramYou can use the same name to represent functions with similar semantics and functions. This makes it easier to remember and improves the ease of use of functions. This is one reason why C ++ uses the overload mechanism. For example, the functions eatbeef, eatfish, and eatchicken In the example 8-1-1 can be represented by the same function name eat and different types of parameters.
Void eatbeef (...); // You can change it to void eat (beef ...);
Void eatfish (...); // You can change it to void eat (fish ...);
Void eatchicken (...); // You can change it to void eat (Chicken ...);
Example 8-1-1 overload function eat
Another reason why C ++ uses the overload mechanism is that the class constructor needs to overload the mechanism. C ++ requires that the constructor and the class have the same name (see Chapter 9th). A constructor can have only one name. What should I do if I want to create an object using several different methods? There is no choice but to use the overload mechanism. Therefore, a class can have multiple constructors with the same name.
8.1.2 how is the overload implemented? Several duplicate functions with the same name are still different. How are they differentiated? We naturally think of two elements of a function interface: parameters and return values.
If the parameters of functions with the same name are different (including different types and order), they are different functions. If functions with the same name only have different return value types, they can be distinguished, but sometimes they cannot. Example: void function (void); int function (void );
The first function has no return value, and the second function has the int type. If you call a function like this: int x = function (); then you can determine that the function is the second function. The problem is that in C ++/C Programs, we can ignore the return value of the function. In this case, neither the compiler nor the programmer knows which function is called. Therefore, you can only distinguish between overloaded functions by parameters rather than different return value types. The compiler generates different internal identifiers for each overload function based on parameters. For example, the compiler generates internal identifiers such as _ eat_beef, _ eat_fish, and _ eat_chicken for the three eat functions in Example 8-1-1 (different compilers may generate internal identifiers of different styles ).
What if a C ++ program needs to call a compiled C function? Assume that the declaration of a C function is as follows: void Foo (int x, int y );
After the function is compiled by the C compiler, its name in the library is _ Foo, while the C ++ compiler generates names such as _ foo_int_int to support function overloading and secure type connection. C ++ programs cannot directly call C functions because their compiled names are different. C ++ provides a C connection to exchange the specified symbol extern "C" to solve this problem. For example:
Extern "C" {void Foo (int x, int y );... // Other functions}
Or write it as extern "C" {# include "myheader. h "... // Other C header file}. This tells C ++ to compile the interpreter. Function foo is a C connection. You should find the name _ Foo in the library instead of _ foo_int_int. The C ++ compiler developer has processed the header files of the C standard library as extern "C", so we can use # include to directly reference these header files.
Note that not two functions with the same name can constitute an overload. The same name of a global function and a class member function is not considered a heavy load because the function has different scopes. Example: void print (...); // Global function class {... Void print (...); // Member function} no matter whether the parameters of the two print functions are different, if a member function of the class calls the global function print, in order to distinguish it from the member function print, the ':' flag should be added when the global function is called. For example: Print (...); // Indicates that print is a global function rather than a member function.
8.1.3 In case of implicit type conversion that causes the overload function to generate ambiguity Example 8-3, the first output function parameter is of the int type, and the second output function parameter is of the float type. Because the number itself does not have a type, it is automatically converted (called implicit type conversion) when it is treated as a parameter ). Statement output (0.5) will generate a compilation error because the compiler does not know whether to convert 0.5 to an int or float type parameter. Implicit type conversion can simplify program writing in many places, but it may leave hidden risks. # Include Void output (int x); // function declaration void output (float X); // function declaration void output (int x) {cout <"output int" <x <Endl ;}
Void output (float X) {cout <"output float" <x <Endl ;}
Void main (void) {int x = 1; float y = 1.0; output (x); // output int 1 output (y ); // output float 1 output (1); // output int 1 // output (0.5); // error! Ambiguous call, because of automatic type conversion output (INT (0.5); // output int 0 output (float (0.5); // output float 0.5}
Example 8-1-3 implicit type conversion causes ambiguity of the overloaded function
8.2 It is easy to confuse overload, overwrite, and override of member functions with hiding member functions. c ++ programmers must clarify the concept; otherwise, errors will be hard to prevent.
8.2.1 reload and overload features of a member function: (1) Same range (in the same class); (2) Same function name; (3) Different parameters; (4) virtual keywords are optional.
Override refers to the function of a derived class that overrides a base class function. The features are as follows: (1) different ranges (respectively located in the derived class and the base class); (2) the function name is the same; (3) the parameter is the same; (4) basic functions must have virtual keywords. In Example 8-2-1, the base: F (INT) and base: F (float) functions are overloaded with each other, while the base: G (void) is derived: G (void) overwrite.
# Include Class base {public: void F (int x) {cout <"base: F (INT)" <x <Endl ;}
Void F (float X) {cout <"base: F (float)" <x <Endl;} virtual void g (void) {cout <"base:: G (void) "<Endl ;}};
Class derived: public base {public: Virtual void g (void) {cout <"derived: G (void)" <Endl ;}}; void main (void) {derived D; base * pb = & D; Pb-> F (42); // base: F (INT) 42 Pb-> F (3.14f ); // base: F (float) 3.14 Pb-> G (); // derived: G (void )}
Example 8-2-1 member function overload and overwrite
8.2.2 confusing hidden rules are not difficult to distinguish between overload and coverage, but the Hidden Rules of C ++ increase the complexity of the problem. Here "hide" means that the function of the derived class shields the base class function with the same name. The rule is as follows: (1) if the function of the derived class has the same name as the base class function, but the parameter is different. In this case, functions of the base class will be hidden regardless of whether there is any virtual keyword (Be sure not to confuse them with overload ). (2) If the function of the derived class has the same name and parameter as the function of the base class, but the base class function does not have the virtual keyword. In this case, the function of the base class is hidden (do not confuse with overwrite ). In Example Program 8-2-2 (a): (1) the function derived: F (float) overwrites base: F (float ). (2) The derived: G (INT) function hides the base: G (float) instead of the overload. (3) The derived: H (float) function hides base: H (float) instead of overwrite. # Include Class base {public: Virtual void F (float X) {cout <"base: F (float)" <x <Endl;} void g (float X) {cout <"base: G (float)" <x <Endl;} void H (float X) {cout <"base: H (float) "<x <Endl ;}};
Class derived: public base {public: Virtual void F (float X) {cout <"derived: F (float)" <x <Endl ;} void g (int x) {cout <"derived: G (INT)" <x <Endl;} void H (float X) {cout <"derived:: H (float) "<x <Endl ;}};
For example 8-2-2 (a), the reload, overwrite, and hide of member functions are investigated by the author. Many C ++ programmers are not aware of the "hidden" issue. Due to lack of deep understanding, the occurrence of "hiding" is a real failure and often produces confusing results. In Example 8-2-2 (B), BP and DP point to the same address. The running result should be the same, but this is not the case.
Void main (void) {derived D; base * pb = & D; derived * Pd = & D; // good: behavior depends solely on type of the object Pb-> F (3.14f); // derived: F (float) 3.14 Pd-> F (3.14f); // derived :: F (float) 3.14
// Bad: behavior depends on type of the pointer
Pb-> G (3.14f); // base: G (float) 3.14
Pd-> G (3.14f); // derived: G (INT) 3 (surprise !)
// Bad: behavior depends on type of the pointer Pb-> H (3.14f); // base: H (float) 3.14 (surprise !) Pd-> H (3.14f); // derived: H (float) 3.14}
Example 8-2-2 (B) Comparison of overloading, overwriting, and hiding
8.2.3 getting rid of hidden rules causes a lot of trouble. In the example 8-2-3 program, the intention of the statement Pd-> F (10) is to call the function base: F (INT), but the base: F (INT) is unfortunately derived :: F (char *) is hidden. The number 10 cannot be implicitly converted into a string, so an error occurs during compilation. Class base {public: void F (int x) ;}; class derived: public base {public: void F (char * Str) ;}; void test (void) {derived * Pd = new derived;
Pd-> F (10); // error}
Example 8-2-3 error caused by hiding
From the Example 8-2-3, it seems silly to hide rules. But there are at least two reasons for hiding rules: the person who writes the statement Pd-> F (10) may really want to call the derived: F (char *) function, but he mistakenly wrote the parameter. With the hidden rules, the compiler can clearly point out errors. This is not necessarily a good thing. Otherwise, the compiler will quietly correct the error, and it will be difficult for programmers to find this error and cause a curse.
* If the class derived has multiple base classes (multiple inheritance), it is sometimes unclear which base classes define function f. If no rule is hidden, Pd-> F (10) may call an unexpected base class function f. Although hiding rules does not seem reasonable, they can indeed eliminate these accidents.
In Example 8-2-3, if the statement Pd-> F (10) must call the function base: F (INT), modify the class derived to the following.
Class derived: public Base
{Public: void F (char * Str); void F (int x) {base: F (x );}};
8.3 The default values of some parameters have the same values in each function call. Writing such statements will get bored. C ++ uses the default parameter values to make writing concise (during compilation, the default value is automatically inserted by the compiler ).
Rules for using the default value of a parameter: [rules 8-3-1] The default value of a parameter can only appear in the declaration of a function, but not in the definition body.
For example: void Foo (INT x = 0, int y = 0); // correct. The default value appears in the function declaration.
Void Foo (INT x = 0, int y = 0) // error. The default value appears in the definition body of the function {... } Why? I think there are two reasons: first, the implementation (Definition) of the function has nothing to do with whether the parameter has a default value, so it is not necessary to make the default value appear in the definition body of the function. Second, the default value of the parameter may be changed. Obviously, modifying the declaration of the function is easier than modifying the definition of the function.
[Rule 8-3-2] If a function has multiple parameters, the parameter can only be taken from the back to the front by default. Otherwise, the function call statement is strange. Correct example: void Foo (int x, int y = 0, int z = 0); incorrect example: void Foo (INT x = 0, int y, int z = 0 );
Note that using the default values of parameters does not assign new functions to the function, but only simplifies the writing. It may improve the ease of use of functions, but may also reduce the comprehensibility of functions. Therefore, we can only use the default values of parameters as appropriate, So we should avoid negative effects caused by improper use. In Example 8-3-2, unreasonable use of the parameter's default value will lead to the ambiguity of the output function of the heavy load function.
# Include Void output (int x); void output (int x, float y = 0.0); void output (int x) {cout <"output int" <x <Endl;} void output (int x, float y) {cout <"output int" <x <"and float" <Y <Endl;} void main (void) {int x = 1; float y = 0.5; // output (x); // error! Ambiuous call output (x, y); // output int 1 and float 0.5}
In Example 8-3-2, the default value of the parameter will cause the overload function to generate ambiguity 8.4 operator overload 8.4.1. In C ++ language, operators can be added with the keyword operator to represent the function, this is called operator overload. For example, two complex addition functions: complex add (const complex & A, const complex & B); can be expressed by operator overloading: complex operator + (const complex &, const complex & B); the difference between operators and common functions is: for common functions, parameters appear in parentheses; for operators, the parameter is displayed on the left and right sides. For example, complex A, B, C ;... C = add (a, B); // use the common function c = a + B; // use the operator + if the operator is overloaded as a global function, only one parameter operator is called The unary operator, and two parameter operators are called binary operators. If the operator is overloaded as a member function of the class, the unary operator has no parameters. The binary operator has only one parameter on the right, because the object itself is a parameter on the left.
In terms of syntax, operators can be defined as both global functions and member functions. The article [Murray, p44-p47] has made a lot of elaboration on this problem, and summarized the rules of table 8-4-1.
We recommend that you reload all unary operators in the operator rules as member functions = () []-> only member functions can be reloaded as Function + =-=/= * = & = │ = ~ ==>>=<<= It is recommended that you reload all the other operators of the member function as the reload rule of the global function table 8-4-1 operator.