数据结构:二维数组(2D Arrays)

目录

什么是二维数组?

二维数组的声明方式

方式 1:静态二维数组 

方式 2:数组指针数组(数组中存放的是指针)

方式 3:双指针 + 二级堆分配

💡 补充建议

如何用“第一性原理”去推导出 C++ 中二维数组的三种声明方式?

第一阶段:内存连续,列固定,行固定 → 推导出方式①

第二阶段:每行独立、列可能不同(不规则矩阵) → 推导出方式②

第三阶段:行数和列数都是运行时才知道的 → 推导出方式③


什么是二维数组?

二维数组本质上是“数组的数组”,你可以将它看作一个矩阵,每个元素有两个索引:

int A[3][4];

表示一个包含 3 行 4 列 的整数矩阵。

访问方式是 A[row][col],例如 A[1][2] 表示第 2 行第 3 列。

二维数组的声明方式

我们来逐一讲解,并图示结构 + 内存布局图解:

方式 1:静态二维数组 

int A[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10,11,12}
};

结构理解:

这是一个连续内存块,编译器分配的实际大小为 3 * 4 * sizeof(int),总共 12 个整型元素。

图示结构: 

A:     → 3 行 × 4 列的矩阵(行优先排列)[1, 2, 3, 4][5, 6, 7, 8][9,10,11,12]

 💾 内存模型(连续存储):

+----+----+----+----+----+----+----+----+----+----+----+----+ ← 低地址
|  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
+----+----+----+----+----+----+----+----+----+----+----+----+ ← 高地址

按行顺序(row-major)存储:第一行 → 第二行 → 第三行

特点:

属性说明
内存连续
高效访问
易传给函数是(需行数、列数信息)
可初始化所有值
灵活性(行数/列数变动)否 不可动态变长
  • 简单高效

  • 编译器在编译时就知道每个元素地址的偏移

  • 适用于尺寸固定的表格/矩阵处理

优势体现:性能最佳(连续内存 + 快速索引)

📌 示例:加速矩阵乘法(靠缓存命中率)

int A[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int B[4][2] = {{1,2}, {1,2}, {1,2}, {1,2}};
int C[3][2] = {0};for (int i = 0; i < 3; ++i)for (int j = 0; j < 2; ++j)for (int k = 0; k < 4; ++k)C[i][j] += A[i][k] * B[k][j];

优势体现点:

  • 编译器知道 A[i][k]B[k][j] 在内存中的确切偏移

  • 数据连续,CPU 缓存命中率高

  • 运行速度远优于动态结构(如 vector<vector<int>>)


方式 2:数组指针数组(数组中存放的是指针)

int* A[3];           // 数组中存放3个 int* 指针
A[0] = new int[4];
A[1] = new int[4];
A[2] = new int[4];

结构理解:

  • A 是一个大小为 3 的指针数组:A[0], A[1], A[2]

  • 每个 A[i] 是一个指向 长度为 4 的数组 的指针

图示结构:

A:      → [指针]    [指针]     [指针]↓         ↓          ↓[1,2,3,4] [5,6,7,8] [9,10,11,12]

 💾 内存模型(不连续):

栈:
+-----+-----+-----+ ← A[0], A[1], A[2] 是在栈区的指针变量
| ptr | ptr | ptr |
+-----+-----+-----+↓      ↓      ↓
堆:[1][2][3][4]       // A[0] 指向此处[5][6][7][8]       // A[1][9][10][11][12]    // A[2]

特点:

属性说明
内存连续? 否   每一行在堆中独立分配
指针存储在哪里? 是   指针数组本身在栈
灵活性? 是   可以变行数或列数
缺点 否  不利于整体拷贝、效率较低、复杂性高
  • 比方式①更灵活

  • 每一行都可以单独分配、单独修改、单独释放

  • 行长度可以不同(“不规则二维数组”)

优势体现:行长不一致的表格处理

📌 示例:一个表格,有 3 行,每行元素个数不同

int* A[3];
A[0] = new int[2]{1,2};          // 第一行只有2个元素
A[1] = new int[3]{3,4,5};        // 第二行3个
A[2] = new int[4]{6,7,8,9};      // 第三行4个for (int i = 0; i < 2; ++i) cout << A[0][i] << " ";
for (int i = 0; i < 3; ++i) cout << A[1][i] << " ";
for (int i = 0; i < 4; ++i) cout << A[2][i] << " ";delete[] A[0]; delete[] A[1]; delete[] A[2];

优势体现点:

  • 不规则矩阵结构(每行不一样)

  • 适合表示数据表格(例如 JSON 表、数据库行)


方式 3:双指针 + 二级堆分配

int** A;
A = new int*[3];         // 分配3个指针
A[0] = new int[4];
A[1] = new int[4];
A[2] = new int[4];

结构理解:

  • A 是一个指向指针的指针:int**

  • 它指向一个动态分配的指针数组

  • 每个指针再指向各自的行数组

图示结构:

堆1(A):
+-----+-----+-----+
| ptr | ptr | ptr | ← A[0], A[1], A[2]
+-----+-----+-----+↓      ↓      ↓
堆2:[1,2,3,4] [5,6,7,8] [9,10,11,12]

 💾 内存模型(全堆 + 不连续):

栈:
+----------------+ ← A(int** 类型,指向堆中指针数组)
| 指向堆的指针地址 |
+----------------+堆(第一次 new):
+-----+-----+-----+
| ptr | ptr | ptr | ← 存放 A[0], A[1], A[2]
+-----+-----+-----+堆(3次 new):
[1,2,3,4]     ← A[0]
[5,6,7,8]     ← A[1]
[9,10,11,12]  ← A[2]

特点:

属性说明
全部在堆中?(适合大型数据)
是否连续? 不连续,多次分配
灵活性? 最高,可以动态控制行/列数
管理复杂度? 需手动 delete 两层,容易泄漏
  • 最具通用性

  • 完全动态二维数组

  • 行数、列数在运行时可变,甚至可以动态增删行列

优势体现:完全运行时控制二维矩阵大小

📌 示例:用户输入决定行列数

int rows, cols;
cin >> rows >> cols;int** A = new int*[rows];
for (int i = 0; i < rows; ++i)A[i] = new int[cols];     // 每行动态开辟// 初始化矩阵
for (int i = 0; i < rows; ++i)for (int j = 0; j < cols; ++j)A[i][j] = i * cols + j;// 打印矩阵
for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j)cout << A[i][j] << " ";cout << endl;
}// 释放内存
for (int i = 0; i < rows; ++i) delete[] A[i];
delete[] A;

