C/C++ 入门:函数

学完变量、运算符和控制流之后,函数就是把一段逻辑整理成“可重复使用的小模块”。

📚 基本概念速读

名称 定义 省流
函数 一段有名字、可以被调用的代码 可复用逻辑
参数 调用函数时传进去的数据 输入
返回值 函数执行完交回来的结果 输出
函数声明 提前告诉编译器函数长什么样 先打招呼
函数定义 真正写出函数体 真正干活
值传递 把实参的值复制一份传给函数 传副本
函数重载 多个函数同名,但参数列表不同 同名不同参
递归 函数自己调用自己 自己拆问题

🧩 函数的基本模型

函数可以先粗略理解成一个加工盒子:给它输入,它执行一段逻辑,然后返回结果。



flowchart LR
    A[参数] --> B[函数体]
    B --> C[返回值]

最小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int add(int a, int b)
{
return a + b;
}

int main()
{
int result = add(3, 5);
cout << result << endl;
return 0;
}

这段代码里:

部分 示例 含义
返回类型 int 函数最后会返回一个整数
函数名 add 调用函数时使用的名字
参数列表 int a, int b 函数需要两个整数输入
函数体 { return a + b; } 函数真正执行的逻辑
函数调用 add(3, 5) 让函数运行一次

🛠️ 函数定义与调用

函数定义

函数定义的一般形式:

1
2
3
4
返回类型 函数名(参数列表)
{
函数体
}

示例:

1
2
3
4
int square(int x)
{
return x * x;
}

square 接收一个整数 x,返回 x * x 的结果。

函数调用

调用函数时,要写函数名,并在括号里放入实际参数:

1
2
int value = square(4);
cout << value << endl; // 16

调用过程可以理解为:

1
2
3
4
5
6
7
8
9
10
square(4)
|
v
x = 4
|
v
return x * x
|
v
返回 16

没有返回值的函数

如果函数只做一件事,不需要返回结果,可以使用 void

1
2
3
4
5
6
7
8
9
10
void printLine()
{
cout << "hello" << endl;
}

int main()
{
printLine();
return 0;
}

void 表示“没有返回值”。这种函数可以直接调用,不需要接收结果。

🧾 函数声明与头文件前置认识

为什么需要函数声明

C/C++ 编译器通常从上到下读取代码。如果在 main 里调用一个后面才定义的函数,编译器可能还不知道这个函数存在。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

int main()
{
cout << add(3, 5) << endl;
return 0;
}

int add(int a, int b)
{
return a + b;
}

这时可以在前面加一个函数声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int add(int a, int b);

int main()
{
cout << add(3, 5) << endl;
return 0;
}

int add(int a, int b)
{
return a + b;
}

int add(int a, int b); 这一行就是声明。它告诉编译器:

后面会有一个叫 add 的函数,它接收两个 int,并返回一个 int

声明和定义的区别

名称 是否有函数体 作用
函数声明 没有 告诉编译器函数接口
函数定义 写出函数实际逻辑
1
2
3
4
5
6
int add(int a, int b);       // 声明

int add(int a, int b) // 定义
{
return a + b;
}

头文件前置认识

实际项目里,函数声明常放在头文件里,比如 math_utils.h

1
2
// math_utils.h
int add(int a, int b);

函数定义放在 .cpp 文件里:

1
2
3
4
5
6
7
// math_utils.cpp
#include "math_utils.h"

int add(int a, int b)
{
return a + b;
}

使用函数的地方包含头文件:

1
2
3
4
5
6
#include "math_utils.h"

int main()
{
return add(3, 5);
}

初学阶段先记住一句话:头文件通常放声明,源文件通常放定义

📦 参数传递

值传递

最常见的是值传递。函数拿到的是实参的一份副本,函数内部修改参数,不会影响外面的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void change(int x)
{
x = 100;
}

int main()
{
int a = 10;
change(a);
cout << a << endl; // 10
return 0;
}

change(a) 调用时,a 的值被复制给 x。修改 x 不等于修改 a

可以这样理解:

1
2
3
4
5
6
7
8
9
a = 10
|
v
复制一份给 x
|
v
x = 100

a 仍然是 10

指针参数预告

如果希望函数能修改外部变量,C 语言里常用指针参数

1
2
3
4
void change(int *p)
{
*p = 100;
}

这里的 p 保存的是变量地址,*p 表示通过地址找到原来的变量。指针涉及地址和内存,后面会单独展开。

引用参数预告

C++ 里还常用引用参数

1
2
3
4
void change(int &x)
{
x = 100;
}

引用可以先理解为“给原变量起了一个别名”。函数里改 x,外面的变量也会被改。引用也会在后续文章里详细讲。

三种传参方式先对比

方式 示例 是否能修改外部变量 初学阶段理解
值传递 int x 传副本
指针参数 int *p 可以 传地址
引用参数 int &x 可以 传别名

🔁 返回值

返回普通值

最常见的返回值就是返回计算结果:

1
2
3
4
5
6
7
int maxValue(int a, int b)
{
if (a > b) {
return a;
}
return b;
}

return 会结束当前函数,并把后面的值交回调用者。

1
2
int m = maxValue(3, 5);
cout << m << endl; // 5

返回状态码

有些函数不只关心“结果是什么”,还关心“是否成功”。这时可以返回状态码。

1
2
3
4
5
6
7
8
9
int divide(int a, int b, int &result)
{
if (b == 0) {
return 1;
}

result = a / b;
return 0;
}

