1. vector初始化概述
在C++标准库中,vector是最常用的动态数组容器之一。作为STL的重要组成部分,vector提供了多种初始化方式以满足不同场景下的需求。掌握这些初始化方法不仅能提高编码效率,还能避免一些常见的性能陷阱。
vector本质上是一个封装了动态大小数组的顺序容器,它能够自动管理内存,在运行时根据需要动态调整存储空间。与普通数组相比,vector的优势在于它知道自己的大小,能够高效地在尾部添加或删除元素,并且提供了丰富的成员函数来操作数据。
2. 默认初始化方式
2.1 空vector初始化
最基本的初始化方式是创建一个不包含任何元素的空vector:
cpp复制std::vector<int> vec1; // 创建一个空的int类型vector
这种初始化方式适用于元素数量未知,需要后续动态添加的场景。此时vector不会分配任何内存空间,直到第一个元素被插入时才会进行内存分配。
注意:默认构造的vector的capacity()为0,这与size()为0是不同的概念。capacity表示当前分配的内存能容纳的元素数量,而size表示实际存储的元素数量。
2.2 带分配器的默认初始化
vector还支持在构造时指定自定义的内存分配器:
cpp复制std::allocator<int> alloc;
std::vector<int, std::allocator<int>> vec2(alloc);
这种形式在需要特殊内存管理策略时非常有用,比如在嵌入式系统中使用特定的内存池分配器。
3. 指定大小的初始化
3.1 固定大小初始化
可以创建具有指定大小但元素值未初始化的vector:
cpp复制std::vector<int> vec3(5); // 创建包含5个int的vector,元素值未定义
这种初始化方式会立即分配足够的内存来存储指定数量的元素,但元素值取决于具体实现(通常是默认构造的值)。
3.2 固定大小和初始值初始化
更常见的做法是同时指定大小和所有元素的初始值:
cpp复制std::vector<int> vec4(5, 10); // 创建包含5个int的vector,每个元素初始化为10
这种形式在需要预分配内存并设置统一初始值时非常高效,避免了后续逐个赋值的开销。
4. 列表初始化方式
4.1 直接列表初始化(C++11)
C++11引入了统一的初始化语法,可以直接用花括号初始化vector:
cpp复制std::vector<int> vec5{1, 2, 3, 4, 5}; // 直接列表初始化
这种方式直观清晰,特别适合已知所有初始元素的场景。编译器会自动推导出vector的大小并初始化每个元素。
4.2 拷贝列表初始化
也可以使用赋值形式的列表初始化:
cpp复制std::vector<int> vec6 = {6, 7, 8, 9, 10}; // 拷贝列表初始化
这两种列表初始化在功能上是等价的,只是语法形式不同。选择哪种形式主要取决于编码风格偏好。
5. 拷贝和移动初始化
5.1 拷贝构造初始化
可以通过另一个同类型vector来初始化新vector:
cpp复制std::vector<int> vec7(vec6); // 拷贝构造
std::vector<int> vec8 = vec6; // 也是拷贝构造
这两种形式都会创建一个与源vector内容完全相同的新vector,包括所有元素和当前容量。
5.2 移动构造初始化(C++11)
C++11引入的移动语义允许高效地转移资源所有权:
cpp复制std::vector<int> vec9(std::move(vec7)); // 移动构造
移动构造后,源vector(vec7)将变为空状态,其内存资源被转移到新vector(vec9)中。这种方式避免了不必要的拷贝,对于大型vector特别高效。
6. 范围初始化
6.1 迭代器范围初始化
可以通过一对迭代器来初始化vector,复制指定范围内的元素:
cpp复制std::vector<int> vec10(vec6.begin(), vec6.begin()+3); // 复制前三个元素
这种初始化方式非常灵活,可以从任何支持迭代器的容器中复制元素,甚至可以是数组:
cpp复制int arr[] = {11, 12, 13, 14, 15};
std::vector<int> vec11(arr, arr + sizeof(arr)/sizeof(arr[0]));
6.2 从其他容器初始化
利用迭代器范围初始化的特性,可以方便地从其他容器创建vector:
cpp复制std::list<int> mylist = {16, 17, 18};
std::vector<int> vec12(mylist.begin(), mylist.end());
7. 特殊初始化技巧
7.1 使用assign方法初始化
虽然assign不是构造函数,但它提供了一种初始化或重置vector的便捷方式:
cpp复制std::vector<int> vec13;
vec13.assign(4, 100); // 4个100
vec13.assign(vec12.begin(), vec12.end()); // 从其他容器复制
assign方法会替换vector当前的所有内容,相当于先clear()再重新初始化。
7.2 使用swap初始化
swap方法可以高效地交换两个vector的内容:
cpp复制std::vector<int> vec14(3, 50);
std::vector<int> vec15(5, 60);
vec14.swap(vec15); // 交换内容
这种方法常用于清空vector并回收内存:
cpp复制std::vector<int>().swap(vec14); // 清空vec14并释放内存
8. 初始化性能考量
8.1 预分配内存
对于已知大小的vector,提前预留内存可以避免多次重新分配:
cpp复制std::vector<int> vec16;
vec16.reserve(1000); // 预分配1000个元素的内存
这与直接指定大小初始化不同,reserve只分配内存而不创建元素。
8.2 移动语义的优势
在C++11及以后版本中,返回vector的函数可以利用移动语义避免拷贝:
cpp复制std::vector<int> createVector() {
std::vector<int> temp{19, 20, 21};
return temp; // 触发移动语义
}
std::vector<int> vec17 = createVector(); // 高效初始化
9. 初始化常见问题与解决方案
9.1 初始化列表歧义
在某些情况下,列表初始化可能会产生歧义:
cpp复制std::vector<int> vec18(5); // 5个默认初始化的元素
std::vector<int> vec19{5}; // 单个元素5
std::vector<int> vec20(5, 1); // 5个1
std::vector<int> vec21{5, 1}; // 两个元素5和1
理解这些细微差别对于正确初始化vector至关重要。
9.2 元素类型初始化
当vector存储的是类对象时,初始化方式会影响构造函数的调用:
cpp复制class MyClass {
public:
MyClass() { std::cout << "Default\n"; }
MyClass(int) { std::cout << "Param\n"; }
};
std::vector<MyClass> vec22(3); // 调用3次默认构造函数
std::vector<MyClass> vec23{3, MyClass()}; // 调用参数构造和默认构造
10. C++17及以后的初始化增强
10.1 结构化绑定初始化
C++17引入的结构化绑定可以与vector初始化结合使用:
cpp复制std::vector<std::pair<int, std::string>> vec24 = {{1, "one"}, {2, "two"}};
auto [first, second] = vec24[0]; // 结构化绑定
10.2 推导指南初始化
C++17还提供了类模板参数推导,简化了vector初始化:
cpp复制std::vector vec25{1.0, 2.0, 3.0}; // 自动推导为vector<double>
这种方式避免了显式指定模板参数,使代码更加简洁。
