news 2026/11/4 14:44:34

c++中vector<int>和vector<int*>的用法及区别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
c++中vector<int>和vector<int*>的用法及区别

前言


std::vector<int>和std::vector<int*>看起来只差一个星号,实际是两种完全不同的数据结构。前者在连续内存里存放整数本身;后者在连续内存里存放指针,指针指向的整数在别处。这个差别影响内存布局、拷贝语义、生命周期、排序行为和性能。


常见误解有三个。第一,认为vector<int*>一定比vector<int>省内存。它省的是「移动元素时的拷贝成本」,但每个元素本身要占一个指针,被指向的对象还要单独分配,总内存通常更多。第二,认为拷贝vector<int*>会复制它指向的整数。vector 的拷贝构造只复制指针值,两个 vector 指向同一批对象,这就是浅拷贝。第三,认为对vector<int*>调用std::sort会按整数值排序。默认比较的是指针地址,不是被指向的值。


本文从内存布局讲起,依次说明用法、拷贝与所有权、迭代器失效、排序与性能,最后给出一个完整示例。示例以 C++17 为准,指针大小按 64 位平台描述,具体数值由实现决定。


一、内存布局


vector<int>内部是一块连续的动态数组,每个元素就是一个int,元素之间紧挨着。v[i]直接得到整数值,取地址&v[i]得到指向该元素的指针。


vector<int*>内部的连续数组存放的是指针,每个元素占一个指针大小(64 位平台上通常是 8 字节)。指针指向的int可以在栈上、在另一块堆内存里、在另一个容器里,彼此不一定相邻。访问*v[i]需要先读出指针,再按地址访问目标,多了一次间接寻址。




维度vector<int>vector<int*>



连续存储的内容int值指针值

元素大小sizeof(int)sizeof(int*)

目标整数位置就在 vector 内vector 外,由指针指向

访问方式v[i]*v[i],需先判空

内存局部性好,缓存友好差,目标分散



局部性差异在遍历时最明显。vector<int>的顺序访问几乎每次都命中缓存行;vector<int*>每访问一个元素就可能跳到另一个内存页,缓存未命中的概率高得多。对性能敏感的热点循环,这个差别往往比算法本身的影响还大。


二、基本用法


vector<int>的用法就是普通的数值容器:push_back存值,下标读写值,排序按值比较。元素之间互相独立,修改一个不影响另一个。


vector<int*>存放的是地址,因此必须先有可以取地址的对象。常见来源有三种:指向局部或全局变量的地址、指向堆上new出来的整数、指向另一个容器中的元素。存入nullptr在语法上合法,但使用前必须判空。


// 适用于 C++17
int a = 10;
int b = 20;
std::vector<int*> ptrs;
ptrs.push_back(&a);
ptrs.push_back(&b);

for (int* p : ptrs) {
if (p != nullptr) {
std::cout << *p << ' ';
}
}

这段代码没有所有权转移:ptrs只是保存了a和b的地址,a、b的生命周期由它们所在的作用域决定。如果ptrs活过了a、b,里面就是悬垂指针,解引用是未定义行为。


如果确实需要 vector 拥有整数,正确的类型是std::vector<std::unique_ptr<int>>,而不是std::vector<int*>。unique_ptr会在析构时释放所指对象,拷贝被禁止,移动转移所有权,语义清晰。裸指针的vector<int*>默认不表达任何所有权,删除与否完全靠约定。


三、拷贝语义:值拷贝与浅拷贝


vector<int>的拷贝是深拷贝:新 vector 有自己的一块内存,每个整数被复制一份。修改副本不影响原对象,原对象销毁也不影响副本。这是值语义。


vector<int*>的拷贝是浅拷贝:新 vector 复制的是指针值,两个 vector 的元素指向同一批整数。通过任一 vector 修改*p,另一边的观察结果同步变化。如果其中一个 vector 负责delete这些指针,另一个 vector 里的指针立刻悬垂;如果两个都尝试delete,就是重复释放。


std::vector<int*> a;
a.push_back(new int(5));

std::vector<int*> b = a; // 浅拷贝,两个 vector 指向同一个 int
*b[0] = 6;
// 此时 *a[0] 也是 6
// 只能 delete 一次,且必须保证另一个 vector 不再使用

这种共享关系不是错误,但必须有明确的约定:谁拥有、谁释放、谁只观察。工程中更推荐std::vector<std::unique_ptr<int>>(独占)或std::vector<std::shared_ptr<int>>(共享),让所有权由类型系统表达,而不是靠注释。


四、迭代器与引用的失效规则


vector 扩容时,内部缓冲区会重新分配,所有指向元素的指针、引用、迭代器都会失效。这条规则对两种 vector 都成立,但「元素」的含义不同。


对vector<int>,元素就是整数本身。扩容后,之前保存的&v[0]或v.begin()都不能再用。


