【C++ Primer Plus】Chapter 8 Exploring Functions

8.1 Inline functions

Inline functions run slightly faster than regular functions, at the cost of requiring more memory.
To use this feature, one of the following actions must be taken:

  1. Add the keyword inline before the function declaration;
  2. Add the keyword inline before the function definition.
// 内联函数,按值传递
#include <iostream>
using namespace std;
inline double square(double x) {
    
    return x*x;}
int main(void)
{
    
    
    double a;
    cout << "enter a double number:";
    cin >> a;
    cout << "a = " << a << ", a square = " << square(a) << endl;
    cout << "square(2.1 + 1.9) = " << square(2.1 + 1.9) << endl;    // 16
    return 0;
}

8.2 Reference variables

  1. A reference is an alias (another name) for a variable that is already defined.
  2. By using reference variables as parameters, the function uses the original data, not a copy of it.
  3. In this way, in addition to pointers, references also provide a very convenient way for functions to process large structures. At the same time, references are also essential for design classes.

8.2.1 Creating reference variables

int rats;
int & rodents = rats; 	// makes rodents an alias for rats
  1. where & is not an address operator, but part of the type identifier.
  2. Just like char* in the declaration refers to a pointer to char, int & refers to a reference to int.
  3. The above reference declaration allows rats and rodents to be interchanged - they point to the same value and memory location.
// 引用变量
#include <iostream>
using namespace std;
int main(void)
{
    
    
    int a = 10;
    int & b = a;    // 引用变量,必须在声明引用时将其初始化
    int * pa;
    pa = &a;        // 指针变量,可以先声明,再赋值。或者 int * pa = &a;
    int * const pr = &a;    // const指针变量,必须在声明引用时将其初始化。b相当于*pr
    cout << "a = " << a << ", b = " << b << ", *pa = " << *pa << endl;
    cout << "&a = " << &a << ", &b = " << &b << ", pa = " << pa << endl;
    a++;
    cout << "a = " << a << ", b = " << b << ", *pa = " << *pa << endl;
    cout << "&a = " << &a << ", &b = " << &b << ", pa = " << pa << endl;
    b++;
    cout << "a = " << a << ", b = " << b << ", *pa = " << *pa << endl;
    cout << "&a = " << &a << ", &b = " << &b << ", pa = " << pa << endl;
    return 0;
}

out:

a = 10, b = 10, *pa = 10
&a = 0xf091ff614, &b = 0xf091ff614, pa = 0xf091ff614
a = 11, b = 11, *pa = 11
&a = 0xf091ff614, &b = 0xf091ff614, pa = 0xf091ff614
a = 12, b = 12, *pa = 12
&a = 0xf091ff614, &b = 0xf091ff614, pa = 0xf091ff614

A reference can be set by initialization declaration, but not by assignment.

int rats = 101;
int * pt = &rats;
int & rodents = *pt;	// 将rodents初始化为*pt使得rodents指向rats。
int bunnies = 50;		
pt = &bunnies;			// 将pt改为指向bunnies,此时rodents引用的还是rats。

8.2.2 Using references as function parameters

Pass by reference allows the called function to access variables in the calling function.

Passing by pointer, passing by reference, and passing by value, the external difference is the way of declaring function parameters:

void swapp(int * p, int * q);	// 按指针传递,调用函数时传递的是变量地址
void swapr(int & a, int & b);	// 按引用传递,调用函数时传递的是变量本身
void swapv(int a, int b);		// 按值传递,调用函数时传递的是变量副本

If the programmer's intention is for the function to use the information passed to it without modifying the information, and at the same time want to use references, const references should be used.

double refcube(const double &ra);

Pass-by-reference restrictions are stricter. After all, if ra is an alias for a variable, the actual argument should be that variable, not an expression or real number.
For a C++ function whose formal parameter is a const reference, if the actual parameter does not match, its behavior is similar to passing by value. To ensure that the original data is not modified, a temporary variable will be used to store the value.
If the reference parameter is const, the compiler will generate a temporary variable in the following two cases:

  1. The actual parameter is of the correct type, but is not an lvalue;
  2. An argument is not of the correct type but can be converted to the correct type.

If the parameter of the function call is not an lvalue or does not match the type of the corresponding const reference parameter, C++ will create an anonymous variable of the correct type, pass the value of the parameter of the function call to the anonymous variable, and let the parameter refer to the variable .

What is an lvalue?
Lvalue parameters are data objects that can be referenced, for example, variables, array elements, structure members, referenced and dereferenced pointers are all lvalues. Non-lvalues ​​include literal constants (except quoted strings, which are denoted by their addresses) and expressions containing multiple items. In C, an lvalue originally referred to an entity that could appear on the left side of an assignment statement, but this was the case before the keyword const was introduced. Both regular and const variables are now considered lvalues, since they are accessed by address. But regular variables are modifiable lvalues, while const variables are unmodifiable lvalues.

