Essential C++ 2_Procedural Programming

2.1 How to Write a Function

    Before a function can be called within our program, it must be declared. A function declaration allows the compiler to verify the correctness of its use � whether there are enough parameters, whether they are of the correct type, and so on. The function declaration specifies the return type, the name, and the parameter list but not the function body. This is called thefunction prototype.

// a declaration of our function 
int fibon_elem( int pos );

    A more reasonable choice is to change our return value to indicate whether fibon_elem() is able to calculate the value:

// revised function prototype 
bool fibon_elem( int pos, int &elem ); 

    A function can return only one value. In this case, the value returned is either true or false based on whether fibon_elem() can calculate the element's value. That leaves us with the problem of returning the element's actual value.In our revised function prototype, we solve that problem by adding a second parameter of type reference to int.     This allows us in effect to have  two values returned from the function.

Here is the final implementation of fibon_elem():

bool fibon_elem( int pos, int &elem ) 
{ 
   // check if invalid position ... 
   if ( pos <= 0 || pos > 1024 ) 
      { elem = 0; return false; } 

   // elem is 1 for positions 1 and 2 
   elem = 1; 
   int n_2 = 1, n_1 = 1; 

   for ( int ix = 3; ix <= pos; ++ix ) 
   { 
         elem = n_2 + n_1; 
         n_2 = n_1; n_1 = elem; 
   } 

   return true; 
} 
The following small program exercises fibon_elem():

#include <iostream> 
using namespace std; 

// forward declaration of fibon_elem() 
// makes function known to compiler ... 
bool fibon_elem( int, int& ); 

int main() 
{ 
   int pos; 
   cout << "Please enter a position: "; 
   cin >> pos; 

   int elem; 
   if ( fibon_elem( pos, elem )) 
        cout << "element # " << pos 
             << " is " << elem << endl; 
   else cout << "Sorry. Could not calculate element # " 
             << pos << endl; 
 } 
    In our example, the declaration of fibon_elem() does not provide names for its two parameters. This is OK. The name of a parameter is necessary only if we need access to the parameter within the function.


2.2 Invoking a Function

    In this section we implement a function to sort a vector of integer values so that we can explore the behavior ofpassing parameters both by reference and by value. The sorting algorithm is a simple bubble sort implemented by two nested for loops. 

void display( vector<int> vec ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
         cout << vec[ix] << ' '; 
   cout << endl; 
} 

void swap( int val1, int val2 )
{ 
   int temp = val1; 
   val1 = val2; 
   val2 = temp; 
}
void bubble_sort( vector<int> vec ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
         for ( int jx = ix+1; jx < vec.size(); ++jx ) 
               if ( vec[ ix ] > vec[ jx ] ) 
                    swap( vec[ix], vec[jx] ); 
} 

int main() 
{ 
   int ia[ 8 ] = { 8, 34, 3, 13, 1, 21, 5, 2 }; 
   vector<int> vec( ia, ia+8 );

   cout << "vector before sort: "; 
   display( vec ); 

   bubble_sort( vec ); 

   cout << "vector after sort:  "; 
   display( vec ); 
} 

    When this program is compiled and executed, the following output is generated, showing that the vector defined within main() is not sorted:

vector before sort: 8 34 3 13 1 21 5 2 
vector after sort:  8 34 3 13 1 21 5 2 
     It explains why even though we swap the values, the change is not reflected in the vector. In effect, the objects passed to swap() are copied, and there is no relationship between the two pairs of objects.  

    When we invoke a function, a special area of memory is set up on what is called theprogram stack. Within this special area of memory there is space to hold the value of each function parameter. (It also holds the memory associated with each object defined within the function � we call these local objects.) When the function completes, this area of memory is discarded. (We say that it is popped from the program stack.)

    By default, when we pass an object to a function, such as vec[ix], its value is copied to the local definition of the parameter. (This is called pass by value semantics.) There is no connection between the objects manipulated withinswap() and the objects passed to it within bubble_sort(). That is why our program fails.

    For our program to work, we must somehow bind the swap() parameters to the actual objects being passed in. (This is calledpass by reference semantics.) The simplest way of doing this is to declare the parameters as references:

/* 
 * OK: by declaring val1 and val2 as references 
 *     changes to the two parameters within swap() 
 *     are reflected in the objects passed to swap() 
 */ 
void swap( int & val1, int & val2 ) 
{ 
    /* 
     * note that our code within swap() 
     * does not change -- only the relationship 
     * between the parameters of swap() and the 
     * objects passed to swap() changes 
     */ 
    int temp = val1; 
    val1 = val2; 
    val2 = temp; 
} 

    Changing vec to be a reference is the final correction to our program:

void bubble_sort( vector<int> &vec ){ /* ... */ } 

To confirm that, let's recompile and execute:

vector before sort: 8 34 3 13 1 21 5 2 
vector after sort:  1 2 3 5 8 13 21 34 

Pass by Reference Semantics

    A reference serves as an indirect handle to an object. We declare a reference by sandwiching an ampersand (&) between the type's name and the name of the reference:

int ival = 1024;  // an object of type int 
int *pi  = &ival; // a pointer to an object of type int 
int &rval = ival; // a reference to an object of type int

    When we write

int jval = 4096; 
rval = ival; 

    we assign ival, the object rval refers to, the value stored byjval. We do not causerval to now refer tojval. A reference cannot be reassigned to refer to another object. When we write

pi = &rval; 

    we assign pi the address of ival, the objectrval refers to. We do not causepi to point torval. All manipulation of a reference acts on the object the reference refers to. This is also true when the reference is a function parameter.

    When we assign val1 with val2 within swap(),

void swap( int &val1, int &val2 ) 
{ 
    // the actual arguments are modified ... 
    int temp = val1; 
    val1 = val2; 
    val2 = temp; 
} 

    we are really assigning vec[ix] with vec[jx], which are the two objectsval1 andval2 refer to in the call ofswap() withinbubble_sort():

swap( vec[ix], vec[jx] ); 

    Similarly, when val2 is assigned with temp withinswap(), we are really assigningvec[jx] with the original value ofvec[ix].

    When an object is passed to a reference parameter, the object's value is not copied. Rather, the address of the object being passed is copied. Each access of the reference parameter within the function is an indirect manipulation of the object passed in.

One reason to declare a parameter as a reference is to allow us to modify directly the actual object being passed to the function. This is important because, as we've seen, our program otherwise may behave incorrectly.

    A second reason to declare a parameter as a reference is to eliminate the overhead of copying a large object. This is a less important reason. Our program is correct; it is simply less efficient.

    For example, we currently pass our vector to display() by value. This means that we copy the entire vector object each time we wish to display it. This is not wrong. The output generated is exactly what we want. It is simply faster to pass the address of the vector. Again, one way to do that is to declare the vector parameter to be a reference:

void display( const vector<int> &vec ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
         cout << vec[ix] << ' '; 
   cout << endl; 
}

    We declare it to be a reference to const vector because we do not modify it within the body of the function. It is not an error to omit theconst. Having theconst there informs readers of the program that we are passing the vector by reference to prevent copying it rather than to modify it within the function.

    If we wish, we can pass our vector as a pointer parameter. The effect is the same: We pass the address of the object into the function rather than make a copy of the entire object. One difference, of course, is the syntax between a reference and a pointer. For example,

void display( const vector<int> *vec ) 
{ 
   if ( ! vec ){ 
        cout << "display(): the vector pointer is 0\n"; 
        return; 
   } 
   for ( int ix = 0; ix < vec->size(); ++ix ) 
         cout << (*vec)[ix] << ' '; 
   cout << endl; 
} 

