C++ Primer record_ Chapter VI

Posted by shopphp on Mon, 07 Mar 2022 16:42:28 +0100

Chapter 6 function

  • A function is a named block of code that we call to execute and the corresponding code.
  • A function can have 0 or more parameters and (usually) produce a result.
  • You can overload functions. The same name corresponds to several different functions.

6.1 function basis

  • A typical function definition includes the following parts: return type, function name, list of 0 or more formal parameters, and function body.
// The factorial of Val is Val * (val-l) * (val-2)* ((val -(val -1))* 1)
int fact(int val)
{
    int ret = 1; //Local variable, used to save the calculation results
    while (val > 1)
    {
        ret *= val--; //Assign the product of ret and val to ret, and then subtract Val by 1
    }
    return ret; //Return results
}
  • We can execute functions by calling operators.
int main()
{
    int j = fact(5); // j equals 120, which is the result of fact(5)
    cout << "5! is " << j << endl;
    return 0;
}
5! is 120
  • The function call completes two tasks: one is to initialize the formal parameters corresponding to the function with the actual parameters, and the other is to transfer the control right to the called function.
  • At this time, the execution of the calling function is temporarily interrupted and the called function starts to execute.
  • The argument is the initial value of the formal parameter. The first argument initializes the first parameter, the second argument initializes the second parameter, and so on.
  • Although there is a corresponding relationship between the real participating formal parameters, the evaluation order of the parameters is not specified. The compiler can evaluate the parameters in any feasible order.
fact("hello");   //Error: incorrect argument type
fact();          //Error: insufficient arguments
fact(42, 10, 0); //Error: too many arguments
fact(3.14);      //Correct: this argument can be converted to type int
  • The formal parameter list of a function can be empty, but cannot be omitted.
void f1(){/*...*/}      //Implicitly define an empty formal parameter list
void f2(void){/*...*/}  //Explicitly define an empty formal parameter list
  • The formal parameters in the formal parameter list are usually separated by commas. Each formal parameter is a declaration containing a declarator. Even if the two formal parameters are of the same type, both types must be written out.
int f3(int v1,v2){/*...*/}      //error
int f4(int v1,int v2){/*...*/}  //correct
  • Most types can be used as the return type of a function.
  • A special return type is void, which means that the function does not return any value.

6.1.1 local objects

  • In C + + language, names have scopes and objects have lifecycles.
  • The scope of the name is part of the program text in which the name is visible.
  • The life cycle of an object is the period of time that the object exists during program execution.
  • At the same time, local variables are hidden in all other declarations with the same name in the outer scope.
  • The local static object is initialized when it passes through the object definition statement for the first time on the execution path of the program, and is not destroyed until the program terminates.
#include <iostream>
using namespace std;