Reference parameters should be declared const whenever possible:

  1. Use const to avoid programming errors that inadvertently modify data;
  2. Use const to enable the function to handle const and non-const arguments, otherwise it will only accept non-const data;
  3. Using const references enables functions to correctly create and use temporary variables.

8.2.4 Using references with structures

Using a structure reference parameter is the same as using a basic variable reference, just use the reference operator & when declaring the structure parameter.

struct free_throws
{
    
    
    std::string name;
    int made;
    int attempts;
    float percent;
};

free_throws one = {
    
    "Ifelsa Branch", 13, 14};	// 结构初始化时,如果指定的初始值比成员少,余下的成员(这里只有percent)将被设置为零。
free_throws two, three, four, five, dup;

void set_pc(free_throws & ft); 			// 在函数中将指向该结构的引用作为参数

void display(const free_throws & ft); 	// 不希望函数修改传入的结构,可使用const

free_throws & accumulate(free_throws & target, const free_throws & source);	// 返回也是引用,返回引用的函数实际上是被引用的变量的别名。
accumulate(dup,five) = four;			// 首先将five的数据添加到dup中,再使用four的内容覆盖dup的内容。
// 假设您要使用引用返回值,但又不允许执行像给accumulate()赋值 这样的操作,只需将返回类型声明为const引用:
const free_throws & accumulate(free_throws & target, const free_throws & source);
display(accumulate(team, two));

Why return references?

  1. double m = sqrt(16.0);In the first statement, the value 4.0 is copied to a temporary location and then copied to m.
  2. cout << sqrt(25.0);In the second statement, the value 5.0 is copied to a temporary location and then passed to cout.
  3. dup = accumulate(team,five);If accumulate() returns a structure, rather than a reference to the structure, the entire structure will be copied to a temporary location, and this copy will be copied to dup. But when the return value is a reference, the team will be copied directly to dup, which is more efficient.
  4. A function that returns a reference is actually an alias for the variable being referenced.

The most important point when returning a reference is to avoid returning a reference to a memory location that no longer exists when the function terminates. The easiest way to avoid this problem is to return a reference that is passed as an argument to the function. References taken as arguments will point to the data used by the calling function, so the returned reference will also point to those data.

8.2.5 Using references for class objects

If the formal parameter type is const string &, when calling the function, the actual parameter used can be a string object or a C-style string, such as a string literal enclosed in quotation marks, a char array terminated by a null character, or a pointer to char pointer variable.

string input = "happy";

string version1(const string & s1, const string & s2)
{
    
    
    string temp;
    temp = s2 + s1 + s2;
    return temp;
}
// temp是一个新的string对象,只在函数version1( )中有效,该函数执行完毕后,它将不再存在。因此,返回指向temp的引用不可行。
// 该函数的返回类型为string,这意味着temp的内容将被复制到一个临时存储单元中,
// 然后在main( )中,该存储单元的内容被复制到一个名为 result 的string中:
string result = version1(input, "***");		// 实参(input和“***”)的类型分别是string和const char *。

The reasons why the version1() function actual parameter and formal parameter type are inconsistent but the program can run:

  1. First, the string class defines a char * to string conversion function, which makes it possible to use C-style strings to initialize string objects.
  2. Second, a property of the parameter discussed earlier in this chapter whose type is a const reference. Assuming that the type of the actual parameter does not match the type of the reference parameter, but can be converted to a reference type, the program creates a temporary variable of the correct type, initializes it with the converted value of the actual parameter, and passes a reference to the temporary variable .

8.2.6 Objects, Inheritance, and References

Language features that enable passing features from one class to another are called inheritance .
ostream is the base class (because ofstream is built on top of it), and ofstream is the derived class (because it is derived from ostream).

  1. The derived class inherits the methods of the base class , which means that the ofstream object can use the features of the base class, such as the formatting methods precision( ) and setf( ).
  2. A base class reference can point to a derived class object without casting. For example, a function with parameter type ostream & can accept an ostream object (such as cout) or an ofstream object you declare as a parameter.

Formatting method in ostream:

  1. The method setf( ) enables you to set various formatting states.
setf(ios_base::fixed);		// 将对象置于使用定点表示法的模式; 
setf(ios_base::showpoint);	// 将对象置于显示小数点的模式,即使小数部分为零。
  1. The method precision( ) specifies how many decimal places to display (assuming the object is in fixed-point mode)
  2. The above two settings will remain unchanged until the corresponding method is called again to reset them.
  3. The method width( ) sets the field width to be used for the next output operation, this setting is only effective when displaying the next value, and then it will revert to the default setting. The default field width is zero, which means just enough to fit the content to be displayed.
// 通过调用同一个函数(只有函数调用参数不同)将数据写入文件和显示到屏幕上
// 该程序要求用户输入望远镜物镜和一些目镜的焦距,然后计算并显示每个目镜的放大倍数。
// 放大倍数等于物镜的焦距除以目镜的焦距,因此计算起来很简单。

