其实C语言中什么都离不开指针,因为每个变量都有地址,函数也有地址,而程序的执行也就像是对不同的内存作不同的操作,所以无论说到什么,它多多少少都能和指针扯到点关系(预处理和关键字不太好扯上关系哈,这本身就是语法标准)
1.数组指针
数组指针,顾名思义就是数组的指针,它是一个指针变量,里边存着数组的地址。而数组指针多用于二维数组的情况下。
首先看如何声明一个数组指针
int arr [10] = { 0 };
int (*parr) [10] = &a;
我之前在数组的博客的最后写到,数组名为首元素的地址,而&数组名则为整个数组的地址,所以在这边我们可以知道&a获得的是整个数组的地址,在有一定的指针基础上我们就可以大胆猜测:假如pa+1,则其跳过的为整个数组
验证一下
可见,其跳过了四十个字节,所以跳过了整个数组。
对二维数组的理解
int arr[3][5] = { 0 };
//arr则为数据类型int [5]的地址,所以将arr+1,就会跳过一行
验证一下:
对以上说明的一点补充:
在C语言中,当你声明了一个数组,就相应的会有对应该数组的"数据类型",比如上上边的int arr [10] 则它对应的"数据类型"就是int [10]。 但是相应的变量名的位置改变了,如果要声明该"数据类型"对应的指针变量,只需在变量名的前面加上*即可。(数据类型打上引号是因为引用这个概念只是为了助于理解,并未经过验证)而对于下面要讲到的函数指针也是如此
2.函数指针
在有了对数组指针的了解下,函数指针的理解就会更加顺畅
先看函数指针的声明:
int test (int x, int y)
{
return 0;
}
int main()
{
int (*ptest)(int, int) = &test;
return 0;
}
如同数组指针一般的,函数指针的声明也是需要在原来变量声明的基础上将标识符的位置改为指针变量名并在前面加上*。而另外要说明的是,函数指针的声明还需要在后面加上形参列表,而形参列表只需注明数据类型,必须将标识符(变量名)省略
来和你打个赌:上面的代码,main函数里面的test前面的&不要也行,不信咱试试:
为什么呢?
因为函数名本来就是函数指针,也就是代表着函数的地址,而取地址+函数名的意义在于,取得该名为test函数的地址,而直接将test的值赋给函数指针的意义在于,直接将test的地址赋给它。也就是在不同的场景下,test函数名有不同的意义,第一个的test的意思就是指代这个函数实体,第二个test的意思就是这个函数的地址。
讲这么多,这函数指针有什么用?
有用,可以作为函数名的重定义,也可以用作回调函数。
先看一下下面的例子:
这就是一个函数名重定义的例子,先解释一下,在我们平常调用函数的时候,后面的括号称为函数调用操作符,而在与函数调用操作符结合的情况下,我们就可以将此时的函数名(test)理解为函数指针。
对于理解重定义,我想举这样一个例子:
你出生了,你出生的这天是国庆节,爸爸妈妈就给你取了个名字叫国庆,你和同学们玩久了,同学们开始给你取外号,有些外号你很乐于接受,所以当他们叫这个外号的时候,你也认为是在叫你。
而不同的是,这个函数对于所有的外号除关键字外其它都很乐于接受。
然后就是回调函数,还是先看一个例子:
回调函数可以认为在一个函数返回时又去调用另外一个函数,而如上代码正是如此,认真将上面代码看懂就能理解回调函数的过程,接下来要说的是回调函数的意义:
回调函数可以将程序更好的进行模块化,在通常情况下我们都是用ifelse来对情况进行分类的,而在实际问题中,我们其实更加需要将输入进来的原始数据进行处理后再分类调用其他函数进行处理,这就是回调函数的意义所在。
3.指针数组
指针数组其是就是一系列相同类型的指针变量的数组罢了,没啥好说的。
但是实际应用价值还是很大的,我们可以将各种相同类型的指针进行组合为数组,将一维数组取地址后放到另外一个一维指针数组,这样一来我们就模拟实现了一个二维数组:
int main()
{
int row1 [5] = {1, 2, 3, 4, 5};
int row2 [5] = {2, 4, 6, 8, 10};
int row3 [5] = {3, 6, 9, 12, 15};
int* two_di[3] = {row1,row2,row3};
printf("%d",two_di[1][1]);
return 0;
}
这完全就可以像使用一个二维数组一样去使用这个一维指针数组
同时也可以使用函数指针数组来用对应的下标去访问对应的函数。