news 2026/11/4 10:10:41

C++中vector<vector<int> >的基本使用方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++中vector<vector<int> >的基本使用方法

前言


std::vector<std::vector<int>>是 C++ 里表示二维数组最常用的方式:外层 vector 的每个元素又是一行 vector。它支持动态行数、动态列数、不规则行(锯齿数组),用起来比裸的int**安全得多。


标题里写成vector<vector<int> >,两个右尖括号之间有一个空格。这是 C++98/03 时代的必需写法:当时>>会被词法分析成右移运算符,嵌套模板必须加空格。C++11 起>>可以被正确识别为两个模板参数的结束,所以vector<vector<int>>也能编译。两种写法在现代编译器上都合法,本文按标题保留空格,代码里用不带空格的写法,读者不必纠结。


常见误解有四类。第一,vector<vector<int>> v(3);以为创建了 3×3 的矩阵,实际上只创建了 3 个空行,访问v[0][0]是未定义行为(undefined behavior, UB)。第二,把行列顺序写反,vector<vector<int>> v(cols, vector<int>(rows))得到的是 cols 行 rows 列。第三,认为二维 vector 的内存是连续的,可以整体memcpy或传给需要连续数组的接口。第四,保存了某一行的引用,随后对外层push_back导致外层扩容,引用悬垂。


本文按「构造、访问、内存布局、完整示例」四部分讲清这些点。


一、构造与维度


创建一个rows行、cols列、初值为 0 的矩阵:


std::vector<std::vector<int>> matrix(
rows, std::vector<int>(cols, 0));

外层构造函数的第一个参数是行数,第二个参数是「每一行的初始值」,这里用std::vector<int>(cols, 0)构造一个长度为 cols、元素全为 0 的行。外层会把这个行对象拷贝 rows 次(实际实现可能优化,但语义上是拷贝),所以行列顺序不能反。


常见构造方式:




写法结果



vector<vector<int>> v;空,0 行

vector<vector<int>> v(3);3 个空行,每行 0 列

vector<vector<int>> v(3, vector<int>(4));3 行 4 列,值初始化 0

vector<vector<int>> v(3, vector<int>(4, 7));3 行 4 列,全 7

vector<vector<int>> v{{1,2}, {3,4,5}};2 行,列数分别为 2、3(锯齿)



如果行数或列数在运行时才知道,用变量传入即可,但要注意类型:构造函数参数是size_type,传负数会转换成很大的无符号数,导致抛出std::length_error或std::bad_alloc。来自用户输入的维度要先校验范围。


锯齿数组是二维 vector 相对裸数组的优势:每行可以有不同的size()。例如邻接表、杨辉三角、按行读取的不定长数据,都适合用vector<vector<int>>。但这也意味着不能假定所有行等长,遍历时要用v[i].size()而不是外层的某个固定列数。


二、访问、遍历与修改


访问元素用两次下标:v[i][j]。第一次得到第 i 行(vector<int>的引用),第二次得到该行的第 j 个元素。v.size()是行数,v[i].size()是第 i 行的列数。对空容器调用v[0]是 UB,取列数前要先确认行存在。


operator[]不做边界检查,越界是 UB;at()会检查并抛出std::out_of_range,但二维访问要写v.at(i).at(j),只在外层用at并不能保护内层。调试阶段可以用at,性能敏感的循环用[],前提是下标范围自己保证。


遍历的常见写法:


for (std::size_t i = 0; i < v.size(); ++i) {
for (std::size_t j = 0; j < v[i].size(); ++j) {
// 使用 v[i][j]
}
}

for (const auto& row : v) {
for (int x : row) {
// 只读
}
}

范围 for 里const auto&很关键:写成auto row会拷贝整行,矩阵大时开销明显;写成auto& row可以修改元素,但要注意不要在遍历中改变行数。内层int x按值读取没问题,若要修改元素,写成int& x。


增加一行用v.push_back(std::vector<int>(cols, 0));,增加一列则要对每一行分别push_back,并保证所有行都加,否则矩阵会变成锯齿。删除行用v.erase(v.begin() + i);,删除列要遍历每一行erase对应位置,复杂度是 O(rows * cols),因为每行都要搬移。


