当前位置: 首页 > news >正文

C++--迭代器(iterator)介绍---主要介绍vector和string中的迭代器

目录

一、迭代器(iterator)的定义 

二、迭代器的类别

三、使用迭代器

3.1 迭代器运算符

3.2 迭代器的简单应用:使用迭代器将string对象的第一个字母改为大写

3.3 将迭代器从一个元素移动到另外一个元素

3.4 迭代器运算

3.5 迭代器的复杂应用:使用迭代器完成二分搜索算法 

四、总结


一、迭代器(iterator)的定义 

迭代器(iteratior)是一种类型【这里的类型不是基本数据类型(int、double等),而是一种STL容器内部定义的“类型”】,用于访问容器中的元素或者在元素之间移动。

🚀 一句话总结 迭代器是 STL 容器内部定义的一种特殊类型,本质是指针或指针封装的类,用于遍历和操作容器中的元素。

C++ 迭代器(Iterator)是一种 用于遍历 STL 容器(如 vectorlistmap)的对象,本质上是一个 封装了指针或遍历逻辑的类或结构体。它支持 * 解引用、++ 递增等操作,使用户能够 像指针一样访问容器元素

根据 访问能力,迭代器分为 输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器,是 STL 算法和容器操作的核心组件

在 C++ 标准库中,stringvector 等容器 支持下标运算符[])来访问元素,但更通用的方式是 使用迭代器。所有 标准库容器 都支持迭代器,但只有少数容器(如 vectorstring)同时支持 下标访问

二、迭代器的类别

C++ STL 中的迭代器按照访问能力和功能不同,分为五大类别,每种类别都对应不同的数据结构:

  1. 输入迭代器(Input Iterator):只能 单向读取 数据,例如 std::istream_iterator
  2. 输出迭代器(Output Iterator):只能 单向写入 数据,例如 std::ostream_iterator
  3. 前向迭代器(Forward Iterator):支持 单向遍历,但可读可写,例如 std::forward_list<int>::iterator
  4. 双向迭代器(Bidirectional Iterator):支持 前进和后退,例如 std::list<int>::iterator
  5. 随机访问迭代器(Random Access Iterator):支持 任意位置访问,例如 std::vector<int>::iterator

在 C++ STL 中,每种迭代器类型通常都是一个 ,而不是简单的指针。这是因为:

  1. 封装底层数据结构(如 list 需要存储当前节点并跳转到下一个)。
  2. 提供更多功能(如 ++ 运算、访问 map 的键值对)。
  3. 支持不同的数据结构(如 map 使用红黑树,迭代器需要中序遍历逻辑)。

🚀 但在某些情况下(如 vector),迭代器可能就是指针,以保持最高效的访问速度

📌 一句话总结 👉 STL 迭代器通常是一个封装了遍历逻辑的类,而不仅仅是一个指针,以适应不同的数据结构和访问模式。

三、使用迭代器

和指针不一样的是,获取容器的迭代器时,不是使用取地址符 &,而是调用容器提供的成员函数,因为 STL 容器都有返回迭代器的成员函数(如 begin()end())。其中begin()成员负责返回指向第一个元素(或第一个字符)的迭代器。如有下述语句:

// 由编译器决定b和e的类型
// b表示v的第一个元素,e表示v尾元素的下一位置
auto b=v.begin(),e=v.end(); // b和e的类型相同

为什么 be 的类型由编译器决定?在 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; // 输出:4auto 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);所有迭代器(如 listvectorO(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>::iteratorstd::cout<<"*iter1:"<<*iter1<<std::endl; // 输出:*iter:1return 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 25std::cout << (*iter).name << " " << (*iter).age << std::endl; // Alice 25return 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;   // 输出:1iter++;std::cout << *iter << std::endl;   //输出:2iter--;std::cout << *iter << std::endl;   //输出:1return 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>::iteratorauto iter2=nums1.begin(); // iter2的类型是 std::vector<int>::iteratorif(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>::iteratorauto iter2=nums2.begin(); // iter2的类型是 std::vector<int>::iteratorif(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; // 输出:stringif(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; // 输出:stringfor(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 iter1auto iter2=iter1+4; // iter2指示n这个字符 std::__cxx11::string::iterator iter2auto it=iter2-iter1; // it 是 std::ptrdiff_t,存储两个迭代器的距离(4)std::cout<<it<<std::endl; // 输出:4std::cout<<*it<<std::endl;// ❌错误:it 是整数,不能解引用
}

 

