嵌入式 C语言 函数 学习笔记

嵌入式 C语言 函数 学习笔记 函数的基本概念和作用1.函数的概念•函数是执行特定任务的代码块。它可以接收输入称为参数完成任务后返回结果。2.函数的作用•提高代码复用性同一个函数可以被多次调用而不需要重复写相同的代码。•增强可读性通过将复杂的逻辑分解成多个函数可以让代码更加清晰。•方便调试和维护函数的独立性使得调试和更新代码更加简单。函数的基本结构1.函数声明Function Declaration告诉编译器函数的存在及其返回类型和参数类型。通常写在main()函数之前或者头文件中。格式返回类型函数名(参数类型参数名,...);示例int add(int a, int b);//声明一个名为add的函数返回值是int类型接收两个int类型的参数2.函数定义Function Definition包含函数的具体实现定义了函数的行为。格式返回类型函数名(参数类型参数名,...) {//函数体执行的操作return返回值;//如果有返回值}示例int add(int a, int b) {return a b;}3.函数调用Function Call在main()函数或其他函数中通过函数名调用函数并传递参数。示例int result add(5, 10);//调用add函数传递5和10两个参数总结结构声明告诉编译器函数的存在。定义实现函数的功能。调用在需要时使用函数。函数的参数1.形参形式参数定义形参是函数定义中声明的参数它是函数接收数据的占位符用于描述函数在执行时将会使用的变量。形参是在函数的定义部分给出的代表函数调用时将传入的值但本身在函数定义时没有具体的值。特点形参在函数内部起作用函数调用时实参的值被复制到形参中。形参是局部变量仅在函数的执行过程中有效函数结束后形参就被销毁。示例int add(int a, int b) { //这里a和b是形参 return a b; }在这个例子中a和b是add函数的形参它们定义了函数期望接收的两个整数。2.实参实际参数定义实参是函数调用时传递给形参的实际值或变量。实参为函数执行提供了具体的输入。当调用函数时实参的值会被复制给形参函数执行时使用这些值。特点实参可以是常量、变量、表达式等。实参可以在函数外部定义并且在函数调用后它们的值不会被形参的操作改变除非通过指针或引用方式传递。示例int result add(5, 10);//这里5和10是实参在这个例子中5和10是传递给函数add的实际参数。函数调用时这两个值分别赋给形参a和b然后在函数体内执行加法操作。3.形参和实参的关系在函数调用过程中实参的值被按值传递给形参。这意味着实参的值被复制到形参中函数内的任何操作都不会影响到实参本身。换句话说形参的变化不影响实参除非使用指针或特殊方式传递。按值传递的例子void increment(int num) { num num 1; printf(Inside function: %d\n, num); // 输出6 } int main() { int x 5; increment(x); printf(Outside function: %d\n, x); // 输出5 return 0; }在这个例子中虽然increment函数将num的值加了1但实参x的值没有变化因为实参的值只是被按值传递给了形参num。4.指针与引用传递的例外虽然C语言默认是按值传递但通过指针可以修改实参的值。使用指针作为形参时可以让函数修改实参的值。指针传递的例子void increment(int *num) { *num *num 1; // 通过指针修改实参的值 } int main() { int x 5; increment(x); // 传递变量x的地址 printf(Outside function: %d\n, x); // 输出6 return 0; }在这个例子中increment函数接收的是x的地址通过解引用操作(*num)修改了实参x的值。这样函数内部的操作对外部的实参有影响。头文件与函数的关系主要体现在函数声明上。头文件通常包含了函数的声明而这些声明会在源文件中被包含通过#include使得其他文件可以调用这些函数。头文件中只包含函数的声明而函数的定义通常在.c文件中。编写简单的计算器函数#include stdio.h //函数声明 int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); int divide(int a, int b); int main() { int num1 10, num2 5; // 调用函数并显示结果 printf(Add: %d\n, add(num1, num2)); printf(Subtract: %d\n, subtract(num1, num2)); printf(Multiply: %d\n, multiply(num1, num2)); printf(Divide: %d\n, divide(num1, num2)); 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) { if (b ! 0) return a / b; else { printf(Error: Division by zero!\n); return 0; } }错误情况1.函数未声明或声明不一致错误情况在调用函数之前没有进行函数声明或者声明的参数类型与实际定义不匹配。示例int main() { int result add(5, 10); // 调用未声明的函数 return 0; }在这个例子中函数add()被调用但没有进行声明或定义编译器会报错implicit declaration of function。解决方案确保在调用函数之前函数要么被声明要么已经被定义。正确示例int add(int a, int b); // 函数声明 int main() { int result add(5, 10); return 0; } int add(int a, int b) { // 函数定义 return a b; }2.返回类型错误或没有匹配的return语句错误情况函数的返回类型与实际的return语句不匹配或者没有返回值的函数未正确设置为void类型。示例int add(int a, int b) { a b; // 忘记了返回值 }在这个例子中add函数是int类型的但没有返回值编译器不会检测到返回值这会导致意想不到的结果。解决方案如果函数有返回值一定要确保return语句返回正确的值。如果函数没有返回值则返回类型应该设为void。正确示例int add(int a, int b) { return a b; // 正确返回值 } 或者 void printSum(int a, int b) { printf(Sum: %d\n, a b); // 没有返回值函数类型设为void }3.函数参数不匹配错误情况调用函数时传递的参数数量或类型与函数定义中的参数不匹配。示例int add(int a, int b); int main() { int result add(5); // 少传递了一个参数 return 0; }在这个例子中add函数需要两个参数但调用时只传递了一个导致编译错误。解决方案确保函数调用时传递的参数数量和类型与函数定义完全匹配。正确示例int add(int a, int b); int main() { int result add(5, 10); // 参数数量匹配 return 0; }4.返回局部变量的地址错误情况函数返回了一个局部变量的地址。局部变量在函数结束后会被销毁因此返回它的地址会导致未定义行为。示例int *getPointer() { int a 5; return a; // 返回局部变量的地址 }a是getPointer函数中的局部变量在函数结束后a的地址不再有效。解决方案避免返回局部变量的地址。如果确实需要返回指针可以使用动态内存分配或将变量声明为静态变量。正确示例int *getPointer() { int *ptr (int *)malloc(sizeof(int)); // 动态分配内存 *ptr 5; return ptr; } 或者 int *getPointer() { static int a 5; // 静态变量不会被销毁 return a; }多文件编译配置任务在tasks.json中添加编译任务便于快捷键调用。配置IntelliSense辅助编辑。递归函数在C语言中递归函数是一种直接或间接调用自身的函数。递归是一种解决问题的编程技术其中问题被分解为一个或多个更小的相同问题直到达到最基本的形式然后逐步解决每个问题。1.递归函数的基本原理递归函数通过重复调用自身来解决问题。每个递归函数至少需要两个部分基线条件Base Case递归终止的条件当满足该条件时递归停止避免无限循环。递归步骤Recursive Step在每次递归调用时问题被分解为一个或多个更小的问题。2.递归函数的结构递归函数的基本结构如下return_type function_name(parameters) { if (base_case_condition) { // 基线条件终止递归 return base_case_value; } else { // 递归步骤继续调用函数自身 return function_name(smaller_problem); } }示例计算阶乘阶乘是递归问题的经典示例。一个正整数的阶乘是所有小于或等于该整数的正整数的乘积。数学上阶乘n!的定义为也可以递归地定义为#include stdio.h int factorial(int n) { if (n 0 || n 1) { // 基线条件n为0或1时返回1 return 1; } else { return n * factorial(n - 1); // 递归步骤 } } int main() { int number 5; printf(Factorial of %d is %d\n, number, factorial(number)); return 0; }示例斐波那契数列斐波那契数列是指这样一个数列0,1123581321345589……这个数列从第3项开始每一项都等于前两项之和。斐波那契数列定义为递归与迭代的比较递归与迭代如for或while循环在解决问题时经常可以相互替代。两者的主要区别如下•递归•自然适用于问题本身具有递归性质的场景。•容易实现但可能导致栈溢出性能开销大。•迭代•循环结构往往更加高效。•代码可能不如递归表达直观尤其是当问题本身是递归时。例如计算斐波那契数列时递归效率低可以用迭代实现来提高性能int fibonacci_iterative(int n) { if (n 0) return 0; if (n 1) return 1; int a 0, b 1, next; for (int i 2; i n; i) { next a b; a b; b next; } return b; }示例求字符串长度#include stdio.h int stringLength(const char *str) { if (*str \0) { return 0; // 遇到结束符返回0 } else { return 1 stringLength(str 1); // 递归调用计算剩余部分的长度 } } int main() { const char *str Hello, World!; printf(Length of the string is %d\n, stringLength(str)); return 0; }goto语句goto label; // 直接跳转到标签位置 ... label: // 定义标签位置 // 代码块其中label是一个标识符用于标记目标位置可以出现在同一函数中的任何地方。注意事项标签名后面需加冒号:goto只能在同一函数内部跳转不能跨函数跳转。示例代码讲解1跳出多层循环#include stdio.h int main() { int i, j; for (i 0; i 5; i) { for (j 0; j 5; j) { if (i 2 j 3) { goto exit_loop; // 当满足条件时跳出所有循环 } printf(i %d, j %d\n, i, j); } } exit_loop: printf(Exited loops at i %d, j %d\n, i, j); return 0; }2错误处理int process_file(const char *filename) { FILE *file fopen(filename, r); char *buffer NULL; if (file NULL) { goto error; // 文件打开失败 } buffer malloc(1024); // 分配内存 if (buffer NULL) { goto error; // 内存分配失败 } // 假设执行了一些操作 ... fclose(file); // 正常关闭文件 free(buffer); // 释放内存 return 0; // 正常返回 error: if (file) fclose(file); // 若文件已打开则关闭 if (buffer) free(buffer); // 若内存已分配则释放 return -1; // 返回错误代码 }适合goto语句的情况1.简化清理流程避免重复代码2.避免增加额外的函数调用开销3.维护局部上下文4.提高可读性避免深层嵌套int process_resources() { if (allocate_resource1()) { if (allocate_resource2()) { if (allocate_resource3()) { // 成功操作 return 0; } release_resource2(); } release_resource1(); } return -1; } int process_resources() { if (!allocate_resource1()) goto error; if (!allocate_resource2()) goto error; if (!allocate_resource3()) goto error; // 成功操作 return 0; error: release_resources(); // 集中释放已分配资源 return -1; }