vector 的面试题,背出"三个指针"只是入门。真正容易挂的是这些细节:
= default到底初始化成什么?拷贝构造为什么用 capacity?reserve和resize谁负责填充?insert/erase之后迭代器为什么不能用?这篇用 10 个问题把第 16 课串一遍。
开篇
学习方式不变:听课 + 自己敲 + AI 辅助。这篇是第 16 课(5.25 vector)的复盘,不是课堂流水账,而是把最容易答错的 10 个点拆开讲。代码在 VS 2026(MSVC 14.51)下编译运行。
一、三指针:vector 的底层骨架

cpp
iterator _start; // 第一个元素
iterator _finish; // 最后一个有效元素的下一个位置
iterator _endofstorage; // 容量末尾的下一个位置
size() = _finish - _startcapacity() = _endofstorage - _startbegin() = _startend() = _finish
end() 不是 _endofstorage。[begin, end) 是左闭右开,end() 指向有效元素的末尾;_endofstorage 后面是备用空间。
二、10 个问题
Q1:vector() = default; 和手写 vector() {} 有什么区别?
cpp
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
vector() = default;
三个指针不是随机值,因为类内有默认成员初始化器(NSDMI) ,= nullptr 会在构造时生效。
= default:让编译器生成默认构造,不是用户提供,可能保持 trivial /constexpr/noexcept;vector() {}:用户提供的空构造,成员一样会走 NSDMI,但语义上不再是"编译器生成"的那个;- 如果成员没有 NSDMI,
= default对内置类型是默认初始化,值不确定;对string这类类类型会调默认构造。
vector() {} 不会产生歧义,它只是一个普通的空构造函数。
Q2:拷贝构造为什么用 reserve(v.capacity()),不用 reserve(v.size())?
cpp
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)
push_back(e);
}
用 capacity 是为了保留原容器的容量 ,避免拷贝过程中反复扩容;用 size 只保证装得下当前元素,后面再 push_back 可能又要扩。
如果 v 是空 vector:
cpp
reserve(0); // 0 > 0 不成立,什么都不做
三个指针仍然是空;第一次 push_back 时 capacity() == 0,才扩到 4。
Q3:operator=(vector<T> v) 为什么参数是值?
cpp
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
这是 copy-and-swap:
- 传值先拷贝一份独立的
v; swap(v)交换_start/_finish/_endofstorage;- 函数结束,
v析构,释放原来的旧空间。
v1 = v1 也安全:v 是 v1 的拷贝,不是同一个对象;交换后 v1 内容不变,v 拿到旧 buffer 并释放。
传值版对左值也要拷贝一次;后面学了移动构造,才能把这次拷贝省掉。
Q4:initializer_list 构造为什么用 reserve,不用 resize?
cpp
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto& e : il)
push_back(e);
}
因为:
reserve(n)只改 capacity,不构造元素、不改 size;resize(n)会把 size 改成 n,并构造/填充 n 个元素。
如果这里用 resize(il.size()):
- 先构造 n 个元素,size 变成 n;
- 再
push_backn 个,最后变成 2n 个元素。
所以初始化列表用 reserve,让 push_back 逐个构造,size 从 0 到 n。
Q5:范围构造为什么写成模板?vector<int> v(s.begin(), s.end()) 能走吗?
cpp
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
写成模板,是为了接受任意容器的迭代器/指针 ,而不是只接受 T*。
string::iterator 本质是 char*,*it 是 char;传进 vector<int> 会转成 ASCII 码,所以能走这个模板。
但这个模板是无约束 的:它不只接受迭代器,连 int 都能推导成功。这就是:
cpp
bit::vector<size_t> v(10, 1); // 编译报错
的原因:InputIterator 被推导成 int,模板体里 *first 对 int 解引用。要严谨,得用 enable_if / concepts 限制成真正的输入迭代器。
Q6:new T[n] 和 STL 的 uninitialized_copy 差在哪?

