跳到主要内容

函数

函数是完成特定任务的独立代码块。使用函数可以将复杂的程序分解为小的、可管理的模块,提高代码的复用性和可读性。

函数基础

函数的定义

函数定义包括返回类型、函数名、参数列表和函数体:

返回类型 函数名(参数列表) {
函数体
return 返回值;
}

一个简单的函数示例:

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

这个函数:

  • 返回类型是 int
  • 函数名是 add
  • 接受两个 int 类型参数 ab
  • 返回两数之和

函数的声明与定义

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;
}

变长参数的步骤:

  1. 声明 va_list 变量
  2. va_start 初始化,指定最后一个固定参数
  3. va_arg 依次获取参数,指定类型
  4. 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、错误码
  • 高级回调模式:事件驱动、插件架构、策略模式
  • 高阶函数模式:映射、过滤、归约

下一章将学习 数组与字符串,了解如何存储和操作一组数据。