6-Functions

来源:互联网 发布:网络运营总监培训 编辑:程序博客网 时间:2024/06/02 04:33

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

  • A function is a block of code with a name. We execute the code by calling the function. A function take zero or more arguments and yields a result. Functions can be overloaded, meaning that the same name may refer to several different functions.

6.1. Function Basics

  • A function definition consists of a return type, a name, a list of zero or more parameters, and a body.
  • The parameters are specified in a comma-separated list enclosed in parentheses.
  • The actions that the function performs are specified in a statement block(§5.1, p.173), referred to as the function body.
  • We execute a function through the call operator, which is a pair of parentheses. The call operator takes an expression that is a function or points to a function. Inside the parentheses is a comma-separated list of arguments. The arguments are used to initialize the function’s parameters. The type of a call expression is the return type of the function.

Calling a Function

  • A function call does two things: It initializes the function’s parameters from the corresponding arguments, and it transfers control to that function. Execution of the calling function is suspended and execution of the called function begins.
  • Execution of a function begins with the definition and initialization of its parameters; ends when a return statement is encountered.
  • The return statement does two things: It returns the value(if any) in the return, and it transfers control out of the called function back to the calling function. The value returned by the function is used to initialize the result of the call expression. Execution continues with whatever remains of the expression in which the call appeared.

Parameters and Arguments

  • Arguments are the initializers for a function’s parameters.
  • We have no guarantees about the order in which arguments are evaluated. The compiler is free to evaluate the arguments in whatever order it prefers.
  • The type of each argument must match the corresponding parameter in the same way that the type of any initializer must match the type of the object it initializes. We must pass exactly the same number of arguments as the function has parameters.

Function Parameter List

  • A function’s parameter list can be empty but cannot be omitted. We define a function with no parameters by writing an empty parameter list or use keyword void to indicate that there are no parameters.
  • A parameter list consists of a comma-separated list of parameters, each of which looks like a declaration with a single declarator. Even when the types of two parameters are the same, the type must be repeated.
  • Parameter names are optional. No two parameters can have the same name. Local variables at the outermost scope of the function may not use the same name as any parameter.
  • Occasionally a function has a parameter that is not used. Such parameters are often left unnamed, to indicate that they aren’t used. Leaving a parameter unnamed doesn’t change the number of arguments that a call must supply.

Function Return Type

  • The return type can be void, cannot be an array type or a function type. But a function may return a pointer to an array or a function.

Exercises Section 6.1

Exercise 6.1

What is the difference between a parameter and an argument?

  • Parameters: Local variable declared inside the function parameter list. They are initialized by the arguments provided in the each function call.
  • Arguments: Values supplied in a function call that are used to initialize the function’s parameters.

Exercise 6.2

Indicate which of the following functions are in error and why. Suggest how you might correct the problems.
(a)
int f()
{
string s;
// …
return s;
}
(b) f2(int i) { /* … */ }
(c) int calc(int v1, int v1) {/* … */ }
(d) double square(double x) return x * x;

  • Return type don’t match. string f()
  • Omit return type. Type f2(int i)
  • Duplicate parameter name. int calc(int v1, int v2) {/* ... */ }
  • Right

Exercise 6.3

Write and test your own version of fact.

#include <iostream>using std::cin;using std::cout;int fact(int val){    int result = 0;    if(val < 0)    {        cout << "Input error! Try Again.\n";    }    else if(val == 0 || val == 1)    {        result = 1;    }    else    {        result = val * fact(val - 1);    }    return result;}int main(){    for(int num = 0; cin >> num; )    {        cout << fact(num) << '\n';    }    return 0;}

Exercise 6.4

Write a function that interacts with the user, asking for a number and generating the factorial of that number. Call this function from main.

  • Same as 6.3

Exercise 6.5

Write a function to return the absolute value of its argument.

template<typename T>T Abs(T obj){    return(obj >= 0 ? obj : -obj);}

6.1.1. Local Objects

  • Names have scope(§ 2.2.4, p. 48) and objects have lifetimes.
    • The scope of a name is the part of the program’s text in which that name is visible.
    • The lifetime of an object is the time during the program’s execution that the object exists.
  • Parameters and variables defined inside a function body are referred to as local variables. They are local to that function and hide declarations of the same name made in an outer scope.
  • Objects defined outside any function exist throughout the program’s execution. Such objects are created when the program starts and are not destroyed until the program ends.

Automatic Objects

  • The objects that correspond to ordinary local variables are created when the function’s control path passes through the variable’s definition. They are destroyed when control passes through the end of the block in which the variable is defined. Objects that exist only while a block is executing are known as automatic objects. After execution exits a block, the values of the automatic objects created in that block are undefined.
  • Automatic objects corresponding to the function’s parameters are initialized by the arguments passed to the function. Automatic objects corresponding to local variables are initialized if their definition contains an initializer. Otherwise, they are default initialized(§ 2.2.1, p. 43), which means that uninitialized local variables of built-in type have undefined values.

Local static Objects

  • We obtain a local variables whose lifetime continues across calls to the function by defining it as static. Each local static object is initialized before the first time execution passes through the object’s definition. Local statics are not destroyed when a function ends; they are destroyed when the program terminates.
#include <iostream>using std::cout;int Fun(){    static int cnt;  // Equivalent to `static int cnt = 0;`    return ++cnt;}int main(){    for(int num = 0; num < 10; ++num)    {        cout << Fun() << ' ';    }    return 0;}
  • Before control flows through the definition of cnt for the first time, cnt is created and given an initial value of 0. Each call increments cnt and returns its new value.
  • If a local static has no explicit initializer, it is value initialized(§ 3.3.1, p. 98), meaning that local statics of built-in type are initialized to zero.

Exercises Section 6.1.1

Exercise 6.6

Explain the differences between a parameter, a local variable, and a local static variable. Give an example of a function in which each might be useful.

  • local variable: Variables defined inside a block;
  • parameter: Local variables declared inside the function parameter list
  • local static variable: local static variable(object) is initialized before the first time execution passes through the object’s definition.Local statics are not destroyed when a function ends; they are destroyed when the program terminates.
size_t count_add(int n)// n is a parameter.{    static size_t ctr = 0;// ctr is a static variable.    ctr += n;    return ctr;}int main(){    for(size_t i = 0; i != 10; ++i)// i is a local variable.    {        cout << count_add(i) << endl;    }    return 0;}

Exercise 6.7

Write a function that returns 0 when it is first called and then generates numbers in sequence each time it is called again.

size_t generate(){    static size_t ctr = 0;    return ctr++;}

6.1.2. Function Declarations

  • A function may be defined only once but may be declared multiple times. The name of a function must be declared before we can use it. With one exception in §15.3(p.603), we can declare a function that is not defined so long as we never use that function.
  • A function declaration is just like a function definition except that a declaration has no function body, replaced by a semicolon. And there is no need for parameter names.
  • Function declarations are known as the function prototype.

Function Declarations Go in Header Files

  • Variables and functions are declared in header files(§ 2.6.3, p. 76) and defined in source files.
  • Put a function declaration directly in each source file is tedious and error-prone. When we use header files for our function declarations, we can ensure that all the declarations for a given function agree. If the interface to the function changes, only one declaration has to be changed.
  • The source file that defines a function should include the header that contains that function’s declaration. The compiler will verify that the definition and declaration are consistent.

Exercises Section 6.1.2

Exercise 6.8

Write a header file named Chapter6.h that contains declarations for the functions you wrote for the exercises in § 6.1(p. 205).

int fact(int val);int func();template <typename T>T Abs(T obj){    return obj >= 0 ? obj : -obj;}