#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
void file_it(ostream & os, double fo, const double fe[],int n);
const int LIMIT = 5;
int main(void)
{
    
    
    ofstream fout;
    const char * fn = "ep-data.txt";
    fout.open(fn);
    if (!fout.is_open())
    {
    
    
        cout << "Can't open " << fn << ". Bye.\n";
        exit(EXIT_FAILURE);
    }
    double objective;
    cout << "Enter the focal length of your "
            "telescope objective in mm: ";
    cin >> objective;
    double eps[LIMIT];
    cout << "Enter the focal lengths, in mm, of " << LIMIT
         << " eyepieces:\n";
    for (int i = 0; i < LIMIT; i++)
    {
    
    
        cout << "Eyepiece #" << i + 1 << ": ";
        cin >> eps[i];
    }
    file_it(fout, objective, eps, LIMIT);   // 将目镜数据写入到文件ep-data.txt中:
    file_it(cout, objective, eps, LIMIT);   // 将同样的信息以同样的格式显示到屏幕上:
    cout << "Done\n";
    return 0;
}

// 参数os(其类型为ostream &)可以 指向ostream对象(如cout),也可以指向ofstream对象(如fout)
void file_it(ostream & os, double fo, const double fe[],int n)
{
    
    
    // ios_base::fmtflags是存储这种信息所需的数据类型名称。
    // 因此,将返回值赋给initial将存储调用file_it( )之前的格式化设置,
    // 然后便可以使用变量initial作为参数来调用setf( ),将所有的格式化设置恢复到原来的值。
    // 因此,该函数将对象回到传递给file_it( )之前的状态。
    ios_base::fmtflags initial;
    initial = os.setf(ios_base::fixed); // save initial formatting state
    os.precision(0);
    os << "Focal length of objective: " << fo << " mm\n";
    os.setf(ios::showpoint);
    os.precision(1);
    os.width(12);       // 这种设置只在显示下一个值时有效
    os << "f.l. eyepiece";
    os.width(15);
    os << "magnification" << endl;
    for (int i = 0; i < n; i++)
    {
    
    
        os.width(12);
        os << fe[i];
        os.width(15);
        os << int (fo/fe[i] + 0.5) << endl;
    }
    os.setf(initial); // restore initial formatting state
}

out:

Focal length of objective: 1800 mm
f.l. eyepiece  magnification
         1.0           1800
         2.0            900
         3.0            600
        34.0             53
         4.0            450

8.2.7 When to Use Reference Parameters

There are two main reasons for using reference parameters:

  1. Programmers can modify data objects in calling functions.
  2. By passing references instead of entire data objects, programs run faster.

For functions that use passed values ​​without modification:

  1. If the data object is small, such as a built-in data type or a small structure, pass it by value.
  2. If the data object is an array, use a pointer since that's the only option, and declare the pointer as a pointer to const.
  3. If the data object is a larger structure, use const pointer or const reference to improve the efficiency of the program. This saves the time and space needed to copy structures.
  4. If the data object is a class object, a const reference is used. The semantics of class design often require the use of references, which is the main reason for this new feature in C++. Therefore, the standard way of passing class object parameters is by reference.

For functions that modify data in the calling function:

  1. If the data object is a built-in data type, a pointer is used.
  2. If you see code like fixit(&x) where x is an int, it's obvious that the function will modify x.
  3. If the data object is an array, only pointers can be used.
  4. If the data object is a structure, use a reference or pointer.
  5. If the data object is a class object, use a reference.

8.3 Default parameters

  1. How to set the default value? The function prototype must be passed.char * left(const char * str, int n = 1);
  2. Only prototypes can specify default values. And the function definition is exactly the same as without default parameters.
  3. For functions with parameter lists, default values ​​must be added from right to left.
int harpo(int n, int m = 4, int j = 5); 		// VALID
// int chico(int n, int m = 6, int j); 			// INVALID
int groucho(int k = 1, int m = 2, int n = 3); 	// VALID

char * left(const char * str, int n = 1);	// 函数原型
char sample[10];
char * ps = left(sample, 4);	// 函数调用
char * pa = left(sample);		// 函数调用,此时默认n=1
char * left(const char * str, int n){
    
    ...}	// 函数定义

8.4 Function overloading

  1. Function polymorphism (function overloading) enables the use of multiple functions with the same name.
  2. A family of functions can be designed through function overloading --** to do the same job, but with a different argument list. ** Its parameter list is also known as the function signature (function signature)
  3. C++ allows functions with the same name to be defined, provided they have different signatures. If the number of parameters and/or parameter types are different, the signatures are also different.
  4. Treat type references and types themselves as the same signature.
  5. When matching functions, no distinction is made between const and non-const variables. It is legal to assign a non-const value to a const variable, but not the other way around.
