Google
 

Monday, January 10, 2011

C++ Primer, Fourth Edition Notes Chapter 7. Functions

A function can be thought of as a programmer-defined operation. Like the built-in operators, each function performs some computation and (usually) yields a result. Unlike the operators, functions have names and may take an unlimited number of operands. Like operators, functions can be overloaded, meaning that the same name may refer to multiple different functions.

Section 7.1 Defining a Function

  1. Calling a Function
    1. To invoke a function we use the call operator, which is a pair of parentheses. As with any operator, the call operator takes operands and yields a result. The operands to the call operator are the name of the function and a (possibly empty) comma-separated list of arguments.
    2. Calling a function does two things: It initializes the function parameters from the corresponding arguments and transfers control to the function being invoked. Execution of the calling function is suspended and execution of the called function begins. Execution of a function begins with the (implicit) definition and initialization of its parameters.
  2. Function Body Is a Scope
    1. Names defined inside a function body are accessible only within the function itself. Such variables are referred to as local variables. They are "local" to that function; their names are visible only in the scope of the function. They exist only while the function is executing. Section 7.5 (p. 254) covers local variables in more detail.
    2. Execution completes when a return statement is encountered. When the called function finishes, it yields as its result the value specified in the return statement. After the return is executed, the suspended, calling function resumes execution at the point of the call.
  3. Parameters and Arguments
    1. Like local variables, the parameters of a function provide named, local storage for use by the function. The difference is that parameters are defined inside the function's parameter list and are initialized by arguments passed to the function when the function is called.
    2. An argument is an expression. It might be a variable, a literal constant or an expression involving one or more operators. We must pass exactly the same number of arguments as the function has parameters. The type of each argument must match the corresponding parameter in the same way that the type of an initializer must match the type of the object it initializes: The argument must have the same type or have a type that can be implicitly converted (Section 5.12, p. 178) to the parameter type.
  4. 7.1.1. Function Return Type
    1. The return type of a function can be a built-in type, such as int or double, a class type, or a compound type, such as int& or string*. A return type also can be void, which means that the function does not return a value.
    2. A function may not return another function or a built-in array type. Instead, the function may return a pointer to the function or to a pointer to an element in the array
    3. Functions Must Specify a Return Type
      1. It is illegal to define or declare a function without an explicit return type
      2. Eariler versions of C++ would accept this program and implicitly define the return type of test as an int. Under Standard C++, this program is an error
        // error: missing return type
        test(double v1, double v2) { /* ... */ }

      3. In pre-Standard C++, a function without an explicit return type was assumed to return an int. C++ programs compiled under earlier, non-standard compilers may still contain functions that implicitly return int


  5. 7.1.2. Function Parameter List

    1. The parameter list of a function can be empty but cannot be omitted. A function with no parameters can be written either with an empty parameter list or a parameter list containing the single keyword void.

      1. void process() { /* ... */ } // implicit void parameter list
      2. process(void){ /* ... */ } // equivalent declaration

    2. A parameter list consists of a comma-separated list of parameter types and (optional) parameter names.
    3. Names are optional, but in a function definition, normally all parameters are named. A parameter must be named to be used.
    4. C++ is a statically typed language (Section 2.3, p. 44). The arguments of every call are checked during compilation.
    5. When we call a function, the type of each argument must be either the same type as the corresponding parameter or a type that can be converted (Section 5.12, p. 178) to that type. The function's parameter list provides the compiler with the type information needed to check the arguments.
    // EXE-7.1.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>
    using namespace std;



    int main(int argc, _TCHAR* argv[])
    {
    int gcd(int v1, int v2);
    //bool is_present(int *, int); // returns bool
    //int count(const string &, char); // returns int
    //Date &calendar(const char*); // returns reference to Date
    //void process(); // process does not return a value

    int v1,v2;
    cout << "Please input two integers:\n";
    cin >> v1 >> v2;
    cout << "The greatest common divisor is " << gcd(v1,v2) << endl;

    int i;
    cin >> i;

    return 0;
    }
    // return the greatest common divisor
    int gcd(int v1, int v2)
    {
    cout << "v1 = " << v1 << " v2 = " << v2 << endl;
    while (v2) {
    int temp = v2;
    v2 = v1 % v2;
    v1 = temp;
    cout << "temp = " << temp
    << " v1 = " << v1
    << " v2 = " << v2 << endl;
    }
    return v1;
    }

