跳到主要内容

函数

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

函数基础

函数的定义

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

返回类型 函数名(参数列表) {
函数体
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

小结

本章介绍了 C 语言的函数:

  • 函数的定义、声明和调用
  • 参数传递:值传递、指针传递、数组传递
  • 返回值:基本类型、指针、void
  • 变长参数函数
  • 递归的概念和应用
  • 变量的作用域和生命周期
  • 函数指针和回调

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