定义和初始化 vector 对象(三十八)
1. 常见初始化方式
下面的表 3.4 列出了初始化 vector 对象的常用方法,其中 T 表示 vector 内元素的类型。
语法形式 | 说明 |
---|---|
vector<T> v1; | 默认初始化:创建一个空 vector,v1 不含任何元素;元素类型为 T,后续可通过 push_back 等操作添加元素。 |
vector<T> v2(v1); | 直接拷贝初始化:v2 是 v1 的副本,包含 v1 中所有元素;等价于 vector<T> v2 = v1; 。 |
vector<T> v2 = v1; | 拷贝初始化:将 v1 的所有元素拷贝给 v2。 |
vector<T> v3(n, val); | 直接初始化:创建一个包含 n 个元素的 vector,每个元素都被初始化为 val。 |
vector<T> v4(n); | 直接初始化:创建一个包含 n 个值初始化的元素的 vector。对内置类型来说,每个元素值为 0;对类类型,则调用默认构造函数。 |
vector<T> v5{a, b, c, ...}; | 列表初始化:使用花括号将若干初始值列表传入,v5 的元素个数即为列表中初始值的个数,每个元素依次赋值为相应的初始值。 |
vector<T> v5 = {a, b, c, ...}; | 拷贝列表初始化:效果等同于列表初始化,v5 被初始化为包含 a, b, c… 的 vector 对象。 |
例如:
#include <vector>
#include <string>
#include <iostream>
using std::vector;
using std::string;
using std::cout;
using std::endl;
int main() {
// 默认初始化:创建一个空 vector,对象中没有元素
vector<string> svec;
// 直接拷贝初始化:v2 包含 svec 中所有元素的副本(此时 svec 为空,所以 v2 也为空)
vector<string> v2(svec);
vector<string> v2_2 = svec; // 等价于 v2(svec)
// 创建一个包含 10 个元素的 vector,每个元素均初始化为 "hi!"
vector<string> v3(10, "hi!");
// 创建一个包含 10 个元素的 vector,每个元素为值初始化
vector<int> v4(10); // 对于 int,值初始化的结果为 0
// 列表初始化:使用 initializer_list 创建 vector
vector<string> v5{"a", "an", "the"};
vector<string> v5_2 = {"a", "an", "the"}; // 等价于列表初始化
// 输出部分内容以验证
cout << "v3 size: " << v3.size() << endl; // 输出 10
cout << "v4 elements: ";
for (auto n : v4)
cout << n << " "; // 每个元素为 0
cout << endl;
cout << "v5 contents: ";
for (const auto &s : v5)
cout << s << " "; // 输出 a an the
cout << endl;
return 0;
}
2. 关于拷贝初始化与直接初始化的讨论
C++ 中提供了两种主要的初始化方式:
- 直接初始化 (Direct Initialization):不使用等号,例如
vector<int> v1(10); // 创建一个包含 10 个 int 元素的 vector,使用直接初始化 vector<int> v3(10, 1); // 创建一个包含 10 个 int 元素,每个元素的值均为 1
- 拷贝初始化 (Copy Initialization):使用等号,例如
vector<int> v2 = v1; // 将 v1 的元素拷贝到 v2 中 vector<int> v5 = vector<int>(10, 1); // 显式创建临时对象再拷贝给 v5
对于只有一个初始值(例如字符串字面值或已有 vector 对象)的情况,直接初始化与拷贝初始化效果通常是一样的。但当涉及多个初始参数时(例如指定元素数量和统一初始值),直接初始化更为直观,拷贝初始化则需要显式创建临时对象,读起来较为冗长且可读性较差。
3. 列表初始化与圆括号初始化的区别
在 C++11 中,除了传统的圆括号初始化,还可以使用花括号来进行列表初始化。列表初始化的含义取决于所提供初始值的数量和类型,常见的区别如下:
-
单个整数值
vector<int> v1(10);
解释:v1 被直接初始化为 10 个元素,每个元素均值初始化(对于 int,初始值为 0)。vector<int> v2{10};
解释:v2 被列表初始化,列表中只有一个元素 10,所以 v2 有一个元素,其值为 10。
-
两个整数值
vector<int> v3(10, 1);
解释:v3 包含 10 个元素,每个元素的值均为 1。vector<int> v4{10, 1};
解释:v4 被列表初始化,列表中有两个元素,分别是 10 和 1,因此 v4 包含两个元素,值分别为 10 和 1。
因此,在使用花括号时,编译器会尽可能将花括号中的值解释为元素初始值的列表,而不是用来构造 vector 对象的参数。
注意:
如果希望利用初始值来构造 vector 的元素数量及统一初始值,必须使用圆括号。例如:vector<int> v3(10, 1); // 正确,v3 有 10 个元素,每个元素为 1
而使用花括号:
vector<int> v4{10, 1}; // v4 有 2 个元素,分别为 10 和 1
4. 值初始化与直接初始化
当只提供 vector 对象中元素的数量而不指定初始值时,vector 会对每个元素执行值初始化。
- 对于内置类型(如 int),值初始化将每个元素初始化为 0。
- 对于类类型(如 string),调用默认构造函数来初始化对象(通常得到空字符串)。
例如:
vector<int> ivec(10); // 10 个 int 元素,每个初始化为 0
vector<string> svec(10); // 10 个 string 元素,每个默认初始化为空字符串
特殊限制:
- 如果 vector 的元素类型不支持默认初始化(例如某些类要求必须明确提供初始值),那么仅提供元素数量将无法完成初始化。
- 当只提供数量时,只能使用直接初始化的形式,而不能使用拷贝初始化。例如:
正确做法应是:vector<int> vi = 10; // 错误:拷贝初始化不能仅用一个整数来表示 vector 的大小
vector<int> vi(10);
5. 小结
-
默认初始化:
vector<T> v;
创建一个空的 vector,随后可以通过 push_back 等操作动态添加元素。 -
拷贝初始化:
vector<T> v2 = v1;
或vector<T> v2(v1);
都是创建一个新 vector,其内容为已有 vector 的副本。 -
直接初始化指定初始值:
vector<T> v3(n, val);
创建一个包含 n 个元素的 vector,每个元素初始化为 val。 -
列表初始化:
使用花括号形式初始化,例如vector<T> v5{a, b, c};
,此时 v5 的元素个数为花括号中的初始值个数,且每个元素分别初始化为对应的值。 -
圆括号与花括号的区别:
同样的数值在圆括号和花括号中可能有不同含义。例如,vector<int> v1(10);
表示 10 个元素(每个为 0),而vector<int> v2{10};
表示只有一个元素,其值为 10;vector<int> v3(10,1);
表示 10 个元素,每个为 1,而vector<int> v4{10,1};
表示两个元素,分别为 10 和 1。 -
值初始化:
当只提供元素数量时,元素根据其类型自动值初始化。
通过全面理解 vector 对象的各种初始化方式,你可以更灵活地构建和管理容器,确保代码既清晰又符合预期。对于复杂的初始化需求,选择合适的初始化方式可以大大提高代码的可读性和维护性。
参考资料
- cppreference.com 关于 std::vector 构造函数的详细说明
- 各大 C++ 编码规范(如 Google C++ Style Guide)中对容器初始化的建议
希望这篇全面的讲解能够帮助你深入理解如何定义和初始化 vector 对象,以及圆括号与花括号在初始化时的微妙区别,从而为后续使用 vector 提供坚实的基础。