指针-什么是函数指针?

指针-什么是函数指针?

指针

既然来看函数指针了,那么指针,相信大家已经有了一些了解,我就简单的说一下指针。指针其实就是地址,可以直接指向内存中的一个单元(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类型的函数声明。

希望本文章可以帮助你理解函数指针的简单意思。

作者博客: https://blog.csdn.net/qq_44025641.

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值