优势体现点:

  • 非常适合处理用户输入、数据文件、图结构(邻接表)

  • 最接近“二维 vector”的行为

💡 补充建议

  • 如果你只需要 固定结构,首选 int A[3][4]

  • 如果你需要 运行时决定行列,用 int** Avector<vector<int>>

  • 如果你想用更现代 C++ 写法:推荐使用 std::vector 来替代动态数组


如何用“第一性原理”去推导出 C++ 中二维数组的三种声明方式?

基础事实(不依赖高层语法):

  1. 内存是线性地址空间(一条一维线)

  2. 数组是连续的同类型内存块

  3. 指针是变量的地址

  4. 一个数组可以存放指针

  5. new / malloc 可以在运行时分配任意大小的内存

  6. C++ 编译器可以通过 arr[i][j] 翻译成偏移运算:*( *(arr + i) + j )

🔍 我的问题目标:我想建一个二维表格

输入需求:

  • 我希望能访问 A[i][j]

  • 这个结构要能容纳多行多列的数据

第一阶段:内存连续,列固定,行固定 → 推导出方式①

 设计需求:

  • 所有元素个数是 M × N

  • 所有行列大小都是固定的

  • 适合做 数值型矩阵处理、图像、表格

🤔 思考过程:

❓ 我能不能一次申请一个大数组,把所有数据按行拼在一起?

当然可以,我做个拍扁的线性数组:

int data[M * N];

然后我写一个访问函数:

int get(int i, int j) {return data[i * N + j];
}

👉 很方便,但语法不直观。我希望写 A[i][j] 而不是 data[i * N + j]

于是我想:

int A[M][N];

这不就是把 M*N 连续空间组织成二维格式了吗?

A[i][j] 被编译器翻译为:

*(A + i*N + j) = *( *(A + i) + j )

这就推导出了:

✅方式①:int A[3][4]


第二阶段:每行独立、列可能不同(不规则矩阵) → 推导出方式②

新设计需求:

  • 每行的长度不同

  • 不想为每一行都分配一样多空间(内存浪费)

  • 比如:

Row 0: 3 elements
Row 1: 5 elements
Row 2: 2 elements

思考过程:

❓ 我能否为每一行单独分配一个一维数组?

当然可以,我可以写:

int* row0 = new int[3];
int* row1 = new int[5];
int* row2 = new int[2];

那我怎么管理这些行?

💡 我想到了:把这些指针放进一个数组中!

int* A[3];      // A[0], A[1], A[2] 分别指向三行
A[0] = row0;
A[1] = row1;
A[2] = row2;

