C语言地狱难度特辑精选2:与指针运算和数据在内存中的存储相关的笔试题

目录

题目一

题目二

题目三

题目四

题目五

题目六

题目七

典型用例


上篇博客的内容只能给到一个量子微难,比较简单,如果你可以很好理解并掌握,那没什么好说的了,跟上我的节奏吧,开始今天的内容。还是那句话,如果感觉本节太难了,已经理解不了了,就算了。毕竟基础才是最重要的。要读懂本节需先掌握指针初阶进阶的所有知识,一维,二维数组的所有知识,结构体的所有知识。

本节内容在本特辑也就可以给到一个量子中难,看能不能使你十分闭塞的眼界变得宽敞一点

话不多说,现在开始!!!!!!!!!!

题目一

#include <stdio.h>
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}

首先我们先分析一下ptr这个指针最开始是指在哪的,可以发现&a是取出整个数组的地址,加1就是跳过整个数组的空间,(int*)就是将ptr强制性类型转换成整型指针(其实ptr原本就是整型的,这步有点多余),所以ptr原本是指向5这个空间的后面的。

所以ptr减1就是减去一个整型的空间,所以就是指向5的,再解引用就可以得到*(ptr - 1)等于5,

*(a + 1)就不用我解释了吧,等于2。

事实证明我们的分析是对的,好像也没有这么难嘛,我们接着看下一题。

题目二

#include <stdio.h>
int main()
{
    //注意逗号表达式
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}

很多人看到这个题一定会认为这个a数组是这样存储数据的:

恭喜你又被出题者套路了!!!(0, 1), (2, 3), (4, 5)是逗号表达式只读取每个的最后一个数字。所以a数组应该是这样的:第一行1   3  第二行 5  0 第三行  0  0。

p = a[0];说明p指向a数组的第一行,为第一行的数组名, p[0]就是指向第一行的第一个元素,就是等于1。

事实说明我们的分析正确。

题目三

假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
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(* p)[4]; p = a;是一个指向a的数组指针,但是a数组不是一行有5个元素吗?[4]这个代表了p指向的一行强制性只有4个元素,所以也就是说在[4]的影响下相当于a变成了每行4个元素,还是5行。p和a都从a数组的第一个元素开始访问。所以要解决这题画图就是最好的解决方法。

所以由图可知&p[4][2] - &a[4][2]就是等于-4,如果用%p打印-4,就是在打印-4的地址,也就是补码,地址也就是这个补码在内存中是以16进制存储的就是FFFFFFFC。

由事实可知我们的分析正确。

反观这题可知,要解对这类题画图是很关键的。

不要偷懒,不要偷懒,不要偷懒,画图画图画图!!!

题目四

#include <stdio.h>
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;
}

这个题和第一题的解题思路是差不多一致的,只不过换成了二维数组罢了。

思路差不多所以就不再细讲了。重点看ptr2,由于aa是首元素地址,但对于二维数组来说,二维数组可以看成是由n多个一维数组构成的,所以二维数组的首元素地址就是构成它的第一个一维数组。所以aa加1就是指在6这个位置的,ptr2减1就是指在5的,解引用后得到的就是5了。

事实说明我们的分析正确。

题目五

在X86环境下//一定要改,不然假设可能不对
假设结构体的大小是20个字节
程序输出的结果是啥?
#include <stdio.h>
struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}* p = (struct Test*)0x100000;
//
//指针+整数
//
int main()
{
    printf("%p\n", p + 0x1); //0x100000+20 == 0x100014
    printf("%p\n", (unsigned long)p + 0x1);//0x100000+1 == 0x100001
    printf("%p\n", (unsigned int*)p + 0x1);//0x100000+1 == 0x100004
    return 0;
}

首先结构体指针p倍强制性赋值为0x100000,也就是p是等于100000,这里就是在考察指针加1到底是跳过了几个字节。

p + 0x1   结构体指针加一就是要跳过一个结构体的大小,也就是要跳过20个字节,最终结果就是0x100000+20 == 0x100014(16进制)

(unsigned long)p + 0x1   p被强制性转换成unsigned long,此时p已经不是表示指针了而是表示一个整数,此时再加1,就是数学上的加一,即0x100000+1 == 0x100001

(unsigned int*)p + 0x1   将p强制性类型转换成一个整型指针,此时和第一个一样加1跳过整个指针的大小,由于此时p为整型指针,所以加1就是跳过4个字节。即0x100000+1 == 0x100004

题目六

#include <stdio.h>
int main()
{
    char* a[] = { "work","at","alibaba" };
    char** pa = a;
    pa++;
    printf("%s\n", *pa);
    return 0;
}

