前言
这里就给大家再浅显的说说数组指针,因为我感觉我上一篇文章的结尾那几个实验就已经把数组指针给说的很明白很明白了,然后再说说我觉得大家在学习这块的时候可能会有些困扰的地方
数组指针
-
指针数组:本质上数组,只是当中的元素是指针类型的
-
数组指针:本质上就是指针。和结构体指针、指针的指针类似,结构体指针指向结构体,指针的指针指向指针,那么数组指针就是指向数组的指针
注意:数组指针不是只能指向数组!!结构体指针也不是只能指向结构体!!…之所以这么命名,是因为定义成不同类型的指针时,它的宽度是不同的,做运算时++、–等地址的步长也是不同的,即每一种类型的指针的特征是不同的,根据我们的需要来选择指针,指针只是操作指向的结构中的数据的工具,我想让什么指针指向什么数据类型的变量都可以
定义
int arr[5] = {1,2,3,4,5}; //这是数组
int (*p)[5]; //这是数组指针。int* p[5]或者int *p[5]是指针数组!
char (*p1)[4]; //这也是数组指针
特征探测
还是像学习其他几个结构类型一样,从宽度以及步长什么的对它探测一下
- 探测宽度:(4 字节)
int (*p)[5]; //定义数组指针
printf("%d",sizeof(p)); //4
- 探测++或者–运算:(如果
int (*p)[5];
,p 去掉一个*
号后的类型为int数组类型,宽度为 5*4=20 字节)
int (*p)[5];
p = (int (*)[5])10;
p++; //10 + 4 * 5 = 30
- 用数组指针去获取数据
这里给个具体的例子,然后靠这个例子去解释一下
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*p)[2];
p = (int (*)[2])arr;
printf("%d",*(*p));
*p
表示把 p 中存的值作为地址,取地址中的值,但是*p
的类型为int [2]
类型,可以看成int*
类型,上面提到过,那么此时要再加一个*
号,才能表示取 arr 数组中的元素,即 int 类型
然后 p 和*p 的值都是一样的,因为都是同一个数组的首地址嘛,但它们的步长是不一样的,p+1 的步长是 8,但*p 的步长是 int,也就是 4
printf("%d %d",*(*(p + 1) + 1),*(*(p + 3) + 3)); //4 10
printf("%d %d",p[1][1],p[3][3]); //这样写也可以
这个怎么理解呢,就把它看成是一个类似于二维数组的样子,再结合上面那个步长的理解就行了,下面是详细的解释
*(*(p + 1) + 1):
先做*(p + 1),因为p是int (*)[2]类型,那么去掉一个*的类型为int [2],宽度为8字节,即2个int,所以p + 1的结果为 p + 1 * 8,即p从现在的地址往后加2个int;而*(p + 1)的类型为int [2]类型,所以*(p + 1)最后相当于是arr中从首地址往数两个元素的地址,即存储3的地址
再做*(*(p + 1) + 1),由于*(p + 1)的类型去掉一个*后的类型为int类型,宽度为4字节,即1个int,而*(*(p + 1) + 1)的类型为int类型,那么做*(*(p + 1) + 1)就表示从现在地址往后数1个int地址,取这个地址中的值,也就是arr中从元素3的地址再往后数一个地址,把4取出来,所以*(*(p + 1) + 1)结果为4
*(*(p + 3) + 3)):
先做*(p + 3),p去掉一个*后类型为int [2],宽度为2 *4 = 8字节,即2个int宽度,那么*(p + 3)就表示在arr数组中从首地址开始往后数 3 * 8 = 24字节,即往后数6个元素的地址,也就是元素7所在的地址
再做*(*(p + 3) + 3),*(p + 3)是int [2]类型,去掉一个*后的类型为int类型,宽度为4,所以*(*(p + 3) + 3)就表示从现在的地址再往后数3 * 4 = 12字节的地址,把这个地址中的值取出来,即从7元素位置开始,再数3个元素,刚好是10元素的地址,然后把10取出来,所以结果为10
一些理解
首先说一下这个&arry(也就是&数组)这个是什么意思,可能大家说,数组本来可以把它看成一个带*的类型,然后&就加一颗*,那就是**类型,其实不然,它其实应该是一个数组指针的类型,就把它看为一个指向数组的地址,下面这张图片其他部分就是我瞎写的,大家看红框的部分就行,以后想快速识别变量的类型,就偷懒报个错让编译器帮你识别就行
还要一个就是要意识到一点,我之前一直说可以把数组看成一个带*的,意思上说的过去,但实际上还是有不同的,这种直接带**的,这里是拿两颗*举例,实际上多少颗*都无所谓的,只要多加一颗*就能得到地址里面的数据,下面做个演示
然后相同的情况下,用数组指针拿数据就不一样了,一定是定义的是几颗星的数据,然后取数据的时候得用对应星去取,其实这就是多维数组指针,下面直接举例说明,一步到位,这其实就是海东老师的一个课后作业
#include "stdafx.h"
char data[] = { //定义一个一维数组
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x10,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x70,0x00,
0x10,0x20,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
int main(int argc,char* argv[]){
//一维数组指针
int (*px)[5]; //定义一个int (*)[5]类型的数组指针px
px = (int (*)[5])data; //让此数组指针指向data数组,去操作数组中的元素
printf("%x\n",*(*(px+2)+2));
//因为px类型为int (*)[5],去掉一个*,类型为int [5],宽度为4*5=20,所以px+2相当于地址加了2*20=40 字节,而*(px+2)的类型为int [5]类型,所以*(px+2)表示的含义就是px最初的地址+40字节后的地址; 再算 *(px+2)+2,因为*(px+2)的类型为int [5],去掉一个*号后的类型为int类型,宽度为4,所以*(px+2)+2相当 于地址又加了4*2=8字节,而*(*(px+2)+2)的类型为int类型,所以*(*(px+2)+2)表示的含义就是取在px的地 址偏移了40字节后再偏移8字节最终得到的地址中的元素,且取4字节宽度作为最终的结果值.因为px指向的是char 类型的data数组,一个元素1字节,所以相当于从第一个元素往后数48个的元素的地址开始往后数4字节,把这个值取出来,而因为这4个字节在内存中是倒着以1字节为单位从低地址往高地址存的,那么70 00 10 20最后要读作 0x20100070
char (*px1)[3]; //还可以定义char (*px1)[3]类型的数组指针
px1 = (char (*)[3])data; //同样可以去操作data数组中的数据
printf("%x\n",*(*(px1+4)+5)); //地址 + 1*3*4 + 1*5 = 地址+17字节,即第18个元素地址开始往后取 1字节宽度的值,即0x10
//二维数组指针
int (*py)[2][3]; //定义int (*)[2][3]类型的二维数组指针py
py = (int (*)[2][3])data; //py指向data数组首地址
printf("%x\n",*(*(*(py+2)+2)+2));
//因为一维数组指针访问元素需要两个*,那么二维就需要三个*才能访问到。还是按照从里往外分析,因为py是int (*)[2][3]类型,去掉一个*号后的类型为int [2][3],宽度为2*3*4,所以py+2相当于从py的初始地址+ 2*3*4 * 2,即*(py+2)最终得到的结果是初始地址+48后的地址;接着再分析*(py+2)+2,因为*(py+2)类型为int [2][3],去掉一个*号后的类型为int [3]类型,宽度为3*4,所以*(py+2)+2相当于此时的地址 + 3*4 * 2 = 24,即*(*(py+2)+2)最终得到的结果是初始地址+48再+24后的地址;最后分析*(*(py+2)+2)+2,因为*(*(py+2)+2)的类型为int [3]类型,去掉一个*号后的类型为int类型,所以*(*(py+2)+2)+2相当于此时的地址 + 4*2,即*(*(*(py+2)+2)+2))最终得到的即结果是取初始地址+48再+24再+8后的地址中的值,由于*(*(*(py+2)+2)+2))的类型为int型,所以取的值是最终得到的地址往后取4字节宽度的值,也就是 00 00 00 64,内存中的数据是以1字节为单位从低地址向高地址反着存的,所以我们最后应该读作0x64000000
//三维数组指针
char (*pz)[2][3][2];
pz = (char (*)[2][3][2])data;
printf("%x\n",*(*(*(*(pz+2)+2)+2)+2));
//从内往外分析地址+了多少:
//2*3*2*1 * 2 = 24 | 3*2*1 * 2 = 12 | 2*1 * 2 = 4 | 1 * 2 = 2
//所以最终地址+了24 + 12 + 4 + 2 = 42,因为数组是char类型,一个元素1字节,所以相当于第一个元素+42 个元素处,向后取1字节宽度的数据,即0x64
//注意char (*)[2][3][2]去掉一个*号类型为char [2][3][2]类型,宽度为2*3*2*1=12字节;再去掉一个*号 类型为char[3][2],宽度为3*2*1=6字节;再去掉一个*号类型为char[2]!宽度为2*1=2字节;再去掉一个*号类 型为char类型!宽度为1字节
}
一定要理解:不是一维数组指针就是专门指向一维数组的而且还是同样长度的。一维数组指针可以访问一维数组,还可以访问二维和三维数组,也可以访问其他类型的数据;同理二维数组也可以访问一维、二维、三维数组、其他类型数据等。数组指针[]里的数组也可以是任意的。用不同的数组指针去访问不同的数组,目的都是使用数组指针去操作数组中的元素,不同点在于不同的数组指针在做运算时的结果不同:比如使用 char (px)[5],宽度为 4,但是它在做++/–/+整数最终要偏移的步长是根据去掉一个号决定的,即 char [5],那么宽度就为 5_1,如果指针做+4 操作,就是 5_1_4 = 20;那么如果使用 int (_px)[3],那么此指针做+4 操作,就是 3_4_4 = 48。所以当我们使用这些不同的指针去获取不同的元素时(比如(*(px+4)+2)),步长是不同的,所以定义一个合适的指针去访问可以事半功倍,而且我们可以根据我们的需求去选择何时的指针
还有就是最核心的一句话,不管是一维数组、二维数组还是三维数组本质上他们的数据在内存中都是一维的形式存储的
作业
1.下面还是先给出代码示例
#include "stdafx.h"
int main(int argc,char* argv[]){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*p)[2];
p = (int (*)[2])arr;
printf("%d %d",*(p+1)[2],p[1][2]); //7 5
return 0;
}
因为 p[1][2]表示取首地址 + 1 * 8 + 2 * 4 字节地址中的元素,即从首个元素往后数 4 个元素,即 5,但是这个*(p+1)[2]不一样了,可以这么理解
*(p+1)中p是int ()[2]类型,所以p+1即p指向的地址+ 1 * (24);而*(p+1)后的类型为p的类型去掉一个*号,即为int [2]类型,注意不是int类型数组,而是int [2]类型的数组。
我们知道如果是一个int类型的数组arr,arr[2]表示数组首地址+2 × 4地址中的元素。但是现在是int [2]类型数组px,px[2]就表示首地址+2 × (2×4)地址中的元素!!即首地址+16中的元素。
综上则是先+8,再+16地址中的元素,共+24,即往后数6个,即为7
这也说明了把数组看成一个带*的,意思上说的过去,但实际上还是有不同的
2.直接上代码了
#include "stdafx.h"
int main(int argc,char* argv[]){
int arr[] = {1,2,3,4,5,6,7,8,9,10};
//定义一个char (*p)[4]可以完成逐个遍历int类型数组,因为每次++都要满足地址+4字节
char (*p2)[4] = (char (*)[4])arr;
for(i = 0;i < 10;i++)
printf("%d ",*(*(p2 + i))); //1 2 3 4 5 6 7 8 9 10
for(i = 0;i < 10;i++)
printf("%d ",*(p2 + 0)[i]); //1 2 3 4 5 6 7 8 9 10
//定义一个int (*p1)[1]也可以完成逐个遍历int类型数组
int (*p1)[1] = (int (*)[1])arr;
for(int i = 0;i < 10;i++)
printf("%d ",*(*(p1 + i))); //1 2 3 4 5 6 7 8 9 10
for(i = 0;i < 10;i++)
printf("%d ",*(*p1 + i)); //1 2 3 4 5 6 7 8 9 10
//定义一个short (*p2)[2]可以完成,
short (*p3)[2] = (short (*)[2])arr;
for(i = 0;i < 10;i++)
printf("%d ",*(*(p3 + i))); //1 2 3 4 5 6 7 8 9 10
for(i = 0;i < 10;i++)
printf("%d ",*(p3 + 0)[i]); //因为*p的类型是short [2]类型的数组,后面再跟个[],那么前面的*p就可以理解成short [2]带*,那么再跟个[1],就是1 乘 short [2]带*去掉一个*后的宽度,即1 * (2*2) = 4
//结构体的也行,其他只要满足条件的都可以
struct S{
char a;
short b;
}; //4
printf("%d\n",sizeof(S));
S (*p4)[1] = (S (*)[1])arr;
for(i = 0;i < 10;i++)
printf("%d ",*(*(p4 + i))); //1 2 3 4 5 6 7 8 9 10
return 0;
}