C++ Primer Plus 第6版 读书笔记(8)第 8章 函数探幽

第8 章 函数探幽

本章内容包括:

内联函数。

引用变量。

如何按引用传递函数参数。

默认参数。

函数重载。

函数模板。

函数模板具体化。

通过第 7 章,您了解到很多有关 C++函数的知识,但需要学习的知识还很多。C++还提供许多新的函
数特性,使之有别于 C 语言。新特性包括内联函数、按引用传递变量、默认的参数值、函数重载(多态)
以及模板函数。

本章介绍的 C++在 C 语言基础上新增的特性,比前面各章都多,这是您进入加加(++)领
域的重要一步.

内联函数

内联函数是在编译时将函数的定义直接插入到调用该函数的地方,而不是通过函数调用的方式执行。通过内联函数可以提高程序的执行效率,减少函数调用的开销。

在C++中,使用关键字inline来声明内联函数。一般情况下,将函数定义放在类定义的头文件中,或者在函数定义前加上inline关键字。

例如:

inline int add(int a, int b) {
    
    
    return a + b;
}

内联函数在编译时会被替换为函数体的实际代码,而不是通过函数调用执行。这样可以避免函数调用的开销,提高程序的执行效率。然而,内联函数的展开也会增加代码的体积,所以内联函数适用于函数体较小且被频繁调用的情况。

需要注意的是,对于递归函数、含有循环或复杂控制流的函数以及包含静态变量的函数,通常不适合声明为内联函数。此外,编译器并不一定会完全遵守内联函数的声明,它可能会根据自己的优化策略选择是否内联某个函数。

总之,内联函数是一种用于提高程序性能的机制,它通过将函数的代码插入到调用处,减少了函数调用的开销,但也会增加代码的体积。我们需要根据实际情况来选择使用内联函数。

// inline.cpp -- using an inline function
#include <iostream>

// an inline function definition
inline double square(double x) {
    
     return x * x; }
int main()
{
    
    
    using namespace std;
    double a, b;
    double c = 13.0;

    a = square(5.0);
    b = square(4.5 + 7.5);   // can pass expressions
    cout << "a = " << a << ", b = " << b << "\n";
    cout << "c = " << c;
    cout << ", c squared = " << square(c++) << "\n";
    cout << "Now c = " << c << "\n";
    // cin.get();
    return 0;  
}

内联函数是在编译时展开的函数,它通常用于简单的函数,以提高程序的执行效率。在C++中,使用关键字inline来声明内联函数。

在上面的代码示例中,square()函数被声明为内联函数。它的定义如下:

inline double square(double x) {
    
     return x * x; }

square()函数接受一个double类型的参数,然后返回该参数的平方。

main()函数中,我们可以通过调用square()函数来计算不同值的平方,并将结果打印出来。例如:

a = square(5.0);

这里,5.0作为参数传递给square()函数,并将返回值赋给变量a

另外,内联函数还支持传递表达式作为参数。例如:

b = square(4.5 + 7.5);

这里,表达式4.5 + 7.5的值作为参数传递给square()函数。

此外,内联函数的展开发生在编译时,因此在代码中多次调用同一个内联函数时,每次调用都会被替换为函数体的复制。在上述代码中,可以看到square(c++)的调用,其中c++是一个后缀递增操作。由于内联函数的展开发生在编译时,所以c++只会在整个表达式计算之前递增一次,而不是在每次复制的调用中递增。

综上所述,内联函数是一种用于提高程序执行效率的特殊函数,它将函数的调用替换为函数体的复制,适用于简单的函数和频繁调用的函数。

当我们在代码中使用内联函数时,编译器会尝试将函数的定义直接嵌入到调用该函数的地方。这样可以减少函数调用带来的额外开销,提高程序的执行效率。

以下是一些关于内联函数的注意事项:

  1. 小函数:内联函数适合用于短小且频繁调用的函数。因为直接插入函数代码而不是通过函数调用,所以函数体越小,插入到调用处的冗余代码就越少,性能提升也越明显。

  2. 头文件中的定义:为了使内联函数在每个调用点都可见,通常将内联函数的定义放在头文件中。这样,在每个使用该内联函数的源文件编译时,都可以将函数的定义嵌入。否则,在链接阶段会出现无法解析的符号错误。

  3. 函数声明与定义的一致性:内联函数的声明和定义必须一致,即函数签名(参数类型,返回类型)和函数体需一致。如果函数的定义与声明不一致,编译器可能会忽略内联修饰符。

  4. 递归函数和循环:递归函数通常不适合用作内联函数,因为递归函数的执行需要保持调用栈的状态。而内联函数的工作方式不适合保持递归调用的状态。

  5. 编译器优化:内联函数的使用并不保证编译器一定会进行内联展开。编译器可能会根据自身的优化策略和设置来决定是否内联函数。我们可以使用编译器的优化选项来指示编译器进行内联展开。

内联函数是一种在编译时将函数调用替换为函数本体的方式,以提高程序执行效率。内联函数适用于短小且频繁调用的函数,但并不能保证编译器一定会进行内联展开。我们需要根据实际情况来选择使用内联函数,并注意遵循内联函数的声明和定义规则。

当我们在编程中使用内联函数时,有几点需要注意:

  1. 使用内联函数可以减少函数调用的开销,但这并不意味着每个函数都应该声明为内联函数。编译器根据一系列规则和优化策略来决定是否将函数展开为内联代码。通常,适合用于内联的函数包括简短的、频繁调用的函数。

  2. 内联函数的定义通常放在头文件中。因为内联函数的定义需要在每个调用点都可见,所以将其放在头文件中可以确保在所有使用的源文件中都能访问到内联函数的定义。否则,在链接阶段会出现找不到函数定义的错误。

  3. 内联函数的声明和定义必须一致,包括参数类型、返回类型和函数体。如果声明和定义不一致,编译器可能会忽略内联修饰符。

  4. 内联函数不适合复杂的函数体、包含循环或递归调用的函数以及包含静态变量的函数。由于内联函数会在每个调用点直接插入代码,复杂的函数体会导致代码重复,并且可能会影响性能。

  5. 内联函数的展开也会增加代码的体积,因此在某些情况下,过度使用内联函数可能会导致代码膨胀,影响可执行文件的大小。

  6. 编译器并不一定会完全遵循内联函数的声明。编译器会根据自身的优化策略和设置来决定是否将函数展开为内联代码。我们可以使用编译器提供的优化选项来指示编译器进行内联展开。

引用变量

在编程中,引用变量是一个存储内存地址的变量,它指向另一个变量或对象的位置。通过引用变量,我们可以间接地访问和修改相应的数据。

在不同的编程语言中,引用变量可能具有不同的表达方式。例如,在C++中,引用变量使用&符号声明,并在声明时与另一个变量绑定;在Java中,引用变量直接声明为对象类型;在Python中,变量实际上就是一个引用,直接通过赋值来引用其他对象。

引用变量的主要作用是允许多个变量同时指向同一个对象,从而实现数据共享和传递。通过修改引用变量的值,可以改变所指向对象的状态。这在处理大型复杂数据结构时非常有用,可以减少内存复制和提高性能。

然而,需要注意的是,引用变量并不总是指向有效的内存地址。如果引用变量没有初始化或者指向已释放的内存,那么访问该引用变量可能导致错误或者未定义行为。因此,在使用引用变量时,需要谨慎处理,确保引用的对象是有效的。

/ firstref.cpp -- defining and using a reference
#include <iostream>
int main()
{
    
    
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

// some implementations require type casting the following
// addresses to type unsigned
    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;
    // cin.get();
    return 0; 
}

这是一个C++的示例代码,演示了引用变量的用法。下面是对代码的解释:

#include <iostream>

引入iostream库,用于输入输出操作。

int main()
{
    
    
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

在主函数中,声明一个整型变量rats并赋值为101。然后,通过引用变量rodentsrats进行引用(绑定)。rodents成为rats的引用,它们指向同一个内存地址。

接下来,使用cout语句输出ratsrodents的值。

    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

通过修改rodents的值,也会同时修改rats的值。这里对rodents进行自增操作,并再次输出ratsrodents的值。

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;

最后,使用&运算符获取ratsrodents的地址,并输出。请注意,两者在内存中的地址是相同的,因为rodentsrats的引用。

    return 0; 
}