// 不同引用类型的重载:
double x = 55.5;
const double y = 32.0;
stove(x); 		// calls stove(double &)
stove(y); 		// calls stove(const double &)
stove(x+y); 	// calls stove(double &&)
// 如果没有定义函数stove(double &&),stove(x+y)将调用函数stove(const double &)。
// 函数重载 和 默认参数 的使用
// 由于新left( )的特征标不同于旧的left( ),因此可以在同一个程序中使用这两个函数。

#include <iostream>
using namespace std;
unsigned long left(unsigned long num, unsigned ct = 1);
char * left(const char * str, int n = 1);
int main(void)
{
    
    
    char * trip = "Hawaii!!"; // test value
    unsigned long n = 12345678; // test value
    cout << "n = " << n << ", trip = " << trip << endl;

    cout << "left(n) = " << left(n) << endl;        // 默认第二个参数为1
    cout << "left(n,3) = " << left(n, 3) << endl;   // 自定义第二个参数为3

    char * temp;
    temp = left(trip,5);
    cout << "left(trip,5) = " << temp << endl;
    delete [] temp; // point to temporary storage

    return 0;
}
// This function returns the first ct digits of the number num.
unsigned long left(unsigned long num, unsigned ct)
{
    
    
    unsigned digits = 1;
    unsigned long n = num;
    if (ct == 0 || num == 0)
        return 0; // return 0 if no digits
    while (n /= 10)
        digits++;
    if (digits > ct)
    {
    
    
        ct = digits - ct;
        while (ct--)
            num /= 10;
        return num; // return left ct digits
    }
    else // if ct >= number of digits
        return num; // return the whole number
}
// This function returns a pointer to a new string
// consisting of the first n characters in the str string.
char * left(const char * str, int n)
{
    
    
    if(n < 0)
        n = 0;
    char * p = new char[n+1];
    int i;
    for (i = 0; i < n && str[i]; i++)
        p[i] = str[i]; // copy characters
    while (i <= n)
        p[i++] = '\0'; // set rest of string to '\0'
    return p;
}

out:

n = 12345678, trip = Hawaii!!
left(n) = 1
left(n,3) = 123
left(trip,5) = Hawai

Name mangling?
A seemingly meaningless mangling (or correction, depending on the person) of the original name that encodes the number and type of arguments. The set of symbols added varies by function signature, and the conventions used for decoration vary by compiler.
With name mangling, C++ can accurately track every overloaded function.
long MyFunctionFoo(int, float);Internally represented as:?MyFunctionFoo@@YAXH

8.5 Function Templates

Function templates are generic function descriptions , that is, they define functions using generics that can be replaced by concrete types such as int or double. By passing a type as an argument to a template, you cause the compiler to generate a function of that type.

template <typename AnyType>			// 第一行指出,要建立一个模板,并将类型命名为AnyType。
// 关键字 template 和 typename 是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号。类型名可以任意选择。
void Swap(AnyType &a, AnyType &b)
{
    
    
    AnyType temp;
    temp = a;
    a = b;
    b = temp;
}
  1. Templates don't create any functions, they just tell the compiler how to define functions.
  2. If you need multiple functions that use the same algorithm for different types, use templates.
  3. If you don't care about backward compatibility and are willing to type longer words, you should use the keyword typename instead of class when declaring type parameters.
  4. Provide the prototype of the template function at the beginning of the file, and provide the definition of the template function after main( ).
  5. Function templates cannot shorten executable programs. The benefit of using templates is that it makes generating multiple function definitions easier and more reliable.
// 函数模板的定义和使用
#include <iostream>
// function template prototype,这两个函数模板相当于函数重载,函数名字相同,参数即类型不同
template <typename T>   // or class T
void Swap(T &a, T &b);
template <typename T> 	// new template
void Swap(T *a, T *b, int n);
int main()
{
    
    
    using namespace std;
    int i = 10;
    int j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);      // generates void Swap(int &, int &)
    cout << "Now i, j = " << i << ", " << j << ".\n";
    double x = 24.5;
    double y = 81.7;
    cout << "x, y = " << x << ", " << y << ".\n";
    cout << "Using compiler-generated double swapper:\n";
    Swap(x,y);      // generates void Swap(double &, double &)
    cout << "Now x, y = " << x << ", " << y << ".\n";
    return 0;
}
// function template definition
template <typename T>   // or class T
void Swap(T &a, T &b)
{
    
    
    T temp;             // temp a variable of type T
    temp = a;
    a = b;
    b = temp;
}

out:

i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
x, y = 24.5, 81.7.
Using compiler-generated double swapper:
Now x, y = 81.7, 24.5.

8.5.3 Explicit reification

A template definition that provides a specialization for a particular type, that is, a specialization function definition—called an explicit specialization—contains the required code.

  1. For a given function name, there can be non-template functions, template functions, and explicitly reified template functions and their overloaded versions.
  2. Explicitly reified prototypes and definitions should template<>begin with and indicate the type by name.
  3. Reifications take precedence over regular templates, and non-template functions take precedence over both reification and regular templates. When the compiler finds a reification definition that matches a function call, it uses that definition instead of looking for a template.
