指针深刻理解

指针深刻理解

看完鹏哥讲的c语言进阶视频后,又找来C语言深度剖析这本书仔细看了一遍,来进一步巩固和理解指针这个重点。

1:数组

img

如上图所示,当我们定义一个数组 a 时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为 a。名字 a 一旦与这块内存匹配就不能被改变。a[0],a[1]等为 a 的元素,但并非元素的名字。

这里需要注意的是:

1 sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。

  1. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。
A)char *p = “abcdef”;
B)char a[] =123456;

例子A中若是想访问字符串中的e的话,有两种方法,

(1)*(p+4)这是这种方法,因为p中存放着这块内存首地址,加上偏移量4,即可访问到e。

(2)p[4]:编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出 p 里存储的地址值,然后加上中括号中 4 个元素的偏
移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。

B的话和A同理。

下面继续来看一段代码来深刻理解&a和a的区别是啥:

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

首先上面已经说过了除了sizeof(数组名)和&数组名,其余的数组名都是表示数组首元素的地址。

对指针进行加 1 操作,得到的是下一个元素的地址,而不是原有地址值直接加 1。所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 因此,对上题来说,a 是一个一维数组,数组中有 5 个元素; ptr 是一个 int 型的指针。

&a + 1: 取数组 a 的首地址,该地址的值加上 sizeof(a) 的值,即 &a + 5sizeof(int),也就是下一个数组的首地址,当前指针已经越过了数组的界限。

ptr这个指针变量存放的是下一个数组的首地址,ptr也就是指向a[5],ptr-1也就是指向a[4],

*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是 a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即 a[1]的首地址,&a+1 是下一个数组的首地址。所以输出 2

在这里插入图片描述

2:c指针数组与数组指针区别和理解

指针数组和数组指针从基本的汉字表面上去理解,会发现指针数组的主语是数组,数组指针的主语是指针,所以很明显的第一个区别就是指针数组是数组,用来存放指针。

在这里插入图片描述

下面接着看一段代码:

#include <stdio.h>int main()
{char a[5] = { 'A','B','C','D' };char(*p3)[5] = &a;char(*p4)[5] = a;return 0;
}

在这里插入图片描述

因为char(*p3)[5] = &a ;char(*p4)[5] = a;定义的都是数组指针,指向的数组大小为char[5]类型,虽然&a和a的值是一样的,但是其表示的意义是不一样的,这里一样的原因理解是,但由于&a 和 a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。

对上述代码进行更改,将数组指针指向的数组大小发生更改。

int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
return 0;
}

运行结果也好理解:p3+1 和 p4+1 的值就相当于跳过sizeof(char)*(数组指针所指向数组的大小,要是数组大小为3,这里就是3)

在这里插入图片描述

1:指针数组详解

其中指针数组的一个应用是可以用一维数组模拟二维数组。

#include <stdio.h>int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9 };int arr2[] = { 7,8,9,6,5,4,1,2,3 };int arr3[] = { 7,8,9,5,2,1,0,4,6 };int* arr[] = { arr1,arr2,arr3 };//arr中每个元素都为一个指针,指针指向对应的数组;每个元素为对应数组的首地址int i;for (i = 0; i < 3; i++){int j;for (j = 0; j < 9; j++){printf("%d", *(arr[i] + j));//实现对应元素的遍历;//printf("%d", arr[i][j]);//实现对应元素的遍历}printf("\n");}return 0;
}

两种不同的方式来访问并打印数组元素:

  1. 使用指针运算和解引用操作符 *

    printf("%d", *(arr[i] + j));
    

    这里,arr[i] 获取指针数组 arr 的第 i 个元素,即指向 arr1arr2arr3 的指针之一。arr[i] + j 计算出指向当前数组第 j 个元素的指针,而 *(arr[i] + j) 解引用该指针,获取该元素的值。

  2. 使用数组下标访问:

    printf("%d", arr[i][j]);
    

    这种方式更直观。arr[i] 获取指向某个数组的指针,而 arr[i][j] 直接访问该数组的第 j 个元素。这是因为 arr[i] 被视为指向第 i 个数组的首元素的指针,而 arr[i][j] 就是访问该指针所指向数组的第 j 个元素。

第二种写法的原因如下:在C语言中,arr[i][j] 这种双重下标访问方式背后的原理与指针算术紧密相关。当你有一个指向指针的指针(或者数组的数组,或者指针数组,如本例所示),这种双重下标访问实际上是两步操作的简写:

  1. arr[i] 访问第 i 个元素,这里的元素是指向 int 的指针(在你的例子中,它指向 arr1arr2arr3)。
  2. [j] 对该指针进行解引用,访问它所指向数组的第 j 个元素。
