C语言系统学习系列 - 第2篇
在掌握了C语言基础语法后,本文将深入探讨函数设计和模块化编程,这是编写高质量、可维护代码的关键技能。

前言

函数是C语言程序的基本构建块,也是实现代码复用和模块化的核心机制。良好的函数设计不仅能提高代码的可读性和可维护性,还能显著提升开发效率。

为什么需要函数?

想象一下,如果我们要编写一个学生管理系统,需要多次计算学生的平均成绩。如果每次都重复编写相同的计算代码,不仅浪费时间,还容易出错。函数就像是一个”代码工厂”,我们定义一次,就可以在任何需要的地方调用。

本文学习目标:

  • 掌握函数的设计原则和最佳实践
  • 理解参数传递机制(值传递vs引用传递)
  • 学会使用递归解决复杂问题
  • 掌握变量的作用域和生命周期
  • 学会模块化编程和代码组织

前置知识:
本文假设您已经掌握了C语言的基本语法、数据类型和控制结构。

上一篇:C语言完全入门指南

一、函数基础与设计原则

(一)函数的基本概念

1. 函数的组成要素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <math.h>

// 函数声明(函数原型)
double calculate_circle_area(double radius);
void print_circle_info(double radius);
int get_max(int a, int b);

/*
* 函数定义的完整结构:
* 返回类型 函数名(参数列表) {
* 函数体
* return 返回值; // 如果返回类型不是void
* }
*/

// 计算圆的面积
double calculate_circle_area(double radius) {
// 输入验证:确保半径为正数
if (radius <= 0) {
printf("错误:半径必须为正数\n");
return -1; // 返回错误标识
}

// 计算并返回面积
return M_PI * radius * radius;
}

// 打印圆的详细信息(无返回值函数)
void print_circle_info(double radius) {
if (radius <= 0) {
printf("无效的半径值\n");
return; // void函数可以使用空return提前退出
}

double area = calculate_circle_area(radius);
double circumference = 2 * M_PI * radius;

printf("圆的信息:\n");
printf(" 半径: %.2f\n", radius);
printf(" 面积: %.2f\n", area);
printf(" 周长: %.2f\n", circumference);
}

// 返回两个数中的较大值
int get_max(int a, int b) {
return (a > b) ? a : b; // 使用三元运算符简化代码
}

int main(void) {
double radius = 5.0;

printf("=== 函数基础演示 ===\n");

// 调用函数
double area = calculate_circle_area(radius);
printf("半径为 %.1f 的圆面积: %.2f\n", radius, area);

// 调用无返回值函数
print_circle_info(radius);

// 函数调用可以作为表达式的一部分
int x = 10, y = 20;
printf("max(%d, %d) = %d\n", x, y, get_max(x, y));

return 0;
}

2. 函数设计的SOLID原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

// 单一职责原则:每个函数只做一件事
// 好的设计:职责单一的函数
bool is_valid_email(const char* email) {
// 简化的邮箱验证:检查是否包含@符号
return strchr(email, '@') != NULL;
}

bool is_valid_age(int age) {
return age >= 0 && age <= 150;
}

void print_validation_result(const char* item, bool is_valid) {
printf("%s: %s\n", item, is_valid ? "有效" : "无效");
}

// 开放封闭原则:函数应该对扩展开放,对修改封闭
// 使用函数指针实现可扩展的验证系统
typedef bool (*validator_func)(const char*);

bool validate_not_empty(const char* str) {
return str != NULL && strlen(str) > 0;
}

bool validate_max_length(const char* str) {
return str != NULL && strlen(str) <= 50;
}

// 通用验证函数,可以接受不同的验证器
bool validate_string(const char* str, validator_func validator) {
return validator(str);
}

// 接口隔离原则:函数接口应该精简,不强迫调用者依赖不需要的参数
// 好的设计:参数精简的函数
double calculate_simple_interest(double principal, double rate, double time) {
return principal * rate * time / 100;
}

// 依赖倒置原则:高层模块不应该依赖低层模块,都应该依赖抽象
// 使用回调函数实现依赖倒置
typedef void (*output_func)(const char*);

void console_output(const char* message) {
printf("控制台: %s\n", message);
}

void log_output(const char* message) {
printf("日志: %s\n", message);
}

// 高层函数,依赖抽象的输出接口
void process_user_data(const char* name, int age, output_func output) {
char buffer[100];

if (is_valid_age(age)) {
snprintf(buffer, sizeof(buffer), "用户 %s,年龄 %d,数据有效", name, age);
} else {
snprintf(buffer, sizeof(buffer), "用户 %s,年龄 %d,数据无效", name, age);
}

output(buffer); // 使用传入的输出函数
}

int main(void) {
printf("=== 函数设计原则演示 ===\n");

// 单一职责原则演示
const char* email = "user@example.com";
int age = 25;

print_validation_result("邮箱", is_valid_email(email));
print_validation_result("年龄", is_valid_age(age));

// 开放封闭原则演示
const char* username = "john_doe";
printf("\n字符串验证:\n");
printf("非空验证: %s\n", validate_string(username, validate_not_empty) ? "通过" : "失败");
printf("长度验证: %s\n", validate_string(username, validate_max_length) ? "通过" : "失败");

// 依赖倒置原则演示
printf("\n用户数据处理:\n");
process_user_data("Alice", 30, console_output);
process_user_data("Bob", -5, log_output);

return 0;
}

(二)参数传递机制

1. 值传递 vs 指针传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>

// 值传递:函数接收参数的副本
void swap_by_value(int a, int b) {
printf("交换前 (函数内): a=%d, b=%d\n", a, b);

// 交换局部变量的值
int temp = a;
a = b;
b = temp;

printf("交换后 (函数内): a=%d, b=%d\n", a, b);
// 注意:这里的交换不会影响main函数中的变量
}

// 指针传递:函数接收变量地址,可以修改原变量
void swap_by_pointer(int* a, int* b) {
printf("交换前 (函数内): *a=%d, *b=%d\n", *a, *b);

// 通过指针交换原变量的值
int temp = *a;
*a = *b;
*b = temp;

printf("交换后 (函数内): *a=%d, *b=%d\n", *a, *b);
}

// 数组参数传递(实际上是指针传递)
void print_array(int arr[], int size) {
printf("数组元素: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}

// 修改数组元素
void double_array_elements(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 直接修改原数组
}
}

// 字符串参数传递
void modify_string(char str[]) {
// 修改字符串的第一个字符
if (str[0] != '\0') {
str[0] = 'X';
}
}

// 常量参数:防止函数修改参数
void print_string_info(const char* str) {
printf("字符串: \"%s\"\n", str);
printf("长度: %zu\n", strlen(str));
// str[0] = 'Y'; // 编译错误:不能修改const参数
}

int main(void) {
printf("=== 参数传递机制演示 ===\n");

// 值传递演示
int x = 10, y = 20;
printf("值传递演示:\n");
printf("交换前 (main): x=%d, y=%d\n", x, y);
swap_by_value(x, y);
printf("交换后 (main): x=%d, y=%d\n", x, y); // 值没有改变

printf("\n指针传递演示:\n");
printf("交换前 (main): x=%d, y=%d\n", x, y);
swap_by_pointer(&x, &y); // 传递变量地址
printf("交换后 (main): x=%d, y=%d\n", x, y); // 值已改变

// 数组传递演示
printf("\n数组传递演示:\n");
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);

printf("原始数组: ");
print_array(numbers, size);

double_array_elements(numbers, size);
printf("修改后数组: ");
print_array(numbers, size);

// 字符串传递演示
printf("\n字符串传递演示:\n");
char message[] = "Hello, World!";
printf("原始字符串: %s\n", message);

modify_string(message);
printf("修改后字符串: %s\n", message);

// 常量参数演示
print_string_info(message);

return 0;
}

2. 函数返回值的最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>

// 错误处理方式1:返回错误码
typedef enum {
CALC_SUCCESS = 0,
CALC_DIVISION_BY_ZERO = 1,
CALC_OVERFLOW = 2,
CALC_INVALID_INPUT = 3
} calc_error_t;

calc_error_t safe_divide(double a, double b, double* result) {
// 参数验证
if (result == NULL) {
return CALC_INVALID_INPUT;
}

// 检查除零错误
if (b == 0.0) {
return CALC_DIVISION_BY_ZERO;
}

// 执行计算
*result = a / b;
return CALC_SUCCESS;
}

// 错误处理方式2:返回布尔值,使用输出参数
bool parse_integer(const char* str, int* value) {
if (str == NULL || value == NULL) {
return false;
}

char* endptr;
errno = 0; // 重置errno

long result = strtol(str, &endptr, 10);

// 检查转换是否成功
if (errno != 0 || *endptr != '\0' || result < INT_MIN || result > INT_MAX) {
return false;
}

*value = (int)result;
return true;
}

// 错误处理方式3:返回特殊值表示错误
int find_max_index(const int arr[], int size) {
if (arr == NULL || size <= 0) {
return -1; // 返回-1表示错误
}

int max_index = 0;
for (int i = 1; i < size; i++) {
if (arr[i] > arr[max_index]) {
max_index = i;
}
}

return max_index;
}

// 多返回值的模拟:使用结构体
typedef struct {
bool success;
int quotient;
int remainder;
} division_result_t;

division_result_t integer_divide(int dividend, int divisor) {
division_result_t result = {false, 0, 0};

if (divisor == 0) {
return result; // 返回失败状态
}

result.success = true;
result.quotient = dividend / divisor;
result.remainder = dividend % divisor;

return result;
}

const char* error_to_string(calc_error_t error) {
switch (error) {
case CALC_SUCCESS: return "成功";
case CALC_DIVISION_BY_ZERO: return "除零错误";
case CALC_OVERFLOW: return "溢出错误";
case CALC_INVALID_INPUT: return "无效输入";
default: return "未知错误";
}
}

int main(void) {
printf("=== 函数返回值最佳实践 ===\n");

// 错误码方式演示
double result;
calc_error_t error = safe_divide(10.0, 3.0, &result);
if (error == CALC_SUCCESS) {
printf("10.0 / 3.0 = %.2f\n", result);
} else {
printf("计算失败: %s\n", error_to_string(error));
}

error = safe_divide(10.0, 0.0, &result);
if (error != CALC_SUCCESS) {
printf("10.0 / 0.0 失败: %s\n", error_to_string(error));
}

// 布尔返回值演示
printf("\n字符串解析演示:\n");
int value;
const char* test_strings[] = {"123", "abc", "456def", "999999999999999999"};
int num_tests = sizeof(test_strings) / sizeof(test_strings[0]);

for (int i = 0; i < num_tests; i++) {
if (parse_integer(test_strings[i], &value)) {
printf("\"%s\" -> %d (成功)\n", test_strings[i], value);
} else {
printf("\"%s\" -> 解析失败\n", test_strings[i]);
}
}

// 特殊值返回演示
printf("\n数组最大值索引:\n");
int numbers[] = {3, 7, 2, 9, 1};
int size = sizeof(numbers) / sizeof(numbers[0]);

int max_idx = find_max_index(numbers, size);
if (max_idx >= 0) {
printf("最大值 %d 位于索引 %d\n", numbers[max_idx], max_idx);
} else {
printf("查找失败\n");
}

// 结构体返回值演示
printf("\n整数除法演示:\n");
division_result_t div_result = integer_divide(17, 5);
if (div_result.success) {
printf("17 ÷ 5 = %d 余 %d\n", div_result.quotient, div_result.remainder);
} else {
printf("除法计算失败\n");
}

return 0;
}

(三)递归编程

1. 递归的基本概念和应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <stdio.h>
#include <string.h>

// 经典递归:计算阶乘
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);
}

// 优化的斐波那契:使用记忆化
long long fibonacci_memo(int n, long long memo[]) {
if (n <= 1) {
return n;
}

// 如果已经计算过,直接返回结果
if (memo[n] != -1) {
return memo[n];
}

// 计算并存储结果
memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo);
return memo[n];
}

// 递归:字符串反转
void reverse_string(char str[], int start, int end) {
// 基础情况
if (start >= end) {
return;
}

// 交换字符
char temp = str[start];
str[start] = str[end];
str[end] = temp;

// 递归处理剩余部分
reverse_string(str, start + 1, end - 1);
}

// 递归:二分查找
int binary_search(int arr[], int left, int right, int target) {
// 基础情况:未找到
if (left > right) {
return -1;
}

int mid = left + (right - left) / 2; // 防止溢出的中点计算

// 基础情况:找到目标
if (arr[mid] == target) {
return mid;
}

// 递归情况
if (arr[mid] > target) {
return binary_search(arr, left, mid - 1, target);
} else {
return binary_search(arr, mid + 1, right, target);
}
}

// 递归:汉诺塔问题
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("将盘子 1 从 %c 移动到 %c\n", from, to);
return;
}

// 将前n-1个盘子从from移动到aux
hanoi(n - 1, from, aux, to);

// 将最大的盘子从from移动到to
printf("将盘子 %d 从 %c 移动到 %c\n", n, from, to);

// 将n-1个盘子从aux移动到to
hanoi(n - 1, aux, to, from);
}

// 递归:计算数组元素之和
int array_sum(int arr[], int size) {
// 基础情况
if (size <= 0) {
return 0;
}

// 递归情况
return arr[size - 1] + array_sum(arr, size - 1);
}

int main(void) {
printf("=== 递归编程演示 ===\n");

// 阶乘演示
int n = 5;
printf("阶乘计算:\n");
printf("%d! = %lld\n", n, factorial(n));

// 斐波那契数列演示
printf("\n斐波那契数列:\n");
printf("普通递归 fibonacci(%d) = %lld\n", 10, fibonacci(10));

// 记忆化斐波那契
long long memo[50];
for (int i = 0; i < 50; i++) {
memo[i] = -1; // 初始化为-1表示未计算
}
printf("记忆化递归 fibonacci(%d) = %lld\n", 40, fibonacci_memo(40, memo));

// 字符串反转演示
printf("\n字符串反转:\n");
char str[] = "Hello, World!";
printf("原字符串: %s\n", str);
reverse_string(str, 0, strlen(str) - 1);
printf("反转后: %s\n", str);

// 二分查找演示
printf("\n二分查找:\n");
int sorted_array[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
int array_size = sizeof(sorted_array) / sizeof(sorted_array[0]);
int target = 7;

int index = binary_search(sorted_array, 0, array_size - 1, target);
if (index != -1) {
printf("在索引 %d 处找到 %d\n", index, target);
} else {
printf("未找到 %d\n", target);
}

// 汉诺塔演示
printf("\n汉诺塔问题 (3个盘子):\n");
hanoi(3, 'A', 'C', 'B');

// 数组求和演示
printf("\n数组求和:\n");
int numbers[] = {1, 2, 3, 4, 5};
int sum = array_sum(numbers, 5);
printf("数组 {1, 2, 3, 4, 5} 的和为: %d\n", sum);

return 0;
}

二、变量作用域与生命周期

(一)作用域规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>

// 全局变量:在整个程序中都可见
int global_counter = 0;
const double PI = 3.14159265359; // 全局常量

// 静态全局变量:只在当前文件中可见
static int file_scope_var = 100;

// 函数声明
void demonstrate_scope(void);
void increment_counters(void);

// 函数定义
void demonstrate_scope(void) {
// 局部变量:只在函数内部可见
int local_var = 10;

printf("函数内部:\n");
printf(" 局部变量: %d\n", local_var);
printf(" 全局变量: %d\n", global_counter);
printf(" 文件作用域变量: %d\n", file_scope_var);

// 块作用域演示
if (local_var > 5) {
int block_var = 20; // 块作用域变量
printf(" 块内变量: %d\n", block_var);

// 变量遮蔽(shadowing)
int global_counter = 999; // 局部变量遮蔽全局变量
printf(" 遮蔽的全局变量: %d\n", global_counter);
}
// block_var在这里不可见

printf(" 块外的全局变量: %d\n", global_counter);
}

void increment_counters(void) {
// 静态局部变量:保持值在函数调用之间
static int static_counter = 0;

global_counter++;
static_counter++;

printf("计数器增加:\n");
printf(" 全局计数器: %d\n", global_counter);
printf(" 静态局部计数器: %d\n", static_counter);
}

int main(void) {
printf("=== 变量作用域演示 ===\n");

// 局部变量
int main_local = 42;

printf("main函数开始:\n");
printf(" main局部变量: %d\n", main_local);
printf(" 全局变量: %d\n", global_counter);

// 调用函数演示作用域
printf("\n");
demonstrate_scope();

// 演示静态变量
printf("\n静态变量演示:\n");
for (int i = 0; i < 3; i++) {
printf("第 %d 次调用:\n", i + 1);
increment_counters();
}

// 循环变量的作用域
printf("\n循环变量作用域:\n");
for (int i = 0; i < 3; i++) {
printf("循环内 i = %d\n", i);
}
// printf("循环外 i = %d\n", i); // 编译错误:i不在作用域内

return 0;
}

(二)存储类别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <stdlib.h>

// 外部变量声明(定义在其他文件中)
// extern int external_var; // 如果有其他文件定义了这个变量

// 静态函数:只在当前文件中可见
static void internal_function(void) {
printf("这是一个静态函数,只能在当前文件中调用\n");
}

// 演示不同存储类别的函数
void storage_class_demo(void) {
// auto存储类别(默认,通常省略)
auto int auto_var = 10; // 等价于 int auto_var = 10;

// register存储类别(建议编译器将变量存储在寄存器中)
register int reg_var = 20;

// static存储类别(静态局部变量)
static int static_var = 30;

printf("存储类别演示:\n");
printf(" auto变量: %d\n", auto_var);
printf(" register变量: %d\n", reg_var);
printf(" static变量: %d\n", static_var);

// 修改变量值
auto_var++;
reg_var++;
static_var++;

printf("修改后:\n");
printf(" auto变量: %d\n", auto_var);
printf(" register变量: %d\n", reg_var);
printf(" static变量: %d\n", static_var);

// 注意:不能获取register变量的地址
// printf("register变量地址: %p\n", &reg_var); // 编译错误
}

// 演示动态内存分配
void dynamic_memory_demo(void) {
printf("\n动态内存分配演示:\n");

// 在堆上分配内存
int* dynamic_array = malloc(5 * sizeof(int));
if (dynamic_array == NULL) {
printf("内存分配失败\n");
return;
}

// 初始化动态数组
for (int i = 0; i < 5; i++) {
dynamic_array[i] = i * i;
}

printf("动态数组: ");
for (int i = 0; i < 5; i++) {
printf("%d ", dynamic_array[i]);
}
printf("\n");

// 释放内存
free(dynamic_array);
dynamic_array = NULL; // 防止悬空指针

printf("内存已释放\n");
}

int main(void) {
printf("=== 存储类别演示 ===\n");

// 多次调用函数,观察静态变量的行为
for (int i = 0; i < 3; i++) {
printf("\n第 %d 次调用:\n", i + 1);
storage_class_demo();
}

// 调用静态函数
internal_function();

// 动态内存演示
dynamic_memory_demo();

return 0;
}

三、模块化编程

(一)头文件和源文件组织

让我们创建一个简单的数学库来演示模块化编程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// math_utils.h - 头文件
#ifndef MATH_UTILS_H // 头文件保护
#define MATH_UTILS_H

#include <stdbool.h>

// 常量定义
#define PI 3.14159265359
#define E 2.71828182846

// 函数声明
double power(double base, int exponent);
double square_root(double number);
bool is_prime(int number);
int gcd(int a, int b);
int lcm(int a, int b);
double factorial_approx(int n); // 使用斯特林公式近似

// 内联函数(C99特性)
static inline double square(double x) {
return x * x;
}

static inline double cube(double x) {
return x * x * x;
}

#endif // MATH_UTILS_H

现在让我们在主文件中使用这个模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// main.c - 主程序文件
#include <stdio.h>
#include <math.h>
#include "math_utils.h" // 包含自定义头文件

// 实现math_utils.h中声明的函数
double power(double base, int exponent) {
if (exponent == 0) return 1.0;
if (exponent < 0) return 1.0 / power(base, -exponent);

double result = 1.0;
for (int i = 0; i < exponent; i++) {
result *= base;
}
return result;
}

double square_root(double number) {
if (number < 0) return -1; // 错误标识
if (number == 0) return 0;

// 使用牛顿法求平方根
double guess = number / 2.0;
double epsilon = 1e-10;

while (fabs(guess * guess - number) > epsilon) {
guess = (guess + number / guess) / 2.0;
}

return guess;
}

bool is_prime(int number) {
if (number < 2) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;

for (int i = 3; i * i <= number; i += 2) {
if (number % i == 0) return false;
}

return true;
}

int gcd(int a, int b) {
// 欧几里得算法
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}

int lcm(int a, int b) {
return (a * b) / gcd(a, b);
}

double factorial_approx(int n) {
// 斯特林公式近似:n! ≈ √(2πn) * (n/e)^n
if (n < 0) return -1;
if (n == 0 || n == 1) return 1;

return sqrt(2 * PI * n) * power(n / E, n);
}

// 演示函数
void demonstrate_math_utils(void) {
printf("=== 数学工具库演示 ===\n");

// 基本运算
printf("基本运算:\n");
printf("2^10 = %.0f\n", power(2, 10));
printf("5^2 = %.0f (使用内联函数)\n", square(5));
printf("3^3 = %.0f (使用内联函数)\n", cube(3));
printf("√16 = %.2f\n", square_root(16));

// 数论函数
printf("\n数论函数:\n");
printf("17是质数吗?%s\n", is_prime(17) ? "是" : "否");
printf("18是质数吗?%s\n", is_prime(18) ? "是" : "否");
printf("gcd(48, 18) = %d\n", gcd(48, 18));
printf("lcm(12, 8) = %d\n", lcm(12, 8));

// 阶乘近似
printf("\n阶乘近似:\n");
for (int i = 5; i <= 10; i++) {
printf("%d! ≈ %.0f\n", i, factorial_approx(i));
}
}

int main(void) {
demonstrate_math_utils();
return 0;
}

编译多文件项目

1
2
3
4
5
6
7
# 如果将函数实现放在单独的.c文件中
gcc -std=c11 -Wall -Wextra -lm math_utils.c main.c -o program

# 或者分步编译
gcc -std=c11 -Wall -c math_utils.c
gcc -std=c11 -Wall -c main.c
gcc math_utils.o main.o -lm -o program

(二)函数指针和回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <stdlib.h>

// 定义函数指针类型
typedef int (*operation_func)(int a, int b);
typedef void (*callback_func)(int result);

// 基本运算函数
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(int a, int b) {
return (b != 0) ? a / b : 0;
}

// 回调函数示例
void print_result(int result) {
printf("计算结果: %d\n", result);
}

void log_result(int result) {
printf("[日志] 运算完成,结果: %d\n", result);
}

// 使用函数指针的计算器
int calculator(int a, int b, operation_func op, callback_func callback) {
int result = op(a, b);
if (callback != NULL) {
callback(result);
}
return result;
}

// 函数指针数组
operation_func operations[] = {add, subtract, multiply, divide_int};
const char* operation_names[] = {"加法", "减法", "乘法", "除法"};

// 高阶函数:对数组应用操作
void apply_operation(int arr[], int size, int (*transform)(int)) {
for (int i = 0; i < size; i++) {
arr[i] = transform(arr[i]);
}
}

// 变换函数
int double_value(int x) { return x * 2; }
int square_value(int x) { return x * x; }
int increment(int x) { return x + 1; }

int main(void) {
printf("=== 函数指针和回调函数演示 ===\n");

// 基本函数指针使用
operation_func op = add;
int result = op(5, 3);
printf("使用函数指针: 5 + 3 = %d\n", result);

// 使用回调函数的计算器
printf("\n回调函数演示:\n");
calculator(10, 4, subtract, print_result);
calculator(6, 7, multiply, log_result);

// 函数指针数组
printf("\n函数指针数组演示:\n");
int a = 12, b = 4;
for (int i = 0; i < 4; i++) {
int res = operations[i](a, b);
printf("%s: %d\n", operation_names[i], res);
}

// 高阶函数演示
printf("\n高阶函数演示:\n");
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);

printf("原始数组: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

// 应用不同的变换
apply_operation(numbers, size, double_value);
printf("翻倍后: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

return 0;
}

四、实践项目:学生管理系统(函数版)

让我们创建一个使用函数模块化设计的学生管理系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define MAX_STUDENTS 100
#define MAX_NAME_LENGTH 50

// 学生结构体
typedef struct {
int id;
char name[MAX_NAME_LENGTH];
int age;
float score;
} Student;

// 全局学生数组和计数器
Student students[MAX_STUDENTS];
int student_count = 0;

// 函数声明
bool add_student(int id, const char* name, int age, float score);
bool remove_student(int id);
Student* find_student(int id);
void display_all_students(void);
void display_student(const Student* student);
float calculate_average_score(void);
Student* find_top_student(void);
void sort_students_by_score(void);
bool save_to_file(const char* filename);
bool load_from_file(const char* filename);
void show_menu(void);
int get_user_choice(void);

// 函数实现
bool add_student(int id, const char* name, int age, float score) {
// 检查是否已满
if (student_count >= MAX_STUDENTS) {
printf("错误:学生数量已达上限\n");
return false;
}

// 检查ID是否已存在
if (find_student(id) != NULL) {
printf("错误:学号 %d 已存在\n", id);
return false;
}

// 添加学生
students[student_count].id = id;
strncpy(students[student_count].name, name, MAX_NAME_LENGTH - 1);
students[student_count].name[MAX_NAME_LENGTH - 1] = '\0';
students[student_count].age = age;
students[student_count].score = score;
student_count++;

printf("成功添加学生:%s\n", name);
return true;
}

Student* find_student(int id) {
for (int i = 0; i < student_count; i++) {
if (students[i].id == id) {
return &students[i];
}
}
return NULL;
}

void display_student(const Student* student) {
if (student == NULL) return;

printf("学号: %d, 姓名: %s, 年龄: %d, 成绩: %.1f\n",
student->id, student->name, student->age, student->score);
}

void display_all_students(void) {
if (student_count == 0) {
printf("暂无学生记录\n");
return;
}

printf("\n=== 所有学生信息 ===\n");
printf("%-6s %-20s %-6s %-6s\n", "学号", "姓名", "年龄", "成绩");
printf("----------------------------------------\n");

for (int i = 0; i < student_count; i++) {
printf("%-6d %-20s %-6d %-6.1f\n",
students[i].id, students[i].name,
students[i].age, students[i].score);
}
}

float calculate_average_score(void) {
if (student_count == 0) return 0.0f;

float total = 0.0f;
for (int i = 0; i < student_count; i++) {
total += students[i].score;
}

return total / student_count;
}

void show_menu(void) {
printf("\n=== 学生管理系统 ===\n");
printf("1. 添加学生\n");
printf("2. 查找学生\n");
printf("3. 显示所有学生\n");
printf("4. 计算平均分\n");
printf("5. 按成绩排序\n");
printf("0. 退出\n");
printf("请选择操作: ");
}

int get_user_choice(void) {
int choice;
scanf("%d", &choice);
return choice;
}

// 简化的排序函数(冒泡排序)
void sort_students_by_score(void) {
for (int i = 0; i < student_count - 1; i++) {
for (int j = 0; j < student_count - 1 - i; j++) {
if (students[j].score < students[j + 1].score) {
// 交换学生记录
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
printf("学生已按成绩降序排列\n");
}

int main(void) {
// 添加一些示例数据
add_student(1001, "张三", 20, 85.5);
add_student(1002, "李四", 19, 92.0);
add_student(1003, "王五", 21, 78.5);

int choice;
do {
show_menu();
choice = get_user_choice();

switch (choice) {
case 1: {
int id, age;
char name[MAX_NAME_LENGTH];
float score;

printf("请输入学号: ");
scanf("%d", &id);
printf("请输入姓名: ");
scanf("%s", name);
printf("请输入年龄: ");
scanf("%d", &age);
printf("请输入成绩: ");
scanf("%f", &score);

add_student(id, name, age, score);
break;
}
case 2: {
int id;
printf("请输入要查找的学号: ");
scanf("%d", &id);

Student* student = find_student(id);
if (student != NULL) {
printf("找到学生: ");
display_student(student);
} else {
printf("未找到学号为 %d 的学生\n", id);
}
break;
}
case 3:
display_all_students();
break;
case 4:
printf("平均成绩: %.2f\n", calculate_average_score());
break;
case 5:
sort_students_by_score();
display_all_students();
break;
case 0:
printf("感谢使用学生管理系统!\n");
break;
default:
printf("无效选择,请重新输入\n");
}
} while (choice != 0);

return 0;
}

五、总结与下一步

(一)本文重点回顾

通过本文的学习,您已经掌握了:

函数设计原则:

  • 单一职责、开放封闭等设计原则
  • 参数传递机制的深入理解
  • 错误处理的最佳实践

递归编程:

  • 递归的基本概念和应用场景
  • 经典递归算法的实现
  • 递归优化技巧

作用域和生命周期:

  • 变量的作用域规则
  • 不同存储类别的特点
  • 内存管理基础

模块化编程:

  • 头文件和源文件的组织
  • 函数指针和回调函数
  • 代码复用和维护性

(二)学习建议

  1. 多练习函数设计:尝试将复杂问题分解为简单函数
  2. 理解递归思维:从简单的递归开始,逐步挑战复杂问题
  3. 注意代码组织:养成良好的模块化编程习惯
  4. 实践项目开发:通过完整项目巩固所学知识
下一篇:数组与字符串深度解析

参考资料:

  • 《代码大全》- Steve McConnell
  • 《重构:改善既有代码的设计》- Martin Fowler
  • 《设计模式》- Gang of Four
  • 《C陷阱与缺陷》- Andrew Koenig
  • C语言函数库参考:https://en.cppreference.com/w/c/header