接上文,我们继续笔试题目讲解。
目录
笔试题3
笔试题4
笔试题5
笔试题6
笔试题7
面试题8
总结
笔试题3
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);//0x00 00 00 04 0x02 00 00 00
return 0;
}
程序分析:
这道题在小端存储的计算机上运行的前提下进行讲解。大小端介绍详见前几期博客的讲解连接如下:数据在内存中的存储_清水加冰的博客-CSDN博客
已知计算机为小端存储,那数组a在内存中存储情况就如下图所示:
1转化为16进制就是0x00 00 00 01,在小端机器中存储方式如上图所示。
ptr1为(int*)(&a+1)
&a为int (*)[4]类型,&a+1也是int (*)[4]类型,(int*)(&a+1)就是将&a+1的类型强制类型转换为int*类型(指针类型不同,+1-1所跳过的空间大小不同)。
那ptr1[-1]就等价于:*(ptr1-1),那ptr1-1所指向位置也就是元素4,如下图:
在解引用输出就是0x4(%x输出16进制)。
ptr2 = (int *)((int)a + 1),a是数组首元素地址,将a强制类型转换为int类型,我们先假设a的地址为0x0014ff20,转换为整形,还是0x0014ff20,只不过这里的16进制表示的是数字,那(int)a + 1也就是正常的整数相加,(int)a + 1=0x0014ff21,再将(int)a + 1转换为int*类型,那此时(int)a + 1,也就是a数组首元素地址跳过了一个字节的空间。如下图所示:
然后输出解引用的值,(int)a + 1从整形又被转换为int*类型的指针,整形指针解引用需要4个字节的空间,于是就从(int)a + 1指向的位置向后找4个字节空间的数据,转换为整形。
即00 00 00 02,又因为存储时是小端存储,所以取出时的顺序应该是:02 00 00 00,转换16进制也是0x02 00 00 00
笔试题4
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);//1
return 0;
}
分析:
a是一个二维数组,进行了初始化,但它实质上只初始化了三个值,即1,3,5。这里需要注意,初始化使用的是逗号表达式,如果想一行一行初始化应该用{0,1}。所以这里有坑需要看仔细。
整形指针变量p=a[0],一般情况下,我们可以把a[0]理解为二维数组的第一行,但这里注意p是一个整形指针,并且赋值的时候没有&a[0],这里的a[0]就相当于是第一行数组的数组名,指向的是数组a[0][0]的地址。p[0]等价于*(p+0),也就是*p,所以结果是1.
笔试题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]);//0xFFFFFFFC -4
return 0;
}
分析:
p是一个int(*)[4];类型的指针,p = a,a是一个5行5列的二维数组,那a就是第一行的地址,a是int(*)[5]类型,在赋值时会发生类型转换。我们注意,a走一步是5个整数空间,p走一步是4个整形空间。
我们来看一下p[4][2]的位置,a[4][2]的位置。
它们之间相差了4个字节。我们知道数组在存储地址时是从低地址到高地址,那我们就可以知道&p[4][2] - &a[4][2]以%d的形式输出就是一个-4。
负4的二进制序列为:
原码:10000000000000000000000000000100
反码:11111111111111111111111111111011
补码:1111 1111 1111 1111 1111 1111 1111 1100
当我们以%p(输出地址)的形式打印时,计算机就会将补码当作地址输出
1111 1111 1111 1111 1111 1111 1111 1100
F F F F F F F C
也就是0xFFFFFFFC。
笔试题6
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));//10 5
return 0;
}
分析:
aa是一个二维数组,&aa取出的是二维数组的地址,&aa+1跳过的是整个二维数组。aa是二维数组首元素的地址,aa+1跳过的是5个整形空间。并且它们都被强制类型转换为int*类型。
ptr1 - 1和ptr2-1也就是ptr1和ptr2向前跳过4个字节(一个整形空间),所以*(ptr1 - 1), *(ptr2 - 1)也就是10和5。
笔试题7
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
分析:
单个字符串时我们可以这样写:char *p=“abcdef”,那当多个字符串呢?
上述题目中就是一个字符指针数组。
a是一个字符指针数组,a[0]存放的是w的地址,a[1]存放的是a(at)的地址,a[2]存放的是a(alibaba)的地址。
char**pa = a;,pa是一个二级指针,pa指向的是a[0],现在pa++之后,pa指向的就是a[1]。
*pa就是a[0],指向的是at中a的地址,而%s输出时需要一个字符串地址才能输出,所以输出结果是at。
面试题8
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *--*++cpp+3);//ER
printf("%s\n", *cpp[-2]+3);//ST
printf("%s\n", cpp[-1][-1]+1);//EW
return 0;
}
这道题于上到题目类似,但这道题难度更高一些。
分析:
我们先根据三个变量的初始化画出它们的关系图:
**++cpp,先是cpp++,此时cpp指向的就是cp[1],**就是两次解引用,第一次解引用是cp[1],再解一次引用就是C[2],C[2]是POINT中P的地址,那么输出的就是POINT
*--*++cpp+3,这个看起来很复杂,也间接的考察了优先级,先是++cpp,由于cpp经过了一次++操作,所以这里++操作之后cpp指向的是cp[2],再解引用就是cp[2],然后再对cp[2]进行--操作,这时cp[2]指向的就是C[0],解引用就是ENTER中E的地址,再+3,指向的就是第二个E,输出也就是ER
*cpp[-2]+3,cpp[-2]等价于*(cpp-2),那么*cpp[-2]+3也就可以写成**(cpp-2)+3,这里我们是先进行cpp-2操作,减2之前,cpp指向的是cp[2],cpp-2指向的就是cp[0],进行一次解引用是cp[0]也就是C+3,在解引用一次就是FIRST中F的地址,在+3,就是S的地址,那么输出就是ST
cpp[-1][-1]+1,这里我们在次转换一下,转换为*(*(cpp-1)-1)+1,首先看cpp,在上一个操作中cpp并没有进行++或--操作,所以此时cpp指向的依然是cp[2],cpp-1指向的就是cp[1],解引用就是cp[1](C+2),然后是*(cp[1]-1)+1,cp-1指向的是C[1],解引用后就是NEW中N的地址,+1指向的就是E的地址,所以最终输出是EW
总结
好了本期内容到此结束,希望这些内容能够对您的编程之路有所帮助。最后,感谢您阅读本文并参与博主提供的练习题。如果您有任何疑问或建议,请随时在评论区留言,博主将尽快回复您。感谢阅读!