1.1理解指针的指针和数组的数组

在C语言中,当你声明一个如 int* arr[] 的数组时,你创建了一个数组,其每个元素都能存储一个指向 int 类型的指针。所以,arr 是一个数组,但每个 arr 的元素(比如 arr[0]arr[1]arr[2] 等)是一个指针,指向一个 int 数组。

1.2双重下标访问的工作原理

当你使用 arr[i][j] 进行访问时,arr[i] 首先得到第 i 个数组的首地址(一个指针)。然后,[j] 操作符被应用到这个地址上,它实际上是通过指针算术来完成的:从这个地址开始,移动 jint 的大小的距离,来访问特定的元素。这是因为在C语言中,数组名或指针被用作数组时,pointer[offset] 等价于 *(pointer + offset)

1.3为什么这样做是有效的

这种访问方式之所以有效,是因为C语言设计时就考虑到了这种使用场景。数组和指针在很多情况下是可以互换的。当你声明一个像 int *arr[] 这样的指针数组时,C语言允许你通过像访问二维数组那样的语法来访问它,即使它实际上是一个数组的数组或指针的数组。这种设计极大地简化了对于复杂数据结构的操作,同时保持了代码的可读性和易于理解。

简而言之,arr[i][j] 能够直接访问指针所指向数组的第 j 个元素,是因为C语言的设计允许通过指针算术和解引用操作符的组合来简化这种操作,使得指针数组的操作既直观又高效。

3:地址的强制转换

struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;

假设 p 的值为 0x100000。 如下表表达式的值分别为多少?

p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?

在C语言中,当你对一个指针进行算术操作时,如 p + 1,这个操作会根据指针指向的数据类型的大小来移动指针。这意味着,如果 p 是一个指向 struct Test 的指针,那么 p + 1 不是简单地将 p 的值增加 1,而是增加 sizeof(struct Test) 的大小,这样 p + 1 就指向了内存中紧接着 p 指向的 struct Test 实例之后的下一个 struct Test 实例的起始位置。

给定的结构体 Test 包含以下成员:

  • 一个 int 类型的 Num,通常是4个字节。
  • 一个 char * 类型的 pcName,指针大小通常是4个字节(在32位架构上)或8个字节(在64位架构上)。
  • 一个 short 类型的 sDate,通常是2个字节。
  • 一个 char 类型的数组 cha[2],总共2个字节。
  • 一个 short 类型的数组 sBa[4],总共8个字节。

根据这些信息,我们可以计算 struct Test 的大小。然而,实际的结构体大小还需要考虑到内存对齐(padding)。内存对齐是编译器自动进行的,以确保结构体中的每个成员都按照其自然对齐要求放置在内存中,这可能会导致结构体的实际大小比简单加起来的成员大小要大。,假定 struct Test 的总大小是20字节。这个大小已经考虑了可能的内存对齐。因此,当你做 p + 1 操作时,指针会按照结构体的实际大小(20字节)进行移动。

如果 p 的初始值是 0x100000,那么 p + 1 的计算将是:

0x100000 + sizeof(struct Test) * 1

如果 sizeof(struct Test) 是20字节,那么:

p + 1 = 0x100000 + 20 = 0x100014
(unsigned long)p + 0x1 = 0x100001
(unsigned int*)p + 0x1 = 0x100004

unsigned long将p强制类型转为无符号长整型,数值不发生改变,但是所属类型已经发生了改变。

unsigned int*一个指向无符号整型的指针,所以为4个字节。

4:二维数组

实际上内存不是表状的,而是线性的。见过尺子吧?尺子和我们的内存非常相似。一般尺子上最小刻度为毫米,而内存的最小单位为 1 个 byte。平时我们说 32 毫米,是指以零开始偏移 32 毫米;平时我们说内存地址为 0x0000FF00 也是指从内存零地址开始偏移0x0000FF00 个 byte。既然内存是线性的,那二维数组在内存里面肯定也是线性存储的。实际上其内存布局如下图:

char a[3][4];

在这里插入图片描述