// 下面是用于交换job结构的非模板函数、模板函数和具体化的原型:
struct job
{
    
    
    char name[40];
    double salary;
    int floor;
};

void Swap(job &, job &);	// non template function prototype

template <typename T>
void Swap(T &, T &);		// template prototype

template <> void Swap<job>(job &, job &);	// explicit specialization for the job type

In the code below, the generic version is used the first time Swap( ) is called, implicitly instantiated. The second call uses an explicit materialized version based on the job type.

struct job
{
    
    
    char name[40];
    double salary;
    int floor;
};

template <class T> 	// template
void Swap(T &, T &);

template <> void Swap<job>(job &, job &);	// explicit specialization for the job type
int main()
{
    
    
    double u, v;
    ...
    Swap(u,v); // 隐式实例化:第一次调用Swap( )时使用通用版本
    job a, b;
    ...
    Swap(a,b); // 显示具体化:第二次调用使用基于job类型的显式具体化版本 void Swap<job>(job &, job &)
}

8.5.4 Instantiation and Reification

  • Including a function template in your code doesn't generate a function definition by itself, it's just a scheme for generating a function definition.
  • When the compiler uses a template to generate a function definition for a specific type, what it gets is a template instance (instantiation).
  • Implicit instantiation, explicit instantiation, and explicit specialization are collectively referred to as specialization.
  • What they have in common is that they both represent function definitions using concrete types, rather than generic descriptions (template definitions).
  1. implicit instantiation
template <class T>
void Swap (T &, T &); // 模板原型
int i, j;
Swap(i, j);			// 隐式实例化
// 函数调用Swap(i, j)导致编译器生成Swap( )的一个实例,该实例使用int类型。
// 模板并非函数定义,但使用int的模板实例是函数定义。
// 因为编译器之所以知道需要进行定义,是由于程序调用Swap( )函数时提供了int参数。
  1. explicit instantiation
template void Swap<int>(int, int); 			// explicit instantiation
  1. explicit reification
template <> void Swap<int>(int &, int &); 	// explicit specialization
template <> void Swap(int &, int &); 		// explicit specialization
...
template <class T>
void Swap (T &, T &); // template prototype
template <> void Swap<job>(job &, job &); // explicit specialization for job
int main(void)
{
    
    
    ...
    template void Swap<char>(char &, char &); // 显式实例化 for char
    short a, b;
    ...
    Swap(a,b); // 隐式实例化
    job n, m;
    ...
    Swap(n, m); // 显式具体化:使用为job类型提供的独立定义
    char g, h;
    ...
    Swap(g, h); // 使用处理显式实例化时生成的模板具体化
    ...
}

8.5.5 Which function version the compiler chooses to use

  1. Step 1: Create a list of candidate functions. It contains functions and template functions with the same name as the called function.
  2. Step 2: Use the list of candidate functions to create a list of feasible functions. These are functions with the correct number of arguments, for which there is an implicit conversion sequence that includes the case where the actual parameter types exactly match the corresponding formal parameter types. For example, a function call that takes a float argument can cast that argument to a double to match a double formal parameter, and a template can generate an instance for a float.
  3. Step 3: Determine if there is a best feasible function. If there is, it is used, otherwise the function call errors out.

The order from best to worst is as follows:

  1. Consider only the signature first, not the return type. (Some parameters are excluded because they cannot be implicitly converted, such as integer types cannot be implicitly converted to pointer types.)
  2. Exact match, but regular functions take precedence over templates.
  3. Promoted conversions (for example, char and shorts are automatically converted to int, floats are automatically converted to double).
  4. Standard conversions (eg, int to char, long to double).
  5. User-defined conversions, such as those defined in a class declaration.
struct blot {
    
    int a; char b[10];};
blot ink = {
    
    25, "spots"};
...
recycle(ink);

// 下面的原型都是完全匹配的:
void recycle(blot); 		// #1 blot-to-blot
void recycle(const blot); 	// #2 blot-to-(const blot)
void recycle(blot &); 		// #3 blot-to-(blot &)
void recycle(const blot &); // #4 blot-to-(const blot &)

8.5.6 Keyword decltype (C++11)

decltype(expression) var; // make var the same type as expression

decltype(x + y) xpy; // make xpy the same type as x + y
xpy = x + y;
// 合并为一条语句:
decltype(x + y) xpy = x + y;

// 第一步:如果expression是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符:
double x = 5.5;
double y = 7.9;
double &rx = x;
const double * pd;
decltype(x) w; 			// w is type double
decltype(rx) u = y; 	// u is type double &
decltype(pd) v; 		// v is type const double *

// 第二步:如果expression是一个函数调用,则var的类型与函数的返回类型相同:
long indeed(int);
decltype (indeed(3)) m; // m is type long

// 第三步:如果expression是一个左值(是用括号括起的标识符),则var为指向其类型的引用。
double xx = 4.4;
decltype((xx)) r2 = xx; // r2 is double &
decltype(xx) w = xx; 	// w is double (Stage 1 match)

