1. C语言函数基础概念解析
在C语言的世界里,函数就像是一个个独立的小工厂,每个工厂都有自己特定的生产流程和产出标准。当我第一次接触这个概念时,导师用了一个生动的比喻:如果把整个程序比作汽车制造厂,那么函数就是各个车间——发动机车间专门生产发动机,轮胎车间专门制造轮胎,最后在总装车间完成整车的组装。
1.1 函数的本质与价值
函数本质上是一段完成特定任务的独立代码块。在C语言中,每个函数都有明确的输入(参数)和输出(返回值)。这种模块化设计带来的好处是显而易见的:
- 代码复用:避免重复编写相同逻辑的代码
- 逻辑清晰:将复杂问题分解为多个小问题
- 便于维护:修改时只需关注特定函数
- 团队协作:不同开发者可以并行开发不同函数
举个例子,假设我们需要在程序中多次计算圆的面积。没有函数的情况下,代码可能是这样的:
c复制// 第一次计算
double radius1 = 5.0;
double area1 = 3.14159 * radius1 * radius1;
// 第二次计算
double radius2 = 7.0;
double area2 = 3.14159 * radius2 * radius2;
而使用函数后:
c复制double calculateCircleArea(double radius) {
return 3.14159 * radius * radius;
}
int main() {
double area1 = calculateCircleArea(5.0);
double area2 = calculateCircleArea(7.0);
// ...
}
1.2 函数的基本结构
一个标准的C语言函数包含以下几个关键部分:
c复制返回类型 函数名(参数列表) {
// 函数体
return 返回值; // 如果返回类型不是void
}
让我们拆解一个实际的例子:
c复制int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
- 返回类型:
int,表示这个函数会返回一个整数值 - 函数名:
max,遵循C语言的命名规则 - 参数列表:
(int a, int b),两个整型参数 - 函数体:包含具体的比较逻辑
- return语句:返回较大的那个数
注意:C语言是区分大小写的,
max和Max会被视为不同的函数名。良好的命名习惯能显著提高代码可读性。
1.3 函数的声明与定义
在实际开发中,我们经常会遇到函数声明(declaration)和定义(definition)的概念:
- 函数声明:告诉编译器函数的签名(返回类型、名称、参数类型),不包含实现
- 函数定义:包含函数的具体实现
c复制// 函数声明
int add(int a, int b);
int main() {
int result = add(3, 4);
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b;
}
这种分离的设计使得我们可以将函数声明放在头文件(.h)中,而将定义放在源文件(.c)中,这是C语言模块化编程的基础。
2. 函数参数传递的深入理解
2.1 值传递与指针传递
C语言中函数参数的传递方式经常让初学者感到困惑。实际上,C语言只有值传递这一种方式,但通过指针我们可以模拟引用传递的效果。
值传递示例:
c复制void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
swap(x, y);
// 这里的x和y的值不会交换
}
指针传递示例:
c复制void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
swap(&x, &y);
// 现在x和y的值已经交换
}
2.2 数组作为函数参数
当数组作为函数参数传递时,实际上传递的是数组首元素的地址。因此,在函数内部对数组的修改会影响原始数组。
c复制void modifyArray(int arr[], int size) {
for(int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
modifyArray(numbers, 5);
// numbers数组现在变为{2, 4, 6, 8, 10}
}
重要提示:在函数参数中,
int arr[]和int *arr是等价的,编译器都会将其视为指针。
2.3 结构体作为函数参数
对于结构体,我们可以选择直接传递结构体(值传递)或传递结构体指针:
c复制typedef struct {
int x;
int y;
} Point;
// 值传递
void printPoint(Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
// 指针传递
void movePoint(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
在实际项目中,大型结构体通常使用指针传递以避免拷贝开销。
3. 函数的返回值与错误处理
3.1 返回值的多种形式
C语言函数的返回值不仅限于基本数据类型,还可以返回指针、结构体等。但需要注意返回局部变量的指针是危险的:
c复制// 错误示例:返回局部变量的地址
int* createArray() {
int arr[3] = {1, 2, 3};
return arr; // arr在函数结束后会被销毁
}
// 正确做法:使用动态内存分配
int* createArray() {
int *arr = malloc(3 * sizeof(int));
if(arr != NULL) {
arr[0] = 1; arr[1] = 2; arr[2] = 3;
}
return arr;
}
3.2 多值返回的实现技巧
C语言函数只能直接返回一个值,但我们可以通过以下方式实现"多值返回":
- 使用指针参数:
c复制void getMinMax(int arr[], int size, int *min, int *max) {
*min = *max = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] < *min) *min = arr[i];
if(arr[i] > *max) *max = arr[i];
}
}
- 返回结构体:
c复制typedef struct {
int min;
int max;
} MinMax;
MinMax getMinMax(int arr[], int size) {
MinMax result;
result.min = result.max = arr[0];
// ...计算逻辑
return result;
}
3.3 错误处理的最佳实践
C语言没有异常机制,常见的错误处理方式包括:
- 通过返回值表示状态:
c复制int divide(int a, int b, int *result) {
if(b == 0) return -1; // 错误码
*result = a / b;
return 0; // 成功
}
- 使用全局变量errno:
c复制#include <errno.h>
double safeSqrt(double x) {
if(x < 0) {
errno = EDOM; // 域错误
return 0.0;
}
return sqrt(x);
}
- 回调函数处理错误:
c复制void handleError(const char *message) {
fprintf(stderr, "Error: %s\n", message);
exit(EXIT_FAILURE);
}
void processData(int data, void (*errorHandler)(const char*)) {
if(data < 0) {
errorHandler("Invalid data");
return;
}
// 正常处理
}
4. 高级函数概念与应用
4.1 递归函数详解
递归是函数直接或间接调用自身的过程。经典的递归例子包括阶乘、斐波那契数列和汉诺塔问题。
阶乘函数的递归实现:
c复制int factorial(int n) {
if(n <= 1) return 1; // 基线条件
return n * factorial(n - 1); // 递归调用
}
递归的注意事项:
- 必须有明确的基线条件(终止条件)
- 每次递归调用应该使问题规模减小
- 递归层次过深可能导致栈溢出
- 某些问题(如斐波那契数列)的递归解法效率很低,因为有大量重复计算
4.2 函数指针的强大功能
函数指针是指向函数的指针变量,这是C语言中非常强大的特性,常用于回调机制。
c复制#include <stdio.h>
// 定义函数类型
typedef int (*CompareFunc)(int, int);
int ascending(int a, int b) { return a - b; }
int descending(int a, int b) { return b - a; }
void sort(int arr[], int size, CompareFunc cmp) {
for(int i = 0; i < size-1; i++) {
for(int j = 0; j < size-i-1; j++) {
if(cmp(arr[j], arr[j+1]) > 0) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int numbers[] = {3, 1, 4, 1, 5, 9, 2, 6};
// 升序排序
sort(numbers, 8, ascending);
// 降序排序
sort(numbers, 8, descending);
return 0;
}
4.3 可变参数函数
C语言支持可变参数函数,最典型的例子是printf。我们可以使用stdarg.h中的宏来定义自己的可变参数函数。
c复制#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
va_list ap;
double sum = 0;
va_start(ap, count);
for(int i = 0; i < count; i++) {
sum += va_arg(ap, double);
}
va_end(ap);
return sum / count;
}
int main() {
printf("Average: %.2f\n", average(3, 1.0, 2.0, 3.0));
return 0;
}
警告:可变参数函数没有类型安全检查,使用时必须确保传递的参数类型与期望的一致。
4.4 内联函数与宏函数
对于小型、频繁调用的函数,可以考虑使用内联函数或宏函数来提高效率。
内联函数:
c复制inline int max(int a, int b) {
return a > b ? a : b;
}
宏函数:
c复制#define MAX(a, b) ((a) > (b) ? (a) : (b))
两者的区别:
- 内联函数由编译器处理,有类型检查;宏由预处理器处理,是简单的文本替换
- 内联函数更安全,调试更方便;宏容易因为参数求值次数问题导致意外结果
- 内联函数是C99标准特性,宏在所有C版本中都可用
5. 函数设计的最佳实践
5.1 单一职责原则
好的函数应该只做一件事,并且做好这件事。一个简单的判断标准是:能否用一句话清晰描述函数的功能,而不需要使用"和"、"或"等连接词。
不良设计:
c复制// 处理数据并保存到文件
void processAndSaveData(Data *data, const char *filename) {
// 处理数据...
// 保存到文件...
}
改进设计:
c复制void processData(Data *data) {
// 只处理数据
}
void saveData(const Data *data, const char *filename) {
// 只保存数据
}
5.2 合理的函数长度
虽然没有严格规定,但一般来说:
- 10行以内的函数:非常理想
- 20-30行:可以接受,但考虑是否可以拆分
- 50行以上:几乎肯定需要重构
经验法则:一个函数的代码应该能完整显示在一个屏幕内(约40-50行),不需要滚动就能看到整体逻辑。
5.3 有意义的命名
函数名应该:
- 使用动词或动词短语
- 准确描述函数功能
- 避免过于笼统(如
doStuff、process) - 遵循项目命名约定(如驼峰式、下划线式)
不良命名:
c复制int func(int a, int b);
良好命名:
c复制int calculateDistance(int x, int y);
5.4 参数设计的艺术
- 参数数量:理想情况下不超过4-5个,过多考虑使用结构体封装
- 参数顺序:重要参数、常用参数放前面
- 输出参数:使用指针明确标识
- 避免布尔参数:它们通常暗示函数做了太多事情
不良设计:
c复制void setupDevice(bool enableLogging, bool useSSL, bool highPerformance);
改进设计:
c复制typedef struct {
bool enableLogging;
bool useSSL;
bool highPerformance;
} DeviceConfig;
void setupDevice(const DeviceConfig *config);
5.5 注释与文档
虽然好的代码应该自文档化,但适当的注释仍然必要:
- 函数头注释:说明功能、参数、返回值、可能的错误
- 复杂逻辑注释:解释为什么这样做,而不是怎么做
- 使用doxygen等工具生成文档
良好注释示例:
c复制/**
* @brief 计算两个点的欧几里得距离
* @param x1 第一个点的x坐标
* @param y1 第一个点的y坐标
* @param x2 第二个点的x坐标
* @param y2 第二个点的y坐标
* @return 两点之间的距离
* @note 对于非常大的坐标值可能会溢出
*/
double calculateDistance(double x1, double y1, double x2, double y2);
6. 常见函数相关陷阱与调试技巧
6.1 栈溢出问题
递归函数或大型局部变量可能导致栈溢出。症状包括程序崩溃或不可预测的行为。
解决方案:
- 对于递归:确保有基线条件,考虑改为迭代实现
- 对于大型数据:使用动态内存分配(malloc/free)
示例:
c复制// 危险的递归实现
void infiniteRecursion() {
infiniteRecursion();
}
// 更安全的迭代实现
void processLargeData() {
int *bigArray = malloc(1000000 * sizeof(int));
if(bigArray == NULL) {
// 处理内存不足
return;
}
// 使用bigArray
free(bigArray);
}
6.2 函数指针误用
函数指针使用不当会导致难以诊断的问题,特别是在类型不匹配时。
常见错误:
c复制int add(int a, int b) { return a + b; }
int main() {
int (*wrongPtr)(float, float) = add; // 类型不匹配
// ...
}
正确做法:
c复制typedef int (*MathFunc)(int, int);
int main() {
MathFunc ptr = add; // 类型安全
int result = ptr(3, 4);
// ...
}
6.3 链接错误排查
当出现"undefined reference"链接错误时,通常是因为:
- 函数声明了但没定义
- 定义在另一个源文件中但没正确链接
- 拼写错误导致名称不匹配
调试技巧:
- 使用
nm工具查看目标文件中的符号 - 检查头文件包含是否正确
- 确保所有需要的源文件都加入了编译
6.4 性能优化技巧
- 小函数内联:对频繁调用的小函数使用
inline关键字 - 减少参数传递开销:大型结构体使用指针传递
- 避免函数调用中的重复计算:
c复制// 不佳
for(int i = 0; i < strlen(s); i++) { ... }
// 改进
int len = strlen(s);
for(int i = 0; i < len; i++) { ... }
- 使用静态函数限制作用域:
c复制static void helperFunction() { ... } // 只在当前文件可见
7. 实战案例:构建一个数学函数库
让我们综合运用所学知识,构建一个简单的数学函数库。
math_utils.h:
c复制#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 基本运算
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
// 几何计算
double calculateCircleArea(double radius);
double calculateCircleCircumference(double radius);
double calculateDistance(double x1, double y1, double x2, double y2);
// 统计函数
double calculateAverage(const double *numbers, int count);
double findMedian(double *numbers, int count);
double findStandardDeviation(const double *numbers, int count);
// 回调函数示例
typedef double (*MathOperation)(double, double);
double performOperation(double a, double b, MathOperation op);
#endif
math_utils.c:
c复制#include "math_utils.h"
#include <math.h>
#include <stdlib.h>
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; }
double divide(int a, int b) {
if(b == 0) {
// 在实际项目中应该更好的错误处理
return 0.0;
}
return (double)a / b;
}
double calculateCircleArea(double radius) {
return M_PI * radius * radius;
}
double calculateCircleCircumference(double radius) {
return 2 * M_PI * radius;
}
double calculateDistance(double x1, double y1, double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
return sqrt(dx*dx + dy*dy);
}
// 其他函数实现...
使用示例:
c复制#include <stdio.h>
#include "math_utils.h"
int main() {
printf("5 + 3 = %d\n", add(5, 3));
printf("Circle area (r=2.5): %.2f\n", calculateCircleArea(2.5));
// 使用回调函数
double result = performOperation(3.0, 4.0, sqrt); // 错误示例,sqrt只有一个参数
// 正确的回调函数应该匹配MathOperation类型
return 0;
}
在这个案例中,我们需要注意:
- 头文件保护(防止重复包含)
- 清晰的函数分类和注释
- 一致的命名约定
- 错误处理(虽然示例中简化了)
- 回调函数类型的匹配
8. 现代C语言函数特性
8.1 C11中的泛型选择
C11标准引入了_Generic关键字,允许我们编写更通用的函数接口。
c复制#include <stdio.h>
#define printValue(x) _Generic((x), \
int: printInt, \
double: printDouble, \
char*: printString \
)(x)
void printInt(int i) { printf("Integer: %d\n", i); }
void printDouble(double d) { printf("Double: %.2f\n", d); }
void printString(char *s) { printf("String: %s\n", s); }
int main() {
printValue(10); // 调用printInt
printValue(3.14); // 调用printDouble
printValue("Hello"); // 调用printString
return 0;
}
8.2 匿名函数(GNU扩展)
GNU C扩展支持嵌套函数和匿名函数,虽然这不是标准C特性,但在某些场景下很有用。
c复制#include <stdio.h>
void sort(int *array, int size, int (*compare)(int, int)) {
// 排序实现...
}
int main() {
int numbers[] = {5, 2, 8, 1, 4};
int size = sizeof(numbers) / sizeof(numbers[0]);
// GNU扩展:嵌套函数
int compare_ascending(int a, int b) { return a - b; }
sort(numbers, size, compare_ascending);
return 0;
}
8.3 属性声明
GCC和Clang等编译器支持函数属性声明,可以优化函数行为或生成更好的警告。
c复制// 声明函数不会返回(如exit())
__attribute__((noreturn)) void fatalError(const char *message) {
fprintf(stderr, "Fatal error: %s\n", message);
exit(EXIT_FAILURE);
}
// 声明函数为纯函数(无副作用,结果只依赖于参数)
__attribute__((pure)) int square(int x) {
return x * x;
}
// 声明函数为热函数(频繁调用,需要优化)
__attribute__((hot)) void processInput(char *input) {
// 处理输入...
}
9. 跨平台开发中的函数注意事项
9.1 调用约定差异
不同平台可能有不同的函数调用约定,这在混合语言编程或系统级编程时尤为重要。
c复制// Windows上的标准调用约定
__stdcall int Win32Function(int a, int b);
// Linux x86上的常见调用约定
__attribute__((cdecl)) int LinuxFunction(int a, int b);
9.2 动态库函数导出
创建跨平台动态库时,需要正确处理函数导出。
Windows DLL导出:
c复制#ifdef _WIN32
#ifdef BUILDING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
#else
#define DLLEXPORT
#endif
DLLEXPORT int exportedFunction(int param);
Linux/Unix共享库:
c复制// 通常使用默认可见性控制,或使用__attribute__((visibility("default")))
9.3 可移植的静态函数
使用static关键字可以限制函数作用域,避免命名冲突。
c复制// 只在当前编译单元可见
static void internalHelper() {
// 实现细节...
}
// 公开接口
void publicAPI() {
internalHelper();
// 其他操作...
}
10. 性能关键型函数优化
10.1 内联汇编集成
对于极端性能敏感的代码段,可以考虑使用内联汇编。
c复制int fastMultiply(int a, int b) {
int result;
__asm__ (
"imull %%ebx, %%eax;"
: "=a" (result)
: "a" (a), "b" (b)
);
return result;
}
警告:内联汇编高度依赖平台和编译器,且难以维护,应谨慎使用。
10.2 向量化函数
现代CPU支持SIMD指令集(如SSE、AVX),可以显著提升数值计算性能。
c复制#include <immintrin.h>
void vectorAdd(float *a, float *b, float *result, int size) {
for(int i = 0; i < size; i += 8) {
__m256 va = _mm256_load_ps(a + i);
__m256 vb = _mm256_load_ps(b + i);
__m256 vresult = _mm256_add_ps(va, vb);
_mm256_store_ps(result + i, vresult);
}
}
10.3 避免函数调用开销
对于极高频调用的简单函数,可以考虑:
- 使用宏函数(但要注意副作用)
- 强制内联(
__attribute__((always_inline))) - 将小函数直接写在头文件中(标记为
static inline)
c复制// 头文件中
static inline int min(int a, int b) {
return a < b ? a : b;
}
11. 函数安全编程实践
11.1 缓冲区溢出防护
处理字符串和数组的函数特别容易受到缓冲区溢出攻击。
不安全示例:
c复制void unsafeCopy(char *dest, const char *src) {
int i = 0;
while(src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
安全改进:
c复制void safeCopy(char *dest, const char *src, size_t destSize) {
if(destSize == 0) return;
size_t i = 0;
while(i < destSize - 1 && src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
11.2 参数验证
所有外部输入都应该被视为不可信的,需要进行验证。
c复制#include <limits.h>
int safeDivide(int a, int b, int *result) {
if(b == 0) return -1; // 除零错误
if(a == INT_MIN && b == -1) return -2; // 溢出
*result = a / b;
return 0; // 成功
}
11.3 使用静态分析工具
现代静态分析工具可以帮助发现函数中的潜在问题:
- Coverity:检测内存泄漏、空指针解引用等
- Clang Static Analyzer:内置于LLVM/Clang中
- Cppcheck:轻量级的C/C++静态分析工具
c复制// 可能的问题示例
void potentialIssue(int *ptr) {
if(someCondition) {
free(ptr);
}
// 如果someCondition为false,ptr可能泄漏
// 如果someCondition为true,后续使用ptr会导致问题
}
12. 测试驱动开发与函数
12.1 单元测试框架
为函数编写单元测试是保证质量的重要手段。常用的C单元测试框架包括:
- Unity:轻量级,适合嵌入式开发
- Check:功能丰富,支持fixture和超时
- Google Test:虽然主要是C++,但也可以测试C代码
使用Check的示例:
c复制#include <check.h>
START_TEST(test_add_positive) {
ck_assert_int_eq(add(2, 3), 5);
}
END_TEST
START_TEST(test_add_negative) {
ck_assert_int_eq(add(-1, -1), -2);
}
END_TEST
Suite *math_suite(void) {
Suite *s = suite_create("Math");
TCase *tc_core = tcase_create("Core");
tcase_add_test(tc_core, test_add_positive);
tcase_add_test(tc_core, test_add_negative);
suite_add_tcase(s, tc_core);
return s;
}
12.2 测试覆盖率
使用gcov和lcov工具测量测试覆盖率:
- 编译时添加
-fprofile-arcs -ftest-coverage标志 - 运行测试程序
- 使用
gcov生成覆盖率报告 - 使用
lcov生成HTML可视化报告
makefile复制# 示例Makefile规则
coverage:
gcc -fprofile-arcs -ftest-coverage test_math.c math_utils.c -o test_math
./test_math
gcov math_utils.c
lcov --capture --directory . --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
12.3 模拟与桩函数
在测试复杂函数时,可能需要模拟某些依赖函数的行为。
使用函数指针实现模拟:
c复制// 生产代码
int connectToDatabase() {
// 实际的数据库连接
}
// 测试代码
int mockConnectToDatabase() {
return 0; // 模拟成功连接
}
typedef int (*ConnectFunc)();
void setConnectFunction(ConnectFunc func) {
currentConnect = func;
}
// 在测试中
setConnectFunction(mockConnectToDatabase);
13. 函数与多线程编程
13.1 线程安全函数
线程安全函数是指可以被多个线程同时调用而不会导致数据竞争或其他并发问题的函数。
非线程安全示例:
c复制static int counter = 0;
void increment() {
counter++; // 非原子操作
}
线程安全改进:
c复制#include <pthread.h>
static int counter = 0;
static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void increment() {
pthread_mutex_lock(&counter_mutex);
counter++;
pthread_mutex_unlock(&counter_mutex);
}
13.2 可重入函数
可重入函数是指可以在执行过程中被中断并在中断服务程序中再次安全调用的函数。这类函数通常:
- 只使用局部变量
- 不使用静态或全局变量
- 不调用不可重入函数
可重入函数示例:
c复制int reentrantAdd(int a, int b) {
return a + b; // 只使用参数和局部变量
}
13.3 线程局部存储
C11引入了_Thread_local关键字,GCC/Clang支持__thread扩展,用于创建线程局部变量。
c复制#include <stdio.h>
#include <pthread.h>
__thread int threadSpecific = 0;
void *threadFunc(void *arg) {
threadSpecific = (int)(long)arg;
printf("Thread %ld: %d\n", (long)pthread_self(), threadSpecific);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, threadFunc, (void*)1);
pthread_create(&t2, NULL, threadFunc, (void*)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
14. 函数与硬件交互
14.1 寄存器操作函数
嵌入式开发中经常需要直接操作硬件寄存器。
c复制// 定义寄存器地址
#define GPIO_BASE 0x40020000
#define GPIO_MODE_REG (*(volatile uint32_t *)(GPIO_BASE + 0x00))
// 设置GPIO模式的函数
void setGPIOMode(int pin, int mode) {
uint32_t temp = GPIO_MODE_REG;
temp &= ~(0x3 << (pin * 2)); // 清除原有设置
temp |= (mode & 0x3) << (pin * 2); // 设置新值
GPIO_MODE_REG = temp;
}
14.2 中断服务例程
中断服务程序(ISR)是特殊的函数,需要注意:
- 执行时间尽可能短
- 避免调用可能阻塞的函数
- 可能需要特殊声明
c复制// ARM Cortex-M中断处理函数示例
__attribute__((interrupt)) void TIM2_IRQHandler(void) {
if(TIM2->SR & TIM_SR_UIF) { // 检查更新中断标志
TIM2->SR &= ~TIM_SR_UIF; // 清除标志
// 处理中断...
}
}
14.3 DMA回调函数
使用DMA传输时,通常需要设置完成回调函数。
c复制void DMA1_Channel1_IRQHandler(void) {
if(DMA1->ISR & DMA_ISR_TCIF1) { // 传输完成中断
DMA1->IFCR |= DMA_IFCR_CTCIF1; // 清除标志
if(dmaCallback != NULL) {
dmaCallback(DMA_COMPLETE); // 调用用户回调
}
}
}
typedef void (*DMACallback)(int status);
volatile DMACallback dmaCallback = NULL;
void setDMACallback(DMACallback callback) {
dmaCallback = callback;
}
15. 函数式编程风格在C中的应用
虽然C不是函数式语言,但我们可以借鉴一些函数式编程概念。
15.1 高阶函数
高阶函数是指接受函数作为参数或返回函数的函数。
c复制#include <stdio.h>
void forEach(int *array, size_t size, void (*action)(int)) {
for(size_t i = 0; i < size; i++) {
action(array[i]);
}
}
void printInt(int x) { printf("%d ", x); }
void squareInt(int x) { printf("%d ", x * x); }
int main() {
int nums[] = {1, 2, 3, 4, 5};
printf("Original: ");
forEach(nums, 5, printInt);
printf("\nSquared: ");
forEach(nums, 5, squareInt);
return 0;
}
15.2 闭包模拟
通过结构体和函数指针可以模拟闭包行为。
c复制typedef struct {
int base;
int (*func)(int, int);
} Closure;
int applyClosure(Closure *c, int x) {
return c->func(c->base, x);
}
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
Closure add5 = {5, add};
Closure mul3 = {3, multiply};
printf("add5(10) = %d\n", applyClosure(&add5, 10));
printf("mul3(10) = %d\n", applyClosure(&mul3, 10));
return 0;
}
15.3 不可变数据
通过const和封装可以创建不可变数据结构。
c复制typedef struct {
const int x;
const int y;
} ImmutablePoint;
ImmutablePoint createPoint(int x, int y) {
ImmutablePoint p = {x, y};
return p;
}
int getX(const ImmutablePoint *p) {
return p->x;
}
// p->x = 10; // 编译错误,x是const
16. 函数设计模式
16.1 工厂函数
用于创建和初始化复杂对象。
c复制typedef struct {
int id;
char name[50];
double price;
} Product;
Product* createProduct(int id, const char *name, double price) {
Product *p = malloc(sizeof(Product));
if(p != NULL) {
p->id = id;
strncpy(p->name, name, sizeof(p->name)-1);
p->name[sizeof(p->name)-1] = '\0';
p->price = price;
}
return p;
}
16.2 策略模式
通过函数指针实现不同的算法策略。
c复制typedef int (*SortStrategy)(int *, int);
int bubbleSort(int *array, int size) {
// 实现冒泡排序
}
int quickSort(int *array, int size) {
// 实现快速排序