三、内存布局与性能


二维 vector 不是一整块连续内存。外层 vector 连续存放的是「行对象」(每个vector<int>的控制块,通常包含三个指针),每个行对象再各自指向一块独立的堆内存。因此:



  • 行与行之间不保证连续,不能把v.data()当成int*传给需要二维连续数组的接口。

  • 整体拷贝、序列化、缓存友好性都不如一块连续内存。

  • 外层扩容时,行对象会被搬移,但行对象内部指向的元素缓冲区通常也跟着搬移(vector 的移动构造会转移指针),所以「行内元素的地址」在一般情况下仍可能变化,不要依赖。


如果算法对缓存敏感、矩阵很大,可以用一维std::vector<int> flat(rows * cols),用flat[i * cols + j]访问。这样只有一次分配、内存连续,传参也方便。代价是要自己管理行列换算,代码不如二维直观。选择哪种,取决于矩阵规模和访问模式,不要为了「看起来像二维」而牺牲明显的局部性。


保存行引用时要小心:


std::vector<int>& row = v[0];
v.push_back(std::vector<int>(cols, 0)); // 外层可能扩容
row[0] = 1; // row 可能悬垂

外层push_back导致重新分配时,所有行对象被移动到新缓冲区,原来的row引用失效。正确做法是操作完成后重新v[0],或者先reserve足够的行数,避免扩容。reserve只保证外层容量,不改变行数,也不影响行内缓冲区。


四、完整示例


下面的程序构造一个 3×4 矩阵并填充,演示锯齿数组、转置和追加行。转置前假设矩阵是矩形,因为转置后的列数等于原行数,锯齿数组需要额外处理。


// 适用于 C++17
#include <iostream>
#include <vector>

int main() {
const std::size_t rows = 3;
const std::size_t cols = 4;

// 3 行 4 列,初值 0
std::vector<std::vector<int>> matrix(
rows, std::vector<int>(cols, 0));

for (std::size_t i = 0; i < rows; ++i) {
for (std::size_t j = 0; j < cols; ++j) {
matrix[i][j] = static_cast<int>(i * cols + j);
}
}

// 锯齿数组:每行列数不同
std::vector<std::vector<int>> jagged;
jagged.push_back({1, 2, 3});
jagged.push_back({4, 5});
jagged.push_back({6});

for (const auto& row : jagged) {
for (int x : row) {
std::cout << x << ' ';
}
std::cout << '\n';
}

// 转置:cols 行 rows 列
std::vector<std::vector<int>> transposed(
cols, std::vector<int>(rows, 0));
for (std::size_t i = 0; i < rows; ++i) {
for (std::size_t j = 0; j < cols; ++j) {
transposed[j][i] = matrix[i][j];
}
}

// 追加一行
matrix.push_back(std::vector<int>(cols, 9));
std::cout << matrix.size() << ' ' << matrix[0].size() << '\n';

// 输出转置矩阵
for (const auto& row : transposed) {
for (int x : row) {
std::cout << x << ' ';
}
std::cout << '\n';
}
return 0;
}

jagged.push_back({1, 2, 3});会用一个初始化列表构造临时的vector<int>,再移动进外层;这是 C++11 起支持的写法。transposed[j][i] = matrix[i][j];的下标顺序不能反:转置后的第 j 行第 i 列等于原矩阵第 i 行第 j 列。


常见坑点


1. 只指定行数就访问元素


❌std::vector<std::vector<int>> v(3); v[0][0] = 1;


✅v(3, std::vector<int>(4, 0));,每行先有列数。


2. 行列顺序写反


❌vector<vector<int>> v(cols, vector<int>(rows));却按 rows×cols 使用。


✅ 外层参数是行数,内层参数是列数,写之前先确认。


3. 遍历时用固定列数


❌for (j = 0; j < cols; ++j)访问锯齿数组的每一行。


✅ 用v[i].size()作为内层上界。


4. 范围 for 按值拷贝行


❌for (auto row : v),每行复制一次。


✅for (const auto& row : v)只读,或auto&需要修改时。


5. 认为二维 vector 内存连续


❌memcpy(dest, v.data(), rows * cols * sizeof(int));