// 第四步:如果前面的条件都不满足,则var的类型与expression的类型相同:
int j = 3;
int &k = j
int &n = j;
decltype(j+6) i1; 	// i1 type int
decltype(100L) i2; 	// i2 type long
decltype(k+n) i3; 	// i3 type int; 虽然k和n都是引用,但表达式k+n不是引用;它是两个int 的和,因此类型为int。

C++11 post-return type:

  • When to use it? There is no way to know in advance the type that will be obtained by adding x and y. It seems like it would be possible to set the return type to decltype ( x + y ), but unfortunately at this point the parameters x and y have not been declared and they are not in scope (the compiler cannot see them and cannot use them). Decltype must be used after declaring the parameter.
  • auto in the post-return is a placeholder, indicating the type provided by the post-return type.
// 无法预先知道将x和y相加得到的类型。
// 此时还未声明参数x和y,它们不在作用域内(编译器看不到它们,也无法使用它们)。
// 必须在声明参数后使用decltype。
template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y) // decltype在参数声明后面,因此x和y位于作用域内
{
    
    
    ...
    return x + y;
}

8.6 Summary

  1. C++ extends the functionality of the C language. By using the inline keyword for a function definition, and providing its function definition before calling the function for the first time, you can make the C++ compiler treat the function as an inline function. That is, instead of making the program jump to a separate code segment to execute a function, the compiler replaces the function call with the corresponding code. Inlining should only be used when the function is short.
  2. A reference variable is a fake pointer that allows an alias (another name) to be created for the variable. Reference variables are primarily used as arguments to functions that work with struct and class objects. In general, identifiers declared as references of a particular type can only point to data of that type; however, if a class (such as ofstream) is derived from another class (such as ostream), a base class reference can point to a derived class object.
  3. C++ prototypes allow you to define default values ​​for parameters. If the function call omits the corresponding parameter, the program will use the default value; if the function call provides a parameter value, the program will use this value (instead of the default value). Default arguments can only be provided from right to left in the argument list. Therefore, if a default value is provided for a parameter, default values ​​must be provided for all parameters to the right of that parameter.
  4. The signature of a function is its parameter list. A programmer can define two functions with the same name as long as their signatures are different. This is known as function polymorphism or function overloading. Typically, functions are overloaded to provide the same service for different data types.
  5. Function templates automate the process of overloading functions. Simply define functions using generics and concrete algorithms, and the compiler will generate the correct function definitions for the specific parameter types used in the program.
  6. Reifications take precedence over regular templates, and non-template functions take precedence over both reification and regular templates. Non-template functions > reifications > regular templates

8.7 Review Questions

  1. Which function is suitable to be defined as an inline function?
    The code size of the inline function must be small, and recursion cannot occur in the inline function.
  2. Suppose the prototype of the song( ) function is as follows: void song(const char * name, int times);
    a. How to modify the prototype so that the default value of times is 1?
    b. What changes are required to the function definition?
    c. Can you provide a default value of "O. My Papa" for name? Yes, but at this time times should also be set to the default value, because for a function with a parameter list, the default value must be added from right to left.void song(const char * name = "O. My Papa", int times = 1);
  3. Write an overloaded version of iquote( ) that displays its argument enclosed in double quotes. Write 3 versions: one for int parameters, one for double parameters, and one for string parameters.
void iquote(int n){
    
    std::cout << "\"" << n << "\"\n";}
void iquote(double n){
    
    std::cout << "\"" << n << "\"\n";}
void iquote(string n){
    
    std::cout << "\"" << n << "\"\n";}
  1. Indicate whether each of the goals below can be accomplished using default arguments or function overloading, or neither, and provide a suitable prototype.
    • a. mass(density, volume) returns the mass of an object with density and volume, and mass(denstity) returns the mass of an object with density and volume of 1.0 cubic meters. These values ​​are all of type double.
      default parameters double mass(double density, double volume = 1.0);
      function overloading double mass(double density, double volume);anddouble mass(double density);
    • b. repeat(10, "I'm OK") displays the specified string 10 times, and repeat("But you're kind of stupid") displays the specified string 5 times.
      The default parameter cannot be used, because the parameter on the left needs to be set to the number of times, and the right must also be a default value;
      function overloading void repeat(int n, const char *str);andvoid repeat(const char *str);
    • c. average(3, 6) returns the average of two int parameters (int type), and average(3.0, 6.0) returns the average of two double values ​​(double type).
      You can use function templates and function overloading.
    • d. mangle("I'm glad to meet you") returns the character I and a pointer to the string "I'm glad to meet you" respectively according to whether the value is assigned to a char variable or a char* variable.
      The parameters are all pointers to strings, and the formal parameters are the same, so functions cannot be overloaded.
  2. Write a function template that returns the greater of two arguments.
