嵌入式 C 语言:函数
函数是一个有特定功能、代码独立的代码块;1.1 声明声明形式如下:数据类型函数名称(形参说明)函数体如:int abs( int a ,int b ) {函数体};① 数据类型是函数返回值的数据类型,若无返回值,须将数据类型声明为 void;② 形参可以有多个,多个形参之间用 逗号 隔开;也可以没有参数,即无参函数;③ 函数体即 “ { 代码块 } ”,表示函数执行功能的代码;
1. 概述
函数是一个有特定功能、代码独立的代码块;
1.1 声明
声明形式如下:
数据类型 函数名称 (形参说明)函数体
如:int abs ( int a ,int b ) { 函数体 };
① 数据类型是函数返回值的数据类型,若无返回值,须将数据类型声明为 void ;
② 形参可以有多个,多个形参之间用 逗号 隔开;也可以没有参数,即无参函数;
③ 函数体即 “ { 代码块 } ”,表示函数执行功能的代码;
<以下代码是函数的简单使用演示:

1.2 说明与使用
函数必须先说明再使用;
函数的说明:数据类型 函数名 ( <形参> );
如 double Power( double a , int b )
其中,数据类型不能缺省,形参名可以缺省: double Power( double , int )
函数的使用:也称函数的调用—— 函数名 ( <实际参数> );
有参函数可以单独成为一个语句,也可以作为一个运算量出现在表达式中;
但无参函数只能单独成为一个语句;
<如下,是求 X 的 n 次幂 的代码演示:注意函数 先声明再使用

若函数实现在 main 函数后面,则须先说明一下;
<一般,函数采用 说明——调用——实现 的流程,即以下代码所示:

当然,也可以解释为什么需要引入头文件: #include <stdio.h>
头文件中有许多函数声明,比如 scanf ( ) ,printf ( );
2. 函数传参
传参方式有三种:
全局变量(几乎不用),复制传递(又叫做值传递),地址传递(又叫做指针传递);
2.1 全局变量
声明在函数外部(包括 main 函数外面)的变量就是全局变量;
但是,一经定义后就会在程序的任何地方看见,一经修改,其值就会受到影响
一般不使用;
< 如下代码所示,定义全局变量,

2.2 复制传递(值传递)
复制传递拷贝的是实参的值,因此过程中不会改变实参的值
因为,形参是新开辟的存储空间,在函数中改变形参的值,不会影响到实参
< 以下代码演示了值传递不能改变实参的值;

2.3 地址传递(指针传递)
又称为指针传递,被调用函数对形参的操作(即实参地址)将直接影响实参的值;
只需要明白形参声明为指针,实参传递的是地址即可;

2.4 练习
编写一个函数,统计字符串中小写字母个数,并将其转换为大写字母;

3. 数组传参
数组传参略有区别,单独学习;
数组传参也有三种方式:全局变量,值传递,地址传递(又叫做指针传递);
值传递与指针传递传递:实参传递的都是数组名
3.1 值传递
实参为数组名,形参为数组名(本质是一个指针变量)
<下面代码展示通过函数求一维数组中元素的和:
第九行可以深刻理解数组值传递中形参的本质是指针变量;

<那如何修改呢:增加一个形参用于记录数组元素个数

3.2 地址传递
地址传递的形参是指针变量;
<下面是通过地址传递方式演示的代码:

一维数组应该传递 数组名、元素个数;
字符串数组只需传递数组名即可,因为遍历时可根据 “ \0 ” 判断;
4. 指针函数
见名知意——即指针类型的函数,函数返回值为地址;
下面看一个经典的指针函数问题:
<以下代码是错误的:自定义函数中的 str 是局部变量,只能在该函数中使用
因此不能作为返回值,返回到 main 函数中;

若需要改正,则返回 全局变量的地址 / 字符串常量的地址 / 静态变量的地址 即可;
,
4.1 练习一
编写函数,用于删除字符串中的空字符;
< 如下代码所示:
双指针协作:p 负责遍历源字符串,s 负责构建无空格的新字符串。
原地修改:函数直接在原字符串内存上操作,无需额外空间。
返回原地址:通过保存初始地址(r),确保返回的是修改后的字符串起始位置。
最终结果:所有空格被删除,新字符串为 "helloworldD",s 指向最后一个字符 'D' 的下一位,添加 '\0'。
局部变量地址:r 是 getString 函数内的局部变量,但返回它指向的地址是安全的。这是因为指针本身是局部的,但它指向的内存地址是调用者提供的外部数组(main 函数中的 ch 数组)。函数只是操作并返回这个外部内存的地址,并没有返回局部变量本身。
辨别:如果返回的是局部变量的值(如 int a = 10; return a;)或局部数组的地址(如 char arr[10]; return arr;),这是危险的,因为局部变量在函数返回后会被销毁。但在代码中,r 指向的是调用者传递的外部内存,因此是安全的。

本题较难,如有疑问可借助 Ai 工具解答;
源代码如下:
#include <stdio.h>
char * removeSpaces(char * str) {
char * dest = str;
char * original = str; // 保存原始地址
while (*str) {
if (*str != ' ') {
*dest = *str;
dest++;
}
str++;
}
*dest = '\0'; // 添加字符串结束符
return original; // 返回外部数组的地址
}
int main() {
char buffer[] = " Hello World "; // 局部数组,但生命周期覆盖main函数
char * result = removeSpaces(buffer);
printf("处理后的字符串: %s\n", result); // 输出: "HelloWorld"
printf("原始数组地址: %p\n", (void*)buffer);
printf("返回的地址: %p\n", (void*)result); // 两者地址相同
return 0;
}
延申:strcpy 函数返回的是前面字符串的地址;

4.2 练习二
编写函数,用于字符串连接;

4.3 练习三
编写函数,将输入的数字转换为字符串;
<如下代码为将数字转换为字符串,需要注意以下问题:
第七行:scanf 语句的正确使用:注意占位符为 %d(而非 %p),不需要使用 ‘ \ n ’(格式字符串中的\n会导致 scanf 持续等待输入,直到遇到非空白字符);
第十八行:i++ 的位置应在循环体最后,否则 s[1] 为空;
第二十行:注意添加 '\0';

<但上述代码是逆序输出,应将数组逆序输出>
第二十行字符串末尾添加 ‘\0\',所以交换字符串内容时从后面的 i - 2 开始,如 25 行所示;

5. 递归函数
递归函数即在函数体中调用函数本身的函数;
< 递归函数最为典型的是计算斐波那契数列,即以下代码:


6. 函数指针
函数指针是用来存放函数地址的指针
函数的地址是函数的入口地址,也就是函数名
< 以下代码是函数指针的简单使用:包括函数指针的声明,使用,调用:

6.1 函数指针数组
保存若干个函数名的数组;
<以下代码是函数指针数组的声明,使用;
19 行:p 作为数组名是常量,不能被赋值。一旦数组被定义,其地址就固定不变了。而普通指针是变量,可以被重新赋值。

总结:
1. 函数一般经理 “声明——调用——实现” ,若函数实现在 main 函数前,无需“ 声明 ”;
2. 函数传参有三种:全局变量,复制传递(值传递),地址传递(指针传递);
3. 复制传递(值传递)本质是拷贝,不会改变实参的数值;地址传递(指针传递)会影响到实参;
3. 整型数组传参至少需要两个形参,一个传递数组名,另一个传递数组个数;字符串数组只需传递字符串首地址即可;
4. 整型数组传参:复制传递(值传递):如 int a[ ],相当于指针函数 int * a ;地址传递(指针传递):int * a;
5. 指针函数是指针类型的函数,返回值是地址,注意局部变量不可以返回;
6. 函数指针( 如 int ( *p )( 形参 ) )是存放函数地址(函数名)的地址变量;
7. 函数指针数组( 如 int ( *p [ 10 ] )( 形参 ) )是存放函数地址(函数名)的地址变量数组,类似于指针数组 int * p [ 10 ];
8. 递归函数与函数指针的使用较困难,不要期望一口气掌握;
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)