【C语言】——指针八:指针运算笔试题解析

【C语言】——指针八:指针运算笔试题解析

    • 一、题一
    • 二、题二
    • 三、题三
    • 四、题四
    • 五、题五
    • 六、题六
    • 七、题七

一、题一

//程序输出结果是什么
int main()
{int a[5] = { 1,2,3,4,5 };int* ptr = (int*)(&a + 1);printf("%d, %d", *(a + 1), *(ptr - 1));return 0;
}

  这道题 * (a + 1) 相信大家都没问题,它表示的是数组的第二个元素
  
我们来重点讲一下*(ptr - 1)

  • 先来看 &a。对数组名进行取地址操作, 取出的是整个数组的地址,虽然数值上与数组首元素相等,但他们是不同的类型。在这里,它类型为 i n t ( ∗ ) [ 5 ] int(*)[5] int[5]
      
  • 再看 &a + 1 ,这里取出的是整个数组的地址,+1 即跳过整个数组。最后,再强制类型转换成 i n t int int * 类型
      
  • ptr - 1 此时,指针已经跳过了整个数组,因为此时的 p t r ptr ptr 已经是 i n t int int* 类型, - 1,后退 4 个字节,即指向数组最后一个元素
      
  • *(ptr - 1) 最后再进行解引用,取出数组最后一个元素

在这里插入图片描述

运行结果:

图:

  
  

  
  
  

二、题二

//在x86环境下
//假设结构体大小20字节
//程序输出结果是啥
struct Test
{int Num;char* pcName;short sDate;char cha[3];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;
}

  

  • 首先我们来理解(定义的结构体类型)*p = (struct Test*)0x100000; 什么意思呢?即定义了一个结构体类型,创建了该类型的指针变量 p p p 。同时,将0x100000这个十六进制数强制类型转换成该结构体指针类型(整数默认 i n t int int 类型)后,将其赋值指针变量 p p p
      
  • printf("%p\n", p + 0x1); 指针类型 +1,跳过的是该指针所指向元素的类型的大小个字节。这里定义的结构体大小为 20 字节,即跳过 20 字节,因为是十六进制表示,即:0x100000 + 0x14,答案:0x100014
      
  • printf("%p\n", (unsigned long)p + 0x1); 第二个 p r i n t f printf printf 语句,先将 p p p 强制类型转换成 u n s i g n e d unsigned unsigned l o n g long long 类型,此时0x100000 仅仅是 一个普通的数字而已,再加 0x1,是单纯的数学上的相加。答案:0x100001
      
  • printf("%p\n", (unsigned int*)p + 0x1); 第三个 p r i n t f printf printf 语句,先将 p p p 强制类型转换成 u n s i g n e d unsigned unsigned i n t int int *类型, u n s i g n e d unsigned unsigned i n t int int 大小为 4 个字节,所以 +1 跳过 4 个字节,十六进制表示。答案:0x100004

  
运行结果:
图

  
  
  
  
  

三、题三

int main()
{int a[3][2] = { (0,1),(2,3),(4,5) };int* p;p = a[0];printf("%d\n", p[0]);return 0;
}

  

  • int a[3][2] = { (0,1),(2,3),(4,5) };先来看对数组的初始化。这里有个小坑点 { (0,1 ), (2,3 ), (4,5 ) }大括号内是三个小括号,表示的是逗号表达式,并不是三个大括号,按行初始化。逗号表达式,从左到右一次执行,整个表达式的结果是最后一个表达式的结果

  因此数组中存放的数据是:

在这里插入图片描述

  

  • p = a[0]; a [ 0 ] 可以看做是二维数组中,第一行数组的数组名,数组名表示的是数组首元素的地址,即 p p p 中存放的是 1 的地址
      
  • printf("%d\n", p[0]); p [ 0 ] 相当于*(p + 0) == *p,即打印 1
      

运行结果:
图:

  
  
  
  
  

