这一章节将会与大家一起针对指针练习的习题与大家一起进行练习学习。
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
这道题的结果是什么呢?
1:*(a + 1)表示的是a[5]中元素的第二个元素,即a[1].
2:ptr是(&a + 1)即超过了这个数组抽象理解为a[5],(ptr - 1)括号内减一则代表返回a[4].
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
p+0x1的值为 0x100000+sizof(Test)0x1。至于此结构体的大小为 20byte,所以 p+0x1的值为:0x100014。
(unsigned long)p + 0x1的值呢?这里涉及到强制转换,将指针变量p保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。
(unsigned int)p + 0x1的值呢?这里的 p被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizeof(unsigned int)*0x1,等于 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;
}
1、&a+1
首先明确,a是一个具有4个整型变量的数组的名字,具体地说是这种数组的首元素的首地址,而&a是数组的首地址,请注意措辞。而关于指针加1,则需要指针运算的知识。没学过指针运算或者已经忘记了这个知识点的朋友们,下面就是关于指针运算你需要知道的事实:
就像上面的例子那样,式子&a+1表示的是指针加法运算,而不是普通的数值加法运算,之所以会这样是因为&a是一个指针而非普通数值(虽然它本质上也是一个整数)。那么你会问:加入此时&a=0xFFFF5700,那么&a+1是多少呢?答案是:取决于&a的类型 。
(a) 如果&a是一个指向char型的指针,那么&a+1 = 0xFFFF5701
(b) 如果&a是一个指向short型的指针,那么&a+1 = 0xFFFF5702
(c) 如果&a是一个指向int型的指针,那么&a+1 = 0xFFFF5704 (32位机器)
(d) 如果&a是一个指向某种结构体struct foo的指针,那么&a+1 = 0xFFFF5700+sizeof(struct foo)
……
还没看出端倪吗?没错,指针加1不是指针内容简单地加1,而是让指针指向下一个数据 ,加2就是让指针指向下两个数据,这个数据的类型就是指针指向的类型,所以指针的加法究竟会让这个指针指向哪里,取决于这个指针指向的数据类型。
因此,综上所述,当&a与整数1做加法时,实际上是指针的加法,加1的含义是:令指针a指向下一个数据 ,下一个数据是啥?当然是紧挨着的下一个具有4个整型变量的数组了(因为&a的类型是指向具有4个整型变量的数组的指针嘛),于是a的指向了4的下一个地址,在用此值初始化ptr1,因此ptr1的指向如图所示:
由于在ptr1初始化的时候,令&a+1强制转换成整型指针,因此ptr1[-1]相当于把ptr1往前挪一个整型大小,即4个字节。 如下图:
显然,打印出的第一个数字是a[3]的内容,即数值4.
当然,我们还必须说明一个事实:数组下标是可以为负数的,实际上,取下标符“[ ]”的内部实现,就是指针运算!比如a[2],等价于*(a+2),即以a地址为基址,取偏移量为2的地址的值。所以ptr1[-1]等价于*(ptr-1)。
2、(int *)((int)a+1)
至于指针ptr2的处理,它先是把数组名a强制转换成整型变量,然后再加1,然后再强制转换成整型指针!真罗嗦,但不要紧,咱有的是耐性,无非就是让ptr2指向a[0]的第二个字节罢了,此时ptr2指向如下图所示:
显然,此时打印的内容就是ptr2所指向的往后4个字节的内容了,也就是a{0}的后三个字节和a[1]的第一个字节,那究竟会打印出什么?上面的图没有画出里面的内容,要是把每个字节的内容都画出来就好了.
要把上图中每一个字节的内容都打印出来没问题,但是你先要知道字节序 的概念,字节序分两种,一种叫大端字节序(big-endian) ,当然除此之外必然有小端 字节序(little-endian) ,让我们用一个问题,来引出字节序的概念,然后再来搞定他们!
问题是这样的:对多字节存储的变量,机器是如何做出解释的??请看下图:
假如这是一个普通的int变量,地球人都知道,在32机器上int占用4个字节存储数据,就像上图中显示的那样,在4个字节中放置了一堆数字,但是机器究竟会把这个数解释成0x0103070F呢,还是解释成0x0F070301呢?答案是:都有可能!
官方解释:
1、所谓大端(big-endian)序,就是高优先位 对应高有效位 。
2、所谓小端(little-endian)序,就是高优先位 对应低有效位 。
民间解释:
1、所谓大端(big-endian)序,就是读取或者存放数据时,最低 位 对应 高地址 。
2、所谓小端(big-endian)序,就是读取或者存放数据时,最低 位 对应 低地址 。
如,要把0x0103070F存放进存储器中时,如果把0E放进高地址处则是小端序,如果把0F放进低地址处则是大端序。照此,上图中存放的数据如果被机器理解成0x0103070F则是该机器是大端序的,否则若被理解成0x0F070301则是小端序的。
回到原来的问题,此时ptr2指向了a[0]的第二个字节。我们以x86平台为例(小端序),此时其内部数据分布是这样的:
由于x86平台是小端序的,根据咱刚刚讨论过的理论,小端序的存取时最低 位 对应 低地址 ,因此将会打印出0200 0000,如果题目中没有说明在x86平台,那答案是不确定的,取决于具体的平台,例如ARM平台就是大端序的。
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
int a[3][2]= {(0,1),(2,3),(4,5)}相当于int a[3][2] = {1,3,5};这里考察的逗号运算符.
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;
}
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;
}
*(ptr1 - 1)较为简单,大家参考上述内容即可得出。
*ptr2相当于aa[1][0], *(ptr2 - 1)就变成了a[0][4]。
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这就是指针的指针调用,较为简单。
int main()
{
char *c[] = {"ENTER","NEW","POINT","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[] 是一个指针数组,他里面存放的指针,每个指针都会指向一个字符串
charcp[] 同样也是指针数组,它里面存放的指针,会指向数组c[]。
char**cpp 是一个三级指针,它指向的是数组cp。
分析前两个代码之前,首先强调优先级问题,以及上一个cpp的值会影响下一段代码。
分析printf(“%s\n\r”, **++cpp)
第一步:因为优先级的原因,首先计算cpp++,原本他指向的cp[]数组,指向的内容存放地址为0x00EFFD38,由于++所以指向了下一项0x00EFFD3C这一项。
第二步:解引用,所以*cpp++的值为0x00EFFD3C,它指向的c[]数组,指向的内容所存放的地址为0x00EFFD58。
第三步:再进行解引用,所以**cpp++的值为0x00EFFD58,他的地址所指向的字符串为PIONT。
分析 printf(“%s\n\r”, –++cpp + 3)
参考上一个代码,这个代码就不多赘述,第一步计算*++cpp解引用,然后计算 - -++cpp ,此时*- -*++cpp的值为0x00EFFD50,它指向字符串ENTER的首个字母E,此时它还是地址,当+3之后,它指向第四个字母E,最后输出的结果为ER。
printf(“%s\n\r”, *cpp[-2] + 3);
我们来看这个代码,上面我说过,此处的cpp会受到前面的影响
所以一开始cpp指向的数组cp[]存放的地址为0x00EFFD40
然后就简单明了,首先计算cpp[-2],cpp[-2]通俗的来说就是-2然后再解引用一下而已,再解引用,得出地址0x00EFD50,他所指向的字符为FIRST,然后加3,输出的结果为ST。
printf(“%s\n\r”, cpp[-1][-1] + 1);
前面我们说过,后边的值会受到前面的影响,有人就会有疑惑,那这样的话,cpp再减一,就不对了,这样的理解是错误的,cpp[-2]我们可以把它当做解引用,并没有做运算,所以cpp的值,还会延续第二个代码结束时的值,好了我们来分析最后一个代码。
和前几个代码大同小异,最后得出的值为EW
这就是指针进阶学习的所有内容了,希望大家通过这两章节的学习可以进一步掌握指针内容的学习。