6.1.3. Separate Compilation

Compiling and Linking Multiple Source Files

  • Assume that the definition of our fact function is in a file named fact.cc and its declaration is in a header file named Chapter6.h. Our fact.cc file will include the Chapter6.h header. We store a main function that calls fact in a second file named factMain.cc. To produce an executable file , we must tell the compiler where to find all of the code we use. We might compile these files as follows:
$ g++ factMain.cc fact.cc          # generates a.out$ g++ factMain.cc fact.cc -o main  # generates main
  • $ is our system prompt, # begins a command-line comment.
  • If we have changed only one of our source files, we’d like to recompile only the file that actually changed. This process yields a file with the .o file extension, indicating that the file contains object code. The compiler lets us link object files together to form an executable.
$ g++ -c factMain.cc               # generates factMain.o$ g++ -c fact.cc                   # generates fact.o$ g++ factMain.o fact.o            # generates a.out$ g++ factMain.o fact.o -o main    # generates main

Exercises Section 6.1.3

Exercise 6.9

Write your own versions of the fact.cc and factMain.cc files. These files should include your Chapter6.h from the exercises in the previous section. Use these files to understand how your compiler supports separate compilation.

// fact.cc#include "Chapter6.h"int fact(int val){    if(val == 0 || val == 1)    {        return 1;    }    else    {        return val * fact(val - 1);    }}
// factMain.cc#include <iostream>#include "Chapter6.h"using std::cout;int main(){    cout << "5! = " << fact(5) << '\n';    cout << Abs(-7188) << '\n';}
$ g++ fact.cc factMain.cc -o main$ ./main 5! = 1207188

6.2. Argument Passing

  • When a parameter is a reference: its corresponding argument is “passed by reference” or the function is “called by reference.” The parameter is an alias for its corresponding argument.
  • When the argument value is copied: the parameter and argument are independent objects. Such arguments are “passed by value” or the function is “called by value.”

6.2.1. Passing Arguments by Value

  • When we initialize a nonreference type variable, the value of the initializer is copied. Changes made to the variable have no effect on the initializer.

Pointer Parameters

  • When we copy a pointer, the value of the pointer is copied. Though the two pointers are distinct, we can change the value of that object by assigning through the pointer.
#include <iostream>using std::cout;void Double(int *ptr){    *ptr *= 2;}int main(){    int num = 10;    int *ptr = &num;    Double(ptr);    cout << num;  // 20    return 0;}

Exercises Section 6.2.1

Exercise 6.10

Using pointers, write a function to swap the values of two ints. Test the function by calling it and printing the swapped values.

#include <iostream>using std::cout;void Swap(int *ptr1, int *ptr2){    int temp = *ptr1;    *ptr1 = *ptr2;    *ptr2 = temp;}int main(){    int num1 = 10, num2 = 20;    int *ptr1 = &num1, *ptr2 = &num2;    Swap(ptr1, ptr2);    cout << num1 << '\t' << num2;  // 20 10    return 0;}

6.2.2. Passing Arguments by Reference

  • A reference parameter is bound directly to the object from which it is initialized. Reference parameters allow a function to change the value of one or more of its arguments.

Using References to Avoid Copies

  • It can be inefficient to copy objects, and some class types(IO types…) cannot be copied. Functions must use reference parameters to operate on objects of a type that cannot be copied.
  • Functions should use references to const for reference parameters they do not need to change.

Using Reference Parameters to Return Additional Information

  • Reference parameters return multiple results.

Exercises Section 6.2.2

Exercise 6.11

Write and test your own version of reset that takes a reference.

#include <iostream>using std::cout;void reset(int &num){    num = 0;}int main(){    int num = 42;    reset(num);    cout << num;    return 0;}

Exercise 6.12

Rewrite the program from exercise 6.10 in § 6.2.1(p. 210) to use references instead of pointers to swap the value of two ints. Which version do you think would be easier to use and why?

#include <iostream>using std::cout;void Swap(int &ref1, int &ref2){    int temp = ref1;    ref1 = ref2;    ref2 = temp;}int main(){    int num1 = 10, num2 = 20;    int &ref1 = num1, &ref2 = num2;    Swap(ref1, ref2);    cout << num1 << '\t' << num2;  // 20 10    return 0;}

Exercise 6.13

Assuming T is the name of a type, explain the difference between a function declared as void f(T) and void f(T&).

  • void f(T) pass arguments by value.
  • void f(T&) pass arguments by references.

Exercise 6.14

Give an example of when a parameter should be a reference type. Give an example of when a parameter should not be a reference.

  • Should: we need change it or it is large.
  • Should not:
void print(std::vector<int>::iterator begin, std::vector<int>::iterator end){    for(std::vector<int>::iterator iter = begin; iter != end; ++iter)    {        std::cout << *iter << std::endl;    }}

Exercise 6.15

Explain the rationale for the type of each of find_char’s parameters In particular, why is s a reference to const but occurs is a plain reference? Why are these parameters references, but the char parameter c is not? What would happen if we made s a plain reference? What if we made occurs a reference to const?

  • Why is s a reference to const but occurs is a plain reference?
    cause the s should not be changed by this function. but occurs’ result must be calculated by the function.
  • Why are these parameters references, but the char parameter c is not?
    cause c maybe a temp variable. such as find_char(s, ‘a’, occurs)
  • What would happen if we made s a plain reference? What if we made occurs a reference to const?
    s could be changed in the function, and occurs would not be changed. so occurs = 0; is an error.

6.2.3. const Parameters and Arguments

  • §2.4.3(p.63): A top-level const is one that applies to the object itself.
  • When we copy an argument to initialize a parameter, top-level consts are ignored. Top-level const on parameters are ignored. We can pass either a const or a nonconst object to a parameter that has a top-level const.
  • We can define several functions that have the same name only if their parameter lists are sufficiently different. Because top-level consts are ignored, we can’t overload functions only differ from whether parameters are const int.
#include <iostream>using std::cout;void Fun1(int num){    cout << "Fun1: " << num << '\n';}void Fun2(const int num){    cout << "Fun2: " << num << '\n';}/*void Fun2(int num)  // error: redefinition of ‘void Fun2(int)’{    cout << "Fun2: " << num << '\n';}*/int main(){    const int num1 = 1;    Fun1(num1);  // Top-level const of num1 is ignored.    int num2 = 2;    Fun2(num1);    Fun2(num2);    return 0;}/*Output:Fun1: 1Fun2: 1Fun2: 2*/

Pointer or Reference Parameters and const

  • We can initialize an object with a low-level const from a nonconst object but not vice versa, and a plain reference must be initialized from an object of the same type.
  • Use reference to const when possible.
