第11天理解指针

目录

数组指针

指针数组

字符指针数组

二维字符数组

const 修饰变量为常量

指针常量

常量指针

typedef 重命名数据类型

关于typedef的一般理解

具体示例分析

复杂示例分析

总结回顾--指针的奥秘:深入理解内存地址与数据存储

一、指针的本质

二、地址类型转换的意义


数组指针

整型指针        指针指向整型空间

int *p;

数组指针        指针指向数组空间

int (*p)[3]; //本质上是指针


下面是几个例子需要理解 

int a[5]; // a==&a[0] 数组名a是首元素a[0]的地址

int *p=a;// p=a

int *p=&a[0];//int *p = a;int *p = &a[0];这两种写法是等价的,都是让指针 p 指向数组 a 的首元素。此时,a[0]p[0]是等价的,都表示访问数组的第一个元素。

*(a+1)== *(p+1)//*(a + 1)*(p + 1)也是等价的,都表示访问数组的第二个元素。因为 a 在表达式中会被转换为指针,a + 1指向数组的第二个元素,解引用后得到第二个元素的值;同理,p + 1也指向数组的第二个元素,解引用后得到相同的值。

p=a+1; // p=&a[1]此时 p 指向数组 a 的第二个元素


e.g.

int a[5];//定义了一个指向包含 5 个整数的数组

int (*p)[5]=&a; // p=&a,即p指向整个数组

a[0]==(*p)[0]==**p

a[0](*p)[0]**p是等价的。

  1. (*p)表示解引用指针 p,得到一个包含 5 个整数的数组,(*p)[0]表示这个数组的第一个元素
  2.  **p也是先解引用 p得到数组,再解引用这个数组的首元素指针得到第一个元素。

a[4]     ==    (*p)[4]    ==    *((*p)+4)    ==   *((int *)(p+1)-1)    ==   *(*(p+1)-1)   

a[4](*p)[4]*((*p)+4)*((int *)(p+1)-1)*(*(p+1)-1)也是等价的。

  1. (*p)[4]表示解引用 p 指向的数组的第五个元素
  2. *((*p)+4)先解引用 p得到数组,再对这个数组的首元素指针加上 4,然后解引用得到第五个元素
  3. *((int *)(p+1)-1)先将 p + 1转换为指向整数的指针,然后减去 1,再解引用得到数组的最后一个元素
  4. *(*(p+1)-1)先解引用 p + 1得到一个新的数组(这里实际上是越界访问,但从逻辑上理解),再对这个新数组的首元素指针减 1,解引用得到原数组的最后一个元素。

e.g.

int a[2][3];

int *p=&a[0][0]; //将指针 p 指向二维数组 a 的第一个元素a[0],即*a 。

a[1][2]     ==    a[0][5]     ==    *(p+5)

因为二维数组在内存中是按行优先存储的,a[1][2]在内存中的位置相对于 a[0][0]正好偏移了 5 个整数的位置。

int (*p)[3]=&a[0]; // 让 p 指向二维数组 a 的第一行a[0]

a[1][2]     ==    *(*(p+1)+2)     ==    (*(p+1))[2]      ==    *(p[1]+2)     ==    p[1][2]      

   *(p + 1)表示指向二维数组的第二行,*(p + 1)+2表示第二行的第三个元素的指针,解引用得到该元素的值;

(*(p + 1))[2]先解引用 p + 1得到第二行数组,再访问第三列的元素;

*(p[1]+2)p[1][2]也是类似的逻辑。

int (*p)[2][3]=&a;//将 &a赋值给 p,即 p 指向整个二维数组 a

a[1][2]    ==     (*p)[1][2]

(*p)表示解引用指针 p,得到二维数组 a(*p)[1][2]表示这个二维数组的第二行第三列的元素。

