文章目录
- 迭代器失效
- **常见的迭代器失效场景**
- 1. **`std::vector`**
- 2. **`std::deque`**
- 3. **`std::list`**
- 4. **`std::map` / `std::set`**
- 5. **`std::unordered_map` / `std::unordered_set`**
- **总结:迭代器失效场景**
- **如何避免迭代器失效?**
- static 和 inline
- 1. `static`
- 2. `inline`
- `static` vs `inline`
- 总结
迭代器失效
在C++中,迭代器失效指的是在容器进行某些操作后,原先获取的迭代器不再指向有效的元素或位置。迭代器失效可能会导致未定义行为(如访问无效内存、程序崩溃等)。不同的容器在特定操作下会有不同的迭代器失效行为。
常见的迭代器失效场景
1. std::vector
-
失效场景:
- 插入元素:
- 如果插入导致
vector
重新分配内存(即容量不足),所有迭代器都会失效。 - 如果没有重新分配内存,插入点之后的迭代器会失效。
- 如果插入导致
- 删除元素:
- 删除点及其之后的迭代器会失效。
resize
、reserve
:- 如果
resize
或reserve
导致内存重新分配,所有迭代器都会失效。
- 如果
clear
:- 清空容器后,所有迭代器都会失效。
- 插入元素:
-
示例:
std::vector<int> vec = {1, 2, 3, 4}; auto it = vec.begin() + 2; // 指向3 vec.push_back(5); // 可能导致内存重新分配,it失效 std::cout << *it; // 未定义行为
2. std::deque
-
失效场景:
- 插入元素:
- 在两端(
push_front
、push_back
)插入元素不会使任何迭代器失效。 - 在中间插入元素会使所有迭代器失效。
- 在两端(
- 删除元素:
- 在两端(
pop_front
、pop_back
)删除元素不会使任何迭代器失效。 - 在中间删除元素会使所有迭代器失效。
- 在两端(
clear
:- 清空容器后,所有迭代器都会失效。
- 插入元素:
-
示例:
std::deque<int> dq = {1, 2, 3, 4}; auto it = dq.begin() + 2; // 指向3 dq.push_back(5); // it仍然有效 dq.insert(dq.begin() + 1, 0); // 所有迭代器失效 std::cout << *it; // 未定义行为
3. std::list
-
失效场景:
- 插入元素:
- 插入元素不会使任何迭代器失效。
- 删除元素:
- 只有被删除元素的迭代器会失效,其他迭代器仍然有效。
clear
:- 清空容器后,所有迭代器都会失效。
- 插入元素:
-
示例:
std::list<int> lst = {1, 2, 3, 4}; auto it = ++lst.begin(); // 指向2 lst.erase(it); // it失效,但其他迭代器仍然有效 std::cout << *it; // 未定义行为
4. std::map
/ std::set
-
失效场景:
- 插入元素:
- 插入元素不会使任何迭代器失效。
- 删除元素:
- 只有被删除元素的迭代器会失效,其他迭代器仍然有效。
clear
:- 清空容器后,所有迭代器都会失效。
- 插入元素:
-
示例:
std::map<int, int> mp = {{1, 10}, {2, 20}, {3, 30}}; auto it = mp.find(2); // 指向{2, 20} mp.erase(it); // it失效,但其他迭代器仍然有效 std::cout << it->second; // 未定义行为
5. std::unordered_map
/ std::unordered_set
-
失效场景:
- 插入元素:
- 如果插入导致重新哈希(rehash),所有迭代器都会失效。
- 否则,插入不会使迭代器失效。
- 删除元素:
- 只有被删除元素的迭代器会失效,其他迭代器仍然有效。
clear
:- 清空容器后,所有迭代器都会失效。
rehash
:- 重新哈希后,所有迭代器都会失效。
- 插入元素:
-
示例:
std::unordered_map<int, int> ump = {{1, 10}, {2, 20}, {3, 30}}; auto it = ump.find(2); // 指向{2, 20} ump.rehash(100); // 所有迭代器失效 std::cout << it->second; // 未定义行为
总结:迭代器失效场景
容器类型 | 插入元素 | 删除元素 | 其他操作 |
---|---|---|---|
std::vector | 可能全部失效(重新分配内存时) | 删除点及其后失效 | resize 、reserve 可能导致失效 |
std::deque | 中间插入使全部失效,两端插入不失效 | 中间删除使全部失效,两端删除不失效 | clear 使全部失效 |
std::list | 不失效 | 只有被删除元素失效 | clear 使全部失效 |
std::map / std::set | 不失效 | 只有被删除元素失效 | clear 使全部失效 |
std::unordered_map / std::unordered_set | 可能全部失效(重新哈希时) | 只有被删除元素失效 | rehash 、clear 使全部失效 |
如何避免迭代器失效?
- 谨慎操作:在插入或删除元素后,尽量避免使用之前的迭代器。
- 更新迭代器:在插入或删除操作后,重新获取迭代器。
- 使用返回值:某些操作(如
insert
、erase
)会返回新的有效迭代器,可以利用这些返回值。auto it = vec.erase(it); // erase返回下一个有效迭代器
理解迭代器失效的场景和规则,可以帮助你编写更安全、更健壮的C++代码。
static 和 inline
在C++中,static
和 inline
是两种常用于避免函数定义在头文件中多次引用导致链接错误(通常是“多重定义”错误)的方式。它们各自有不同的作用和使用场景:
1. static
- 当函数或变量声明为
static
时,它的作用域被限制在当前的源文件中(即文件内可见)。因此,即使该头文件被多个源文件引用,每个源文件内部会有自己独立的函数副本,避免了多个源文件之间的符号冲突。 static
关键字保证了函数在其他源文件中不可见,不会影响到其他源文件中的相同函数名。
使用示例:
// 在头文件中定义 static 函数
static void myFunction() {// 函数实现
}
- 每个包含该头文件的源文件都会有
myFunction
的独立副本。即使它被多个源文件引用,也不会导致链接错误。
2. inline
inline
关键字用于告诉编译器尝试将函数的调用替换为函数体的内容,从而消除函数调用的开销。对于头文件中的函数,使用inline
可以避免多个定义导致的链接错误,因为编译器会为每个引用的源文件提供该函数的定义,而不会在链接时产生多重定义。- 尽管
inline
表示希望进行内联,但更重要的是它能解决多文件中函数多次定义的问题。
使用示例:
// 在头文件中定义 inline 函数
inline void myFunction() {// 函数实现
}
- 使用
inline
后,每个源文件中对该函数的引用都会引入该函数的实现,从而避免了多个源文件之间的链接冲突。
static
vs inline
static
解决的问题是作用域问题:每个源文件都会有自己的副本,不会发生不同源文件间的符号冲突。inline
主要解决的是性能问题(通过内联化)以及链接问题(避免多重定义)。
常见的用法:
- 如果函数的实现非常简单且短小,通常会同时使用
inline
和static
,这样不仅避免了链接错误,也可能提高效率:
// 在头文件中使用 inline 和 static
static inline void myFunction() {// 函数实现
}
这样,每个源文件在引用头文件时都会有 myFunction
的独立副本,并且编译器可能会将其内联,从而避免了链接错误并提高性能。
总结
static
保证函数仅在当前源文件内有效,避免了多文件链接冲突。inline
允许在多个文件中定义相同的函数,而不会导致链接错误,并尝试优化函数调用(通过内联化)。