C++ 语法之数组指针

news2025/3/22 18:05:58

一维数组:

如果我们定义了一个一维数组,那么这个数组名,就是指向第一个数组元素的地址,也即,是整个数组分配的内存空间的首地址。

比如 int a[3]; 定义了一个包含三个元素的数组。因为一个int占4个字节,那么系统就会为这个数组分配12个字节的内存空间,用来存储这个数组。

可以运行下面代码来观察每个元素的地址:

int main() {
    int a[3];
    a[0] = 1;
    a[1] = 20;
    a[2] = 33;

    cout << "a的地址:" << a << "\n";
    for (int i = 0; i < 3; i++)
        cout << &a[i] << "\n";

}

 运行结果:

可以看到内存的首地址是FAA8开始,注意这里的a和a[0]的地址是相等,因为它们都是代表数组的第一个地址。

然后依次加4个字节,FAAC,FAB0。

那么因此得出结论,数组名元素值每加1,它就会访问到下个四字节的地址(注意这里是根据类型的)。

那么a[3]就应该是FAB4,注意这里数组定义三个,下标是从0开始的,所以到2就结束了。

但我们依然可以通过a[3]访问到FAB4,只不过不是合法的,你不能写入,因为这不是属于你的内存。

如下例子:

#include <iostream>

using namespace std;

int main() {
    int a[3];
    a[0] = 1;
    a[1] = 20;
    a[2] = 33;

    cout << "a的地址:" << a << "\n";
    for (int i = 0; i < 3; i++)
        cout << &a[i] << "\n";

    cout << "a[3]的地址:" << &a[3] << "-------a[3]的值:" << a[3]; //访问a[3]的值,int类型4个字节


}

结果: (注意访问a[3]是不合法的,只为示例使用)

 

可以看到a[3]的地址,是在a[2]的基础上加了个四个字节,这就是数组的规则。其实可以看作是指针的一种运用。

所以一维数组,我们可以像定义int 变量指针来定义数组指针,如下:

#include <iostream>

using namespace std;

int main() {
    int a[3];
    a[0] = 1;
    a[1] = 20;
    a[2] = 33;

    int* p = a;
    cout << *p << "\n" << p[1] << "\n" << *(p + 2);

}

输出值: 

这里要特别说是*(p+2)这个访问,因为指针变量不像正常变量,比如int a;

a+1的话,它就会在a的值加1, 指针变量+1的意思,是在原来的地址上,增加该类型的字节数。这个是根据指针类型来决定的。比如int类型指针加1就是,4字节的+4,+2就是8。

可以通过输出地址值来查看,比如:

    int* p = a;
    int* p1 = p + 1;
    cout << p << "\n" << p1 << endl;

二维数组:

接下来说一下二维数组

 比如定义了一个int b[2][3],可以看成是什么,可以看成是我们定义了2个一维数组a[3]。我们分开来看。

那么b[0]就是一个内存地址,指向了第一个a[3]一维数组的地址。

而b[1]就是指向了第二个a[3]一维数组的首地址。

可以通过地址观察验证:

#include <iostream>

using namespace std;

int main() {
    int b[2][3];
    b[0][0] = 1;
    b[0][1] = 20;
    b[0][2] = 33;

   //输出二堆数组的每个地址:
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 3; j++)
            cout << "b[" << i << "][" << j << "]地址:" << &b[i][j]<< endl;

    //输出b[0]和b[1]地址:
    cout << "\n\nb[0]地址:" << b[0] << "\nb[1]地址:" << b[1] << endl;
}

 结果:

可以看到定义了一个二维数组,根据地址来看,在内存中是跟一维数组同样的分配方法,依次增加,每次四个字节,F5E8,F5EC,F5F0.....

只不过是以二维的层次来解读的,通过两个下标来控制,比如b[2][3]数组,你找到规则后,你就可以像一维数组那样推断出b[4][2]是哪个地址,访问的数据段是哪里(当然同上,这种访问也是不合法的,只为示例说明)

分解出来后:

那么b[0]即b[0][0]的的首地址,这个跟前面一维数组同样的原理,a跟a[0]地址一样。

而b[1]的地址,就是要加上12个字节,即F5F4,即b[1][0]首地址。

这个12个字节,就是包含3个整数的一维数组的跨度,这不难理解,跟前面都对应上了。