void Fun1(int *){}void Fun2(int &){}int main(){    int num1 = 0;    const int num2 = 0;    Fun1(&num1);    // ok    // error: invalid conversion from ‘const int*’ to ‘int*’    Fun1(&num2);    Fun2(num1); // ok    //error: invalid initialization of reference of type ‘int&’ from    //expression of type ‘const int’    Fun2(num2);}

Exercises Section 6.2.3

Exercise 6.16

The following function, although legal, is less useful than it might be. Identify and correct the limitation on this function:
bool is_empty(string& s) { return s.empty(); }

bool is_empty(const string &s){    return s.empty();}

Exercise 6.17

Write a function to determine whether a string contains any capital letters. Write a function to change a string to all lowercase. Do the parameters you used in these functions have the same type? If so, why? If not, why not?

#include <iostream>#include <string>using std::cout;using std::string;// Whether a string contains any capital lettersbool HasCapitalLetter(const string &str){    for(char ch : str)    {        if('A' <= ch && ch <= 'Z')        {            return true;        }    }    return false;}// Change a string to all lowercasevoid ToAllLowercase(string &str){    for(char &ch : str)    {        ch =(('A' <= ch && ch <= 'Z') ? ch +('a' - 'A') : ch);    }}int main(){    string str1 = "Gaoxiangnumber1";    string str2 = "gaoxiangnumber1";    cout << HasCapitalLetter(str1) << '\t' << HasCapitalLetter(str2) << '\t' <<         HasCapitalLetter("gaoxiangNumber1") << '\n';  // 1 0 1    ToAllLowercase(str1);    cout << str1;  // gaoxiangnumber1    return 0;}/*1   0   1gaoxiangnumber1*/

Exercise 6.18

Write declarations for each of the following functions. When you write these declarations, use the name of the function to indicate what the function does.
(a) A function named compare that returns a bool and has two parameters that are references to a class named matrix.
(b) A function named change_val that returns a vector iterator and takes two parameters: One is an int and the other is an iterator for a vector.

  • bool compare(const matrix &obj1, const matrix &obj2);
  • vector::iterator change_val(int num, vector::iterator &it);

Exercise 6.19

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::iterator, vector::iterator, int);
vector vec(10);
(a) calc(23.4, 55.1);
(b) count(“abcda”, ‘a’);
(c) calc(66);
(d) sum(vec.begin(), vec.end(), 3.8);

  • Illegal: too many arguments
  • Legal
  • Legal
  • Legal

Exercise 6.20

When should reference parameters be references to const? What happens if we make a parameter a plain reference when it could be a reference to const?

  • When we don’t change parameters’ value.
  • Compile error.

6.2.4. Array Parameters

  • When we pass an array to a function, that argument is automatically converted to a pointer to the first element in the array; the size of the array is irrelevant.
void Fun(int [10]){}void Fun(int *){}   //error: redefinition of ‘void Fun(int*)’void Fun(int []){}  //error: redefinition of ‘void Fun(int*)’int main(){    int arr[5];    Fun(arr);       //ok    int num = 0;    Fun(num);       //error: invalid conversion from ‘int’ to ‘int*’    Fun(&num);      //ok    return 0;}
  • Because arrays are passed as pointers, functions don’t know the size of the array they are given. There are three common techniques used to manage pointer parameters.
    1.Using a Marker to Specify the Extent of an Array: Make the array itself to contain an end marker. E.g.: Functions that deal with C-style strings stop processing the array when they see a null character. This convention works well for data where there is an obvious end-marker value that does not appear in ordinary data.
    2.Using the Standard Library Conventions: Pass pointers to the first and one past the last element in the array.
    3.Explicitly Passing a Size Parameter: Define a second parameter that indicates the size of the array.

Array Parameters and const

  • When a function does not need write access to the array elements, the array parameter should be a pointer to const(§ 2.4.2, p. 62). A parameter should be a plain pointer to a nonconst type only if the function needs to change element values.

Array Reference Parameters

  • We can define a parameter that is a reference to an array. The parentheses around &arr are necessary(§ 3.5.1, p. 114)
void Fun(int(&arr)[10]){}   // arr is a reference to an array of ten intsvoid Fun(int& arr[10]){}        // error: declaration of ‘arr’ as array of referencesint main(){    int num = 0;    int arr1[10], arr2[5];    // error: invalid initialization of reference ‘int(&)[10]’ from an rvalue of type ‘int*’    Fun(&num);    Fun(arr1);    // error: invalid initialization of reference ‘int(&)[10]’ from type ‘int [5]’    Fun(arr2);    return 0;}

Passing a Multidimensional Array

  • A multidimensional array is passed as a pointer to its first element(§3.6, p.128). Because we are dealing with an array of arrays, that element is an array, so the pointer is a pointer to an array. The size of the second and any subsequent dimension is part of the element type and must be specified.
void Fun(int(*arr)[10]){}   // pointer to an array of ten intsvoid Fun(int *arr[10]){}    // array of ten pointersvoid Fun(int arr[][10]){}   // error: redefinition of ‘void Fun(int(*)[10])’int arr1[1][10], arr2[5][10], arr3[10], arr4[10][1];Fun(arr1);  //okFun(arr2);  //okFun(arr3);  // error: no matching function for call to ‘Fun(int [10])’Fun(arr4);  // error: no matching function for call to ‘Fun(int [10][1])’

Exercises Section 6.2.4

Exercise 6.21

Write a function that takes an int and a pointer to an int and returns the larger of the int value or the value to which the pointer points. What type should you use for the pointer?

int Fun(const int num, const int *ptr){    return((num > *ptr) ? num : *ptr);}

Exercise 6.22

Write a function to swap two int pointers.

#include <iostream>using std::cout;void Fun(int* &ptr1, int* &ptr2){    int *tmp = ptr1;    ptr1 = ptr2;    ptr2 = tmp;}int main(){    int num1 = 1, num2 = 2, *ptr1 = &num1, *ptr2 = &num2;    cout << ptr1 << '\t' << *ptr1 << '\t' << ptr2 << '\t' << *ptr2 << '\n';    Fun(ptr1, ptr2);    cout << ptr1 << '\t' << *ptr1 << '\t' << ptr2 << '\t' << *ptr2 << '\n';    return 0;}/*0x7ffce66860d8  1   0x7ffce66860dc  20x7ffce66860dc  2   0x7ffce66860d8  1*/

Exercise 6.23

Write your own versions of each of the print functions presented in this section. Call each of these functions to print i and j defined as follows:
int i = 0, j[2] = {0, 1};

#include <iostream>using std::cout;using std::endl;using std::begin;using std::end;void print(const int *pi){    if(pi)    {        cout << *pi << endl;    }}void print(const char *p){    if(p)    {        while(*p)        {            cout << *p++;        }    }    cout << endl;}void print(const int *beg, const int *end){    while(beg != end)    {        cout << *beg++ << endl;    }}void print(const int ia[], size_t size){    for(size_t i = 0; i != size; ++i)    {        cout << ia[i] << endl;    }}void print(int(&arr)[2]){    for(auto i : arr)    {        cout << i << endl;    }}int main(){    int i = 0, j[2] = { 0, 1 };    print(begin(j), end(j));    print(&i);    print(j, end(j)-begin(j));    print(j);    return 0;}

Exercise 6.24

Explain the behavior of the following function. If there are problems in the code, explain what they are and how you might fix them.

void print(const int ia[10]){    for(size_t i = 0; i != 10; ++i)        cout << ia[i] << endl;}
  • The size of array is may not be 10. We should provide explicitly size argument.

6.2.5. main:Handling Command-Line Options

int main(int argc, char *argv[]){ ... }int main(int argc, char **argv) { ... }  // Equivalent
  • argc passes the number of strings in array argv. argv is an array of pointers to C-style character strings.
  • When arguments are passed to main, argv[0] contains the program’s name, not input. Subsequent elements pass the arguments provided on the command line. The element just past the last pointer is guaranteed to be 0.

Exercises Section 6.2.5

Exercise 6.25

Write a main function that takes two arguments. Concatenate the supplied arguments and print the resulting string.

#include <cstdio>int main(int argc, char **argv){    for(int index = 0; index < argc; ++index)    {        printf("argv[%d]: %s\n", index, argv[index]);    }    return 0;}

Exercise 6.26

Write a program that accepts the options presented in this section. Print the values of the arguments passed to main.

  • Same as 6.25.

