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

news2025/3/9 8:32:25

目录

一、迭代器(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; // 输出: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);所有迭代器(如 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>::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,通常等价于 longint(取决于平台)。

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

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

#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++ 中有一些相似之处,但它们的本质和用途有所不同。下面是它们的相同点不同点的详细对比:


相同点:

  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;  // ✅ 正确,输出 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*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;  // 输出 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. 适用于所有迭代器类型

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

迭代器类型典型容器++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) {  // ✅ 使用 ++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_eachstd::find 等。

it++(后置递增)

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312046.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud——Consul服务注册与发现

一、为什么要引入服务注册中心 &#xff08;1&#xff09;为什么引入 微服务硬编码 IP / 端口的核心问题总结 环境变更敏感&#xff1a;当支付微服务的 IP 或端口修改时&#xff0c;订单微服务必须同步修改所有调用该支付服务的代码或配置&#xff0c;否则将无法正常通信无法…

C语言_数据结构总结5:顺序栈

纯C语言代码&#xff0c;不涉及C 想了解链式栈的实现&#xff0c;欢迎查看这篇文章&#xff1a;C语言_数据结构总结6&#xff1a;链式栈-CSDN博客 这里分享插入一下个人觉得很有用的习惯&#xff1a; 1. 就是遇到代码哪里不理解的&#xff0c;你就问豆包&#xff0c;C知道&a…

人工智能之数学基础:正交矩阵

本文重点 正交矩阵是线性代数中一个重要的特殊矩阵&#xff0c;它在许多领域都有广泛的应用。 什么是正交矩阵 如图所示&#xff0c;当矩阵A满足如上所示的条件的时候&#xff0c;此时我们就可以认为是正交矩阵&#xff0c;需要注意一点矩阵A必为方阵。 正交矩阵的充要条件 …

抓包分析工具介绍

什么是抓包分析工具&#xff1f; 抓包分析工具&#xff0c;也称为网络数据包嗅探器或协议分析器&#xff0c;用于捕获和检查网络上传输的数据包。这些数据包包含了网络通信的详细信息&#xff0c;例如请求的资源、服务器的响应、HTTP 头信息、传输的数据内容等等。通过分析这些…

2025/3/8 第 27 场 蓝桥入门赛 题解

1. 38红包【算法赛】 签到题&#xff1a; 算倍数就行了 #include <bits/stdc.h> using namespace std; int main() {int ans0;for(int i1;i<2025;i){if(i % 3 0)ans;else if(i % 8 0)ans;else if(i % 38 0)ans;}cout<<ans<<endl;return 0; } 2. 祝福…

使用Node.js从零搭建DeepSeek本地部署(Express框架、Ollama)

目录 1.安装Node.js和npm2.初始化项目3.安装Ollama4.下载DeepSeek模型5.创建Node.js服务器6.运行服务器7.Web UI对话-Chrome插件-Page Assist 1.安装Node.js和npm 首先确保我们机器上已经安装了Node.js和npm。如果未安装&#xff0c;可以通过以下链接下载并安装适合我们操作系…

deepseek 3FS编译

3FS在ubuntu22.04下的编译&#xff08;记录下编译过程&#xff0c;方便后续使用&#xff09; 环境信息 OS ubuntu 22.04内核版本 6.8.0-52-genericlibfuse 3.16.1rust 1.75.0FoundationDB 7.1.66meson 1.0.0ninja 1.10.1 libfuse编译 以下建议均在root下执行 pip3 install…

每日一练之移除链表元素

题目&#xff1a; 画图解析&#xff1a; 方法&#xff1a;双指针 解答代码&#xff08;注&#xff1a;解答代码带解析&#xff09;&#xff1a; //题目给的结构体 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* }…

力大砖飞,纯暴力搜索——蓝桥p2110(写着玩的)

#include<bits/stdc.h>const int N1000000;using namespace std;bool mp[2][N];int cnt0; int n;void dfs(int row,int col){cntcnt%1000000007;if(coln && row2){cnt;return ;}if(row>2){ //下一列 dfs(0,col1);return;}if(mp[row][col]1){ //下一行 dfs(row…

如何计算两个向量的余弦相似度

参考笔记&#xff1a; https://zhuanlan.zhihu.com/p/677639498 日常学习之&#xff1a;如何计算两个向量或者矩阵的余弦相似度-CSDN博客 1.余弦相似度定理 百度的解释&#xff1a;余弦相似度&#xff0c;又称为余弦相似性&#xff0c;是通过计算两个向量的夹角余弦值来评估…

OkHttp:工作原理 拦截器链深度解析

目录 一、OKHttp 的基本使用 1. 添加依赖 2. 发起 HTTP 请求 3. 拦截器&#xff08;Interceptor&#xff09; 4. 高级配置 二、OKHttp 核心原理 1. 责任链模式&#xff08;Interceptor Chain&#xff09; 2. 连接池&#xff08;ConnectionPool&#xff09; 3. 请求调度…

python: DDD+ORM using oracle 21c

sql script: create table GEOVINDU.School --創建表 ( SchoolId char(5) NOT NULL, -- SchoolName nvarchar2(500) NOT NULL, SchoolTelNo varchar(8) NULL, PRIMARY KEY (SchoolId) --#主鍵 );create table GEOVINDU.Teacher ( TeacherId char(5) NOT NULL , TeacherFirstNa…

基于 LeNet 网络的 MNIST 数据集图像分类

1.LeNet的原始实验数据集MNIST 名称&#xff1a;MNIST手写数字数据集 数据类型&#xff1a;灰度图 &#xff08;一通道&#xff09; 图像大小&#xff1a;28*28 类别数&#xff1a;10类&#xff08;数字0-9&#xff09; 1.通过torchvision.datasets.MNIST下载并保存到本地…

Day4 C语言与画面显示练习

文章目录 1. harib01a例程2. harib01b例程3. harib01e例程4. harib01f例程5. harib01h例程 1. harib01a例程 上一章主要是将画面搞成黑屏&#xff0c;如果期望做点什么图案&#xff0c;只需要再VRAM里写点什么就好了&#xff0c;使用nask汇编语言实现一个函数write_mem8&#…

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

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

《UE5_C++多人TPS完整教程》学习笔记34 ——《P35 网络角色(Network Role)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P35 网络角色&#xff08;Network Role&#xff09;》 的学习笔记&#xff0c;该系列教学视频为计算机工程师、程序员、游戏开发者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09; Stephe…

手写简易Tomcat核心实现:深入理解Servlet容器原理

目录 一、Tomcat概况 1. tomcat全局图 2.项目结构概览 二、实现步骤详解 2.1 基础工具包&#xff08;com.qcby.util&#xff09; 2.1.1 ResponseUtil&#xff1a;HTTP响应生成工具 2.1.2 SearchClassUtil&#xff1a;类扫描工具 2.1.3 WebServlet&#xff1a;自定义注解…

mac本地安装运行Redis-单机

记录一下我以前用的连接服务器的跨平台SSH客户端。 因为还要准备毕设...... 服务器又过期了&#xff0c;只能把redis安装下载到本地了。 目录 1.github下载Redis 2.安装homebrew 3.更新GCC 4.自行安装Redis 5.通过 Homebrew 安装 Redis 安装地址&#xff1a;https://git…

【ThreeJS Basics 09】Debug

文章目录 简介从 dat.GUI 到 lil-gui例子安装 lil-gui 并实例化不同类型的调整改变位置针对非属性的调整复选框颜色 功能/按钮调整几何形状文件夹调整 GUI宽度标题关闭文件夹隐藏按键切换 结论 简介 每一个创意项目的一个基本方面是能够轻松调整。开发人员和参与项目的其他参与…

【笔记】STM32L4系列使用RT-Thread Studio电源管理组件(PM框架)实现低功耗

硬件平台&#xff1a;STM32L431RCT6 RT-Thread版本&#xff1a;4.1.0 目录 一.新建工程 二.配置工程 ​编辑 三.移植pm驱动 四.配置cubeMX 五.修改驱动文件&#xff0c;干掉报错 六.增加用户低功耗逻辑 1.设置唤醒方式 2.设置睡眠时以及唤醒后动作 ​编辑 3.增加测试命…