一、多级指针
1.多级指针反汇编
-
直接通过例子以及反汇编来分析**
*()
与[]
的互换**-
定义指针类型变量如下
说明:在开发中,不能直接光定义指针而不赋值,如
char* p1;
、char** p2;
,这样如果后面要使用*p1
,由于p1没有赋值,那么p1分配的内存中存的都是一些没有意义的数,那么*p1
表示把p1中的值作为地址,取此地址中的值,那么这个地址可能不是一个正常的地址或者不让访问的地址,于是就会发生地址错误的报错提醒,但是反汇编代码是已经生成了(所以下面的代码可能会显示内存访问错误,但是反汇编代码是生成的,可以方便我们学习,真正的应用在数组指针),只是程序由于找不到此地址或者不让访问而使程序运行错误#include "stdafx.h" int main(int argc,char* argv){ char a1[] = {0}; char* p1 = a1; //p1中存的a1数组的首地址 char** p2 = &p1; char*** p3 = &p2; return 0; }
-
了解
*p1
、*(p1+0)
、p1[0]
的反汇编:char a1[] = {0}; char* p1 = a1; printf("%d\n",*p1); printf("%d\n",*(p1+0)); printf("%d",p1[0]);
*p1
的反汇编就是将*p1
的值作为地址,取这个内存地址中存的值;*(p1+0)
和p1[0]
也是同理,反汇编都是一样的 -
了解
*(p1+2)
、p1[2]
的反汇编:char a1[] = {0}; char* p1 = a1; printf("%d %d\n",*(p1+2),p1[2]);
可以发现:
*(p1+2)
和p1[2]
的反汇编也是一样的,都是将p1中的值取出来作为地址,取此地址中+2字节后的地址中的值(因为char*
类型+整数结果是+1字节*整数) -
了解多级指针
*(*p2)
的反汇编:char a1[] = {0}; char* p1 = a1; char** p2 = &p1; printf("%d\n",*(*p2));
*p2
同理也是将p2中存的值作为地址,将此地址中存的值取出来,按照上面的代码,就是将a1的首地址取出来*(*p2)
则一层一层的分析:*p2
分析过了,它的类型为char*
,接着对char*
类型变量再加*
号,结果为char类型,即表示将*p2
的到的值作为地址,将此地址中的值取出来,按照上面的代码,得到的就是a1数组中的首地址元素 -
了解
*(*(p2+2)+3)
、p2[2][3]
的反汇编:int a1[] = {0}; int* p1 = a1; int** p2 = &p1; printf("%d %d\n",*(*(p2+0)),*(*(p2+0)+0)); //这两个的反汇编一模一样,且和*(*p2)意义相同 printf("%d %d\n",*(*(p2+2)+3),p2[2][3]);
*(*(p2+2)+3)
:先分析*(p2+2)
,表示先将p2中存的值作为地址,将此地址 + 2 * 4后得到的地址中的值取出来;接着将*(p2+2)
的结果再作为地址,将此地址 + 3 * 4后得到的地址中的值取出来p2[2][3]
:也是如此(其实就是day18学的带星号类型基本使用中的带*
类型的运算,没什么区别) -
了解
*(*(*(p3+1)+2)+3)
、p3[1][2][3]
的反汇编:char a1[] = {0}; char* p1 = a1; char** p2 = &p1; char*** p3 = &p2; printf("%d %d\n",*(*(*p3)),*(*(*(p3+0)+0)+0)); //两个的反汇编一模一样,且含义相同 printf("%d %d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]); //两个的反汇编一模一样,且含义相同
*(*(*(p3+1)+2)+3)
:先分析*(p3+1)
,表示将p3中存的值作为地址,将此地址 + 1 * 4后得到的地址中的值取出来(地址+ 1 * 多少,取决于p3的类型去掉一个*
后的宽度);接着将此值作为地址,再将此地址 + 2 * 4后得到的地址中的值取出来;接着将值作为地址,再将此地址 + 3 * 1后得到的地址中的值取出来p3[1][2][3]
同理
-
2.*()
与 []
的相互转换总结
-
总结:
基本类型* p; //不能定义空指针!不然会地址错误(这里这样定义只是为了说明) 基本类型** p2; 基本类型*** p3; 基本类型***** p5; *(p+i) = p[i] *(*(p2+i)+k) = p2[i][k] *(*(*(p3+i)+k)+m) = p3[i][k][m] *(*(*(*(*(p5+i)+k)+m)+w)+t) = p5[i][k][m][w][t]
二、数组指针
1.数组指针和指针数组
-
指针数组:本质上数组,只是当中的元素是指针类型的
-
数组指针:本质上就是指针。和结构体指针、指针的指针类似,结构体指针指向结构体,指针的指针指向指针,那么数组指针就是指向数组的指针
注意:数组指针不是只能指向数组!!结构体指针也不是只能指向结构体!!…之所以这么命名,是因为定义成不同类型的指针时,它的宽度是不同的,做运算时++、–等地址的步长也是不同的,即每一种类型的指针的特征是不同的,根据我们的需要来选择指针,指针只是操作指向的结构中的数据的工具,我想让什么指针指向什么数据类型的变量都可以!
2.数组指针的定义
-
定义:
int arr[5] = {1,2,3,4,5}; //这是数组 int (*p)[5]; //这是数组指针。int* p[5]或者int *p[5]是指针数组! char (*p1)[4]; //这也是数组指针
p变量是一个指针,声明了指向的数组类型,而且注意**
*p
必须要加括号**,不然它的含义就变成了定义了一个名为p的int*
类型的数组,即指针数组;这个定义的含义为:告诉编译器==定义了一个指针叫p,它指向有5个int类型元素的数组==,但是不带表这个数组指针以后使用就只能指向定义时的这种数组,它想指什么数组就指什么数组(不理解的话可以看一看day23.2-指针领悟) -
使数组指针指向某类型变量(赋值)
int arr[5] = {1,2,3,4,5}; int (*p)[5]; //定义数组指针 p = (int (*)[5])arr; //使数组指针指向arr数组
p指向arr数组的意思就是p中存的是arr数组的首地址
3.数组指针的特征探测
-
探测宽度:(4字节)
int (*p)[5]; //定义数组指针 printf("%d",sizeof(p)); //4
因为p是一个指针,只是一个指向数组的指针而已,我们知道指针的宽度都为4字节,所以数组指针的宽度也为4字节
-
探测类型并赋值:(带
*
类型,即指针类型)int (*p)[5]; //p = 10; 会报错,下面会显示错误原因 p = (int (*)[5])10; //强转
错误原因表示无法将
int
常量类型转换成int (*)[5]
类型,所以我们可以发现数组指针的类型为:int (*)[5]
,其实就是一个指针类型,即带*
类型 -
探测**++或者–运算**:(如果
int (*p)[5];
,p去掉一个*
号后的类型为int数组类型,宽度为5*4=20字节)int (*p)[5]; p = (int (*)[5])10; p++; //10 + 4 * 5 = 30
因为p的类型为
int (*)[5]
,我们知道指针做++运算,先将指针去掉一个*
后,得到此时类型的宽度,用原来的p的值 + 1 * 此时类型的宽度,即可得到++后的结果。上式即 10 + 1 * 20 = 30(p去掉一个*
号后的类型为int [5]
,一个int类型4字节,那么此数组的宽度为4 * 5 = 20) -
探测**+或者-一个整数运算**:(如果
char (*p)[6];
,p去掉一个*
号后的类型为char[6]数组类型,宽度为6*1=6字节)char (*p)[6]; p = (char (*)[6])10; p = p + 5; //10 + 6 * 5 = 40
4.用数组指针获取元素
-
先来了解一个东西:
*(px)
是啥类型? int [5]类型int arr[5] = {1,2,3,4,5}; int (*p)[5]; //不一定只能是[5],int (*p)[3]也可以,反正就是定义一个数组指针而已,目的都是为了操作指向 的东西中的数据,只是做对指针做运算时宽度不同,比如int (*p)[2],p++时即地址+2*4;如果使 用short (*p)[6],p+2时即地址+2*6*2 p = (int (*)[5])arr;
-
那么数组指针需要使用多级来获取元素
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 [5]
类型,可以看成int*
类型,上面提到过,那么此时要再加一个*
号,才能表示取arr数组中的元素,即int类型 -
使用指针的运算获取指定位置的元素
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int (*p)[2]; //定义数组指针p p = (int (*)[2])arr; //让p指向arr数组的首地址 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
- 先做
5.数组指针的*p
和p
-
我们知道如果指针是一个普通类型的指针(如char*那些),那么
*p
和p
的值是不同,*p
表示把p中存的值作为地址,取此地址中的值;p
中存的是指向的变量的首地址 -
但是如果是数组指针:这两个值是否一致呢?
#include "stdafx.h" int main(int argc,char* argv[]){ int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int (*px)[2]; //定义了一个指向int类型长度为2的数组这种类型的数组指针,但是不代表p只能指向这个数组 px = (int (*)[2])arr; //所以可以将定义的这种类型的数组指针指向我们想要操作当中数据的数组 printf("%d %d",px,*px); //这两个表示的都是arr的首地址,所以值是一样的,但是宽度不一样 return 0; }
我们发现数组指针的
px
和*px
的值是一样的:-
因为
px
的类型是int (*)[2]
,它是一个指针,它指向arr数组,即它存的是arr数组的首地址 -
*px
的类型就是px
的类型去掉一个*
号,即为int [2]
类型,即表示arr这个数组,那么单独使用数组名的含义就是指数组的首地址,所以值是一样的 -
我们可以这么理解:
px
和*px
都是将编译器给px指针变量分配的内存中的值取出来(通过反汇编看出来)
-
-
虽然值是一样的,但是由于数据类型不同,那么宽度就不同,在对它们进行指针的运算时(++、–、加整数等),得到的结果也是不同的
三、作业
-
*()
与[]
是可以互换的,也就是说:*(*(p+1)+2)
相当与p[1][2]
,那*(p+1)[2]
是否一定等于p[1][2]
呢? 通过反汇编进行论证。#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)
先将首地址 + 1 * 8,接着再p[2]
(这里为什么是p,可以好好理解一下5.数组指针的*p和p)表示再将此时的地址 + 2 * 8,取这个地址中的元素,即一共+了24字节,表示从第一个元素往后的第六个元素,即7还可以这么理解:
*(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
-
使用数组指针遍历一个一维数组
#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; }