在 C++ 标准库中:

  • std::ptrdiff_t 是一个 整数类型,专门用于存储两个迭代器之间的距离(偏移量)。
  • 它的本质是一个 signed integer,通常等价于 longint(取决于平台)。

注意:两个迭代器之间不支持加法运算。 

 迭代器之间的关系运算符(<、>、<=、>=):

#include<iostream>
#include<string>int main(){std::string s("string");auto iter1 = s.begin(); //std::__cxx11::string::iterator iter1auto iter2=iter1+4; // iter2指示n这个字符 std::__cxx11::string::iterator iter2auto it1=iter2<iter1; // it1 的类型是bool类型std::cout<<it1<<std::endl; // 输出:0auto 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==searchNumreturn 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; // 输出:4return 0;
}

上述代码的思路源自于我的另一篇博客:leetcode704------二分法查找有序数组中特定的值_二分法算法详细步骤是什么-CSDN博客 

四、总结

米哈游一面:C++迭代器和指针的异同?

迭代器(Iterator)和指针(Pointer)在 C++ 中有一些相似之处,但它们的本质和用途有所不同。下面是它们的相同点不同点的详细对比:


相同点:

  1. 都可以用于访问和操作元素

    • 迭代器和指针都可以用来遍历数据结构(如数组、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
      
  2. 支持指针运算

    • 迭代器和指针都支持类似的操作,比如 * 解引用、++ 递增、-- 递减等。
    • 例如:
      ++it;   // 迭代器前进到下一个元素
      ++ptr;  // 指针也可以前进到下一个元素
      
  3. 都可以用于 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 容器都支持。仅适用于 vectorarray,不能用于 listmap
类型安全性迭代器是类型安全的,能确保不会随意访问无效区域。指针可能会指向非法地址,导致程序崩溃。

示例代码对比

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 容器(如 vectorlistmap)。
  • 需要提高 代码的可读性安全性(避免指针越界)。
  • 需要使用 STL 算法(如 std::findstd::sort)。

使用指针的情况:

  • 需要操作 原始数组C 风格的字符串
  • 需要进行 手动内存管理(如 malloc/freenew/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;
}

🔴 编译错误

error: no match for ‘operator<<’ (operand types are ‘std::ostream’ and ‘std::vector<int>::iterator’)
在 C++ 中, 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;  // ✅ 正确,输出 10return 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*itint
  • std::map<K, V>::iterator*itstd::pair<const K, V>
  • std::set<T>::iterator*itconst 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;  // 输出 2it++;  // ✅ 后置递增,返回旧值但不使用std::cout << *it << std::endl;  // 输出 3return 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; }  // ✅ 前置递增直接修改 ptrlist_iterator operator++(int) { list_iterator temp = *this; // ❌ 额外创建临时对象ptr = ptr->next; return temp;}
};

🔹 it++ 需要拷贝 list_iterator,增加了不必要的临时对象和析构调用,而 ++it 直接操作指针,性能更优。


3. 适用于所有迭代器类型

不同类型的迭代器性能差异较大