6.2.6. Functions with Varying Parameters

  • C++11 provides two ways to write a function that takes a varying number of arguments. If the argument types:
    1. All same: we can pass a library type named initializer_list.
    2. Not all same: we can write a variadic template(§ 16.4(p. 699)).
  • C++ also has a parameter type, ellipsis, that can be used to pass a varying number of arguments. This facility should be used only in programs that need to interface to C functions.

initializer_list Parameters

  • We can write a function that takes an unknown number of arguments of a single type by using an initializer_list parameter. An initializer_list is a library type that represents an array(§ 3.5, p. 113) of values of the specified type. This type is defined in the initializer_list header. The operations that initializer_list provides are listed in Table 6.1.

  • initializer_list is a template type(§3.3, p.96). When we define an initializer_list, we must specify the type of the elements that the list will contain.
  • The elements in an initializer_list are always const values; there is no way to change the value of an element in an initializer_list.
  • When we pass a sequence of values to an initializer_list parameter, we must enclose the sequence in curly braces.
  • A function with an initializer_list parameter can have other parameters.
  • Because initializer_list has begin and end members, we can use a range for(§5.4.3) to process the elements.
#include <iostream>#include <string>using std::cout;using std::string;using std::initializer_list;void Fun(string str, initializer_list<int> ilist){    cout << str << ": ";    for(initializer_list<int>::iterator beg = ilist.begin(); beg != ilist.end(); ++beg)    {        cout << *beg << ' ';    }    cout << '\n';}int main(){    Fun("gao", {1, 2, 3, 4, 5});    Fun("xiang", {});    Fun("number1", {5, 4, 3, 2, 1});    return 0;}/*Output:gao: 1 2 3 4 5xiang:number1: 5 4 3 2 1*/

Ellipsis Parameters

  • Ellipsis parameters allow programs to interface to C code that uses a C library facility named varargs. Generally an ellipsis parameter should not be used for other purposes.
  • Ellipsis parameters should be used only for types that are common to both C and C++. Objects of most class types are not copied properly when passed to an ellipsis parameter.
  • An ellipsis parameter may appear only as the last element in a parameter list and may take either of two forms.
void foo(parameter_list, ...);void foo(...);
  • The first form specifies the type(s) for some of foo ’s parameters. Arguments that correspond to the specified parameters are type checked as usual. No type checking is done for the arguments that correspond to the ellipsis parameter. In first form, the comma following the parameter declarations is optional.

Exercises Section 6.2.6

Exercise 6.27

Write a function that takes an initializer_list and produces the sum of the elements in the list.

#include <iostream>using std::cout;using std::initializer_list;void Fun(initializer_list<int> ilist){    int sum = 0;    for(initializer_list<int>::iterator beg = ilist.begin(); beg != ilist.end(); ++beg)    {        sum += *beg;    }    cout << sum << '\n';}int main(){    Fun({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});    return 0;}/*Output:55*/

Exercise 6.28

In the second version of error_msg that has an ErrCode parameter, what is the type of elem in the for loop?

  • const std::string&

Exercise 6.29

When you use an initializer_list in a range for would you ever use a reference as the loop control variable? If so, why? If not, why not?

  • Depends on the type of elements of initializer_list.
    1. When the type is POD-Type, reference is unnecessary. Because POD is cheap to copy(such as int).
    2. Otherwise, Using reference( const) is the better choice.

6.3. Return Types and the return Statement

  • A return statement terminates the function that is currently executing and returns control to the point from which the function was called. There are two forms of return statements:
return;return expression;

6.3.1. Functions with No Return Value

  • A return with no value may be used only in a function that has a return type of void. Functions that return void are not required to contain a return.
  • A function with a void return type may use the second form of the return statement only to return the result of calling another function that returns void. Returning any other expression from a void function is a compile-time error.

6.3.2. Functions That Return a Value

  • Every return in a function with a return type other than void must return a value. The value returned must have the same type as the function return type, or it must have a type that can be implicitly converted(§ 4.11, p. 159) to that type.