四、题四

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;
}
  • 理解这题,关键的是理解&p[4][2]&a[4][2]分别指向哪个元素。
      
  • 首先来看&a[4][2]
    • & a [ 4 ] [ 2 ] a[4][2] a[4][2] 等价于 & * ( * (a + 4)+ 2),& 和 * 相互抵消,即等价于 * (a + 4)+ 2。这里 a a a 是什么呢? a a a 是第一行这个一维数组的地址,类型为 i n t ( ∗ ) [ 5 ] int(*)[5] int()[5] a a a + 4,指向第五行。再对其进行解引用,得到的是第五行这个数组,即第五行的数组名,即第五行数组首元素的地址,对其 +2,指向第五行第三个元素

  

图:

  

  • 接着我们来看&p[4][2]
    • p p p 的类型为 i n t ( ∗ ) [ 4 ] int(* )[4] int()[4] ,题目中将 a a a 的地址给 p p p ,其实会报警告,因为 a a a i n t ( ∗ ) [ 5 ] int( * )[5] int()[5] 类型,类型不匹配,但程序能运行。
    • &p[4][2] 等价于* (p + 4)+ 2。首先,我们要理解 p p p + 1 跳过几个字节。因为 p p p 的类型为 i n t ( ∗ ) [ 4 ] int(* )[4] int()[4] ,所以在 p p p 眼里,二维数组 a a a 是一个每行有 4 个元素的数组+1 跳过的是 4 个整型,即 16 个字节。而 p p p + 4 则是过了 16 个整型,即 64 个字节
        
        
  • * (p + 4) + 2:对 p p p 进行解引用,再 +2,这个过程与 * (a + 4)+ 2 相同,即指针移动了两个整型大小的字节,指向第三个元素
      

-

  

  • 可以看到,&p[4][2]&a[4][2]之间相差了 4 个元素,因为&p[4][2]是小地址,所以 &p[4][2] - &a[4][2] = -4
      

  • printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); &p[4][2] - &a[4][2]的结果是 - 4,因此以%d的形式打印没问题。那以%p形式打印呢?%p是打印地址,地址肯定是无符号类型。- 4 的补码是11111111 11111111 11111111 11111100,转为十六进制为 FF FF FF FC
      

运行结果:

在这里插入图片描述

  
  
  
  
  

五、题五

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\n", *(ptr1 - 1), *(ptr2 - 1));return 0;
}

  

  • 这道题的数组初始化没有坑啊,不用担心。
      
  • int* ptr1 = (int*)(&aa + 1); & a a aa aa:& + 数组名,取出的是整个数组的地址,+1 跳过整个数组,再将其强制类型转换成 i n t int int* 类型。
      
  • int* ptr2 = (int*)(*(aa + 1)); a a aa aa数组首元素的地址,即第一行的地址,+1 跳过第一行,指向第二行。对其进行解引用,得到的是第二行的数组名,即第二行首元素的地址。其实这里强制类型转换成 i n t int int *是多余的,第二行首元素的地址本来就是 i n t int int *类型。
      
  • printf("%d %d", *(ptr1 - 1), *(ptr2 - 1)); 此时 * ( p t r 1 − 1 ) (ptr1 - 1) (ptr11) 与 * ( p t r 2 − 1 ) (ptr2 - 1) (ptr21) 都是 i n t int int *类型,- 1 后退 4 个字节,即一个整型元素。
      

图:

  
运行结果:
在这里插入图片描述

  
  
  
  
  

六、题六

int main()
{char* a[] = { "work","at","alibaba" };char** pa = a;pa++;printf("%s\n", *pa);return 0;
}

  

  • char* a[ ] = { "work","at","alibaba" }; 这是一个指针数组,数组中每个元素都放着指向对应字符串的首元素的地址。
      
  • char** pa = a; 这里取出数组首元素的地址,因为数组首元素原本类型为指针类型,所以这里 p a pa pa二级指针
      
  • pa++; p a pa pa = p a pa pa + 1,指向数组第二个元素的地址。
      
  • printf("%s\n", *pa); p a pa pa 解引用,得到指向 “ a t at at” 的地址,打印 a t at at
      

