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

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

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

/ 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;
}
目录
相关文章
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
88 6
|
2月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
45 0
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
39 3
|
2月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
399 1
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
51 1
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
70 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
105 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
93 4