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

0.前言

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

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


1.函数概述

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

示例:

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

2.函数名

  • 函数指针接收一个地址值(函数标签),就可以直接用指针操作该函数。
#include <stdio.h>
int main()
{
	int (*myshow)(const char *,...);
	printf("hello world!\n");
	myshow=printf;
	myshow("============\n");
	return 0;
}

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

#include <stdio.h>
int main()
{
	int (*myshow)(const char *,...);
	printf("the printf is %p\n",printf);
	myshow=printf;
	myshow("============\n");
	return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{
	int (*myshow)(const char *,...);
	printf("the printf is %p\n",printf);
	myshow=(int(*)(const char *,...))0x8048320;//*将数转换成地址,其他又将地址转换成函数标签
	myshow("============\n");
	return 0;
}

在这里插入图片描述

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

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

3.输入参数

承上启下的功能

//主函数(上层调用者):
函数名(要传递的值);//实参
//====================
//子函数(下层):
函数的返回值  函数名(接收的数据)//形参
{
}
//实参传递给形参
//传递的形式:拷贝

3.1拷贝传递

  • 拷贝内存大小一致性
#include <stdio.h>
void myswap(int buf)//理解为预留了4个字节作为接收器
{
}

int main()
{
	myswap();//传递参数不匹配
	return 0;
}

在这里插入图片描述

#include <stdio.h>
void myswap(int buf)
{
	printf("the buf is %x\n",buf);
}

int main()
{
	myswap(0x123);//匹配
	return 0;
}

在这里插入图片描述

#include <stdio.h>
void myswap(int buf)
{
	printf("the buf is %x\n",buf);
}

int main()
{
	int a=20;
	myswap(a);//匹配
	return 0;
}

在这里插入图片描述

#include <stdio.h>
void myswap(char buf)
{
	printf("the buf is %x\n",buf);
}

int main()
{
	myswap(0x1234);
	return 0;
}

在这里插入图片描述

  • 即使是地址,也是按位拷贝。
#include <stdio.h>
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;
}

在这里插入图片描述

#include <stdio.h>
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;
}

在这里插入图片描述

3.2值传递

#include <stdio.h>
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);
}

//交换失败

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

3.3地址传递

#include <stdio.h>
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);
}

在这里插入图片描述

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

举例:

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

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)//保持一致,按位逐一拷贝
  • 函数变量为大空间,如结构体定义时建议写成结构体指针方式,地址传递,更加节约空间。

数组

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

传递方式选择

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

3.5连续空间只读性

viod fun(char a);//函数只想拿到一个8bit的副本
viod fun(char *b);//修改or连续空间(连续空间是只读还是可读可写这是一个问题)
//=====连续空间=======
#include <stdio.h>
void fun(char *p)
{
	p[1]='2';//字符串是常量,常量区无法写,出现段错误
}
int main()
{
	fun("hello");//字符串在字符串连续空间中
}
//==========更正========
#include <stdio.h>
void fun(char *p)
{
	p[1]='2';//数组是可读可写的,无段错误
}
int main()
{
	char buf[]=	fun("hello");//字符串在字符串连续空间中
	fun(buf);//数组buf是可读可写的,在栈空间中
}
  • 只读连续空间必须使用:const
char *p;//认为是可修改的,大概率是可修改的
const char *p;//只读空间,为了看看空间,不修改
void fun(const char *p)
{
}

举例:
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++
	}	
}
  • strlen数长度
int strlen(sonst char *p)//字符串连续空间就带*,数长度不修改内容带const
{
	/*错误处理,判断输入参数是否合法*/
	if(p==NULL)
	{
	return;//或其他
	}
	/*内存处理,从头到尾逐一处理,即遍历*/
	while(*p)
	{
	/*代码实现*/
	p++;
	}
}
  • 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++;
	}
}

3.7非字符空间操作

  • 不再以/0作为结尾。内存读取方式不是一个字节一个字节读,0不是结束标志。
int* p;unsiged char *p;short *p;struct abc *p;...
  • 结束标志:非字符空间结束标志结束标志是数量,数个数。
  • 举例,标准声明:
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);
}
  • 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;...
}

3.8函数地址传递总结

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

以上,三种方式为重点。

4.函数返回值

4.1基本语法

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

在这里插入图片描述

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

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);
}
  • 修改指针指向的地址
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的指向地址的内容有可能会变
}

4.3返回连续空间类型

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

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

#include <stdio.h>
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;
}

在这里插入图片描述

#include <stdio.h>
char *fun(void)
{
	return "hello world!";//双引号是字符串常量(本事就是地址),常量区的内容不会因为函数返回而消失
}
int main()
{
	char *p;
	p=fun();
	printf("the p is %s\n",p);
	return 0;
}

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

4.5函数内部实现

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

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

#include <stdio.h>
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;
}

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

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

结束语

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不僈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值