运行结果:
图:
  
  可能有些小伙伴对二级指针有点迷糊,这张图就一目了然啦(这里是 x86 环境下,x64 环境下指针变量大小 8 字节)
  

在这里插入图片描述

  
  这里我们可以把各个地址打印出来观察

int main()
{char* a[] = { "work","at","alibaba" };printf("%p\n", &a[0]);printf("%p\n", &a[1]);printf("%p\n", &a[2]);printf("\n");printf("%p\n", a[0]);printf("%p\n", a[1]);printf("%p\n", a[2]);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;
}

  
  要理解这道题,画图是必不可少的,我们先把图画出来

在这里插入图片描述

  

printf("%s\n", **++cpp);
  

  • 前置++ 与解引用操作符 ∗ * 的优先级一致,根据结合性从右往左运算
  • 先是 c p p cpp cpp 自增,自增后指向 c p [ 1 ] cp[1] cp[1]
  • 第一次解引用,找到 c p [ 1 ] cp[1] cp[1],第二次解引用找到 c [ 2 ] c[2] c[2]
  • 最终打印:POINT
      
      
    在这里插入图片描述

  

printf("%s\n", *-- * ++cpp + 3);
  

  • 首先 + 运算符优先级最小,因此 +3 最后才算
  • 其他四个操作符优先级一样,根据结合性从左到右计算
  • 先是 c p p cpp cpp 自增,自增后指向 c p [ 2 ] cp[2] cp[2]
  • c p p cpp cpp 解引用,找到 c p [ 2 ] cp[2] cp[2]
  • c p [ 2 ] cp[2] cp[2] 自减, c c c + 1 自减为 c c c ,原本指向 c [ 1 ] c[1] c[1],自减后指向 c [ 0 ] c[0] c[0]
  • c p [ 2 ] cp[2] cp[2] 解引用,找到 c [ 0 ] c[0] c[0]
  • c [ 0 ] c[0] c[0] 指向的是 “ENTER” 字符串的首元素 'E’+3 则指向 ‘E’
  • 答案:ER

  
  
在这里插入图片描述

  

printf("%s\n", *cpp[-2] + 3);
  

  • 首先来看 c p p [ − 2 ] cpp[-2] cpp[2],它等价于 * ( c p p − 2 ) (cpp - 2) (cpp2) c p p cpp cpp - 2 后再解引用(注: c p p cpp cpp 本身值没变),找到 c p [ 0 ] cp[0] cp[0]
  • 接着,再解引用,找到 c [ 3 ] c[3] c[3]
  • c [ 3 ] c[3] c[3] 指向 “FIRST” 字符串的首元素 'F’+3 则指向 ‘S’
  • 答案:ST

  在这里插入图片描述

  

printf("%s\n", cpp[-1][-1] + 1);
  

  • c p p [ − 1 ] [ − 1 ] cpp[-1][-1] cpp[1][1] 等价于 * ( * ( c p p cpp cpp - 1 ) - 1),先来看里面的 *( c p p cpp cpp - 1),将其解引用,找到 c p [ 1 ] cp[1] cp[1]
  • 接着,再看 * ( c p [ 1 ] − 1 ) (cp[1] - 1) (cp[1]1) c p [ 1 ] − 1 cp[1] - 1 cp[1]1,原本指向 c [ 2 ] c[2] c[2]- 1 后指向 c [ 1 ] c[1] c[1]
  • c [ 1 ] c[1] c[1] 指向 “NEW” 字符串的首元素 'N’+1 则指向 ‘E’
  • 答案:EW

  
  在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/598389.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第7章 数据安全

思维导图 7.1 引言 数据安全包括安全策略和过程的规划、建立与执行,为数据和信息资产提供正确的身份验证、授权、访问和审计。虽然数据安全的详细情况(如哪些数据需要保护)因行业和国家有所不同,但是数据安全实践的目标是相同的,即根据隐私和…

