• 梦开始的地方——C语言指针练习题


    指针练习

    注意我的环境是:win10下的VS2019-X86环境

    练习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取出整个数组的地址,但它的地址和首元素的地址还是一样的,+1跳过了整个数组,现在指向的是数组最后一个元素后面的那个地址,把它强制转换为int*的指针
    • a是数组首元素地址,+1就是数组第二个元素的地址,解引用拿到的就是第2个元素的
    • 对(a+1)解引用输出的就是 2
    • ptr现在指向的是数组最后一个元素后面的那一块地址,现在prt是一个整形指针-1,往回走4个字节。就指向了数组最后一个元素的起始地址

    最后输出

    2,5
    
    • 1

    练习2

    注意这是在在VS2019,x86环境

    struct Test
    {
        int Num;
        char *pcName;
        short sDate;
        char cha[2];
        short sBa[4];
    }*p;
    //假设p 的值为0x100000。 如下表表达式的值分别为多少?
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 结构体是20个字节,p是一个结构体指针,0x1是一个数组,相当于对指针+1,跳过结构体指针所指向元素大小,也就是20个字节

    • 最后打印 100014

    • p的地址是0x100000,强制转为为一个无符号的长整形,此时就会把0x100000当做16进制转换为无符号长整形

      0x100000 转二进制 00010000 00000000 00000000 ,再+1就是 00010000 00000000 00000001

      最后以%p的形式来打印,就会把这个二进制当做地址来打印,当然它是以16进制的形式打印

      0x100001

    • 最后一个printf,把结构体指针强制类型转换为无符号整形的指针,跳过4个字节,0x100004

    最后打印

    00100014
    00100001
    00100004
    
    • 1
    • 2
    • 3

    练习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
    • 我这里的环境是win10下的VS2019-X86环境
    • 我们知道数组的地址是随着下标的增长从低到高,内存中存储的是16进制的数字,一个16进制位对应4个比特位
    • 我这里是小端存储,所以在内存中把低位字节存储在低地址处,高位字节储存在高地址处。
    • &a+1跳过整个数组,再强制类型转换为int*的指针,现在ptr1指向数组末尾的地址处
    • a是数组名里面存的是地址,地址是一个十六进制的数字,把这个地址强制转换为int类型的10进制。
    • 强制类型转换后再+1,就是对整形加了个1。然后再把+1之后的整形转换为int*的地址
    • 我们知道每个内存单元的大小是一个字节,每个字节有自己的一个地址,上面+1相当于对地址+1
    • 所以就跳过了一个字节,一个字节8个位,两个16进制位就等于一个字节
    • ptr1[-1]相当于(prt1+(-1)),此时ptr1就指向了数组最后一个元素的起始地址,再以%x打印
    • 打印的是16进制,我们是以小端存储放进去的就要以小端存储的形式拿出来,就是0x00000004
    • 此时ptr2指向的是数组第一个元素第二个字节的位置,ptr2是一个int*的指针,解引用拿到后面四个字节
    • 又因为我这里的平台是小端存储,所以拿出来就是0x02000000

    最后打印

    4,2000000
    
    • 1

    在这里插入图片描述

    练习4

    #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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 化括号里放的是逗号表达式,逗号表达式取的是最后一个值
    • 二维数组在内存中也是连续存储的,p指向数组首元素
    • p[0]相当于*(p+0)

    输出

    1
    
    • 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
    • p是一个指针,指向了一个整形数组,数组有4个元素,把二维数组首元素也就是第一行一维数组的地址赋给p

    • p[4][2]等价于*(*(p+4)+2),p是一个数组指针,指向的数组有4个元素,所以每加一次就会跳过4个元素

    • *(p+4)相当于拿到了一个数组名,也就是数组的首地址,再+2就拿到这个数组第二个元素的地址,解引用就拿到第二元素

    • &p[4][2] - &a[4][2],指针和指针相减的绝对值拿到的是指针之间的元素个数,这里是小减大所以是-4

    • -4在内存中的补码

      原码:10000000 00000000 00000000 00000100

      反码:11111111 11111111 11111111 11111011

      补码:11111111 11111111 11111111 11111100

    • 再以%p的形式打印,而%p打印地址是无符号的,它就会认为-4存的内存中的补码就是原码直接以16进制打印

    • FFFF FFFC

    • 而%d是打印有符号的整形就会直接打印-4

    打印结果

    FFFFFFFC,-4
    
    • 1

    在这里插入图片描述

    练习6

    int main()
    {
    	int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    	int* ptr1 = (int*)(&a + 1);
    	int* ptr2 = (int*)(*(a + 1));
    	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • &a取出整个二维数组的地址+1,跳过整个二维数组,再强制类型转换为int*
    • a是二维数组的数组名,数组名是首元素地址,也就是第一行一维数组的地址,*(a+1)跳过第一行,拿到整个第二行一维数组的地址,也就是第二行的数组名。再强制类型转换为int*
    • *(ptr1-1),ptr1是一个整形指针,-1往回走4个字节
    • *(ptr2-1),同理往回走

    最后输出

    10,5
    
    • 1

    在这里插入图片描述

    练习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
    • char* a[]数组里存的都是字符串第一个字符的地址
    • pa是一个二级指针,存放的数组a的地址,也就是char* a[]首元素的地址
    • pa++,相当于指针+1,此时就指向了数组第二元素
    • *pa,pa指向数组第二元素,第二个元素是字符串的首地址,对它解引用就拿到了字符串

    最后输出

    at
    
    • 1

    在这里插入图片描述

    练习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

    没有执行任何printf的指针指向图

    在这里插入图片描述

    • **(++cpp),第一接引用拿到c数组第二个元素的地址,再解引用拿到第二元素所指向的字符串
    • 此时的cpp指向发生改变

    在这里插入图片描述

    • 再执行第二printf,cpp里面存的是c+2的地址
    • ++cpp,让cpp指向下一个位置也就是c+1元素,在解引用找到c+1--修改c+1的指向,让它指向它的上一个元素,此时它指向的是一个字符串“ENTER”,再解引用拿到这字符串的首地址,再+3
    • 此时指向的就是"ST"

    在这里插入图片描述

    • 前两个printf会修改指针指向,而后面两个不会
    • *cpp[-2]等价于*(cpp-2),指向的是“FIRST”,再+3跳过3个字符
    • cpp[-1][-1]等价于*(*(cpp-1)-1),此时指向的是"NEW"
    • 在+1跳过一个字符

    最后输出

    POINT
    ER
    ST
    EW
    
    • 1
    • 2
    • 3
    • 4

  • 相关阅读:
    力扣贪心——跳跃游戏I和II
    Qt开发学习笔记02
    【计算机网络】第三章:数据链路层
    Redis基础入门
    Azure - 机器学习企业级服务概述与介绍
    [SpringBoot]SpringBoot整合第三方技术
    【算法基础】基础算法(三)--(双指针算法、位运算、离散化、区间合并)
    深度学习基础——YOLOv5目标检测
    VUE element-ui之el-form表单点击按钮自动增加表单(输入框),可新增删除
    LVS部署-DR集群
  • 原文地址:https://blog.csdn.net/weixin_53946852/article/details/127732490