size_t count_calls()
{
    static size_t ctr = 0; //After the call, this value is still valid
    return ++ctr;
}
int main()
{
    for (size_t i = 0; i != 10; ++i)
    {
        cout << count_calls() << endl;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10

6.1.2 function declaration

  • The declaration of a function does not contain a function body.
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
  • It is recommended to declare in the header file.

6.1.3 separate compilation

  • As the program becomes more and more complex, separate compilation is needed.
  • The statement is written in h file, defined in cpp file.

6.2 parameter transfer

  • Each time a function is called, its formal parameters are recreated and initialized with the passed in arguments.
  • When the formal parameter is a reference type, we say that its corresponding argument is passed by reference or the function is called by reference.
  • When the value of an argument is copied to a formal parameter, the formal parameter and the argument are two independent objects. We say that such an argument is passed by value or a function is called by value.

6.2.1 value transfer parameters

  • When initializing a variable of non reference type, the initial value is copied to the variable.
  • The mechanism of passing value parameters is exactly the same. All operations of a function on formal parameters will not affect the arguments.
int n = 0; // Initial variable of type int
int i = n; // i is a copy of the value of n
i = 42;    // The value of i changes; The value of n remains unchanged
  • The behavior of the pointer is the same as that of other non reference types. When the pointer copy operation is performed, the value of the pointer is copied, but we can indirectly access the object it refers to.
//This function takes a pointer and sets the value indicated by the pointer to 0
void reset(int *ip)
{
    *ip = 0; //Change the value of the object indicated by the pointer ip
    ip = 0;  //Only the local copy of ip is changed, and the arguments are not changed
}
int i = 42;
reset(&i);                 //Instead of changing the address of i
cout << "i=" << i << endl; //Output i=0

6.2.2 pass reference parameters

  • The operation on the reference actually acts on the object referenced by the reference.
//This function takes a reference to an int object and sets the value of the object to 0
void reset(int &i) // i is another name of the object passed to the reset function
{
    i = 0; //Changed the value of the object referenced by i
}
int j = 42;
reset(j);                  // j is passed by reference, and its value is changed
cout << "j=" << j << endl; //Output j=0
  • Copying large class type objects or container objects is inefficient, and even some class types do not support copy operations at all. Functions can only access objects of this type by referencing formal parameters.
//Compare the length of two string objects
bool isShorter(const string &s1,const string &s2)
{
    return s1.size()<s2.size();
}
  • Use reference parameters to return additional information.
#include <iostream>
using namespace std;

//Returns the location index of the first occurrence of c in s
//The reference parameter occurs is responsible for counting the total number of occurrences of c
string::size_type find_char(const string &s, char c, string::size_type &occurs)
{
    auto ret = s.size(); //Location of first occurrence (if any)
    occurs = 0;          //Sets the value of the formal parameter that represents the number of occurrences
    for (decltype(ret) i = 0; i != s.size(); ++i)
    {
        if (s[i] == c)
        {
            if (ret == s.size())
            {
                ret = i; //Record where c first appears
            }
            ++occurs; //The number of occurrences will be increased by 1
        }
    }
    return ret; //The number of occurrences is implicitly returned by occurs
}

int main()
{
    string s = "openopen";
    string::size_type ctr = 0;
    auto index = find_char(s, 'o', ctr);
    cout << s << endl;
    cout << ctr << endl;
}
openopen
2

6.2.3 const formal parameters and arguments

  • Because when initializing a formal parameter with an argument, the top const will be ignored. In other words, if the formal parameter has a top const, the argument can be passed in const int or int. if the variable formal parameter with the same name and type is overloaded, there will be uncertainty. The above is personal understanding, but I don't understand it in the book.
const int ci = 42; //You can't change ci. const is the top level
int i = ci;        //Correct: when copying ci, its top const is ignored
int *const p = &i; // const is the top level and cannot assign a value to p
*p = 0;            //Correct: it is allowed to change the content of the object through p, and now i becomes 0

void fcn(const int i){ /*fcn i can be read, but i cannot be written*/}
void fcn(int i){ /*...*/} //Error: duplicate definition of fcn(int)
  • The pointer or reference shape participates in const. We can initialize an underlying const object with a non constant quantity, but not vice versa. An ordinary reference must be initialized with an object of the same type.
int i = 42;
const int *cp = &i; //Correct: but cp cannot change i
const int &r = i;   //Correct: but r cannot change i
const int &r2 = 42; //correct:
int *p = cp;        //Error: the type of p does not match the type of cp
int &r3 = r;        //Error: the types of r3 and r do not match
int &r4 = 42;       //Error: cannot initialize a non constant reference with literal value
void reset(int &i) // i is another name of the object passed to the reset function
{
    i = 0;
}
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i) {} //Call the reset function whose formal parameter type is int *
reset(&ci);  //Error: cannot initialize int with pointer to const int object*
reset(i);    //Call the reset function whose formal parameter type is int &
reset(ci);   //Error: unable to bind Kutong reference to const object ci
reset(42);   //Error: cannot bind normal app to literal
reset(str);  //Error: type mismatch, ctr is unsigned
  • Try to use constant references.

6.2.4 array parameters

  • Two special properties of arrays have an impact on our definition and use of functions acting on arrays.
    1. Copying arrays is not allowed.
    2. When using arrays, they are usually converted to pointers.
//Although the forms are different, the three print functions are equivalent
//Each function has a formal parameter of type const int *
//Although the expressions are different, the following three functions are equivalent: the only formal parameter of each function is const int*
void print(const int *);
void print(const int[]);   //It can be seen that the intention of the function is to act on an array
void print(const int[10]); //The dimension here indicates how many elements we expect the array to contain, which is not necessarily true
int i = 0, j[2] = {0, 1};
print(&i); //Correct: & I is of type int*
print(j);  //Correct: convert J to int * and point to j[0]
  • Use the tag to specify the length of the array.
void print(const char *cp)
{
    if (cp) //If cp is not a null pointer
    {
        while (*cp) //As long as the character indicated by the pointer is not an empty character
        {
            cout << *cp++; //Outputs the current character and moves the pointer one position forward
        }
    }
}
  • Standard library specification.
void print(const int *beg, const int *end)
{
    //Output all elements from beg to end (excluding end)
    while (beg != end)
    {
        cout << *beg++ << endl; //Outputs the current element and moves the pointer forward one position
    }
}

int j[2] = {0, 1};
// j is converted to point to its first element
//The second argument is a pointer to the trailing element of j
print(begin(j), end(j)); // begin and end functions
  • Explicitly pass a formal parameter that represents the size of the array.
// const int ia [] is equivalent to const int* ia
// Size indicates the size of the array, which is explicitly passed to the function to control access to ia elements
void print(const int ia[], size_t size)
{
    for (size_t i = 0; i != size; ++i)
    {
        cout << ia[i] << endl;
    }
}
  • Array parameter and const. When the function does not need to write to the array element, the array parameter should be a pointer to const.
  • Array references formal parameters, which can be references to arrays.
//Correct: the formal parameter is a reference to the array, and the dimension is part of the type
void print(int (&arr)[10])
{
    for (auto elem : arr)
    {
        cout << elem << endl;
    }
}

int i = 0, j[2] = {0, 1};
int k[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
print(&i); //Error: argument is not an array of 10 integers
print(j);  //Error: argument is not an array of 10 integers
print(k);  //Correct: the argument is an array of 10 integers
  • When a multidimensional array is passed to a function, what is really passed is a pointer to the first element of the array.
// matrix points to the first element of the array, which is an array of 10 integers
void print(int (*matrix)[10], int rowSize)
{ /*...*/
}
int *matrix[10];   // An array of 10 pointers
int (*matrix)[10]; //Pointer to an array of 10 integers
//Equivalent definition
void print(int matrix[][10], int rowSize)
{ /*...*/
}

6.2.5 main: processing command line options

  • The main function is usually defined with an empty parameter list int main() {}.
  • The main function can accept the argument int main(int argc, char *argv []) {...} Equivalent to int main(int argc, char **argv) {...}.

6.2.6 functions with variable parameters

  • Sometimes we can't predict in advance that we should pass several arguments to the function.
  • In order to write functions that can handle different arguments, the new C++11 standard provides two main methods:
    1. If all argument types are the same, you can pass a parameter named initializer_list.
    2. If the types of arguments are different, you can write a special variable parameter template.
  • initializer_ The list type parameter is a standard library type.
void error_msg(initializer_list<string> il)
{
    for (auto beg = il.begin(); beg != il.end(); ++beg)
    {
        cout << *beg << " ";
    }
    cout << endl;
}

//expected and actual are string objects
if (expected!=actual)
{
    error_msg({"functionX";expected,actual});
}
else
{
    error_msg({"functionX","okay"});
}
  • With initializer_ Functions with list type parameters can also have other parameters at the same time.
void error_msg(ErrCode e, initializer_list<string> il)
{
    cout << e.msg() << ": ";
    for (const auto &elem : il)
    {
        cout << elem << " ";
    }
    cout << endl;
}

// expected and actual are string objects
if (expected != actual)
{
    error_msg(ErrCode(42),{"functionX";expected,actual });
}
else
{
    error_msg(ErrCode(0),{"functionX", "okay"});
}
  • The ellipsis parameter is set to facilitate C + + programs to access some special C code.
void foo(parm_list,...);
void foo(...);

6.3 return type and return statement

  • return statementterminates the currently executing function and returns control to the place where the function was called.
  • There are two forms:
    1. return;
    2. return expression;

6.3.1 function without return value

  • A return statement without a return value can only be used in a function whose return type is void.
void swap(int &v1, int &v2)
{
    //If the two values are equal, you do not need to exchange and exit directly
    if (v1 == v2)
    {
        return;
    }
    //If the program is executed here, it indicates that some functions still need to be completed
    int tmp = v2;
    v2 = v1;
    v1 = tmp;
    //There is no need to display the return statement here
}

6.3.2 function with return value

  • The second form of return statement provides the result of the function. As long as the return type of the function is not void, each return statement in the function must return a value.
//This code cannot be compiled because it contains an incorrect return value
bool str_subrange(const string &str1, const string &str2)
{
    //Same size: in this case, the ordinary equality judgment result is used as the return value
    if (str1.size() == str2.size())
    {
        return str1 == str2; //Correct: = = operator returns Boolean value
    }
    //Get the size of the shorter string object
    auto size = (str1.size() < str2.size()) ? str1.size() : str2.size();
    //Check whether the corresponding characters of two string objects are equal, limited to the shorter string length
    for (decltype(size) i = 0; i != count; ++i)
    {
        if (str1[i] != str2[i])
        {
            return; //Error #1: no return value, the compiler will report this error
        }
    }
    //Error #2: the control flow may have ended the function without returning any value
    //The compiler may not be able to detect this error
}
  • The way to return a value is exactly the same as initializing a variable or parameter.
//If the value of ctr is greater than 1, the plural form of word is returned
string make_plural(size_t ctr, const string &word, const string &ending)
{
    return (str > 1) ? word + ending : word;
}
* Like other reference types, if a function returns a reference, the reference is only an alias of the object it refers to.
//Pick out the shorter one of the two string objects and return its reference
const string &shorterString(const string &s1, cosnt string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}
  • Do not return references or pointers to local objects.
//This function attempted to return a local error
const string &manip()
{
    string ret;
    //Change ret in some way
    if (!ret.empty())
    {
        return ret; //Error: return reference of local object!
    }
    else
    {
        return "Empty"; //Error: 'Empty' is a local temporary quantity
    }
}
  • Like other operators, the calling operator also has priority and association law.
  • The priority of calling operators is the same as that of point operators and arrow operators, and also conforms to the left combination law.
//Call the size member of the string object, which is returned by the shorterString function
auto sz = shorterString(sl, s2).size();
  • A reference returns an lvalue. Call a function that returns a reference to get an lvalue, and other return types get an lvalue.
char &get_val(string &str, string::size_type ix)
{
    return str[ix]; // get_val assumes that the index value is valid
}

int main()
{
    string s("a value");
    cout << s << endl;   //Output a value
    get_val(s, 0) = 'A'; //Change the value of s[0] to A
    cout << s << endl;   //Output A value
    return 0;
}
  • If the return type is a constant reference, we cannot assign a value to the result of the call.
const string &shorterString(const string &s1, cosnt string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}
shorterString("hi", "bye") = "X"; //Error: return value is a constant
  • The new C++11 standard stipulates that the list initialization return value can return the list of values surrounded by curly braces.
vector<string> process()
{
    //...
    // expected and actual are string objects
    if (expected.empty())
    {
        return {}; //Returns an empty vector object
    }
    else if (expected == actual)
    {
        return {"functionX", "okay"}; //Returns the vector object initialized by the list
    }
    else
    {
        return {"functionX", expected, actual};
    }
}
  • The main function main is allowed to end directly without a return statement. If the control reaches the end of the main function and there is no return statement, the compiler will implicitly insert a return statement that returns 0.
  • If a function calls itself, whether the call is direct or indirect, it is called a recursive function.
//Calculate the factorial of Val, i.e. 1 * 2 * 3* val
int factorial(int val)
{
    if (val > 1)
    {
        return factorial(val - 1) * val;
    }
    return 1;
}

6.3.3 return array pointer

  • Because arrays cannot be copied, functions cannot return arrays. However, functions can return pointers or references to arrays.
typedef int arrT[10]; // arrT is a type alias that represents an array of 10 integers
using arrT = int[10]; // Equivalent declaration of arrT
arrT *func(int i);    // fun returns a pointer to an array containing 10 integers

int arr[10];          // arr is an array of 10 integers
int *p1[10];          // p1 is an array of 10 pointers
int (*p2)[10] = &arr; // p2 is a pointer to an array containing 10 integers
  • The function form of returning array pointer is as follows:
    1. Type indicates the type of the element.
    2. dimension represents the size of the array.
    3. (* function(parameter_list)) must have parentheses at both ends, just as we must have parentheses at both ends when defining p2.
  • func(int i) means that an argument of type int is required when calling func function.
  • (* func(int i)) means that we can dereference the result of the function call.
  • (* func(int i))[10] means that the call to dereference func will get an array of size 10.
  • int (*func(int i))[10] indicates that the elements in the array are of type int.
Type (*function(parameter_list))[dimension]
int (*func(int i))[10];
  • Using the tail representation, any function definition can use tail return, but this form is most effective for functions with complex return types, such as pointers to arrays or references to arrays.
//func accepts an argument of type int and returns a pointer to an array containing 10 integers
auto func(int i) -> int (*)[10];
  • Using decltype, if the searchers know which array the pointer returned by the function will point to, they can declare the return type with keywords.
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
//Returns a pointer to an array containing 5 integers
decltype(odd) *arrPtr(int i)
{
    return (i % 2) ? &odd : &even; //Returns a pointer to an array
}

6.4 function overloading

  • If several functions in the same scope have the same name but different formal parameter lists, we call them overloaded functions.
void print(const char *cp);
void print(const int *beg, const int *end);
void print(const int ia[], size_t size);

int j[2] = {0, 1};
print("Hello World");        //Call print(const char *)
print(j, end(j) - begin(j)); // void print(const int ia[],size_t size);
print(begin(j), end(j));     // void print(const int *beg,const int *end);
  • The main function cannot be overloaded.
  • Overloaded functions make it possible to define a set of functions and call different functions according to different call methods.
Record lookup(const Account &); //Find records by Account
Record lookup(const Phone &);   //Find records by Phone
Record lookup(const Name &);    //Find records by Name
Account acct;
Phone phone;
Record r1 = lookup(acct);  //Call to accept the version of the Account
Record r2 = lookup(phone); //Call to accept Phone version
  • For overloaded functions, they should be different in the number or type of formal parameters.
  • Suppose there are two functions with the same formal parameter list but different return types, then it is wrong.
Record lookup(const Account &);
bool lookup(const Account &); //Error: only the return type is different from the previous function
  • Judge whether the types of two formal parameters are different.
Record lookup(const Account &acct);
Record lookup(const Account &); //The name of the formal parameter is omitted
typedef Phone Telno;
Record lookup(const Phone &);
Record lookup(const Telno &); // Telno and Phone are of the same type
  • Overload and const formal parameters. The top-level const does not affect the object of the incoming function.
Record lookup(Phone);
Record lookup(const Phone); //Repeated declaration of Record lookup(Phone)
Record lookup(Phone *);
Record lookup(Phone *const); //Repeated declaration of Record lookup(Phone *)

//For functions that accept references or pointers, the formal parameters corresponding to the object are different whether it is a constant or a non constant
//Four independent overloaded functions are defined
Record lookup(Account &);       //The function acts on a reference to the Account
Record lookup(const Account &); //The new function acts on constant references
Record lookup(Account *);       //A new function that acts as a pointer to the Account
Record lookup(const Account *); //New function that acts as a pointer to a constant
  • Although function overloading can reduce the burden of naming and remembering functions to some extent, it is best to overload only those operations that are really very similar.
  • const_cast and overload.
const string &shorterString(const string &s1, cosnt string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2)
{
    auto &r = shorterString(const_cast<const string &>(sl), const_cast<const string &>(s2));
    return const_cast<string &>(r);
}
//Why don't you write that?
string &shorterString(string &s1, string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}
  • Function matching means that after defining a set of overloaded functions, we need to call them with reasonable arguments.
  • In this process, we associate the function call with one of a group of overloaded functions. Function matching is also called overload determination.
  • When an overloaded function is called, there are three possible results:
    1. The compiler finds a function that best matches the argument and generates code that calls the function.
    2. No function can be found to match the argument of the call. At this time, the compiler issues an error message of no match.
    3. There is more than one function to match, but each is not the obvious best choice. At this time, the error is called ambiguous call.

6.4.1 overloading and scope

  • If we declare a name in the inner scope, it will hide the entity with the same name declared in the outer scope.
string read();
void print(const string &);
void print(double); //Overload print function
void fooBar(int ival)
{
    bool read = false; //New scope: Hidden outer read
    string s = read(); //Error: read is a Boolean value, not a function
    //Bad habits: in general, declaring a function in a local scope is not a good choice
    void print(int); //New scope: hides the previous print
    print("Value:"); //Error: Print (const string &) is hidden
    print(ival);     //Correct: the current print(int) is visible
    print(3.14);     //Correct: call print(int);print(double) is hidden
}
void print(const string &);
void print(double); //Overload print function
void print(int);    //Overload print function
void fooBar2(int ival)
{
    print("Value:"); //Call print (const string &)
    print(ival);     //Call print(int)
    print(3.14);     //Call print(double)
}

6.5 special purpose language features

  • Three function related language features:
    1. Default argument.
    2. Inline function and constexpr function.
    3. Some functions commonly used in program debugging.

6.5.1 default arguments

  • In many calls of a function, they are given the same value. At this time, we call this repeated value the default argument of the function.
  • When the function parameter is called, it can be omitted or included by default.
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrand = ' ');
string window;
window = screen();             //Equivalent to screen(24,80, '')
window = screen(66);           //Equivalent to screen(66,80, '')
window = screen(66, 256);      //Equivalent to screen (66256, '')
window = screen(66, 256, '#'); //Equivalent to screen (66256, '#')
window = screen(, , '?');      //Error: only trailing arguments can be omitted
window = screen('?')           //Call screen('?',80, '')
  • For the declaration of functions, the usual habit is to put them in the header file, and a function is declared only once, but it is also legal to declare the same function multiple times.
//Formal parameters representing height and width have no default values
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*');     //Error: duplicate declaration, cannot modify an existing default value
string screen(sz = 24, sz = 80, char); //Add arguments: correct by default
  • Local variables cannot be used as default arguments.
  • As long as the type of the expression can be converted to the type required by the formal parameter, the expression can be used as the default argument.
// Declarations of wd, def, and ht must appear outside the function
sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(), sz = wd, char = def);
string window = screen(); //Call screen(ht(),80, '')
void f2()
{
    def = '*';          //Change the value of the default argument
    sz wd = 100;        //The wd defined by the outer layer is hidden, but the default value is not changed
    windown = screen(); //Call screen(ht(),80, '*')
}