所以现在问题来了:

即现在二维数组b应该是什么样的指针指向它?它加1是多少?

(推理过程,如果不适请跳过直接看结果)

现在是二维数组b包含了两个一维数组a[3];

可以把a[3]数组看成是一种元素。

那么就是b包含了两个元素。也就是看成是一维数组b[0]=元素1 b[1]等于元素2。

所以指针就像是这样 元素 *p=b 也就是 int *p[3]=b; (此元素有三个整数所以3,前面加上int 类型)

又因为[]的运算级高于*号,所以int *p[3],你这里是定义了一个指针数组,相当定义了三个指针变量.

这里提一下指针数组和数组的指针 的区别:

那么相当于int a,b,c;   p[0]=&a;   p[1]=&b;  p[2]=&c;

指针数组是这样用的。

所以我们得给*p加上括号,让它成为一个二维数组的指针,即 int (*p)[3];

那么定义二维数组指针指向b就是 int (*p)[3]=b; 

而由此可以推断,b+1也就是p+1; 它的跨度是一个a[3]的跨度 12字节。

那么现在我们来实际应用验证一下:


#include <iostream>

using namespace std;

int main() {
    int b[2][3];
    b[0][0] = 1;
    b[0][1] = 20;
    b[0][2] = 33;

    b[1][1] = 66;
    b[1][2] = 88;

   //输出二堆数组的每个地址:
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 3; j++)
            cout << "b[" << i << "][" << j << "]地址:" << &b[i][j]<< endl;

    //输出b[0]和b[1]地址:
    cout << "\n\nb[0]地址:" << b[0] << "\nb[1]地址:" << b[1] << endl;
    //以上为观察对比数据

    int(*p)[3] = b;
    cout << "以下为二维数组指针p的用法:" << endl;
    //取地址的方法:
    cout << "*p是b[0]的地址,p是b的地址,所以相等(包括&b[0][0]):" << *p << "-------" << p << endl;
    //取值的方法
    cout << **p << endl;
    cout << (*p)[0] << endl;
    cout << (*p + 1)[1] << endl;

    cout << p[1][2] << endl;



}

结果:

注意这里的(*p+1)[1]指向的是 b[0][2] 输出的33,这是由于运行符优先级的问题。

因为要先运算*p所以得到了b[0]这个类型的地址,然后加1,就只会加4字节。即b[0][1];

然后,再加上一个[1],就会再加4字节,实际跨度就只有8字节,正好是b[0][2]所以输出33.

类似这样:

   int a[3] = { 1,20,50 };
   cout << (a + 1)[1] << endl; //输出的是50 
   //或者指针的用法
   int* pa = a;
   cout << (pa + 1)[1] << endl;//跟上面一样

那么,如果想访问b[1][1]该怎么做,用括号指定优先级,先运算p+1,再取地址,然后[1]。

即:(正确用法)

    cout << (*(p + 1))[1] << endl;

那么为什么又不是*(p + 1)[1]呢?我们可以根据优先级来计算,最终这个指向哪个地址,获取的是什么值。

p+1 可以知道,跨度为12字节,因为(p+1)所以先计算,之后[]优先级要高于*,所以 再计算[1],

这又是加1,那么(p+1)的结果仍然是一个跨度12字节的类型,再[1],也就是再加上12字节,之后再*,它就是b[2][0]的地址,即b[2]。

地址是可以确定的,如下实验:


#include <iostream>

using namespace std;

int main() {
    int b[3][3];

    b[2][0] = 100;

    int(*p)[3] = b;


    cout << *(p + 1)[1] << endl; //结果 输出b[2][0] 100

}

那么这里还有一个问题,同样是地址,为什么加一个*号就取值取呢,为什么不是b[2]的地址呢?

这里我们要明白通过p+1 和p[1]的用法,虽然它们同样是给地址加上了12个字节,关键是[]这个运算是什么意思,也就是p+1返回的是什么,p[1]返回的是什么。

看下例代码:
 

#include <iostream>

using namespace std;

int main() {
    int b[2][3];


    int(*p)[3] = b;

    cout << p + 1 << endl;
    cout << p[1] << endl;

}

运行结果:


 地址是同样的,但是p+1 是一个二维指针的地址 返回类型是(*p)[3];
 而p[1] 加12字节然后会解引一层指针,也就是一维指针 返回类类型是 int *p 