int main() 
{ 
   int ia[ 8 ] = { 8, 34, 3, 13, 1, 21, 5, 2 }; 
   vector<int> vec( ia, ia+8 ); 

   cout << "vector before sort: "; 
   display( &vec ); // pass the address now 

   // ... 
}

    A more important difference between a pointer and a reference parameter is that a pointer may or may not actually address an object. Before we dereference a pointer, we must always make sure that it is not set to 0. A reference, however, always refers to some object, so the check for 0 is unnecessary.

In general, unless you wish to modify the parameter within the function, as we did withfibon_elem() in the preceding section,

bool fibon_elem( int pos, int &elem ); 

     I recommendnot passing built-in types by reference. The reference mechanism is primarily intended to support the passing of class objects as parameters to functions.

Scope and Extent

Recall that a function is temporarily placed on the program stack in a special area of memory for the extent of its execution.Local objects are stored in this area of memory. When the function completes, this area of memory is discarded. The local objects no longer exist. Addressing a nonexisting object in general is a bad programming idiom. 

    The region of the program over which an object is active is called itsscope. We say thatsize andelems havelocal scope within the functionfibon_seq(). The name of an object that has local scope is not visible outside its local scope.

    An object declared outside a function has file scope. An object that has file scope is visible from the point of its declaration to the end of the file within which it is named. An object at file scope hasstatic extent. This means that its memory is allocated before the beginning ofmain() and remains allocated until the program is terminated.      An object of a built-in type defined at file scope is always initialized to 0. An object of a built-in type defined at local scope, however, is left uninitialized unless explicitly provided with an initial value by the programmer.

Dynamic Memory Management

    Both local and file extent are managed for us automatically. There is a third form of storage duration calleddynamic extent. This memory comes from the program'sfree store and is sometimes calledheap memory. This memory must be managed explicitly by the programmer. Memory allocation is done using thenew expression, whereas memory deallocation is done using thedelete expression.

    The new expression is written this way:

new Type; 

    Here, Type can be any built-in or class type known to the program, or thenew expression can be written as

new Type( initial_value ); 

For example,

int *pi; 
pi = new int; 

    assigns pi the address of an object of type int allocated in heap memory. By default, an object allocated on the heap is uninitialized. The second form of thenew expression allows us to specify an initial value. For example,

pi = new int( 1024 ); 

    also assigns pi the address of an object of type int allocated in heap memory. This object, however, is initialized to a value of 1,024.

To allocate an array of heap elements, we write

int *pia = new int[ 24 ]; 

    This code allocates an array of 24 integer objects on the heap.pia is initialized to address the first element of this array. The array elements themselves are uninitialized. There is no syntax for initializing an array of elements allocated on the heap.

     A heap object is said to have dynamic extent because it is allocated at run-time through use of the new expression and continues to exist until explicitly deallocated through use of thedelete expression. For example, the following delete expression causes the object addressed bypi to be deallocated:

delete pi; 

    To delete an array of objects, we add an empty subscript operator between the pointer addressing the array and the delete expression:

delete [] pia; 

    We do not need to check that pi is nonzero:

if ( pi != 0 ) // unnecessary -- compiler checks for us 
     delete pi; 

     The compiler does this check automatically. If for some reason the programmer does not apply thedelete expression, the heap object is never deallocated. This is called a    memory leak.InChapter 6, we look at why we might choose to use dynamic memory allocation in the design of our programs. We look at ways to prevent memory leaks in our discussion of exception handling inChapter 7.


2.3 Providing Default Parameter Values

    Printing a trace of our bubble sort program to ofil required that I makeofil available to the multiple functions I wished to debug. Let's see how we might revisebubble_sort() to do away with its reliance on the file scope instance ofofil:

void bubble_sort( vector<int> &vec, ofstream &ofil ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
       for ( int jx = ix+1; jx < vec.size(); ++jx ) 
           if ( vec[ ix ] > vec[ jx ] ) 
           { 
              ofil << "about to call swap! ix: " << ix 
                     << " jx: " << jx << "\tswapping: " 
                     << vec[ix] << " with " << vec[ jx ] << endl; 

               swap( vec[ix], vec[jx], ofil ); 
           } 
} 

    Although this technique removes our reliance on the file scope instance ofofil, it introduces a number of potentially vexing problems. Every call tobubble_sort() now requires our user to pass in an ofstream class object. Also, we're generating this information without the user being able to turn it off. In the general case, when things are going well, no one is interested in this information.

    We'd prefer not to bother the user either with having to specify an output stream or with having to turn anything off. By default, we'd like not to generate information. However, we'd like to allow the interested user to generate the information and specify the file into which the information should be stored. How might we do that?

    C++ allows us to associate a default value for all or a subset of parameters. In our case, we provide the ofstream pointer parameter with a default value of 0:

void bubble_sort( vector<int> &vec, ofstream *ofil = 0 ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
       for ( int jx = ix+1; jx < vec.size(); ++jx ) 
           if ( vec[ ix ] > vec[ jx ] ) 
           { 
              if ( ofil != 0 ) 
                     (*ofil)  << "about to call swap! ix: " << ix 
                              << " jx: " << jx << "\tswapping: " 
                             << vec[ix] << " with " << vec[ jx ] << endl; 
                swap( vec[ix], vec[jx], ofil ); 
           } 
} 

    This revised version of bubble_sort() declares its second parameter as a pointer to an ofstream object rather than as a reference.We must make this change to provide a default value of 0, indicating that no ofstream object is addressed. Unlike a pointer, a reference cannot be set to 0. A reference must always refer to some object.

    An invocation of bubble_sort() with a single argument generates no debugging information. An invocation with a second argument that addresses an ofstream object generates the debugging information:

int main() 
{ 
   int ia[ 8 ] = { 8, 34, 3, 13, 1, 21, 5, 2 }; 
   vector<int> vec( ia, ia+8 ); 

   // no debug information --
   // it is as if we invoked bubble_sort( vec, 0 ); 
   bubble_sort( vec ); 
   display( vec ); 

   // ok: debug information generated ... 
   ofstream ofil( "data.txt" ); 
   bubble_sort( vec, &ofil ); 
   display( vec, ofil ); 
} 

    The implementation of display() presents a different situation. Currently, it hard-codes the output tocout. In the general case,cout is fine. In some cases, however, users are likely to prefer to supply an alternative target, such as a file. Our implementation must support both uses ofdisplay() inmain(). Our solution is to makecout the default ostream parameter:

void display( const vector<int> &vec, ostream &os = cout ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
         os << vec[ix] << ' '; 
   os << endl; 
}

    There are two somewhat unintuitive rules about providing default parameters. The first rule is that default values are resolved positionally beginning with the right-most parameter. If a parameter is provided with a default value, all the parameters to its right must also have a default value. The following, for example, is illegal:

// error: no default value for vec 
void display( ostream &os = cout, const vector<int> &vec );

    The second rule is that the default value can be specified only once � either in the declaration or in the definition of the function, but not both. So where should we specify the default value?

     Typically, a function declaration is placed in a header file.This header file is then included by files that wish to use the function. (Recall that we included thecstdlib header file to include the declaration of the exit() library function.) The definition of a function typically is placed in a program text file. This file is compiled once and is linked to our program whenever we wish to use the function. The header file, that is, provides the greater visibility of the function. (Header files are discussed in more detail inSection 2.9.)

    Because of its greater visibility, we place the default value in the function declaration rather than in the function definition.For example, the declaration and definition ofdisplay() generally would look like this:

// header file declaration specifies default value 
// let's call the header file: NumericSeq.h 

void display( const vector<int>&, ostream&=cout ); 

// program text file definition includes header file 
// the definition itself does not specify the default value 

#include "NumericSeq.h" 

void display( const vector<int> &vec, ostream &os ) 
{ 
   for ( int ix = 0; ix < vec.size(); ++ix ) 
         os << vec[ix] << ' '; 
   os << endl; 
} 