程序结束,返回0表示正常退出。

这个示例代码展示了引用变量的使用。通过引用变量,我们可以创建别名并直接访问和修改原始变量的值,实现数据共享和传递。

// secref.cpp -- defining and using a reference
#include <iostream>
int main()
{
    
    
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;

    int bunnies = 50;
    rodents = bunnies;       // can we change the reference?
    cout << "bunnies = " << bunnies;
    cout << ", rats = " << rats;
    cout << ", rodents = " << rodents << endl;

    cout << "bunnies address = " << &bunnies;
    cout << ", rodents address = " << &rodents << endl;
    // cin.get();
    return 0; 
}

这是另一个C++的示例代码,展示了引用变量的一些特性。下面是对代码的解释:

#include <iostream>

引入iostream库,用于输入输出操作。

int main()
{
    
    
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

在主函数中,声明一个整型变量rats并赋值为101。然后,通过引用变量rodentsrats进行引用(绑定)。

接下来,使用cout语句输出ratsrodents的值。

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;

使用&运算符获取ratsrodents的地址,并输出。请注意,两者在内存中的地址是相同的,因为rodentsrats的引用。

    int bunnies = 50;
    rodents = bunnies;       // can we change the reference?
    cout << "bunnies = " << bunnies;
    cout << ", rats = " << rats;
    cout << ", rodents = " << rodents << endl;

在此代码段中,声明了一个整型变量bunnies并赋值为50。然后,将bunnies赋值给rodents,即修改了rodents所引用的值。请注意,这里并没有修改rodents的引用目标,仅修改了其所引用对象的值。

接下来,使用cout语句输出bunniesratsrodents的值。

    cout << "bunnies address = " << &bunnies;
    cout << ", rodents address = " << &rodents << endl;

最后,使用&运算符获取bunniesrodents的地址,并输出。请注意,bunniesrodents在内存中的地址是不同的,因为它们是两个独立的变量。

    return 0; 
}

程序结束,返回0表示正常退出。

这个示例代码演示了引用变量的一些特性。通过引用变量,我们可以创建别名并直接访问原始变量的值。在赋值操作中,引用变量会更改其所引用对象的值,而不是修改引用本身。引用变量与原始变量共享相同的内存地址。

当我们使用引用变量时,可以进行以下操作:

  1. 声明引用变量:在编程语言中,可以使用特定的语法来声明引用变量。具体的语法可能因编程语言而异,但通常使用符号或关键字表示。例如,在C++中,可以使用&符号来声明引用变量。

  2. 初始化引用变量:引用变量必须在声明时被初始化,即要将其绑定到另一个变量或对象。这样引用变量就指向了相应的内存地址。初始化后,引用变量将成为该变量或对象的别名,并且可以通过该引用变量来访问和修改其值。

  3. 传递引用变量:可以将引用变量作为参数传递给函数或方法。通过这种方式,函数可以直接操作原始变量的值,而不需要进行额外的内存复制。这样可以提高性能并减少内存消耗。

  4. 修改引用变量:通过引用变量,可以修改所指向的变量或对象的值。这种修改是直接的,会反映在原始变量上。这对于在函数内部修改传入的变量非常有用。

  5. 注意引用的有效性:在使用引用变量时,需要确保引用的对象是有效的。如果引用变量指向已释放的内存或未初始化的变量,访问该引用变量可能导致错误或未定义行为。因此,在使用引用变量时,需要进行适当的检查和管理,以确保引用的正确性。

需要注意的是,具体的引用变量用法和语法可能因编程语言而异。在使用引用变量时,应该查阅相应编程语言的文档和规范,了解具体的用法和语法规则。

使用传值和引用参数的示例代码

/ cubes.cpp -- regular and reference arguments
#include <iostream>
double cube(double a);
double refcube(double &ra);
int main ()
{
    
    
    using namespace std;
    double x = 3.0;

    cout << cube(x);
    cout << " = cube of " << x << endl;
    cout << refcube(x);
    cout << " = cube of " << x << endl;
    // cin.get();
    return 0;
}

double cube(double a)
{
    
    
    a *= a * a;
    return a;
}

double refcube(double &ra)
{
    
    
    ra *= ra * ra;
    return ra; 
}

这是一个展示了使用传值和引用参数的示例代码。代码如下所示:

#include <iostream>

double cube(double a);
double refcube(double &ra);

int main()
{
    
    
    using namespace std;
    double x = 3.0;

    cout << cube(x);
    cout << " = cube of " << x << endl;
    cout << refcube(x);
    cout << " = cube of " << x << endl;

    return 0;
}

double cube(double a)
{
    
    
    a *= a * a;
    return a;
}

double refcube(double &ra)
{
    
    
    ra *= ra * ra;
    return ra;
}

在主函数中,首先声明一个变量x并赋值为3.0。然后调用cube(x)函数,并使用cout输出计算结果,再次输出x的值。接着调用refcube(x)函数,并同样使用cout输出计算结果以及x的值。

cube函数使用传值方式计算a的立方。在函数内部,先将a自乘两次,然后将结果返回。

refcube函数使用引用参数计算ra的立方。在函数内部,同样将ra自乘两次,然后将结果返回。由于使用引用参数,函数修改的是原始变量x的值。

通过比较两个函数的输出结果可以看出,使用传值参数的cube函数并没有修改原始变量x的值,而使用引用参数的refcube函数成功修改了原始变量x的值。

将引用用于结构

//strc_ref.cpp -- using structure references
#include <iostream>
#include <string>
struct free_throws
{
    
    
    std::string name;
    int made;
    int attempts;
    float percent;
};

void display(const free_throws & ft);
void set_pc(free_throws & ft);
free_throws & accumulate(free_throws &target, const free_throws &source);

int main()
{
    
    
    free_throws one = {
    
    "Ifelsa Branch", 13, 14};
    free_throws two = {
    
    "Andor Knott", 10, 16};
    free_throws three = {
    
    "Minnie Max", 7, 9};
    free_throws four = {
    
    "Whily Looper", 5, 9};
    free_throws five = {
    
    "Long Long", 6, 14};
    free_throws team = {
    
    "Throwgoods", 0, 0};
    free_throws dup;
    set_pc(one);
    display(one);
    accumulate(team, one);
    display(team);
// use return value as argument
    display(accumulate(team, two));
    accumulate(accumulate(team, three), four);
    display(team);
// use return value in assignment
    dup = accumulate(team,five);
    std::cout << "Displaying team:\n";
    display(team);
    std::cout << "Displaying dup after assignment:\n";
    display(dup);
    set_pc(four);
// ill-advised assignment
    accumulate(dup,five) = four;
    std::cout << "Displaying dup after ill-advised assignment:\n";
    display(dup);
    // std::cin.get();
    return 0;
}

void display(const free_throws & ft)
{
    
    
    using std::cout;
    cout << "Name: " << ft.name << '\n';
    cout << "  Made: " << ft.made << '\t';
    cout << "Attempts: " << ft.attempts << '\t';
    cout << "Percent: " << ft.percent << '\n';
}
void set_pc(free_throws & ft)
{
    
    
    if (ft.attempts != 0)
        ft.percent = 100.0f *float(ft.made)/float(ft.attempts);
    else
        ft.percent = 0;
}

free_throws & accumulate(free_throws & target, const free_throws & source)
{
    
    
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);
    return target;
}

这段代码是一个简单的示例,用于展示如何使用结构体引用以及结构体的操作。

首先,在代码中定义了一个名为free_throws的结构体。该结构体包含了篮球队员的姓名(name成员变量)、投中次数(made成员变量)、尝试次数(attempts成员变量)和命中率(percent成员变量)。

