当前位置: 首页 > news >正文

数据结构:数组(Array)

目录

什么是数组(Array)?

🔍为什么数组的下标要从 0 开始? 

一、内存地址与偏移量的关系:从 0 开始是最自然的映射

二、指针的起点就是第 0 个元素的地址

三、历史原因:BCPL → B → C → C++

数组的内存体现

 数组的声明

数组的访问方式 


什么是数组(Array)?

数组(Array)是 C++ 中的一种线性数据结构,用于存储多个相同类型的变量,并且这些变量在内存中是连续排列的。

你可以把它想象成一个排好队的储物柜,每个柜子有编号(下标),每个柜子里放着一个值。例如:

int arr[5] = {10, 20, 30, 40, 50};

这表示我们声明了一个包含 5 个 int 类型的数组,它依次存储:

  • arr[0] = 10

  • arr[1] = 20

  • arr[2] = 30

  • arr[3] = 40

  • arr[4] = 50

注意:数组的下标从 0 开始,而不是 1。


🔍为什么数组的下标要从 0 开始? 

虽然最初很多人觉得「从 1 开始」更符合直觉,但数组从 0 开始其实是有深刻的底层原因和效率考量,它与 指针、地址计算、语言设计哲学 有关。我们来系统解释这个设计逻辑。 

一、内存地址与偏移量的关系:从 0 开始是最自然的映射

在 C/C++ 中,数组实际上是指针加偏移(pointer arithmetic)。

例子:

int A[4] = {10, 20, 30, 40};

假设数组 A 从地址 0x1000 开始,且每个 int 占 4 字节。

下标 i内存地址数学计算
A[0]0x1000A + 00x1000 + 0 * 4
A[1]0x1004A + 10x1000 + 1 * 4
A[2]0x1008A + 20x1000 + 2 * 4

 访问 A[i] 实际上是计算:

*(A + i)  // 指针 + 偏移量

 👉 如果下标从 1 开始,那就必须写成:

*(A + (i - 1))

 这样会多一个运算(减法),无论在运行效率还是语义上都不自然。

二、指针的起点就是第 0 个元素的地址

当你声明:int A[5];

数组名 A 实际上是指向 A[0] 的地址。不是 A[1],不是别的起点。

所以,

*A     == A[0]
*(A+1) == A[1]
*(A+2) == A[2]

 如果下标从 1 开始,就会出现“偏移一格”的矛盾,代码会更难维护。

三、历史原因:BCPL → B → C → C++

🧬 C语言起源于 B 和 BCPL

最早的语言 BCPL 和 B语言 中没有数组的概念,只有“地址 + 偏移”。C 语言继承了这种偏移访问模型,所以自然地,数组从 0 开始偏移。

Dennis Ritchie(C 语言的设计者)就是遵循这个简洁、底层直观的设计哲学。

现代语言很多也从 0 开始

大多数现代语言也继承了这个设计:

语言数组是否从 0 开始
C✅ 是
C++✅ 是
Java✅ 是
Python✅ 是
JavaScript✅ 是
Rust✅ 是

虽然也有一些语言(如 Fortran、Lua)允许你从 1 开始索引,但这并不常见。


数组的内存体现

数组的核心特征是:所有元素在内存中是挨着排放的,没有任何间隔。

我们用一个直观的内存图解来说明:

假设 int 类型占用 4 字节(常见情况),数组如下:

int arr[4] = {100, 200, 300, 400};

 如果 arr[0] 存储在内存地址 0x1000,那么在内存中是这样的:

内存地址       值
0x1000      arr[0] = 100
0x1004      arr[1] = 200
0x1008      arr[2] = 300
0x100C      arr[3] = 400

▶️ 特点总结:

  • 每个元素都紧挨着上一个,偏移量是 sizeof(类型)

  • 编译器知道数组是连续的,所以可以通过起始地址和偏移快速定位任意元素:
    arr[i] 等价于 *(arr + i)


 数组的声明

在 C++ 中,声明数组就是告诉编译器我们要创建一个连续内存区域,用于存储多个相同类型的数据项。声明时必须指定类型和元素数量。 

1. int A[5];

含义:

  • 声明一个整型数组 A,包含 5 个元素。

  • 未初始化,每个元素的值是未定义的垃圾值(在局部变量中)。

注意:

  • 在函数内部声明的数组(局部数组)不会自动清零。

  • 在全局或静态作用域中声明的数组会被自动初始化为 0。