6.5.2 inline function and constexpr function

  • Using a function is generally a little slower than evaluating the value of an equivalent expression, so you can define a smaller operation as an inline function.
  • To designate a function as an inline function is usually to expand it "inline" at each call point.
const string &shorterString(const string &s1, cosnt string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}
//Call as follows
cout << shorerString(s1, s2) << endl;
//During compilation, inline expansion takes the following form
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
  • Add the keyword inline before the return type of the function, so that it can be declared as an inline function.
inline const string &shorterString(const string &s1, cosnt string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

6.5.3 commissioning assistance

  • C + + sometimes uses a technology similar to header file protection to selectively execute debugging code.
  • assert is a preprocessing macro. The so-called preprocessing macro is actually a preprocessing variable, and its behavior is a bit similar to that of an inline function.
  • assert is managed by the preprocessor rather than the compiler, so it is used without namespace declaration.
//First evaluate expr. If the expression is false, assert outputs information and terminates the execution of the program. If the expression is true, assert does nothing.
assert(expr);
  • NDEBUG preprocesses variables, and the behavior of assert depends on the state of NDEBUG.
  • If NDEBUG is defined, assert does nothing.
  • By default, NDEBUG is not defined. At this time, assert will perform runtime check.
  • Many compilers provide a command line option that allows us to define preprocessing variables.
gcc -D NDEBUG main.c
#define NDEBUG / / equivalent to the above
  • You can also use NDEBUG to write your own conditional debugging code and conditional compilation.
void print(const int ia[], size_t size)
{
#ifndef NDEBUG / / if NDEBUG is defined, these codes will be ignored
    //__ func__ Is a local static variable defined by the compiler, which is used to store the name of the function
    cerr << __func__ << ": array size is" << size << endl;
#endif
    //...
}
  • The preprocessor defines several system macros that are useful for program debugging.
__fun__//Name of the stored function
__FILE__//The string literal that holds the file name
__LINE__//The integer literal value that holds the current line number
__TIME__//String literal that holds the compilation time of the file
__DATE__//The literal value of the string that holds the compilation date of the file
  • You can use these constants to provide more information in the error message:
if (word.size() < threshold)
{
    cerr << "Error: " << __FILE__ << ": in function " << __func__ << " at line " << __LINE__ << endl
         << " Compiled on " << __DATE__ << " at " << __TIME__ << endl
         << " Word read was \"" << word << "\": Length too short" << endl;
}

6.6 function matching

  • When the number of formal parameters of several overloaded functions is equal and the types of some formal parameters can be converted from other types, function matching shall be carried out.
  • The first step is to select the overloaded function set corresponding to this call. The functions in the set are called candidate functions.
  • The second step is to investigate the arguments provided by this call, and then select the functions that can be called by this group of arguments from the candidate functions. These newly selected functions are called feasible functions.
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); //Call void f(double,double)
//f(int) is feasible because the argument type double can be converted to the parameter type int
//f(double, double) is feasible because its second parameter provides the default value, and the type of the first parameter is double, which is exactly the same as the argument type used by the function.
  • The third step is to select the function that best matches this call from the feasible functions. The closer the argument type is to the formal parameter type, the better they match. Therefore, the exact matching is better than the matching requiring type conversion.
  • When there are two or more arguments, the function matching is more complex. As follows, because each feasible function achieves a better matching on one argument, the compiler will eventually reject its request because the call is ambiguous.
