【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;
}