2.4 Using Local Static Objects

An alternative solution, in this case, is a local static object. For example,

Note: !!!!!!!

const vector<int>*               //to define this function return a point of vector type 
fibon_seq( int size ) 
{ 
   static vector< int > elems;   //與需要返回的vector類型的指針相呼應
   // do the logic to populate it ... 

   return &elems;                // 返回一個vector類型的指針
}

Note: Contrasting with the next example:

vector<int> fibon_seq( int size ) 
{ 
   if ( size <= 0 || size > 1024 ) 
   { 
        cerr << "Warning: fibon_seq(): " 
             << size << " not supported -- resetting to 8\n"; 

        size = 8; 
   } 

   vector<int> elems( size ); 
   for ( int ix = 0; ix < size; ++ix ) 
         if ( ix == 0 || ix == 1 ) 
              elems[ ix ] =  1; 
         else elems[ ix ] =  elems[ix-1] + elems[ix-2]; 
   return elems;
} 
 elems is now defined as a local static object offibon_seq(). What does this mean? Unlike a nonstatic local object, the memory associated with a static local object persists across function invocations.elems is no longer destroyed and re-created with each invocation offibon_seq(). This is why we can now safely returnelems's address.

    A local static object allows us to define a single vector to hold the elements of the Fibonacci sequence. With each invocation offibon_seq(), we need calculate only those elements that are not as yet inserted intoelems. Here is one possible implementation:

const vector<int>* 
fibon_seq( int size ) 
{ 
   const int max_size = 1024; 
   static vector< int > elems; 

   if ( size <= 0 || size > max_size ){ 
        cerr << "fibon_seq(): oops: invalid size: " 
             << size << " -- can't fulfill request.\n"; 
        return 0; 
   } 

   // if size is equal to or greater than elems.size(), 
   // no calculations are necessary ... 
   for ( int ix = elems.size(); ix < size; ++ix ){ 
        if ( ix == 0 || ix == 1 ) 
             elems.push_back( 1 ); 
        else elems.push_back( elems[ix-1]+elems[ix-2] ); 
   } 
   return &elems; 
} 

    Previously, we have always defined a vector to be of a particular size and have assigned values to existing elements. But in this version offibon_seq(), we have no way of guessing how big a vector we'll need. Rather, we defineelems to be an empty vector and insert elements as we need them.push_back() inserts the value at the back of the vector. The memory to support this is managed automatically by the vector class itself. (InChapter 3 we look in detail at vectors and the other standard library container classes.)

2.5 Declaring a Function Inline

    An inline function represents a request to the compiler toexpand the function at each call point. (The previous method was to use nested functions. ) With an inline function, the compiler replaces the function call with a copy of the code to be executed. In effect, this allows us to benefit from the performance improvement of folding the functions back intofibon_elem() while still maintaining the three functions as independent operations.

We declare a function inline by prefixing its definition with theinline keyword:

// ok: now fibon_elem() is an inline function 
inline bool fibon_elem( int pos, int &elem ) 
       {  /* definition same as above */ } 

    In general, the best candidate functions for inlining, such asfibon_elem() andis_size_ok(), are small, frequently invoked, and not computationally complex.

    The definition of an inline function is usually placed within a header file. For it to be expanded, its definition must be available before its call.

2.6 Providing Overloaded Functions

    Rather than have each function generate its own diagnostic messages, let's provide a generaldisplay_message() function. 

Similarly, our program in Chapter 1 might use it to display its greeting to the user

const string 
      greeting( "Hello. Welcome to Guess the Numeric Sequence" ); 

display_message( greeting ); 
and to display the two elements of the sequence:

const string seq( "The two elements of the sequence are " ); 
display_message( seq, elem1, elem2 ); 
In another instance, we might want to use display_message() simply to output a newline or tab character:

display_message( '\n' ); display_message( '\t' ); 

    Can we really pass parameters to display_message() that differ both in type and number? Yes. How?Through function overloading.

    Two or more functions can be given the same name if the parameter list of each function is unique either by the type or the number of parameters. For example, the following declarations represent the four overloaded instances ofdisplay_message() invoked earlier:

void display_message( char ch ); 
void display_message( const string& ); 
void display_message( const string&, int ); 
void display_message( const string&, int, int );

    How does the compiler know which instance of the four overloaded functions to invoke? It compares the actual arguments supplied to the function invocation against the parameters of each overloaded instance, choosing the best match. This is why the parameter list of each overloaded function must be unique.

    The return type of a function by itself does not distinguish two instances of a function that have the same name. The following, for example, is illegal. It results in a compile-time error:

// error: parameter list not return type must be unique 
ostream& display_message( char ch ); 
bool display_message( char ch ); 

2.7 Defining and Using Template Functions

    Let's say that a colleague of ours has asked for three additionaldisplay_message() instances to handle a vector of integers, a vector of doubles, and a vector of strings:

void display_message( const string&, const vector<int>& ); 
void display_message( const string&, const vector<double>& ); 
void display_message( const string&, const vector<string>& ); 

    On completing the implementation of these three instances, we notice that the function body for each instance is exactly the same. The only difference across these functions is the type of the second parameter:

void display_message( const string&, const vector<string>&, 
                      ostream& = cout ); 
void display_message( const string &msg, const vector<int> &vec ) 
{ 
     cout << msg; 
     for ( int ix = 0; ix < vec.size(); ++ix ) 
           cout << vec[ ix ] << ' '; 
} 

void display_message( const string &msg, const vector<string> &vec ) 
{ 
     cout << msg; 
     for ( int ix = 0; ix < vec.size(); ++ix ) 
           cout << vec[ ix ] << ' '; 
     cout << '\n'; 
} 

void display_message( const string&, const vector<string>&, 
                      ostream& = cout ); 
    There's no reason to think that another colleague won't come along asking for yet another instance that supports a vector of some additional type. It would certainly save us a great deal of effort if we could define a single instance of the function body rather than duplicate the code multiple times and make the necessary minor changes to each instance. To do that, however, we need a facility to bind that single instance to each vector type we wish to display.The function template mechanism provides just this facility.

    A function template factors out the type information of all or a subset of the types specified in its parameter list. In the case ofdisplay_message(), we wish to factor out the type of the element contained within the vector. This allows us to define a single instance of the unchanging part of the function template. 

    A function template begins with the keywordtemplate. It is followed by a list of one or more identifiers that represent the types we wish to defer. The list is set off by a less-than/greater-than bracket pair (<,>). The user supplies the actual type information each time he uses a particular instance of the function. These identifiers in effect serve as placeholders for actual data types within the parameter list and body of the function template. For example,

template <typename elemType> 
void display_message( const string &msg, 
                      const vector<elemType> &vec ) 
{ 
     cout << msg; 
     for ( int ix = 0; ix < vec.size(); ++ix ) 
     { 
           elemType t = vec[ ix ]; 
           cout << t << ' '; 
     } 
} 

    The keyword typename specifies elemType as a type placeholder within the function templatedisplay_message().elemType is an arbitrary name. I could have easily chosenfoobar orT. We must defer the actual type of the vector to be displayed. We do that by placingelemType within the bracket pair following vector.

    What about the first parameter? msg never varies its type with each invocation ofdisplay_message(). It is always a constant reference to a string class object, so there is no need to factor its type. A function template typically has a combination of explicit and deferred type specifiers in its parameter list.

    How do we use a function template? It looks pretty much the same as the use of an ordinary function. For example, when we write

vector< int > ivec; 
string msg; 
// ... 
display_message( msg, ivec ); 

    the compiler binds elemType to type int. An instance ofdisplay_message() is created in which the second parameter is of typevector<int>. Within the function body, the local objectt also becomes an object of typeint