f(42,2.56)

6.6.1 argument type conversion

  • In order to determine the best match, the compiler divides the conversion from argument type to formal parameter type into several levels. The specific order is as follows:
    1. Exact matching, including the following cases:
    • The argument type and formal parameter type are the same.
    • The argument is converted from the array type or function type to the corresponding pointer type.
    • Add the top-level const to the argument or remove the top-level const from the argument.
    1. Matching through const conversion.
    2. Matching through type promotion.
    3. Matching by arithmetic type conversion or pointer conversion.
    4. Matching through class type conversion.
  • Small integers are generally promoted to int type or larger integer type. Even if the argument is a small integer value, it will be directly promoted to int type. At this time, using the short version will lead to type conversion.
void ff(int);
void ff(short);
ff('a'); // char promoted to int; Call f(int)
  • The type of literal value 3.14 is double, which can be converted into both long and float. Because there are two possible arithmetic type conversions, the call is ambiguous.
void manip(long);
void manip(float);
manip(3.14); //Error: ambiguous call
  • If the difference between overloaded functions is whether the parameter of their reference type refers to const, or whether the parameter of pointer type refers to const, the compiler determines which function to choose by whether the argument is a constant when the call occurs.
Record lookup(Account &);       //The argument to the function is a reference to Account
Record lookup(const Account &); //The argument to the function is a constant reference
const Account a;
Account b;
lookup(a); //Call lookup (const account &)
lookup(b); //Call record lookup (account &);