Section 7.2 Argument Passing



  1. Each parameter is created anew on each call to the function. The value used to initialize a parameter is the corresponding argument passed in the call.
  2. Parameters are initialized the same way that variables are. If the parameter has a nonreference type, then the argument is copied. If the parameter is a reference (Section 2.5, p. 58), then the parameter is just another name for the argument.
  3. 7.2.1. Nonreference Parameters

    1. Parameters that are plain, nonreference types are initialized by copying the corresponding argument. When a parameter is initialized with a copy, the function has no access to the actual arguments of the call. It cannot change the arguments.
    2. Nonreference parameters represent local copies of the corresponding argument. Changes made to the parameter are made to the local copy. Once the function terminates, these local values are gone.
    3. Pointer Parameters

      1. A parameter can be a pointer (Section 4.2, p. 114), in which case the argument pointer is copied. As with any nonreference type parameter, changes made to the parameter are made to the local copy. If the function assigns a new pointer value to the parameter, the calling pointer value is unchanged.
      2. If we want to prevent changes to the value to which the pointer points, then the parameter should be defined as a pointer to const
      3. Whether a pointer parameter points to a const or nonconst type affects the arguments that we can use to call the function
      4. This distinction follows from the initialization rules for pointers (Section 4.2.5, p. 126). We may initialize a pointer to const to point to a nonconst object but may not use a pointer to nonconst to point to a const object.
      5. We can call a function that takes a nonreference, nonconst parameter passing either a const or nonconst argument.
      6. What may be surprising, is that although the parameter is a const inside the function, the compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int
        void fcn(const int i) { /* fcn can read but not write to i */ }
        void fcn(int i) { /* ... */ } // error: redefines fcn(int)

      7. This usage exists to support compatibility with the C language, which makes no distinction between functions taking const or nonconst parameters.

    4. Limitations of Copying Arguments

        Copying an argument is not suitable for every situation. Cases where copying doesn't work include:



        • When we want the function to be able to change the value of an argument.



        • When we want to pass a large object as an argument. The time and space costs to copy the object are often too high for real-world applications.



        • When there is no way to copy the object.


        In these cases we can instead define the parameters as references or pointers.



  4. 7.2.2. Reference Parameters

    1. Like all references, reference parameters refer directly to the objects to which they are bound rather than to copies of those objects. When we define a reference, we must initialize it with the object to which the reference will be bound. Reference parameters work exactly the same way. Each time the function is called, the reference parameter is created and bound to its corresponding argument.
    2. Programmers who come to C++ from a C background are used to passing pointers to obtain access to the argument. In C++ it is safer and more natural to use reference parameters.
    3. Using Reference Parameters to Return Additional Information
    4. Using (const) References to Avoid Copies

      1. The other circumstance in which reference parameters are useful is when passing a large object to a function. Although copying an argument is okay for objects of built-in data types and for objects of class types that are small in size, it is (often) too inefficient for objects of most class types or large arrays. Moreover, as we'll learn in Chapter 13, some class types cannot be copied. By using a reference parameter, the function can access the object directly without copying it.
      2. When the only reason to make a parameter a reference is to avoid copying the argument, the parameter should be const reference

    5. References to const Are More Flexible

      1. The problem is that a nonconst reference (Section 2.5, p. 59) may be bound only to nonconst object of exactly the same type.
      2. Parameters that do not change the value of the corresponding argument should be const references. Defining such parameters as nonconst references needlessly restricts the usefulness of a function.
      3. Reference parameters that are not changed should be references to const. Plain, nonconst reference parameters are less flexible. Such parameters may not be initialized by const objects, or by arguments that are literals or expressions that yield rvalues.

    6. Passing a Reference to a Pointer

      1. We know that we use * to define a pointer and & to define a reference.
        // swap values of two pointers to int
        void ptrswap(int *&v1, int *&v2)
        {
        int *tmp = v2;
        v2 = v1;
        v1 = tmp;
        }

        The parameter

             int *&v1

        should be read from right to left: v1 is a reference to a pointer to an object of type int. That is, v1 is just another name for whatever pointer is passed to ptrswap.




  5. 7.2.3. vector and Other Container Parameters

    1. Ordinarily, functions should not have vector or other library container parameters. Calling a function that has a plain, nonreference vector parameter will copy every element of the vector
    2. In order to avoid copying the vector, we might think that we'd make the parameter a reference. However, for reasons that will be clearer after reading Chapter 11, in practice, C++ programmers tend to pass containers by passing iterators to the elements we want to process

  6. 7.2.4. Array Parameters

    1. Arrays have two special properties that affect how we define and use functions that operate on arrays: We cannot copy an array (Section 4.1.1, p. 112) and when we use the name of an array it is automatically converted to a pointer to the first element (Section 4.2.4, p. 122). Because we cannot copy an array, we cannot write a function that takes an array type parameter. Because arrays are automatically converted to pointers, functions that deal with arrays usually do so indirectly by manipulating pointers to elements in the array.
    2. Defining an Array Parameter
        // three equivalent definitions of printValues
        void printValues(int*) { /* ... */ }
        void printValues(int[]) { /* ... */ }
        void printValues(int[10]) { /* ... */ }

      1. Even though we cannot pass an array directly, we can write a function parameter that looks like an array. Despite appearances, a parameter that uses array syntax is treated as if we had written a pointer to the array element type. These three definitions are equivalent; each is interpreted as taking a parameter of type int*.
      2. It is usually a good idea to define array parameters as pointers, rather than using the array syntax. Doing so makes it clear that what is being operated on is a pointer to an array element, not the array itself. Because an array dimension is ignored, including a dimension in a parameter definition is particularly misleading

    3. Parameter Dimensions Can Be Misleading

      1. The compiler ignores any dimension we might specify for an array parameter
      2. When the compiler checks an argument to an array parameter, it checks only that the argument is a pointer and that the types of the pointer and the array elements match. The size of the array is not checked.

    4. Array Arguments

      1. As with any other type, we can define an array parameter as a reference or nonreference type. Most commonly, arrays are passed as plain, nonreference types, which are quietly converted to pointers.
      2. As usual, a nonreference type parameter is initialized as a copy of its corresponding argument. When we pass an array, the argument is a pointer to the first element in the array. That pointer value is copied; the array elements themselves are not copied. The function operates on a copy of the pointer, so it cannot change the value of the argument pointer. The function can, however, use that pointer to change the element values to which the pointer points. Any changes through the pointer parameter are made to the array elements themselves.
      3. Functions that do not change the elements of their array parameter should make the parameter a pointer to const
        // f won't change the elements in the array
        void f(const int*) { /* ... */ }

    5. Passing an Array by Reference

      1. As with any type, we can define an array parameter as a reference to the array. If the parameter is a reference to the array, then the compiler does not convert an array argument into a pointer. Instead, a reference to the array itself is passed. In this case, the array size is part of the parameter and argument types. The compiler will check that the size of the array argument matches the size of the parameter
      2. The parentheses around &arr are necessary because of the higher precedence of the subscript operator
             f(int &arr[10])     // error: arr is an array of references
        f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints

    6. Passing a Multidimensioned Array

      1. Recall that there are no multidimensioned arrays in C++ (Section 4.4, p. 141). Instead, what appears to be a multidimensioned array is an array of arrays.
      2. As with any array, a multidimensioned array is passed as a pointer to its zeroth element. An element in a multidimenioned array is an array. The size of the second (and any subsequent dimensions) is part of the element type and must be specified
      3. Again, the parentheses around *matrix are necessary:




             int *matrix[10];   // array of 10 pointers
        int (*matrix)[10]; // pointer to an array of 10 ints

      4. We could also declare a multidimensioned array using array syntax. As with a single-dimensioned array, the compiler ignores the first dimension and so it is best not to include it



             // first parameter is an array whose elements are arrays of 10 ints
        void printValues(int matrix[][10], int rowSize);



  7. 7.2.5. Managing Arrays Passed to Functions

    1. As we've just seen, type checking for a nonreference array parameter confirms only that the argument is a pointer of the same type as the elements in the array. Type checking does not verify that the argument actually points to an array of a specified size.
    2. It is up to any program dealing with an array to ensure that the program stays within the bounds of the array.
    3. There are three common programming techniques to ensure that a function stays within the bounds of its array argument(s). The first places a marker in the array itself that can be used to detect the end of the array.
    4. Using the Standard Library Conventions

      1. A second approach is to pass pointers to the first and one past the last element in the array. This style of programming is inspired by techniques used in the standard library.

    5. Explicitly Passing a Size Parameter

      1. A third approach, which is common in C programs and pre-Standard C++ programs, is to define a second parameter that indicates the size of the array.


  8. 7.2.6. main: Handling Command-Line Options

    1. The way this usage is handled is that main actually defines two parameters:
        int main(int argc, char *argv[]) { ... }
        int main(int argc, char **argv) { ... }

    2. The second parameter, argv, is an array of C-style character strings. The first parameter, argc, passes the number of strings in that array.
    3. When arguments are passed to main, the first string in argv, if any, is always the name of the program. Subsequent elements pass additional optional strings to main.

  9. 7.2.7. Functions with Varying Parameters

    1. Ellipsis parameters are in C++ in order to compile C programs that use varargs. See your C compiler documentation for how to use varargs. Only simple data types from the C++ program should be passed to functions with ellipses parameters. In particular, objects of most class types are not copied properly when passed to ellipses parameters.
    2. Ellipses parameters are used when it is impossible to list the type and number of all the arguments that might be passed to a function. Ellipses suspend type checking. Their presence tells the compiler that when the function is called, zero or more arguments may follow and that the types of the arguments are unknown. Ellipses may take either of two forms:




            void foo(parm_list, ...);
        void foo(...);

      The first form provides declarations for a certain number of parameters. In this case, type checking is performed when the function is called for the arguments that correspond to the parameters that are explicitly declared, whereas type checking is suspended for the arguments that correspond to the ellipsis. In this first form, the comma following the parameter declarations is optional.

      Most functions with an ellipsis use some information from a parameter that is explicitly declared to obtain the type and number of optional arguments provided in a function call. The first form of function declaration with ellipsis is therefore most commonly used.