template <typename AnyType>
AnyType Max(AnyType x, AnyType y)
{
    
    
    if (x > y)
        return x;
    else
        return y;
    // return x > y ? x : y;
}
  1. Given the template for Review 7 and the box structure for Review 4, provide a template incarnation that takes two box arguments and returns the larger one.
struct box
{
    
    
    char maker[40];
    float height;
    float width;
    float length;
    float volume;
};

template <typename AnyType>
AnyType Max(AnyType x, AnyType y)
{
    
    
    return x > y ? x : y;
}

template <> box Max<box>(box b1, box b2)
{
    
    
    return b1.volume > b2.volume ? b1 : b2;
}
  1. In the following code (assuming the code is part of a complete program), what type are v1, v2, v3, v4, and v5?
int g(int x);
...
float m = 5.5f;
float & rm = m;
decltype(m) v1 = m;		// float, v1由m决定
decltype(rm) v2 = m;	// float &
decltype((m)) v3 = m;	// float &
decltype (g(100)) v4;	// int
decltype (2.0 * m) v5;	// double, 2.0是double

8.8 Programming Exercises

// 1.编写通常接受一个参数(字符串的地址),并打印该字符串的函数。
// 然而,如果提供了第二个参数(int类型),且该参数不为0,
// 则该函数打印字符串的次数将为该函数被调用的次数
// (注意,字符串的打印次数不等于第二个参数的值,而等于函数被调用的次数)。

#include <iostream>
using namespace std;
void show(const char *str, int n = 0);
int main(void)
{
    
    
    show("chai");
    show("come on!");
    show("I love you.", 8);
    return 0;
}
void show(const char *str, int n)
{
    
    
    static int count = 0;   // 调用多次该函数,只初始化一次
    count++;
    if (n == 0)
        cout << str << endl;
    else
    {
    
    
        for (int i=0; i<count; i++)
            cout << str << endl;
    }
}
// 2.CandyBar结构包含3个成员。
// 第一个成员存储candy bar的品牌名称;第二个成员存储candy bar的重量(可能有小数);第三个成员存储 candy bar的热量(整数)。
// 请编写一个程序,它使用一个这样的函数,即将CandyBar的引用、char指针、double和int作为参数,并用最后3个值设置相应的结构成员。
// 最后3个参数的默认值分别为“Millennium Munch”、2.85和350。
// 另外,该程序还包含一个以CandyBar的引用为参数,并显示结构内容的函数。
// 请尽可能使用const。

#include <iostream>
#include <cstring>
using namespace std;
struct CandyBar
{
    
    
    char name[20];
    float weight;
    int heat;
};
void set_value(CandyBar & aaa, const char *str = "Millennium Munch", const double w = 2.85, const int h = 350);
void show(const CandyBar & aaa);
int main(void)
{
    
    
    CandyBar aaaaa;
    set_value(aaaaa);
    show(aaaaa);
    return 0;
}
void set_value(CandyBar & aaa, const char *str, const double w, const int h)
{
    
    
    strcpy(aaa.name, str);  // 这里使用字符串拷贝函数
    aaa.weight = w;
    aaa.heat = h;
}
void show(const CandyBar & aaa)
{
    
    
    cout << aaa.name << endl;
    cout << aaa.weight << endl;
    cout << aaa.heat << endl;
}
// 3.编写一个函数,它接受一个指向string对象的引用作为参数,并将该string对象的内容转换为大写,为此可使用表6.4描述的函数toupper( )。
// 然后编写一个程序,它通过使用一个循环让您能够用不同的输入来测试这个函数,该程序的运行情况如下:

#include <iostream>
#include <cstring>
#include <cctype>
using namespace std;
void transform(string & str);
int main(void)
{
    
    
    cout << "please enter a string (q to quit):";
    string str;
    getline(cin, str);  // 这里注意
    while (str != "q")
    {
    
    
        transform(str);
        cout << str << endl;
        cout << "please enter a string (q to quit):";
        getline(cin, str);
    }
    cout << "Bye" << endl;
    return 0;
}
void transform(string & str)
{
    
    
    for (int i=0; i < str.size(); i++)
        str[i] = toupper(str[i]);   // 使用引用参数,这里直接改变它本身,不需要再返回
}
// 4.请提供其中描述的函数和原型,从而完成该程序。
// 注意,应有两个 show( )函数,每个都使用默认参数。请尽可能使用cosnt参数。
// set( )使用 new分配足够的空间来存储指定的字符串。这里使用的技术与设计和实现类时使用的相似。
#include <iostream>
using namespace std;
#include <cstring>  // for strlen(), strcpy()
struct stringy
{
    
    
    char * str;     // points to a string
    int ct;         // length of string (not counting '\0')
};
// prototypes for set(), show(), and show() go here
void set(stringy & str, const char *source);
void show(const stringy & str, int n = 1);
void show(const char *str, int n = 1);
int main()
{
    
    
    stringy beany;
    char testing[] = "Reality isn't what it used to be.";
    set(beany, testing);    // first argument is a reference,
                            // allocates space to hold copy of testing,
                            // sets str member of beany to point to the
                            // new block, copies testing to new block,
                            // and sets ct member of beany
    show(beany);            // prints member string once
    show(beany, 2);         // prints member string twice
    testing[0] = 'D';
    testing[1] = 'u';
    show(testing);          // prints testing string once
    show(testing, 3);       // prints testing string thrice
    show("Done!");
    delete [] beany.str;
    return 0;
}
void set(stringy & str, const char *source)
{
    
    
    str.ct = strlen(source) + 1;
    str.str = new char[str.ct];
    strcpy(str.str, source);
}
void show(const stringy & str, int n)
{
    
    
    for (int i=0; i<n; i++)
        cout << str.str << endl;
}
void show(const char *str, int n)
{
    
    
    for (int i=0; i<n; i++)
        cout << str << endl;
}
// 6.编写模板函数maxn( ),它将由一个T类型元素组成的数组和一个表示数组元素数目的整数作为参数,并返回数组中最大的元素。
// 在程序对它进行测试,该程序使用一个包含6个int元素的数组和一个包含4个 double元素的数组来调用该函数。
// 程序还包含一个具体化,它将char指针数组和数组中的指针数量作为参数,并返回最长的字符串的地址。
// 如果有多个这样的字符串,则返回其中第一个字符串的地址。
// 使用由5个字符串指针组成的数组来测试该具体化。
#include <iostream>
#include <cstring>
using namespace std;
template <typename T>
T maxn(T arr[], int n); // 模板函数
template <> string maxn<string>(string str[], int n);
int main(void)
{
    
    
    int arr1[6] = {
    
    0, 2, 4, 6, 2, 1};
    double arr2[4] = {
    
    3.14, 3.1415, 2.1, 0.5};
    cout << "int arr1[6] max : " << maxn(arr1, 6) << endl;
    cout << "double arr2[4] max : " << maxn(arr2, 4) << endl;
    string str[5] = {
    
    "hello world!", "chai", "mian", "i love you", "thank you"};
    cout << "char *str[5] max : " << maxn(str, 5) << endl;
    return 0;
}
template <typename T>
T maxn(T arr[], int n)
{
    
    
    T temp = arr[0];
    for (int i=0; i<n; i++)
        if (arr[i] > temp)
            temp = arr[i];
    return temp;
}
template <>
string maxn<string>(string str[], int n)  // 函数模板具体化
{
    
    
    int temp = 0;
    for (int i=0; i<n; i++)
    {
    
    
        if (str[temp].size() < str[i].size())
            temp = i;
    }
    return str[temp];
}
// 7.修改程序清单 8.14,使其使用两个名为 SumArray()的模板函数来返回数组元素的总和,而不是显示数组的内容。
// 程序应显示thing的总和以及所有debt的总和。
#include <iostream>
template <typename T> // template A
void ShowArray(T arr[], int n);
template <typename T> // template B
void ShowArray(T * arr[], int n);
template <typename T>
T SumArray(T arr[], int n);
template <typename T>
T SumArray(T * arr[], int n);
struct debts
{
    
    
    char name[50];
    double amount;
};
int main()
{
    
    
    using namespace std;
    int things[6] = {
    
    13, 31, 103, 301, 310, 130};
    struct debts mr_E[3] =
            {
    
    
                    {
    
    "Ima Wolfe", 2400.0},
                    {
    
    "Ura Foxe", 1300.0},
                    {
    
    "Iby Stout", 1800.0}
            };
    double * pd[3];         // set pointers to the amount members of the structures in mr_E
    for (int i = 0; i < 3; i++)
        pd[i] = &mr_E[i].amount;
    cout << "Listing Mr. E's counts of things:\n";
    ShowArray(things, 6);   // uses template A
    cout << "\nListing Mr. E's debts:\n";
    ShowArray(pd, 3);       // uses template B (more specialized)
    cout << "\nThe sum of things : " << SumArray(things, 6) << endl;
    cout << "\nThe sum of debts : " << SumArray(pd, 3) << endl;
    return 0;
}
template <typename T>
void ShowArray(T arr[], int n)
{
    
    
    using namespace std;
    cout << "template A\n";
    for (int i = 0; i < n; i++)
        cout << arr[i] << ' ';
    cout << endl;
}
template <typename T>
void ShowArray(T * arr[], int n)
{
    
    
    using namespace std;
    cout << "template B\n";
    for (int i = 0; i < n; i++)
        cout << *arr[i] << ' ';
    cout << endl;
}
template <typename T>
T SumArray(T arr[], int n)
{
    
    
    T sum = 0;
    for (int i=0; i<n; i++)
        sum += arr[i];
    return sum;
}
template <typename T>
T SumArray(T * arr[], int n)
{
    
    
    T sum = 0;
    for (int i=0; i<n; i++)
        sum += *arr[i];
    return sum;
}

Guess you like

Origin blog.csdn.net/qq_39751352/article/details/126732808