现在我就可以访问 A[i][j] 了:

  • A[i] 是一行指针

  • A[i][j] 是访问这一行中的元素

这就推导出了:

✅方式②:int* A[3];


第三阶段:行数和列数都是运行时才知道的 → 推导出方式③

 新设计需求:

  • 用户在运行时告诉我有多少行和列

  • 比如:

cin >> rows >> cols;

❓ 我该怎么在运行时分配二维表格呢?

我不能用 int A[rows][cols],因为在 C++ 中数组大小必须是编译时常量(除 VLA 编译器扩展)

🤔 思考过程:

第一步:

我要创建一个 行指针数组:

int** A = new int*[rows];

A 就是一个“二维数组的外壳”:A[i] 将是每一行的指针

第二步:

为每一行动态创建列空间:

for (int i = 0; i < rows; ++i)A[i] = new int[cols];

这样就构成了一个“指针的指针”:二维结构。

得到:

✅方式③:int** A; A = new int*[rows]; A[i] = new int[cols];

总结:需求驱动 + 构造能力 → 推导出三种方式

场景/需求你能用的构造推导出的结构实际语法
数据量固定、行列固定数组连续二维数组int A[M][N]
行数固定、列数可变数组 + 指针指针数组 + 行数组int* A[M];
行列都可变双层指针 + 堆分配二级动态数组int** A;
目标:二维数据结构↓
选择1:一维内存线? → 折叠二维到一维 → A[i][j] ≈ A[i*N + j] → ✅ int A[M][N]↓
选择2:能不能每行分开存? → 每行是 int*,用指针数组存行 → ✅ int* A[M]↓
选择3:连行数都未知? → 指针数组也要动态分配 → ✅ int** A; A = new int*[M]

 

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

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

相关文章

HAProxy 和 Nginx的区别

HAProxy 和 Nginx 都是优秀的负载均衡工具&#xff0c;但它们在设计目标、适用场景和功能特性上有显著区别。以下是两者的详细对比&#xff1a;1. 核心定位特性HAProxyNginx主要角色专业的负载均衡器/代理Web 服务器 反向代理/负载均衡设计初衷高性能流量分发高并发 HTTP 服务…

基于Java+SpringBoot的健身房管理系统

源码编号&#xff1a;S586源码名称&#xff1a;基于SpringBoot的健身房管理系统用户类型&#xff1a;多角色&#xff0c;用户、教练、管理员数据库表数量&#xff1a;13 张表主要技术&#xff1a;Java、Vue、ElementUl 、SpringBoot、Maven运行环境&#xff1a;Windows/Mac、JD…

【MySQL安装-yum/手动安装,卸载,问题排查处理完整文档(linux)】

一.使用Yum仓库自动安装 步骤1:添加MySQL Yum仓库 sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-6.noarch.rpm步骤2:安装MySQL服务器 sudo yum install mysql-server -y步骤3:启动并设置开机自启 sudo systemctl start mysqld sudo systemct…

自定义线程池-实现任务0丢失的处理策略

设计一个线程池&#xff0c;要求如下&#xff1a;队列最大容量为10&#xff08;内存队列&#xff09;。当队列满了之后&#xff0c;拒绝策略将新的任务写入数据库。从队列中取任务时&#xff0c;若该队列为空&#xff0c;能够从数据库中加载之前被拒绝的任务模拟数据库 (TaskDa…

【NLP入门系列四】评论文本分类入门案例

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

Ubuntu安装ClickHouse

注&#xff1a;本文章的ubuntu的版本为&#xff1a;ubuntu-20.04.6-live-server-amd64。 Ubuntu&#xff08;在线版&#xff09; 更新软件源 sudo apt-get update 安装apt-transport-https 允许apt工具通过https协议下载软件包。 sudo apt-get install apt-transport-htt…

C++26 下一代C++标准

C++26 将是继 C++23 之后的下一个 C++ 标准。这个新标准对 C++ 进行了重大改进,很可能像 C++98、C++11 或 C++20 那样具有划时代的意义。 一:C++标准回顾 C++ 已经有 40 多年的历史了。过去这些年里发生了什么?这里给出一个简化版的答案,直到即将到来的 C++26。 1. C++9…

【MySQL】十六,MySQL窗口函数

在 MySQL 8.0 及以后版本中&#xff0c;窗口函数&#xff08;Window Functions&#xff09;为数据分析和处理提供了强大的工具。窗口函数允许在查询结果集上执行计算&#xff0c;而不必使用子查询或连接&#xff0c;这使得某些类型的计算更加高效和简洁。 语法结构 function_…

微型气象仪在城市环境的应用