学习理解这部分知识,可以:

  1. 画图辅助理解
    • 对于数组和指针的关系,可以画出数组在内存中的布局图。例如,对于一维数组int a[5],可以将其想象成内存中的连续 5 个存储单元,每个单元存储一个int类型的值。当定义int *p = a;时,在图上可以将指针p指向数组a的第一个元素所在的存储单元。这样在考虑p + 1时,就很容易理解它指向了数组中的下一个元素,因为在内存中,p + 1的地址值是p的地址值加上sizeof(int)(假设int类型占 4 个字节,那么p+1的地址比p的地址大 4)。
    • 对于二维数组,比如int a[2][3],可以将其看作是一个有 2 行 3 列的表格。在内存中,它仍然是连续存储的,按照行优先的顺序。如果int *p=&a[0][0];,那么p就像一个游标,从表格的左上角(第一个元素)开始,p + 1就指向表格中的下一个元素。当定义int (*q)[3]=&a[0];时,q可以看作是指向每行(包含 3 个元素的一维数组)的指针,q + 1就指向二维数组的下一行。
  2. 通过代码示例逐步调试
    • 编写简单的测试程序,在程序中输出数组元素的地址和指针的值。直观地看到指针和数组元素地址的变化情况。对于二维数组也可以采用类似的方法,逐步观察指针的移动和元素的访问过程。

从基本概念出发,逐步深入理解

  • 首先要牢记数组名在很多情况下会转换为指向数组首元素的指针这个基本概念。对于指针的算术运算,要理解+1操作对于不同类型指针(如指向int的指针、指向数组的指针等)的实际意义,它是根据指针所指向的类型的大小来移动指针的。然后再深入理解多级指针(如指向数组的指针的指针)的概念,从简单的一维数组指针开始,逐步过渡到二维、三维数组等更复杂的指针应用场景。

指针数组

整型数组 ----数组元素是整型     

指针数组 ----数组元素是指针

int *a[5]; //本质上是数组

a[0] a[1] a[2] 都是指针

int *a[2][3];

a[0] a[1] 都是一维指针数组

a[0][0] a[0][1] a[1][1] 都是指针

字符指针数组

char *a[5]={“123”, “abcdef”, “45789”, “zxcvb”, “000123456”};

char b= ‘A’;

a[0]=&b;

a[1]= “123456”; 字符指针指向首字符地址,不能修改字符串的数据

sizeof(a) == 40

定义了一个字符指针数组 a,它包含五个指针,分别指向五个不同的字符串常量。

接着定义了一个字符变量 b,初始化为 'A'。然后让 a[0]指向 b 的地址,此时 a[0]不再指向字符串,而是指向单个字符 b。这就像把原来指向一个字符串物品堆的标签,改指向了单独的一个字符物品。

之后又让 a[1]指向新的字符串常量 “123456”。由于字符串常量在内存中通常是不可修改的,所以虽然指针可以指向不同的字符串,但不能改变字符串常量本身的内容。就像标签可以指向不同的物品堆,但不能改变物品堆里的物品。

这里的两个赋值语句有一定区别:

  1. 赋值内容不同
    • a[0]=&b;是让a[0]指向一个字符变量b(存储单个字符)。
    • a[1]= “123456”;是让a[1]指向一个字符串常量(包含多个字符和结束符)。
  2. 可修改性不同
    • 通过a[0]可以修改b的值。
    • 不能修改a[1]指向的字符串常量内容,否则可能出错。

最后,sizeof(a)等于 40,因为在这个环境下指针可能占用 8 个字节,而数组 a 有五个指针元素,所以总共占用 40 个字节。就像一个盒子里装了五个特定大小的东西,通过测量盒子大小可以知道这些东西总共占用的空间。

 

二维字符数组

定义一个二维字符数组

char a[3][80]={ “123”, “abcdef”, “45789”};

其中a[0] 最多存储 80 个字符

a[1][0]= ‘h’; //数组是变量的集合,可以更改数据

总长度sizeof(a) == 240

const 修饰变量为常量

const int a=5; // a 是只读变量,const修饰谁,谁就是常量

a=6; // 不合法,因为a被声明为const,不能被赋值修改。

int *p=&a;//用一个指向int类型的指针p指向这个const修饰的变量a的地址。

*p = 6; // 合法,通过指针去修改这个地址上的值

指针常量

数字常量         1         值不能改变

指针常量         int * const p;         p 被 const 修饰,所以 p 的值不能改变

  • int a=5,b=10;
  • int * const p=&a;//定义了一个常量指针p。这里的 “常量指针” 是指指针本身的值(即它所指向的地址)不能被改变,但它所指向的内容可以被改变。
  • *p=6; // 合法,虽然p不能指向其他地址,但它所指向的内容是可以修改的。这里通过*p修改了p所指向的变量(即a)的值,将其从5改为6
  • p=&b; // 不合法,初始化时让p指向了变量a的地址,之后就不能再让p指向其他变量的地址了