Azkaban集群模式部署详细教程

序言 Azkaban是一个用于工作流程调度和任务调度的开源工具,它可以帮助用户轻松地管理和监控复杂的工作流程。Azkaban的架构设计旨在提供高度可扩展性和可靠性,同时保持易用性和灵活性。 Azkaban的架构可以分为三个主要组件:Executor、Web Server和db数据…

ES11 学习

文章目录 1. Promise.allSettled2. Module 新增2.1 ! 动态导入 import()2.2 import.meta2.3 export * as obj from module 3. 字符串 matchAll()4. BigInt实际开发相关使用 5. globalThis6. 空值合并运算符7. 可选链操作符 1. Promise.allSettled Promise.allSettled() 返回一个…

mac如何检测移动硬盘 mac硬盘检测工具 Tuxera怎么用 Tuxera NTFS官网

在工作学习中,我们都绕不开用移动硬盘来拷贝存储一些文件。但是在使用过程中,我们经常遇到“mac检测不到移动硬盘”“移动硬盘不存在”等问题,今天本文就带大家了解下mac如何检测移动硬盘,mac硬盘检测工具。 一、mac如何检测移动…

Linux:五种IO模型的基本认识

文章目录 IO的本质五种IO模型异步和同步 阻塞IO非阻塞IO信号驱动IO IO的本质 在之前的内容中已经结束了对于网络和操作系统的学习,那么回过来再继续看IO,什么是IO呢? 对于网络的学习当中,实际上也是一种IO,数据从计算…

micropython控制esp32上的LED闪烁

ESP32电路图 部分PCB原理图: https://doc.itprojects.cn/0006.zhishi.esp32/01.download/esp32_Schematic%20Prints.pdf GPIO2输出高低电平,控制LED。下面是控制LED灯闪烁的代码。 import machine import time pin2 machine.Pin(2, machine.Pin.OUT) while True:…

阿里云ICP备案工信部短信核验详细流程,1分钟搞定教程!

网站ICP备案或APP备案通过云厂商的备案初审后,需要在工信部完成短信核验操作,本文云服务器吧yunfuwuqiba.com以阿里云备案为例,来详细说明工信部短信核验详细流程,非常简单: 阿里云备案提交到阿里云初审,初…

利用甘特图实现精细化项目管控

在项目管理中,通过精细化管控,项目经理能够有效规划、监督和协调各项任务,从而最大限度控制风险,优化资源配置,并确保按时、按质、按量完成项目目标。而在众多项目管理工具中,甘特图无疑是实现精细化项目管控的利器。zz-plan 是一个非常好用的在线甘特图制作工具,一…

【研发管理】产品经理知识体系-数字化战略

导读: 数字化战略对于企业的长期发展具有重要意义。实施数字化战略需要企业从多个方面进行数字化转型和优化,以提高效率和创新能力,并实现长期竞争力和增长。 目录 1、定义 2、数字化战略必要性 3、数字战略框架 4、数字化转型对产品和服务设计的影响…

vitepress系列-04-规整sideBar左侧菜单导航

规整左侧菜单导航 新建navConfig.ts 文件用来管理左侧导航菜单: 将于其他的配置分开,避免config.mts太大 在config目录下,新建 sidebarModules文件目录用来左侧导航菜单 按模块进行分类: 在config下新建sidebarConfig.ts文件&…

k8s1(1),Linux运维基础开发与实践

#设置主机名 hostnamectl hostnameXXX #配置免密(包括操作机) ssh-keygen ssh-copy-id master*/slave* #传输hosts cat > /etc/hosts <<EOF 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain loca…

LeetCode-131. 分割回文串【字符串 动态规划 回溯】

LeetCode-131. 分割回文串【字符串 动态规划 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯&#xff0c; 回溯三部曲解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个…