1.二维指针传参本质
前面讲了一维数组的传参本质,其实是可以类比的。
二维数组的传参本质是指在编程中,二维数组作为参数传递给函数时,编译器或解释器是如何处理这种传递的。在很多编程语言中,二维数组实际上是一种数组的数组,即数组的每个元素都是一个数组。
例如,在C语言中,一个二维数组可以这样定义:
int arr[3][4];
这个二维数组实际上包含了3个元素,每个元素是一个数组,这些数组的长度都是4。当我们创建这样一个二维数组时,实际上是在内存中分配了一块连续的内存区域,这片区域足够放下3个长度为4的整型数组。
当我们将这个二维数组作为参数传递给一个函数时,我们在函数调用时传递的是整个二维数组的首地址(即指针)。这个首地址指向了二维数组第一个子数组的首地址。在函数内部,我们可以使用这个指针来访问和操作二维数组。
如果不能理解的话我们可以对比一下:
如果之前我们有个二维数组雪瑶传参给一个函数的时候是这样写的。
这里的实参是二维数组,形参也写成二维数组的形式,那还有什么写法吗?
我们知道数组名就是首元素的地址,而二维数组的首元素就是一维数组,他的函数名就是第一组一维数组的地址。
根据这个说法:第一组一维数组的类型即使int[5]。那么第一组的地址类型就是数组指针类型
int(*)[5]。那就意味着二维数组传参的本质上也是传递了地址,传递的是第一行这个一维数组的地址,那我们把形参写成指针形式就是这样:
总结:二维数组传参,形参部分可以写成数组,也可以写成指针。
刚才回头看了一遍课,发现了一个挺本质的东西:
arr+i产生的是下标为i元素的地址: arr是起始地址,+i就是跳过i个元素,产生的还是地址。
arr只是提供的的起始地址,而i就是偏移量,[i]就相当于解引用,就是这样才能找到arr里面的元素。
所以说这两个东西本质上是一样的。
2.函数指针变量
2.1函数指针变量的创建
首先现问一下,什么是函数指针。当然从字面意思上来理解,其实还是指针。同样我们还是可以类比一下:
整型指针是用来存放整型地址的指针;字符指针就是用来存放字符地址的指针;数组指针就是用来存放数组地址的指针。所以函数指针就是用来存放函数地址的指针。
挺重要的一个点:&arr是一个地址,arr也是一个地址。那单独的函数名是一个地址吗?我们也可以试一下。
关于函数是否有地址我们可以验证一下:
这时我们发现函数确实有地址,函数名就是函数的地址,当然我们也可以通过&函数名的形式来获取地址。
我们知道了函数是有地址的,如果想给他保存起来就要创建函数指针变量,那函数指针变量怎么写呢?
其实跟数组指针变量差不多,拿上面代码的函数作为例子:
首先先给一个函数指针变量p = &test 。在就是(*p)说明p是一个指针,这里需要括起来。
(*p) = test,但是很不巧这个函数没有参数所以再进一步就是这样(*p) () = &test。又因为指向的函数的返回类型是int所以最后就是这样的:
int (*p)() = &test,如果这个函数有参数的话,括号里就应该写上参数的类型。那如果把括号去掉可以吗?不行如果把括号去掉,p就成了函数名了,后面括号里是它的参数,前面的int *是它的类型。
int(*p)[5] = &arr,确实是和指针的写法挺像的。
接下来我们练习一下:创建一个函数指针变量,存放test函数的地址。
就是这样:int *(*p)(char*) = &test;最后我们在解析一下这个指针类型:
int*是时p指向函数(test)的返回类型;*p是函数指针变量名;char*时p指向函数的参数类型和个数的交代。 OK了兄弟们。
2.2函数指针变量的使用
我们用函数指针变量存放函数是为了调用使用它。那怎么进行呢?看下面
int(* p )(int , int) = &add;首先p中存放的是add函数的地址,(*p)就是函数了,(*p)(3,7)就是给函数传参,然后把他赋值给r最终就是int r = (*p)(3,7);现在就可以利用这个函数了,就像这样。
接下来还有一个重要的点,我会在代码说明。
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int (*pf1)(int ,int) = &add;
int (*pf2)(int ,int) = add;
int r1 = (*pf1)(3,7);
int r2 = (*pf2)(3,7);
int r3 = add(3,7);//这是正常调用函数的写法,add这个函数名本身是个地址,而pf2中也存的地址
//所以上面的也可以写成这样。int r2 = pf2(3,7);int r1 = pf1(3,7);这样都没有问题。
printf("%d\n", r1);
printf("%d\n", r2);
printf("%d\n", r3);
return 0;
}
关于函数指针类型还是要知道的:
int (*pf)(int ,int) 当把pf去掉了就是函数指针类型:int (*)(int ,int)
2.3两端有趣的代码
不得不说我看完这串代码之后感觉跟没学过指针一样,但是呢还是忒分析啊。
这个东西还是要拆开来看的。首先我们要先从里面开始分析,有没有发现void(*)()很熟悉那我们现在变一下:void(*p)()再看一下,这不就是函数指针的类型嘛,这一段就是利用指针进行函数传参的过程,只不过这个指针表达式他是一个指向不接受任何参数且不返回任何值的函数。
接着呢就是(void(*p)())0这个这个就是正了八经的强制类型转化了,0是int类型的这里强制类型转换就是把0转换成void(*p)()函数指针类型。
综合而起来(*(void(*p)())0)()在这里,0是一个空指针常量,被转换为指向返回类型为void的函数的指针,然后进行解引用,就是找到0地址处的函数,在调用传参。
总的来说这其实就是一次函数的调用,调用0地址处放的这个函数。
那么再来看这有段代码 ,还是要拆开来看。
void(*)(int)是一个函数指针类型,而int是一个整型类型,signal是一个函数,后面的两个类型int和void(*)(int)是这个函数的两个参数。
void(*signal(void(*)(int)))(int);是一个函数的声明,这个函数接收了两个参数,signal函数的返回类型是一个函数指针,其形式是void(*)(int),大体就是这样啦。
2.3.1typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化,有助于更清楚地了解函数。
给个例子:如果你觉得unsigned int写起来很麻烦就可以使用typedef关键字,写成uint
就像这样:
typedef unsigned int uint;
指针类型也是可以这样的就像这样。
typedef int* ptr_t;
但是呢函数指针和数组指针是有区别的:
比如说我们有数组指针类型int(*)[5]需要重命名成parr_t那我们可以这样写
typedef int(*parr_t)[5];
而函数指针类型就是这样写的,比如将void(*)(int)类型重命名为pf_t就可以这样写。
typedef void(*pf_t)(int);
这样看来还是有统一点的,就是你重命名的名称都是要靠近*的。
既然可以简化,那么我们是不是可以把上面的两段有趣的代码简写一下。
代码2
首先可以把void(*)(int)简化一下,还是和上面一样把他重命名成acc就是这样:
typedef void(*acc)(int);然后这个函数里面的函数指针类型都可以写成acc,也就成了这样:
acc signal(int,acc); 乂,这样是不是就好理解了。
总结:
这一篇嘎嘎有收获,但是还请大家指正,提高水平。才会多分享更有水准的的文章。
;