微型气象仪凭借其体积小、成本低、部署灵活、数据实时性强等特点&#xff0c;在城市环境中得到广泛应用&#xff0c;能够为城市规划、环境管理、公共安全、居民生活等领域提供精细化气象数据支持。一、核心应用场景1. 城市微气候监测与优化热岛效应研究场景&#xff1a;在城市不…

【仿muduo库实现并发服务器】eventloop模块

仿muduo库实现并发服务器一.eventloop模块1.成员变量std::thread::id _thread_id;//线程IDPoller _poll;int _event_fd;std::vector<Function<Function>> _task;TimerWheel _timer_wheel2.EventLoop构造3.针对eventfd的操作4.针对poller的操作5.针对threadID的操作…

Redis 加锁、解锁

Redis 加锁和解锁的应用 上代码 应用调用示例 RedisLockEntity lockEntityYlb RedisLockEntity.builder().lockKey(TradeConstants.HP_APP_AMOUNT_LOCK_PREFIX appUser.getAccount()).value(orderId).build();boolean isLockedYlb false;try {if (redisLock.tryLock(lockE…

在 Windows 上为 WSL 增加 root 账号密码并通过 Shell 工具连接

1. 为 WSL 设置 root 用户密码 在 Windows 上使用 WSL&#xff08;Windows Subsystem for Linux&#xff09;时&#xff0c;默认情况下并没有启用 root 账号的密码。为了通过 SSH 或其他工具以 root 身份连接到 WSL&#xff0c;我们需要为 root 用户设置密码。 设置 root 密码步…

2730、找到最长的半重复子字符穿

题目&#xff1a; 解答&#xff1a; 窗口为[left&#xff0c;right]&#xff0c;ans为窗口长度&#xff0c;same为子串长度&#xff0c;窗口满足题设条件&#xff0c;即只含一个连续重复字符&#xff0c;则更新ans&#xff0c;否则从左边开始一直弹出&#xff0c;直到满足条件…

MCP Java SDK源码分析

MCP Java SDK源码分析 一、引言 在当今人工智能飞速发展的时代&#xff0c;大型语言模型&#xff08;LLMs&#xff09;如GPT - 4、Claude等展现出了强大的语言理解和生成能力。然而&#xff0c;这些模型面临着一个核心限制&#xff0c;即无法直接访问外部世界的数据和工具。M…

[Linux]内核如何对信号进行捕捉

要理解Linux中内核如何对信号进行捕捉&#xff0c;我们需要很多前置知识的理解&#xff1a; 内核态和用户态的区别CPU指令集权限内核态和用户态之间的切换 由于文章的侧重点不同&#xff0c;上面这些知识我会在这篇文章尽量详细提及&#xff0c;更详细内容还得请大家查看这篇…

设计模式-观察者模式、命令模式

观察者模式Observer&#xff08;观察者&#xff09;—对象行为型模式定义&#xff1a;定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,在它的状态发生变化时,会通知所有的观察者.先将 Observer A B C 注册到 Observable &#xff0c;那么当 Observable 状态…

【Unity笔记01】基于单例模式的简单UI框架

单例模式的UIManagerusing System.Collections; using System.Collections.Generic; using UnityEngine;public class UIManager {private static UIManager _instance;public Dictionary<string, string> pathDict;public Dictionary<string, GameObject> prefab…

深入解析 OPC UA:工业自动化与物联网的关键技术

在当今快速发展的工业自动化和物联网&#xff08;IoT&#xff09;领域&#xff0c;数据的无缝交换和集成变得至关重要。OPC UA&#xff08;Open Platform Communications Unified Architecture&#xff09;作为一种开放的、跨平台的工业通信协议&#xff0c;正在成为这一领域的…

MCP 协议的未来发展趋势与学习路径

MCP 协议的未来发展趋势 6.1 MCP 技术演进与更新 MCP 协议正在快速发展&#xff0c;不断引入新的功能和改进。根据 2025 年 3 月 26 日发布的协议规范&#xff0c;MCP 的最新版本已经引入了多项重要更新&#xff1a; 1.HTTP Transport 正式转正&#xff1a;引入 Streamable …

硬件嵌入式学习路线大总结(一):C语言与linux。内功心法——从入门到精通,彻底打通你的任督二脉!

嵌入式工程师学习路线大总结&#xff08;一&#xff09; 引言&#xff1a;C语言——嵌入式领域的“屠龙宝刀”&#xff01; 兄弟们&#xff0c;如果你想在嵌入式领域闯出一片天地&#xff0c;C语言就是你手里那把最锋利的“屠龙宝刀”&#xff01;它不像Python那样优雅&#xf…