函数
函数是完成特定任务的独立代码块。使用函数可以将复杂的程序分解为小的、可管理的模块,提高代码的复用性和可读性。
函数基础
函数的定义
函数定义包括返回类型、函数名、参数列表和函数体:
返回类型 函数名(参数列表) {
函数体
return 返回值;
}
一个简单的函数示例:
int add(int a, int b) {
return a + b;
}
这个函数:
- 返回类型是
int - 函数名是
add - 接受两个
int类型参数a和b - 返回两数之和
函数的声明与定义
C 语言要求函数在使用前必须声明。函数声明(原型)告诉编译器函数的存在:
#include <stdio.h>
int add(int a, int b); // 函数声明(原型)
int main(void) {
int result = add(3, 5); // 函数调用
printf("结果: %d\n", result);
return 0;
}
int add(int a, int b) { // 函数定义
return a + b;
}
如果函数定义在调用之前,可以省略声明:
#include <stdio.h>
int add(int a, int b) { // 定义在调用之前
return a + b;
}
int main(void) {
int result = add(3, 5);
printf("结果: %d\n", result);
return 0;
}
推荐做法:在头文件或源文件开头声明所有函数,然后在后面定义。
函数调用
调用函数时,需要提供实际参数:
int result = add(3, 5); // 3 和 5 是实参
实参会传递给形参,函数执行完毕后返回结果。
参数传递
值传递
C 语言默认使用值传递,函数内部修改形参不会影响实参:
void increment(int x) {
x = x + 1; // 修改的是形参的副本
printf("函数内: %d\n", x);
}
int main(void) {
int a = 10;
increment(a);
printf("函数外: %d\n", a); // a 仍然是 10
return 0;
}
输出:
函数内: 11
函数外: 10
指针参数
要在函数内修改实参,需要传递指针:
void increment(int* x) {
*x = *x + 1; // 通过指针修改原值
}
int main(void) {
int a = 10;
increment(&a); // 传递地址
printf("a = %d\n", a); // a 变为 11
return 0;
}
指针参数的常见用途:
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 5, y = 10;
swap(&x, &y);
printf("x=%d, y=%d\n", x, y); // x=10, y=5
return 0;
}
数组参数
数组作为参数时,实际上传递的是指针:
int sum(int arr[], int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
int main(void) {
int numbers[] = {1, 2, 3, 4, 5};
int result = sum(numbers, 5);
printf("总和: %d\n", result);
return 0;
}
以下三种写法等价:
int sum(int arr[], int size);
int sum(int arr[5], int size); // 5 会被忽略
int sum(int* arr, int size); // 推荐写法
在函数内修改数组元素会影响原数组:
void double_all(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
const 参数
使用 const 可以防止函数修改参数:
int sum(const int* arr, int size) {
// arr[0] = 0; // 编译错误:不能修改 const 数据
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
返回值
基本返回值
函数可以返回各种类型的值:
int get_max(int a, int b) {
return (a > b) ? a : b;
}
double get_average(int a, int b) {
return (a + b) / 2.0;
}
char get_grade(int score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}
无返回值
返回类型为 void 的函数不返回值:
void print_line(int length) {
for (int i = 0; i < length; i++) {
printf("-");
}
printf("\n");
// 不需要 return,或可以写 return;
}
返回指针
函数可以返回指针,但要注意不要返回局部变量的地址:
int* wrong_function(void) {
int local = 10;
return &local; // 错误:返回局部变量的地址
}
正确的做法:
int* create_int(int value) {
int* ptr = malloc(sizeof(int)); // 动态分配
if (ptr != NULL) {
*ptr = value;
}
return ptr; // 返回堆上的地址
}
// 使用后需要释放
int* p = create_int(10);
printf("%d\n", *p);
free(p);
返回静态变量的地址也是安全的:
char* get_error_message(int code) {
static char buffer[100]; // 静态存储期
sprintf(buffer, "Error code: %d", code);
return buffer;
}
函数参数默认值
C 语言不支持函数参数默认值,但可以通过函数重载或条件判断模拟:
void greet_impl(const char* name, int use_formal) {
if (use_formal) {
printf("Hello, Mr./Ms. %s\n", name);
} else {
printf("Hi, %s\n", name);
}
}
void greet(const char* name) {
greet_impl(name, 0); // 默认非正式
}
void greet_formal(const char* name) {
greet_impl(name, 1); // 正式称呼
}
变长参数
某些函数可以接受可变数量的参数,如 printf。使用 <stdarg.h> 实现变长参数函数:
#include <stdarg.h>
#include <stdio.h>
int sum(int count, ...) {
va_list args;
va_start(args, count); // 初始化
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int); // 获取下一个参数
}
va_end(args); // 清理
return total;
}
int main(void) {
printf("%d\n", sum(3, 1, 2, 3)); // 6
printf("%d\n", sum(5, 1, 2, 3, 4, 5)); // 15
return 0;
}
变长参数的步骤:
- 声明
va_list变量 - 用
va_start初始化,指定最后一个固定参数 - 用
va_arg依次获取参数,指定类型 - 用
va_end清理
变长参数的限制:
- 至少有一个固定参数
- 无法直接知道参数数量(需要额外传递或约定)
- 类型不安全(编译器不检查类型)
递归
递归是函数调用自身的过程。递归函数必须有终止条件。
基本递归
计算阶乘:
int factorial(int n) {
if (n <= 1) { // 终止条件
return 1;
}
return n * factorial(n - 1); // 递归调用
}
// factorial(5) = 5 * factorial(4)
// = 5 * 4 * factorial(3)
// = 5 * 4 * 3 * factorial(2)
// = 5 * 4 * 3 * 2 * factorial(1)
// = 5 * 4 * 3 * 2 * 1
// = 120
斐波那契数列:
int fibonacci(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
递归与迭代
递归代码简洁,但效率可能较低。上面的斐波那契递归有大量重复计算。迭代版本更高效:
int fibonacci_iterative(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
递归示例:汉诺塔
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("移动圆盘 1 从 %c 到 %c\n", from, to);
return;
}
hanoi(n - 1, from, aux, to);
printf("移动圆盘 %d 从 %c 到 %c\n", n, from, to);
hanoi(n - 1, aux, to, from);
}
int main(void) {
hanoi(3, 'A', 'C', 'B');
return 0;
}
尾递归
尾递归是递归调用在函数末尾的情况。编译器可以优化尾递归,避免栈溢出:
int factorial_tail(int n, int accumulator) {
if (n <= 1) {
return accumulator;
}
return factorial_tail(n - 1, n * accumulator);
}
int factorial(int n) {
return factorial_tail(n, 1);
}
作用域与生命周期
局部变量
在函数内部声明的变量是局部变量,作用域限于函数内部:
void function(void) {
int local = 10; // 局部变量
printf("%d\n", local);
}
局部变量在函数调用时创建,函数返回时销毁。
全局变量
在函数外部声明的变量是全局变量,作用域是整个程序:
#include <stdio.h>
int counter = 0; // 全局变量
void increment(void) {
counter++; // 访问全局变量
}
int main(void) {
increment();
increment();
printf("counter = %d\n", counter); // counter = 2
return 0;
}
全局变量在程序开始时创建,程序结束时销毁。
静态变量
static 关键字可以改变变量的生命周期:
void counter_func(void) {
static int count = 0; // 静态局部变量
count++;
printf("调用次数: %d\n", count);
}
int main(void) {
counter_func(); // 调用次数: 1
counter_func(); // 调用次数: 2
counter_func(); // 调用次数: 3
return 0;
}
静态局部变量:
- 作用域仍是局部的
- 生命周期是整个程序运行期间
- 只初始化一次
变量隐藏
局部变量可以隐藏同名的全局变量:
int value = 100; // 全局变量
void func(void) {
int value = 10; // 局部变量隐藏全局变量
printf("局部: %d\n", value); // 10
}
int main(void) {
printf("全局: %d\n", value); // 100
func();
printf("全局: %d\n", value); // 100
return 0;
}
内联函数
inline 关键字建议编译器将函数代码直接嵌入调用处,减少函数调用开销:
static inline int square(int x) {
return x * x;
}
int main(void) {
int result = square(5); // 可能被展开为 5 * 5
return 0;
}
内联函数适合:
- 代码简短的函数
- 频繁调用的函数
注意:inline 只是建议,编译器可能忽略。
函数指针
函数指针是指向函数的指针,可以用于回调、策略模式等场景。
声明与使用
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main(void) {
int (*operation)(int, int); // 声明函数指针
operation = add; // 指向 add 函数
printf("%d\n", operation(5, 3)); // 8
operation = subtract; // 指向 subtract 函数
printf("%d\n", operation(5, 3)); // 2
return 0;
}
函数指针数组
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }
int main(void) {
int (*operations[])(int, int) = {add, subtract, multiply, divide};
char* names[] = {"加", "减", "乘", "除"};
int a = 10, b = 5;
for (int i = 0; i < 4; i++) {
printf("%s: %d\n", names[i], operations[i](a, b));
}
return 0;
}
回调函数
函数指针常用于回调机制:
void process_array(int* arr, int size, int (*transform)(int)) {
for (int i = 0; i < size; i++) {
arr[i] = transform(arr[i]);
}
}
int double_it(int x) { return x * 2; }
int square_it(int x) { return x * x; }
int main(void) {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
process_array(arr, size, double_it);
// arr: {2, 4, 6, 8, 10}
process_array(arr, size, square_it);
// arr: {4, 16, 36, 64, 100}
return 0;
}
使用 typedef 简化
typedef int (*Operation)(int, int);
int add(int a, int b) { return a + b; }
int main(void) {
Operation op = add;
printf("%d\n", op(3, 5));
return 0;
}
main 函数
main 函数是程序的入口点,有两种标准形式:
// 无参数形式
int main(void) {
return 0;
}
// 带命令行参数形式
int main(int argc, char* argv[]) {
return 0;
}
argc 是参数数量,argv 是参数数组:
int main(int argc, char* argv[]) {
printf("程序名: %s\n", argv[0]);
printf("参数数量: %d\n", argc - 1);
for (int i = 1; i < argc; i++) {
printf("参数 %d: %s\n", i, argv[i]);
}
return 0;
}
运行 ./program hello world 输出:
程序名: ./program
参数数量: 2
参数 1: hello
参数 2: world
小结
本章介绍了 C 语言的函数:
- 函数的定义、声明和调用
- 参数传递:值传递、指针传递、数组传递
- 返回值:基本类型、指针、void
- 变长参数函数
- 递归的概念和应用
- 变量的作用域和生命周期
- 函数指针和回调
下一章将学习 数组与字符串,了解如何存储和操作一组数据。