使用传值和引用参数的示例代码
/ 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; }