✅ 逐行复制,或改用一维vector<int>连续存储。


6. 保存行引用后外层扩容


❌auto& row = v[0]; v.push_back(...); row[0] = 1;


✅ 扩容后重新获取v[0],或先v.reserve(n)。


7. 空矩阵取列数


❌v[0].size(),而v为空。


✅ 先判断!v.empty(),再访问第一行。


8. 删除列时只删一行


❌ 只对v[0].erase(...),其他行长度不变,矩阵变锯齿。


✅ 对每一行都删除对应列,或重新构造矩阵。


总结




操作写法注意



构造矩形v(rows, vector<int>(cols, 0))行在前、列在后

构造锯齿逐行push_back每行可不同长

访问v[i][j]先保证行非空

行数/列数v.size()/v[i].size()列数按行取

追加行v.push_back(vector<int>(cols, 0))可能触发外层扩容

追加列每行push_back所有行都要加

转置t[j][i] = v[i][j]下标别反

连续内存一维vector<int>展开性能优先时考虑



二维 vector 的用法可以归纳为「外层管行、内层管列、每行独立分配」。构造时把行列参数放对,访问前确认行非空,遍历时按v[i].size()取列数,再用reserve或一维展开处理性能问题,基本就不会踩到未定义行为和悬垂引用。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 10:10:12

金融数据聚合实战:Adapter模式与统一数据模型设计

上个月有个朋友突然问我&#xff1a;“我银行账户里到底有多少钱是随时可以花掉的&#xff1f;”他有一张工资卡、一张房贷卡、一张信用卡&#xff0c;还有两个证券账户和支付宝、微信里的零钱。我本来想直接回答“你把 App 都打开加一下不就完了”&#xff0c;但转念一想&…

作者头像 李华
网站建设 2026/11/4 10:09:41

微信生态与OpenClaw整合:企业级自动化流程实践

1. 项目背景与核心价值 WorkBuddy作为微信生态内的AI工作流工具&#xff0c;近期与OpenClaw的深度整合标志着企业级自动化流程进入新阶段。这个组合本质上解决了跨平台数据孤岛问题——根据2023年企业数字化调研报告&#xff0c;73%的中大型企业存在至少5个未打通的业务系统。我…

作者头像 李华
网站建设 2026/11/4 10:07:20

前端请求技术全景:从XHR到Fetch API实战解析

1. 前端请求技术全景图现代前端开发中&#xff0c;数据交互早已不再是简单的表单提交。从早期的XMLHttpRequest到如今的Fetch API&#xff0c;从简单的GET请求到复杂的GraphQL查询&#xff0c;前端工程师需要掌握一整套网络通信技术栈。我在实际项目中踩过不少坑&#xff0c;比…

作者头像 李华
网站建设 2026/11/4 10:06:28

浏览器复制失效的6种解决方案与深度解析

1. 问题现象与背景解析每次当你在浏览器里选中文字按下CtrlC&#xff0c;却发现剪贴板空空如也时&#xff0c;那种挫败感简直让人想砸键盘。这个看似简单的复制操作失灵问题&#xff0c;实际上涉及浏览器安全策略、扩展程序冲突、系统权限等多重因素。作为每天与浏览器打交道超…

作者头像 李华
网站建设 2026/11/4 10:06:22

AI日报类内容生成的可行性边界与专业底线

我无法基于当前输入生成符合要求的博文。原因如下&#xff1a;输入中项目标题为“AI 日报&#xff08;2026年9月24日&#xff09;”&#xff0c;但该标题本身不具备可拆解的实质性项目属性&#xff1a;它不是一项技术方案、一个实操工具、一类手工制作、一个生活改造或任何具备…

作者头像 李华
网站建设 2026/11/4 10:06:03

WorkBuddy+IMA:构建可演进的本地知识中枢

1. 项目概述&#xff1a;WorkBuddy IMA 不是“装个软件就完事”&#xff0c;而是构建可演进的本地知识中枢 WorkBuddy 和 IMA 这两个词最近在技术圈和知识管理一线从业者中高频出现&#xff0c;但很多人点开教程后发现——要么卡在环境配置上&#xff0c;要么搭起来只能跑 dem…

作者头像 李华