Section 7.3 The return Statement



  1. A return statement terminates the function that is currently executing and returns control to the function that called the now-terminated function



    return;
    return expression;

  2. 7.3.1. Functions with No Return Value

    1. 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 statement. In a void function, an implicit return takes place after the function's final statement.
    2. Typically, a void function uses a return to cause premature termination of the function. This use of return parallels the use of the break (Section 6.10, p. 212) statement inside a loop.
    3. A function with a void return type ordinarily may not use the second form of the return statement. However, a void function may return the result of calling another function that returns void

  3. 7.3.2. Functions that Return a Value

    1. The second form of the return statement provides the function's result. 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 must have a type that can be implicitly converted to that type.
    2. Failing to provide a return after a loop that does contain a return is particularly insidious because many compilers will not detect it. The behavior at run time is undefined.
    3. Return from main

      1. There is one exception to the rule that a function with a return type other than void must return a value: The 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.
      2. Another way in which the return from main is special is how its returned value is treated. As we saw in Section 1.1 (p. 2), the value returned from main is treated as a status indicator. A zero return indicates success; most other values indicate failure. A nonzero value has a machine-dependent meaning.
      3. To make return values machine-independent, the cstdlib header defines two preprocessor variables (Section 2.9.2, p. 69) that we can use to indicate success or failure

            #include <cstdlib>
            int main()
            {
            if (some_failure)
            return EXIT_FAILURE;
            else
            return EXIT_SUCCESS;
            }

      4. Returning a Nonreference Type

        1. The value returned by a function is used to initialize a temporary object created at the point at which the call was made. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression. C++ programmers usually use the term "temporary" as an abreviation of "temporary object."
        2. The temporary is initialized by the value returned by a function in much the same way that parameters are initialized by their arguments. If the return type is not a reference, then the return value is copied into the temporary at the call site. The value returned when a function returns a nonreference type can be a local object or the result of evaluating an expression.

      5. Returning a Reference

        1. When a function returns a reference type, the return value is not copied. Instead, the object itself is returned.

      6. Never Return a Reference to a Local Object

        1. There's one crucially important thing to understand about returning a reference: Never return a reference to a local variable.
        2. When a function completes, the storage in which the local objects were allocated is freed. A reference to a local object refers to undefined memory after the function terminates.
        3. One good way to ensure that the return is safe is to ask: To what pre-existing object is the reference referring?

      7. Reference Returns Are Lvalues

        1. If we do not want the reference return to be modifiable, the return value should be declared as const:
           const char &get_val(...

      8. Never Return a Pointer to a Local Object

        1. For the same reasons that it is an error to return a reference to a local object, it is also an error to return a pointer to a local object. Once the function completes, the local objects are freed. The pointer would be a dangling pointer (Section 5.11, p. 176) that refers to a nonexistent object.

  4. 7.3.3. Recursion

    1. A function that calls itself, either directly or indirectly, is a recursive function.
    2. A recursive function must always define a stopping condition; otherwise, the function will recurse "forever," meaning that the function will continue to call itself until the program stack is exhausted. This is sometimes called an "infinite recursion error."
    3. The main function may not call itself.
    // EXE-7.3.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>
    #include <cstdlib>

    using namespace std;

    void swap2(int &v1, int &v2);
    void swap(int &v1, int &v2);
    void do_swap(int &v1, int &v2);
    void print(int a, int b, string mark);
    char &get_val(string &str, string::size_type ix);

    int main(int argc, _TCHAR* argv[])
    {
    int SAME_FAILURE = 1;
    cout << "Please input two integers:\n";
    int a(0),b(0);
    cin >> a >> b;
    print(a,b,"Before swap:");
    swap(a,b);
    print(a,b,"After swap:");

    print(a,b,"Before swap2:");
    swap2(a,b);
    print(a,b,"After swap2:");

    if("Reference Returns Are Lvalues")
    {
    string s("a value");
    cout << s << endl; // prints a value
    get_val(s, 0) = 'A'; // changes s[0] to A

    cout << s << endl; // prints A value
    }

    cout << "Please exit number:\n";
    cin >> SAME_FAILURE ;

    if(SAME_FAILURE)
    return EXIT_FAILURE;
    else
    return EXIT_SUCCESS;
    }
    // recursive version greatest common divisor program
    int rgcd(int v1, int v2)
    {
    if (v2 != 0) // we're done once v2 gets to zero
    return rgcd(v2, v1%v2); // recurse, reducing v2 on each call
    return v1;
    }

    char &get_val(string &str, string::size_type ix)
    {
    return str[ix];
    }

    // return plural version of word if ctr isn't 1
    string make_plural(size_t ctr, const string &word,
    const string &ending)
    {
    return (ctr == 1) ? word : word + ending;
    }

    // find longer of two strings
    const string &shorterString(const string &s1, const string &s2)
    {
    return s1.size() < s2.size() ? s1 : s2;
    }
    // Disaster: Function returns a reference to a local object
    const string &manip(const string& s)
    {
    string ret = s;
    // transform ret in some way
    return ret; // Wrong: Returning reference to a local object!
    }

    // Determine whether two strings are equal.
    // If they differ in size, determine whether the smaller
    // one holds the same characters as the larger one
    bool str_subrange(const string &str1, const string &str2)
    {
    // same sizes: return normal equality test
    if (str1.size() == str2.size())
    return str1 == str2; // ok, == returns bool
    // find size of smaller string
    string::size_type size = (str1.size() < str2.size())
    ? str1.size() : str2.size();
    string::size_type i = 0;
    // look at each element up to size of smaller string
    while (i != size) {
    if (str1[i] != str2[i])
    return false; // error: no return value
    }
    // error: control might flow off the end of the function without a return
    // the compiler is unlikely to detect this error
    }

    void print(int a, int b, string mark)
    {
    cout << mark
    << " a=" << a
    << " b=" << b << endl;
    }
    // ok: swap acts on references to its arguments
    void swap(int &v1, int &v2)
    {
    // if values already the same, no need to swap, just return
    if (v1 == v2)
    return;
    // ok, have work to do
    int tmp = v2;
    v2 = v1;
    v1 = tmp;
    // no explicit return necessary
    }
    void do_swap(int &v1, int &v2)
    {
    int tmp = v2;
    v2 = v1;
    v1 = tmp;
    // ok: void function doesn't need an explicit return
    }
    void swap2(int &v1, int &v2)
    {
    if (v1 == v2)
    //return false; // error: void function cannot return a value
    return;
    return do_swap(v1, v2); // ok: returns call to a void function

    }

Section 7.4 Function Declarations



  1. Just as variables must be declared before they are used, a function must be declared before it is called. As with a variable definition (Section 2.3.5, p. 52), we can declare a function separately from its definition; a function may be defined only once but may be declared multiple times.
  2. A function declaration consists of a return type, the function name, and parameter list. The parameter list must contain the types of the parameters but need not name them. These three elements are referred to as the function prototype. A function prototype describes the interface of the function
  3. Function prototypes provide the interface between the programmer who defines the function and programmers who use it. When we use a function, we program to the function's prototype.
  4. Parameter names in a function declaration are ignored. If a name is given in a declaration, it should serve as a documentation aid:
    void print(int *array, int size);

  5. Function Declarations Go in Header Files

    1. Recall that variables are declared in header files (Section 2.9, p. 67) and defined in source files. For the same reasons, functions should be declared in header files and defined in source files
    2. It may be temptingand would be legalto put a function declaration directly in each source file that uses the function. The problem with this approach is that it is tedious and error-prone. By putting function declarations into header files, we can ensure that all the declarations for a given function agree. If the interface to the function changes, only one declaration must be changed.
    3. The source file that defines the function should include the header that declares the function.
    4. Including the header that contains a function's declaration in the same file that defines the function lets the compiler check that the definition and declaration are the same. In particular, if the definition and declaration agree as to parameter list but differ as to return type, the compiler will issue a warning or error message indicating the discrepancy

  6. 7.4.1. Default Arguments

    1. A default argument is a value that, although not universally applicable, is the argument value that is expected to be used most of the time. When we call the function, we may omit any argument that has a default. The compiler will supply the default value for any argument we omit
    2. A default argument is specified by providing an explicit initializer for the parameter in the parameter list. We may define defaults for one or more parameters. However, if a parameter has a default argument, all the parameters that follow it must also have default arguments.
    3. A function that provides a default argument for a parameter can be invoked with or without an argument for this parameter. If an argument is provided, it overrides the default argument value; otherwise, the default argument is used
    4. Because char is an integral type (Section 2.1.1, p. 34), it is legal to pass a char to an int parameter and vice versa. This fact can lead to various kinds of confusion, one of which arises in functions that take both char and int parametersit can be easy for callers to pass the arguments in the wrong order. Using default arguments can compound this problem
    5. Part of the work of designing a function with default arguments is ordering the parameters so that those least likely to use a default value appear first and those most likely to use a default appear last
    6. Default Argument Initializers

      1. A default argument can be any expression of an appropriate type

    7. Constraints on Specifying Default Arguments

      1. We can specify default argument(s) in either the function definition or declaration. However, a parameter can have its default argument specified only once in a file.
      2. Default arguments ordinarily should be specified with the declaration for the function and placed in an appropriate header
      3. If a default argument is provided in the parameter list of a function definition, the default argument is available only for function calls in the source file that contains the function definition.

    // EXE-7.4.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>

    using namespace std;

    string::size_type screenHeight();
    string::size_type screenWidth(string::size_type);
    char screenDefault(char = ' ');
    string screenInit(
    string::size_type height = screenHeight(),
    string::size_type width = screenWidth(screenHeight()),
    char background = screenDefault());

    //string screenInit(string::size_type height = 24,
    // string::size_type width = 80,
    // char background = ' ' );


    int main(int argc, _TCHAR* argv[])
    {
    string screen;
    screen = screenInit(); // equivalent to screenInit (24,80,' ')
    screen = screenInit(66); // equivalent to screenInit (66,80,' ')
    screen = screenInit(66, 256); // screenInit(66,256,' ')
    screen = screenInit(66, 256, '#');

    return 0;
    }
    string::size_type screenHeight()
    {
    string::size_type height(100);
    return height;
    }
    string::size_type screenWidth(string::size_type width)
    {
    return width;
    }

    char screenDefault(char input)
    {
    return 'a';
    }
    string screenInit(string::size_type height ,
    string::size_type width ,
    char background )
    {
    return "";
    }

Section 7.5 Local Objects



  1. In C++, names have scope, and objects have lifetimes. To understand how functions operate, it is important to understand both of these concepts. The scope of a name is the part of the program's text in which that name is known. The lifetime of an object is the time during the program's execution that the object exists.
  2. The names of parameters and variables defined within a function are in the scope of the function: The names are visible only within the function body. As usual, a variable's name can be used from the point at which it is declared or defined until the end of the enclosing scope.
  3. 7.5.1. Automatic Objects

    1. By default, the lifetime of a local variable is limited to the duration of a single execution of the function. Objects that exist only while a function is executing are known as automatic objects. Automatic objects are created and destroyed on each call to a function.
    2. The automatic object corresponding to a local variable is created when the function control path passes through the variable's definition. If the definition contains an initializer, then the object is given an initial value each time the object is created. Uninitialized local variables of built-in type have undefined values. When the function terminates, the automatic objects are destroyed.
    3. Parameters are automatic objects. The storage in which the parameters reside is created when the function is called and is freed when the function terminates.
    4. Automatic objects, including parameters, are destroyed at the end of the block in which they were defined. Parameters are defined in the function's block and so are destroyed when the function terminates. When a function exits, its local storage is deallocated. After the function exits, the values of its automatic objects and parameters are no longer accessible.

  4. 7.5.2. Static Local Objects

    1. A static local object is guaranteed to be initialized no later than the first time that program execution passes through the object's definition. Once it is created, it is not destroyed until the program terminates; local statics are not destroyed when the function ends. Local statics continue to exist and hold their value across calls to the function
    // EXE-7.5.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>

    using namespace std;

    size_t count_calls()
    {
    static size_t ctr = 0; // value will persist across calls
    return ++ctr;
    }
    int main()
    {
    for (size_t i = 0; i != 10; ++i)
    cout << count_calls() << endl;

    int i;
    cin >> i;
    return 0;
    }

Section 7.6 Inline Functions



    The benefits of defining a function for such a small operation include:



    • It is easier to read and understand a call to shorterString than it would be to read and interpret an expression that used the equivalent conditional expression in place of the function call.



    • If a change needs to be made, it is easier to change the function than to find and change every occurrence of the equivalent expression.



    • Using a function ensures uniform behavior. Each test is guaranteed to be implemented in the same manner.



    • The function can be reused rather than rewritten for other applications


  1. There is, however, one potential drawback to making shorterString a function: Calling a function is slower than evaluating the equivalent expression. On most machines, a function call does a lot of work: registers are saved before the call and restored after the return; the arguments are copied; and the program branches to a new location
  2. inline Functions Avoid Function Call Overhead

    1. A function specified as inline (usually) is expanded "in line" at each point in the program in which it is invoked.
    2. The inline specification is only a request to the compiler. The compiler may choose to ignore this request
    3. 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 1,200-line function is also not likely to be explanded inline.

  3. Put inline Functions in Header Files

    1. Unlike other function definitions, inlines should be defined in header files.
    2. To expand the code of an inline function at the point of call, the compiler must have access to the function definition. The function prototype is insufficient.
    3. An inline function may be defined more than once in a program as long as the definition appears only once in a given source file and the definition is exactly the same in each source file. By putting inline functions in headers, we ensure that the same definition is used whenever the function is called and that the compiler has the function definition available at the point of call.
    4. Whenever an inline function is added to or changed in a header file, every source file that uses that header must be recompiled
    // EXE-7.6.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>

    using namespace std;;

    inline const string &
    shorterString(const string &s1, const string &s2);

    int main(int argc, _TCHAR* argv[])
    {
    cout << "Please input two strings:\n";
    string s1,s2;
    cin >> s1 >> s2;

    cout << shorterString(s1, s2) << endl;

    int i;
    cin >> i;
    return 0;
    }
    // inline version: find longer of two strings
    inline const string &
    shorterString(const string &s1, const string &s2)
    {
    return s1.size() < s2.size() ? s1 : s2;
    }

Section 7.7 Class Member Functions



  1. 7.7.1. Defining the Body of a Member Function

    1. The function prototype must be defined within the class body. The body of the function, however, may be defined within the class itself or outside the class body.
    2. Members that are functions must be defined as well as declared. We can define a member function either inside or outside of the class definition.
    3. A member function that is defined inside the class is implicitly treated as an inline function
    4. A member function may access the private members of its class.
    5. Member Functions Have an Extra, Implicit Parameter

      1. When we call a member function, we do so on behalf of an object

    6. Introducing this

      1. Each member function (except for static member functions, which we cover in Section 12.6 (p. 467)) has an extra, implicit parameter named this. When a member function is called, the this parameter is initialized with the address of the object on which the function was invoked.

    7. Introducing const Member Functions

      1. That const modifies the type of the implicit this parameter. When we call total.same_isbn(trans), the implicit this parameter will be a const Sales_Item* that points to total. It is as if the body of same_isbn were written as
      2. A function that uses const in this way is called a const member function. Because this is a pointer to const, a const member function cannot change the object on whose behalf the function is called.
         // pseudo-code illustration of how the implicit this pointer is used
        // This code is illegal: We may not explicitly define the this pointer ourselves
        // Note that this is a pointer to const because same_isbn is a const member
        bool Sales_item::same_isbn(const Sales_item *const this,
        const Sales_item &rhs) const
        { return (this->isbn == rhs.isbn); }

      3. A const object or a pointer or reference to a const object may be used to call only const member functions. It is an error to try to call a nonconst member function on a const object or through a pointer or reference to a const object

    8. Using the this Pointer

      1. Inside a member function, we need not explicitly use the this pointer to access the members of the object on which the function was called. Any unqualified reference to a member of our class is assumed to be a reference through this


  2. 7.7.2. Defining a Member Function Outside the Class

    1. Member functions defined outside the class definition must indicate that they are members of the class

  3. 7.7.3. Writing the Sales_item Constructor

    1. Constructors Are Special Member Functions

      1. Unlike other member functions, constructors have no return type. Like other member functions they take a (possibly empty) parameter list and have a function body.
      2. A class can have multiple constructors. Each constructor must differ from the others in the number or types of its parameters
      3. Constructors usually should ensure that every data member is initialized.
      4. the default constructor, which is the one that takes no arguments.The default constructor says what happens when we define an object but do not supply an (explicit) initializer:

    2. Defining a Constructor

      1. Like any other member function, a constructor is declared inside the class and may be defined there or outside the class.

    3. Constructor Initialization List

      1. The colon and the following text up to the open curly is the constructor initializer list. A constructor initializer list specifies initial values for one or more data members of the class. It follows the constructor parameter list and begins with a colon. The constructor initializer is a list of member names, each of which is followed by that member's initial value in parentheses. Multiple member initializations are separated by commas.

    4. Synthesized Default Constructor

      1. If we do not explicitly define any constructors, then the compiler will generate the default constructor for us
      2. The compiler-created default constructor is known as a synthesized default constructor. It initializes each member using the same rules as are applied for variable initializations (Section 2.3.4, p. 50).
      3. The initial value of members of built-in type depend on how the object is defined. If the object is defined at global scope (outside any function) or is a local static object, then these members will be initialized to 0. If the object is defined at local scope, these members are uninitialized. As usual, using an uninitialized member for any purpose other than giving it a value is undefined.
      4. The synthesized default constructor often suffices for classes that contain only members of class type. Classes with members of built-in or compound type should usually define their own default constructors to initialize those members.

  4. 7.7.4. Organizing Class Code Files

    1. As we saw in Section 2.9 (p. 67), class declarations ordinarily are placed in headers. Usually, member functions defined outside the class are put in ordinary source files. C++ programmers tend to use a simple naming convention for headers and the associated class definition code. The class definition is put in a file named type .h or type .H, where type is the name of the class defined in the file. Member function definitions usually are stored in a source file whose name is the name of the class.


Section 7.8 Overloaded Functions



  1. Two functions that appear in the same scope are overloaded if they have the same name but have different parameter lists.
  2. 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.
  3. The compiler uses the argument type(s) passed in the call to figure out which function to call.
  4. There may be only one instance of main in any program. The main function may not be overloaded.

    1. Distinguishing Overloading from Redeclaring a Function

      1. Functions cannot be overloaded based only on differences in the return type.
      2. 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









        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& 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:

             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?

             // which is easier to understand?
        myScreen.home(); // we think this one!
        myScreen.move();



  5. 7.8.1. Overloading and Scope

    1. 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. 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
    3. In general, it is a bad idea to declare a function locally. Function declarations should go in header files
    4. In C++ name lookup happens before type checking.

  6. 7.8.2. Function Matching and Argument Conversions

    1. 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



  7. 7.8.3. The Three Steps in Overload Resolution

    1. Candidate Functions、

      1. 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.

    2. Determining the Viable Functions

      1. 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
      2. 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
      3. If there are no viable functions, then the call is in error

    3. Finding the Best Match, If Any

      1. 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

    4. Overload Resolution with Multiple Parameters

      1. 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
      2. 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


  8. 7.8.4. Argument-Type Conversions

    1. 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).



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



        4. Match through a class-type conversion. (Section 14.9 (p. 535) covers these conversions.)


    2. 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.
    3. Matches Requiring Promotion or Conversion

      1. 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
      2. One important point to realize is that the small integral types promote to int
      3. A conversion that is done through a promotion is preferred to another standard conversion

    4. Parameter Matching and Enumerations

      1. 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
      2. Although we cannot pass an integral value to a enum parameter, we can pass an enum to a parameter of integral type
      3. 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

    5. Overloading and const Parameters

      1. Whether a parameter is const only matters when the parameter is a reference or pointer.
      2. We can overload a function based on whether a reference parameter refers to a const or nonconst type
      3. It is worth noting that we cannot overload based on whether the pointer itself is const
      // EXE-7.8.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>

      using namespace std;

      void f();
      void f(int);
      void f(int, int);
      void f(double, double = 3.14);
      enum Tokens {INLINE = 128, VIRTUAL = 129};
      void ff(Tokens);
      void ff(int);

      int main(int argc, _TCHAR* argv[])
      {
      f(5.6);
      //f(42, 2.56);
      f(static_cast<double>(42), 2.56); // calls f(double, double)
      f(42, static_cast<int>(2.56)); // calls f(int, int)

      Tokens curTok = INLINE;
      ff(128); // exactly matches ff(int)
      ff(INLINE); // exactly matches ff(Tokens)
      ff(curTok); // exactly matches ff(Tokens)

      int i;
      cin >> i;
      return 0;
      }

      void ff(Tokens t)
      {
      cout << "void ff(Tokens t) called" << endl;
      }

      void ff(int t)
      {
      cout << "void ff(int) called" << endl;
      }


      void f()
      {
      cout << "void f() called" << endl;
      }
      void f(int input)
      {
      //cout << input << endl;
      cout << "void f(int input) called" << endl;
      }
      void f(int a , int b)
      {
      //cout << a << " " << b << endl;
      cout << "void f(int a , int b) called" << endl;
      }
      void f(double a, double b)
      {
      //cout << a << " " << b << endl;
      cout << "void f(double a, double b) called" << endl;
      }

