目录
一、迭代器(iterator)的定义
二、迭代器的类别
三、使用迭代器
3.1 迭代器运算符
3.2 迭代器的简单应用:使用迭代器将string对象的第一个字母改为大写
3.3 将迭代器从一个元素移动到另外一个元素
3.4 迭代器运算
3.5 迭代器的复杂应用:使用迭代器完成二分搜索算法
四、总结
一、迭代器(iterator)的定义
迭代器(iteratior)是一种类型【这里的类型不是基本数据类型(int、double等),而是一种STL容器内部定义的“类型”】,用于访问容器中的元素或者在元素之间移动。
🚀 一句话总结 迭代器是 STL 容器内部定义的一种特殊类型,本质是指针或指针封装的类,用于遍历和操作容器中的元素。
C++ 迭代器(Iterator)是一种 用于遍历 STL 容器(如 vector
、list
、map
)的对象,本质上是一个 封装了指针或遍历逻辑的类或结构体。它支持 *
解引用、++
递增等操作,使用户能够 像指针一样访问容器元素。
根据 访问能力,迭代器分为 输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器,是 STL 算法和容器操作的核心组件。
在 C++ 标准库中,string
和 vector
等容器 支持下标运算符([]
)来访问元素,但更通用的方式是 使用迭代器。所有 标准库容器 都支持迭代器,但只有少数容器(如 vector
、string
)同时支持 下标访问。
二、迭代器的类别
C++ STL 中的迭代器按照访问能力和功能不同,分为五大类别,每种类别都对应不同的数据结构:
- 输入迭代器(Input Iterator):只能 单向读取 数据,例如
std::istream_iterator
。 - 输出迭代器(Output Iterator):只能 单向写入 数据,例如
std::ostream_iterator
。 - 前向迭代器(Forward Iterator):支持 单向遍历,但可读可写,例如
std::forward_list<int>::iterator
。 - 双向迭代器(Bidirectional Iterator):支持 前进和后退,例如
std::list<int>::iterator
。 - 随机访问迭代器(Random Access Iterator):支持 任意位置访问,例如
std::vector<int>::iterator
。
在 C++ STL 中,每种迭代器类型通常都是一个 类,而不是简单的指针。这是因为:
- 封装底层数据结构(如
list
需要存储当前节点并跳转到下一个)。 - 提供更多功能(如
++
运算、访问map
的键值对)。 - 支持不同的数据结构(如
map
使用红黑树,迭代器需要中序遍历逻辑)。
🚀 但在某些情况下(如 vector
),迭代器可能就是指针,以保持最高效的访问速度。
📌 一句话总结 👉 STL 迭代器通常是一个封装了遍历逻辑的类,而不仅仅是一个指针,以适应不同的数据结构和访问模式。
三、使用迭代器
和指针不一样的是,获取容器的迭代器时,不是使用取地址符 &
,而是调用容器提供的成员函数,因为 STL 容器都有返回迭代器的成员函数(如 begin()
和 end()
)。其中begin()成员负责返回指向第一个元素(或第一个字符)的迭代器。如有下述语句:
// 由编译器决定b和e的类型
// b表示v的第一个元素,e表示v尾元素的下一位置
auto b=v.begin(),e=v.end(); // b和e的类型相同
为什么 b
和 e
的类型由编译器决定?在 C++ 中,auto
关键字让编译器根据变量的初始化表达式自动推导类型。
end成员则负责返回指向容器(或string 对象)“尾元素的下一个位置”的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的“尾后”元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。特殊情况下,如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
begin()的使用:对于一个vector<int>的数组,如何根据迭代器指示的元素获取该元素所在的索引位置?
#include<iostream>
#include<vector>
int main(){
// 对于一个vector<int>的数组,如何根据迭代器指示的元素获取该元素所在的索引位置?
std::vector<int> nums{1,2,3,4,5};
auto it1 = nums.begin();
auto iter2 = it1+3; // 迭代器指向4这个元素
std::cout<<*iter2<<std::endl; // 输出:4
auto Local = iter2-it1; // 输出3,Local的类型为std::ptrdiff_t,两个迭代器相减,返回两个迭代器之间的距离
// 根据上一条语句,如果我们想获取迭代器指示的元素所在的位置,我们可以这样:
// auto local_num=目标迭代器-容器对象.begin();
std::cout<<Local<<std::endl;
return 0;
}
注意:上述的方法仅适用于 vector
等连续存储容器。
方法 | 代码 | 适用范围 | 时间复杂度 |
---|---|---|---|
std::distance() | std::distance(v.begin(), it); | 所有迭代器(如 list 、vector ) | O(1)(vector)O(n)(list) |
指针运算 | it - v.begin(); | 仅适用于 vector 等连续存储容器 | O(1) |
3.1 迭代器运算符
表3.6列举了迭代器支持的一些运算。使用==和!=来比较两个合法的迭代器是否相等,如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等; 否则就说这两个迭代器不相等。
和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素,试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。
对上表进行代码分析:
1.*iter :返回迭代器iter所指元素的引用
#include<iostream>
#include<vector>
int main(){
std::vector<int> nums1={1,2,3,4};
auto iter1=nums1.begin(); // iter1的类型是 std::vector<int>::iterator
std::cout<<"*iter1:"<<*iter1<<std::endl; // 输出:*iter:1
return 0;
}
2.iter->mem:解引用 iter 并获取该元素的名为 mem 的成员,等价于(*iter).mem。
以访问结构体成员为例:
#include <iostream>
#include <vector>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {{"Alice", 25}, {"Bob", 30}};
auto iter = people.begin(); // 迭代器指向第一个元素
// 访问结构体成员
std::cout << iter->name << " " << iter->age << std::endl; // Alice 25
std::cout << (*iter).name << " " << (*iter).age << std::endl; // Alice 25
return 0;
}
✅ iter->mem
等价于 (*iter).mem
,只是 iter->mem
语法更简洁。
✅ 这与指针访问结构体成员的规则相同。
✅ 适用于 STL 容器的迭代器,如 vector
(指向对象)、map
(指向 pair<K, V>
)。
3.4.++iter/--iter:令iter指示容器中的下一个/上一个元素 。
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {1,2,3,4,5};
auto iter = nums.begin(); // 迭代器指向第一个元素
std::cout << *iter << std::endl; // 输出:1
iter++;
std::cout << *iter << std::endl; //输出:2
iter--;
std::cout << *iter << std::endl; //输出:1
return 0;
}
5.6.iter1==iter2或iter1!=iter2 :判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元素或者它们是同一个容器的尾后迭代器,则相等;反之不相等。【注意:不同对象的迭代器必然不相等】
#include<iostream>
#include<vector>
int main(){
// 定义两个vector对象
std::vector<int> nums1={1,2,3,4};
auto iter1=nums1.begin(); // iter1的类型是 std::vector<int>::iterator
auto iter2=nums1.begin(); // iter2的类型是 std::vector<int>::iterator
if(iter1==iter2){
std::cout<<"iter1和iter2相等!"<<std::endl;
}
else{
std::cout<<"iter1和iter2不相等!"<<std::endl;
}
return 0;
}
//输出:iter1和iter2相等!
不同对象的迭代器必然不相等,如下所示:
#include<iostream>
#include<vector>
int main(){
// 定义两个vector对象
std::vector<int> nums1={1,2,3,4};
std::vector<int> nums2={1,2,3,4};
auto iter1=nums1.begin(); // iter1的类型是 std::vector<int>::iterator
auto iter2=nums2.begin(); // iter2的类型是 std::vector<int>::iterator
if(iter1==iter2){
std::cout<<"iter1和iter2相等!"<<std::endl;
}
else{
std::cout<<"iter1和iter2不相等!"<<std::endl;
}
return 0;
}
//输出:iter1和iter2不相等!
//这是因为iter1与iter2是两个不同对象的迭代器
3.2 迭代器的简单应用:使用迭代器将string对象的第一个字母改为大写
使用迭代器将 string 对象的第一个字母改为了大写形式。
#include<iostream>
#include<string>
int main(){
std::string s("string");
// string对象能够直接cout出来
std::cout<<s<<std::endl; // 输出:string
if(s.begin() != s.end()){ // 确保 s 非空
auto it = s.begin(); // it表示 s 的第一个字符
*it = toupper(*it);
}
std::cout<<s<<std::endl; // 输出:String
}
3.3 将迭代器从一个元素移动到另外一个元素
迭代器使用递增(++)运算符来从一个元素移动到下一个元素。从逻辑上来说,迭代器的递增和整数的递增类似,整数的递增是在整数值上“+1”,迭代器的递增则是将迭代器“向前移动一个位置”。
举例:
将string对象从小写转换为大写,依次处理string对象的字符直至我们处理完全部字符或者遇到空白。
#include<iostream>
#include<string>
int main(){
std::string s("string");
// string对象能够直接cout出来
std::cout<<s<<std::endl; // 输出:string
for(auto it=s.begin();it!=s.end()&& !isspace(*it);++it){
*it=toupper(*it);
}
std::cout<<s<<std::endl; // 输出:STRING
}
循环首先用s.begin的返回值来初始化it,意味着it指示的是s中的第一个字符(如果有的话)。条件部分检查是否已到达s的尾部,如果尚未到达,则将it解引用的结果传入 isspace 函数检查是否遇到了空白。每次迭代的最后,执行++it令迭代器前移一个位置以访问s的下一个字符。
循环体内部和上一个程序if语句内的最后一句话一样,先解引用it,然后将结果传入toupper函数得到该字母对应的大写形式,再把这个大写字母重新赋值给it所指示的字符。
3.4 迭代器运算
迭代器的递增运算令迭代器每次移动一个元素,所有的标准库容器都有支持递增运算的迭代器。类似的,也能用==和!=对任意标准库类型的两个有效迭代器进行比较。
string和vector的迭代器提供了更多额外的运算符,一方面可使得迭代器的每次移动跨过多个元素,另外也支持迭代器进行关系运算。所有的这些运算被称作迭代器运算(iterator arithmetic)。
iter+n:
#include<iostream>
#include<string>
int main(){
std::string s("string");
auto it = s.begin(); // it迭代器指示s的第一个字符
it = it+2; // 迭代器加上一个整数值仍得一个迭代器
std::cout<<*it<<std::endl; // 输出:r
}
iter-n:
#include<iostream>
#include<string>
int main(){
std::string s("string");
auto it = s.end(); //
it = it-2; // 迭代器减上一个整数值仍得一个迭代器
std::cout<<*it<<std::endl; // 输出:n
}
iter+=n:
#include<iostream>
#include<string>
int main(){
std::string s("string");
auto it = s.begin(); //
it+=2; // 迭代器加法的复合赋值语句
std::cout<<*it<<std::endl; // 输出:r
}
iter1-iter2:
#include<iostream>
#include<string>
int main(){
std::string s("string");
auto iter1 = s.begin(); //std::__cxx11::string::iterator iter1
auto iter2=iter1+4; // iter2指示n这个字符 std::__cxx11::string::iterator iter2
auto it=iter2-iter1; // it 是 std::ptrdiff_t,存储两个迭代器的距离(4)
std::cout<<it<<std::endl; // 输出:4
std::cout<<*it<<std::endl;// ❌错误:it 是整数,不能解引用
}
在 C++ 标准库中:
std::ptrdiff_t
是一个 整数类型,专门用于存储两个迭代器之间的距离(偏移量)。- 它的本质是一个
signed integer
,通常等价于long
或int
(取决于平台)。
注意:两个迭代器之间不支持加法运算。
迭代器之间的关系运算符(<、>、<=、>=):
#include<iostream>
#include<string>
int main(){
std::string s("string");
auto iter1 = s.begin(); //std::__cxx11::string::iterator iter1
auto iter2=iter1+4; // iter2指示n这个字符 std::__cxx11::string::iterator iter2
auto it1=iter2<iter1; // it1 的类型是bool类型
std::cout<<it1<<std::endl; // 输出:0
auto it2=iter2>iter1; // it2 的类型是bool类型
std::cout<<it2<<std::endl; // 输出:1
}
3.5 迭代器的复杂应用:使用迭代器完成二分搜索算法
#include<iostream>
#include<vector>
// 注意:二分法的前提是数组有序
int TwoSearch(std::vector<int> &nums,int searchNum){
// 定义指示最左端、指示最右端、指示中间的迭代器
auto iterator_left = nums.begin(); // 初始iterator_left指向数组最左端元素
auto iterator_right = nums.begin()+nums.size(); // 初始iterator_right指向数组最右端元素
auto iterator_mid = nums.begin()+nums.size()/2; // 初始iterator_mid指向数组中间元素
// 开始二分法
while(iterator_left<=iterator_right){ // 查找区间位于[iterator_left, iterator_right]
auto iterator_mid=iterator_left+(iterator_right-iterator_left)/2; // 更新区间中间迭代器
if(searchNum<*iterator_mid){ // 查找的元素在左区间
iterator_right=iterator_mid-1; // iterator_right要赋值iterator_mid-1,因为当前这个iterator_mid指向的元素一定不是searchNum,那么接下来要查找的左区间结束位置指向为iterator_mid-1
}
else if(searchNum>*iterator_mid){// 查找的元素在右区间
iterator_left=iterator_mid+1; // iterator_left要赋值iterator_mid+1,因为当前这个iterator_mid指向的元素一定不是searchNum,那么接下来要查找的右区间起始位置指向为iterator_mid+1
}
else{ // *iterator_mid==searchNum
return iterator_mid-nums.begin(); // 返回待查找元素的下标索引
}
}
return -1; // 如果没查找到元素,返回-1
}
int main(){
int SearchNum = 6; // 待查找的元素
std::vector<int> nums = {1,3,4,5,6}; // 待搜查的数组
int SearchLocal= TwoSearch(nums,SearchNum);
std::cout<<"待查找元素在数组中的下标索引为:"<<SearchLocal<<std::endl; // 输出:4
return 0;
}
上述代码的思路源自于我的另一篇博客:leetcode704------二分法查找有序数组中特定的值_二分法算法详细步骤是什么-CSDN博客
四、总结
米哈游一面:C++迭代器和指针的异同?
迭代器(Iterator)和指针(Pointer)在 C++ 中有一些相似之处,但它们的本质和用途有所不同。下面是它们的相同点和不同点的详细对比:
相同点:
都可以用于访问和操作元素
- 迭代器和指针都可以用来遍历数据结构(如数组、vector、list、map 等)。
- 例如,对于
std::vector<int>
,可以使用指针或迭代器访问其元素:std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = vec.begin(); // 迭代器 int* ptr = &vec[0]; // 指针 std::cout << *it << " " << *ptr; // 输出 1 1
支持指针运算
- 迭代器和指针都支持类似的操作,比如
*
解引用、++
递增、--
递减等。- 例如:
++it; // 迭代器前进到下一个元素 ++ptr; // 指针也可以前进到下一个元素
都可以用于 STL 算法
- STL 算法(如
std::sort()
、std::find()
)可以接受指针和迭代器作为参数。- 例如:
std::sort(vec.begin(), vec.end()); // 迭代器 std::sort(arr, arr + 5); // 指针
不同点:
对比项 迭代器(Iterator) 指针(Pointer) 本质 迭代器是一个抽象对象,可以封装复杂数据结构的遍历逻辑。 指针是内存地址,可以直接指向变量或数组中的元素。 适用范围 适用于所有 STL 容器(vector、list、map、set 等)。 主要用于数组和动态内存管理( new
/delete
)。封装性 迭代器封装了数据结构的遍历规则,使得使用者不需要关心底层细节。 指针是裸露的内存地址,用户需要自己管理访问规则。 操作安全性 迭代器通常带有边界检查机制,比如 vector::end()
避免访问越界。指针容易发生越界访问,可能导致未定义行为。 灵活性 迭代器可以支持更复杂的数据结构(如链表、哈希表)。 指针主要适用于线性数据结构(数组、连续内存块)。 算术运算 只有某些类型的迭代器(如 random_access_iterator
)支持+
、-
运算。指针可以直接进行算术运算( +
、-
)。适用于 STL 容器 迭代器是 STL 容器的主要访问方式,几乎所有 STL 容器都支持。 仅适用于 vector
或array
,不能用于list
或map
。类型安全性 迭代器是类型安全的,能确保不会随意访问无效区域。 指针可能会指向非法地址,导致程序崩溃。
示例代码对比
1. 使用指针遍历数组
#include <iostream> int main() { int arr[] = {1, 2, 3, 4, 5}; int* ptr = arr; // 指针指向数组的首元素 while (ptr != arr + 5) { std::cout << *ptr << " "; ++ptr; // 指针移动到下一个元素 } return 0; }
输出:
1 2 3 4 5
2. 使用迭代器遍历
std::vector
#include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; std::vector<int>::iterator it = vec.begin(); // 获取迭代器 while (it != vec.end()) { std::cout << *it << " "; ++it; // 迭代器移动到下一个元素 } return 0; }
输出:
1 2 3 4 5
什么时候用迭代器,什么时候用指针?
✅ 使用迭代器的情况:
- 需要遍历 STL 容器(如
vector
、list
、map
)。- 需要提高 代码的可读性 和 安全性(避免指针越界)。
- 需要使用 STL 算法(如
std::find
、std::sort
)。✅ 使用指针的情况:
- 需要操作 原始数组 或 C 风格的字符串。
- 需要进行 手动内存管理(如
malloc/free
或new/delete
)。- 需要进行 高性能的低级别内存操作(如直接操作内存块)。
总结
迭代器 指针 安全性 更安全,避免非法访问 可能会越界或悬空 适用场景 STL 容器(vector、list、map 等) 数组、动态内存管理 封装性 提供封装,支持不同数据结构 直接指向内存,操作透明 性能 性能稍低(需要额外封装) 直接访问内存,性能更高 可读性 更易读,符合 STL 习惯 代码更底层,容易出错 一般来说,C++ 开发推荐使用迭代器,除非有特殊需求需要使用指针。
不可以直接
std::cout
输出迭代器本身,但可以 解引用迭代器 来输出它指向的元素。
1. 为什么不能直接
std::cout
迭代器?迭代器本质上是一个对象(类似指针),它没有重载
operator<<
,不能直接输出。例如:
#include <iostream> #include <vector> int main() { std::vector<int> v = {10, 20, 30}; auto it = v.begin(); std::cout << it << std::endl; // ❌ 错误:没有定义 << 操作 return 0; }
🔴 编译错误:
在 C++ 中,error: no match for ‘operator<<’ (operand types are ‘std::ostream’ and ‘std::vector<int>::iterator’)
std::cout << x;
本质上调用的是operator<<
运算符重载函数,如果某个类型没有实现operator<<
,那么就无法直接用std::cout
输出它的对象。operator<<
是 C++ 中的一个 流插入运算符,它用于将数据输出到std::cout
(或其他std::ostream
流)。STL 迭代器(如std::vector<int>::iterator
)是一个 类对象,但 C++ 标准库 没有提供operator<<
的重载,因此std::cout << it;
不能直接使用。
2. 迭代器如何正确输出?
✅ 解引用迭代器 (
*it
) 来输出其指向的元素:#include <iostream> #include <vector> int main() { std::vector<int> v = {10, 20, 30}; auto it = v.begin(); std::cout << *it << std::endl; // ✅ 正确,输出 10 return 0; }
✅ 遍历容器并输出所有元素:
#include <iostream> #include <vector> int main() { std::vector<int> v = {10, 20, 30}; for (auto it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; // ✅ 正确,输出 10 20 30 } std::cout << std::endl; return 0; }
3. 特殊情况:迭代器类型不同,解引用方式不同
不同的 STL 容器返回的迭代器类型不同,但都可以
*it
解引用:
std::vector<int>::iterator
→*it
是int
std::map<K, V>::iterator
→*it
是std::pair<const K, V>
std::set<T>::iterator
→*it
是const T
示例:遍历
std::map
#include <iostream> #include <map> int main() { std::map<int, std::string> myMap = {{1, "One"}, {2, "Two"}}; for (auto it = myMap.begin(); it != myMap.end(); ++it) { std::cout << it->first << " -> " << it->second << std::endl; // ✅ 正确 } return 0; }
总结
❌
std::cout << it;
错误,因为it
是一个迭代器,而std::ostream
没有重载<<
来支持迭代器直接输出。
✅std::cout << *it;
正确,因为*it
访问了迭代器指向的值。
✅ 遍历容器时,解引用迭代器 来输出每个元素的值。
为什么一般使用前置递增而不使用后置递增?换句话说,前置递增的优点是什么?
在 C++ 中,一般用
++it
(前置递增)而不是it++
(后置递增),主要是因为 前置递增通常比后置递增更高效。
1.
++it
vs.it++
的区别两者的语法和功能基本相同,都是让迭代器向前移动一个位置,但底层实现有所不同:
++it
(前置递增):先增加值,再返回递增后的迭代器。it++
(后置递增):先保存当前值,递增后返回旧值,因此会产生额外的临时对象。示例
#include <iostream> #include <vector> int main() { std::vector<int> v = {1, 2, 3}; auto it = v.begin(); ++it; // ✅ 前置递增,直接递增迭代器 std::cout << *it << std::endl; // 输出 2 it++; // ✅ 后置递增,返回旧值但不使用 std::cout << *it << std::endl; // 输出 3 return 0; }
运行结果:
2 3
两者在功能上没有区别,但在性能和实现方式上有所不同。
2. 为什么
++it
更高效?后置递增
it++
需要创建临时对象,而前置递增++it
直接修改自身,不需要额外的存储和拷贝。
++it
(前置递增)—— 更高效Iterator& operator++() { // 前置递增 // 直接修改自身 move_to_next(); return *this; }
✅ 直接修改迭代器本身,不产生额外的对象。
it++
(后置递增)—— 额外开销Iterator operator++(int) { // 后置递增 Iterator temp = *this; // 额外创建一个副本 move_to_next(); return temp; // 返回旧值 }
🔴 创建了
temp
作为旧值的拷贝,然后才递增,导致:
- 额外的构造和析构开销(尤其是复杂迭代器)。
- 性能开销更大。
3. 什么时候
it++
适合?虽然
++it
更推荐,但在某些情况下it++
是必须的。例如:
- 在
for
循环中,it++
语义更直观- 在需要“使用旧值再递增”的场景
示例:删除
std::list
中的特定元素#include <iostream> #include <list> int main() { std::list<int> lst = {1, 2, 3, 4, 5}; for (auto it = lst.begin(); it != lst.end(); ) { if (*it % 2 == 0) { it = lst.erase(it); // 删除元素,返回下一个迭代器 } else { it++; // 这里必须用 it++,因为要先使用旧值,再递增 } } for (int x : lst) std::cout << x << " "; // 输出 1 3 5 }
🔹
it++
先返回当前迭代器的值,然后递增,保证lst.erase(it)
删除后还能正确更新it
。
4. 总结
方式 适用场景 额外开销 速度 ++it
(前置递增)大多数情况下 无 更快 it++
(后置递增)需要旧值时 有(创建临时对象) 稍慢 ✅ 结论:一般情况下,推荐 使用
++it
,避免不必要的性能开销,除非确实需要用it++
来保留原值。 🚀
为什么在迭代器中推荐使用前置递增 (
++it
)?在 C++ 迭代器中使用前置递增 (
++it
) 而不是后置递增 (it++
),不仅仅是为了性能优化,还与迭代器的实现特点紧密相关。
1. 迭代器是类对象,后置递增会创建临时对象
迭代器本质上是一个类对象(不像普通指针),它可能包含额外的状态(如指向的容器、当前位置等)。
- 后置递增 (
it++
) 需要创建一个临时迭代器对象,这对复杂的迭代器(如std::list<int>::iterator
)来说,代价较高。- 前置递增 (
++it
) 直接修改迭代器自身,无需创建临时对象,因此更加高效。示例:不同类型的迭代器
对于
std::vector<int>::iterator
:#include <vector> #include <iostream> int main() { std::vector<int> v = {1, 2, 3}; auto it = v.begin(); ++it; // ✅ 推荐:直接修改迭代器 it++; // ❌ 额外创建临时迭代器对象 return 0; }
对
std::vector<int>::iterator
而言,虽然后置递增的额外开销不算大,但对于 更复杂的迭代器(如std::list<int>::iterator
),这个开销会更加明显。
2. 迭代器可能是非平凡对象
在 STL 容器中,有些迭代器是非平凡对象,它们可能包含:
- 指向数据结构的指针
- 额外的元数据(如
std::list
迭代器存储前后指针)- 需要动态分配资源的情况
例如,
std::list<int>::iterator
内部不是简单的指针,而是一个封装了链表节点指针的对象:struct list_iterator { node* ptr; // 指向当前链表节点 list_iterator& operator++() { ptr = ptr->next; return *this; } // ✅ 前置递增直接修改 ptr list_iterator operator++(int) { list_iterator temp = *this; // ❌ 额外创建临时对象 ptr = ptr->next; return temp; } };
🔹
it++
需要拷贝list_iterator
,增加了不必要的临时对象和析构调用,而++it
直接操作指针,性能更优。
3. 适用于所有迭代器类型
不同类型的迭代器性能差异较大:
迭代器类型 典型容器 ++it
it++
指针(原生迭代器) int*
✅ 无额外开销 ✅ 无额外开销 随机访问迭代器 std::vector<int>::iterator
✅ 性能相同 ✅ 几乎无额外开销 双向/前向迭代器 std::list<int>::iterator
✅ 更快(不创建临时对象) ❌ 额外开销(拷贝迭代器对象) 输入/输出迭代器 std::istream_iterator<int>
✅ 推荐 ❌ 可能导致未定义行为
- 对于指针(如
int*
),++it
和it++
没有太大区别,但对于复杂的迭代器(如std::list
),it++
可能导致额外的临时对象创建。- 输入/输出迭代器 甚至不允许后置递增,因为它们是一次性对象,使用
it++
可能会导致未定义行为。
4. 适用于范围
for
循环和 STL 算法在现代 C++ 代码中,推荐使用
++it
,尤其是在 STL 算法和范围for
循环中:std::vector<int> v = {1, 2, 3}; for (auto it = v.begin(); it != v.end(); ++it) { // ✅ 使用 ++it std::cout << *it << " "; }
🔹 这样做的优势
- 兼容所有 STL 容器的迭代器
- 避免
it++
可能的额外拷贝,提高性能
5. 只有在需要保留旧值时使用
it++
唯一适用
it++
的情况是 必须保留递增前的值,例如:std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); auto old = it++; // ✅ 需要使用旧值,所以用 it++ std::cout << *old << std::endl; // 输出 1 std::cout << *it << std::endl; // 输出 2
🔹 这里
old
需要存储it
递增前的值,所以必须用it++
,否则++it
会直接修改it
,无法得到旧值。
6. 结论
✅ 推荐使用
++it
(前置递增)
- 不会创建临时对象,适用于所有 STL 迭代器类型。
- 对于复杂迭代器(如
std::list<int>::iterator
)性能提升明显。- 符合 C++ STL 算法和惯例,如
std::for_each
和std::find
等。❌
it++
(后置递增)
- 需要创建一个额外的临时对象,可能导致性能下降。
- 适用于需要保留旧值的情况(如删除操作)。
- 在 C++ 中,如果你使用 后置递增 (
it++
),编译器会额外创建一个临时迭代器对象,这个临时对象的生命周期仅限于operator++(int)
调用的过程中。在执行完该操作之后,这个临时对象会被销毁。总之,C++ 迭代器的特点决定了前置递增 (
++it
) 是更高效、更通用的选择,特别是在 STL 代码和现代 C++ 开发中。 🚀