> 技术文档 > 《C++初阶之STL》【auto关键字 + 范围for循环 + 迭代器】_c++ auto循环

《C++初阶之STL》【auto关键字 + 范围for循环 + 迭代器】_c++ auto循环


【auto关键字 + 范围for循环 + 迭代器】目录

  • 前言:
  • --------------- auto关键字 ---------------
    • 1. 什么是auto?
    • 2. 使用关键字auto时需要注意什么?
    • 3. 怎么使用auto关键字?
  • --------------- 范围for循环 ---------------
    • 1. 什么是范围for循环?
    • 2. 怎么使用范围for循环?
    • 3. 范围for循环有什么优势?
  • --------------- 迭代器 ---------------
    • 1. 什么是迭代器?
    • 2. 迭代器有哪些?
      • 2.1:按“功能强弱”进行划分
      • 2.2:按“读写权限+遍历方向”进行划分
    • 3. 怎么使用迭代器?

在这里插入图片描述

往期《C++初阶》回顾:

/------------ 入门基础 ------------/
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】
/------------ 类和对象 ------------/
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
【初始化列表 + 自定义类型转换 + static成员】
【友元 + 内部类 + 匿名对象】
【经典案例:日期类】
/------------ 内存管理 ------------/
【内存分布 + operator new/delete + 定位new】
/------------ STL ------------/
【泛型编程 + STL简介】

前言:

Hi~ 小伙伴们大家好呀 (●’◡’●)!这周末我们就要进入 “三伏天” 的入伏阶段啦🔥,一年中最热、最潮湿的时期即将正式开启,大家一定要注意做好防暑措施哦~🌞💦

今天我们要学习的【auto 关键字 + 范围 for 循环 + 迭代器】内容📚,主要是为后面学习 STL 容器打基础的哟 (。・ω・。)ノ♡
虽然这部分知识相对简单(◕‿◕✿),可大家也不能掉以轻心,要认真掌握呀(ง •_•)ง

--------------- auto关键字 ---------------

1. 什么是auto?

:在进行string类的模拟实现之前,我们要先来学习一下C++的两个小语法

  1. 关键字auto
  2. 范围for循环

方便后面我们进行模拟实现。

在 C语言 和 C++ 中,auto 的含义有所不同:

C 语言中的 auto

auto: 是 C 语言的 存储类型说明符,用于声明具有 自动存储期的局部变量

  • 具有自动存储期的变量在进入声明它的程序块时被创建,在该程序块活动时存在,退出程序块时被撤销。

  • 在函数内部定义的变量,若未声明为其他存储类型(staticregisterextern ),默认就是自动变量,所以实际中auto关键字常被省略 。例如int a = 0;auto int a = 0; 是等价的。

  • 另外

    • 注意一:用auto声明变量时可不进行初始化。
    • 注意二:当省略数据类型时,auto修饰的变量默认为int型 。

C++中的 auto

  • C++98 和 C++03 标准:与 C 语言中 auto 的含义一致,用于声明自动变量,但因即使不使用 auto 声明,变量也拥有自动生命期,所以该用法多余且极少使用 。

  • C++11 及以后标准auto 被重新定义为自动推断变量类型的 类型指示符

    • 使用 auto 定义变量时必须进行初始化。

    • 在编译阶段,编译器会根据初始化表达式来推导 auto 实际代表的类型,此时 auto 只是一个类型声明时的 “占位符” 。

      auto num = 10; // num会被推导为int类型auto str = std::string(\"hello\"); // str会被推导为std::string类型

在 C++ 后续标准中,auto 的功能进一步扩展:

C++14

  • auto可用于推导普通函数的返回类型

    • 例如auto func() { return 42; } ,编译器会根据return语句推导出函数返回类型为int
  • auto可作为泛型 Lambda 表达式的参数类型提高代码复用性

C++17

  • 引入模板参数推导,允许使用 auto 指定函数模板参数类型时,编译器可根据实参推导模板参数类型
  • 引入结构化绑定,允许使用 auto 解构数组结构体tuple方便访问复合数据类型元素

总结:auto 在 C++ 中的应用,尤其是在编写模板代码或处理复杂类型时,能大大简化代码编写,提高编程效率 。

2. 使用关键字auto时需要注意什么?

在 C++ 中使用auto关键字时,需要注意以下几点:

1. 必须初始化:

  • auto 必须通过初始化表达式推导类型,否则会导致编译错误。

    auto x; // 错误:未初始化,无法推导类型auto x = 10; // 正确:根据10推导为int

