目录
上一篇复习了第三次的8道编程题,前面我们讲的都是指针的基础,这一节开始是高级指针的内容。
说明:我们学过单片机的一般都是有C语言基础的了,网上关于C语言的资料有很多,大家如果对C语言不熟悉的话可以先去详细学一下,再以这篇博文作为复习资料学习。
这篇博文的目的是复习C语言,我们会陆续以30多个编程题作为复习要点,这30多个编程题基本涵盖了C语言所有的内容了,只要你掌握了这30多个编程题,那么你的C语言基本就没什么问题了。
注意:由于本专栏是嵌入式全栈开发专栏,为了我们能熟悉以后实际工作中的开发环境,我们写C语言全部在Linux中的vim编辑器中写,这么做事为了我们能够熟练掌握Linux系统的常用命令以及Linux上的vim编辑器的常用工作命令,以达到对口训练的目的!
vim编辑器的一些工作命令在上一篇博文中已经详细介绍过了,如果不了解可以先去看看。
我们正式开始:
函数指针
函数指针的定义
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
把函数的这个首地址(函数入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。
函数指针变量定义的一般形式为:
类型说明符 (*指针变量名)();
例如:
int (*pf)( );
表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
这样定义是如何确定它就是函数指针的呢?我们C语言中有个法则叫“右左法则”,意思是我们先分析右边再分析左边,我们以函数指针的定义为例:int (*pf)( ); 先看p这个变量的右边,是个括号,我们只能转向看p的左边,是个*号,说明p是个指针变量名。括号内分析结束,出了这个括号后一样先分析右边,是个括号,那说明这个是个函数调用运算符,也就是说p这个指针一定是指向了一个函数,而这个函数没有参数,再看左边是个int,说明这个函数的返回值是个int型。
函数指针的调用
例如:
int func(int a, int b)
{
return a + b ;
}
int main()
{
int (*pf)(int, int);
pf = func;//第一步:将函数名(函数入口地址)赋值给指针
pf(); //第二步:通过函数指针来调用函数,等价于func();
return 0;
}
注意:被赋值给指针的返回值类型一定要跟函数指针定义时返回值类型一样,否则会出现类型不兼容。
补充:关键字typedef重命名
比如当我们嫌每次都敲unsigned char太长了,我们可以将它重命名:typedef unsigned char uchar; 将unsigned char重命名为uchar,之后我们写uchar即表示unsigned char。
同样如果我们嫌函数指针的类型int (*指针变量名 ) ( ); 每次定义太麻烦了,我们可以重命名:typedef int(*T)(int,int); 表示用T来表示函数指针类型int (*指针变量名 ) (),之后我们写函数指针类型就可以写为:T k; T表示函数指针类型,k是指针变量,等价于 int(*k)(int,int)。
笔试题20
下面两个表达式什么意思?
int *(*(*fp)(int))[10];
int *(*(*array[5])())();
我们用“右左法则”分析一下:
int *(*(*fp)(int))[10];先从fp开始分析起,它的右边是个括号,所以看向它的左边是*号说明它是个指针,出来后fp它的右边是个括号且有参数,说明是个函数调用语句,fp指向函数,函数的参数是个int型,然后fp的左边是个*说明这个函数的返回值是个指针;
(*(*fp)(int))出来后右边是个中括号,说明是个数组定义语句,数组有10个元素。再往左边看是个int*型,说明每个数组元素都是int型的指针。
int *(*(*array[5])())();先从array开始分析起,它的右边是个[5]说明是个array是个数组,有5个元素,array的左边是个*,说明每个元素是个指针。(*array[5])出来右边是个()括号,说明是个函数定义语句,每个元素指向函数1,函数1没有参数,(*array[5])的左边是个*号,说明函数1的返回值是个指针,(*(*array[5])())出来右边是个()说明是个函数定义语句,说明函数1的返回值指向函数2,函数2没有参数,(*(*array[5])())的左边是个int*,说明函数2的返回值是个int*的指针。
函数指针在实际应用中比较少,但是在笔试的时候会考到。一般它会在回调函数的时候用到。
回调函数
回调函数:把函数的名字作为另一个函数的参数
用处:可以修改函数的功能
我们将之前写过的冒泡排序的代码改成这样,就能实现一个函数既能实现从大到小、也能实现从小到大排序的功能:
#include <stdio.h>
int less(int x,int y)
{
return (x>y)?1:0;
}
int greater(int x,int y)
{
return (x<y)?1:0;
}
void sort(int*a,int size,int(*p)(int,int))
{
int i,j;
//冒泡排序
for(i=0;i<size-1;i++)
{
for(j=0;j<size-1-i;j++)
{
if(p(a[j],a[j+1]))
{
int tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
}
}
}
}
int main()
{
int a[10]={0};
int num;//循环次数
//获取数组元素
int size=sizeof(a)/sizeof(a[0]);
for(num=0;num<size;num++)
{
scanf("%d",&a[num]);
}
sort(a,size,greater);
//遍历数组
for(num=0;num<size;num++)
{
printf("%d ",a[num]);
}
return 0;
}
运行结果:
这样我们只需要改这里
greater就是从到大到小排列,改成less就就是从小到大排列
回调函数还是比较重要的,到后面我们学习C++的时候还会涉及到。
指针函数
1、指针函数是指返回值是指针的函数。
在C语言中允许一个函数的返回值是一个指针(地址),这种返回指针值的函数称为指针型函数,简称指针函数。
2、定义指针型函数的一般形式为:
类型说明符 *函数名(形参表)
{
/*函数体*/
}
其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。
注意:不能返回局部变量的地址!!!
比如这样写是错的:
char *init()
{
char str[32]={0};
return str;
}
int main()
{
char *s=init();
strcpy(s, ”helloworld’);
return 0;
}
因为当char *init()这个函数返回str数组的地址给char *s时,由于char str[32]是个局部变量,所以它的空间会被释放掉,所以即使s记住了这个数组的地址也没用,因为s所指向的那空间已经不存在了。
这段代码我们可以这样写:
char *init()
{
char *str=(char*)malloc(sizeof(char)*32);
return str;
}
int main()
{
char *s=init();
strcpy(s, ”helloworld’);
free(s);
return 0;
}
因为这样写是在堆空间给数组申请空间的,不会被释放掉。
或者定义成全局变量运行完函数体后也不会被立即释放掉:
char str[32]={0};
char *init()
{
return str;
}
int main()
{
char *s=init();
strcpy(s, ”helloworld’);
return 0;
}
int(*p)() 和 int *p()区别?
int (*p)()定义的是一个变量。表示p是一个指向函数入口的指针变量,该函数的返回值是整型,(*p)的两边的括号不能少。 因此int(*p)() 是一个函数指针。
int *p()则不是变量说明而是函数声明。说明p是一个指针型函数,其返回值是一个指向整型量的指针,*p两边没有括号。作为函数说明,在括号内最好写入形式参数,这样便于与变量说明区别。 因此int *p() 是一个指针函数。
对于指针型函数定义,int *p()只是函数头部分,一般还应该有函数体部分。
以上就是这篇内容,如想了解更多,欢迎订阅本专栏!
如有问题可评论区或者私信留言,如果想要进交流群请私信!