深入理解 指针数组 数组指针 函数指针 函数指针数组 指向函数指针数组的指针

看到小猿的题目是不是开始怀疑自己学了个假c语言! 一个指针竟然有这么多用法,绕来绕去的,是不是有些头晕目眩呢,别怕,小猿这就来拯救你。

首先,提三个问题
1、什么是数组?
2、什么是指针?
3、指针和数组之间有哪些关系?

好,先解决第一个问题。
① 什么是数组: 简单的理解数组就是存储相同类型数据的一个集合
例如:int array[10]={1,2,3,4},这就是一个简单的数组
:图片为数组的内存布局
这里写图片描述

② 什么是指针:指针就是地址,地址就是指针
以下是百度百科给出的解释:
(指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。 [1] 在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。)
:图片为指针的内存布局
这里写图片描述
如上图所示,我们把 p 称为指针变量,p 里存储的内存地址处的内存称为 p 所指向的内存。指针变量 p 里存储的任何数据都将被当作地址来处理。
我们可以简单的理解为:一个基本的数据类型(包括结构体等自定义类型)加上了“* ”号就成了一个指针类型的模子。这个模子大小是一定的,与“”前面的数据类型无关;“ ”号前面的数据类型只是说明了指针所指向的内存里存储的数据类型。在32位系统下,都是4字节。

③ 指针和数组之间的关系
很多初学者认为数组和指针是一样的,其实他们没有任何关系,只是他们经常穿着相似的衣服出来骗人而已。
指针就是指针,指针变量在32位系统下永远是4字节,其值是某一个内存的地址。
数组就是数组,其大小与元素类型和个数有关;定义数组时必须指明数组元素的类型和数组的大小;数组可以存任何类型的数据,但不能存函数。

3.1
下面我们就详细讨论讨论它们之间似是而非的一些特点。例如,函数内部有如下定义:
A),char*p = “abcdef”;
B),chara[]= “123456”;
3.1.1,以指针的形式访问和以下标的形式访问指针
例子 A)定义了一个指针变量 p,p 本身在栈上占 4 个 byte,p 里存储的是一块内存的首地址。这块内存在静态区,其空间大小为 7 个 byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。比如现在需要读取字符‘e’,我们有两种方式:
1),以指针的形式:*(p+4)。先取出 p 里存储的地址值,假设为 0x0000FF00,然后加上 4 个字符的偏移量,得到新的地址0x0000FF04。然后取出 0x0000FF04 地址上的值。
2),以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出 p 里存储的地址值,然后加上中括号中 4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了

3.1.2,以指针的形式访问和以下标的形式访问数组
例子 B)定义了一个数组 a,a 拥有 7 个 char 类型的元素,其空间大小为 7。数组 a 本身在栈上面。对 a 的元素的访问必须先根据数组的名字 a 找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名+匿名”访问。比如现在需要读取字符‘5’,
我们有两种方式:
1),以指针的形式:*(a+4)。a 这时候代表的是数组首元素的首地址,假设为 0x0000FF00,
然后加上 4 个字符的偏移量,得到新的地址 0x0000FF04。然后取出 0x0000FF04 地址上的值。
2),以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a 作为数组首元素的首地址,然后加上中括号中 4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。
由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个“以 XXX 的形式的访问”这种表达方式。
另外一个需要强调的是:上面所说的偏移量 4 代表的是 4 个元素,而不是 4 个 byte。只不过这里刚好是 char 类型数据 1 个字符的大小就为 1 个 byte。记住这个偏移量的单位是元素的个数偏移量的单位是元
素的个数而不是 byte 数,在计算新地址时千万别弄错了。

下面介绍 指针数组 数组指针 函数指针 函数指针数组 指向函数指针数组的指针。
⑴指针数组和数组指针
先给个介绍,
指针数组首先他是一个数组,数组的的元素都是指针,数组占多少字节由数组本身决定,可以理解为“用来存储指针的数组”。
数组指针*首先他是一个指针,指向的是一个数组*。32位系统下永远占4字节,至于它指向的数组占多少字节我们并不知道,可以理解为“指向数组的指针”。

举个例子来判断什么是指针数组,什么是数组指针。
(A)int *p1[10]; ——指针数组
(B)int (*p2)[10]; ——数组指针
这里需要明白一个符号之间优先级的问题。“[]”的优先级比“”高,所以p1先与“[]”结合,构成一个数组的定义,数组名为p1,int 修饰的是数组的内容,即数组的每个元素。这个数组中的5个元素指向int 类型的指针,所以(A)为指针数组。
对于(B)来说,“()”比“[]”优先级高,所以*p2先与“()”结合,构成一个指针,指针变量名为p2,int修饰的是数组的内容,即数组的每一个元素。这里的数组没有名字,是个匿名数组。根据前面的介绍,这是我们应该明白p2是一个指针,它指向一个包含10个int类型数据的数组,即数组指针。
下面给张图加深理解
这里写图片描述

⑵函数指针
函数指针定义:顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。
给出几个例子看看:
(A)char * (* fun1)(char* p1,char* p2);
(B)char * * fun2 (char* p1,char* p2);
(C)char * fun3 (char* p1,char* p2);
现在分析这三个表达式:
(A)这里的fun1并不是什么函数,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数返回值也是一个指针。
(B)也很简单,与(C)表达式相比,唯一不同就是函数返回值类型为char * *,是个二级指针。
(C)这个是最简单的,fun3是函数名,p1,p2是参数,函数的返回值类型为char*类型。

⑶函数指针数组
定义:上面我们了解过 char (* pc)(char * p); 是一个函数指针,那好,现在我们变形一下,改为
char* (* pc[5])(char *p);
很明显,这里将(* pc)改成了一个数组指针,然后就变成了函数指针数组。怎么理解这段代码呢?
首先我们知道它是一个数组,数组名为pc,数组内存放了5个指向函数的指针。这些指针指向返回值类型为指向字符的指针,参数为一个指向字符的指针的函数。 听起来是不是很拗口,没事,只要记住这是一个指针数组,是数组 就OK了。

⑷指向函数指针数组的指针
看到这个标题有没有炸毛呢?啊哈哈哈哈哈。。。
不要慌,现在小猿来替你一一解决你的疑虑。从这个题目来看不就是一个指针嘛。只不过这个指针指向的是一个数组,数组里存放的元素都是指向函数的指针而已。
举个简单的例子:
char * (* (*pc)[5])(char *p);
注意了,这里的pc和上面的那个pc已经不是一回事了,(3)里面那个pc并不是一个指针,而是一个数组名,而这里的pc的的确确是一个指针。这个指针指向一个包含5个元素的数组,数组里面存放的指向函数的指针,这些指针指向返回值类型为char 类型的指针,参数是char类型的指针。总之,明白一点,这是个指针就ok了。

下面举个例子看看:由于编辑器的原因,无法将代码片直接写在上面,就截了张图,大家将就着看看,理解就行。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_39412582/article/details/79900170