所以*(p+1)输出的还是地址,此时已经退化成一维的指针。

而*p[1]则是会直接取值,(你可以将[1]的运算符看作是比p+1还多了一个解引操作,即自动将结果加了一个*)(再次划重点[1]增加的值,是根据前面的指针类型数据来增加的int *p是加4,char 加1

int (*p)[3] 是加12)

所以最终*p[1]就是b[1][0]的值。

那么这里留下个题目(*p)[1]又是哪个值? 如果你能分析出来,那么基本上你对二维数组指针有了一个比较好的了解了。

 可以看到这些都是有着严格的区分和应用的,所以二维数组b,是不能用int *p这样来指向的,类型不匹配。

但int *p可以指向b[0],b[1],本质上 b[0]是个一维数组名。那么类推三维数组也是一样的,可以依次拆分,按逻辑分解。这里就不介绍了,感兴趣的可以自行实验,乃至更多维的。

下面补充一些字符串数组的说明(未完待续)

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

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

相关文章

PLY格式文件如何转换成3DTiles格式——使用GISBox软件实现高效转换

一、概述 在三维GIS和数字孪生领域&#xff0c;3DTiles格式已成为主流的数据格式之一。它由Cesium团队提出&#xff0c;专为大规模3D数据可视化设计&#xff0c;能够高效地加载和展示海量模型数据。而PLY格式则是一种常见的三维模型文件格式&#xff0c;主要用于存储点云数据或…

Java定时任务的三重境界:从单机心跳到分布式协调

《Java定时任务的三重境界&#xff1a;从单机心跳到分布式协调》 本文将以生产级代码标准&#xff0c;揭秘Java定时任务从基础API到分布式调度的6种实现范式&#xff0c;深入剖析ScheduledThreadPoolExecutor与Quartz Scheduler的线程模型差异&#xff0c;并给出各方案的性能压…

【Linux网络】手动部署并测试内网穿透

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

java项目之在线购物系统(源码+文档)

项目简介 在线购物系统实现了以下功能&#xff1a; 使用在线购物系统的用户分管理员和用户两个角色的权限子模块。 管理员所能使用的功能主要有&#xff1a;主页、个人中心、用户管理、商品分类管理、商品信息管理、系统管理、订单管理等。 用户可以实现主页、个人中心、我的…

OO_Unit1

第一次作业 UML类图 代码复杂度分析 其中Expr中的toString方法认知复杂度比较高&#xff0c;主要源于多层条件嵌套和分散的字符串处理逻辑&#xff0c;重构时可重点关注这两部分的解耦。 代码量分析 1.”通用形式“ 我觉得我的设计的最大特点就是“通用形式”&#xff0c;具…

重要重要!!fisher矩阵元素有什么含义和原理; Fisher 信息矩阵的形式; 得到fisher矩阵之后怎么使用

fisher矩阵元素有什么含义和原理 目录 fisher矩阵元素有什么含义和原理一、对角线元素( F i , i F_{i,i} Fi,i​)的含义与原理二、非对角线元素( F i , j F_{i,j} Fi,j​)的含义与原理Fisher 信息矩阵的形式矩阵的宽度有位置权重数量决定1. **模型参数结构决定矩阵维度**2.…

[已解决]jupyter notebook报错 500 : Internal Server Error及notebook闪退

jupyter notebook出现如上图的报错&#xff0c;可以在黑色窗口中检查是为什么报错。 我检查发现是nbconvert导致的问题&#xff0c;卸载重装nbconvert。 但是这时候出现&#xff0c;jupyter notebook闪退问题。jupyter的黑色窗口出现一秒钟就没了。 在Anaconda Prompt中检查ju…