接下来,定义了几个函数来操作和显示free_throws对象。

  • display函数用于显示一个free_throws对象的内容。它接受一个常量引用参数ft,以避免对原始对象进行修改。在函数内部,使用cout对象输出对象的姓名、投中次数、尝试次数和命中率。

  • set_pc函数用于计算和设置投篮命中率。它接受一个非常量引用参数ft,因为它需要修改原始对象的成员变量。在函数内部,通过判断尝试次数是否为0,计算出命中率,并将其赋值给对象的命中率成员变量。

  • accumulate函数用于累加两个free_throws对象的数据,并返回一个free_throws引用。它接受一个非常量引用参数target作为目标对象,一个常量引用参数source作为源对象。在函数内部,将目标对象的投中次数和尝试次数分别加上源对象的对应值,并调用set_pc函数更新目标对象的命中率。最后,返回目标对象的引用。

main函数中,首先创建了一些free_throws对象,分别赋值给不同的变量(one、two、three、four、five和team)。这些对象表示了不同篮球队员的投篮数据和一个整个团队的总数据。

接下来,通过调用set_pc函数,计算和设置了对象one的命中率,并调用display函数显示出来。

然后,通过调用accumulate函数将对象one的数据累加到了team对象中,并再次调用display函数显示了team对象的数据。

接着,演示了如何将accumulate函数的返回值作为参数传递给display函数,以便直接显示累加结果。

然后,又通过多次调用accumulate函数,将不同对象的数据逐步累加到team对象中,并再次调用display函数显示了team对象的最新数据。

接下来,演示了将accumulate函数的返回值赋值给另一个free_throws对象dup,并通过调用display函数显示了team对象和dup对象的数据。

最后,代码中还演示了一种不推荐的做法,即将accumulate函数的返回值用于赋值,并再次调用accumulate函数进行修改。这样的操作是合法的,但可能会导致代码难以理解和维护。

总的来说,这段代码通过结构体引用的方式,展示了如何操作和修改结构体对象,并且演示了函数返回值的使用场景。通过累加和更新数据,可以实现对结构体成员变量的修改和计算。

函数重载

函数重载是指在同一个作用域内,可以定义多个具有相同名称但参数列表(包括参数类型、顺序和数量)不同的函数。

通过函数重载,可以根据不同的参数类型或数量来调用不同的函数逻辑,提高了代码的灵活性和可读性。它允许使用相同的函数名来表示一组相关的操作,从而更直观地表达代码的意图。

函数重载的规则如下:

  1. 函数名称必须相同,但是参数列表必须不同。
  2. 参数列表可以有不同的参数类型、参数顺序或参数数量。
  3. 返回类型可以相同也可以不同。
  4. 函数重载仅通过函数的名称和参数列表进行区分,与返回类型无关。

以下是一个函数重载的示例:

#include <iostream>

// 重载的函数add,接受两个整数参数
int add(int a, int b) {
    
    
    return a + b;
}

// 重载的函数add,接受三个整数参数
int add(int a, int b, int c) {
    
    
    return a + b + c;
}

// 重载的函数add,接受两个浮点数参数
float add(float a, float b) {
    
    
    return a + b;
}

int main() {
    
    
    int result1 = add(2, 3);
    int result2 = add(2, 3, 4);
    float result3 = add(2.5f, 3.7f);

    std::cout << result1 << std::endl;  // 输出:5
    std::cout << result2 << std::endl;  // 输出:9
    std::cout << result3 << std::endl;  // 输出:6.2

    return 0;
}

在上述示例中,定义了三个重载的add函数。第一个函数接受两个整数参数,第二个函数接受三个整数参数,第三个函数接受两个浮点数参数。

main函数中,通过根据需要选择调用不同版本的add函数,实现了整数相加和浮点数相加的功能,并打印出结果。

请注意,函数重载是 C++ 的特性,在函数调用时会根据传入的参数类型或数量,自动匹配调用合适的函数版本。这样可以简化函数命名,提高代码的可读性和灵活性。

函数模版

函数模板(Function Template)是C++中一种用于创建通用函数的特性。函数模板允许定义一个通用的函数,可以在不同的数据类型上进行操作,从而避免了重复编写类似功能的多个函数。

使用函数模板,可以根据具体的参数类型生成对应的函数代码,实现类型无关的重用。

函数模板的语法如下:

template <typename T>
void functionName(T parameter) {
    
    
    // 函数体
}

