【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++的两个小语法
关键字auto
范围for循环
方便后面我们进行模拟实现。
在 C语言 和 C++ 中,auto 的含义有所不同:
C 语言中的 auto
auto
: 是 C 语言的存储类型说明符
,用于声明具有自动存储期的局部变量
具有自动存储期的变量在进入声明它的程序块时被创建,在该程序块活动时存在,退出程序块时被撤销。
在函数内部定义的变量,若未声明为其他存储类型(如:
static
、register
、extern
),默认就是自动变量,所以实际中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
声明指针时,auto
和auto*
等价(*
可加可不加),因为编译器会根据初始化表达式自动推导为指针类型
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均为int auto* 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 <iostream>
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 <iostream>
#include <string>
#include <map>
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<std::string, std::string>::iterator it = dict.begin();//使用auto自动推导迭代器类型(现代C++推荐写法):auto it = dict.begin(); // it会被自动推导为std::map<std::string, std::string>::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::vector
、std::list
)、初始化列表等。
范围 for循环工作原理:
迭代对象
:
- 对于
数组
,直接遍历数组的每个元素。- 对于
标准库容器
(如:vector
、map
),使用容器的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 <vector>std::vector<int> vec = {1, 2, 3}; for (auto& num : vec) // 使用引用允许修改元素 { num *= 2; }
3. 遍历 map
#include <map>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 <iostream>
#include <string>
#include <map>
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” 的对象,提供了一种统一的方式来操作不同类型的容器,使得代码可以不依赖于具体容器的内部结构,从而增强代码的通用性和可移植性。
迭代器的核心作用:
遍历容器元素
迭代器可以像指针一样逐一遍历容器中的元素,支持向前或向后移动(取决于容器类型和迭代器种类)访问容器数据
通过迭代器,可以读取或修改容器中的元素(取决于迭代器的类型是否支持写操作)统一容器操作接口
C++ 标准库中的算法(如:sort
、find
、for_each
等)都依赖迭代器来操作容器,使得同一套算法可以适配不同类型的容器(如:vector
、list
、set
等)
2. 迭代器有哪些?
注:迭代器可以根据不同的划分方式划分出不同的迭代器:下面的我们将介绍以下的两种划分方式。
2.1:按“功能强弱”进行划分
类型 | 功能 | 支持操作 |
---|---|---|
输入迭代器 | 只能读取 容器元素单向移动 (只能递增)不支持重复读取(类似一次性指针) | ++it (递增)*it (解引用读取)== 和 != (比较) |
输出迭代器 | 只能写入 容器元素单向移动 (只能递增)不支持读取 | ++it (递增)*it = value (赋值写入) |
前向迭代器 | 支持 读取和写入 (若容器允许)单向移动 可 多次访问 同一元素 | 输入迭代器 + 可保存状态(如多次解引用同一迭代器) |
双向迭代器 | 支持 前后双向 移动(递增和递减) | 前向迭代器 + --it (递减) |
随机访问迭代器 | 支持 随机访问 元素(类似指针算术运算)可直接跳跃到任意位置 | 双向迭代器 + it + n 、it - 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 <iostream>
#include <string>
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;
}