1.5. 函数
函数是 C 语言中组织代码的基本单元,也是结构化编程的核心。
1.5.1. 函数定义
返回值类型 函数名(参数列表) {
函数体
return 返回值;
}
完整示例
#include <stdio.h>
// 函数声明(原型)
int add(int a, int b);
void print_message(void);
// 函数定义
int add(int a, int b) {
return a + b;
}
void print_message(void) {
printf("Hello from function!\n");
// 无返回值,不需要 return
}
int main(void) {
int sum = add(3, 4); // 调用
printf("Sum = %d\n", sum); // 7
print_message();
return 0;
}
重要
在 C 语言中,函数必须在使用前声明或定义。 通常将函数声明放在文件开头或头文件中。
1.5.2. 参数传递
1.5.2.1. 值传递(传值)
C 语言中**默认都是值传递**:
void swap_wrong(int a, int b) {
int temp = a;
a = b;
b = temp; // 只交换了形参,不影响实参
}
int main(void) {
int x = 3, y = 5;
swap_wrong(x, y);
printf("%d %d\n", x, y); // 3 5(没有交换!)
return 0;
}
1.5.2.2. 地址传递(传指针)
利用指针实现真正的交换:
void swap(int *a, int *b) {
int temp = *a; // 通过指针访问实参
*a = *b;
*b = temp;
}
int main(void) {
int x = 3, y = 5;
swap(&x, &y); // 传递地址
printf("%d %d\n", x, y); // 5 3(成功交换)
return 0;
}
小技巧
传指针是 C 语言中模拟"引用传递"的方式,用于: - 修改调用者的变量 - 避免复制大结构体(提高性能) - 动态内存分配
1.5.2.3. 数组作为参数
// 数组作为参数时退化为指针
// 以下三种声明等价:
int sum(int arr[], int n);
int sum(int *arr, int n);
int sum(int arr[10], int n); // 编译器忽略 10
// 必须同时传递数组长度!
int sum(int arr[], int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += arr[i];
}
return total;
}
int main(void) {
int nums[] = {1, 2, 3, 4, 5};
int result = sum(nums, 5); // 15
return 0;
}
小心
数组参数退化为指针后,函数内 sizeof(arr) 得到的是**指针大小**,不是数组大小。
1.5.3. 返回值
// 返回值
int square(int x) {
return x * x;
}
// 返回指针
int *find_max(int *arr, int n) {
int *max = &arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > *max) max = &arr[i];
}
return max; // 返回指向最大值的指针
}
警告
永远不要返回局部变量的地址!
int *bad_func(void) {
int local = 42;
return &local; // 错误!函数返回后 local 已销毁
}
可用 static 或动态内存分配解决:
int *good_func(void) {
static int value = 42; // 静态变量,生命周期贯穿整个程序
return &value;
}
1.5.4. 函数原型(声明)
// 函数原型告诉编译器函数的存在
int multiply(int x, int y); // 完整原型
int multiply(int, int); // 可省略参数名
// 无参数函数
void version1(void); // 明确表示无参数
void version2(); // 旧式风格,参数不确定(不推荐)
备注
VC10 中 int func() 和 int func(void) 略有不同:
- func(void):明确无参数,调用时传参导致编译错误
- func():参数未指定,调用时传参仅产生警告
1.5.5. 作用域与生命周期
存储类别 |
关键字 |
作用域 |
生命周期 |
初始值 |
|---|---|---|---|---|
自动 |
(无) |
代码块内 |
函数调用期间 |
不确定 |
寄存器 |
|
代码块内 |
函数调用期间 |
不确定 |
静态局部 |
|
代码块内 |
整个程序运行期 |
0 |
全局 |
(无) |
整个文件 |
整个程序运行期 |
0 |
静态全局 |
|
本文件内 |
整个程序运行期 |
0 |
外部 |
|
整个程序 |
整个程序运行期 |
0 |
作用域示例
#include <stdio.h>
int global = 100; // 全局变量(所有文件可见)
static int file_static = 200; // 静态全局变量(仅本文件可见)
void counter(void) {
static int count = 0; // 静态局部变量
count++;
printf("调用次数: %d\n", count);
}
int main(void) {
counter(); // 1
counter(); // 2
counter(); // 3
int local = 50; // 局部变量
printf("global = %d\n", global);
return 0;
}
1.5.6. 递归函数
// 阶乘:n! = n × (n-1)!
long long factorial(int n) {
if (n <= 1) return 1; // 基线条件
return n * factorial(n - 1); // 递归调用
}
// 斐波那契数列
long long fibonacci(int n) {
if (n <= 1) return n; // 基线条件
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main(void) {
printf("5! = %lld\n", factorial(5)); // 120
for (int i = 0; i < 10; i++) {
printf("%lld ", fibonacci(i));
}
printf("\n"); // 0 1 1 2 3 5 8 13 21 34
return 0;
}
小心
递归必须有**基线条件**(终止条件),否则会无限递归导致栈溢出
每次递归调用都消耗栈空间,过深的递归可能导致堆栈溢出
VC10 默认栈大小约 1MB,可通过链接器选项
/STACK调整
1.5.7. inline 函数(C99)
// VC10 支持 __inline 关键字(C99 的 inline)
__inline int max(int a, int b) {
return (a > b) ? a : b;
}
// 使用 inline 减少函数调用开销
// 但编译器是否真正内联由优化级别决定
1.5.8. 函数指针
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int main(void) {
// 函数指针数组
int (*ops[])(int, int) = {add, sub, mul};
int a = 10, b = 5;
printf("add: %d\n", ops[0](a, b)); // 15
printf("sub: %d\n", ops[1](a, b)); // 5
printf("mul: %d\n", ops[2](a, b)); // 50
return 0;
}
小技巧
函数指针常用于:
- 回调函数(如 qsort 的比较函数)
- 策略模式(替换不同的算法实现)
- 状态机转换表