6.7 function pointer

  • Function pointers point to functions rather than objects.
  • Like other pointers, function pointers point to a particular type.
  • The type of a function is determined by its return type and formal parameter type, regardless of the function name.
//Compare the length of two string objects
bool lengthCompare(const string &, const string &);
//pf points to a function whose parameters are references to two const string s, and the return value is of type bool.
bool (*pf)(const string &, const string &); //uninitialized
//Declare a function named pf that returns bool*
bool *pf(const string &, const string &);
  • When we use the function name as a value, the function is automatically converted to a pointer.
bool (*pf)(const string &, const string &);
pf = lengthCompare;                          // pf points to a function named lengthCompare
pf = &lengthCompare;                         //Equivalent assignment statement: it is desirable to take the address character
bool b1 = pf("hello", "goodbye");            //Call compare length
bool b2 = (*pf)("hello", "goodbye");         //An equivalent call
bool b3 = lengthCompare("hello", "goodbye"); //Another equivalent call
string::size_type sumLength(const string &, const string &);
bool cstringCompare(const char *, const char *);
pf = 0;              //Correct: pf does not point to any function
pf = sumLength;      //Error: return type mismatch
pf = cstringCompare; //Error: parameter type mismatch
pf = lengthCompare;  //Correct: the types of function and pointer match exactly
  • Pointer of overloaded function. The compiler determines which function to choose through the pointer type. The pointer type must accurately match one of the overloaded functions.
