在C++中,普通的数组(C-style array)、std::initializer_list
、 std::array
和std::vector
是四种不同的容器类型,它们各自有不同的特性和用途。下面是对它们进行详细比较和解释。
普通数组是C++中最基本的数组类型,它在C语言中就已经存在。
特点:
语法:
int arr[5] = {1, 2, 3, 4, 5};
使用限制:
std::initializer_list
是C++11引入的一种轻量级容器,专门用于初始化列表。
特点:
{}
可以轻松初始化 std::initializer_list
。语法:
#include <initializer_list>
#include <iostream>
void printList(std::initializer_list<int> list) {
for (int elem : list) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
int main() {
printList({1, 2, 3, 4, 5});
return 0;
}
主要用途:
std::array
是C++11引入的标准库容器,封装了固定大小的数组,提供了更丰富的功能。
特点:
size()
、at()
(带边界检查)等。语法:
#include <array>
#include <iostream>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 使用 at() 访问元素,带边界检查
std::cout << "Element at index 2: " << arr.at(2) << std::endl;
// 使用迭代器
for (auto it = arr.begin(); it != arr.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
优势:
std::vector
类似的接口,但由于其大小是固定的,所以比 std::vector
更轻量。std::array
是普通数组的更安全、更方便的替代品。std::initializer_list
: 只读容器,专门用于初始化列表,常用于函数参数。std::array
: 封装了固定大小数组,提供了强类型安全和更丰富的功能,是普通数组的更好的替代品。相对于前面三者,std::vector
是一种动态大小的容器,提供了更为强大的功能。它是C++标准模板库(STL)中最常用的序列容器之一。之前有写过vector的模拟实现的博客:
基本用法:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 添加元素
vec.push_back(6);
// 访问元素
std::cout << "Element at index 2: " << vec[2] << std::endl;
// 使用迭代器遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 删除元素
vec.pop_back();
return 0;
}
与普通数组(C-style array)相比:
std::vector
可以动态调整大小。std::vector
提供了 at()
方法,具有边界检查。std::vector
方便。与 std::initializer_list
相比:
std::initializer_list
是一个轻量级的只读容器,不能修改和扩展;而 std::vector
是一个可读写的容器,支持动态大小。std::initializer_list
常用于函数参数的初始化;而 std::vector
适用于更多场景。与 std::array
相比:
std::array
的大小是固定的,类似于普通数组,不能在运行时改变;而 std::vector
支持动态大小。std::array
的性能通常略优于 std::vector
,因为它没有动态分配和管理内存的开销;而 std::vector
由于需要动态分配内存,有时会比 std::array
稍慢。std::initializer_list
: 只读容器,专门用于初始化列表,常用于函数参数。std::array
: 封装了固定大小数组,提供了更安全、更方便的替代品。std::vector
: 动态大小容器,自动管理内存,功能丰富,是大多数情况下的首选容器。std::vector
的灵活性和功能使它成为C++编程中最常用的容器之一,适用于需要动态大小和频繁增删元素的场景。
每种容器都有其特定的应用场景,选择时要根据具体需求来决定。
在C++中,std::vector
是一个动态数组,随着元素的增加或删除,其底层内存可能会重新分配。当发生重新分配时,所有指向旧存储空间的迭代器、指针和引用将失效,导致可能的程序错误。这就是所谓的迭代器失效问题。
std::vector
中迭代器可能失效的主要情况有以下几种:
插入元素时:
当 std::vector
需要扩展容量时,可能会重新分配更大的内存,并将现有元素复制到新的内存位置。此时,所有旧的迭代器、指针、和引用都会失效。
如果插入的位置不是 vector
的末尾,所有从插入点开始的迭代器也会失效,因为后面的元素要向后移动。
删除元素时:
clear()
和 erase()
操作:
clear()
会删除所有元素,使所有迭代器失效。erase()
会使指向被删除元素和它后面的元素的迭代器失效。std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) {
it = v.erase(it);
}
}
erase已经返回下一个元素的位置,如果再加加可能起不到遍历的效果,甚至会访问到不属于vector的空间。
要处理 std::vector
的迭代器失效问题,可以采用以下几种方法:
这是最简单的策略。如果在遍历 std::vector
的时候不修改它,迭代器就不会失效。
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << std::endl;
}
当执行插入或删除操作时,可以重新获取迭代器。对于插入操作,插入元素后可以通过返回的新迭代器继续操作。对于删除操作,erase
返回的迭代器指向删除元素之后的下一个元素。
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); /* nothing */) {
if (*it % 2 == 0) {
it = v.erase(it); // erase 返回删除元素之后的迭代器
} else {
++it;
}
}
在插入大量元素之前,可以使用 reserve()
方法提前分配足够的空间,避免频繁的内存重新分配,减少迭代器失效的机会。
std::vector<int> v;
v.reserve(1000); // 预分配空间,避免多次重新分配
for (int i = 0; i < 1000; ++i) {
v.push_back(i); // 不会导致迭代器失效
}
在遍历 std::vector
时,使用索引而不是迭代器,可以避免迭代器失效的问题。
std::vector<int> v = {1, 2, 3, 4, 5};
for (std::size_t i = 0; i < v.size(); /* nothing */) {
if (v[i] % 2 == 0) {
v.erase(v.begin() + i); // 删除偶数
} else {
++i;
}
}
有些标准库算法,如 remove_if
,不会修改容器的大小,而是重排容器元素。这些算法不会导致迭代器失效。你可以结合 erase
和 remove_if
来安全地删除符合条件的元素。
std::vector<int> v = {1, 2, 3, 4, 5};
v.erase(
std::remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }),
v.end()
);
在这里,std::remove_if
会把不符合条件的元素移动到容器的末尾,而 erase
则将那些元素实际删除。这样可以避免多次 erase
操作造成的迭代器失效。
vector
末尾时,尽量提前调用 reserve()
预分配空间。erase
返回的迭代器或结合 remove_if
等标准库算法。vector
需要特别小心同步问题,可以考虑使用锁或其他并发安全容器。如果你能看到这里,给你点个赞,如果对你有帮助的话不妨点赞支持一下~
因篇幅问题不能全部显示,请点此查看更多更全内容