常量指针

整型指针         int *p; 指针指向整型

常量指针         char const *p; 指针指向常量         const 修饰*p,*p 不能改变

                        const char *p;

  • char a=57,b=32;
  • char const *p=&a;

//定义了一个指向常量字符的指针p,并初始化为指向a。这意味着不能通过这个指针来修改它所指向的内容。

  • *p=97; // 不合法

//不能通过这个指针来修改p所指向的内容。

  • p=&b; // 合法

//不能通过p修改它所指向的内容,但是可以改变指针p本身的值,让它指向其他的地址

  • *p=97; // 不合法

//虽然,现在p指向b,但仍不能通过这个指针来修改p所指向的内容。

  • printf(“%d\n”,*p); // 合法

//只是读取p所指向的内容并输出其对应的整数值,没有尝试修改它所指向的内容

const int *const p; //p 和*p 都是只读

const int *const p,这里有两个const关键字。第一个const修饰int,表示指针所指向的内容是常量。第二个const修饰指针p本身,表示指针p是一个常量,它存储的地址值不能被改变。

  1. 指针指向的内容只读(*p
    • 另一个const表示指针指向的变量的值不能被修改。即使p指向了某个变量,也不能通过p来改变这个变量的值。 
  2. 指针本身只读(p
    • const修饰指针p,就像把指针 “钉” 在了一个地址上,不能再让它指向别的地方。比如,不能重新给p赋值一个新的变量地址。

typedef 重命名数据类型

关于typedef的一般理解

typedef A B; // A 代表数据类型 B 代表新名称

A m;

B n; //m 和 n 数据类型相同

具体示例分析

typedef int * a;//定义了a作为int*类型的别名

a n; // 等同于int* n;,即定义了一个指向整数的指针n

int a,*b; // int a; int *b;声明了一个整数变量a和一个指向整数的指针变量b

int *a,b; // int *a; int b;声明了一个指向整数的指针变量a和一个整数变量b

typedef int * INT;//定义了一个别名INT代表int*类型。

INT a,b; // INT a; INT b; === int *a; int* b;//即声明了两个指向整数的指针变量ab

复杂示例分析

typedef int*(*F[2])[3];

F m; // 等同于int*(*m[2])[3];,即定义了一个变量m

sizeof(F) == 16

这定义了一个较为复杂的类型别名。F是一个数组类型,这个数组有 2 个元素,每个元素是一个指针,这个指针指向一个包含 3 个整数指针的数组。

从逐步分析这个类型定义:

分析F的长度

  • 先看FF被定义为一个数组类型,名为F的数组有 2 个元素,即F[2]
  • 接着看每个元素的类型,每个元素是一个指针,所以是(*F[2]),指针指向的对象是一个包含 3 个元素的数组。
  • 最后看这个包含 3 个元素的数组的元素类型,这个数组的元素是指向整数的指针,即int*,所以完整的类型就是int*(*F[2])[3],表示F是一个包含 2 个元素的数组,每个元素是一个指针,指向一个包含 3 个指向整数指针的数组。
  • 首先看 int*(*)[3]这个类型:

    • 它表示一个指针,这个指针指向一个包含 3 个指向整数指针(int*)的数组。
    • 在假设指针大小为 8 字节的系统中,这个指针本身占用 8 字节的空间。
  • 接着看 F的类型:

    • F被定义为 int*(*F[2])[3],这意味着 F是一个包含 2 个元素的数组。
    • 每个元素的类型是前面提到的指向包含 3 个指向整数指针的数组的指针。
    • 所以 sizeof(F)就等于 2 倍的这种指针的大小,即 2 * sizeof(int*(*)[3])

所以 sizeof(F) = 2 * sizeof(int*(*)[3]) = 2 * 8 = 16字节。

总结回顾--指针的奥秘:深入理解内存地址与数据存储

在 C 和 C++ 等编程语言中,指针是一个强大而又复杂的概念。它就像是一把钥匙,能够打开内存世界的大门,让我们直接访问和操作计算机内存中的数据。

一、指针的本质

指针的核心作用是保存地址。在计算机内存中,每个数据都存储在特定的空间位置,而这个位置可以用地址来表示。不同类型的指针保存不同类型数据的地址,这意味着指针的类型与它所指向的地址的类型紧密相关。例如,一个指向整数的指针只能保存整数变量的地址,而不能保存其他类型变量的地址。

在定义指针时,我们先确定指针指向的空间变量的类型,然后在变量名前面加上*来表示这是一个指针。例如,int *ptr表示ptr是一个指向整数的指针。当我们使用*解引用指针时,得到的是指针所指向的地址对应的空间变量

此外,指针的大小通常是固定的,并且与计算机的位数有关。在 32 位系统中,指针通常占用 4 个字节;而在 64 位系统中,指针通常占用 8 个字节。

二、地址类型转换的意义

地址类型转换主要是在转换地址所代表的空间大小。这是因为不同类型的数据在内存中占用的空间大小可能不同。例如,一个整数可能占用 4 个字节,而一个浮点数可能占用 8 个字节。当我们进行地址类型转换时,实际上是在告诉编译器如何解释特定地址上存储的数据。

让我们来看一个具体的例子,在地址 0x8000010002 的位置存储一个浮点型数据

float *p=(float *)0x8000010002;

*p=3.14;

(long)p+2 == 0x8000010004

首先,将地址 0x8000010002 强制转换为 float * 类型指针并赋值给 p,使 p 指向该地址的浮点型数据。接着,用 *p 将 3.14 存储到这个地址。然后看 (long)p + 2,由于指针大小与计算机位数有关,假设系统中指针占 8 字节。将 p 强制转换为 long 类型,是把地址值转为长整型数。而 (long)p + 2 是将地址值加 2 字节,因 long 占 8 字节、float 占 4 字节,加 2 字节相当于指向下一个浮点数地址,此时 (long)p + 2 的值为 0x8000010004。 

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

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

相关文章

蓝桥杯题目理解

1. 一维差分 1.1. 小蓝的操作 1.1.1. 题目解析: 这道题提到了对于“区间”进行操作,而差分数列就是对于区间进行操作的好方法。 观察差分数列: 给定数列:1 3 5 2 7 1 差分数列:1 2 2 -3 5 6 题目要求把原数组全部…

基于SpringBoot的高校体测管理系统设计与实现(源码+定制+开发)高校体测记录系统设计、高校体测信息管理平台、智能体测管理系统开发、高校体测记录系统设计

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

25届电信保研经验贴(自动化所)

个人背景 学校:中九 专业:电子信息工程 加权:92.89 绩点:3.91/4.0 rank:前五学期rank2/95,综合排名rank1(前六学期和综合排名出的晚,实际上只用到了前五学期) 科研…

海外云手机实现高效的海外社交媒体营销

随着全球化的深入发展,越来越多的中国企业走向国际市场,尤其是B2B外贸企业,海外社交媒体营销已成为其扩大市场的重要手段。在复杂多变的海外市场环境中,如何有效提高营销效率并降低运营风险,成为了众多企业的首要任务。…

路由器 相关知识

一、路由器是什么 参考:图解系列--路由器和它庞大的功能_路由功能-CSDN博客 路由器是指:主要负责 OSI参考模型中网络层的处理工作,并根据路由表信息在不同的网络 之间转发IP 分组的网络硬件(图3-1)。这里的网络一般是指IP 子网,…

Java基础(7)图书管理系统

目录 1.前言 2.正文 2.1思路 2.2Book包 2.3people包 2.4operation包 2.5主函数 3.小结 1.前言 哈喽大家好吖,今天来给前面Java基础的学习来一个基础的实战,做一个简单的图书管理系统,这里边综合利用了我们之前学习到的类和对象&…

爬虫ip技术未来发展趋势

各位朋友,大家好!有伙伴问爬虫技术未来会有更好的发展么,那今天小蝌蚪来跟大家聊聊爬虫技术未来的发展趋势分享一下行业咨询。 大家在日常工作和生活中,都希望事情能更省心、高效吧?未来的爬虫技术就朝着这个方向发展…

sheng的学习笔记-AI基础-正确率/召回率/F1指标/ROC曲线

AI目录:sheng的学习笔记-AI目录-CSDN博客 分类准确度问题 假设有一个癌症预测系统,输入体检信息,可以判断是否有癌症。如果癌症产生的概率只有0.1%,那么系统预测所有人都是健康,即可达到99.9%的准确率。 但显然这样的…

在Keil调试内存中的程序

在Keil调试内存中的程序 目录 在Keil调试内存中的程序1. 问题引出2. 测试工程3. 工程和Keil配置 实验环境: MCU:STM32F103C8T6 (Flash 64K RAM 20K)Keil:uVision V5.27.0.0仿真器:ST-Link 参考源码:https://download.c…

Redis 集群 总结

前言 相关系列 《Redis & 目录》(持续更新)《Redis & 集群 & 源码》(学习过程/多有漏误/仅作参考/不再更新)《Redis & 集群 & 总结》(学习总结/最新最准/持续更新)《Redis & 集群…

导出问题处理

问题描述 测试出来一个问题,使用地市的角色,导出数据然后超过了20w的数据,提示报错,我还以为是偶然的问题,然后是发现是普遍的问题,本地环境复现了,然后是,这个功能是三套角色&…

ESP32-S3学习笔记:常用的ESP-IDF命令总结

参考资料:1.esptool.py工具 2.idf.py工具 后续文章的讲解需要用到IDF命令行工具,当前文章简单介绍一下。 目录 打开命令行的小技巧 一、读flash信息 二、擦除flash 三、读flash数据 四、写flash数据 打开命令行的小技巧 大家安装完IDF开发包后…

React类组件详解

React类组件是通过创建class继承React.Component来创建的,是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释: 一、定义与基本结构 类组件使用ES6的class语法定义,并继承自React.Component。它们具有更复杂的功能&#xf…

腾讯云 COS 多 AZ 存储保证服务高可用性

腾讯云 COS 的多 AZ 存储架构能够为用户数据提供数据中心级别的容灾能力。多 AZ 存储将客户数据分散存储在城市中多个不同的数据中心,当某个数据中心因为自然灾害、断电等极端情况导致整体故障时,多 AZ 存储架构依然可以为客户提供稳定可靠的存储服务。 …

表格编辑demo

<el-form :model"form" :rules"status ? rules : {}" ref"form" class"form-container" :inline"true"><el-table :data"tableData"><el-table-column label"计算公式"><templat…

ArcGIS001:ArcGIS10.2安装教程

摘要&#xff1a;本文详细介绍arcgis10.2的安装、破解、汉化过程。 一、软件下载 安装包链接&#xff1a;https://pan.baidu.com/s/1T3UJ7t_ELZ73TH2wGOcfpg?pwd08zk 提取码&#xff1a;08zk 二、安装NET Framework 3.5 双击打开控制面板&#xff0c;点击【卸载程序】&…

05方差分析续

文章目录 1.Three way ANOVA2.Latin square design2.Hierarchical (nested) ANOVA3.Split-plot ANOVA4.Repeated measures ANOVA5.Mixed effect models 1.Three way ANOVA 三因素相关分析 单因子分析的代码 data(mtcars) nrow(mtcars) # 32 mtcars$cyl as.factor(mtcars$cyl…

c#子控件拖动父控件方法及父控件限在窗体内拖动

一、效果 拖放位置不超过窗体四边,超出后自动靠边停靠支持多子控件拖动指定控件拖放(含父控件或窗体)点击左上角logo弹出消息窗口(默认位置右下角)1.1 效果展示 1.2 关于MQTTnet(最新版v4.3.7.1207)实现在线客服功能,见下篇博文 https://github.com/dotnet/MQTTnet 网上…

BIO,NIO,直接内存,零拷贝

前置知识 什么是Socket&#xff1f; Socket是应用层与TCP/IP协议族通信的中间软件抽象层&#xff0c;它是一组接口&#xff0c;一般由操作系统提供。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议处理和通信缓存管理等等都隐藏在Sock…

莱维飞行(Levy Flight)机制的介绍和MATLAB例程

文章目录 莱维飞行机制算法简介自然现象中的应用优化问题中的应用关键公式 MATLAB代码示例代码说明运行结果 莱维飞行机制算法的应用前景1. 自然科学中的应用2. 计算机科学中的应用3. 工程技术中的应用4. 金融与经济学中的应用5. 医疗与生物信息学中的应用6. 未来研究方向 结论…