介绍
在本实验中,你将学习 C++ 中的函数。你将学习如何定义和调用函数,以及如何向函数传递参数。
在本实验中,你将学习 C++ 中的函数。你将学习如何定义和调用函数,以及如何向函数传递参数。
有时,某段代码需要被多次使用。为了更好地维护和理解代码,最好将这些代码放入一个“子程序”——函数中,并多次“调用”这个函数。
使用函数涉及两方:调用函数的 调用者 和被调用的 函数。调用者将 参数 传递给函数。函数接收这些参数,在函数体内执行编程操作,并将结果返回给调用者。
假设我们需要多次计算圆的面积,最好编写一个名为 getArea()
的函数,并在需要时重复使用它。
/* 测试函数 */
#include <iostream>
using namespace std;
const int PI = 3.14159265;
// 函数原型(函数声明)
double getArea(double radius);
int main() {
double radius1 = 1.1, area1, area2;
// 调用函数 getArea()
area1 = getArea(radius1);
cout << "area 1 is " << area1 << endl;
// 调用函数 getArea()
area2 = getArea(2.2);
cout << "area 2 is " << area2 << endl;
// 调用函数 getArea()
cout << "area 3 is " << getArea(3.3) << endl;
}
// 函数定义
// 根据半径返回圆的面积
double getArea(double radius) {
return radius * radius * PI;
}
输出:
area 1 is 3.63
area 2 is 14.52
area 3 is 32.67
在 C++ 中,你需要声明一个函数原型(在函数被使用之前),并提供函数定义,函数体中包含编程操作。
函数定义的语法如下:
返回值类型 函数名 ( 参数列表 ) {
函数体 ;
}
函数原型告诉编译器函数的接口,即返回类型、函数名和参数类型列表(参数的数量和类型)。函数现在可以在文件中的任何地方定义。例如:
// 函数原型 - 在函数被使用之前声明。
double getArea(double); // 不带参数名
double getArea(double radius); // 参数名被忽略,但可以作为文档
"void" 返回类型
如果不需要向调用者返回值,你可以将其返回类型声明为 void
。在函数体中,你可以使用不带返回值的 "return;
" 语句将控制权返回给调用者。
实际参数 vs. 形式参数
在上面的例子中,getArea(double radius)
签名中声明的变量 (double radius)
被称为 形式参数。它的作用域在函数体内。当函数被调用者调用时,调用者必须提供所谓的 实际参数(或 实参),其值随后用于实际计算。例如,当通过 "area1 = getArea(radius1)
" 调用函数时,radius1
是实际参数,其值为 1.1
。
函数局部变量和参数的作用域
所有变量,包括函数的参数,在函数内部声明的变量仅对函数可用。它们在函数被调用时创建,并在函数返回后释放(销毁)。它们被称为 局部变量,因为它们是函数局部的,在函数外部不可用。
C++ 引入了所谓的函数 默认参数。如果调用者在调用函数时省略了相应的实际参数,则会使用这些默认值。默认参数在函数原型中指定,不能在函数定义中重复。默认参数根据它们的位置解析。因此,它们只能用于替换 尾部 参数以避免歧义。例如:
/* 测试函数默认参数 */
#include <iostream>
using namespace std;
// 函数原型 - 在此处指定默认参数
int fun1(int = 1, int = 2, int = 3);
int fun2(int, int, int = 3);
int main() {
cout << fun1(4, 5, 6) << endl; // 无默认值
cout << fun1(4, 5) << endl; // 4, 5, 3(默认)
cout << fun1(4) << endl; // 4, 2(默认), 3(默认)
cout << fun1() << endl; // 1(默认), 2(默认), 3(默认)
cout << fun2(4, 5, 6) << endl; // 无默认值
cout << fun2(4, 5) << endl; // 4, 5, 3(默认)
// cout << fun2(4) << endl;
// 错误:函数 'int fun2(int, int, int)' 的参数太少
}
int fun1(int n1, int n2, int n3) {
// 不能在函数定义中重复默认参数
return n1 + n2 + n3;
}
int fun2(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
输出:
15
12
9
6
15
12
C++ 引入了 函数重载(或 函数多态),它允许你拥有多个版本的相同函数名,通过参数列表(参数的数量、类型或顺序)来区分。重载函数不能通过返回类型来区分(会导致编译错误)。匹配调用者参数列表的版本将被选中执行。例如:
/* 测试函数重载 */
#include <iostream>
using namespace std;
void fun(int, int, int); // 版本 1
void fun(double, int); // 版本 2
void fun(int, double); // 版本 3
int main() {
fun(1, 2, 3); // 版本 1
fun(1.0, 2); // 版本 2
fun(1, 2.0); // 版本 3
fun(1.1, 2, 3); // 版本 1 - double 1.1 被强制转换为 int 1(无警告)
// fun(1, 2, 3, 4);
// 错误:没有匹配的函数用于调用 'fun(int, int, int, int)'
// fun(1, 2);
// 错误:重载函数 'fun(int, int)' 的调用不明确
// 注意:候选函数为:
// void fun(double, int)
// void fun(int, double)
// fun(1.0, 2.0);
// 错误:重载函数 'fun(double, double)' 的调用不明确
}
void fun(int n1, int n2, int n3) { // 版本 1
cout << "版本 1" << endl;
}
void fun(double n1, int n2) { // 版本 2
cout << "版本 2" << endl;
}
void fun(int n1, double n2) { // 版本 3
cout << "版本 3" << endl;
}
输出:
版本 1
版本 2
版本 3
版本 1
你也可以将数组传递给函数。然而,你还需要将数组的大小传递给函数。这是因为在被调用的函数内部无法从数组参数中得知数组的大小。例如:
/* 计算数组和的函数 */
#include <iostream>
using namespace std;
// 函数原型
int sum(int array[], int size); // 需要同时传递数组大小
void print(int array[], int size);
// 测试驱动程序
int main() {
int a1[] = {8, 4, 5, 3, 2};
print(a1, 5); // {8,4,5,3,2}
cout << "sum is " << sum(a1, 5) << endl; // sum is 22
}
// 函数定义
// 返回给定数组的和
int sum(int array[], int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
sum += array[i];
}
return sum;
}
// 打印给定数组的内容
void print(int array[], int size) {
cout << "{";
for (int i = 0; i < size; ++i) {
cout << array[i];
if (i < size - 1) {
cout << ",";
}
}
cout << "}" << endl;
}
输出:
{8,4,5,3,2}
sum is 22
参数可以通过两种方式传递给函数:传值 和 传引用。
传值
在传值方式中,会创建参数的“副本”并传递给函数。被调用的函数操作的是“克隆”的副本,无法修改原始数据。在 C/C++ 中,基本类型(如 int
和 double
)是通过传值方式传递的。
/* 基本类型通过传值方式传递给函数 */
#include <iostream>
using namespace std;
// 函数原型
int inc(int number);
// 测试驱动程序
int main() {
int n = 8;
cout << "调用函数前,n 的值是 " << n << endl; // 8
int result = inc(n);
cout << "调用函数后,n 的值是 " << n << endl; // 8
cout << "结果是 " << result << endl; // 9
}
// 函数定义
// 返回 number+1
int inc(int number) {
++number; // 修改参数,对调用者无影响
return number;
}
输出:
调用函数前,n 的值是 8
调用函数后,n 的值是 8
结果是 9
传引用
另一方面,在传引用方式中,调用者变量的 引用 被传递给函数。换句话说,被调用的函数操作的是同一份数据。如果被调用的函数修改了参数,调用者的原始数据也会被修改。在 C/C++ 中,数组是通过传引用方式传递的。C/C++ 不允许函数返回数组。
/* 递增数组每个元素的函数 */
#include <iostream>
using namespace std;
// 函数原型
void inc(int array[], int size);
void print(int array[], int size);
// 测试驱动程序
int main() {
int a1[] = {8, 4, 5, 3, 2};
// 递增前
print(a1, 5); // {8,4,5,3,2}
// 执行递增
inc(a1, 5); // 数组通过传引用传递(有副作用)
// 递增后
print(a1, 5); // {9,5,6,4,3}
}
// 函数定义
// 递增给定数组的每个元素
void inc(int array[], int size) { // array[] 不是 const
for (int i = 0; i < size; ++i) {
array[i]++; // 副作用
}
}
// 打印给定数组的内容
void print(int array[], int size) {
cout << "{";
for (int i = 0; i < size; ++i) {
cout << array[i];
if (i < size - 1) {
cout << ",";
}
}
cout << "}" << endl;
}
输出:
{8,4,5,3,2}
{9,5,6,4,3}
在传递引用时,尽可能使用 const
,因为它可以防止你无意中修改参数,并保护你免受许多编程错误的影响。
在线性搜索中,搜索键会与数组中的每个元素依次进行比较。如果找到匹配项,则返回匹配元素的索引;否则返回 -1。线性搜索的时间复杂度为 O(n)。
/* 使用线性搜索在数组中查找给定的键 */
#include <iostream>
using namespace std;
int linearSearch(const int a[], int size, int key);
int main() {
const int SIZE = 8;
int a1[SIZE] = {8, 4, 5, 3, 2, 9, 4, 1};
cout << linearSearch(a1, SIZE, 8) << endl; // 0
cout << linearSearch(a1, SIZE, 4) << endl; // 1
cout << linearSearch(a1, SIZE, 99) << endl; // 8 (未找到)
}
// 在数组中搜索给定的键
// 如果找到,返回数组索引 [0, size-1];否则返回 -1
int linearSearch(const int a[], int size, int key) {
for (int i = 0; i < size; ++i) {
if (a[i] == key) return i;
}
// a[0] = 1;
// 这将导致错误,因为 a[] 是 const,表示只读
return -1;
}
输出:
0
1
-1
你可以通过使用 &
表示的 引用参数 来传递基本类型的参数。
/* 测试通过引用声明传递基本类型参数的传引用 */
#include <iostream>
using namespace std;
int squareByValue (int number); // 传值
void squareByReference (int &number); // 传引用
int main() {
int n1 = 8;
cout << "调用前,值是 " << n1 << endl; // 8
cout << squareByValue(n1) << endl; // 无副作用
cout << "调用后,值是 " << n1 << endl; // 8
int n2 = 9;
cout << "调用前,值是 " << n2 << endl; // 9
squareByReference(n2); // 有副作用
cout << "调用后,值是 " << n2 << endl; // 81
}
// 通过传值传递参数 - 无副作用
int squareByValue (int number) {
return number * number;
}
// 通过声明为引用 (&) 传递参数
// - 对调用者有副作用
void squareByReference (int &number) {
number = number * number;
}
输出:
调用前,值是 8
64
调用后,值是 8
调用前,值是 9
调用后,值是 81
C++ 在库 <cmath>
中提供了许多常用的数学函数。
sin(x), cos(x), tan(x), asin(x), acos(x), atan(x):
参数类型和返回类型为 float, double, long double。
sinh(x), cosh(x), tanh(x):
双曲三角函数。
pow(x, y), sqrt(x):
幂和平方根。
ceil(x), floor(x):
返回浮点数的上限和下限整数。
fabs(x), fmod(x, y):
浮点数绝对值和取模。
exp(x), log(x), log10(x):
指数和对数函数。
cstdlib
头文件(从 C 的 stdlib.h
移植而来)提供了一个函数 rand()
,它生成一个介于 0 和 RAND_MAX
(包括)之间的伪随机整数。
/* 测试随机数生成 */
#include <iostream>
#include <cstdlib> // 用于 rand(), srand()
#include <ctime> // 用于 time()
using namespace std;
int main() {
// rand() 生成一个 [0, RAND_MAX] 之间的随机数
cout << "RAND_MAX 是 " << RAND_MAX << endl; // 32767
// 生成 10 个介于 0 和 99 之间的伪随机数
// 不设置随机数种子。
// 每次运行此程序时,你将得到相同的序列
for (int i = 0; i < 10; ++i) {
cout << rand() % 100 << " "; // 需要 <cstdlib> 头文件
}
cout << endl;
// 使用当前时间作为随机数生成器的种子
srand(time(0)); // 需要 <cstdlib> 和 <ctime> 头文件
// 生成 10 个伪随机数
// 由于当前时间不同,每次运行将得到不同的序列
for (int i = 0; i < 10; ++i) {
cout << rand() % 100 << " "; // 需要 <cstdlib> 头文件
}
cout << endl;
}
输出:
RAND_MAX 是 2147483647
83 86 77 15 93 35 86 92 49 21
29 0 83 60 22 55 97 80 68 87
- name: 检查是否存在关键字
script: |
#!/bin/bash
grep -i 'rand' /home/labex/Code/test.cpp
error: 哎呀!我们发现你没有在 "test.cpp" 中使用 "rand()" 方法。
timeout: 3
使用函数的好处包括: