函数
函数是完成特定任务的独立代码块。使用函数可以将复杂的程序分解为小的、可管理的模块,提高代码的复用性和可读性。
函数基础
函数的定义
函数定义包括返回类型、函数名、参数列表和函数体:
返回类型 函数名(参数列表) {
函数体
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
函数设计原则
良好的函数设计是编写可维护代码的基础。以下是经过实践验证的设计原则。
单一职责原则
每个函数应该只做一件事,并把它做好。
// 不好:一个函数做太多事情
void process_file(const char* filename) {
FILE* fp = fopen(filename, "r");
if (!fp) return;
char line[256];
int count = 0;
int sum = 0;
while (fgets(line, sizeof(line), fp)) {
count++;
int value = atoi(line);
sum += value;
}
fclose(fp);
double average = count > 0 ? (double)sum / count : 0;
printf("行数: %d, 总和: %d, 平均值: %.2f\n", count, sum, average);
}
// 好:拆分成多个职责单一的函数
int count_lines(FILE* fp) {
int count = 0;
char line[256];
long pos = ftell(fp);
while (fgets(line, sizeof(line), fp)) count++;
fseek(fp, pos, SEEK_SET);
return count;
}
int sum_values(FILE* fp) {
int sum = 0;
char line[256];
while (fgets(line, sizeof(line), fp)) {
sum += atoi(line);
}
rewind(fp);
return sum;
}
typedef struct {
int count;
int sum;
double average;
} FileStats;
FileStats analyze_file(const char* filename) {
FileStats stats = {0};
FILE* fp = fopen(filename, "r");
if (!fp) return stats;
stats.count = count_lines(fp);
stats.sum = sum_values(fp);
stats.average = stats.count > 0 ? (double)stats.sum / stats.count : 0.0;
fclose(fp);
return stats;
}
函数参数设计
参数数量控制:
函数参数不宜过多,一般不超过 4 个。如果参数过多,考虑使用结构体:
// 参数过多,难以阅读和维护
void draw_rect(int x, int y, int width, int height,
int color, int border_width, int border_color,
int fill_style, int corner_radius);
// 使用结构体封装
typedef struct {
int x, y;
int width, height;
int color;
int border_width;
int border_color;
int fill_style;
int corner_radius;
} RectParams;
void draw_rect(const RectParams* params);
输入/输出参数顺序:
遵循"输入在前,输出在后"的惯例:
// 输入参数在前,输出参数在后
int parse_int(const char* str, int* result); // 好
int parse_int(int* result, const char* str); // 不推荐
使用 const 标记只读参数:
// 明确表示不会修改输入数据
size_t count_occurrences(const char* str, char ch);
int compare_strings(const char* s1, const char* s2);
错误处理模式
C 语言没有异常机制,需要显式处理错误。以下是几种常见的错误处理模式。
返回值表示成功/失败:
// 返回 0 表示成功,非 0 表示错误
int create_file(const char* filename, const char* content) {
if (filename == NULL || content == NULL) {
return -1; // 无效参数
}
FILE* fp = fopen(filename, "w");
if (fp == NULL) {
return -2; // 文件打开失败
}
if (fputs(content, fp) < 0) {
fclose(fp);
return -3; // 写入失败
}
fclose(fp);
return 0; // 成功
}
// 使用
int result = create_file("test.txt", "Hello");
if (result != 0) {
fprintf(stderr, "创建文件失败,错误码: %d\n", result);
}
通过指针参数返回错误:
// 函数返回计算结果,错误通过参数返回
double divide(double a, double b, int* error) {
if (b == 0.0) {
if (error) *error = 1; // 除零错误
return 0.0;
}
if (error) *error = 0; // 成功
return a / b;
}
// 使用
int err;
double result = divide(10.0, 2.0, &err);
if (err) {
printf("计算错误\n");
}
使用 errno:
标准库使用的错误报告机制:
#include <errno.h>
#include <string.h>
long read_file_size(const char* filename) {
FILE* fp = fopen(filename, "r");
if (fp == NULL) {
errno = ENOENT; // 设置错误码
return -1;
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fclose(fp);
return size;
}
// 使用
errno = 0; // 调用前清零
long size = read_file_size("test.txt");
if (size < 0) {
printf("错误: %s\n", strerror(errno));
}
错误码枚举:
typedef enum {
ERR_OK = 0,
ERR_NULL_POINTER,
ERR_OUT_OF_MEMORY,
ERR_FILE_NOT_FOUND,
ERR_INVALID_ARGUMENT,
ERR_OVERFLOW
} ErrorCode;
const char* error_message(ErrorCode code) {
static const char* messages[] = {
"成功",
"空指针",
"内存不足",
"文件未找到",
"无效参数",
"溢出"
};
return messages[code];
}
ErrorCode safe_divide(int a, int b, int* result) {
if (result == NULL) return ERR_NULL_POINTER;
if (b == 0) return ERR_INVALID_ARGUMENT;
if (a == INT_MIN && b == -1) return ERR_OVERFLOW;
*result = a / b;
return ERR_OK;
}
// 使用
int result;
ErrorCode err = safe_divide(10, 2, &result);
if (err != ERR_OK) {
fprintf(stderr, "错误: %s\n", error_message(err));
}
高级回调模式
回调函数是 C 语言实现多态和扩展性的重要手段。
事件驱动模式
// 定义事件类型
typedef enum {
EVENT_CLICK,
EVENT_KEY_PRESS,
EVENT_MOUSE_MOVE
} EventType;
// 定义事件数据
typedef struct {
EventType type;
int x, y; // 坐标
int key_code; // 按键码
} Event;
// 定义回调类型
typedef void (*EventHandler)(const Event* event, void* user_data);
// 事件处理器注册表
typedef struct {
EventHandler handlers[3];
void* user_data[3];
} EventSystem;
void register_handler(EventSystem* sys, EventType type,
EventHandler handler, void* user_data) {
sys->handlers[type] = handler;
sys->user_data[type] = user_data;
}
void dispatch_event(EventSystem* sys, const Event* event) {
if (sys->handlers[event->type]) {
sys->handlers[event->type](event, sys->user_data[event->type]);
}
}
// 使用示例
void on_click(const Event* event, void* user_data) {
printf("点击位置: (%d, %d)\n", event->x, event->y);
}
void on_key(const Event* event, void* user_data) {
printf("按键: %d\n", event->key_code);
}
int main(void) {
EventSystem sys = {0};
register_handler(&sys, EVENT_CLICK, on_click, NULL);
register_handler(&sys, EVENT_KEY_PRESS, on_key, NULL);
Event click = {EVENT_CLICK, 100, 200, 0};
dispatch_event(&sys, &click);
return 0;
}
插件架构
使用回调实现简单的插件系统:
// 定义插件接口
typedef struct Plugin Plugin;
typedef const char* (*PluginGetNameFunc)(void);
typedef int (*PluginExecuteFunc)(const char* input, char* output, size_t size);
typedef struct {
PluginGetNameFunc get_name;
PluginExecuteFunc execute;
} PluginVTable;
struct Plugin {
const PluginVTable* vtable;
void* internal_data;
};
// 插件管理器
#define MAX_PLUGINS 10
typedef struct {
Plugin plugins[MAX_PLUGINS];
int count;
} PluginManager;
void register_plugin(PluginManager* mgr, Plugin plugin) {
if (mgr->count < MAX_PLUGINS) {
mgr->plugins[mgr->count++] = plugin;
}
}
Plugin* find_plugin(PluginManager* mgr, const char* name) {
for (int i = 0; i < mgr->count; i++) {
if (strcmp(mgr->plugins[i].vtable->get_name(), name) == 0) {
return &mgr->plugins[i];
}
}
return NULL;
}
// 示例插件实现
const char* uppercase_get_name(void) { return "uppercase"; }
int uppercase_execute(const char* input, char* output, size_t size) {
size_t len = strlen(input);
if (len >= size) return -1;
for (size_t i = 0; i < len; i++) {
output[i] = toupper((unsigned char)input[i]);
}
output[len] = '\0';
return 0;
}
const PluginVTable uppercase_vtable = {
.get_name = uppercase_get_name,
.execute = uppercase_execute
};
策略模式
通过函数指针实现算法的可替换性:
// 定义排序策略
typedef int (*CompareFunc)(const void*, const void*);
typedef void (*SortFunc)(void* arr, size_t n, size_t size, CompareFunc cmp);
// 不同的排序策略
void bubble_sort_impl(void* arr, size_t n, size_t size, CompareFunc cmp);
void quick_sort_impl(void* arr, size_t n, size_t size, CompareFunc cmp);
typedef struct {
const char* name;
SortFunc sort;
} SortStrategy;
// 根据数据特点选择策略
SortStrategy* select_sort_strategy(size_t n, int nearly_sorted) {
static SortStrategy strategies[] = {
{"bubble", bubble_sort_impl},
{"quick", quick_sort_impl}
};
// 小规模或基本有序的数据用冒泡排序
if (n < 100 || nearly_sorted) {
return &strategies[0];
}
return &strategies[1];
}
高阶函数模式
虽然 C 不是函数式语言,但可以通过函数指针模拟高阶函数。
函数组合
typedef int (*IntFunc)(int);
// 组合两个函数:compose(f, g)(x) = f(g(x))
IntFunc compose(IntFunc f, IntFunc g) {
// 需要静态存储或动态分配来保存组合
// 这里展示概念,实际实现需要考虑线程安全
static IntFunc saved_f, saved_g;
static int composed(int x) {
return saved_f(saved_g(x));
}
saved_f = f;
saved_g = g;
return composed;
}
// 使用
int add_one(int x) { return x + 1; }
int double_it(int x) { return x * 2; }
IntFunc add_then_double = compose(double_it, add_one);
int result = add_then_double(5); // (5 + 1) * 2 = 12
柯里化模拟
// 原始函数
int add(int a, int b) { return a + b; }
// 柯里化包装器
typedef struct {
int first_arg;
int (*apply)(int, int);
} CurriedAdd;
CurriedAdd add_curry(int a) {
CurriedAdd ca = {a, add};
return ca;
}
int curried_apply(CurriedAdd ca, int b) {
return ca.apply(ca.first_arg, b);
}
// 使用
CurriedAdd add5 = add_curry(5);
int result = curried_apply(add5, 3); // 8
映射和过滤
// 对数组每个元素应用函数
void array_map(int* arr, size_t n, int (*func)(int)) {
for (size_t i = 0; i < n; i++) {
arr[i] = func(arr[i]);
}
}
// 过滤数组,返回新长度
size_t array_filter(int* arr, size_t n, int (*predicate)(int)) {
size_t write = 0;
for (size_t i = 0; i < n; i++) {
if (predicate(arr[i])) {
arr[write++] = arr[i];
}
}
return write;
}
// 折叠/归约
int array_reduce(const int* arr, size_t n, int initial, int (*func)(int, int)) {
int result = initial;
for (size_t i = 0; i < n; i++) {
result = func(result, arr[i]);
}
return result;
}
// 使用示例
int square(int x) { return x * x; }
int is_even(int x) { return x % 2 == 0; }
int add_op(int a, int b) { return a + b; }
int main(void) {
int arr[] = {1, 2, 3, 4, 5, 6};
size_t n = sizeof(arr) / sizeof(arr[0]);
// 映射:平方
array_map(arr, n, square);
// arr: {1, 4, 9, 16, 25, 36}
// 过滤:保留偶数
n = array_filter(arr, n, is_even);
// arr: {4, 16, 36}, n = 3
// 归约:求和
int sum = array_reduce(arr, n, 0, add_op);
// sum = 56
return 0;
}
小结
本章介绍了 C 语言的函数:
- 函数的定义、声明和调用
- 参数传递:值传递、指针传递、数组传递
- 返回值:基本类型、指针、void
- 变长参数函数
- 递归的概念和应用
- 变量的作用域和生命周期
- 函数指针和回调
- 函数设计原则:单一职责、参数设计
- 错误处理模式:返回值、errno、错误码
- 高级回调模式:事件驱动、插件架构、策略模式
- 高阶函数模式:映射、过滤、归约
下一章将学习 数组与字符串,了解如何存储和操作一组数据。