2. 推导规则可能与预期不符:

(1)忽略顶层const引用

  • auto 会忽略初始化表达式的顶层const引用属性,除非显式指定

    const int a = 10;auto b = a; // b的类型是int(忽略顶层const)auto& c = a; // c的类型是const int&(保留const) int x = 10;int& ref = x;auto y = ref; // y的类型是int(忽略引用)

(2)数组函数会退化为指针

  • 当初始化表达式是数组函数时,auto 会将其推导为指针类型除非使用decltype(auto)

    int arr[5] = {1, 2, 3, 4, 5};auto ptr = arr; // ptr的类型是int*(数组退化为指针)

3. 声明指针或引用时的语法差异

  • 指针类型:使用auto声明指针时,autoauto*等价(*可加可不加),因为编译器会根据初始化表达式自动推导为 指针类型

    int* p = new int(10); auto ptr1 = p; // ptr1类型为int*auto* ptr2 = p; // ptr2类型也为int*(与ptr1等价)
  • 引用类型:声明引用时必须显式使用&,否则auto会推导为 值类型(非引用)

    int x = 20; auto& ref = x; // 正确:ref为int&(引用)auto val = x; // 错误:val为int(值类型,非引用)

4. 同一行声明多个变量时类型必须一致

当在同一行使用 auto 声明多个变量时,所有变量的类型必须完全一致,否则会编译报错。

  • 因为:编译器仅对第一个变量的类型进行推导,其他变量强制使用该类型。

    //错误示例:auto a = 10, b = 3.14; // 错误:a推导为int,b推导为double(类型不一致)auto* p1 = &a, p2 = &b; // 若a和b类型不同,p2可能为不同类型的指针 //正确示例:auto a = 10, b = 20; // 正确:a和b均为intauto* p1 = &a, p2 = &b; // 正确:p1和p2均为int*(假设a和b为int)

5. 不能作为函数参数,但可作为返回值(谨慎使用)

  • 作为函数参数auto 无法用于函数参数的类型声明。

    • 因为:函数参数需要明确的类型。

      // 错误示例:void func(auto x); // 错误:auto不能作为函数参数类型
  • 作为函数返回值:C++14 允许auto作为函数返回类型(需通过return语句推导唯一类型),但需注意:

    • 函数体必须可见不能在头文件中声明后在源文件中定义

    • 若存在多个return语句,推导的类型必须一致

    auto add(int a, int b) // C++14及以后{ return a + b; // 返回类型推导为int}

3. 怎么使用auto关键字?

代码示例1

#include using namespace std;//可以作返回值,但建议谨慎使用(需确保返回类型明确)auto func(){return 3; //返回类型被推导为int}int main(){cout << \"============== 基础类型推导 ==============\" << endl;int a = 10;auto b = a; // b的类型推导为intauto c = \'a\'; // c的类型推导为charauto d = func(); // d的类型由func1()的返回类型决定//错误示例:auto变量必须初始化,编译器无法推导未初始化变量的类型//auto e; // 缺少初始化表达式// 打印类型信息(不同编译器输出可能不同)cout << \"b的类型是:\" << typeid(b).name() << endl; //可能输出\"int\"cout << \"c的类型是:\" << typeid(c).name() << endl; //可能输出\"char\"cout << \"d的类型是:\" << typeid(d).name() << endl; //取决于func1()的返回类型cout << \"============== 指针和引用推导 ==============\" << endl;int x = 10;//知识点1:使用auto声明指针时,auto和auto*等价(*可加可不加)auto y = &x; // y的类型推导为int*auto* z = &x; // 显式指定指针类型,z的类型为int*//知识点2:声明引用时必须显式使用&auto& w = x; // w的类型推导为int&cout << \"y的类型是:\" << typeid(y).name() << endl; // int*cout << \"z的类型是:\" << typeid(z).name() << endl; // int*cout << \"w的类型是:\" << typeid(w).name() << endl; // 注意:这里输出的是int,但是其实w的类型是:int&//因为:引用类型被 “剥除”:当对引用类型使用 typeid 时,返回的是被引用对象的类型,而非引用类型本身cout << \"============== 多变量声明限制 ==============\" << endl;auto aa = 1, bb = 2; // 合法:aa和bb都被推导为intcout << \"aa的类型是:\" << typeid(aa).name() << endl; // intcout << \"bb的类型是:\" << typeid(bb).name() << endl; // int//错误示例://auto cc = 3, dd = 4.0; // cc是int,dd是double//auto多变量声明时,所有变量必须推导为同一类型cout << \"============== 数组类型限制 ==============\" << endl;//错误示例:auto不能用于声明数组类型//auto array[] = { 4, 5, 6 };return 0;}