2.8 Pointers to Functions Add Flexibility

    The definition of a pointer to function is complicated. It must specify the return type and parameter list of the function it is addressing. In our case, the parameter list is a singleint, and the return type is const vector<int>*. In addition, the definition must place a* somewhere to indicate that the object being defined is a pointer. Finally, of course, we must give the pointer a name. Let's call itseq_ptr. As usual, our first attempt is almost correct.

const vector<int>* *seq_ptr( int ); // almost correct 

    This code defines seq_ptr as a function that has a parameter list of a single int and has a return type of a pointer to a pointer to aconst vector of elements of type int! To have seq_ptr be recognized as a pointer, we must override the default precedence of* with parentheses:

const vector<int>* (*seq_ptr)( int ); // ok 

seq_ptr can address any function with the same return type and parameter list. This means that it can address each of the six numeric sequence functions. Let's rewritefibon_elem() as the more general seq_elem() as follows:

bool seq_elem( int pos, int &elem, 
               const vector<int>* (*seq_ptr)(int)) 
{ 
    // invoke function addressed by seq_ptr 

    const vector<int> *pseq = seq_ptr( pos ); 
    if ( ! pseq ) 
       {  elem = 0; return false; } 

    elem = (*pseq)[ pos-1 ]; 
    return true; 
}

   or the address of a function. The next question is, how do we access a function's address? It's one of the least complicated operations in C++. We just name it

// assigns seq_ptr the address of pell_seq() 
seq_ptr = pell_seq; 

       What if we wish to setseq_ptr to a different sequence function with each iteration of our display loop without having to name each function explicitly? To solve this problem, we can again resort to indexing into an array. In this case, we define an array of pointers to functions:

// seq_array is an array of pointers to functions 
const vector<int>* (*seq_array[])( int ) = { 
      fibon_seq,  lucas_seq,  pell_seq, 
      triang_seq, square_seq, pent_seq 
};

  seq_array is an array of pointers to functions holding six elements. The first element is the address offibon_seq(), the second, lucas_seq(), and so on. We can set seq_ptr with each iteration of a while loop while the user still wishes to guess another sequence:

int seq_index = 0; 
while ( next_seq == true ) 
{ 
   seq_ptr = seq_array[ ++seq_index ]; 
   // ... 
} 

2.9 Setting Up a head file

    There can be only one definition of a function in a program. However, there can be multiple declarations. We don't put definitions in a header file because the header file is included in multiple text files within a program.

    Objects defined at file scope are also declared in a header file if multiple files may need to access the objects. This is because an object can not be referred to until it has been declared to the program. For example, if seq_array were defined at file scope, we would likely provide a declaration within NumSeq.h. Not unexpectedly, our first try is not quite correct:
// This is not quite right ... 
const int seq_cnt = 6; 
const vector<int>* (*seq_array[seq_cnt])( int ); 

This is not correct because it is interpreted as the definition of seq_array and not as a declaration. Just as with a function, an object can be defined only once in a program. The definition of an object, as well as the definition of a function, must be placed in a program text file. We turn the definition of seq_array into a declaration by prefacing it with the keyword extern:

// OK: this is a declaration 
extern const vector<int>* (*seq_array[seq_cnt])( int ); 

    A const object, like an inline function, is treated as an exception to the rule. The definition of a const object is not visible outside the file it is defined in. This means that we can define it in multiple program files without error.(注意,剛纔的seq_array 並不是一 const object,他是一個 "指向 const object " 的指針)

    if the header file is in the same directory as the program text file including it, we use quotation marks. If it is anywhere else, we use angle brackets. The slightly more technical answer is that if the file name is enclosed by angle brackets, the file is presumed to be a project or standard header file. The search to find it examines a predefined set of locations. If the file name is enclosed by a pair of quotation marks, the file is presumed to be a user-supplied header file. The search to find it begins in the directory in which the including file is located.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章