以数组下标的方式来访问其中的某个元素:a[i][j]。编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个数组。a[3]这个一维数组的三个元素分别为:a[0],a[1],a[2]。a[i][j]的首地址为&a[i]+j*sizof(char) 写为指针的形式为:a+i*sizeof(char)*4+j*sizeof(char) ,可以换算成以指针的形式表示:*(*(a+i)+j)。解释 *(a+i) 解引用得到第 i 行的首地址这一点,可能会有些混淆。对于二维数组 char a[3][4];a 本身可以被视为指向其第一个元素(即第一行 a[0])的指针。这里的每个元素(每一行)本身是一个 char[4] 类型的数组。因此,a 的类型是 char (*)[4],即指向一个含有 4 个字符的数组的指针。当我们说 *(a+i) 时,这里的操作实际上是在进行两个步骤:

  1. 指针算术运算a+i 计算的是第 i 行的首地址。由于 a 是指向第一行的指针,a+i 实际上是利用指针算术来计算第 i 行的起始地址。这里的 i 乘以的是每行的大小(在本例中是 4 个 char),这是自动完成的,不需要显式乘以 sizeof(char) 或行的大小。这一步并没有解引用,只是计算地址。
  2. 解引用*(a+i) 的操作是对计算得到的地址进行解引用。但是,这里的“解引用”可能会造成一些理解上的混淆。在这个上下文中,我们不是在获取一个 char 值,而是获取指向第 i 行首元素的指针。这是因为 a+i 已经是一个指向 char[4] 的指针,解引用这个指针实际上给出的是 char[4] 类型的数组的第一个元素的地址,这与直接使用 a[i] 是等价的。
  3. 因此,当我们说“解引用得到第 i 行的首地址”时,我们实际上是在描述获取到这一行数组的起始点的过程。在 C 语言中,数组名(在这个例子中是 a[i])被用作表达式时,会被转换(除了 sizeof& 操作符的情况)为指向其第一个元素的指针。所以,*(a+i)a[i] 在这里是等价的,都表示第 i 行的首地址。

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

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

相关文章

身份证识别系统(安卓)

设计内容与要求&#xff1a; 通过手机摄像头捕获身份证信息&#xff0c;将身份证上的姓名、性别、出生年月、身份证号码保存在数据库中。1&#xff09;所开发Apps软件至少需由3-5个以上功能性界面组成。要求&#xff1a;界面美观整洁、方便应用&#xff1b;可以使用Android原生…

JS 对象数组排序方法测试

输出 一.Array.prototype.sort() 1.默认排序 sort() sort() 方法就地对数组的元素进行排序&#xff0c;并返回对相同数组的引用。默认排序是将元素转换为字符串&#xff0c;然后按照它们的 UTF-16 码元值升序排序。 由于它取决于具体实现&#xff0c;因此无法保证排序的时…

数据可视化基础与应用-02-基于powerbi实现医院数据集的指标体系的仪表盘制作

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍基于powerbi实现医院数据集的指标体系的仪表盘制作。 数据集描述 医生数据集doctor 医生编号是唯一的&#xff0c;名称会存在重复 医疗项目数据projects 病例编号是唯一的&#xff0c;注意这个日期编号不是真…

面试时如何回答接口测试怎么进行

一、什么是接口测试 接口测试顾名思义就是对测试系统组件间接口的一种测试&#xff0c;接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 …

【C++ 07】string 类的常用接口介绍

文章目录 &#x1f308; Ⅰ string 类对象的常见构造函数&#x1f308; Ⅱ string 类对象的容量相关操作&#x1f308; Ⅲ string 类对象的访问及遍历1. 下标访问及遍历2. 正向迭代器访问3. 反向迭代器访问 &#x1f308; Ⅳ string 类对象的修改操作1. 插入字符或字符串2. 字符…

Vue前端的工作需求

加油&#xff0c;新时代打工人&#xff01; 需求&#xff1a; 实现带树形结构的表格&#xff0c;父数据显示新增下级&#xff0c;和父子都显示编辑。 技术&#xff1a; Vue3 Element Plus <template><div><el-table:data"tableData"style"width…

了解游戏中的数据同步

目录 数据同步 通过比较来看状态同步和帧同步 状态同步 帧同步 帧同步实现需要的条件 两者相比较 数据同步 在联机游戏中&#xff0c;我的操作和数据要同步给同一局游戏中其他所有玩家&#xff0c;其他玩家的操作和数据也会同步给我。这叫做数据同步&#xff0c;目前数据…

国产数据库概述

这是ren_dong的第33篇原创 1、什么是数据库&#xff1f; 1.1、基本概念 定义&#xff1a;数据库是 按照一定的数据结构组织、存储和管理数据的仓库。可视为电子化的文件柜&#xff0c;用户可以对文件中的数据进行新增、查询、更新、删除等操作。 作用&#xff1a;业务数据 存储…

kettle下载及安装

JDK下载 安装kettle之前需要安装JDK JDK下载链接&#xff1a;JDK下载 配置环境变量&#xff1a; 新建系统变量&#xff1a;变量值为JDK安装路径 Path新增&#xff1a; kettle下载 链接地址&#xff1a;PDI&#xff08;kettle&#xff09; 点击下载 同意 Click here to a…