Section 7.9 Pointers to Functions



  1. A function pointer is just thata pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type. A function's type is determined by its return type and its parameter list. A function's name is not part of its type
    // pf points to function returning bool that takes two const string references
    bool (*pf)(const string &, const string &);
    // declares a function named pf that returns a bool*
    bool *pf(const string &, const string &);

  2. The parentheses around *pf are necessary
  3. Using Typedefs to Simplify Function Pointer Definitions

    1. Function pointer types can quickly become unwieldy. We can make function pointers easier to use by defining a synonym for the pointer type using a typedef
      typedef bool (*cmpFcn)(const string &, const string &);

    2. This definition says that cmpFcn is the name of a type that is a pointer to function. That pointer has the type "pointer to a function that returns a bool and takes two references to const string."

  4. Initializing and Assigning Pointers to Functions

    1. When we use a function name without calling it, the name is automatically treated as a pointer to a function.
           // compares lengths of two strings
      bool lengthCompare(const string &, const string &);

      any use of lengthCompare, except as the left-hand operand of a function call, is treated as a pointer whose type is

           bool (*)(const string &, const string &);

    2. Using the function name is equivalent to applying the address-of operator to the function name:
        cmpFcn pf1 = lengthCompare;
        cmpFcn pf2 = &lengthCompare;


    3. A function pointer may be initialized or assigned only by a function or function pointer that has the same type or by a zero-valued constant expression.
    4. Initializing a function pointer to zero indicates that the pointer does not point to any function.
    5. There is no conversion between one pointer to function type and another
    6. A pointer to a function can be used to call the function to which it refers. We can use the pointer directlythere is no need to use the dereference operator to call the function
    7. If the pointer to function is uninitialized or has a value of zero, it may not be used in a call. Only pointers that have been initialized or assigned to refer to a function can be safely used to call a function

  5. Calling a Function through a Pointer


  6. Function Pointer Parameters

    1. A function parameter can be a pointer to function. We can write such a parameter in one of two ways:

  7. Returning a Pointer to Function

    1. A function can return a pointer to function, although correctly writing the return type can be a challenge:
    2. The best way to read function pointer declarations is from the inside out, starting with the name being declared
      // ff is a function taking an int and returning a function pointer
      // the function pointed to returns an int and takes an int* and an int
      int (*ff(int))(int*, int);
      We can figure out what this declaration means by observing that
      ff(int)
      says that ff is a function taking one parameter of type int. This function returns
      int (*)(int*, int);
      a pointer to a function that returns an int and takes two parameters of type int* and an int.

    3. Typedefs can make such declarations considerably easier to read

      1. // PF is a pointer to a function returning an int, taking an int* and an int
             typedef int (*PF)(int*, int);
             PF ff(int);  // ff returns a pointer to function

    4. We can define a parameter as a function type. A function return type must be a pointer to function; it cannot be a function
    5. An argument to a parameter that has a function type is automatically converted to the corresponding pointer to function type. The same conversion does not happen when returning a function

  8. Pointers to Overloaded Functions

    1. It is possible to use a function pointer to refer to an overloaded function:
    // EXE-7.9.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <vector>

    using namespace std;

    // pf points to function returning bool that takes two const string references
    bool (*pf)(const string &, const string &);
    typedef bool (*cmpFcn)(const string &, const string &);
    // compares lengths of two strings
    bool lengthCompare(const string &, const string &);
    string::size_type sumLength(const string&, const string&);
    bool cstringCompare(char*, char*);

    /* useBigger function's third parameter is a pointer to function
    * that function returns a bool and takes two const string references
    * two ways to specify that parameter:
    */

    // third parameter is a function type and is automatically treated as a pointer to function
    void useBigger(const string &, const string &,
    bool(const string &, const string &));
    // equivalent declaration: explicitly define the parameter as a pointer to function
    //void useBigger(const string &, const string &,
    // bool (*)(const string &, const string &));

    void ff(vector<double>);
    void ff(unsigned int);

    int main(int argc, _TCHAR* argv[])
    {
    cmpFcn pf1 = 0; // ok: unbound pointer to function
    cmpFcn pf2 = lengthCompare; // ok: pointer type matches function's type
    pf1 = lengthCompare; // ok: pointer type matches function's type
    pf2 = pf1; // ok: pointer types match

    cmpFcn pf3 = lengthCompare;
    cmpFcn pf4 = &lengthCompare;

    // pointer to function returning bool taking two const string&
    cmpFcn pf5;
    //pf5 = sumLength; // error: return type differs
    //pf5 = cstringCompare; // error: parameter types differ
    pf5 = lengthCompare; // ok: function and pointer types match exactly

    cmpFcn pf = lengthCompare;
    lengthCompare("hi", "bye"); // direct call
    pf("hi", "bye"); // equivalent call: pf1 implicitly dereferenced
    (*pf)("hi", "bye"); // equivalent call: pf1 explicitly dereferenced


    // which function does pf1 refer to?
    void (*pf9)(unsigned int) = &ff; // ff(unsigned)
    pf9(20);
    (*pf9)(21);
    // error: no match: invalid parameter list
    //void (*pf10)(int) = &ff;

    // error: no match: invalid return type
    //double (*pf10)(vector<double>) ;
    //void (*pf11)(vector<double>) = &ff;
    void (*pf11)(vector<double>);
    pf11 = ff;
    vector<double> vect ;
    vect.push_back(2.0);
    (*pf11)(vect);
    pf11(vect);

    int i;
    cin >> i;
    return 0;
    }

    void useBigger(const string &a, const string &b,bool f(const string &, const string &))
    {
    cout << "void useBigger(const string &a, const string &b,bool f(const string &, const string &)) called" << endl;
    }

    bool lengthCompare(const string & a, const string & b)
    {
    cout << "bool lengthCompare(const string & a, const string & b) called" << endl;
    return 0;
    }
    string::size_type sumLength(const string& a, const string& b)
    {
    cout << "string::size_type sumLength(const string& a, const string& b) called" << endl;
    return 0;
    }
    bool cstringCompare(char* a, char* b)
    {
    cout << "bool cstringCompare(char*, char*) called" << endl;
    return 0;
    }

    void ff(vector<double> a)
    {
    cout << "void ff(vector<double> a) called" << endl;
    }
    void ff(unsigned int a)
    {
    cout << "void ff(unsigned int a) called" << endl;
    }

No comments: