C++中的函数:编写清晰且可复用代码的关键
你是否注意到,随着程序规模的增长,代码变得越来越难以理解和维护?如果你曾经觉得自己的代码像一个复杂的迷宫,那可能是因为你还没有充分利用C++的函数。函数就像构造模块,允许将程序划分为可管理的部分,使代码更易读、更易维护,并且更优化。在本课程中,你将学习如何有效地使用函数,以改善代码的组织结构,编写更结构化的程序,并使你的C++开发更加专业和高效。
学习目标
完成本课程后,你将学会:
- 理解 函数的目的以及它们在C++中的重要性。
- 创建 正确的函数,确保代码结构清晰。
- 调用 函数并理解它们的执行方式。
- 区分 返回值函数与仅执行指令的函数。
- 比较 不同的函数定义方式,并根据实际情况选择最佳方案。
目录
函数的声明、调用和定义
方法一:先声明 – 调用 – 定义
方法二:声明后先实现再调用
返回值的传递
递归:调用自身的函数
函数的多返回值
函数重载(overloading)
C++中的内联函数(inline functions)
关于C++函数的最终思考
函数的声明、调用和定义
在C++中,函数是可重复使用的代码块,使程序结构更加模块化和有组织。每个函数封装一个特定的任务,有助于提高代码的清晰度和可维护性。要在程序中使用函数,我们需要遵循三个基本步骤:声明、调用和定义。
这三个概念至关重要,每个概念在代码结构中都扮演着特定的角色。让我们详细了解每个部分。
- 函数的声明
在代码中使用函数之前,编译器必须知道它的存在。这是通过函数声明或函数原型实现的。
函数声明向编译器提供三个关键信息:
- 函数返回的数据类型(如果没有返回值,则使用void)。
- 函数的名称。
- 函数的参数列表(如果有),以及它们的数据类型。
函数声明的一般语法如下:
fan_hui_lei_xing han_shu_ming (can_shu_lie_biao);
通常,函数声明放在
main()之前,或者在多文件程序中放入头文件(.h)。 - 函数的调用
声明函数后,我们可以在代码中调用它,使其执行定义的功能。
调用函数时:
- 执行其定义中包含的代码。
- 如果函数有返回值,该值可以存储在变量中或直接用于表达式。
- 如果函数是
void类型,它只执行指令,不返回任何值。
函数调用的语法很简单,只需写出函数名,并在括号内提供参数(如果需要):
han_shu_ming(can_shu);
- 函数的定义
函数定义是实现其功能的地方。这里指定了在调用函数时执行的指令。
函数定义的一般语法如下:
fan_hui_lei_xing han_shu_ming (can_shu_lie_biao) { // han_shu_ti: yao_zhi_xing_de_zhi_ling return zhi; // (ru_guo_han_shu_you_fan_hui_zhi) }每个函数定义必须遵循以下规则:
- 如果函数已声明,则定义必须与声明匹配。
- 如果函数返回一个值(例如
int),必须包含return语句,并返回一个值。 - 如果函数没有返回值(
void),它只需执行代码块内的指令,无需return。
执行流程
当程序运行时,函数按照它们在main()中的调用顺序执行。执行流程如下:
- 编译器识别函数声明。
- 在
main()中,遇到函数调用时,程序控制流跳转到函数定义。 - 执行函数中的指令。
- 如果函数有返回值,该值会返回到调用函数的代码位置。
- 程序流程返回到
main()或调用它的函数。
提前声明的重要性
在使用函数之前声明它是至关重要的,因为C++编译器按自上而下的顺序解析代码。如果在定义或声明之前调用函数,会导致错误。
主要有两种方式解决这个问题:
- 在
main()之前声明函数,并在之后定义它(如前面介绍的方式)。 - 在
main()之前定义函数,避免提前声明。
这两种方法都可以,但第一种方法在大型程序中更有用,尤其是涉及多个文件时。
方法:声明 – 调用 – 定义
在C++中,组织函数的一种常见方法是声明 – 调用 – 定义。 按照这种逻辑,我们可以将代码分为三个基本阶段:
- 声明: 在使用函数之前,向编译器声明其存在,指定函数名、返回类型及参数(如果有)。
- 调用: 在代码主函数(通常是
main())中调用函数,执行其功能。 - 定义: 详细实现函数,指定当函数被调用时执行的指令。
这种结构有助于提高代码的组织性,使其更易维护和扩展。让我们来看一个示例,该示例采用这种方法:
示例:函数 consoladice()
在下面的代码中,我们遵循声明 – 调用 – 定义的顺序:
#include <iostream>
using namespace std;
// shou xian sheng ming han shu
void consoladice();
int main() {
// diao yong han shu
consoladice();
return 0;
}
// ding yi xian qian sheng ming de han shu, xiang xi qi nei bu shi xian
void consoladice() {
cout << "zhe shi yi tiao jian dan de wen ben huo zifu chuan." << endl;
cout << "xian zai wo gei ni kan yi ge shu zi wu. zhe li shi: " << 5 << endl;
cout << "kan kan 10/5 de jie guo shi shen me. jie guo shi: " << 10/5 << endl;
cout << "yi zhong chang jian de pi de jin si shi 22/7. jie guo shi: " << 22/7 << endl;
cout << "zai C++ zhong, 22/7 he 22.0/7 bing bu yi yang." << endl;
cout << "zhi yao zuo zhe yang de xiao bian hua, wo men ke yi kan dao 22.0/7 de jie guo shi " << 22.0/7 << endl;
cout << "ni bu jue de zhe yang de jie guo geng hao ma?" << endl;
}
该代码的预期输出为:
zhe shi yi tiao jian dan de wen ben huo zifu chuan.
xian zai wo gei ni kan yi ge shu zi wu. zhe li shi: 5
kan kan 10/5 de jie guo shi shen me. jie guo shi: 2
yi zhong chang jian de pi de jin si shi 22/7. jie guo shi: 3
zai C++ zhong, 22/7 he 22.0/7 bing bu yi yang.
zhi yao zuo zhe yang de xiao bian hua, wo men ke yi kan dao 22.0/7 de jie guo shi 3.14286
ni bu jue de zhe yang de jie guo geng hao ma?
为了更好地理解“声明 – 调用 – 定义”方法,让我们关注代码的三个关键部分:
- 第5行:函数声明
void consoladice();告诉编译器代码中存在一个名为consoladice()的函数。- 其返回类型为
void,表示该函数不返回任何值。 - 尽管此时尚未实现
consoladice()的功能,但这个声明使编译器在之后的代码中识别该函数。
- 第9行:函数调用
- 在
main()函数内部,语句consoladice();触发该函数的执行。 - 由于之前的声明,编译器已知道
consoladice()的存在。 - 当调用该函数时,程序控制流跳转到其定义处,并执行其内容。
- 在
- 第14至22行:函数定义
- 在这里,
consoladice()的实现被详细定义。 - 该函数在控制台上输出多个信息,包括文本、数字和数学计算结果。
- 值得注意的是
22/7和22.0/7之间的区别。当使用22/7时,两个数都是整数,因此执行的是整数除法,结果为3。但当使用22.0/7时,运算会自动转换为浮点运算,结果为3.14286。
- 在这里,
方法:先声明并实现,再调用
在C++中,除了声明 – 调用 – 定义的方法外,还有另一种组织函数的方式:先声明并实现,再调用。 这种方法在定义函数时,不需要单独的声明,而是在main()之前直接定义函数。
在此方法中,我们不需要先声明函数再在main()后面定义,而是直接在main()之前声明并实现函数。这种方式的优点是代码更加紧凑,在小型程序中更容易阅读。
此方法的一般结构如下:
// zai main() qian ding yi han shu
fan_hui_lei_xing han_shu_ming(can_shu_lie_biao) {
// han_shu_ti
return zhi; // ru_guo_xu_yao
}
int main() {
// diao yong han shu
han_shu_ming(can_shu);
}
由于函数在main()之前已定义,编译器在调用它时已经知道其存在,因此无需单独声明。
示例:不需要单独声明的 consoladice() 函数
现在来看一个实际示例,应用这种方法:
#include <iostream>
using namespace std;
// zai diao yong zhi qian ding yi han shu
void consoladice() {
cout << "zhe shi yi tiao jian dan de wen ben huo zifu chuan." << endl;
cout << "xian zai wo gei ni kan yi ge shu zi wu. zhe li shi: " << 5 << endl;
cout << "kan kan 10/5 de jie guo shi shen me. jie guo shi: " << 10/5 << endl;
cout << "yi zhong chang jian de pi de jin si shi 22/7. jie guo shi: " << 22/7 << endl;
cout << "zai C++ zhong, 22/7 he 22.0/7 bing bu yi yang." << endl;
cout << "zhi yao zuo zhe yang de xiao bian hua, wo men ke yi kan dao 22.0/7 de jie guo shi " << 22.0/7 << endl;
cout << "ni bu jue de zhe yang de jie guo geng hao ma?" << endl;
}
int main() {
// diao yong han shu
consoladice();
return 0;
}
在该代码中,我们可以看到:
- 在第5至13行,函数的声明和定义合并
consoladice()直接在main()之前定义,无需额外声明。 - 在第17行,函数在
main()内被调用由于该函数已被定义,编译器能正确解析并执行它。
- 运行结果完全相同
与“声明 – 调用 – 定义”方法相比,该方法的执行结果完全相同,但代码结构更加紧凑。
✅ 优势:
- 在小型程序中,代码更加简洁明了。
- 无需额外声明,减少代码行数。
- 在所有函数都位于同一文件的小型脚本中,便于阅读。
❌ 劣势:
- 在大型程序中,如果
main()之前定义了大量函数,可能会影响代码的组织性。 - 如果使用多个文件(.h 和 .cpp),这种方法不太适用,通常建议将声明和实现分离。
返回值的传递
到目前为止,我们已经学习了执行指令但不返回任何值的函数。然而,在许多情况下,函数需要返回一个值,以便用于其他计算或存储到变量中。这个过程称为返回值的传递。
在本节中,我们将学习返回值函数的工作原理,它们与void类型函数的区别,以及如何在C++中利用这一机制。
实践示例:计算矩形的面积
为了演示返回值的传递,我们将实现一个函数,该函数接收矩形的底边和高,并计算其面积。
#include <iostream>
using namespace std;
// han shu ji suan ju xing de mian ji, bing fan hui jie guo
double calcularArea(double base, double altura) {
return base * altura;
}
int main() {
double base, altura;
// qing qiu yong hu shu ru shu ju
cout << "qing shu ru ju xing de di bian: ";
cin >> base;
cout << "qing shu ru ju xing de gao: ";
cin >> altura;
// diao yong han shu, jiang jie guo cun chu dao bian liang zhong
double area = calcularArea(base, altura);
// xian shi jie guo
cout << "ju xing de mian ji shi: " << area << endl;
return 0;
}
- 函数
calcularArea()返回一个值- 函数接收两个参数:
base(底边)和altura(高)。 - 通过乘法计算面积:
base * altura。 - 使用
return语句将计算结果返回给调用它的程序部分。
- 函数接收两个参数:
- 在
main()中使用返回值- 用户输入
base和altura的值。 - 调用函数
calcularArea(),并将其返回值存储在变量area中。 - 最终,在控制台上显示计算结果。
- 用户输入
- 与
void函数的关键区别如果
calcularArea()是void类型,我们需要在函数内部直接输出结果,而不是返回计算结果以供main()使用。
示例:判断一个数是偶数还是奇数
#include <iostream>
using namespace std;
bool esPar(int numero) {
return numero % 2 == 0;
}
int main() {
int numero;
cout << "qing shu ru yi ge shu: "; cin >> numero;
if (esPar(numero)) {
cout << "zhe ge shu shi ou shu." << endl;
} else {
cout << "zhe ge shu shi ji shu." << endl;
}
return 0;
}
在这个示例中,函数 esPar() 返回 true(如果输入的数是偶数)或 false(如果输入的数是奇数)。main() 使用该返回值来决定输出不同的消息。
递归:调用自身的函数
递归 是一种技术,其中函数调用自身以解决问题,将问题拆分为更小的版本。这在计算阶乘、斐波那契数列以及遍历树等数据结构的算法中特别有用。
示例:计算一个数的阶乘
一个数的阶乘 n 计算如下:n!=n\cdot(n-1)\cdot(n-2) \cdots 3 \cdot2 \cdot 1。这种定义本身就具有递归特性,我们可以用数学公式表示如下:
\begin{array}{rl} 0! &=1\\ n! &= n\cdot(n-1)!\\ \end{array}
基于这个数学定义,我们可以用C++编写如下递归函数:
#include <iostream>
using namespace std;
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
}
return n*factorial(n - 1);
}
int main() {
int numero;
cout << "qing shu ru yi ge shu: "; cin >> numero;
cout << "jie cheng de " << numero << " shi " << factorial(numero) << endl;
return 0;
}
在此代码中:
- 函数
factorial(n)调用自身,传入n-1,直到达到基准情况(n == 0或n == 1)。 - 该函数通过递归方式计算,将所有值相乘,最终获得结果。
示例:斐波那契数列
斐波那契数列是一个递增序列:1, 1, 2, 3, 5, 8, 13, \cdots。该序列的特点是每个数等于前两个数之和。
如果用 fibo(n) 表示斐波那契函数,则可以用以下递归数学定义:
\begin{array}{rl} fibo(0) &= 1\\ fibo(1) &= 1 \\ fibo(n) &= fibo(n-1) + fibo(n-2) \end{array}
下面是一个C++程序,用递归方式计算斐波那契数列:
#include<iostream>
using namespace std;
int fibo(int numero){
if (numero==0||numero==1){
return 1;
}
return fibo(numero-1)+fibo(numero-2);
}
int main(){
int x=0, i=0;
cout << "qing shu ru yi ge shu: "; cin >> x;
while (i < x){
cout <<"di " << i+1 << " ge wei zhi de fei bo na qi shu shi: " << fibo(i)<<endl;
i=i+1;
}
}
函数的多重返回值
在C++中,函数可以使用std::pair、std::tuple 或变量引用来返回多个值。
示例:使用 std::pair 返回两个值
#include <iostream>
#include <utility> // shi yong std::pair
using namespace std;
pair<int, int> dividir(int a, int b) {
return make_pair(a / b, a % b);
}
int main() {
int numerador=0, denominador=1;
cout << "qing shu ru chu zi: "; cin >> numerador;
cout << "qing shu ru mu zi: "; cin >> denominador;
pair<int, int> resultado = dividir(numerador, denominador);
cout << "shang: " << resultado.first << endl;
cout << "yu shu: " << resultado.second << endl;
return 0;
}
在这里,函数dividir() 返回两个值:整数除法的商和余数。
示例:使用 std::tuple 返回多个值
#include <iostream>
#include <tuple>
using namespace std;
tuple<int, int, int> operaciones(int a, int b) {
return make_tuple(a + b, a - b, a * b);
}
int main() {
int suma=0, resta=0, producto=0;
int a=0, b=0;
cout << "qing shu ru yi ge shu: "; cin >> a;
cout << "qing zai shu ru yi ge shu: "; cin >> b;
std::tie(suma, resta, producto) = operaciones(a, b);
cout << "jia fa: " << suma << ", jian fa: " << resta << ", cheng fa: " << producto << endl;
return 0;
}
函数重载(overloading)
函数重载允许定义多个同名函数,但参数的类型或数量不同。这提高了代码的可读性和可重用性。
#include <iostream>
#include <string> // shi yong std::string
#include <cmath>
using namespace std;
// zheng fang xing huo yuan quan de mian ji (yi ge shu ju)
double area(double lado) {
return lado * lado;
}
// ju xing de mian ji (liang ge shu ju)
double area(double base, double altura) {
return base * altura;
}
// san jiao xing de mian ji (san ge shu ju)
double area(double a, double b, double c){
return 0.25*sqrt((a+b+c)*(a+b-c)*(a-b+c)*(-a+b+c));
}
int main() {
string figura;
double resultado = 0;
double l1=0, l2=0, l3=0;
// wen ti xing zhuang
cout << "qing shu ru xing zhuang? (zhengfangxing, yuanquan, juxing huo sanjiaoxing): ";
cin >> figura;
// pan duan xing zhuang
if (figura == "zhengfangxing") {
cout << "bian chang shi duo shao? "; cin >> l1;
resultado = area(l1);
cout << "zheng fang xing de mian ji shi: " << resultado << endl;
}
else if (figura == "juxing"){
cout << "ji shu ru di bian: "; cin >> l1;
cout << "ji shu ru gao: "; cin >> l2;
resultado = area(l1, l2);
cout << "ju xing de mian ji shi: " << resultado << endl;
}
else if (figura == "yuanquan") {
l1 = 3.141592653;
cout << "shu ru ban jing: "; cin >> l2;
resultado = area(l1, l2);
cout << "yuan quan de mian ji shi: " << resultado << endl;
}
else if (figura == "sanjiaoxing"){
cout << "shu ru san ge bian chang:" << endl;
cout << "bian 1: "; cin >> l1;
cout << "bian 2: "; cin >> l2;
cout << "bian 3: "; cin >> l3;
if ((l1+l2+l3)*(l1+l2-l3)*(l1-l2+l3)*(-l1+l2+l3)<0){
cout << "zhe ge san jiao xing wu fa shi xian";
}
else {
resultado = area(l1,l2,l3);
cout << "san jiao xing de mian ji shi: " << resultado << endl;
}
}
else {
cout << "bu he fa de xing zhuang." << endl;
}
return 0;
}
C++中的内联函数(inline functions)
C++中的inline函数提供了一种优化程序性能的机制,减少了函数调用的开销。与传统的函数调用不同,编译器会尝试在调用的地方直接展开函数代码,而不是跳转到函数的存储位置执行。
内联函数的语法
inline fan_hui_lei_xing han_shu_ming(can_shu_lie_biao) {
// han_shu_ti
return zhi; // ru_guo_xu_yao
}
使用inline后,程序不需要跳转到另一个内存地址执行函数,从而减少执行时间。
内联函数 vs 传统函数
| 特性 | 传统函数 | 内联函数 |
|---|---|---|
| 函数调用 | 程序执行时需要跳转到函数地址。 | 编译器直接将函数代码插入调用处。 |
| 运行时间 | 由于调用开销,可能稍慢。 | 对于小型函数,可以加快执行速度。 |
| 内存使用 | 函数代码仅存储一次。 | 如果使用过多,会导致可执行文件变大。 |
内联函数示例
#include <iostream>
using namespace std;
inline int cuadrado(int x) {
return x * x;
}
int main() {
cout << "shu zi 5 de ping fang shi: " << cuadrado(5) << endl;
return 0;
}
🔍 编译器执行过程:
- 编译器用
5 * 5直接替换cuadrado(5)。 - 不需要函数调用跳转。
- 计算在调用位置直接完成,提高性能。
inline 的优缺点
✅ 优势
- 消除函数调用开销: 在小型函数和频繁调用的函数中减少执行时间。
- 编译器可进行优化: 避免使用CPU寄存器和栈,提高效率。
- 保证函数代码在编译期可用。
❌ 劣势
- 可能增加二进制文件大小: 如果
inline函数在程序中被多次调用,代码会在多个地方展开,导致可执行文件增大。 - 编译器可能会忽略
inline请求: 当编译器认为函数太大或不适合内联展开时,可能会忽略inline修饰符。
