• 【嵌入式C语言】8.函数


    0.前言

    • 本文主要讲解C语言函数。主要包括函数结构当中的函数名、输入参数、返回值三大部分相关内容。

    • 最近编码练习受益良多,个人建议荐逛博客学习的同时一定要多写代码多刷题,网上就有很多在线编码的网站。
    • 个人比较推荐在牛客网刷题(点击可以跳转),主要它登陆后会保存刷题记录进度,重新登录时写过的题目代码不会丢失,我觉得这一点还挺好的。
    • 个人刷题练习系列专栏:个人CSDN牛客刷题专栏
    • 牛客C语言题目位置如下:
      在这里插入图片描述


    1.函数概述

    • 一堆代码的集合,用一个标签描述它。主要目的是复用化
    • 内存访问类型:标签访问,即函数名访问,函数也是连续空间。
    • 函数三要素:
      ①函数名,即标签,又即地址;
      ②输入参数;
      ③返回值。
    • 在定义函数时,必须将3要素告知编译器。
    • 内存访问方式
      变量名访问:char a;普通变量1要素
      代码标签访问:char buf[10];指针变量2要素

    示例:

    int fun(int a,int b,char c)
    {
    }
    
    • 1
    • 2
    • 3
    • 如何用指针保存函数名?
    char *p;//保存指针
    char (*p)[10];//保存数组
    int (*p)(int a,int b,char c);//保存函数
    
    • 1
    • 2
    • 3

    2.函数名

    • 函数指针接收一个地址值(函数标签),就可以直接用指针操作该函数。
    #include 
    int main()
    {
    	int (*myshow)(const char *,...);
    	printf("hello world!\n");
    	myshow=printf;
    	myshow("============\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    在这里插入图片描述

    #include 
    int main()
    {
    	int (*myshow)(const char *,...);
    	printf("the printf is %p\n",printf);
    	myshow=printf;
    	myshow("============\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    #include 
    int main()
    {
    	int (*myshow)(const char *,...);
    	printf("the printf is %p\n",printf);
    	myshow=(int(*)(const char *,...))0x8048320;//*将数转换成地址,其他又将地址转换成函数标签
    	myshow("============\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    • 知道一个函数的地址,就可以转换并重复调用。

    • 指针数组,保存多个函数的标签,分别执行调用多个函数。
    //指针数组原型示例:
    char *p[5];
    p[1]="xxx";
    p[2]="xxx";
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    //指针数组调用函数示例:
    int (*p[7])(int a,int b);
    p[0]=fun1;//注册
    p[2]=fun2;
    ...
    p[day](10,20);//回调
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 函数名就是地址,知道地址就可以去调用它。

    3.输入参数

    承上启下的功能

    //主函数(上层调用者):
    函数名(要传递的值);//实参
    //====================
    //子函数(下层):
    函数的返回值  函数名(接收的数据)//形参
    {
    }
    //实参传递给形参
    //传递的形式:拷贝
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.1拷贝传递

    • 拷贝内存大小一致性
    #include 
    void myswap(int buf)//理解为预留了4个字节作为接收器
    {
    }
    
    int main()
    {
    	myswap();//传递参数不匹配
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    #include 
    void myswap(int buf)
    {
    	printf("the buf is %x\n",buf);
    }
    
    int main()
    {
    	myswap(0x123);//匹配
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    #include 
    void myswap(int buf)
    {
    	printf("the buf is %x\n",buf);
    }
    
    int main()
    {
    	int a=20;
    	myswap(a);//匹配
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    #include 
    void myswap(char buf)
    {
    	printf("the buf is %x\n",buf);
    }
    
    int main()
    {
    	myswap(0x1234);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    • 即使是地址,也是按位拷贝。
    #include 
    void myswap(int buf)
    {
    	printf("the buf is %x\n",buf);
    }
    
    int main()
    {
    	char *p="hello world!";
    	printf("the p if %x\n",p)
    	myswap(p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    #include 
    void myswap(int a)//可以同名
    {
    	printf("the buf is %x\n",a);
    }
    
    int main()
    {
    	char *p="hello world!";
    	char b[10];
    	printf("the p if %x\n",b)
    	myswap(b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    3.2值传递

    #include 
    void myswap(int a,int b)//可以同名
    {
    	int c;
    	c=a;
    	a=b;
    	b=c;
    }
    
    int main()
    {
    	int a=20;
    	int b=30;
    	myswap(a,b);
    	printf("after swap,the a is %d,the b is %d\n",a,b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    //交换失败

    • 上层调用者拥有保护自己空间值不被修改的能力。

    3.3地址传递

    #include 
    void myswap(int *a,int *b)//可以同名,指针(地址)才是永远标识一个变量的方式
    {
    	int c;
    	c=*a;
    	*a=*b;
    	*b=c;
    }
    
    int main()
    {
    	int a=20;
    	int b=30;
    	myswap(&a,&b);//指针(地址)才是永远标识一个变量的方式
    	printf("after swap,the a is %d,the b is %d\n",a,b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    • 上层调用者让下层子程序修改自己空间值的方式。

    举例:

    int a;
    scanf("%d",a);//函数中对数据操作,返回后不会改变空间的值
    scanf("%d",&a);//函数中对地址操作,可以改变空间的值
    
    • 1
    • 2
    • 3

    3.4连续空间的传递

    结构体

    struct abc{int a;int b;int c};
    struct abc buf;
    //==========================
    //实参:
    fun(buf);
    //形参:
    void fun(struct abc a1)//保持一致,按位逐一拷贝
    //===========================
    //形参:
    fun(&buf);
    //实参:
    void fun(struct abc *a2)//保持一致,按位逐一拷贝
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 函数变量为大空间,如结构体定义时建议写成结构体指针方式,地址传递,更加节约空间。

    数组

    int abc[10];
    //============
    //实参:
    fun(abc);
    //形参:
    void fun(int *p)
    //or
    void fun(int p[10]//也可以,一般不这么写
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 连续空间的传递,需要考虑指针(地址)传递。

    传递方式选择

    • 值传递:
      上层调用者保护自己空间值不被修改时使用。
    • 地址传递:
      上层调用者让下层子程序修改自己空间值时使用。
      连续空间的传递时使用。

    3.5连续空间只读性

    viod fun(char a);//函数只想拿到一个8bit的副本
    
    • 1
    viod fun(char *b);//修改or连续空间(连续空间是只读还是可读可写这是一个问题)
    //=====连续空间=======
    #include 
    void fun(char *p)
    {
    	p[1]='2';//字符串是常量,常量区无法写,出现段错误
    }
    int main()
    {
    	fun("hello");//字符串在字符串连续空间中
    }
    //==========更正========
    #include 
    void fun(char *p)
    {
    	p[1]='2';//数组是可读可写的,无段错误
    }
    int main()
    {
    	char buf[]=	fun("hello");//字符串在字符串连续空间中
    	fun(buf);//数组buf是可读可写的,在栈空间中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 只读连续空间必须使用:const
    char *p;//认为是可修改的,大概率是可修改的
    const char *p;//只读空间,为了看看空间,不修改
    void fun(const char *p)
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    举例:
    printf
    在这里插入图片描述

    3.6字符空间操作

    • 修改 int* char* …

    • 空间传递 利用地址,省内存
      子函数看看空间里的情况 const
      子函数反向修改上层空间里的内容(又分为字符空间和非字符空间两种情况)

    • 空间:空间首地址、结束标志两要素

    • 字符空间与非字符空间:结束标志的不同

    • 结束标志:内存里面存放了0x00(1B),是字符空间的结束标志
      非字符空间0x00,不能当成结束标志

    字符空间
    char类型是字符空间的绝对标志

    //char*操作模板
    void fun(char *p)
    {
    	int i=0;;
    	while(p[i])//==0结束
    	{
    	p[i]=x;
    	i++
    	}	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • strlen数长度
    int strlen(sonst char *p)//字符串连续空间就带*,数长度不修改内容带const
    {
    	/*错误处理,判断输入参数是否合法*/
    	if(p==NULL)
    	{
    	return;//或其他
    	}
    	/*内存处理,从头到尾逐一处理,即遍历*/
    	while(*p)
    	{
    	/*代码实现*/
    	p++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • strcopy
    //""--->初始化const char*
    //char buf[10]--->初始化char*
    int strcopy(char *dest,const char*sre)//字符串连续空间就带*,dest复制修改内容不带const,sre不更改,加const
    {
    	/*错误处理,判断输入参数是否合法*/
    	if(sre==NULL)
    	{
    	return;//或其他
    	}
    	/*内存处理,从头到尾逐一处理,即遍历*/
    	while(*sre)
    	{
    	/*代码实现*/
    	sre++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.7非字符空间操作

    • 不再以/0作为结尾。内存读取方式不是一个字节一个字节读,0不是结束标志。
    int* p;unsiged char *p;short *p;struct abc *p;...
    
    • 1
    • 结束标志:非字符空间结束标志结束标志是数量,数个数。
    • 举例,标准声明:
    void  fun(unsignedcchar *p,int len)
    {
    int i;
    for(i=0;i<len;i++)
    {
    	//操作
    	p[i]=x;
    }
    }
    int main ()
    {
    	struct sensor_data buf;
    	//int buf[100];//若改成int,又得该子函数参数
    	fun(&buf,sizeof(buf)*1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • void*:非字符空间(数据)的标识符;char*:字符空间的标识符。
    • void*变量,操作时尽量不使用char类型接收,不要s%去看,可使用unsigned char或其他。
    • menmcpy
      在这里插入图片描述
    • recv
      在这里插入图片描述
    • send
      在这里插入图片描述
    int fun(void* buf,int len)//处理方式
    {
    	unsigned char*tmp=(unsigned char*)buf;void*强制类型转换为unsigned char*,按字节去操作处理
    	tmp[i]=x;...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.8函数地址传递总结

    • 单变量修改 int* char* …(1)
    • 空间传递 利用地址,省内存
      子函数看看空间里的情况 const。(2)
      子函数反向修改上层空间里的内容(又分为字符空间char* 和非字符空间void*两种情况)。(3)

    以上,三种方式为重点。

    4.函数返回值

    4.1基本语法

    • 返回值:提供启下功能的一种表现形式。
    - 返回类型 函数名称(输入列表)
    {
    	return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 调用者:a=fun();“=”接收,无“=”接收。
    • 被调者:
      int fun()
      {
      return num;
      }
    • 返回值,返回方式是拷贝的过程。
    #include 
    char fun(void)
    {
    	return 0x123;
    }
    int main()
    {
    	int ret;
    	ret=fun();
    	printf("the ret is %x\n",ret);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    • 返回类型两种,只能返回一个
      基本数据类型
      指针类型(空间)
      不能返回数组

    4.2返回基本数据类型

    • int char float 结构体等等
    • 其中结构体:struct abc fun(void);//可以,但一般不这么干,上下函数都得开辟大空间,内存占用太大
    //方式1:
    int fun1(void)//返回值方式返回
    {
    	return xxx;
    }
    int main()
    {
    	int a=0;
    	a=fun1();
    }
    //===============
    //方式2:
    void fun2(int *p)//空间修改方式返回
    {
    	*p=xxx;
    }
    int main()
    {
    	int a=0;
    	fun2(&a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 修改指针指向的地址
    int *fun1(void);//返回值是地址
    int main()
    {
    	int *p;
    	p=fun1();//指针接收地址,修改了指针的值,指向了返回的地址
    }
    //================
    //同上
    void fun2(int **p);//接收值是二维指针,函数可以修改指针的值(修改指针指向的地址)
    int main()
    {
    int*p;
    fun2(&p);//指针(地址)的地址,为二维指针
    }
    
    //======================
    //对比理解
    void fun2(int *p);//接收值是指针,函数可以修改指针的值
    int main()
    {
    int*p;
    fun2(p)//未取地址,传的是p,还是值转传递,p的内容不会变,即p指向的位置不会变,p的指向地址的内容有可能会变
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.3返回连续空间类型

    • 不能返回数组,指针作为空间返回的唯一数据类型。
      int *fun()//返回值为连续空间
    • 返回地址2+1个问题:指向谁,指向空间读写性;指向合法性。

    作为函数的设计者,必须保证函数返回的地址指向的空间是合法的。【即不是局部变量】

    #include 
    char *fun(void)
    {
    	char buf[]="hello world!";//buf局部有效
    	return buf;
    }
    int main()
    {
    	char *p;
    	p=fun();
    	printf("the p is %s\n",p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    #include 
    char *fun(void)
    {
    	return "hello world!";//双引号是字符串常量(本事就是地址),常量区的内容不会因为函数返回而消失
    }
    int main()
    {
    	char *p;
    	p=fun();
    	printf("the p is %s\n",p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    -作为使用者,调用函数时,只需设置与返回值类型一致的变量进行接收即可。不额外多创建变量接收,防止浪费内存。

    4.5函数内部实现

    • 基本数据类型框架:
    基本数据类型 fun(void)
    {
    	基本数据类型 ret;
    	//处理操作
    	ret=xxx;
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    int fun(int *p)
    {
    	int ret=0;
    	//...
    	count++;//等处理操作
    	//...
    	ret=xxx;
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 返回数据类型地址空间框架:
    char *fun(void)
    {
    	char*p=NULL;
    	char buf[];
    	return buf;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    地址空间第一种框架举例,静态区

    #include 
    char *fun(void)
    {
    	static char buf[]="hello world!";
    	return buf;//双引号是字符串常量(本事就是地址),常量区的内容不会因为函数返回而消失
    }
    int main()
    {
    	char *p;
    	p=fun();
    	printf("the p is %s\n",p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    地址空间第二种框架举例,只读区(字符串常量,意义不大,不太常用,如4.3中例2)
    地址空间第三种框架举例,堆区(malloc free)

    #include 
    #include 
    #include 
    char *fun(void)
    {
    	char*s=(char*)malloc(100);//①申请(malloc使用三步骤)
    	strcpy(s,"hello world!")//②拷贝(函数返回后,s释放了,但malloc申请的空间还在)
    	return buf;//双引号是字符串常量(本事就是地址),常量区的内容不会因为函数返回而消失
    }
    int main()
    {
    	char *p;
    	p=fun();
    	printf("the p is %s\n",p);
    	free(p);//③释放(释放p指向的空间,即malloc申请的空间)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    举例:
    在这里插入图片描述

    结束语

  • 相关阅读:
    什么是去中心化云计算?
    《ElementPlus 与 ElementUI 差异集合》el-select 差异点,如:高、宽、body插入等
    爬虫批量下载科研论文(SciHub)
    【视频去噪】基于全变异正则化最小二乘反卷积是最标准的图像处理、视频去噪研究(Matlab代码实现)
    CS144 计算机网络 Lab1:Stream Reassembler
    南卡对比评测明基护眼灯,2022双十一哪一款护眼台灯更值得入手
    推荐几个好用的IDEA插件_让你解放双手的秘密(一)
    为什么有的人把代码写的如此复杂?
    postgresql|数据库|数据迁移神器ora2pg的安装部署和初步使用
    网络安全-pikachu之文件上传漏洞2
  • 原文地址:https://blog.csdn.net/weixin_43490708/article/details/126594356