C++ Primer Plus learning notes

Posted by sirfartalot on Fri, 31 Dec 2021 12:57:32 +0100

Chapter VIII Function exploration

1. C + + inline function

After the function is created normally, when the program calls the function, the program jumps to the function address. When using inline functions, the compiler will directly replace the function call with the function code during compilation, which saves the time for the program to jump to the function address and speeds up the running speed. However, each time an inline function is called, a copy of the function will be created, and ten copies will be created ten times. Therefore, to create inline functions, the number of lines of code should not be large, and inline functions cannot be recursive. When using inline functions, the keyword inline should be added before the function declaration and definition. Compared with macro definition, inline function can better achieve the purpose of passing parameters by value.

inline double squar(double x) { return x * x; }  // Inline function
#Define square (x) x * x / / macro
int a = aquar(3+2);  // a = 5 * 5 = 25
int b = SAUAR(3+2);  // b = 3 + 2 * 3 + 2 = 11

2. Reference variables

A reference is equivalent to aliasing a variable. Both the reference and the original variable name refer to the same variable. References are defined as:

int rats;
int & rodents = rats;  // rodents is a reference to rats, & not an address character, but a reference to int

References must be initialized at the time of declaration. They cannot be declared before assignment, just as const constants are declared.

In C + +, the important role of reference is as a function parameter. Reference as a parameter can change the original variable (alias the original variable, and the function directly operates on the original variable). The function call initializes the formal parameter with the actual parameter:

void swapr(int & a, int & b);

To understand a reference, you can think of it as a pointer and directly modify the original data in memory.

Reference is an alias of a variable. Therefore, the parameter requirements of a function whose formal parameter is a reference are more stringent than those of a function whose formal parameter is a general variable:

int cube(int a);
int recube(int &b);
int x = 2;
int m = cube(x + 2);  // legitimate
int z1 = cube(4.13);  // legitimate
int n = recube(x + 2);  // wrongful
int z1 = recube(4.13);  // wrongful

When the formal parameter is a reference to const,

int con_recube(const int& c);
int num1 = con_recube(x + 2);  // legitimate
int num2 = con_recube(4.13);  // legitimate

The type of the function argument is correct, but it is not an lvalue; Or the type does not match the formal parameter, but can be converted to a matching type. The function will create a temporary variable and initialize it to the value of the type corresponding to the formal parameter. Then the function formal parameter is a reference to the temporary variable, and the temporary variable will be released after the function call. When a formal parameter is a pointer, it has the same effect as a reference. That is to say, if the intention of a function that accepts a reference or pointer is to modify parameters, it will be organized when creating temporary variables. If the parameters are limited by const, it will eliminate this side effect and create temporary variables without modifying formal parameters.

3. Default parameters

When defining a default parameter, you can only add a default value from right to left. When defining a function, you can omit the default value.

int harpo(int n, int m = 4, int j = 5);  // legitimate
int harpo(int n, int m, int j)  // Omit default values when defining
{
    ...
}
int chico(int n, int m = 6, int j);  // illegal

You cannot skip parameters when calling a default function

int beeps = harpo(3, , 8)  // illegal

4. Function overloading

Function overloading is only related to the characteristics of parameters, that is, the number, type or order of parameters, and has nothing to do with the return value of the function.

When calling overloaded functions, if there is no function that exactly matches the arguments, standard type cast will be used. If multiple overloads can be cast, an error will be reported when matching. References are not overloaded, but const.

void dribble(const char*)
void dribble(char*)  // heavy load

When the parameters of an overloaded function are lvalues, const s, and rvalues, the most matching function will be called.

void stove(double & r1);
void stove(const double & r2);
void stove(double && r3);
double x = 55.3;
const double y = 32.2;
stove(x);  // Call void stop (double & R1)
stove(y);  // Call void stop (const double & R2)
stove(x+y);  // Call void stop (double && R3)

5. Function template

Format of function template:

template <typename T>  // typename can be replaced by class
void Swap(T & a, T & b);  // Function declaration
    ...
template <typename T>
void Swap(T & a, T & b)  // Function definition
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

T indicates any standard type. The keywords template, typename and angle brackets cannot be omitted. They must be included in the declaration and definition. The function template automatically generates the corresponding function definition every time the template function is called, which does not shorten the program.

Function templates can also be overloaded, and the formal parameters of overloaded functions can also have other types, not only T type.

template <typename T>
void Swap(T a[], T b[], int n);

For types that are not applicable in the template function definition, for example, in a = b, T is an array, or if (a > b), and T is a structure. Display materialization can be used to deal with this problem.

struct job
{
    ...
};
template <> void Swap<job>(job &, job &);  // The function template shows the materialized prototype, and there are no parameters in the first angle bracket
    ...                                    // The < job > of swap < job > is optional

template <> void Swap<job>(job &, job &)  // Function definition
{
    ...
}

The compiler uses the function definition generated by the template, that is, the template instance, which is called implicit instantiation. c + + allows display instantiation. When calling template functions, the following methods are adopted:

Swap<int>(m, n);  // Show instantiation, tell the compiler to generate a function definition where T is int, and
                  // m. Cast the type of n to int

There are no angle brackets after the instantiated template, but there are when the materialized function declaration or definition is displayed. If you use the same type of display instantiation and display materialization in the same file, an error will be reported.

int m = 5;
double x = 14.3;
Swap<double>(m, x);  // An error will be reported. M and X are non const references, and a copy will not be created.
                     // A reference to m cannot be cast to double

If there are multiple matching prototypes such as overloads or templates, an error will be reported. The function matching the prototype has priority. If the argument is non const when the formal parameter is a pointer or reference, the function whose formal parameter is non const has priority over the function whose formal parameter is const. Non template functions take precedence over template functions. Explicit materialization takes precedence over implicit instantiation. For multiple matching functions, more specific functions will be selected.

When a function is called, angle brackets can be used to tell the compiler to use the template:

template <typename T>
T lesser(T a, T b);

int lesser(int a, int b);

int main()
{
    int m = 4;
    int n = 8;
    double x = 15.3;
    double y = 21.4;    
    
    lesser(m, n);  // Calling a non template function
    lesser(x, y);  // Call template function
    lesser<>(m, n);  // Display tells the compiler to use template functions
    lesser<int>(x, y)  // Explicit Instantiation 
    return 0;
}

decltype keyword can infer variable types:

//decltype(exp) var; 
// Exp has no parentheses, and the type of var is the same as exp
double x = 5.5;
double y = 7.9;
double & rx = x;
const double *pd;
decltype(x) w;  // w is of type double
decltype(rx) u = y;  // rx is a double & type
decltype(pd) v;  // pd is of type const double *

// exp is a function call, and the type of var is the same as the return type of the function
long indeed(int); 
decltype (indeed(3)) m;  // m is an int type and will not actually call the function. The compiler only looks at it
                         // Function return type
// Exp has parentheses, and the type of var is a reference to exp
double xx = 4.4;
decltype((xx)) r2 = xx;  // The type of r2 is double&
decltype(xx) w = xx;  // The type of w is double

For the following cases:

template<class T1, class T2>
decltype(x+y) gt(T1 x, T2 y);  // The return type cannot be inferred this way

X and y are not declared, and the compiler cannot know what x and y are.

You can use the method of postposition of return type:

template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y);  // statement

// definition
template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
    ...
}

In this way, decltype follows the declaration of parameter x and y.

Topics: C++ Back-end