C++ Primer 再探迭代器

歡迎閱讀我的 【C++Primer】專欄

專欄簡介:本專欄主要面向C++初學者,解釋C++的一些基本概念和基礎語言特性,涉及C++標準庫的用法,面向對象特性,泛型特性高級用法。通過使用標準庫中定義的抽象設施,使你更加適應高級程序設計技術。希望對讀者有幫助!

在這里插入圖片描述
在這里插入圖片描述

目錄

  • 10.4再探迭代器
    • 插人迭代器
    • iostream迭代器
    • 使用算法操作流迭代器
    • ostream_iterator操作
    • 使用流迭代器處理類類型
    • 反向迭代器
    • 反向迭代器需要遞減運算符

10.4再探迭代器

除了為每個容器定義的迭代器之外,標準庫在頭文件iterator中還定義了額外幾種迭代器。這些迭代器包括以下幾種。

  • 插入迭代器(insert iterator):這些迭代器被綁定到一個容器上,可用來向容器插入元素。
  • 流迭代器(stream iterator):這些迭代器被綁定到輸入或輸出流上,可用來遺歷所關聯的IO流。
  • 反向迭代器(reverse iterator):這些迭代器向后而不是向前移動。除了forward_1ist之外的標準庫容器都有反向迭代器。
  • 移動迭代器(move iterator):這些專用的迭代器不是拷貝其中的元素,而是移動它們。

插人迭代器

插入器是一種迭代器適配器,它接受一個容器,生成一個追代器,能實現向給定容器添加元素。當我們通過一個插入迭代器進行賦值時,該迭代器調用容器操作來向給定容器的指定位置插入一個元素。表10.2列出了這種迭代器支持的操作。

表10.2:插入迭代器操作

it=t在tt指定的當前位置插入值t。假定c是it綁定的容器,依賴于揣入迭代器的不同種類,此賦值會分別調用c.push_back(t)、c.push_front(t)或c.insert(tp),其中p為傳遞給inserter的迭代器位置
*it,++it,it++這些操作雖然存在,但不會對it做任何事情。每個操作都返回it

插入器有三種類型,差異在于元素插入的位置:

  • back_inserter創建一個使用push_back的迭代器。
  • front_inserter創建一個使用push_front的迭代器。
  • inserter 創建一個使用insert的迭代器。此函數接受第二個參數,這個參數必須是一個指向給定容器的迭代器。元素將被插入到給定迭代器所表示的元素之前。

只有在容器支持push_front的情況下,我們才可以使用front_inserter。類似的,只有在容器支持push_back的情況下,我們才能使用back_inserter。

理解插入器的工作過程是很重要的:當調用inserter(c,iter)時,我們得到一個迭代器,接下來使用它時,會將元素插入到itez原秈所指向的元素之前的位置。即,如果it是由inserter生成的迭代器,則下面這樣的賦值語句

*it = val;

其效果與下面代碼一樣

it=c.insert(it,val);//it指向新加入的元素
++it;//遞增it使它指向原來的元素

front_inserter生成的迭代器的行為與insertez生成的迭代器完全不一樣。當我們使用front_inserter時,元素總是插入到容器第一個元素之前。即使我們傳遞給inserter的位置原來指向第一個元素,只要我們在此元素之前插入一個新元素,此元素就不再是容器的首元素了:

list<int>lst={1,2,3,4};
list<int>lst2,lst3;//空list
//拷貝完成之后,lst2包含4 3 2 1
copy(lst.cbegin(),lst.cend(),front_tnserter(lst2));
//拷貝完成之后,lst3包含1 2 3 4
copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin()));

當調用front_inserter?時,我們得到一個插入迭代器,接下來會調用push_front。當每個元素被插入到容器c中時,它變為c的新的首元素。因此,front_inserter生成的迭代器會將插入的元素序列的順序顛倒過來,而inserter和back_inserter則不會。

iostream迭代器