2. int A[5] = {2, 4, 6, 8, 10};

 含义:

  • 声明一个大小为 5 的整型数组,并完全初始化所有元素。

  • A[0] = 2, A[1] = 4, ..., A[4] = 10

特点:

  • 初始化列表刚好填满数组,无自动补零。

  • 所有元素值由你控制。

3. int A[5] = {2, 4};

含义:

  • 声明一个大小为 5 的数组,只初始化前两个元素。

  • 剩下的元素会被自动补零。

 结果是:

A[0] = 2  
A[1] = 4  
A[2] = 0  
A[3] = 0  
A[4] = 0

4. int A[5] = {0};

含义:

  • 声明一个大小为 5 的数组,仅第一个元素初始化为 0。

  • 其余元素也会被自动补零。

  • 快速清零的技巧:用 {0} 初始化整个数组。

实际效果:

A[0] = 0  
A[1] = 0  
A[2] = 0  
A[3] = 0  
A[4] = 0

5. int A[] = {2, 4, 6, 8, 10};

含义:

  • 不指定大小,由初始化列表的元素数量自动推断大小为 5。

  • 效果与 int A[5] = {2, 4, 6, 8, 10}; 相同。

编译器推断出:int A[5];     // ← 实际等价形式

 更简洁,特别是在你明确初始化所有元素的情况下。

 


数组的访问方式 

通过索引访问数组元素(Index)

这是最常见、最直接的方式。

A[index]
  • index 是整数类型,从 0 开始。

  • 索引值必须在合法范围内:0数组大小 - 1

用指针访问数组元素 

数组名与指针的关系:

在大多数表达式中,数组名会自动退化为指向第一个元素的指针:

int A[5] = {1, 2, 3, 4, 5};
int* p = A;         // A 就是 &A[0]

此时:

  • pA 都指向数组开头

  • 你可以用指针访问元素

✅ 使用 *(pointer + index): 

int A[5] = {1, 2, 3, 4, 5};
int* p = A;cout << *(p + 0);   // 输出 1
cout << *(p + 3);   // 输出 4*(p + 2) = 100;     // 修改 A[2] 为 100

🟰 等价关系:

表达式含义
A[i]访问数组第 i 个元素
*(A + i)使用数组名当指针
*(p + i)使用指针访问数组元素
p[i]指针变量也支持 [] 下标运算(语法糖)
http://www.dtcms.com/a/264522.html

相关文章:

  • 文心快码答用户问|Comate AI IDE专场
  • 文心4.5开源模型部署实践
  • 使用Vue3实现输入emoji 表情包
  • 阿里云AppFlow AI助手打造智能搜索摘要新体验
  • 基于开源链动2+1模式AI智能名片S2B2C商城小程序的场景零售创新研究
  • 【Unity】MiniGame编辑器小游戏(八)三国华容道【HuarongRoad】
  • Active-Prompt:让AI更智能地学习推理的革命性技术
  • BlenderBot对话机器人大模型Facebook开发
  • Spring Framework 中 Java 配置
  • 51单片机外部引脚案例分析
  • 环境土壤物理Hydrus1D2D模型实践技术应用及典型案例分析
  • Docker Desktop导致存储空间不足时的解决方案
  • 【QT】ROS2 Humble联合使用QT教程
  • 【Unity】MiniGame编辑器小游戏(九)打砖块【Breakout】
  • 纹理贴图算法研究论文综述
  • 二、jenkins之idea提交项目到gitlab、jenkins获取项目
  • 将大仓库拆分为多个小仓库
  • 前端请求浏览器提示net::ERR_UNSAFE_PORT的解决方案
  • WPF路由事件:冒泡、隧道与直接全解析
  • 【Harmony】鸿蒙企业应用详解
  • 小型水电站综合自动化系统的介绍
  • 计算机组成笔记:缓存替换算法
  • QT6 源(147)模型视图架构里的表格窗体 QTableWidget 的范例代码举例,以及其条目 QTableWidgetItem 类型的源代码。
  • Re:从零开始的 磁盘调度进程调度算法(考研向)
  • Node.js 安装使用教程
  • Spring 生态创新应用:微服务架构设计与前沿技术融合实践
  • Kuikly 与 Flutter 的全面对比分析,结合技术架构、性能、开发体验等核心维度
  • 对于3DGS的理解
  • Redisson 与 Java 驻内存数据网格:分布式缓存的高可用解决方案
  • 【强化学习】深度解析 GRPO:从原理到实践的全攻略