【XIAO ESP32S3 sense 通过 ESPHome 与 Home Assistant 连接】

XIAO ESP32S3 sense 通过 ESPHome 与 Home Assistant 连接 1. 什么是 ESPHome 和 Home Assistant&#xff1f;2. 软件准备3. 开始4. 将 Grove 模块与 ESPHome 和 Home Assistant 连接5. Grove 连接和数据传输6. Grove -智能空气质量传感器 &#xff08;SGP41&#xff09;7. OV2…

Filter(过滤器)

文章目录 过滤器的编写&#xff1a;过滤器 APIFilterFilterConfigFilterChain 生命周期过滤器核心方法的细节多个过滤器执行顺序<br /> 过滤器——Filter&#xff0c;它是JavaWeb三大组件之一。另外两个是Servlet和Listener。 它是在2000年发布的Servlet2.3规范中加入的一…

百度文库旋转验证码识别

最近研究了一下图像识别&#xff0c;一直找到很好的应用场景&#xff0c;今天我就发现可以用百度的旋转验证码来做一个实验。没想到效果还挺好&#xff0c;下面就是实际的识别效果。 1、效果演示 2、如何识别 2.1准备数据集 首先需要使用爬虫&#xff0c;对验证码图片进行采…

区块链媒体发布推广10个热门案例解析-华媒舍

区块链技术的发展已经引起了媒体的广泛关注&#xff0c;越来越多的区块链媒体纷纷发布推广相关的热门案例。本文将介绍10个成功的区块链媒体推广案例&#xff0c;并分享它们的成功秘诀&#xff0c;帮助读者更好地了解区块链媒体推广的方法与技巧。 随着区块链技术的成熟和应用场…

第二证券:富时罗素扩容 A股引入国际增量资金

日前&#xff0c;英国富时罗素指数公司&#xff08;FTSE Russell&#xff0c;简称“富时罗素”&#xff09;公布的全球股票指数&#xff08;FTSE Global Equity Index Series&#xff09;半年度指数检查陈述显现&#xff0c;将新调入A股76只、调出1只。此前&#xff0c;富时罗素…

【LeetCode】升级打怪之路 Day 12:单调队列

今日题目&#xff1a; 239. 滑动窗口最大值 | LeetCode 今天学习了单调队列这种特殊的数据结构&#xff0c;思路很新颖&#xff0c;值得学习。 Problem&#xff1a;单调队列 【必会】 与单调栈类似&#xff0c;单调队列也是一种特殊的数据结构&#xff0c;它相比与普通的 que…

Get Your Back Covered! Coverage, CodeCov和Tox

1. Coverage - 衡量测试的覆盖率 我们已经掌握了如何进行单元测试。接下来,一个很自然的问题浮现出来,我们如何知道单元测试的质量呢?这就提出了测试覆盖率的概念。覆盖率测量通常用于衡量测试的有效性。它可以显示您的代码的哪些部分已被测试过,哪些没有。 coverage.py …

智慧公厕:打造智慧城市的环卫明珠

在城市建设中&#xff0c;公共卫生设施的完善和智能化一直是重要环节。而智慧公厕作为智慧城市建设的重要组成部分&#xff0c;发挥着不可替代的作用。本文以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案例现场实景实图&#xff0c;解读智慧公厕如何助力打…

【数据结构】B树

1 B树介绍 B树&#xff08;英语&#xff1a;B-tree&#xff09;&#xff0c;是一种在计算机科学自平衡的树&#xff0c;能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作&#xff0c;都在对数时间内完成。B树&#xff0c;概括来说是一个一般化的…

MySQL高可用性攻略:快速搭建MySQL主从复制集群 !

MySQL高可用性攻略&#xff1a;快速搭建MySQL主从复制集群 &#xff01; MySQL基础知识&#xff1a;介绍MySQL数据库的基本概念和常用命令&#xff0c;如何创建数据库、表、用户和权限管理等。 MySQL安装教程&#xff1a;Centos7 安装MySQL5.7.29详细安装手册 MySQL数据类型&…

【大厂AI课学习笔记NO.63】模型的维护

说是模型的维护&#xff0c;其实这堂课都是在讲“在工业环境中开发和部署机器学习模型的流程”。 上图来自于我的笔记思维脑图&#xff0c;已经上传&#xff0c;要链接的访问的主页查看资源。 一路走来&#xff0c;我们学习了数据管理、模型学习、模型验证、模型部署等重要的步…