一、概念
. 数组:数组是用于储存多个相同类型数据的集合。
. 指针:指针相当于一个变量,存放的是其它变量在内存中的地址。
二、指针数组和数组指针
2.1数组指针
int (*p)[n];
也叫行指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。
2.2指针数组
int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i<3;i++)
p[i]=a[i]
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
优先级:()>[]>*
三、对指针的操作
解引用:用*来获取指针指向的数据,这个不用多说。
指针的运算:加减运算,需要注意的是,指针的加减运算的粒度是基于指针类型的长度,在下例中:
int *p = (int*)0x1000;
char *str = (char*)0x1000;
p++;
str++;
print("p=%d,str=%d\r\n",p,str);
输出结果:
p=0x1004,str=0x1001
可以看到,p指向int型数据,p++就相当于p+sizeof(int),而str++就相当于str+sizeof(char).
四、例题
4.1
int a[5] = { 1, 2, 3, 4, 5 };
int *p = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(p - 1));
题目分析:&a+1是先取出数组a的地址,然后加1,那么指针变量p则指向数组最后一个元素后边的那块空间,然后对p-1进行解引用操作,就是对p向左偏移4个字节的空间解引用,得到的结果自然是5。对a+1进行解引用操作,这里a是数组名,代表数组首元素的地址,a+1则是数组第二个元素的地址,解引用自然就是2了。
输出结果:2,5
4.2
struct test
{
int num;
char *pcname;
short date;
char cha[2];
short ba[4];
}*p;
p + (0x1) = 0x___
(unsigned long)p+0x1 = 0x___
(unsigned int*)p + 0x1 = 0x___
题目分析:结构体指针大小为20个字节,p是一个指向结构体的指针,p加1跳过二十个字节,所以 p + (0x1) =0x10014 。第二个是把结构体指针强制类型转换为(unsigned long)的数,对它加1,就直接给这个数加1,结果为0x100001。第三个是把结构体指针强制类型转换为(unsigned int*)类型,对它加1,偏移4个字节,结果为0x100004
输出结果:0x10014,0x100001,0x100004
4.3
int a[4] = { 1, 2, 3, 4 };
int *p1 = (int *)(&a + 1);
int *p2 = (int *)((int)a + 1);
printf("%x,%x", p1[-1], *p2);
题目分析:&a取出来的是数组a的地址,然后加1跳过整个数组,在强制类型装换为整型指针,则p1则指向4后边的那块空间。p1[-1]相当于*(p1-1),p1是一个整型指针,-1向后偏移4个字节,在解引用以十六进制打印得到4。在vs编译器里,数据是以小端存储的。a代表数组首元素的地址,将1的地址强制转换为一个整型数字,在加1,就是把1的地址当成一个数加1,在强制类型装换从其实地址跳过1个字节。然后p2进行解引用可以访问后边的四个字节,又因为它是以小端存储的,变过来为02 00 00 00,以十六进制打印结果为2000000。
输出结果:4,2000000
4.4
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
编译器执行会报错。
题目分析:p是一个数组指针,指向一个长度为4的数组,首地址是a的首地址。对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节。
p[i][j]也就是*(p[i]+j),即*((p+i)+j)。a为5行5列,a[4][2]对应从0开始的第22个元素,p[4][2]对应第(44+2)
输出结果:FFFFFFFC,-4
4.5
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf("%d\n", p[0]);
题目分析:数组赋值里边嵌套的是小括号,而不是花括号,小括号的里边是逗号表达式,所以数组的赋值相当于a[3][2]={1,3,5},这样结果就是1了。
输出结果:1
4.6
int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *p1 = (int *)(&a + 1);
int *p2 = (int *)(*(a + 1));
printf("%d,%d\n", *(p1 - 1), *(p2 - 1));
&a是取出整个数组的地址,对它加1跳过整个数组,然后将它强制类型装换为整型指针类型,对(p1 - 1)解引用得到的是p1向后偏移4个字节的那块空间的内容,既10。a代表数组第一行的地址,然后加1代表第二行的地址,对它解引用得到第二行的数组名a[1],既第二行首元素地址,然后将它强制类型装换为整型指针类型,对(p2 - 1)解引用得到第二行首元素地址向后偏移4个字节那块空间的内容,即5。
输出结果:10,5
4.7
char *a[] = { "work", "at", "alibaba" };
char **pa = a;
pa++;
printf("%s\n", *pa);
a是一个指针数组,里边存放了work,at,alibaba首元素的地址,pa又存放了数组a首元素(w的地址)的地址,pa++跳过一个char *的大小,现在pa指向数组第二个元素,在解引用得到at的地址,以%s打印结果就是at.
输出结果:at
4.8
char *c[] = { "ENTER", "NEW", "POINT", "EIRST" };
char**cp[] = { c + 3, c + 2, c + 1, c };
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
输出结果:POINT,ER,ST,EW
char **cp[]={c+3,c+2,c+1,c};
这是二重指针。因为前边定义的c是数组类型,也就是一种常量指针(地址),所以c+3,c+2,c+1,c都是指向数组c每个元素的地址。现在要将数组元素的地址放在数组中,那么该用什么数据类型呢?存放一个变量(也可以是字符串)地址时,需要的是指针,那么存放一个地址的地址,就需要一个二重指针(因为只有指针可以存放地址,这里是存放一个地址的地址,所以要用二重指针)。char *** cpp=cp;
cp本来就是一个常量指针,再加上他的类型是char**,所以它就是一个三级指针。这里cpp的类型是char***,也是三级指针,所以将cp直接赋值给cpp也就没有问题啦(类型相同的嘛(都是三级指针))。printf("%s#",** ++cpp);
++的优先级高,所以先执行++cpp,也就是得到数组cp的第二个元素地址。
两个 ** 我们分开看,先看* ++cpp,这是取得数组cp第二个元素的值c+2。
在看** ++cpp,相当于*(c + 2),所以这里得到的是数组c第三个元素的地址,也就是"POINT"字符串的地址,所以这里输出POINT#printf("%s#",* – *++cpp);
由优先级和执行顺序的关系得知,这里会先执行++cpp,因为前一个输出语句已经导致cpp加1了,所以现在cpp再加1,那么会得到数组cp的第三个元素地址。再执行 * ++cpp,这是取得数组cp第三个元素的值c+1。
然后再执行-- * ++cpp,相当于–(c+1),也就是c,然后再执行 * c,所以取得是数组c的第一个元素的地址,所以输出结果为ENTER#printf("%s#",* cpp[-2]+3);
数组下标优先,所以会先执行cpp[-2],也就是*(cpp-2);因为前两输出语句导致cpp加了2,所以现在-2后,又使cpp-2的地址为数组cp的首地址,所以得到数组cp第一个元素c+3。再执行 * (cpp[-2]),也就是 * (c+3),这就得到了数组c的第四个元素的地址,也就是字符串"FIRST"的地址,然后这个地址再加3,所以得到字符串ST的地址,因此输出结果为STprintf("%s#",cpp[-1][-1]+1);
先执行cpp[-1],相当于 * (cpp-1)因为前边两个输出语句导致cpp被加了2,它目前指向数组cp的第三个元素,所以cpp-1相当于数组cp第二个元素c+2,然后再执行cpp[-1][-1],相当于(c+2)[-1],也就是*(c+2-1) = *(c+1),所以得到数组c的第二个元素的地址,也就是字符串"NEW"的地址,然后在这个地址上加1,就得到字符串EW的地址,所以输出结果为EW