雖然iostream類型不是容器,但標準庫定義了可以用于這些IO類型對象的迭代器。istream_iterator讀取輸入流,ostream_iterator向一個輸出流寫數據。這些迭代器將它們對應的流當作一個特定類型的元素序列來處理。通過使用流選代器,我們可以用泛型算法從流對象讀取數據以及向其寫入數據。istream_iterator操作當創建一個流迭代器時,必須指定迭代器將要讀寫的對象類型。一個istream_iterator使用>>來讀取流。因此,istream_iterator要讀取的類型必須定義了輸入運算符。當創建一個istream_iterator時,我們可以將它綁定到一個流。當然,我們還可以默認初始化迭代器,這樣就創建了一個可以當作尾后值使用的迭代器。

istream_iterator<int>int_it(cin);//從cin讀取int
istream_iterator<int> int_eof; // 尾后迭代器
ifstream in("afile");
istream_iterator<string>str_it(in);//從“afile“讀取字符串

下面是一個用istream_iterator從標準輸入讀取數據,存入一個vector的例子:

istream_iterator<int>in_iter(cin);//從cin讀取int
istream_iterator<int>eof;//istream尾后選代器
while(in_iter!=eof)//當有數據可供讀取時
//后置途增運算讀取流,返回選代器的舊值
//解引用選代器,獲得從流讀取的前一個值
vec.push_back(*in_iter++);

此循環從cin讀取int值,保存在vec中。在每個循環步中,循環體代碼檢查in_iter是否等于eof。eof被定義為宇的istream_iterator,從而可以當作尾后迭代器來使用。對于一個綁定到流的迭代器,一于其關聯的流遇到文件尾或遇到IO錯誤,迭代器的值就與尾后迭代器相等。

此程序最困難的部分是傳遞給push_back的參數,其中用到了解引用運算符和后置遞增運算符。該表達式的計算過程與我們之前寫過的其他結合解引用和后置遞增運算的表達式一樣。后置遞增運算會從流中讀取下一個值,向前推進,但返回的是迭代器的舊值。迭代器的舊值包含了從流中讀取的前一個值,對迭代器進行解引用就能獲得此值。

我們可以將程序重寫為如下形式,這體現了istream_iterator更有用的地方:

istream_iterator<int>in_tter(cin),eof;//從cin讀取int
vector<int>vec(in_iter,eof);//從迭代器范圍構造vec

本例中我們用一對表示元素范圍的迭代器來構造vec。這兩個迭代器是istream_iterator,這意味著元素范圍是通過從關聯的流中讀取數據獲得的。這個構造函數從cin中讀取數據,直至遇到文件尾或者遇到一個不是int的數據為止。從流中讀取的數據被用來構造vec。

表10.3:istream_iterator操作

istream_iteratorin(is);in從輸入流is讀取類型為0的值
istream_iteratorend;讀取類型為的值的istream_itterator迭代器,表示尾后位置
in1=in2in1和in2必須讀取相同類型。如果它們都是尾后迭代器,或綁定到相同
in1!=in2的輸入,則兩者相等
*in返回從流中讀取的值
n->mem與(*in).mem的含義相同
++inn++使用元素類型所定義的>>運算符從輸入流中讀取下一個值。與以往一樣,前置版本返回一個指向遞增后迭代器的引用,后置版本返回舊值

使用算法操作流迭代器

由于算法使用迭代器操作木處理數據,而流迭代器又至少支持樹些選代器操作,因此我們至少可以用桅些算法來操作流迭代器。下面是一個例子,我們可以用一對istream_iterator來調用accumulate:

istream_iterator<int>in(cin),eof;
cout<<accumulate(in,eof,0)<<endl;

此調用會計算出從標準輸入讀取的值的和。如果輸入為:

23 109 45 89 6 34 12 90 34 23 56 23 8 89 23

則輸出為664。

istream_iterator允許使用懶惰求值

當我們將一個istream_iterator綁定到一個流時,標準庫并不保證迭代器立即從流讀取數據。具體實現可以推遲從流中讀取數據,直到我們使用迭代器時才真正讀取。標準庫中的實現所保證的是,在我們第一次解引用迭代器之前,從流中讀取數據的操作已經完成了。對于大多數程序來說,立即讀取還是推遲讀取沒什么差別。但是,如果我們創建了一個istream_iterator,沒有使用就銷毀了,或者我們正在從兩個不同的對象同步讀取同一個流,那么何時讀取可能就很重要了。