2025年渗透测试面试题总结- 某亭-安全研究员(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 一、SQL注入过滤单引号绕过方法 二、MySQL报错注入常用函数 三、报错注入绕WAF 四、MySQL写文件函数…

Redis分布式锁如何实现——简单理解版

目录 前言 满足条件 加锁之后产生的问题 避免死锁的方法 Lua脚本实现避免释放其他锁 看门狗判断过期 扩展 Lua脚本 Redission 前言 在如今开发的某些项目中&#xff0c;多个进程必须以互斥的方式独占共享资源&#xff0c;这时用分布式锁是最直接有效的&#xff0c;分布式…

数字化转型驱动卫生用品安全革新

当315晚会上晃动的暗访镜头揭露卫生巾生产车间里漂浮的异物、纸尿裤原料仓中霉变的碎屑时&#xff0c;这一触目惊心的场景无情地撕开了“贴身安全”的遮羞布&#xff0c;暴露的不仅是部分企业的道德缺失&#xff0c;更凸显了当前检测与监管体系的漏洞&#xff0c;为整个行业敲响…

自适应柔顺性策略:扩散引导控制中学习近似的柔顺

24年10月来自斯坦福大学和 TRI 的论文“Adaptive Compliance Policy: Learning Approximate Compliance for Diffusion Guided Control”。 柔顺性在操作中起着至关重要的作用&#xff0c;因为它可以在不确定的情况下平衡位置和力的并发控制。然而&#xff0c;当今的视觉运动策…

SVN简明教程——下载安装使用

SVN教程目录 一、开发中的实际问题二、简介2.1 版本控制2.2 Subversion2.3 Subversion的优良特性2.4 工作原理2.5 SVN基本操作 三、Subversion的安装与配置1. 服务器端程序版本2. 下载源码包3. 下载二进制安装包4. 安装5. 配置版本库① 为什么要配置版本库&#xff1f;② 创建目…

“智改数转”新风口,物联网如何重构制造业竞争力?

一、政策背景 为深化制造业智能化改造、数字化转型、网络化联接&#xff0c;江苏省制定了《江苏省深化制造业智能化改造数字化转型网络化联接三年行动计划&#xff08;2025&#xff0d;2027年&#xff09;》&#xff0c;提出到2027年&#xff0c;全省制造业企业设备更新、工艺…

从数据洪流到智能洞察:人工智能如何解锁大数据的价值?

引言&#xff1a;数据洪流时代&#xff0c;企业的机遇与挑战 在这个信息爆炸的时代&#xff0c;数据正以前所未有的速度增长。IDC预测&#xff0c;全球数据量将在未来几年内持续飙升&#xff0c;企业每天都会产生海量的用户行为数据、市场交易数据、设备传感数据等。理论上&…

蓝桥杯 之 数论

文章目录 习题质数找素数 LCM报数游戏 快速幂数字诗意 组合数与错位排序小蓝与钥匙 同余取模 数论&#xff0c;就是一些数学问题&#xff0c;蓝桥杯十分喜欢考察&#xff0c;常见的数论的问题有&#xff1a;取模&#xff0c;同余&#xff0c;大整数分解&#xff0c;素数&#x…

SpringBoot的启动原理?

大家好&#xff0c;我是锋哥。今天分享关于【SpringBoot的启动原理&#xff1f;】面试题。希望对大家有帮助&#xff1b; SpringBoot的启动原理&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot的启动原理主要是通过 SpringApplication 类来…

从零开始搭建向量数据库:基于 Xinference 和 Milvus 的文本搜索实践

引言 在 AI 和大数据时代&#xff0c;向量数据库正成为处理非结构化数据&#xff08;如文本、图像&#xff09;的利器。最近&#xff0c;我尝试用 Xinference 和 Milvus 搭建一个简单的文本搜索系统&#xff0c;从读取本地文本文件到实现交互式查询和高亮显示匹配结果&#xf…

scrapy入门(深入)

Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 新建项目 (scrapy startproject xxx)&#xff1a;新建一个新的…

docker模拟Dos_SYN Flood拒绝服务攻击 (Ubuntu20.04)

目录 ✅ 一、实验环境准备&#xff08;3 个终端&#xff09; &#x1f449; 所以最终推荐做法&#xff1a; 2️⃣ 配置 seed-attacker 为攻击者&#xff0c;开启 telnet 服务&#xff1a; 3️⃣ 配置 victim-10.9.0.5 为受害者服务器&#xff0c;开启 telnet 客户端并监听&…

基于PySide6的CATIA自动化工具开发实战——空几何体批量清理系统

一、功能概述 本工具通过PySide6构建用户界面&#xff0c;结合PyCATIA库实现CATIA V5的自动化操作&#xff0c;提供两大核心功能&#xff1a; ​空几何体清理&#xff1a;智能识别并删除零件文档中的无内容几何体&#xff08;Bodies&#xff09;​空几何图形集清理&#xff1…