迭代器类型典型容器++itit++
指针(原生迭代器)int*无额外开销无额外开销
随机访问迭代器std::vector<int>::iterator性能相同几乎无额外开销
双向/前向迭代器std::list<int>::iterator更快(不创建临时对象)额外开销(拷贝迭代器对象)
输入/输出迭代器std::istream_iterator<int>推荐可能导致未定义行为
  • 对于指针(如 int*),++itit++ 没有太大区别,但对于复杂的迭代器(如 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) {  // ✅ 使用 ++itstd::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_eachstd::find 等。

it++(后置递增)

  • 需要创建一个额外的临时对象,可能导致性能下降。
  • 适用于需要保留旧值的情况(如删除操作)。
  • 在 C++ 中,如果你使用 后置递增 (it++),编译器会额外创建一个临时迭代器对象,这个临时对象的生命周期仅限于 operator++(int) 调用的过程中。在执行完该操作之后,这个临时对象会被销毁。

总之,C++ 迭代器的特点决定了前置递增 (++it) 是更高效、更通用的选择,特别是在 STL 代码和现代 C++ 开发中。 🚀

相关文章:

C++--迭代器(iterator)介绍---主要介绍vector和string中的迭代器

目录 一、迭代器&#xff08;iterator&#xff09;的定义 二、迭代器的类别 三、使用迭代器 3.1 迭代器运算符 3.2 迭代器的简单应用&#xff1a;使用迭代器将string对象的第一个字母改为大写 3.3 将迭代器从一个元素移动到另外一个元素 3.4 迭代器运算 3.5 迭代器的复…...

RuleOS:区块链开发的“新引擎”,点燃Web3创新之火

RuleOS&#xff1a;区块链开发的“新引擎”&#xff0c;点燃Web3创新之火 在区块链技术的浪潮中&#xff0c;RuleOS宛如一台强劲的“新引擎”&#xff0c;为个人和企业开发去中心化应用&#xff08;DApp&#xff09;注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…...

机器学习之强化学习

引言 在人工智能的众多分支中&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&#xff09; 因其独特的学习范式而备受关注。与依赖标注数据的监督学习或探索数据结构的无监督学习不同&#xff0c;强化学习的核心是智能体&#xff08;Agent&#xff09;通过与环境…...

基于 uni-app 和 Vue3 开发的汉字书写练习应用

基于 uni-app 和 Vue3 开发的汉字书写练习应用 前言 本文介绍了如何使用 uni-app Vue3 uview-plus 开发一个汉字书写练习应用。该应用支持笔画演示、书写练习、进度保存等功能&#xff0c;可以帮助用户学习汉字书写。 在线演示 演示地址: http://demo.xiyueta.com/case/w…...

每天五分钟深度学习PyTorch:向更深的卷积神经网络挑战的ResNet

本文重点 ResNet大名鼎鼎,它是由何恺明团队设计的,它获取了2015年ImageNet冠军,它很好的解决了当神经网络层数过多出现的难以训练的问题,它创造性的设计了跳跃连接的方式,使得卷积神经网络的层数出现了大幅度提升,设置可以达到上千层,可以说resnet对于网络模型的设计具…...

electron + vue3 + vite 主进程到渲染进程的单向通信

用示例讲解下主进程到渲染进程的单向通信 初始版本项目结构可参考项目&#xff1a;https://github.com/ylpxzx/electron-forge-project/tree/init_project 主进程到渲染进程&#xff08;单向&#xff09; 以Electron官方文档给出的”主进程主动触发动作&#xff0c;发送内容给渲…...

《白帽子讲 Web 安全》之身份认证

目录 引言 一、概述 二、密码安全性 三、认证方式 &#xff08;一&#xff09;HTTP 认证 &#xff08;二&#xff09;表单登录 &#xff08;三&#xff09;客户端证书 &#xff08;四&#xff09;一次性密码&#xff08;OTP&#xff09; &#xff08;五&#xff09;多因…...

postgrel

首先按照惯例&#xff0c;肯定是需要对PostgreSQL数据库进行一系列信息收集的&#xff0c;常用的命令有以下这些&#xff1a;-- 版本信息select version();show server_version;select pg_read_file(PG_VERSION, 0, 200);-- 数字版本信息包括小版号SHOW server_version_num;SEL…...

Java基础——java8+新特性——方法引用(::)

1. 什么是方法引用&#xff1f; 定义&#xff1a;Java 8 引入的语法糖&#xff0c;用于 简化 Lambda 表达式&#xff0c;直接引用已有的方法。 符号&#xff1a;使用 :: 双冒号操作符。 本质&#xff1a;将方法作为函数式接口的实例传。 2. 方法引用的四种类型 类型 语法 …...

基于SpringBoot的商城管理系统(源码+部署教程)

运行环境 数据库&#xff1a;MySql 编译器&#xff1a;Intellij IDEA 前端运行环境&#xff1a;node.js v12.13.0 JAVA版本&#xff1a;JDK 1.8 主要功能 基于Springboot的商城管理系统包含管理端和用户端两个部分&#xff0c;主要功能有&#xff1a; 管理端 首页商品列…...

uniapp实现的个人中心页面(仿小红书)

采用 uniapp 实现的一款仿小红书个人中心页面模板&#xff0c;支持vue2、vue3, 同时适配H5、小程序等多端多应用。 简约美观大方 可到插件市场下载尝试&#xff1a; https://ext.dcloud.net.cn/plugin?id22516 示例...

K8s面试题总结(十一)

1.如何优化docker镜像的大小&#xff1f; 使用多阶段构建&#xff08;multi-stage build&#xff09;选择更小的基础镜像&#xff08;如alpine&#xff09;减少镜像层数&#xff0c;合并RUN命令 2.请解释Docker中的网络模式&#xff08;如bridge,host,none) Bridge&#xff…...

用CMake编译glfw进行OpenGL配置,在Visual Studio上运行

Visual Studio的下载 Visual Studio 2022 C 编程环境 GLFW库安装 GLFW官网地址 GLFW官网地址&#xff1a;https://www.glfw.org下载相应版本&#xff0c;如下图&#xff1a; CMake软件进行编译安装 下载CMake 下载的如果是源码包&#xff0c;需要下载CMake软件进行编译安装…...

仿12306项目(4)

基本预定车票功能的开发 对于乘客购票来说&#xff0c;需要有每一个车次的余票信息&#xff0c;展示给乘客&#xff0c;供乘客选择&#xff0c;因此首个功能是余票的初始化&#xff0c;之后是余票查询&#xff0c;这两个都是控台端。对于会员端的购票&#xff0c;需要有余票查询…...

【GPT入门】第9课 思维树概念与原理

【GPT入门】第9课 思维树概念与原理 1.思维树概念与原理2. 算24游戏的方法 1.思维树概念与原理 思维树&#xff08;Tree of Thought&#xff0c;ToT &#xff09;是一种大模型推理框架&#xff0c;旨在解决更加复杂的多步骤推理任务&#xff0c;让大模型能够探索多种可能的解决…...

uniapp登录用户名在其他页面都能响应

使用全局变量 1、在APP.vue中定义一个全局变量&#xff0c;然后在需要的地方引用它&#xff1b; <script>export default {onLaunch: function() {console.log(App Launch)this.globalData { userInfo: {} };},onShow: function() {console.log(App Show)},onHide: fu…...

一周热点-OpenAI 推出了 GPT-4.5,这可能是其最后一个非推理模型

在人工智能领域,大型语言模型一直是研究的热点。OpenAI 的 GPT 系列模型在自然语言处理方面取得了显著成就。GPT-4.5 是 OpenAI 在这一领域的又一力作,它在多个方面进行了升级和优化。 1 新模型的出现 GPT-4.5 目前作为研究预览版发布。与 OpenAI 最近的 o1 和 o3 模型不同,…...

Linux基础--用户管理

目录 查看用户 使用命令: id 创建用户 使用命令: useradd ​编辑 为用户设置密码 使用命令: passwd ​编辑 删除用户 使用命令: userdel 创建用户组 使用命令: groupadd 删除用户组 使用命令: groupdel 用户设置 使用命令: usermod 将用户从组中去除 使用…...

软件测试的基础入门(二)

文章目录 一、软件&#xff08;开发&#xff09;的生命周期什么是生命周期软件&#xff08;开发&#xff09;的生命周期需求分析计划设计编码测试运行维护 二、常见的开发模型瀑布模型流程优点缺点适应的场景 螺旋模型流程优点缺点适应的场景 增量模型和迭代模型流程适应的场景…...

【SpringMVC】深入解析@ RequestMapping 注解的概念及使用和 MVC 介绍

Spring Web MVC入门 1. Spring Web MVC 什么是 Spring Web MVC&#xff1f; MVC官方文档介绍 Spring Web MVC是Spring框架中的一个用来做网站开发的部分&#xff0c;它是基于Servlet技术的。 虽然它的正式名字叫“Spring Web MVC”&#xff0c;但大家一般都简称它“SpringMVC”…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验

2024年初&#xff0c;人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目&#xff08;一款融合大型语言模型能力的云端AI编程IDE&#xff09;时&#xff0c;技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力&#xff0c;TRAE在WayToAGI等…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

GeoServer发布PostgreSQL图层后WFS查询无主键字段

在使用 GeoServer&#xff08;版本 2.22.2&#xff09; 发布 PostgreSQL&#xff08;PostGIS&#xff09;中的表为地图服务时&#xff0c;常常会遇到一个小问题&#xff1a; WFS 查询中&#xff0c;主键字段&#xff08;如 id&#xff09;莫名其妙地消失了&#xff01; 即使你在…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统

核心速览 研究背景 ​​研究问题​​&#xff1a;这篇文章要解决的问题是当前大型语言模型&#xff08;LLMs&#xff09;在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色&#xff0c;但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成&#xff08;RA…...