void ff(int *);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; // pf1 points to ff(unsigned)
void (*pf2)(int) = ff;          //Error: no ff matches the parameter list
double (*pf3)(int *) = ff;     //Error: return types of ff and pf3 do not match
  • Function pointer parameter
bool lengthCompare(const string &, const string &);
//The third parameter is the function type, which is automatically converted to a pointer to the function
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
//Equivalent declaration: explicitly define a formal parameter as a pointer to a function
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
//Automatically convert the function lengthCompare into a pointer to the function
userBigger(s1, s2, lengthCompare);
// Func and Func2 are function types
typedef bool Func(const string &, const string &);
typedef decltype(lengthCompare) Func2; //Equivalent type
// FuncP and FuncP2 are pointers to functions
typedef bool (*FuncP)(const string &, const string &);
typedef decltype(lengthCompare) *Func2; //Equivalent type
// Equivalent declaration of useBigger, in which the type alias is used
void useBigger(const string &, const string &, Func);//The compiler automatically converts the function type represented by Func to a pointer
void useBigger(const string &, const string &, FuncP2);
  • Returns a pointer to a function.
using F = int(int *, int);      // F is a function type, not a pointer
using PF = int (*)(int *, int); // PF is a pointer type
PF f1(int);                     //Correct: PF is the pointer to the function, and f1 returns the pointer to the function
F f1(int);                      //Error: F is a function type, f1 cannot return a function
F *f1(int);                     //Correct: explicitly specify that the return type is a pointer to a function
  • Interpret the expression from the inside out.
int (*f1(int))(int *, int);

Topics: C++