bool str_sub_range(const string &str1, const string &str2){    auto size =(str1.size() < str2.size()) ? str1.size() : str2.size();    for(decltype(size) i = 0; i != size; ++i)    {        if(str1[i] != str2[i])            return;// error 1: no return value; compiler should detect this error    }    // error 2: control might flow off the end of the function without a return    // the compiler might not detect this error}
  • Second error: The function fails to provide a return after the loop, execution would fall out of the for. There should be a return to handle this case. The compiler may or may not detect this error. If it does not detect the error, what happens at run time is undefined.

How Values Are Returned

  • The return value is used to initialize a temporary at the call site, and that temporary is the result of the function call.
  • When a function returns a reference, that reference is just another name for the object to which it refers.

Never Return a Reference or Pointer to a Local Object

  • After a function terminates, references/pointers to local objects refer to memory that is no longer valid.

Functions That Return Class Types and the Call Operator

  • The call operator[()] has the same precedence as the dot(.) and arrow(->) operators(§4.6) and it is left associative. If a function returns a pointer, reference or object of class type, we can use the result of a call to call a member of the resulting object.
string Fun(string, string) {}auto size = Fun(s1, s2).size();
  • Because these operators are left associative, the result of Fun is the left-hand operand of the dot operator. That operator fetches the size member of that string.

Reference Returns Are Lvalues

  • Calls to functions that return references are lvalues; other return types yield rvalues.
  • A call to a function that returns a reference can be used in the same ways as any other lvalue. We can assign to the result of a function that returns a reference to nonconst.
#include <iostream>#include <string>using std::cout;using std::string;char &GetChar(string &str, int index){    return str[index];}int main(){    string str = "gaoxiangnumber1";    cout << str << '\n';    GetChar(str, 0) = 'A';    cout << str << '\n';    return 0;}/*Output:gaoxiangnumber1Aaoxiangnumber1*/

List Initializing the Return Value

  • C++11: Functions can return a braced list of values that is used to initialize the temporary that represents the function’s return. If the list is empty, that temporary is value initialized(§ 3.3.1, p. 98). Otherwise, the value of the return depends on the function’s return type.
#include <iostream>#include <vector>using std::cout;using std::vector;void Print(vector<int> &vec){    if(vec.empty())    {        cout << "Empty\n";        return;    }    for(auto it : vec)    {        cout << it << ' ';    }    cout << '\n';}vector<int> Fun(int flag){    switch(flag)    {    case 1:        return {};    case 2:        return {1, 2};    case 3:        return {1, 2, 3};    default:        return {0, -1};    }}int main(){    for(int flag = 0; flag < 5; ++flag)    {        vector<int> vec = Fun(flag);        Print(vec);    }    return 0;}/*Output:0 -1Empty1 21 2 30 -1*/
  • If function returns a built-in type, a braced list may contain at most one value, and that value must not require a narrowing conversion(§ 2.2.1, p. 43). If the function returns a class type, then the class itself defines how the initializers are used(§3.3.1).

Return from main

  • Exception to the rule that a function with a return type other than void must return a value: main function is allowed to terminate without a return. If control reaches the end of main and there is no return, then the compiler implicitly inserts a return of 0.
  • The value returned from main is treated as a status indicator. A zero return indicates success; other values indicate failure. A nonzero value has a machine-dependent meaning. To make return values machine independent, the cstdlib header defines two preprocessor variables(§2.3.2, p.54) that we can use to indicate success or failure.
int main(){    if(some_failure)        return EXIT_FAILURE; // defined in cstdlib    else        return EXIT_SUCCESS; // defined in cstdlib}
  • Because these are preprocessor variables, we must not precede them with std::, nor may we mention them in using declarations.

Recursion

  • A function that calls itself is a recursive function.
  • There must always be a path through a recursive function that does not involve a recursive call; otherwise, the function will continue to call itself until the program stack is exhausted.
  • The main function may not call itself.

Exercises Section 6.3.2

Exercise 6.30

Compile the version of str_subrange as presented on page 223 to see what your compiler does with the indicated errors.

  • g++: error: return-statement with no value, in function returning ‘bool’

Exercise 6.31

When is it valid to return a reference? A reference to const?

  • When you can find the pre-exited object that the reference referred.

Exercise 6.32

Indicate whether the following function is legal. If so, explain what it does; if not, correct any errors and then explain it.

int &get(int *arry, int index){    return arry[index];}int main(){    int ia[10];    for(int i = 0; i != 10; ++i)        get(ia, i) = i;}
  • Legal, it gave the values(0 ~ 9) to array ia .

Exercise 6.33

Write a recursive function to print the contents of a vector.

#include <iostream>#include <vector>using std::cout;using std::vector;void Print(vector<int> &vec, int index){    if(index == 0)    {        cout << vec[0] << ' ';        return;    }    Print(vec, index - 1);    cout << vec[index] << ' ';}int main(){    vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};    Print(vec, vec.size() - 1);}/*Output:1 2 3 4 5 6 7 8 9 10*/

Exercise 6.34

What would happen if the stopping condition in factorial were
if(val != 0)

  • Case 1: If argument is positive, recursion stops at 0.
  • case 2 : If argument is negative, recursion would never stop. A stack overflow would occur.

Exercise 6.35

In the call to fact, why did we pass val - 1 rather than val–?

  • Recursive function will always use val as the parameter, so a recursion loop would happen.

6.3.3. Returning a Pointer to an Array

  • A function can return a pointer or a reference to an array(§ 3.5.1, p. 114).

Declaring a Function That Returns a Pointer to an Array

  • If we want to define a function that returns a pointer to an array, the dimension must follow the function’s name. The parameter list precedes the dimension.
Type( *function(parameter_list) )[dimension]int(*Fun())[10];            // Return a pointer to an array of five intstypedef int Array[10];  // Array is a synonym for the type array of ten intsusing Array = int [10]; // Equivalent declaration of ArrayArray* Fun();           // Equivalent to: int(*Fun())[10];

Using a Trailing Return Type

  • C++11: Trailing return can be defined for any function. A trailing return type follows the parameter list and is preceded by ->. To signal that the return follows the parameter list, we use auto where the return type appears.
// func takes an int argument and returns a pointer to an array of ten intsauto func(int i) -> int(*)[10];

Using decltype

  • If we know the array to which our function can return a pointer, we can use decltype to declare the return type. Following function returns a pointer to one of two arrays, depending on the value of its parameter:
int odd[] = {1,3,5,7,9};int even[] = {0,2,4,6,8};// returns a pointer to an array of five int elementsdecltype(odd) *Fun(int i){    return(i % 2) ? &odd : &even; // returns a pointer to the array}
  • Since decltype doesn’t convert an array to its corresponding pointer type, the type returned by decltype is an array type, to which we must add * to indicate that Fun returns a pointer.

Exercises Section 6.3.3

Exercise 6.36

Write the declaration for a function that returns a reference to an array of ten strings, without using either a trailing return, decltype, or a type alias.

string(&Fun(string(&array)[10]))[10]

Exercise 6.37

Write three additional declarations for the function in the previous exercise. One should use a type alias, one should use a trailing return, and the third should use decltype. Which form do you prefer and why?

using Array = string [10];Array &Fun(string(&array)[10]);auto Fun(string(&array)[10]) -> string(&) [10]string array[10];decltype(array) &Fun(string(&array)[10])

Exercise 6.38

Revise the arrPtr function on to return a reference to the array.

decltype(arrStr)& arrPtr(int i){    return(i % 2) ? odd : even;}

6.4. Overloaded Functions

  • Functions that have the same name but different parameter lists and that appear in the same scope are overloaded. When we call these functions, the compiler can deduce which function we want based on the argument type we pass. The main function may not be overloaded.
  • Overloaded functions must differ in the number or the types of their parameters. It is an error for two functions to differ only in terms of their return types, if so, then the second and following declarations are wrong.

Determining Whether Two Parameter Types Differ

  • Two parameter lists can be identical, even if they don’t look the same.
// each pair declares the same functionRecord lookup(const Account &acct);Record lookup(const Account&); // parameter names are ignoredtypedef Phone Telno;Record lookup(const Phone&);Record lookup(const Telno&); // Telno and Phone are the same type
  • Parameter names are only a documentation aid. They do not change the parameter list.
  • Since a type alias(§2.5.1, p.67) does not create a new type, two parameters that differ only in that one uses an alias and the other uses the type to which the alias corresponds are not different.

Overloading and const Parameters

  • A parameter that has a top-level const is the same type as one without a top-level const. But we can overload based on whether the parameter is a reference(or pointer) to the const or nonconst version of a given type(low-level const).
// Top-level constvoid Fun(int ) {}void Fun(const int) {}  //error: redefinition of ‘void Fun(int)’// Low-level constvoid Fun(int *) {}void Fun(const int*) {} //okvoid Fun(int &) {}void Fun(const int &) {}    //ok
  • Because there is no conversion(§4.11.2, p.162) from const, we can pass a const object(or a pointer to const) only to the version with a const parameter.
    Because there is a conversion to const, we can call either function on a nonconst object or a pointer to nonconst.
    The compiler will prefer the nonconst versions when we pass a nonconst object or pointer to nonconst.

const_cast and Overloading

  • §4.11.3(p.163): const_casts are useful in the context of overloaded functions.
const string &ShorterString(const string &str1, const string &str2){    return str1.size() <= str2.size() ? str1 : str2;}
  • This function takes and returns references to const string. We can call the function on a pair of nonconst string arguments, but we’ll get a reference to a const string as the result. We want to have a version of ShorterString that, when given nonconst arguments, would yield a plain reference.
string &ShorterString(string &str1, string &str2){    const string &result = ShorterString(const_cast<const string&>(str1),                                         const_cast<const string&>(str2));    return const_cast<string &>(result);}

Calling an Overloaded Function

  • Function matching(or overload resolution) is the process by which a particular function call is associated with a specific function from a set of overloaded functions. The compiler determines which function to call by comparing the arguments in the call with the parameters offered by each function in the overload set.
  • For any given call to an overloaded function, there are three possible outcomes:
    1. The compiler finds exactly 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 issues an error message that there was no match.
    3. There is more than one function that matches and none of the matches is clearly best. This case is also an error; it is an ambiguous call.

Exercises Section 6.4

Exercise 6.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 get();double get();(c)int *reset(int *);double *reset(double *);
  • Illegal: top-level const is ignored.
  • Illegal: functions can’t differ only in return type.
  • ok.

6.4.1. Overloading and Scope

  • If we declare a name in an inner scope, that name hides uses of that name declared in an outer scope. Names do not overload across scopes.
string  read()              {return "gaoxiangnumber1";}void    print(string &str)      {cout << str << '\n';}void    print(double num)   {cout << num << '\n';}void    print(int num)      {cout << num << '\n';}int main(){    bool read = false;      // new scope: hides the outer declaration of read    string str = read();    // error: read is a bool variable, cannot be used as a function    void print(int);        // new scope: hides previous instances of print    int num1 = 3;    double num2 = 3.14;    // error: print(string &) is hidden    print("gaoxiangnumber1");   //error: invalid conversion from ‘const char*’ to ‘int’    print(num1);                    // ok: print(int) is visible. print: 3    print(num2);                    // ok: calls print(int); print(double) is hidden. print: 3}
  • 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 a name is found, the compiler ignores uses of that name in any outer scope.
  • In C++, name lookup happens before type checking. The first call passes a string literal, but the only declaration for print that is in scope has a parameter that is an int. A string literal cannot be converted to an int, so this call is an error.
  • When we call print passing a double, the double argument can be converted to an int, so the call is legal.
  • Had we declared print(int) in the same scope as the other print functions, these calls would be resolved differently because the compiler will see all three functions:
void print(const string &str) {cout << str << '\n';}void print(double num) {cout << num << '\n';}void print(int num) {cout << num << '\n';}int main(){    int num1 = 3;    double num2 = 3.14;    print("gaoxiangnumber1");    print(num1);    print(num2);}

6.5. Features for Specialized Uses

6.5.1. Default Arguments

  • A default argument is specified as an initializer for a parameter in the parameter list. We may define defaults for one or more parameters. If a parameter has a default argument, all the parameters that follow it must also have default arguments.

Calling Functions with Default Arguments

  • If we want to use the default argument, we omit that argument when we call the function.
string screen(int height = 24, int width = 80, char background = ' '){    cout << height << '\t' << width << '\t' << background << '\n';    return "gaoxiangnumber1";}int main(){    string window = screen();       // equivalent to screen(24,80,' ')    window = screen(66);        // equivalent to screen(66,80,' ')    window = screen(66, 256);   // screen(66,256,' ')    window = screen(66, 256, '#');  // screen(66,256,'#')}/*Output:24  80   66  80   66  256  66  256 #*/
  • Arguments in the call are resolved by position. The default arguments are used for the trailing(right-most) arguments of a call. To override the default for background, we must also supply arguments for height and width.
window = screen(, , '?');   // error: can omit only trailing argumentswindow = screen('0');       // calls screen(48, 80, ‘ ’);
  • The second call passes a single character value and a char can be converted(§4.11.1) to the type of the left-most parameter. In this call, the char argument is implicitly converted to int, and is passed as the argument to height.
  • Design a function with default arguments to order 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 Declarations

  • It is legal to redeclare a function multiple times. Each parameter can have its default specified only once in a given scope. Any subsequent declaration can add a default only for a parameter that has not previously had a default specified. Defaults can be specified only if all parameters to the right already have defaults.
// no default for the height or width parametersstring screen(sz, sz, char = ' ');string screen(sz, sz, char = '*'); // error: redeclarationstring screen(sz = 24, sz = 80, char); // ok: adds default arguments

Default Argument Initializers

  • Local variables may not be used as a default argument. Excepting that restriction, a default argument can be any expression that has a type that is convertible to the type of the parameter.
// the declarations of wd, def, and ht must appear outside a functionsz wd = 80;char def = ' ';sz ht();string screen(sz = ht(), sz = wd, char = def);string window = screen();   // calls screen(ht(), 80, ' ')
  • Names used as default arguments are resolved in the scope of the function declaration. The value that those names represent is evaluated at the time of the call.
void f2(){    def = '*';              // changes the value of a default argument    sz wd = 100;            // hides the outer definition of wd but not change the default    window = screen();  // calls screen(ht(), 80, '*')}

Exercises Section 6.5.1

Exercise 6.40

Which, if either, 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);
  • Right
  • Error: miss the default value for wd and bckgrnd.

Exercise 6.41

Which, if any, of the following calls are illegal? Why? Which, if any, are legal but unlikely to match the programmer’s intent? Why?

char *init(int ht, int wd = 80, char bckgrnd = ' ');(a) init();(b) init(24,10);(c) init(14, '*');
  • Illegal: miss value for at least “ht”
  • Right
  • Right, but unlikely to match the programmer’s intent. We should provide value for “ht”.

Exercise 6.42

Give the second parameter of make_plural(§6.3.2, p.224) a default argument of ‘s’. Test your program by printing singular and plural versions of the words success and failure.

#include <iostream>#include <string>using std::cout;using std::endl;using std::string;string make_plural(size_t ctr, const string& word, const string& ending = "s"){    return (ctr > 1) ? word + ending : word;}int main(){    cout << "singual: " << make_plural(1, "success", "es") << " "         << make_plural(1, "failure") << endl;    cout << "plural : " << make_plural(2, "success", "es") << " "         << make_plural(2, "failure") << endl;    return 0;}

6.5.2. Inline and constexpr Functions

inline Functions Avoid Function Call Overhead

  • A function specified as inline usually is expanded “in line” at each call. The run-time overhead of making a function call is removed. We can define an inline function by putting the keyword inline before the function’s return type.
  • The inline specification is only a request to the compiler. The compiler may choose to ignore this request. In general, the inline mechanism is meant to optimize small, straight-line functions that are called frequently. Many compilers will not inline a recursive function. A 75-line function will almost surely not be expanded inline.

constexpr Functions

  • A constexpr function is a function that can be used in a constant expression(§2.4.4). Its define restrictions: The return type and the type of each parameter must be a literal type(§2.4.4, p.66), and the function body must contain exactly one return statement:
constexpr int new_sz() { return 42; }constexpr int foo = new_sz();   // ok: foo is a constant expression
  • The compiler can verify at compile time that a call to new_sz returns a constant expression, so we can use new_sz to initialize our constexpr variable. constexpr functions are implicitly inline.
  • A constexpr function body may contain other statements so long as those statements generate no actions at run time. For example, a constexpr function may contain null statements, type aliases, and using declarations.
  • A constexpr function is permitted to return a value that is not a constant:
// scale(arg) is a constant expression if arg is a constant expressionconstexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
  • The scale function will return a constant expression if its argument is a constant expression but not otherwise:
int arr[scale(2)];  // ok: scale(2) is a constant expressionint i = 2;          // i is not a constant expressionint a2[scale(i)];   // error: scale(i) is not a constant expression

Put inline and constexpr Functions in Header Files

  • inline and constexpr functions may be defined multiple times in the program. All of the definitions of a given inline or constexpr must match exactly. inline and constexpr functions normally are defined in headers.

Exercises Section 6.5.2

Exercise 6.43

Which one of the following declarations and definitions would you put in a header? In a source file? Explain why.

(a) inline bool eq(const BigInt&, const BigInt&) {...}(b) void putValues(int *arr, int size);
  • Declaration and definition: header file.
  • Declaration: header file; definition: source file.

Exercise 6.44

Rewrite the isShorter function from § 6.2.2(p. 211) to be inline.

inline bool isShorter(const string& s1, const string& s2){    return s1.size() < s2.size();}

Exercise 6.45

Review the programs you’ve written for the earlier exercises and decide whether they should be defined as inline. If so, do so. If not, explain why they should not be inline.

  • As you like.

Exercise 6.46

Would it be possible to define isShorter as a constexpr? If so, do so. If not, explain why not.

  • No. Because std::string::size() is not a constexpr function and s1.size() == s2.size() is not a constant expression.

6.5.3. Aids for Debugging

  • The program contains debugging code that is executed only while the program is being developed. When the application is completed and ready to ship, the debugging code is turned off. This approach uses two preprocessor facilities: assert and NDEBUG.

The assert Preprocessor Macro

  • assert is a preprocessor macro that is a preprocessor variable that acts like an inline function.
  • The assert macro takes a single expression, which it uses as a condition.
    assert(expr);
    evaluates expr and if the expression is false(i.e., zero), then assert writes a message and terminates the program. If the expression is true(i.e., is nonzero), then assert does nothing.
  • The assert macro is defined in . Preprocessor names are managed by the preprocessor not the compiler, so we use preprocessor names directly and do not provide a using declaration for them. That is, we refer to assert, not std::assert, and provide no using declaration for assert.

The NDEBUG Preprocessor Variable

  • The behavior of assert depends on the status of a preprocessor variable named NDEBUG. If NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined, so, assert performs a run-time check.
  • We can turn off debugging by providing a #define to define NDEBUG. Most compilers provide a command-line option that lets us define preprocessor variables.
  • If NDEBUG is defined, there is no run-time check. Therefore, assert should be used only to verify things that truly should not be possible. It can be useful as an aid in getting a program debugged but should not be used to substitute for run-time logic checks or error checking that the program should do.
  • We can write our own conditional debugging code using NDEBUG. If NDEBUG is not defined, the code between the #ifndef and the #endif is executed. If NDEBUG is defined, that code is ignored:
#include <iostream>using std::cout;void print(const int ia[], size_t size){#ifndef NDEBUG// __func__ is a local static defined by the compiler that holds the function's name    cout << __func__ << ": array size is " << size << endl;#endif// ...}int main(){    int arr[10];    print(arr, 10); //print: array size is 10    return 0;}
  • The compiler defines func in every function that is a local static array of const char that holds the name of the function.
  • The preprocessor defines four names that can be useful in debugging:
      FILE string literal containing the name of the file
      LINE integer literal containing the current line number
      TIME string literal containing the time the file was compiled
      DATE string literal containing the date the file was compiled
#include <iostream>#include <string>using std::cout;using std::string;void print(const string &word, int threshold = 100){    if (word.size() < threshold)        cout << "Error: " << __FILE__             << " : in function " << __func__             << " at line " << __LINE__ << '\n'             << " Compiled on " << __DATE__             << " at " << __TIME__ << '\n'             << " Word read was \"" << word             << "\": Length too short" << '\n';}int main(){    print("gaoxiangnumber1");    return 0;}/*Output:$ ./mainError: main.cpp : in function print at line 12 Compiled on Aug 26 2016 at 08:23:02 Word read was "gaoxiangnumber1": Length too short*/##Exercises Section 6.5.3###Exercise 6.47>Revise the program you wrote in the exercises in §6.3.2(p.228) that used recursion to print the contents of a vector to conditionally print information about its execution. For example, you might print the size of the vector on each call. Compile and run the program with debugging turned on and again with it turned off.```cpp<div class="se-preview-section-delimiter"></div>#include <iostream><div class="se-preview-section-delimiter"></div>#include <vector>using std::cout;using std::vector;<div class="se-preview-section-delimiter"></div>#define NDEBUGvoid Print(vector<int> &vec, int index){<div class="se-preview-section-delimiter"></div>#ifdef NDEBUG    cout << "vector size: " << vec.size() << '\n';<div class="se-preview-section-delimiter"></div>#endif    if(index == 0)    {        cout << vec[0] << ' ';        return;    }    Print(vec, index - 1);    cout << vec[index] << ' ';}int main(){    vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};    Print(vec, vec.size() - 1);}/*Output:vector size: 10vector size: 10vector size: 10vector size: 10vector size: 10vector size: 10vector size: 10vector size: 10vector size: 10vector size: 101 2 3 4 5 6 7 8 9 10*/<div class="se-preview-section-delimiter"></div>

Exercise 6.48

Explain what this loop does and whether it is a good use of assert:

string s;while(cin >> s && s != sought) { } // empty bodyassert(cin);<div class="se-preview-section-delimiter"></div>
  • This loop let user input a word all the way until the word is sought.
  • It isn’t a good use of assert. The assert macro is often used to check for conditions that “cannot happen”. But the assert would always happen when users input EOF directly. Using assert(!cin || s == sought) is more better.

6.6. Function Matching

  • 1.First identifies the candidate functions that have the same name as the called function and whose declaration is visible at the point of the call.
    2.Second selects viable functions from the candidate functions. Viable function must have the same number of parameters as there are arguments in the call, and the type of each argument must match(or be convertible to) the type of its corresponding parameter. If there are no viable functions, the compiler will complain that there is no matching function.
    3.Third determines which viable function provides the best match for the call.

Function Matching with Multiple Parameters

f(42, 2.56);

  • There is an overall best match if there is one and only one function for which
    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 best match, then the call is error(call of overloaded ‘xxx’ is ambiguous).

Exercises Section 6.6

Exercise 6.49

What is a candidate function? What is a viable function?

  • Candidate functions have the same name as the called function and their declaration are visible at the point of the call.
  • Viable functions are subset of candidate functions. Viable function must have the same number of parameters as there are arguments in the call, and the type of each argument must match(or be convertible to) the type of its corresponding parameter.

Exercise 6.50

Given the declarations for f from page 242, list the viable functions, if any for each of the following calls. Indicate which function is the best match, or if the call is illegal whether there is no match or why the call is ambiguous.

void f();void f(int);void f(int, int);void f(double, double = 3.14);(a) f(2.56, 42)(b) f(42)(c) f(42, 0)(d) f(2.56, 3.14)<div class="se-preview-section-delimiter"></div>
  • Illegal.
  • void f(int);
  • void f(int, int);
  • void f(double, double = 3.14);

Exercise 6.51

Write all four versions of f. Each function should print a distinguishing message. Check your answers for the previous exercise. If your answers were incorrect, study this section until you understand why your answers were wrong.

<div class="se-preview-section-delimiter"></div>#include <iostream>using std::cout;using std::endl;void f(){    cout << "f()" << endl;}void f(int){    cout << "f(int)" << endl;}void f(int, int){    cout << "f(int, int)" << endl;}void f(double, double = 3.14){    cout << "f(double, double = 3.14)" << endl;}int main(){    f(2.56, 42); // error: call of overloaded ‘f(double, int)’ is ambiguous    f(42);    f(42, 0);    f(2.56, 3.14);    return 0;}<div class="se-preview-section-delimiter"></div>

6.6.1. Argument Type Conversions

  • Compiler ranks the conversions that could be used to convert each argument to the type of its corresponding parameter. Conversions are ranked as follows:
    1. An exact match. An exact match happens when:
      • The argument and parameter types are identical.
      • The argument is converted from an array or function type to the corresponding pointer type.(§ 6.7 (p. 247) covers function pointers.)
      • A top-level const is added to or discarded from the argument.
    2. Match through a const conversion(§ 4.11.2, p. 162).
    3. Match through a promotion(§ 4.11.1, p. 160).
    4. Match through an arithmetic(§ 4.11.1, p. 159) or pointer conversion(§4.11.2,p.161).
    5. Match through a class-type conversion.(§ 14.9(p. 579) covers these conversions.)

Matches Requiring Promotion or Arithmetic Conversion

  • The small integral types always promote to int or to a larger integral type. Given two functions, one takes an int and the other takes a short, the short version will be called only on values of type short.
void Fun(int){    cout << "int\n";}void Fun(short){    cout << "short\n";}int main(){    short num = 100;    Fun('g');       // int    Fun(5);     // int    Fun(num);   // short    return 0;}<div class="se-preview-section-delimiter"></div>
  • All the arithmetic conversions are treated as equivalent to each other.
    E.g.: double -> int has the same precedence as double -> float.
void Fun(int);void Fun(float);Fun(3.14);  // error: ambiguous call<div class="se-preview-section-delimiter"></div>

Function Matching and const Arguments

void Fun(int *){    cout << "int *\n";}void Fun(const int *){    cout << "const int *\n";}int main(){    int *ptr1 = nullptr;    const int *ptr2 = nullptr;    Fun(ptr1);  // int *    Fun(ptr2);  // const int *    return 0;}<div class="se-preview-section-delimiter"></div>
  • If two functions differ only as to whether a pointer/reference parameter points/refers to const or nonconst, the compiler can distinguish which function to call based on the constness of the argument:
    1. If the argument is a pointer/reference to const, the call will match the function that takes a const*/ const&;
    2. If the argument is a pointer/reference to nonconst, the function taking a plain pointer/reference is called.

Exercises Section 6.6.1

Exercise 6.52

Given the following declarations,

void manipulate(int, int);double num;<div class="se-preview-section-delimiter"></div>

what is the rank(§ 6.6.1, p. 245) of each conversion in the following calls?
(a) manipulate(‘a’, ‘z’);
(b) manipulate(55.4, num);

    1. Match through a promotion.
    1. Match through an arithmetic conversion.

Exercise 6.53

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);<div class="se-preview-section-delimiter"></div>
  • Legal.
  • Legal.
  • Illegal. Top-level const omit.

6.7. Pointers to Functions

  • A function pointer is a pointer that denotes a function rather than an object. A function’s type is determined by its return type and the types of its parameters. The function’s name is not part of its type.
    bool Fun(const string &, const string &);
    has type
    bool(const string&, const string&).
  • The parentheses around *pf are necessary. If we omit the parentheses, then we declare pf as a function that returns a pointer to bool:
// pf points to a function returning bool that takes two const string referencesbool(*pf)(const string &, const string &); // uninitialized// declares a function named pf that returns a bool*bool *pf(const string &, const string &);<div class="se-preview-section-delimiter"></div>

Using Function Pointers

  • When we use the name of a function as a value, the function is automatically converted to a pointer.
pf = Fun;   // pf points to the function named Funpf = &Fun;  // equivalent assignment: address-of operator is optional<div class="se-preview-section-delimiter"></div>
  • We can use a pointer to a function to call the function to which the pointer points. There is no need to dereference the pointer:
bool b1 = pf("hello", "goodbye");       // calls Funbool b2 = (*pf)("hello", "goodbye");    // equivalent callbool b3 = Fun("hello", "goodbye");  // equivalent call<div class="se-preview-section-delimiter"></div>
  • There is no conversion between pointers to one function type and pointers to another function type. We can assign nullptr(§ 2.3.2, p. 53) or a zero-valued integer constant expression to a function pointer to indicate that the pointer does not point to any function.

Pointers to Overloaded Functions

  • When we declare a pointer to an overloaded function, the compiler uses the type of the pointer to determine which overloaded function to use. The type of the pointer must match one of the overloaded functions exactly:
void ff(int*);void ff(unsigned int);void(*pf1)(unsigned int) = ff;  // pf1 points to ff(unsigned)void(*pf2)(int) = ff;           // error: no ff with a matching parameter listdouble(*pf3)(int*) = ff;        // error: return type of ff and pf3 don't match<div class="se-preview-section-delimiter"></div>

Function Pointer Parameters

  • As with arrays(§6.2.4), we cannot define parameters of function type but can have a parameter that is a pointer to function. When we pass a function as an argument, it is automatically converted to a pointer.
// pf is a function type and is automatically treated as a pointer to functionvoid Fun(bool pf(const string &, const string &));// equivalent declaration: explicitly define the parameter as a pointer to functionvoid Fun(bool(*pf)(const string &, const string &));<div class="se-preview-section-delimiter"></div>
  • Type aliases(§2.5.1) and decltype(§2.5.3) can simplify code that uses function pointers.
// Func and Func2 have function typetypedef bool Func(const string&, const string&);typedef decltype(Fun) Func2;        // equivalent type// Func_Ptr and FuncP2 have pointer to function typetypedef bool (*Func_Ptr)(const string&, const string&);typedef decltype(Fun) *FuncP2;  // equivalent type<div class="se-preview-section-delimiter"></div>
  • Because decltype returns a function type, if we want a pointer we must add the * ourselves.
// equivalent declarations of Fun using type aliasesvoid Fun(Func);void Fun(FuncP2);<div class="se-preview-section-delimiter"></div>
  • The first case: the compiler automatically convert the function type represented by Func to a pointer.

Returning a Pointer to Function

  • As with arrays(§6.3.3), we can’t return a function type but can return a pointer to a function type. We must write the return type as a pointer type because the function return type is not automatically converted to a pointer type.
using F = int(int*, int);       // F is a function type, not a pointerusing PF = int(*)(int*, int);   // PF is a pointer typeint(*f1(int))(int*, int);PF f1(int); // ok: PF is a pointer to function; f1 returns a pointer to functionF f1(int);  // error: F is a function type; f1 can't return a functionF *f1(int); // ok: explicitly specify that the return type is a pointer to functionauto f1(int) -> int(*)(int*, int);<div class="se-preview-section-delimiter"></div>

Using auto or decltype for Function Pointer Types

  • If we know which function we want to return, we can use decltype to simplify writing a function pointer return type. Assume we have two functions, both of which return a string::size_type and have two const string& parameters. We can write a third function that takes a string parameter and returns a pointer to one of these two functions as follows:
string::size_type Sum_Length(const string&, const string&);string::size_type Larger_Length(const string&, const string&);// depending on the value of its string parameter,// Get_Func returns a pointer to Sum_Length or to Larger_Lengthdecltype(Sum_Length) *Get_Func(const string &);<div class="se-preview-section-delimiter"></div>
  • When we apply decltype to a function, it returns a function type, not a pointer to function type. We must add a * to indicate that we are returning a pointer, not a function.

Exercises Section 6.7

Exercise 6.54

Write a declaration for a function that takes two int parameters and returns an int, and declare a vector whose elements have this function pointer type.

int func(int, int);using pFunc1 = decltype(func) *;typedef decltype(func) *pFunc2;using pFunc3 = int (*)(int a, int b);using pFunc4 = int(int a, int b);typedef int(*pFunc5)(int a, int b);using pFunc6 = decltype(func);vector<int(*)(int, int)> vec;std::vector<pFunc1> vec1;std::vector<pFunc2> vec2;std::vector<pFunc3> vec3;std::vector<pFunc4*> vec4;std::vector<pFunc5> vec5;std::vector<pFunc6*> vec6;<div class="se-preview-section-delimiter"></div>

Exercise 6.55

Write four functions that add, subtract, multiply, and divide two int values. Store pointers to these values in your vector from the previous exercise.

int Add(int a, int b){    return a + b;}int Subtract(int a, int b){    return a - b;}int Multiply(int a, int b){    return a * b;}int Divide(int a, int b){    return b != 0 ? a / b : 0;}<div class="se-preview-section-delimiter"></div>

Exercise 6.56

Call each element in the vector and print their result.

<div class="se-preview-section-delimiter"></div>#include <iostream><div class="se-preview-section-delimiter"></div>#include <string><div class="se-preview-section-delimiter"></div>#include <vector>using std::vector;using std::cout;inline int f(const int, const int);typedef decltype(f) fp; // fp is just a function type not a function pointerinline int NumAdd(const int n1, const int n2){    return n1 + n2;}inline int NumSub(const int n1, const int n2){    return n1 - n2;}inline int NumMul(const int n1, const int n2){    return n1 * n2;}inline int NumDiv(const int n1, const int n2){    return n1 / n2;}int main(){    vector<fp*> v {NumAdd, NumSub, NumMul, NumDiv};    for(auto it = v.cbegin(); it != v.cend(); ++it)    {        cout << (*it)(2, 2) << std::endl;    }    return 0;}

Chapter Summary

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

0 0
原创粉丝点击