不多说什么,直接上题目,带你掌握指针中的精髓题目。助你对指针有进一步的理解。
画图是对指针理解最重要的一步,不要以为编程就是敲代码,敲代码只是体力活,思考的过程才是关键。
注意:测试平台为vs2013,32位条件下,指针的大小位 4个字节。
请问下面输出的结果是什么?
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//最终结果为 2 和 5;
开始分析:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
首先这里有一个数组,数组有五个元素;每个元素的类型为int类型,每个元素占 4 个字节;
int *ptr = (int *)(&a + 1);
这里 &a,表示取出整个数组的地址,该地址的类型为:int(*)[5],当 &a + 1,
在这里表示,跳过了 1 数组的大小个字节,即指向了下面图的位置:
int *ptr = (int *)(&a + 1);
然后 &a + 1转化为 int* 的指针类型,交给 ptr指针维护,
即 ptr指针,指向了 &a + 1的位置;
printf( "%d,%d", *(a + 1), *(ptr - 1));
在这里 a 表示数组首元素的地址, a+1表示跳过了一个元素类型的地址,即是(a+1) == &a[1];
当对 a+1解引用,*(a+1),表示 a[1]的元素,以 %d的形式打印,结果为:2;
而 指针 ptr的类型为 int*,类型决定指针访问的字节,
所以该 ptr - 1,表示指针ptr 向前访问了4个字节,即对其解引用 *(ptr - 1)
表示 a[5]元素,结果为:5;
图解如下:
输出结果是什么:
//假设结构体的大小位 20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}* p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
//最终结果:0x100014;0x100001;0x100004;
开始分析:
p = (struct Test*)0x100000;
让p指向该地址0x100000;
printf("%p\n", p + 0x1);
首先,指针p是指向结构体 struct Test 的指针,
其次我们要清楚的明白,对于指针的加减整数,就是要清楚的知道,指针的类型是什么,
因为指针的类型,决定了我指针加减整数时候,跳过的字节个数。
在 这里 p + 0x1,并不是 0x100000 + 0x1 那么简单;(0x表示十六进制);
这是指针 加 整数,所以 p+ 1 = 0x100014;p+1跳过了20个字节,由于是十六进制,
所以 p+1 == 0x100014; 即该结果为:0x100014;
printf("%p\n", (unsigned long)p + 0x1);
在这里, 指针 p 转换为 unsigned long 类型,是一个整数, 整数 + 1就是一个加法运算,
即 (unsigned long)p + 0x1 = 0x100000 + 0x1 = 0x100001;
但是,这里打印 以 %p形式打印,结果就是 0x100001.
printf("%p\n", (unsigned int*)p + 0x1);
在这里,指针 p 转化为 unsigned int*类型的指针,有什么用呢? 作用就是决定了
指针p加减整数访问字节的个数,由于是unsigned int* ,所以 p + 0x1,表示 p 指针跳过了 4个字节;
即 p + 0x1 = 0x100004; 所以最终结果为 0x100004
输出结果是什么:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
分析过程:
int a[4] = { 1, 2, 3, 4 };
首先:这里是一个数组名 a ,有四个元素,每个元素的类型为int ,每个元素的字节大小为 4 ,
整个数组的字节大小为 16;
int *ptr1 = (int *)(&a + 1);
&a + 1,就是数组 a 跳过了 1个数组大小的字节,指向了数组最后一个元素的下一个位置,
(int*)(&a + 1) ,类型转化为 int*, 目的很明确,就是为了访问字节的个数 为 4个,
交过 ptr1 指针维护;即 ptr1 指向了数组a 最后一个元素的下一个位置。
int *ptr2 = (int *)((int)a + 1);
在这里 把数组 a,转化为 int 类型 ,(int)a就表示整数了,(int)a + 1,表示该整数 + 1,
然后又转化为指针int*类型,(int*)((int)a + 1),
表示 该指针(int*)((int)a + 1)指向了比a的地址还多1个字节的地址。
看看内存布局如下图:(为了方便说明,我们把每个数组元素的每个字节都画出来了,vs2013是小端模式)
printf( "%x,%x", ptr1[-1], *ptr2);
来到这里 ptr1[-1],通过指针 ptr1去和中括号[ ] 一起使用,表示的是 ptr[-1] = *(ptr - 1);
即我们只需要找到 ptr - 1的位置,解引用就可以得到 ptr[-1]的值了,在这里 ptr -1 ,表示
跳过了一个 数组元素,向前移动了 4 个字节,这个移动字节的个数,是 ptr的指针类型 int* 决定的。
所以呢 ptr[-1] 在这里表示 a[3] == 4;
但是,这里以%x,打印的是十六进制的形式,所以最终结果为:0x4;
对于, *ptr2 ,表示解引用指针ptr2地址对应的内容,但是对指针解引用的前提,我们要清楚指针的
类型,这里指针 ptr2的类型为 int*, 所以解引用指针访问4个字节,在这里 *ptr2,我们从图看,
从 ptr2的位置读取四个字节:00 00 00 02 ,由于是小端存储模式,所以读取数据时候,看到的为:
02 00 00 00;以 % x的形式打印,即该值最终结果为:0x2 00 00 00;
结果是什么:
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
//最终结果:1
分析过程:
这一题就相对来说很简单了
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
首先我们要明白逗号表达式的取值为括号最后一个值,
所以这里表达的意思是初始化,内存布局如下:
int *p;
p = a[0];
这里指针 p 指向 a[0],a [0] 表示 *(a + 0);即二维数组的首元素地址;
把二维数组看成一维数组组成的话,那么 a[0] 就表示指向一维数组的指针,即p指向该位置,
为指向一维数组的指针;
printf( "%d", p[0]);
所以 p [0] = *(p + 0);表示该指针指向的二维数组的第一行的首元素的值,即 p[0] == 1;
最终结果为 1;
输出结果:
这道题有点难,不过如果懂原理,一步步分析也是可以的,
int main()
{
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]);
return 0;
}
//最终结果:0xff ff ff fc 和 -4;
分析过程:
首先我们得明白二维数组得本质就是一维数组;
其次我们要知道int(*p)[4]
的指针类型,其次要知道指针类型代表有什么意义;
指针加减整数代表什么,指针(地址相减代表什么);
如果上面的原理都懂,这道题就是照猫画虎的,很简单;
int a[5][5];
二维数组本质就是一维数组,在内存布局时连续的,
int(*p)[4];
p是一个数组指针,该指针类型为 int(*)p[4],代表指针p所能访问的字节为:指针p所指向的数组,
该数组有4个元素,每个元素类型为int,数组共16个字节;
p = a;
这句话的意思是 指针 p 指向 了数组 a,
要注意, 指针 p的类型 为 int(*)[4],只能一次访问 16个字节;
而二维数组 a,是一个由5个一维数组组成的数组;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
&p[4][2],通过指针 p 和下标访问数组,&p[4][2] == *(*(p + 4) +2);
&a[4][2],是二维数组的访问,a[4][2] == *(a + 4) + 2;
在内存布局如下:
&p[4][2] - &a[4][2]
表达的意思是地址 - 地址,表示他们之间相差的距离,所以该值为 - 4,因为 &p[4][2]的地址小于&a[4][2] ,
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
在看这里,是一个以 %p打印的,和%d打印的,%p打印的就是地址,地址是不可能由负数的,所以这里 -4
以 %p打印,会出现 一个 -4转化为 无符号类型的打印,且为十六进制,因为地址为十六进制表示
所以最终结果:
-4的无符号类型值:0xff ff ff fc;
和
-4;
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//最终结果:10 和 5
分析过程:
&aa
,表示整个数组得大小,对其加减操作,跳过得是整个数组的地址滴。aa == a[0];
那么*(aa + 1) = a[1];
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//最终结果:at;
分析过程:
我们要明白字符串是存放在常量区的,而通过char*
类型指针 指向字符串时候,是把字符串的首地址交给了char*指针维护滴 所以,在这道题中,数组 a 的每个元素类型为 char*
,表面看到的是三个
字符串 work ,at,alibaba, 存储在数组中,其实只是存储的是这个三个字串的首地址。
如下图:
所以,当我们以%s
形式打印 *pa
时候,打印的就是字符串"at"
.
这道题,是检验你能力的时候到了,明不明白指针就看这个复杂的题目了,充分的考察了对指针的理解程度。
int main()
{
char *c[] = {"ENTER","NEW","ROINT","FIRST"};
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);
return 0;
}
让我来好好分析一波:
char *c[] = {"ENTER","NEW","POINT","FIRST"};
首先看这个字符串数组 c ,由于上一题有经验了,所以我们很清楚内存布局是什么:
char**cp[] = {c+3,c+2,c+1,c};
这句代码又是什么意思呢? 首先我们知道数组 cp 的每个元素的类型为 char** 比 char* 多一个级别,
而 初始化该 cp数组又是 char* 类型的数组名, 数组名是首元素地址,所以这里的 c+3,c+2,c+1,c 都是地址。
我们画出内存布局
char***cpp = cp;
这句话很明显 把数组名cp赋值给指针 cpp,也就是数组cp的首地址,就是用cpp指向cp数组;
printf("%s\n", **++cpp);
这个代码最重要的一部分 **++cpp;
这里先++cpp 即 cpp指向了下一位 cp数组的位置,即cp[1]的位置,
然后对其解引用 *++cpp;得到的是cp数组cp[1]的内容:即 c+2;
继续对其解引用**++cpp,得到的就是 c+2地址指向的内容了,即数组 c[2]内容,c[2]的内容还是地址;
由于通过 %s打印,所以找到 c[2]的内容为“ROINT”字符串的首地址,所以打印结果为:“ROINT”
printf("%s\n", *--*++cpp+3);
这句话核心 *--*++cpp+3; 先算哪个运算符呢?*(--(* (++cpp )))+3
1. 在这里先算 ++cpp ,那么原来cpp指向cp[1]的位置,现在指向 cp[2]的位置了;
2. 在算*++cpp,得到的是cpp指向的内容 即 cp[2]的内容,该内容为 c+1;
3. 继续算 --*++cpp,表示 cp[2]空间的内容 - 1,即c+1 -1得到的是 c,
那么,也说明了cp[2]的内容不再是 c+1,而是 c, c就是数组c的首地址c[0]的位置;
4. 继续算 *--*++cpp,表示获取 c[0]的内容,该内容为字符串“ENTER”的首地址;
5. 最后算: *--*++cpp+3,就是该字符串“ENTER”从首地址“E”的位置,
偏移了3个字节位置来到了下一个位置"E",
那么以 %s打印的话:就是从 该位置 “E”,往后打印,最终结果:“ER”;
printf("%s\n", *cpp[-2]+3);
核心部分 *cpp[-2] + 3; 等价 **(cpp -2) + 3;
1. 所以 cpp[-2]表示 *(cpp -2),即在内存布局看到的是 cp[0]的内容 c+3;
注意啊,cpp的指向没有发生变化啊,还是指向 cp[2]的位置;
2. 继续算 *cpp[-2],表示获取 c+3的地址指向的内容,为c[3]的内容为字符串“FIRST”的首地址;
3. 继续算 *cpp[-2]+3,表示从字符串“FIRST”的首地址,“F”的位置,
偏移 3 个字节,得到的是"S"的首地址,以%s打印,最终结果为:ST
printf("%s\n", cpp[-1][-1]+1);
在这里 ,我们要知道等价关系cpp[-1][-1] +1 == *( *(cpp -1) -1 ) + 1;
接下来就很好分析了:
1. cpp[-1]表示 *(cpp -1),从内存布局可以看到就是 cp[1]的内容,为 c+2;
2. cpp[-1][-1]表示 *( *(cpp -1) -1 ),即为 c+ 2 -1 = c+1的地址内容; 就是 c+1指向的内容
为:"NEW"字符串的首地址N;
3. cpp[-1][-1]+1,自然而然就是字符串“NEW”的首地址从“N”的位置,偏移 1 个字节,来到了 E的地址;
4. 所以以 %s,打印最终结果为:“EW”.
总结:这道题终于结束了,最终分析过程大家也看到了,其实也不是很难,就是逻辑链长了一点点而已。
其实这 8道题,是真的需要对指针有一定的理解才会完完全全做对,我们对指针,画图是最好理解的方式。我们在这里要了解的主要内容总结一下:
看看你们回答不回答得了这些问题,假如可以得话,是真的对指针有了不错的认识哦。
(ps:问题没答案,自由发挥,抛砖引用罢了。)