对vector<int*>,元素是指针槽位。扩容后,指向槽位的指针(例如int**)和迭代器失效,但槽位里保存的指针值被复制到新缓冲区,它们指向的整数没有移动。也就是说,之前从 vector 里取出的int* p = v[0];在扩容后仍然有效,只要被指向的整数还活着。这个区别经常被混淆:失效的是「装指针的容器」,不是「指针指向的对象」。


// 适用于 C++17
std::vector<int*> v;
int x = 1;
v.push_back(&x);
int* p = v[0]; // 指向 x
v.push_back(nullptr); // 可能扩容
// p 仍然指向 x,有效;但 v.begin() 等迭代器已失效

反过来,如果int*指向的是另一个vector<int>的元素,那么那个 vector 扩容时,被指向的整数会搬家,指针同样失效。判断失效时,要分清是哪一层容器发生了重新分配。


五、排序与比较


对vector<int>调用std::sort,默认的operator<比较整数值,结果符合直觉。


对vector<int*>调用std::sort,默认比较的是指针地址。地址大小与整数值毫无关系,排序结果看起来是乱的。要按被指向的值排序,必须传入自定义比较器:


std::sort(ptrs.begin(), ptrs.end(),
[](int* lhs, int* rhs) { return *lhs < *rhs; });

这个比较器要求所有指针非空,否则解引用空指针是未定义行为。如果容器里可能有nullptr,要先定义空指针排在前还是后,并在比较器里显式处理。


另一个细节是:直接用内置的<比较两个不相干对象的指针,结果未指定(unspecified),并不保证构成严格全序。标准库的std::less<int*>对指针提供了全序保证,std::sort的默认比较用的正是std::less,所以默认排序本身不会违反严格弱序,只是比较的是地址而非值。自定义比较器按*lhs < *rhs比较时,如果有两个指针指向相等的值,比较器对二者都返回false,这是合法的严格弱序,排序仍可正常进行。若要求相同值保持原顺序,应使用std::stable_sort。


六、完整示例


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

int main() {
// 值容器:排序按值,修改互不影响
std::vector<int> values = {3, 1, 4, 1, 5};
std::sort(values.begin(), values.end());
for (int v : values) {
std::cout << v << ' ';
}
std::cout << '\n'; // 1 1 3 4 5

// 指针容器:元素指向三个独立变量
int a = 30;
int b = 10;
int c = 20;
std::vector<int*> ptrs = {&a, &b, &c};

// 默认排序比较地址,结果与值无关,这里显式按值排序
std::sort(ptrs.begin(), ptrs.end(),
[](int* lhs, int* rhs) { return *lhs < *rhs; });

for (int* p : ptrs) {
std::cout << *p << ' ';
}
std::cout << '\n'; // 10 20 30

// 通过指针修改,原变量同步变化
*ptrs.front() = 99;
std::cout << a << ' ' << b << ' ' << c << '\n'; // 30 99 20

// 浅拷贝:两个 vector 指向同一批变量
std::vector<int*> copy = ptrs;
*copy.back() = 77; // copy.back() 指向 a
std::cout << a << ' ' << b << ' ' << c << '\n'; // 77 99 20
return 0;
}

推演输出。values排序后是 1、1、3、4、5。ptrs初值为 30、10、20 的地址,按值排序后顺序变成 10、20、30,对应&b、&c、&a。ptrs.front()现在指向b,写入 99 后b变为 99,a仍为 30,c仍为 20,输出30 99 20。


copy与ptrs共享指针,copy.back()也指向a。写入 77 后,a变成 77,三个变量依次为 77、99、20。这验证了浅拷贝的行为:修改通过副本做,原变量同样变化。同时也说明,指针容器排序后元素顺序与变量声明顺序无关,必须按实际指向判断哪个变量会受影响。


这段示例没有堆分配,因此不需要delete。如果指针来自new,必须在合适的时机逐个释放,并确保释放后没有其他 vector 还持有同一地址。


七、如何选择


优先使用vector<int>。它内存连续、缓存友好、拷贝语义直观、不需要手动管理生命周期。只有在以下场景才考虑指针容器:元素是大型对象且需要多态、需要共享同一份数据、需要容器只保存引用而不复制对象。即使如此,也应该优先用智能指针而不是裸指针。




场景推荐类型



保存一组整数std::vector<int>

观察外部对象,不拥有std::vector<int*>或std::vector<std::reference_wrapper<int>>

独占一组堆对象std::vector<std::unique_ptr<int>>

共享一组堆对象std::vector<std::shared_ptr<int>>

需要多态std::vector<std::unique_ptr<Base>>



常见坑点


坑点一:对vector<int*>默认排序。


❌std::sort(ptrs.begin(), ptrs.end());,按地址排序,结果与整数值无关。


✅ 传入按*lhs < *rhs比较的 lambda,并处理空指针。


坑点二:保存局部变量的地址后越界使用。


