使用传值和引用参数的示例代码
/ 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函数进行修改。这样的操作是合法的,但可能会导致代码难以理解和维护。
总的来说,这段代码通过结构体引用的方式,展示了如何操作和修改结构体对象,并且演示了函数返回值的使用场景。通过累加和更新数据,可以实现对结构体成员变量的修改和计算。
函数重载
函数重载是指在同一个作用域内,可以定义多个具有相同名称但参数列表(包括参数类型、顺序和数量)不同的函数。
通过函数重载,可以根据不同的参数类型或数量来调用不同的函数逻辑,提高了代码的灵活性和可读性。它允许使用相同的函数名来表示一组相关的操作,从而更直观地表达代码的意图。
函数重载的规则如下:
- 函数名称必须相同,但是参数列表必须不同。
- 参数列表可以有不同的参数类型、参数顺序或参数数量。
- 返回类型可以相同也可以不同。
- 函数重载仅通过函数的名称和参数列表进行区分,与返回类型无关。
以下是一个函数重载的示例:
#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> 告诉编译器接下来的代码是模板代码,并且使用类型参数 T,T 可以是任意合法的标识符。
在函数体中,可以使用类型参数 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函数中,首先声明了几个变量a、b、c和d,分别为int类型和float类型。
通过调用swapValues函数模板来交换这些变量的值,不论是int类型还是float类型的变量,都可以使用相同的函数模板进行操作。
值得注意的是,在函数模板中,编译器会根据实际参数类型自动生成对应类型的函数代码,并进行编译。
通过函数模板,可以实现对不同类型数据的通用操作,提高代码的重用性和可读性。同时,C++标准库中许多常见的函数(如std::sort、std::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类型的变量i和j,并输出它们的初始值。
接着,调用Swap(i, j)来交换i和j的值,编译器会根据Swap函数模板的定义,生成一个针对int类型的具体函数。最后,再次输出i和j的值,可以看到它们已经被交换了。
类似地,还定义了一个交换double类型值的例子,通过调用Swap(x, y)来交换x和y的值,并最终输出交换后的结果。
总之,函数模板可以根据不同的数据类型自动生成对应的函数代码,从而提供了一种通用的解决方案,避免了重复编写相似功能的函数。
// 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[]类型的数组a和b,以及一个整数n,用于指定数组的长度。该模板函数用于交换两个数组中的元素。
在函数内部,使用一个循环遍历数组元素。对于每个位置上的元素,都执行一个交换操作:将数组a的当前位置上的元素赋值给临时变量temp,将数组b的当前位置上的元素赋值给数组a,最后将temp的值赋给数组b。通过这个过程,实现了两个数组中元素的交换。
在main函数中,首先声明并初始化了两个int类型的变量i和j,并输出它们的初始值。然后调用了Swap(i,j)来交换i和j的值,因为i和j的类型为int,所以编译器会根据Swap模板函数的定义,生成一个专门针对int类型的具体函数。最后,再次输出i和j的值,可以看到它们已经被成功交换。
接着,创建了两个int类型的数组d1和d2,并分别初始化它们的值。通过调用Show函数,展示了数组d1和d2的内容。然后,调用了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结构体对象的部分成员,即只交换salary和floor字段的值。在函数内部,创建了两个临时变量t1和t2,分别存储j1和j2的salary和floor的值。然后,将j1的salary赋值为j2的salary,将j2的salary赋值为t1,同样地,将j1的floor赋值为j2的floor,将j2的floor赋值为t2。通过这个过程,实现了特定成员的交换。
在main函数中,首先声明并初始化了两个int类型的变量i和j,并输出它们的初始值。然后调用了Swap(i, j)来交换i和j的值,因为i和j的类型为int,所以会选择调用通用版本的Swap模板函数。最后,再次输出i和j的值,可以看到它们已经被成功交换。
接着,创建了两个job结构体对象sue和sidney,并分别初始化它们的值。通过调用Show函数,展示了这两个对象的内容。然后,调用了Swap(sue, sidney)来交换这两个对象的salary和floor字段的值,因为sue和sidney的类型为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 编程练习:
- 打印字符串函数
#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; }
- 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; }
- 字符串转大写函数
#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; }