C++ 函数基础

C++C++Beginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本实验中,你将学习 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
图片描述

传值 vs. 传引用

参数可以通过两种方式传递给函数:传值传引用

传值

在传值方式中,会创建参数的“副本”并传递给函数。被调用的函数操作的是“克隆”的副本,无法修改原始数据。在 C/C++ 中,基本类型(如 intdouble)是通过传值方式传递的。

/* 基本类型通过传值方式传递给函数 */
#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
图片描述

2.8 数学函数

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

总结

使用函数的好处包括:

  1. 分而治之:通过简单、小的片段或组件构建程序。将程序模块化为自包含的任务。
  2. 避免重复代码:复制和粘贴很容易,但维护和同步所有副本却很困难。
  3. 软件复用:你可以通过将函数打包到库代码中,在其他程序中复用这些函数。

您可能感兴趣的其他 C++ 教程