ostream_iterator操作

我們可以對任何具有輸出運算符(<<運算符的類型定義ostream_iterator。當創建一個ostream_iterator時,我們可以提供(可選的)第二參數,它是一個字符流,在輸出每個元素后都會打印此字符串。此字符串必須是一個C風格字符串(即,一個字符串字面常量或者一個指向以空字符結尾的字符數組的指針)。必須將ostream_iterator綁定到一個指定的流,不允許宇的或表示尾后位置的ostream_iterator。

表10.4:ostream_iterator操作

ostream_iterator out(os)out將類型T的值寫到輸出流os中
ostream_iterator_out(os,d);out將類型為7的值寫到輸出流os中,每個值后面都輸出一個d。d指向一個空字符結尾的字符數組
out= val用<<運算法val寫入到out所綁定的ostream中。val的類型必須與out可寫的類型兼容
*out,++out,out++這些運算符是存在的,但不對out做任何事情。每個運算符都返回out

我們可以用ostream_iterator來輸出值的序列:

ostream_iterator<int> out_iter(cout, " ");
for(auto e:vec)
*out_iter++ = e;//賦值語句實際上將元素寫到cout
cout<<endl;

此程序將vec中的每個元素寫到cout,每個元素后加一個空格。每次向out_iter賦值時,寫操作就會被提交。值得注意的是,當我們向out_iter賦值時,可以忽略解引用和遞增運算。即,循環可以重寫成下面的樣子:

for(auto e:vec)out_iter = e;//賦值語句將元素寫到cout
cout<<endl;

運算符*和++實際上對ostream_iterator對象不做任何事情,因此忽略它們對我們的程序沒有任何影響。但是,推薦第一種形式。在這種寫法中,流迭代器的使用與其他迭代器的使用保持一致。如果想將此循環改為操作其他迭代器類型,修改起來非常容易。而且,對于讀者來說,此循環的行為也更為清晰。可以通過調用copy來打印vec中的元素,這比編寫循環更為簡單:

copy(vec.begin(),vec.end(),out_iter);
cout<<endl;

使用流迭代器處理類類型

我們可以為任何定義了輸入運算符(>>的類型創建istream_iterator對象。類似的,只要類型有輸出運算符(C<<),我們就可以為其定義ostream_iterator。由于Sales_item既有輸入運算符也有輸出運算符:

istream_iterator<Sales_item>item_iter(cin),eof;
ostream_iterator<Sales_item>out_iter(cout,"\n");
//將第一筆交易記錄存在sum中,并讀取下一條記錄
Sales_item sum =*item_iter++;
while(item_iter!=eof){//如果當前交易記錄(存在ttem_iter中)有著相同的ISBN號if(ttem_iter->isbn()==sum.isbn())sum+=*item_iter++;//將其加到sum上并讀取下一條記錄else{out_iter= sum;//輸出sum當前值sum = *item_iter++;//讀取下一條記錄}
}
out_iter = sum;//記得打印最后一組記錄的和

此程序使用item_iter從cin讀取Sales_item交易記錄,并將和寫入cout,每個結果后面都跟一個換行符。定義了自己的迭代器后,我們就可以用item_iter讀取第一條交易記錄,用它的值來初始化sum:

//將第一條交易記錄保存在sum中,并讀取下一條記錄
Sales_item sum = *item_iter++;

此處,我們對item_iter執行后置遞增操作,對結果進行解引用操作。這個表達式讀取下一條交易記錄,并用之前保存在item_iter中的值來初始化sum。

while循環會反復執行,直至在cin上遇到文件尾為止。在while循環體中,我們檢查sum與剛剛讀入的記錄是否對應同一本書。如果兩者的ISBN不同,我們將sum賦予out_iter,這將會打印sum的當前值,并接著打印一個換行符。在打印了前一本書的交易金額之和后,我們將最近讀入的交易記錄的副本賦予sum,并遞增迭代器,這將讀取下一條交易記錄。循環會這樣持續下去,直至遇到錯誤或文件尾。在退出之前,記住要打印輸入中最后一本書的交易金額之和。

反向迭代器

反向追代器就是在容器中從尾元素向首元素反向移動的迭代器。對于反向迭代器,遞增〔以及遞減)操作的含義會顛倒過來。遞增一個反向迭代器(++it會移動到前一個元素;遞減一個迭代器(一it會移動到下一個元素。除了forward_1ist之外,其他容器都支持反向迭代器。我們可以通過調用rbegin、rend、crbegin和crend成員函數來獲得反向追代器。這些成員函數返回指向容器尾元素和首元素之前一個位置的迭代器。與普通迭代器一樣,反向迭代器也有const和非const版本。

下面的循環是一個使用反向迭代器的例子,它按逆序打印vec中的元素:

vector<int>vec={0,1,2,3,415,6,7,8,9};
//從尾元素到首元素的反向選代器
for(auto x_iter=vec.crbegin();//將_iter綁定到尾元素x_iter!=vec.crend();//crend指向首元素之前的位置++r_iter)//實際是遞減,移動到前一個元素
cout<<*r_iter<<endl;// 打印9,8,7,..。0

雖然顛倒遞增和遞減運算符的含義可能看起來令人混淆,但這樣做使我們可以用算法透明地向前或向后處理容器。例如,可以通過向sort傳遞一對反向迭代器來將vector整理為遞減序:

sort(vec.begin(),vec.end());//按“正常序“排序vec
//按遞序排序:將最小元素放在vec的末尾
sort(vec.rbegin(),vec.rend());

反向迭代器需要遞減運算符

不必驚訝,我們只能從既支持++也支持一的迭代器來定義反向迭代器。畢竟反向迭代器的目的是在序列中反向移動。除了forward_list之外,標準容器上的其他迭代器都既支持遞增運算又支持遞減運算。但是,流迭代器不支持遞減運算,因為不可能在一個流中反向移動。因此,不可能從一個forward_list或一個流迭代器創建反向迭代器。

反向迭代器和其他迭代器間的關系

假定有一個名為line的string,保存著一個逗號分隔的單詞列表,我們希望打印line中的第一個單詞。使用find可以很容易地完成這一任務:

//在一個迎號分隔的列表中查找第一個元素
auto comma=find(line.cbegin(),line.cend(),',');
cout<<string(line.cbegin(),comma)<<endl;

如果line中有逗號,那么comma將指向這個逗號;否則,它將等于line.cend()。當我們打印從line.cbegin()到comma之間的內容時,將打印到逗號為止的字符,或者打印整個string〔如果其中不含逗號的話)。

如果希望打印最后一個單詞,可以改用反向迭代器:

// 在一個逗號分隔的列表中查找最后一個元素
auto comma=find(line.crbegin(),line.czend(),',');

由于我們將crbegin()和crend()傳遞給find,find將從1ine的最后一個字符開始向前搜索。當find完成后,如果1ine中有逗號,則rcomma指向最后一個逗號一一即,它指向反向搜索中找到的第一個逗號。如果1ine中沒有逗號,則rcomma指向

line.crend()。

當我們試圖打印找到的單詞時,最有意思的部分就來了。看起來下面的代碼是顯然的方法

//錯誤:將送序輸出單詞的字符
cout<<string(line.crbegin(),rcomma)<<endl;

但它會生成錯誤的輸出結果。例如,如果我們的輸入是

FIRST,MIDDLE,LAST

則這條語句會打印TSAL!

從技術上講,普通迭代器與反向迭代器的關系反映了左閉合區間的特性。關鍵點在于[line.crbegin(),rcomma)和[rcomma.base(),line.cend())指向line中相同的元素范圍。為了實現這一點,rcomma和rcomma.base()必須生成相鄰位置而不是相同位置,crbegin()和cend()也是如此。

反向迭代器的目的是表示元素范圍,而這些范圍是部隊稱的,這導致一個重要的結果:當我們從一個普通速代器初始化一個反向追代器,或是給一個反向迭代器賦值時,結果迭代器與原迭代器指向的并不是相同的元素。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70728.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70728.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70728.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

排查和解決線程池瓶頸問題案例

在分布式系統中&#xff0c;線程池的使用非常普遍&#xff0c;尤其是在處理異步任務時。然而&#xff0c;線程池的配置不當可能會導致性能瓶頸&#xff0c;進而影響系統的整體性能。本文將分享一個實際案例&#xff0c;介紹如何通過日志分析和線程池優化來解決系統中的性能瓶頸…

影響板材的熱導率有哪些因素?

板材熱導率受多種因素左右&#xff0c;可劃分為內部材料特性與外部環境條件兩大方面 內部材料特性 化學構成&#xff1a;不同化學元素及化合物組合形成的板材&#xff0c;熱導率表現大相徑庭&#xff1b;金屬板材&#xff0c;像銅與鋁&#xff0c;熱導率優異&#xff0c;這是…

給字符串加密解密

加密規則&#xff1a;輸入1a2b3c 輸出 abbccc 解密&#xff1a;輸入abbccc 輸出 1a2b3c 代碼&#xff1a; using System;namespace 加密解密 {class Program{static void Main(string[] args){Encryption("4b2a8p");Decryption("ppppppoovvv");Console.…

人工智能中的特征是什么?

什么是人工智能中的特征&#xff1f; 在人工智能中&#xff0c;特征&#xff08;feature&#xff09;是指從原始數據中提取出的、能夠代表數據關鍵信息并用于模型訓練的屬性或變量。特征通常是對原始數據的抽象或轉換&#xff0c;目的是捕捉數據中的模式、結構或相關性&#x…

20250226-代碼筆記05-class CVRP_Decoder

文章目錄 前言一、class CVRP_Decoder(nn.Module):__init__(self, **model_params)函數功能函數代碼 二、class CVRP_Decoder(nn.Module):set_kv(self, encoded_nodes)函數功能函數代碼 三、class CVRP_Decoder(nn.Module):set_q1(self, encoded_q1)函數功能函數代碼 四、class…

洛谷 P3628/SPOJ 15648 APIO2010 特別行動隊 Commando

題意 你有一支由 n n n 名預備役士兵組成的部隊&#xff0c;士兵從 1 1 1 到 n n n 編號&#xff0c;你要將他們拆分成若干特別行動隊調入戰場。出于默契的考慮&#xff0c;同一支特別行動隊中隊員的編號應該連續&#xff0c;即為形如 i , i 1 , ? , i k i, i 1, \cdo…

PCL源碼分析:曲面法向量采樣

文章目錄 一、簡介二、源碼分析三、實現效果參考資料一、簡介 曲面法向量點云采樣,整個過程如下所述: 1、空間劃分:使用遞歸方法將點云劃分為更小的區域, 每次劃分選擇一個維度(X、Y 或 Z),將點云分為兩部分,直到劃分區域內的點少于我們指定的數量,開始進行區域隨機采…

Go語言--語法基礎2--下載安裝

2、下載安裝 1、下載源碼包&#xff1a; go1.18.4.linux-amd64.tar.gz。 官方地址&#xff1a;https://golang.google.cn/dl/ 云盤地址&#xff1a;鏈接&#xff1a; https://pan.baidu.com/s/1N2jrRHaPibvmmNFep3VYag 提 取碼&#xff1a; zkc3 2、將下載的源碼包解壓…

lowagie(itext)老版本手繪PDF,包含頁碼、水印、圖片、復選框、復雜行列合并等。

入口類&#xff1a;exportPdf ? package xcsy.qms.webapi.service;import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.common.utils.StringUtils; import com.ibm.icu.text.RuleBasedNumberFormat; import com.lowa…

3-2 WPS JS宏 工作簿的打開與保存(模板批量另存為工作)學習筆記

************************************************************************************************************** 點擊進入 -我要自學網-國內領先的專業視頻教程學習網站 *******************************************************************************************…

Ubuntu20.04之VNC的安裝使用與常見問題

Ubuntu20.04之VNC的安裝與使用 安裝圖形桌面選擇安裝gnome桌面選擇安裝xface桌面 VNC-Server安裝配置開機自啟 VNC Clientroot用戶無法登入問題臨時方案永久方案 安裝圖形桌面 Ubuntu20.04主流的圖形桌面有gnome和xface兩種&#xff0c;兩種桌面的安裝方式我都會寫&#xff0c…

Day46 反轉字符串

I. 編寫一個函數&#xff0c;其作用是將輸入的字符串反轉過來。輸入字符串以字符數組 s 的形式給出。 不要給另外的數組分配額外的空間&#xff0c;你必須原地修改輸入數組、使用 O(1) 的額外空間解決這一問題。 class Solution {public void reverseString(char[] s) {int i …

用FileZilla Server 1.9.4給Windows Server 2025搭建FTP服務端

FileZilla Server 是一款免費的開源 FTP 和 FTPS 服務器軟件&#xff0c;分為服務器版和客戶端版。服務器版原本只支持Windows操作系統&#xff0c;比如筆者曾長期使用過0.9.60版&#xff0c;那時候就只支持Windows操作系統。當時我們生產環境對FTP穩定性要求較高&#xff0c;比…

【愚公系列】《Python網絡爬蟲從入門到精通》033-DataFrame的數據排序

標題詳情作者簡介愚公搬代碼頭銜華為云特約編輯,華為云云享專家,華為開發者專家,華為產品云測專家,CSDN博客專家,CSDN商業化專家,阿里云專家博主,阿里云簽約作者,騰訊云優秀博主,騰訊云內容共創官,掘金優秀博主,亞馬遜技領云博主,51CTO博客專家等。近期榮譽2022年度…

營銷過程烏龜圖模版

營銷過程烏龜圖模版 輸入 公司現狀產品服務客戶問詢客戶期望電話、電腦系統品牌軟件硬件材料 售前 - 溝通 - 確定需求 - 滿足需求 - 售后 機料環 電話、電腦等設備軟件硬件、系統品牌等工具材料 人 責任人協助者生產者客戶 法 訂單由誰評審控制程序營銷過程控制程序顧客滿意度…

Kubernetes (K8S) 高效使用技巧與實踐指南

Kubernetes&#xff08;K8S&#xff09;作為容器編排領域的核心工具&#xff0c;其靈活性和復雜性并存。本文結合實戰經驗&#xff0c;從運維效率提升、生產環境避坑、核心功能應用等維度&#xff0c;總結高頻使用技巧與最佳實踐&#xff0c;分享如何快速掌握 K8S。 一、kubect…

Idea java項目結構介紹

一般來說&#xff0c;一個典型的 IntelliJ IDEA Java 項目具有特定的結構&#xff0c;以下是對其主要部分的介紹&#xff1a; 項目根目錄 項目的最頂層目錄&#xff0c;包含了整個項目的所有文件和文件夾&#xff0c;通常以項目名稱命名。在這個目錄下可以找到.idea文件夾、.g…

C++大整數類的設計與實現

1. 簡介 我們知道現代的計算機大多數都是64位的&#xff0c;因此能處理最大整數為 2 64 ? 1 2^{64}-1 264?1。那如果是超過了這個數怎么辦呢&#xff0c;那就需要我們自己手動模擬數的加減乘除了。 2. 思路 我們可以用一個數組來存儲大數&#xff0c;數組中的每一個位置表…

2024年第十五屆藍橋杯大賽軟件賽省賽Python大學A組真題解析

文章目錄 試題A: 拼正方形(本題總分:5 分)解析答案試題B: 召喚數學精靈(本題總分:5 分)解析答案試題C: 數字詩意解析答案試題A: 拼正方形(本題總分:5 分) 【問題描述】 小藍正在玩拼圖游戲,他有7385137888721 個2 2 的方塊和10470245 個1 1 的方塊,他需要從中挑出一些…

開源RAG主流框架有哪些?如何選型?

開源RAG主流框架有哪些?如何選型? 一、開源RAG框架全景圖 (一)核心框架類型對比 類型典型工具技術特征適用場景傳統RAGLangChain, Haystack線性流程(檢索→生成)通用問答、知識庫檢索增強型RAGRAGFlow, AutoRAG支持重排序、多路召回優化高精度問答、復雜文檔處理輕量級…