在这里插入图片描述

代码示例2

#include #include #include using namespace std;int main(){/*------------------创建一个map字典------------------*/// 创建一个map字典,存储英文单词到中文的映射// key类型为std::string,value类型为std::stringstd::map<std::string, std::string> dict = {{ \"apple\", \"苹果\" }, // 键值对1{ \"orange\", \"橙子\" }, // 键值对2 { \"pear\", \"梨\" } // 键值对3};/*------------------创建一个迭代器------------------*/ //原始写法(较冗长):// std::map::iterator it = dict.begin();//使用auto自动推导迭代器类型(现代C++推荐写法):auto it = dict.begin(); // it会被自动推导为std::map::iterator类型/*------------------使用迭代器遍历map字典------------------*/while (it != dict.end()){//1.输出当前键值对//1.1:it->first 表示key(英文单词)//1.2:it->second 表示value(中文翻译)cout << it->first << \":\" << it->second << endl;//2.移动到下一个元素++it;}return 0;}

在这里插入图片描述

--------------- 范围for循环 ---------------

1. 什么是范围for循环?

范围for循环(Range-based for loop):是 C++11 引入的一种语法糖,用于简化遍历容器序列的过程。

  • 传统的 for 循环需要显式指定循环范围,不仅代码冗长,还容易因索引越界等问题引入错误,而基于范围的 for 循环则提供了更简洁、易读的语法,避免了传统 for 循环中迭代器索引的显式使用,自动完成迭代过程。
  • 从实现原理上看,范围 for 循环是迭代器模式的语法糖。 编译器会自动将其转换为等价的迭代器遍历代码,包括迭代器的获取元素访问边界判断。这种转换在汇编层面表现为与手动编写的迭代器代码基本一致,因此不会引入额外的性能开销。

2. 怎么使用范围for循环?

范围 for循环基本语法:

for (元素类型 变量名 : 容器/序列) { // 使用变量名访问当前元素}
  • declaration:定义一个变量,用于存储每次迭代时从 range 中取出的元素。
  • range:表示要遍历的范围,可以是数组、容器(std::vectorstd::list)、初始化列表等。

范围 for循环工作原理:

  • 迭代对象
    • 对于 数组,直接遍历数组的每个元素。
    • 对于 标准库容器vectormap),使用容器的begin()end()迭代器。
    • 对于 自定义类型,需提供begin()end()成员函数或全局函数。
  • 变量类型
    • 可使用 auto 自动推导元素类型
    • 若需修改元素值,应声明为引用类型(auto&const auto&

代码片段示例:

1. 遍历数组

int arr[] = {1, 2, 3, 4, 5};for (int num : arr) { std::cout << num << \" \"; // 输出: 1 2 3 4 5}

2. 遍历 vector

#include std::vector<int> vec = {1, 2, 3};for (auto& num : vec) // 使用引用允许修改元素{ num *= 2;}

3. 遍历 map

#include std::map<int, std::string> dict = { {1, \"one\"}, {2, \"two\"}};for (const auto& pair : dict) { std::cout << pair.first << \": \" << pair.second << \"\\n\";}

4. 初始化列表

for (int x : {10, 20, 30}) { std::cout << x << \" \"; // 输出: 10 20 30}

3. 范围for循环有什么优势?

#include #include #include using namespace std;int main(){ // 定义一个包含5个整数的数组 int array[] = { 1, 2, 3, 4, 5 }; /******************** C++98 风格的遍历 ********************/ /* * 传统遍历方式特点: * 1. 需要手动计算数组长度 * 2. 使用下标访问元素 * 3. 需要维护循环变量i */ //使用for循环修改数组中的元素 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) { array[i] *= 2; } //使用for循环输出修改后的数组 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) { cout << array[i] << \" \"; } cout << endl; /******************** C++11 风格的遍历 ********************/ /* * 范围for循环特点: * 1. 自动推导元素类型(使用auto) * 2. 自动处理数组边界 * 3. 代码更简洁 */ //使用引用修改元素(auto&) for (auto& e : array) e *= 2; //使用值访问元素(auto) for (auto e : array) cout << e << \" \"; cout << endl; /******************** 字符串遍历示例 ********************/ string str(\"hello world\"); /* * 字符串遍历说明: * 1. auto自动推导为char类型 * 2. 不需要考虑字符串长度 * 3. 可以方便地处理每个字符 */ for (auto ch : str) { cout << ch << \" \"; // 输出:h e l l o w o r l d } cout << endl; return 0;}

在这里插入图片描述

--------------- 迭代器 ---------------

1. 什么是迭代器?

迭代器(Iterator):是一种抽象的编程概念,用于在容器(:数组、链表、集合、映射等)中遍历元素,并访问容器中的数据,而无需暴露容器的底层实现细节。

  • 它本质上是一个 “指针 - like” 的对象,提供了一种统一的方式来操作不同类型的容器,使得代码可以不依赖于具体容器的内部结构,从而增强代码的通用性和可移植性。

迭代器的核心作用:

  1. 遍历容器元素
    迭代器可以像指针一样逐一遍历容器中的元素,支持向前或向后移动(取决于容器类型和迭代器种类)
  2. 访问容器数据
    通过迭代器,可以读取或修改容器中的元素(取决于迭代器的类型是否支持写操作)
  3. 统一容器操作接口
    C++ 标准库中的算法(如:sortfindfor_each 等)都依赖迭代器来操作容器,使得同一套算法可以适配不同类型的容器(如:vectorlistset 等)

2. 迭代器有哪些?

:迭代器可以根据不同的划分方式划分出不同的迭代器:下面的我们将介绍以下的两种划分方式。

2.1:按“功能强弱”进行划分

类型 功能 支持操作 输入迭代器 只能读取 容器元素
单向移动(只能递增)
不支持重复读取(类似一次性指针) ++it(递增)
*it(解引用读取)
==!=(比较) 输出迭代器 只能写入 容器元素
单向移动(只能递增)
不支持读取 ++it(递增)
*it = value(赋值写入) 前向迭代器 支持 读取和写入(若容器允许)
单向移动
多次访问 同一元素 输入迭代器
+
可保存状态(如多次解引用同一迭代器) 双向迭代器 支持 前后双向 移动(递增和递减) 前向迭代器
+
--it(递减) 随机访问迭代器 支持 随机访问 元素(类似指针算术运算)
可直接跳跃到任意位置 双向迭代器
+
it + nit - n
it[n]
it1 - it2(计算距离)
<, <=, >, >=(比较大小)

在这里插入图片描述

2.2:按“读写权限+遍历方向”进行划分

迭代器类型 说明 典型获取方式 iterator 可读写正向遍历 容器元素的迭代器
在容器中正常顺序地访问和修改元素 begin()end() const_iterator 只读正向遍历 容器元素的迭代器
用于在不修改容器元素的情况下,按正常顺序遍历容器 cbegin()cend() reverse_iterator 可读写反向遍历 容器元素的迭代器
用于以逆序方式访问和修改容器元素 rbegin()rend() const_reverse_iterator 只读反向遍历 容器元素的迭代器
用于在不修改容器元素的前提下,逆序遍历容器 crbegin()crend()

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. 怎么使用迭代器?

#include #include using namespace std;int main(){string s(\"Hello, World!\");cout << \"原始字符串: \" << s << endl << endl;/*------------------使用iterator正向遍历(可读写)------------------*/cout << \"iterator 正向遍历: \";for (string::iterator it = s.begin(); it != s.end(); ++it) {cout << *it; //读取元素*it = toupper(*it); //修改元素(转换为大写)}cout << endl << \"修改后的字符串: \" << s << endl << endl;/*------------------使用const_iterator正向遍历(只读)------------------*/cout << \"const_iterator 正向遍历: \";for (string::const_iterator cit = s.cbegin(); cit != s.cend(); ++cit) {cout << *cit; //只读访问//*cit = \'x\'; //错误:不能通过const_iterator修改}cout << endl << endl;/*------------------使用reverse_iterator反向遍历(可读写)------------------*/cout << \"reverse_iterator 反向遍历: \";for (string::reverse_iterator rit = s.rbegin(); rit != s.rend(); ++rit) {cout << *rit; //反向读取元素*rit = tolower(*rit); //修改元素(转换为小写)}cout << endl << \"再次修改后的字符串: \" << s << endl << endl;/*------------------使用const_reverse_iterator反向遍历(只读)------------------*/cout << \"const_reverse_iterator 反向遍历: \";for (string::const_reverse_iterator crit = s.crbegin(); crit != s.crend(); ++crit) {cout << *crit; //反向只读访问// *crit = \'x\'; //错误:不能修改}cout << endl;return 0;}

在这里插入图片描述
在这里插入图片描述