• 八道指针笔试题


    在这里插入图片描述



    一、笔试题1:

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      首先定义了一个数组a,其有5个元素每个元素为int型。接着&a(取出的是整个数组a的地址,因为&数组名取出的是整个数组的地址),然后+1(跳过一整个数组,也就是数组a,地址指向数组末尾的位置;原因是:地址(指针)的类型的决定了加减整数时的步长),最后将这个地址强制类型转化为int*,再赋值给int* ptr。内存布局如下图所示:

    在这里插入图片描述

      所以*(a + 1)就是在访问数组a第二个元素,也就是这里的2;*(ptr - 1)就是在访问数组a第五个元素,也就是这里的5(因为地址(指针)的类型决定了其在解引用操作的时候能过访问空间的大小,即:int型指针解引用访问int型大小的空间,char型指针解引用访问char型大小的空间)。故printf打印的及如果为2,5

    在这里插入图片描述


    二、笔试题2:

    //已知,结构体Test的类型大小是20个字节,假设p 的值为0x100000。
    //问:如下表表达式的值分别为多少?
    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);
    	printf("%p\n", (unsigned long)p + 0x1);
    	printf("%p\n", (unsigned int*)p + 0x1);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

      首先创建了一个结构体类型Test,然后用该结构体类型定义了的指针变量p,并初始化为0x100000(也就是使指针p指向了地址为0x100000的内存空间)。接着调用主函数:

      第一个printf:打印的是p + 0x1的地址,也就是p+1的地址(因为这里的0x1表示的是十六进制的1,转化为十进制也是1)。由于p是一个指向Test类型结构体的指针,所以+1就是跳过一个该类型结构体的大小,也就是20个字节,故打印结果为0x100000 + 20 = 0x100014

      第二个printf:打印的是(unsigned long)p + 0x1,运算顺序是先将指针强制类型转化为unsigned long类型,也就是无符号长整形,那对整型+1不就仅仅只是加一嘛。故表达式(unsigned long)p + 0x1的结果为0x100001,然后printf以地址类型打印整型类型的0x100001,结果为:0x100001

      第三个printf:打印的是(unsigned int*)p + 0x1,与之前一样先强制类型转化unsigned int*注意:这可是一个指针类型啊,加减整数可不仅仅只是加减整数),所以这里的p+1其实是跳过了一个int型的大小,也就是4个字节。故打印结果为:0x100000 + 4 = 0x100004

    在这里插入图片描述


    三、笔试题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);
    return 0;
    }
    //问:打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      首先创建了一个int [4]型的数组,然后为int*类型的指针ptr1和ptr2赋值,其中int *ptr1 = (int *)(&a + 1);与笔试1类似,指向的是数组a的末尾。这道题主要考点这于(int *)((int)a + 1),它先是将a强制类型转化为int型,也就是将数组a首元素的地址强制类型转化为int型,当成整型来看(因为数组名一般情况下表示数组首元素的地址)。然后对其+1,自然结果仅仅就是加一,然后将得到的这个整型数强制类型转化为(int*)后赋值给pt2,故此时ptr2指向的就是数组首元素地址向后跳一个字节后的空间。内存布局如下图所示:

    在这里插入图片描述

      最后打印ptr1[-1]等价于*(ptr-1),printf是以%x(十六进制)的形式打印,所以最后的结果为:4,2000000

    在这里插入图片描述


    四、笔试题4:

    int main()
    {
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
    return 0;
    }
    //问:最后打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      首先定义了一个3行2列的二维数组,然后对其初始化{ (0, 1), (2, 3), (4, 5) },大家有没有觉得很变扭,初始化二维数组什么时候用()啦?不因该是{ {0, 1}, {2, 3}, {4, 5} }这样嘛。所以这里的()并不是代表一行,而因该是逗号表达式,故这里的初始化因该是不完全初始化,内存布局如下图所示:
    在这里插入图片描述
      接着将p = a[0];,其中a[0]表示的数组a第一行的数组名,而数组名又表示数组首元素的地址,故这里的a[0]表示第一行首元素的地址,即a[0][0]的地址。然后printf打印p[0],结果为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]);
    return 0;
    }
    //问:最后打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      首先创建了一个5行5列的二维数组a,然后又创建了一个数组指针p(其指向的数组有4个元素,每个元素都是int型的)。我们有知道二维数组的数组名本质上表示的是第一行的地址,p = a;就是将二维数组a第一行的地址赋值给了数组指针p(注意:虽然它两的类型不同,但传递的值是不变的),故你会发现此时a和p指向了同一个位置。

      其实我们完全可以把p和a都看成二维数组(只不过这两个二维数组的起始位置是相同的,而且p代表的二维数组每行只有4个元素,而a代表的每行却有5个元素)。然后来理解 &p[4][2] - &a[4][2],这求的不就是两个地址之间元素的个数嘛(因为指针 ‘-’ 指针的值是,指针之间元素的个数),而且是二维数组p第4行第2列元素地址与二维数组a第4行第2列元素地址之间元素的个数,那不就只要知道p[4][2]前元素的个数和a[4][2]前元素的个数之后相减就可以得到想要的结果了(注意:指针‘-’指针的值也可能会是负数)。p[4][2]前元素的个数是4 * 4 + 2 = 18a[4][2]前元素的个数是4 * 5 + 2 = 22,故&p[4][2] - &a[4][2]的结果为 -4 。内存布局如下图所示:

    在这里插入图片描述

      然而这道题还没有做完,他还考了一个知识点,printf是要以两种不同的形式分别输出&p[4][2] - &a[4][2]表达式的结果。我们知道%d表示是以有符号整型输出,故输出因该是:-4。而%p是专门用来打印地址的(而且是十六进制形式输出),但我们要知道地址在存放的时候可没有原码、反码、补码这么一说,它是直接放入内存当中的,故取的时候也直接取就完事了,不需要经过任何的转换(即内存当中是什么,打印的就是什么)。打印结果就是-4的补码,即:ff ff ff fc。

    在这里插入图片描述


    六、笔试题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));
    	return 0;
    }
    //问:最后打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      首先创建了一个2行5列的二维数组aa,然后你会发现这题的考点其实与之例题3的考点差不多。只不过是将一维数组的数组名换成了二维数组的数组名罢了。下面看内存布局:

    在这里插入图片描述
    在这里插入图片描述


    七、笔试题7:

    int main()
    {
    	char* a[] = { "work","at","alibaba" };
    	char** pa = a;
    	pa++;
    	printf("%s\n", *pa);
    	return 0;
    }
    //问:最后打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      首先创建了一个字符指针数组,然后使每一个字符指针都指向一个常量字符串,接着创建了一个二级指针pa,让其指向指针数组a首元素a[0]。内存布局如下图所示:

    在这里插入图片描述
      *pa访问的是a[1],而a[1]中存放的是字符串“at”首字符a的地址,所以printf打印的结果是“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);
    printf("%s\n", *--*++cpp+3);
    printf("%s\n", *cpp[-2]+3);
    printf("%s\n", cpp[-1][-1]+1);
    return 0;
    }
    //问:最后打印结果是什么?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      关于这道题大家就不要吝啬你的草稿纸了,内存布局画出来分析吧,不然光靠想是做不出来的,因为太过于混乱了。内存布局如下图所示:

    在这里插入图片描述
      接下来就是简单的根据表达式的优先级的运算,来最终求出打印的结果。

    在这里插入图片描述


    总结

      我们在看代码的时候可不能只是看到代码本身啊,一定要做到看代码就能联想出其对应的内存布局,如果能做到这一步那我们不管面对什么题目都可以做到一目了然了。

  • 相关阅读:
    基于javaweb软件专业介绍管理系统
    【SpringSecurity】三更草堂项目案例分析2 - 认证主体业务编写
    Springboot毕设项目体育器材及场地管理系统60g3njava+VUE+Mybatis+Maven+Mysql+sprnig)
    面试题 | 说一说cookie sessionStorage localStorage 区别?
    【redis】Stream、String 超详细介绍
    Bert不完全手册3. Bert训练策略优化!RoBERTa & SpanBERT
    为什么 JavaScript 模块中的默认导出很糟糕
    rac环境rman备份
    新的 Reptar CPU 缺陷影响英特尔台式机和服务器系统
    【Proteus仿真】【Arduino单片机】DS1302时钟
  • 原文地址:https://blog.csdn.net/m0_66769266/article/details/126850564