二维数组在内存中是连续一维布局,可直接用首地址当一维数组用,但需知总元素数和行优先顺序;动态分配的指针数组或vector不连续,必须拷贝展开。

二维数组在内存中本来就是一维布局
直接取首地址就能当一维数组用,前提是知道总元素个数和内存排布顺序(通常是行优先)。比如 int arr[3][4] 在内存里连续存了 12 个 int,&arr[0][0] 就是起始地址。
常见错误是误以为需要“复制”或“转换”——其实只是换种方式解读同一块内存:
- 行优先(C/C++ 默认):按行遍历,
arr[i][j]对应一维索引i * cols + j - 列优先(如 Fortran)不适用,除非你手动重排
- 如果二维数组是栈上局部数组(如
int a[2][3]),不能返回&a[0][0]给调用方用,因为函数返回后内存失效
用指针强制转换获取一维视图
对已知尺寸的二维数组,最轻量的方式是类型转换:
int arr[2][3] = {{1,2,3}, {4,5,6}};
int* flat = &arr[0][0]; // ✅ 正确:指向首元素
// 或等价写法
int* flat2 = reinterpret_cast<int>(arr); // ✅ 同样有效</int>
注意:arr 本身衰减为 int (*)[3](指向含 3 个 int 的数组的指针),转成 int* 是安全的,但必须确保后续访问不超过 6 个元素,否则越界。
- 不要写
int* flat = (int*)arr;—— C 风格转换可读性差,且容易掩盖问题 - 如果二维数组是动态分配的(如
new int*[rows]每行 new),这种转换完全无效——那是不连续的指针数组 - 对
std::vector<:vector>></:vector>同样不适用,它不是一块连续内存
std::vector> 转一维 vector
这是真正需要“展开”的场景。不能靠地址转换,必须拷贝:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
std::vector<:vector>> mat = {{1,2}, {3,4,5}, {6}};
std::vector<int> flat;
flat.reserve(mat.size() * (mat.empty() ? 0 : mat[0].size())); // 预估容量,避免多次 realloc
for (const auto& row : mat) {
flat.insert(flat.end(), row.begin(), row.end());
}</int></:vector>
性能关键点:
-
reserve()很重要,否则每行insert都可能触发内存重分配 - 如果每行长度差异大,预估容量不准,不如先遍历一次统计总 size 再
reserve - 用
std::vector<int> flat; flat.assign(...)</int>不方便,因为没有直接接受嵌套迭代器的重载
传参时如何让函数接收“展开后的一维视图”
如果你控制函数签名,推荐用 std::span<const t></const>(C++20)或 gsl::span(或裸指针+长度),而不是硬编码二维尺寸:
void process_flat(std::span<const int> data) {
for (int x : data) { /* ... */ }
}
// 调用:
int arr[2][3] = {{1,2,3}, {4,5,6}};
process_flat({&arr[0][0], 6}); // ✅ 显式传起始地址和长度</const>
这样既支持栈数组、堆数组,也兼容 std::vector 的 .data():
- 避免函数内部再做维度计算,职责更清晰
- 比
int**或int[][N]更通用、更安全 - 如果还在用 C++17 及之前,用
const int* data, size_t len是最稳妥的替代
真正容易被忽略的是内存连续性前提——所有“展开”技巧都依赖底层数据物理连续。一旦用了指针数组、vector of vector、或 padding 对齐的结构体数组,就必须老老实实循环拷贝,别试图用 reinterpret_cast 蒙混过关。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