老师这版:
cpp
T* tmp = new T[n]; // 默认构造 n 个元素
for (i < old_size)
tmp[i] = _start[i]; // 再对前 old_size 个赋值
STL30:
cpp
allocate(n); // 只申请原始内存
uninitialized_copy(first, last, result); // 只对有效元素拷贝构造
差别:
- 老师版对 n 个元素都默认构造(包括用不到的容量),再对 old_size 个赋值;
- STL 只构造 old_size 个有效元素,未使用的容量是裸内存。
所以"扩容必须默认构造全部"不对,这是 new T[n] 写法的代价,不是扩容的本质。

Q7:insert 在 pos == _finish 时,循环会执行吗?

cpp
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
pos == _finish 时,end = _finish - 1,end >= pos 一开始就不成立,循环不执行;直接写 *pos,++_finish,返回 pos(新元素的位置)。
Q8:erase 删除最后一个元素时,返回什么?
cpp
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
删除最后一个时,pos == _finish - 1,所以 it == _finish,循环不执行;--_finish 后 pos == _finish,返回新的 end()。
删除所有偶数要写:
cpp
it = v.erase(it); // 用返回值更新
不能:
cpp
v.erase(it);
++it; // it 已失效,UB
v.erase(it); *it = 1000; 也是错的:it 失效,再解引用是未定义行为。VS Debug 会检查迭代器,g++ 可能看起来"没事",但不代表合法。
Q9:vector<string> 的 reserve 为什么不能用 memcpy?
memcpy 是浅拷贝:
- 对
int这种 trivially copyable 类型,没问题; - 对
string这种管理资源的类型,memcpy只复制对象里的指针/size/capacity; - 两个 string 指向同一块堆,析构时 double free。
SSO 短字符串可能"看起来没崩":字符存在对象内部的缓冲区,memcpy 复制的是各自的内联 buffer;长字符串存堆指针,就会共享同一块堆。
但不管长短,对 std::string 用 memcpy 都是 UB,不能依赖"没崩"。正确写法是:
cpp
tmp[i] = _start[i]; // 调用 string::operator=,深拷贝
Q10:resize 变小后,被"删掉"的 string 什么时候析构?
老师版:
cpp
else
{
_finish = _start + n;
}
这时不会立刻析构 被"删掉"的 string,它们还在 new T[n] 的数组里。
什么时候析构?
- vector 析构时:
delete[] _start会析构整个数组的 n 个元素,包括_finish后面的; reserve扩容时:delete[] _start也会析构旧数组的全部 n 个元素,然后才用新空间;- 如果之后
push_back写进这些位置,赋值会覆盖原来的 string,旧 buffer 这时才释放。
不是"在栈帧里析构"------_start 指向的是堆上的数组 ,局部变量 tmp 才在栈上;对象本身在堆上。
三、错题复盘
| 容易说错 | 正确说法 |
|---|---|
reserve 会填充元素 |
reserve 只改 capacity;resize 才填充/截断 |
memcpy 有浅拷贝没问题 |
浅拷贝正是资源管理类型的坑;trivially copyable 才没问题 |
| 扩容必须默认构造全部 | 这是 new T[n] 的代价;STL 用 uninitialized_copy 只构造有效元素 |
resize 变小立刻析构 |
只移动 _finish;析构被推迟到 delete[] / 覆盖 / 扩容 |
erase 后还能用旧迭代器 |
旧迭代器失效;用 it = v.erase(it) 接收返回值 |

结尾
这 10 个问题基本覆盖了手写 vector 的高频坑:
- 三指针决定 size/capacity,
end()是_finish; reserve只管容量,resize才管 size;memcpy对资源管理类型是浅拷贝,必须用赋值/拷贝构造;insert/erase会挪数据、可能扩容,旧迭代器会失效;new T[n]会默认构造全部,STL 的uninitialized_copy只构造有效元素。
下一篇继续 vector 的迭代器、深拷贝和更多接口。