其中,template <typename T> 告诉编译器接下来的代码是模板代码,并且使用类型参数 TT 可以是任意合法的标识符。

在函数体中,可以使用类型参数 T 来定义变量、执行操作等。

以下是一个简单的函数模板示例:

#include <iostream>

// 函数模板,用于交换两个值
template <typename T>
void swapValues(T& a, T& b) {
    
    
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    
    
    int a = 1, b = 2;
    float c = 1.5f, d = 2.7f;

    std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
    swapValues(a, b);
    std::cout << "After swap: a = " << a << ", b = " << b << std::endl;

    std::cout << "Before swap: c = " << c << ", d = " << d << std::endl;
    swapValues(c, d);
    std::cout << "After swap: c = " << c << ", d = " << d << std::endl;

    return 0;
}

在上述示例中,定义了一个函数模板swapValues用于交换两个值。在main函数中,首先声明了几个变量abcd,分别为int类型和float类型。

通过调用swapValues函数模板来交换这些变量的值,不论是int类型还是float类型的变量,都可以使用相同的函数模板进行操作。

值得注意的是,在函数模板中,编译器会根据实际参数类型自动生成对应类型的函数代码,并进行编译。

通过函数模板,可以实现对不同类型数据的通用操作,提高代码的重用性和可读性。同时,C++标准库中许多常见的函数(如std::sortstd::max等)也是使用函数模板实现的。

/ funtemp.cpp -- using a function template
#include <iostream>
// function template prototype
template <typename T>  // or class T
void Swap(T &a, T &b);

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";
    // cin.get();
    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; 
}

这段代码演示了如何使用函数模板来创建通用的交换函数。

在这段代码中,定义了一个名为Swap的函数模板,用于交换两个值。在main函数中,先声明了int类型的变量ij,并输出它们的初始值。

接着,调用Swap(i, j)来交换ij的值,编译器会根据Swap函数模板的定义,生成一个针对int类型的具体函数。最后,再次输出ij的值,可以看到它们已经被交换了。

类似地,还定义了一个交换double类型值的例子,通过调用Swap(x, y)来交换xy的值,并最终输出交换后的结果。

总之,函数模板可以根据不同的数据类型自动生成对应的函数代码,从而提供了一种通用的解决方案,避免了重复编写相似功能的函数。

// twotemps.cpp -- using overloaded template functions
#include <iostream>
template <typename T>     // original template
void Swap(T &a, T &b);

template <typename T>     // new template
void Swap(T *a, T *b, int n);

void Show(int a[]);
const int Lim = 8;
int main()
{
    
    
    using namespace std;
    int i = 10, j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);              // matches original template
    cout << "Now i, j = " << i << ", " << j << ".\n";

    int d1[Lim] = {
    
    0,7,0,4,1,7,7,6};
    int d2[Lim] = {
    
    0,7,2,0,1,9,6,9};
    cout << "Original arrays:\n";
    Show(d1); 
    Show(d2);
    Swap(d1,d2,Lim);        // matches new template
    cout << "Swapped arrays:\n";
    Show(d1);
    Show(d2);
    // cin.get();
    return 0;
}

template <typename T>
void Swap(T &a, T &b) 
{
    
    
    T temp;
    temp = a;
    a = b;
    b = temp;
}

