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)
{
}
举例:


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;
}

举例:

结束语
- 以上就是C语言函数的内容,核心还是函数名、输入参数、返回值的熟练。可以在牛客尝试刷刷C的题目来练习。牛客网刷题(点击可以跳转),可以尝试注册使用。

参考资料:
链接: 嵌入式C语言

被折叠的 条评论
为什么被折叠?