调用时:

1
2
3
4
5
6
7
8
int result = 0;
int code = divide(10, 2, result);

if (code == 0) {
cout << result << endl;
} else {
cout << "divide failed" << endl;
}

这里约定:

返回码 含义
0 成功
0 失败或不同错误类型

这种风格在 C 语言和系统编程里很常见。

避免返回局部对象地址

不要返回局部变量的地址:

1
2
3
4
5
int* bad()
{
int x = 10;
return &x; // 错误示例:不要这样写
}

原因是:x 是函数里的局部变量,函数结束后它的生命周期也结束了。返回 &x 等于把一个已经失效的位置交给外面使用。

可以粗略理解为:

1
2
3
4
5
6
7
8
9
10
11
12
13
进入 bad()
|
v
创建局部变量 x
|
v
返回 x 的地址
|
v
函数结束,x 失效
|
v
外面拿到的是危险地址

初学阶段先记住:不要返回局部变量的指针或引用

🧬 函数重载

同名函数

C++ 允许多个函数使用同一个名字,只要它们的参数列表不同。这叫函数重载

1
2
3
4
5
6
7
8
9
int add(int a, int b)
{
return a + b;
}

double add(double a, double b)
{
return a + b;
}

调用时,编译器会根据参数类型选择合适的版本:

1
2
cout << add(3, 5) << endl;       // 调用 int 版本
cout << add(1.2, 3.4) << endl; // 调用 double 版本

参数匹配

重载主要看参数列表,包括参数个数和参数类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void print(int x)
{
cout << x << endl;
}

void print(double x)
{
cout << x << endl;
}

void print(int x, int y)
{
cout << x << ", " << y << endl;
}

下面三种调用会匹配不同函数:

1
2
3
print(10);
print(3.14);
print(10, 20);

返回类型不能单独构成重载

只改返回类型,不改参数列表,不算合法重载:

1
2
3
4
5
6
7
8
9
int getValue()
{
return 10;
}

// double getValue()
// {
// return 10.0;
// }

原因是调用 getValue() 时,编译器只看调用形式,无法仅靠返回类型稳定判断应该选哪个函数。

二义性

如果多个重载版本都像是“差不多能匹配”,编译器可能报二义性错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void show(long x)
{
cout << x << endl;
}

void show(double x)
{
cout << x << endl;
}

int main()
{
show(10);
return 0;
}

10int,它既可以转换成 long,也可以转换成 double。类似场景可能让编译器难以选择。

避免二义性的简单办法:

做法 示例
让参数类型更明确 show(10L);
手动类型转换 show(static_cast<double>(10));
不写过于接近的重载 避免 longdouble 混在一起

🧠 递归基础

什么是递归

递归就是函数自己调用自己。它适合处理“一个大问题可以拆成更小的同类问题”的场景。

典型例子是阶乘:

1
2
3
4
5
5! = 5 * 4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1! = 1

代码:

1
2
3
4
5
6
7
8
int factorial(int n)
{
if (n == 1) {
return 1;
}

return n * factorial(n - 1);
}

递归出口

递归必须有出口,也就是停止继续调用自己的条件。

1
2
3
if (n == 1) {
return 1;
}

这就是阶乘函数的递归出口。如果没有出口,函数会一直调用自己,直到程序崩溃。

更稳一点的写法通常会处理 0

1
2
3
4
5
6
7
8
int factorial(int n)
{
if (n <= 1) {
return 1;
}

return n * factorial(n - 1);
}

调用栈

每次函数调用,程序都会在调用栈里保存这次调用的现场。递归调用多次,就会一层一层压栈。

factorial(4) 为例:

1
2
3
4
factorial(4)
-> factorial(3)
-> factorial(2)
-> factorial(1)

到达出口后,再一层一层返回:

1
2
3
4
factorial(1) 返回 1
factorial(2) 返回 2 * 1
factorial(3) 返回 3 * 2
factorial(4) 返回 4 * 6

所以 factorial(4) 的结果是 24

典型错误

错误 问题 修正
没有递归出口 无限递归 写清楚停止条件
递归没有逼近出口 参数不变或方向错 每次调用都让问题变小
递归层数太深 调用栈耗尽 改用循环或优化算法
忘记处理边界值 0、负数等输入异常 先判断边界

错误示例:

1
2
3
4
int bad(int n)
{
return bad(n - 1);
}

这个函数永远不会停止。

一个更合理的版本:

1
2
3
4
5
6
7
8
int sumTo(int n)
{
if (n <= 0) {
return 0;
}

return n + sumTo(n - 1);
}

⚠️ 常见误区

误区 正解
函数声明和函数定义是一回事 声明只告诉接口,定义才有函数体
参数传进去后一定会修改原变量 值传递只修改副本
return 之后函数还会继续执行 return 会立刻结束当前函数
返回局部变量地址也能用 局部变量函数结束后失效,不能这样返回
函数重载只看函数名 还要看参数个数和参数类型
递归就是循环的高级写法 递归依赖问题拆分和调用栈,不是无脑替代循环

✅ 总结

函数的核心价值,是把一段逻辑封装成清晰、可复用、可组合的模块。

初学阶段先抓住四件事:函数怎么定义、怎么调用、参数怎么传、结果怎么返回。至于指针、引用、头文件拆分和递归优化,后面可以继续一层层展开。

Happy Hacking! 🎉