嵌入式C语言
5 函数
5 - 1 函数概述
三要素:**1.函数名(地址)**2.参数 3.返回值
-
指针保存函数
int (*p)(int, int, char);
是一个定义了一个函数指针变量p
的语句,该函数指针变量可以指向一个参数类型为int
、int
、char
,返回类型为int
的函数。这种函数指针的定义可以解读为:
(*p)
: 表示p
是一个指针变量,通过*
表示指针的间接引用。(int, int, char)
: 表示函数的参数列表,参数类型依次为int
、int
、char
。int
: 表示函数的返回类型。
如何使用这种函数指针:
cCopy code#include <stdio.h> // 定义一个函数,参数类型为 int、int、char,返回类型为 int int my_function(int a, int b, char c) { printf("参数 a: %d\n", a); printf("参数 b: %d\n", b); printf("参数 c: %c\n", c); return a + b; } int main() { int (*p)(int, int, char); // 声明一个函数指针变量 p p = my_function; // 将 my_function 函数的地址赋值给函数指针 p int result = p(2, 3, 'c'); // 通过函数指针调用 my_function 函数,并传入参数 printf("函数指针调用的结果: %d\n", result); return 0; }
在上面的示例中,我们定义了一个函数指针变量
p
,并将my_function
函数的地址赋值给它。然后,我们通过函数指针p
调用my_function
函数,并传入参数进行调用。最后,我们打印函数调用的结果。注意函数指针的类型必须与所指向的函数的参数类型和返回值类型一致。指针指向printf
int main() { int (*myshow)(const char *,...); printf("hello\n"); myshow = printf; //myshow = (int(*)(const char*,...))0x00007ff794a516e0; myshow("world!\n"); return 0; }
指针数组存放函数
int (*p[7])(int,int); p[0] = fun1; p[1] = fun2; ... //调用 p[day](10,20);
5 - 2 值传递
-
形参实参拷贝
在函数调用过程中,形参和实参之间的值传递通常使用拷贝方式。
当函数被调用时,形参会在函数栈帧中创建,并且将相应的实参值复制到形参中。这意味着在函数内部对形参的任何修改都不会影响到原始的实参,因为形参只是实参的副本。
这种方式称为值传递(Pass by Value),因为函数接收的是实参的值的拷贝,而不是实参本身。这样的拷贝操作保护了原始实参的值,使得函数内部的修改不会对原始实参产生直接影响。
cCopy code#include <stdio.h> // 定义一个函数,参数类型为 int void increment(int x) { x++; // 在函数内部对形参进行修改 } int main() { int num = 5; printf("num before function call: %d\n", num); // 打印函数调用前的值 increment(num); // 调用函数 increment,并传递 num 作为实参 printf("num after function call: %d\n", num); // 打印函数调用后的值 return 0; }
在上面的示例中,我们定义了一个函数
increment
,接受一个形参x
,在函数内部对形参进行了自增操作。然后在main
函数中声明了一个变量num
并赋值为 5,将其作为实参传递给increment
函数进行调用。在函数调用前后,我们分别打印了变量num
的值。从输出可以看出,在函数调用过程中对形参的修改并没有影响到原始的实参num
的值,因为形参x
只是num
的副本。 -
地址传递
地址传递是将实参的地址(指针)传递给函数的形参,形参在函数内部可以通过指针访问和修改原始实参的值。这意味着函数在内部直接操作的是原始实参的内存地址,对形参的任何修改都会直接反映到原始实参上。
示例:
cCopy code#include <stdio.h> // 定义一个函数,参数类型为 int 指针 void increment(int* x) { (*x)++; // 在函数内部通过指针对原始实参进行修改 } int main() { int num = 5; printf("num before function call: %d\n", num); // 打印函数调用前的值 increment(&num); // 调用函数 increment,并传递 num 的地址作为实参 printf("num after function call: %d\n", num); // 打印函数调用后的值 return 0; }
scanf("%d",&a); //取地址符号代表着,对a的值进行地址值传递
5 - 3 连续空间传递
-
数组
地址传递
int abc[10]; 实参: fun(abc); 形参: void fun(int *p); void fun(int p[10]);
-
结构体
- 函数值传递:将结构体对象作为函数参数传递时,会将对象的值复制一份传递给函数。函数内部对参数进行修改不会影响原始对象。
cCopy codestruct MyStruct { int x; int y; }; void funcByValue(struct MyStruct obj) { obj.x = 10; // 修改参数副本的值 obj.y = 20; } int main() { struct MyStruct obj = { 1, 2 }; funcByValue(obj); // 传递结构体对象的副本 printf("x: %d, y: %d\n", obj.x, obj.y); // 输出原始对象的值,不受函数内修改影响 return 0; }
- 地址传递:将结构体对象的地址作为函数参数传递时,函数可以通过指针访问原始对象的内存,从而可以对对象进行修改。
cCopy codestruct MyStruct { int x; int y; }; void funcByAddress(struct MyStruct* pObj) { pObj->x = 10; // 通过指针修改原始对象的值 pObj->y = 20; } int main() { struct MyStruct obj = { 1, 2 }; funcByAddress(&obj); // 传递结构体对象的地址 printf("x: %d, y: %d\n", obj.x, obj.y); // 输出修改后的对象的值 return 0; }
使用地址传递时需要小心指针的使用,以避免悬空指针或非法访问内存的错误。在函数值传递中,由于会进行值的复制,对较大的结构体可能会引发性能问题。
-
字符空间
结束标志:
'\0'
空字符
'\0'
,也称为空格字符或零字符,是一个ASCII值为0的字符,用于表示字符串的结束。//strlen模板 int strlen(const char *p) { int i = 0; //错误处理 if(p == NULL) { return 0; } //内存处理 while(p[i]){ //逻辑处理 i++; } }
//strcpy模板 void strcpy(char *dest,const char *src); //"" 初始化const char * //char buf[10] 初始化char*
形参 (const char *)
默认为字符空间的标识符
const char *
是一个指向常量字符的指针,其中const
关键字表示该指针指向的数据是常量,不能被修改;char
表示指针指向的数据类型是字符型。需要注意的是,
const char *
类型的指针可以指向字符串常量,但并不一定非要指向字符串常量,它也可以指向其他类型的常量字符数据。在使用const char *
类型的指针时,应当遵循常量性规则,确保不对指针指向的数据进行修改。 -
非字符空间
结束标志:数量
形参 (void *,int len)
默认为数据空间的标识符
般情况下,使用
void
类型指针作为函数参数时,需要在函数内部进行适当的类型转换和内存操作,以确保对数据的处理是正确的。例如,可以使用类型转换将void
类型指针转换为其他类型的指针(如 char*、int* 等),从而可以按照具体的数据类型进行操作。以下是一个简单的示例函数定义,其中包含了
(void *, int len)
形参:cCopy codevoid processData(void *data, int len) { // 在函数内部处理传入的数据,通过 void 类型指针来接受不同类型的数据 // 使用 len 参数控制数据的处理长度 // 可能需要进行类型转换、内存拷贝等操作 // 示例:将 void 类型指针转换为 char* 类型指针,以按照字节进行处理 unsigned char* pData = (unsigned char*)data; for (int i = 0; i < len; i++) { // 处理数据,例如输出每个字节的值 printf("%x ", pData[i]); } }
-
总结
- 子函数查询空间:
const*
- 紫番薯反向修改上层空间内容:
char* (字符)
void* (数据)
- 子函数查询空间:
5 - 4 返回值
-
拷贝传递
char fun(void) { int a = 0x123; int *p = &a; int buf[10]; return 0x123;//只能拷贝接受八个字节,低八位 0x23 return p;//只能拷贝接受地址的低四位 return buf;//只能拷贝接受地址的低四位 } int main() { int ret; ret = fun(); return 0; }
-
返回基本类型
int* fun(void); void fun2(int **p); //二者等价,本质为子函数修改指针p的指向,并返回
-
返回连续空间
指针为空间返回的唯一数据类型
int* fun(void) { //char buf[] = "hello world";//错误 static char buf[] = "hello world"; return buf; } int main() { char *p; p = fun(); printf("%s",p);//正确 return 0; }
-
上述例子中,
buf
是数组的首地址,存在于栈空间,fun函数
返回后自动销毁,所以p指针指向空间是非法的但是使用
static
修饰buf
后,使其成为数据段空间,则可返回
int* fun(void) { return "hello world"; } int main() { char *p; p = fun(); printf("%s",p);//正确 return 0; }
- 上述例子中,返回只读区的常量区字符串
int* fun(void) { char* s = (char*)malloc(100); strcpy(s,"hellow world"); return s; } int main() { char *p; p = fun(); printf("%s",p);//正确 free(p); return 0; }
- 上述例子中,返回堆空间
-