这个题一看就知道是阿里巴巴的笔试题,因为"work","at","alibaba"。

在指针数组中,a中存的是w,a,和a的地址也就是每一个字符串的首字母的地址。一级指针一定是装在二级指针里的,所以pa里存的就是a空间里的内容即{w,a,a},pa++使pa由原本的指向首元素的地址即w变成指向a,因为a是数组名,传的是首元素的地址。所以*pa再用printf打印就得到at这个单词了。

这里有人就有疑惑了,为什么printf可以通过首元素就可以打印得到整个字符串。那是因为printf打印字符串的话,就会根据传入的地址向后一个字节一个字节的访问,直到遇到\0才会停止。

事实说明我们的分析没有问题

题目七

#include <stdio.h>
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;
}

这个题确实是难度挺大的,它需要画的图比较多而且问的问题的格式也比较复杂。

比如说*-- * ++cpp + 3,肯定是先++cpp再解引用再--最后算*。

要做对这个题的关键在画图:

c数组里的元素为每个的首元素地址,所以从上往下就是“E”“N”“P”“F”。

  char** cp[] = { c + 3,c + 2,c + 1,c };由于一阶指针要存在二阶指针里,所以**cp也没有什么问题。

此时cp第一个元素为c + 3也就是FIRST的首字母的地址,以此类推,cp里面装的就是“F”“P”“N”“E”。如图

这样再看下一行,  char*** cpp = cp;二阶指针要装在三阶指针里,所以也没有什么问题。cpp指向cp的首元素的地址,就是F。就是最开始的位置即为c + 3。

所以题目都分析完了,我们看问题。

  printf("%s\n", **++cpp);   ++cpp使cpp由指向c + 3变成指向c + 2,再解引用得到c + 2指向的point在c中的地址,在解引用得到point,所以答案就是point

printf("%s\n", *-- * ++cpp + 3);由于此时cpp是指向c + 2的所以++p就是指向c + 1的解引用得到cp指向c + 1的这个指针,这个指针是指向NEW的,再--使cp指向ENTER再解引用得到ENTER的首元素的地址即为E的地址,再加3使c向后跳3个字母指向E,此时可以认为首元素变成E了,再用printf打印就得到ER,所以答案就是ER。

printf("%s\n", *cpp[-2] + 3); 首先cpp[-2]的意思是*(cpp - 2),由于经过前两问的变动使得cpp变成指向c + 1了,所以减2又再次指向c + 3,再解引用得到cp中指向FIRST首元素的指针,所以再解引用得到FIRST的首元素的地址。最后c指针加3使其向后跳转3个字母即跳转至S,再用printf打印得到ST。所以答案就是ST。

printf("%s\n", cpp[-1][-1] + 1); cpp[-1][-1]的意思是*(*(cpp - 1) - 1),那有的人就会这样想了:

经过前两个的运算,cpp此时是指在了C+1的位置对吧,因为加了两次,此时cpp【-2】使得Cpp又向上减2变到指向C+3的位置,那么第4问,cpp[-1]使cpp再向上减1,这时cpp不就越界了吗?事实是这样吗,我们先看一下运行结果。

咦,第四个有运行结果的。这里说一下为什么。

cpp[-2]不会修改cpp的指向位置,前面的++cpp会修改cpp的指向,是因为++cpp就等同于cpp+=1。cpp[-2],这个跟你一维数组是一样的,比如int arr[10] = {0};arr[3]就是*(arr+3),访问完3下标的元素后,arr还是首元素的地址,并不是说arr就变成了3下标的地址了,所以cpp[-1]不会越界访问,而是指向了c + 2的地址,这样的话cpp[-1][-1] + 1的解发就和前面的三问都差不多了,这里就不用我多说了吧。

本节内容就到这里,我想看到这里已经汗流浃背了吧老弟。

典型用例

由于下面的题解法和我上面有解析的7个题的解法都差不多,所以就不再解析,不懂的记得及时私信我呦!!!

下面程序的结果是:( )

int main()
{
    int aa[2][5] = { 10,9,8,7,6,5,4,3,2,1 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

A.1, 6
B.10, 5
C.10, 1
D.1, 5

下面程序的结果是:( )

int main()
{
    int a[5] = { 5, 4, 3, 2, 1 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}

A.5, 1
B.4, 1
C.4, 2
D.5, 2

预知答案如何请看下篇博客,如果觉得博主的文章很不错那请多多点赞,收藏+关注,我太喜欢点赞啦!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值