❌ 函数返回vector<int*>,里面存的是函数内局部变量的地址。


✅ 保证被指向对象的生命周期覆盖 vector 的使用期,或改为按值存储。


坑点三:拷贝vector<int*>后重复释放。


❌ 两个 vector 持有同一批new出来的指针,各自delete一遍。


✅ 明确单一所有者,或改用unique_ptr/shared_ptr。


坑点四:解引用空指针。


❌ 容器里存了nullptr,遍历时直接*p。


✅ 使用前判断p != nullptr,或在比较器中定义空指针顺序。


坑点五:以为 vector 扩容会让被指向的整数失效。


❌int* p = v[0]; v.push_back(...);之后认为p一定失效。


✅ 区分「指针槽位失效」与「指向对象失效」;只有后者搬家时才悬垂。


坑点六:以为vector<int*>更省内存。


❌ 每个元素一个指针,目标还要单独分配,总内存通常更大。


✅ 小对象直接用vector<int>;只有大对象或需共享时才用指针。


坑点七:把vector<int>元素的地址长期保存。


❌int* p = &v[0]; v.push_back(1);之后继续用p,扩容后悬垂。


✅ 在容器稳定后再取地址,或改用下标/迭代器重新获取。


坑点八:把vector<int*>当成拥有资源的容器。


❌ 默认析构不会delete任何指针,堆对象全部泄漏。


✅ 用智能指针容器表达所有权,或显式编写释放逻辑。


总结




维度vector<int>vector<int*>



存储内容整数值指针

拷贝深拷贝浅拷贝

生命周期由容器管理由外部管理,易悬垂

排序默认行为按值按地址

扩容影响元素搬家槽位搬家,目标不动

缓存友好度高低

适用场景通用数值序列观察、共享、多态



两者的选择本质上是在回答「容器是否拥有数据」。拥有数据时,值容器最简单;需要引用外部对象时,指针容器可以做到,但必须同时解决生命周期和所有权问题。把这两个问题想在前面,比记住语法差异更重要。




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

Antigravity+Blender MCP:用自然语言搭建智慧仓储数字孪生场景

我前段时间在推进一个智慧仓储可视化项目&#xff0c;原本的计划是先在 Blender 里手动搭出一个仓库场景&#xff0c;再交给前端团队用 Three.js 加载。结果第一天我就遇到了一个尴尬的问题&#xff1a;建模太慢。货架要一个格子一个格子复制&#xff0c;AGV 小车要自己拼轮子和…

作者头像 李华
网站建设 2026/11/4 14:42:59

技术路线图绘制指南:逻辑主线、分层表达与视觉克制

技术路线图这东西&#xff0c;在论文答辩现场见过太多次翻车了。有的同学把Visio里拖出来的流程图直接贴进论文&#xff0c;箭头横七竖八&#xff0c;框里塞满小字&#xff0c;导师翻到那一页眉头一皱&#xff0c;直接问“你这图想说明什么”。也见过另一种极端&#xff0c;图做…

作者头像 李华
网站建设 2026/11/4 14:42:58

GreatSQL 8.0.32 CentOS 7 部署避坑指南:SELinux与firewalld配置关键点

简介&#xff1a;本资源是郑州大学计算机与人工智能学院《数据库系统原理》课程的全套实验报告&#xff0c;面向高校数据库初学者及实践教学需求者&#xff0c;聚焦DBMS系统认知、国产数据库GreatSQL部署与运维、实验过程记录与结果分析等核心能力培养。报告覆盖9个递进式实验&…

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

轻量级三维异常检测框架:旋转不变性与工业应用

1. 论文核心价值解析这篇发表于2023年的论文提出了一种轻量级三维异常检测框架&#xff0c;主要解决了工业质检场景中三维点云数据的旋转敏感性问题。我在半导体封装检测项目中曾深有体会——当元器件以不同角度进入扫描区域时&#xff0c;传统方法会产生大量误报。该论文的创新…

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

自动驾驶技术演进:从L2到L4的范式转移与工程挑战

1. 自动驾驶技术演进的分水岭 去年在CES展会上&#xff0c;某车企展示的L4级自动驾驶演示车在封闭场地完美完成了无接管行驶。但当工程师被问及量产时间表时&#xff0c;却给出了"5-10年"的模糊答案。这个场景完美诠释了当前自动驾驶领域最尖锐的矛盾——实验室里的技…

作者头像 李华
网站建设 2026/11/4 14:40:37

JVM核心机制解析:类加载、内存管理与性能优化

1. JVM底层原理精要解析 作为一名在Java领域摸爬滚打多年的老码农&#xff0c;我见过太多开发者对JVM的认知停留在"知道但说不清"的状态。市面上那些大部头的经典著作确实全面&#xff0c;但动辄七八百页的篇幅让不少忙于业务的同行望而却步。今天我就用最直白的语言…

作者头像 李华