template <typename T>
void Swap(T a[], T b[], int n)
{
    
    
    T temp;
    for (int i = 0; i < n; i++)
    {
    
    
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

void Show(int a[])
{
    
    
    using namespace std;
    cout << a[0] << a[1] << "/";
    cout << a[2] << a[3] << "/";
    for (int i = 4; i < Lim; i++)
        cout << a[i];
    cout << endl;
}

这段代码展示了使用重载的函数模板来创建通用的交换函数。

首先,在代码中定义了一个名为Swap的模板函数,它有两个参数,类型为T&(引用类型)。该模板函数用于交换两个值,不管这两个值的具体类型是什么。在函数内部,创建了一个临时变量temp,将参数a的值赋给temp,然后将参数b的值赋给a,最后将temp的值赋给b。通过这个过程,实现了两个值的交换。

接下来,定义了一个重载的模板函数Swap,它有三个参数,分别是T[]类型的数组ab,以及一个整数n,用于指定数组的长度。该模板函数用于交换两个数组中的元素。

在函数内部,使用一个循环遍历数组元素。对于每个位置上的元素,都执行一个交换操作:将数组a的当前位置上的元素赋值给临时变量temp,将数组b的当前位置上的元素赋值给数组a,最后将temp的值赋给数组b。通过这个过程,实现了两个数组中元素的交换。

main函数中,首先声明并初始化了两个int类型的变量ij,并输出它们的初始值。然后调用了Swap(i,j)来交换ij的值,因为ij的类型为int,所以编译器会根据Swap模板函数的定义,生成一个专门针对int类型的具体函数。最后,再次输出ij的值,可以看到它们已经被成功交换。

接着,创建了两个int类型的数组d1d2,并分别初始化它们的值。通过调用Show函数,展示了数组d1d2的内容。然后,调用了Swap(d1,d2,Lim)来交换这两个数组中的元素。由于Swap函数模板被重载了,所以编译器会根据参数类型选择调用合适的函数。最后,再次调用Show函数,显示交换后的数组内容。

总之,通过使用重载的函数模板,代码实现了通用的交换函数,可以适用于不同类型的参数,提供了更灵活和通用的交换操作。

// twoswap.cpp -- specialization overrides a template
#include <iostream>
template <typename T>
void Swap(T &a, T &b);

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

// explicit specialization 
template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);

int main()
{
    
    
    using namespace std;
    cout.precision(2);
    cout.setf(ios::fixed, ios::floatfield);
    int i = 10, 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";

    job sue = {
    
    "Susan Yaffee", 73000.60, 7};
    job sidney = {
    
    "Sidney Taffee", 78060.72, 9};
    cout << "Before job swapping:\n";
    Show(sue);
    Show(sidney);
    Swap(sue, sidney); // uses void Swap(job &, job &)
    cout << "After job swapping:\n";
    Show(sue);
    Show(sidney);
    // cin.get();
    return 0;
}

template <typename T>
void Swap(T &a, T &b)    // general version
{
    
    
    T temp;
    temp = a;
    a = b;
    b = temp;
}

// swaps just the salary and floor fields of a job structure

template <> void Swap<job>(job &j1, job &j2)  // specialization
{
    
    
    double t1;
    int t2;
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}

void Show(job &j)
{
    
    
    using namespace std;
    cout << j.name << ": $" << j.salary
         << " on floor " << j.floor << endl;
}

这段代码展示了如何通过特化版本覆盖模板函数。

首先,在代码中定义了一个模板函数Swap,它有两个参数,类型为T&(引用类型)。该模板函数用于交换两个值,不管这两个值的具体类型是什么。在函数内部,创建了一个临时变量temp,将参数a的值赋给temp,然后将参数b的值赋给a,最后将temp的值赋给b。通过这个过程,实现了两个值的交换。

然后,定义了一个特化的模板函数Swap<job>,它有两个参数,类型为job&(引用类型)。该特化函数用于交换两个job结构体对象的部分成员,即只交换salaryfloor字段的值。在函数内部,创建了两个临时变量t1t2,分别存储j1j2salaryfloor的值。然后,将j1salary赋值为j2salary,将j2salary赋值为t1,同样地,将j1floor赋值为j2floor,将j2floor赋值为t2。通过这个过程,实现了特定成员的交换。

main函数中,首先声明并初始化了两个int类型的变量ij,并输出它们的初始值。然后调用了Swap(i, j)来交换ij的值,因为ij的类型为int,所以会选择调用通用版本的Swap模板函数。最后,再次输出ij的值,可以看到它们已经被成功交换。

接着,创建了两个job结构体对象suesidney,并分别初始化它们的值。通过调用Show函数,展示了这两个对象的内容。然后,调用了Swap(sue, sidney)来交换这两个对象的salaryfloor字段的值,因为suesidney的类型为job,所以会选择调用特化版本的Swap<job>函数。最后,再次调用Show函数,显示交换后的对象内容。

总之,通过在模板中创建特化版本,并在特化版本中提供针对特定类型的交换操作,代码实现了更精细和特定的交换功能。

8.6 总结:

C++ 扩展了 C 语言的函数功能,其中包括引入内联函数和引用变量等特性。内联函数可以通过在函数定义前加上 inline 关键字,并在首次调用前提供函数定义来实现。引用变量允许为变量创建别名,主要用于处理结构和类对象的函数参数。需要注意的是,基类引用可以指向派生类对象。

C++ 还支持函数参数默认值的定义,当函数调用省略了某些参数时,程序将使用默认值;当函数调用提供了参数值时,程序将使用提供的值。定义默认参数时需要按照从右到左的顺序提供。

函数的特征标是通过参数列表来确定的,允许定义同名函数但特征标不同,从而实现函数多态或函数重载。此外,函数模板可以自动完成函数重载的过程,通过使用泛型和具体算法定义函数,编译器将为特定参数类型生成正确的函数定义。

8.8 编程练习

1.编写通常接受一个参数(字符串的地址),并打印该字符串的函数。然而,如果提供了第二个参数
(int 类型),且该参数不为 0,则该函数打印字符串的次数将为该函数被调用的次数(注意,字符串的打印次数不等于第二个参数的值,而等于函数被调用的次数)。是的,这是一个非常可笑的函数,但它让您能够使用本章介绍的一些技术。在一个简单的程序中使用该函数,以演示该函数是如何工作的。

2.CandyBar 结构包含 3 个成员。第一个成员存储 candy bar 的品牌名称;第二个成员存储 candy bar的重量(可能有小数);第三个成员存储 candy bar 的热量(整数)。请编写一个程序,它使用一个这样的函数,即将 CandyBar 的引用、char 指针、double 和 int 作为参数,并用最后 3 个值设置相应的结构成员。最后 3 个参数的默认值分别为“Millennium Munch”、2.85 和 350。另外,该程序还包含一个以 CandyBar 的引用为参数,并显示结构内容的函数。请尽可能使用 const。

3.编写一个函数,它接受一个指向 string 对象的引用作为参数,并将该 string 对象的内容转换为大写, 为此可使用表 6.4 描述的函数 toupper( )。然后编写一个程序,它通过使用一个循环让您能够用不同的输入来测试这个函数,该程序的运行情况如下:

8.8 编程练习:

  1. 打印字符串函数
#include <iostream>
#include <string>

void printString(const std::string& str, int n = 1) {
    
    
    for (int i = 0; i < n; i++) {
    
    
        std::cout << str << std::endl;
    }
}

int main() {
    
    
    std::string str = "Hello, world!";
    printString(str); // 调用一次,打印一次字符串
    printString(str, 3); // 调用三次,打印三次字符串
    return 0;
}
  1. CandyBar 结构和函数
#include <iostream>
#include <string>

struct CandyBar {
    
    
    std::string brand;
    double weight;
    int calories;
};

void setCandyBar(CandyBar& candy, const char* brand = "Millennium Munch", double weight = 2.85, int calories = 350) {
    
    
    candy.brand = brand;
    candy.weight = weight;
    candy.calories = calories;
}

void showCandyBar(const CandyBar& candy) {
    
    
    std::cout << "Brand: " << candy.brand << std::endl;
    std::cout << "Weight: " << candy.weight << std::endl;
    std::cout << "Calories: " << candy.calories << std::endl;
}

int main() {
    
    
    CandyBar candy;
    setCandyBar(candy); // 使用默认参数设置结构成员
    showCandyBar(candy); // 显示结构内容
    return 0;
}
  1. 字符串转大写函数
#include <iostream>
#include <string>

void toUpper(std::string& str) {
    
    
    for (int i = 0; i < str.length(); i++) {
    
    
        str[i] = toupper(str[i]);
    }
}

int main() {
    
    
    std::string str;
    while (true) {
    
    
        std::cout << "Please enter a string (q to quit): ";
        std::getline(std::cin, str);

        if (str == "q") {
    
    
            break;
        }

        toUpper(str);
        std::cout << "Uppercase: " << str << std::endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/shaozheng0503/article/details/131605781