指针-什么是函数指针?
指针
既然来看函数指针了,那么指针,相信大家已经有了一些了解,我就简单的说一下指针。指针其实就是地址,可以直接指向内存中的一个单元(1Byte),可以方便编程人员去直接操作内存。指针有指向基本类型的指针(char*,int*,double*等等),也有数组指针,结构体指针等等。那么函数有指针吗?答案是有的,那么接下来就来简单讲解一下什么是函数指针。
函数指针
什么是函数指针?
首先,我们写的代码在内存中吗?答案是一定在! 我们知道函数被调用,就会在栈区形成栈帧,其中的程序地址空间图如下:
那么我们之前了解到的那些局部变量,都是在栈区开辟的空间,然后存入相应的数据。全局变量是在全局数据区,字符串常量是存在字符常量区的。那么我们也知道对于C语言来说,函数写在那里,如果我们不在主函数中调用他,那么他是不会被执行的。那假如我们去调用他,去哪调用他呢?普通的功能函数和主函数其实都是一样的是一个代码块。那么如果在主函数内调用一个函数,就一定需要在一个地方开始让内存开始执行代码(指令)。
CPU是什么?简单的看其实就是一个算数而且执行任务很快的一个傻子,自主的并不会做什么事情,聪明的我们人类,我们会编程,我们只需要给他命令让他去干某件事情。
我们呢是通过内存向CPU发送指令的,CPU访存主要有两件事情:
①读取数据
②读取代码。
我们看见上面的那张图,还有一个区叫做代码区,还没有提及。首先我们可以确认他是在内存中的,那么他就一定会有地址。
举个例子
#include<stdio.h>
void fun() {
//nothing
}
int main() {
fun();
printf("0x%p", fun);
return 0;
}
输出为:
0x00B610F5
我们将一个函数名(fun)输出,结果为一个16进制的数,其实这个数据就是一个数据,就是这个函数在内存中的地址,而函数就是存在内存中的代码区的,这个地址0x00B610F5就是函数fun的首地址。
既然是一个地址,那么也就是数据,就可以拿一个变量来存。我们现在应该会有一种直接就是,指针变量就是用来专门存地址的。我们的int
数据可以用int*
的指针来存,那么现在函数的地址可以拿指针来存吗?
函数指针应运而生。那么函数指针长什么样子呢?
首先我们知道,数组和数组指针的样子:
int test[SIZE1][SIZE2];//定义了一个二维数组
int(*arr)[SIZE2] = test;//定义了一个指向test数组的指针变量arr
函数的格式:
返回类型 函数名 (形参声明)
{
函数体;
}
函数的声明(举例)
void fun(int, int);//函数声明
void fun(int a, int b){//函数定义
printf("%d", a + b);//输出a+b的结果
}
那么我们类比数组指针的定义,上面fun
函数指针应该为:
void (*p) (int, int) = &fun;//定义了一个函数指针变量p,并将其初始化为&fun
注意:初始化表达式中的&操作符时可选的,因为函数名被使用时总是由编译器把它转换为函数指针。
&操作符只是显式地说明了编译器隐式执行的任务。
由此,我们就知道了函数指针的定义了,那么我们又该如何使用函数指针呢?
怎么使用函数指针?
首先,我们讲一个概念,对指针解引用是什么意思?
我们看如下代码:
int a = 6;//定义整形变量a,并赋值为6
int* b = &a;//定义整形指针b,指向变量a
int c = *b;//定义整形变量c,并初始化为*b(其实*b就是a,这里做右值,这里其实是将c初始化为6)
*b = 7;//对*b赋值为7(其实是将a修改为7)
我们可以确认概念:
对指针解引用就代表该指针所指向的目标。
所以,如果我们对函数指针解引用,那么就应该代表这个函数本身。
我们可以这样写吗?(以fun函数为例)
fun(3,4);//调用fun函数,输出7
(*p)(3,4);//会输出7吗?
上面语句执行后会输出7吗? 我们经过编译之后发现是会输出7的。
那么我们现在就知道了函数指针的使用了。
现在直接告诉一个结论:
我们也可以直接这样写:
p(3,4);//同样可以输出7
实际上,我们所理解的是要使用一个指针的东西就要对其进行解引用,但是,我们刚刚说了一句话:
函数名被使用时总是由编译器把它转换为函数指针 ,说白了,编译器要的就是一个地址,一个指针,
那么如果我们只给一个函数指针就好了,这里p(3,4);
我们就是拿一个函数指针再配合()
操作符进行了
函数的调用。
有趣的例子
例子1:
//代码1
(*(void (*)())0)();
这个例子是什么意思呢?
乍一看,这是个什么玩意?那么下面我们好好分析分析
经过我们上面的讲解,我们可以将其分为两块:
(*(xxx)0)();
void (*)()
很明显,void(*)()
是一个类型,是个函数指针类型。我们再分解:
(*xxx)();
(void (*)())0
我们在void(*)()
外面加了一对()
还有一个0,我们知道,加括号就是对后面的数据进行强制转换,那么,
我们这里就是对一个常量0,进行强转,把他转换为了函数指针类型。
我们常见的类型转换是以下这种:
short a = 6;
int b = (int)a;
我们常见的是,对一个变量进行强转,其实我们可以知道,这里的a做右值(内容),那么a
中的内容是数据吗?其实就是0000 1010,强转只是改变了我们看待这一串二进制序列的方
式,在这里,我们就把他当int看,所以b也就被初始化为6。
所以我们这里只是把0这个数字序列当一个函数指针来看,然后我们继续分解:
xxx();
*((void (*)())0)
那么,对一个指针进行解引用,就代表这个指针所指向的地址的内容。所以 *((void (*)())0)
就是把0号地
址里面的内容拿出来,然后我们把里面的内容重新当做一个地址(一个函数指针),我们刚刚说了,编译器
要的是一个地址,一个指针,现在我们就给它提供了一个指针(一个指向0号地址里所放的内容的指针),所
以这里这样写地址()
这样我们就清楚的知道了。(*(void (*)())0)();
就是执行以0号地址里存的那个地址为
首地址的函数。
例子2:
//代码2
void (*signal(int, void(*)(int)))(int);
那么这个又是个啥呢?我们来看:
我们再来分解:
void (*xxx)(int);
signal(int, void(*)(int))
signal(int, void(*)(int))
这个可以容易的看出,他的结构为函数名 + 参数列表
其中,参数列表中是int
和
void(*)(int)
,前者为整数类型,后者为函数指针类型,我们现在已知的就是这么多了。
接下来,我们先来看一下我们较为熟悉的数组指针:
char(*p)[SIZE];
我们可以知道,这是定义了一个数组指针类型char(*)[SIZE]
的变量p。
我们也知道void (*p) (int, int);
是定义了一个函数指针类型void (*) (int, int)
的变量p。
我们会发现定义一个指针变量都是在*
后面加上名字,而不像我们常规的int a;
是类型 + 名字
。
那么假如我们要想给函数的返回值类型定义为函数指针类型,是不是就该往void (*xxx)(int)
中的xxx
这里来填写一个函数名 + 参数列表
那么不就是void (*signal(int, void(*)(int)))(int);
就是我们的题目。所以,至此,我们可以得出结论:
void (*signal(int, void(*)(int)))(int);
就是一个函数名为:signal
参数列表为int
类型和void(*)(int)
类
型,返回值为void
类型的函数声明。
希望本文章可以帮助你理解函数指针的简单意思。