国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
《白話C++》第三章《感受》(一)3.13.Hello STL 列表篇

3.13. Hello STL 列表篇

vector是一種容器。我們還知道,vector其實(shí)是一個(gè)“類模板”,具體使用前,必須通過以下語法指定將要存儲(chǔ)的元素類型:

vector<元素類型>

 

這就是決定了vector是一個(gè)“通用”的容器,如果元素類型是“Beauty”,那么它就是一個(gè)裝美女的容器,如果元素類型是“Money”,那么它就是裝錢的容器。

“列表/list” 也是一種容器,同樣,list也是一個(gè)“類模板”,具體使用前,必須通過以下語法指定將要存儲(chǔ)的元素類型:

list <元素類型>

 

看來list也是一種通用容器——或許你想問:既生vector,何生list呢?

 

3.13.1. vector VS. list

最簡(jiǎn)單的回答是:list和vector的結(jié)構(gòu)不同,因此決定了它們?cè)谀芰Ψ止ど系牟煌G蚁日f說生活中的容器。桶是圓柱體,柜子是立方體,這就叫“結(jié)構(gòu)不同的容器”。在設(shè)計(jì)上,圓柱體的桶,有利于用最少的材質(zhì),制造出最大容量的桶,而柜子正好相反,如果家里所有衣柜、書柜等等全部是圓柱體,那么它們將憑空浪費(fèi)掉屋子的很多空間。

因此,雖然容器的功能粗一看就是“存放元素”,但是,由于要存放的元素自身結(jié)構(gòu)不同,或者外部對(duì)容器的遍歷方式不同等原因,我們不得不需要有多種“功能傾向”明顯不同的容器。比如vector和list。

vector最大的結(jié)構(gòu)特點(diǎn)是:它開辟連續(xù)的內(nèi)存空間用以存儲(chǔ)元素。而list正好相反,它允許使用不連續(xù)內(nèi)存空間。

假設(shè)你想在熱鬧的市區(qū)蓋5間房子,有兩種方案,第一種方案把5間房連續(xù)蓋在一起,這會(huì)讓你生活很方便,但你需要一大塊地皮,可能市政府不會(huì)滿足你,為了你這一塊地,可能要拆牽別人許多房;第二種方案你同意把五間房分開蓋,這會(huì)給生活帶來一定不便,但現(xiàn)在你只需在市區(qū)見縫插針地找到5塊小地皮,就可以動(dòng)工了。

第一種方案正是vector分配內(nèi)存的策略,第二種方案則是list分配內(nèi)存的策略。由此,我們可以得出第一個(gè)“猜想”:如果我們有一大堆對(duì)象,并且這些對(duì)象個(gè)頭都很大,那么我們就必須考慮是否放棄采用vector直接存儲(chǔ)?

作為代替方案,有兩種,其一是仍然采用vector存儲(chǔ),但存放的元素改成是“堆對(duì)象”,也就是說將每個(gè)對(duì)象創(chuàng)建在“堆內(nèi)存區(qū)”中,而容器中僅僅保存對(duì)象在“堆內(nèi)存”中的地址。這個(gè)方案不足之處是:由于“堆對(duì)象”我們必須手工“殺死它們”,所以會(huì)帶來額外的內(nèi)存管理工作。其二,我們可以采用list容器進(jìn)行管理。list允許我們?nèi)匀粚?shù)據(jù)保存在“棧內(nèi)存區(qū)”中,不過必須將每個(gè)元素分開保存,從而有利于分配出足夠的內(nèi)存。

當(dāng)然,“內(nèi)存分配”并不是我們考慮是否采用list的唯一因素——如果非要找一個(gè)vector的代替方案,STL中的deque容器可能更適合——更為重要的考慮對(duì)元素的訪問,包括讀取元素、添加元素、插入元素、刪除元素的方式,及其代價(jià)。

比如“插入元素”:在vector模式下:你有三間緊密蓋在一起的房子,現(xiàn)在你想在第1、2間房子中插入一間新房子——老天,這在現(xiàn)實(shí)中幾乎難以實(shí)現(xiàn)。聽說有一種“樓房平移”技術(shù),允許在地基下安裝輪子,然后以極慢的,比如一天1厘米的速度,將樓房不知不覺地挪開數(shù)米……

vector采用連續(xù)內(nèi)存保存元素,因此當(dāng)需要在中間插入元素時(shí),同樣需要將插入點(diǎn)之后的所有元素往后“平移”,有時(shí)甚至需要將整個(gè)內(nèi)存摧毀重建。類似的,在vector中刪除元素的代價(jià)也非常之大。由此我們得出第二個(gè)“猜想”:如果你在存儲(chǔ)對(duì)象的同時(shí),還需要經(jīng)常插入、刪除位于中部的元素,那么采用vector也是不合適的。這時(shí)候又可以考慮list,因?yàn)閘ist中的元素,東一個(gè)西一個(gè),元素之間存在不是“物理”的,而是“邏輯”上的次序關(guān)系。要在所謂“中間位置”插入一個(gè)元素,易如反掌。

“猜想”到此結(jié)束,還是直接了解一下list的結(jié)構(gòu)吧。

 

3.13.2. 基礎(chǔ)

(圖 39 list 內(nèi)存結(jié)構(gòu)示意)

  • 每個(gè)方格表示一塊內(nèi)存區(qū)域,其中黑色部分表示已經(jīng)被其它數(shù)據(jù)占用的內(nèi)存塊(釘子戶)。
  • A、B、C三個(gè)元素依次加入list中,分別“見縫插針”地占用三塊地盤,從圖示可看,這三塊內(nèi)存并不連續(xù),甚至連次序也沒有保證。
  • 每個(gè)數(shù)據(jù)加入list時(shí),不僅要占用自身需要的影子,而且要額外占用兩塊小內(nèi)存,分別用來保存“前一元素的地址”和“后一元素的地址”,即圖示中的“前”、“后”。
  • 圖中箭頭表示:A的后一元素是B,B的后一元素是C;C的前一元素是B;B的前一元素是A。
  • A沒有前一元素,C沒有后一元素,但為了使用方便,分別將它們的“前一元素地址”和“后一元素地址”設(shè)置一個(gè)虛擬值(具體實(shí)現(xiàn)方式并無標(biāo)準(zhǔn))。訪問這兩個(gè)虛擬值,將會(huì)導(dǎo)致未定義的行為。

現(xiàn)在,假設(shè)B左邊的那位“釘子戶”搬走,而我們正好在此時(shí)在A和B之間插入A’元素,這引起的變化是什么呢?

(圖 40 A與B之間插入A''元素)

  • 原有的A、B、C三個(gè)元素不必“搬家”,仍然位于原來的位置。換句話說,往list中插入新元素(刪除也一樣),不需要移動(dòng)任何原有的元素。所以速度很快。
  • 變化的只是A、B中“前一元素地址”和“后一元素地址”的值,在上圖用箭頭表示,即:A的“后一元素”改為指向A’,B的“前一元素”改為指向A’。而A’則把“前一元素”指向A,“后一元素”指向B。

list可以實(shí)現(xiàn)高速地插入、刪除元素,這一點(diǎn)讓vector望塵莫及。vector僅當(dāng)在尾部“追加”元素時(shí),才有可能因?yàn)椴槐剡w移前面元素而獲得較高速度。然而,list卻沒有vector的“隨機(jī)訪問”能力。我們無法采用“[N]”來直接訪問list中第N個(gè)元素,相反,我們只能直接訪問到第一個(gè),或最后一個(gè)元素,然后依據(jù)“后一元素地址”,不斷往后, 或者依據(jù)“前一元素地址”不斷往前,從而遍歷每個(gè)元素。

如果你不太理解,那么想象一隊(duì)士兵,我們被規(guī)定只允許“接觸”正副班長(zhǎng),但卻要將球傳給中間的某個(gè)士兵時(shí)該如何辦?(提示:一隊(duì)士兵中,通常班長(zhǎng)排頭,副班長(zhǎng)壓尾)。

3.13.3. 迭代器/iterator概念

由于在訪問某一元素時(shí),必須同時(shí)得到“前一元素地址”和“后一元素地址”等相關(guān)信息,因此,當(dāng)我們?cè)谠L問list時(shí),并不是直接得到當(dāng)初存入list的裸數(shù)據(jù)(如上圖中的A、B、C),而是得到另外一種類型的數(shù)據(jù),稱為“迭代器/Iterator”。這不僅僅是list的策略,而是STL中常規(guī)容器共有的特點(diǎn)。對(duì)于list,“迭代器”包裝了裸數(shù)據(jù),同時(shí)又新增了用于前往“前一元素”和“后一元素”的信息。

有關(guān)迭代器的實(shí)現(xiàn),需要觸及許多們尚未學(xué)習(xí)的C++知識(shí)。在今天,你暫時(shí)可以把迭代器當(dāng)作是對(duì)“裸數(shù)據(jù)”的一層封裝,比如一個(gè)list<Beauty>的迭代器,暫時(shí)可以理解為這樣一個(gè)結(jié)構(gòu):

struct iterator            {            Beauty *ptr; //指向我們保存的“美女”元素               iterator* next; //后一個(gè)美女的位置               iterator* prior; //前一個(gè)美女的位置            };            

依據(jù)這個(gè)簡(jiǎn)單的迭代器結(jié)構(gòu),假設(shè)我們現(xiàn)在擁有一個(gè)iterator的對(duì)象,名為 cur。那么,我們可以這樣的操作:

1.訪問到當(dāng)前“美女”的名字:

cur.ptr->GetName();

2. 前進(jìn)到后一個(gè)“美女”元素:

cur = cur.next;

3. 后退到前一個(gè)“美女”元素:

cur = cur.prior;

 

真實(shí)的STL迭代器內(nèi)部實(shí)現(xiàn)要比上面的例子,復(fù)雜得多——作為回報(bào)——它的對(duì)外的接口更加直觀了:

1. 訪問到當(dāng)前“美女”的名字:

cur->GetName();

2. 前進(jìn)到后一個(gè)“美女”元素:

++cur; //或者:cur++;

3. 后退到前一個(gè)“美女”元素:

--cur; //或者:cur--;

 

〖小提示〗:迭代器特定接口的實(shí)現(xiàn)原理

和vector提供的“[]”一樣,“++、- -、->、*”也是操作符,同樣可以通過“操作符重載”技術(shù),使其與“函數(shù)”類型的方式,提供特定的功能。

普通迭代器的類型名稱為:iterator。不過它總是定義在具體的容器類中。對(duì)于list<Beauty>。我們可以認(rèn)為存在這樣一個(gè)類型:

struct list< Beauty >            {            //…            };            

而它的迭代器類型的定義,總是嵌套其中:

struct list< Beauty >            {            struct iteraotr            {            //…              };            //…            };            

回憶一下,我們經(jīng)??梢园岩粋€(gè)類也當(dāng)作一個(gè)名字空間(namespace),所以,list<int>容器類型的迭代器的類型完整名稱即是:list< Beauty >::iterator——還記得“德國(guó)小蠊::小強(qiáng)”嗎?

定義一個(gè)list<Beauty>的迭代器的變量,代碼為:

list<Beauty>::iterator iter;

由于list的迭代器既可以前進(jìn)(++操作),又可以后退(--操作),因此,我們稱之為“雙向迭代器”。

list的不少常用函數(shù),都會(huì)涉及到迭代器,比如返回值是一個(gè)迭代器,或者參數(shù)是一個(gè)迭代器。我們先了解和迭代器無關(guān)的常用函數(shù)。

 

3.13.4. 常用函數(shù)(一)

請(qǐng)新建一個(gè)控制臺(tái)應(yīng)用項(xiàng)目,命名為“HelloSTLVector”。打開項(xiàng)目唯一的源文件:main.cpp。如果你覺得必要,請(qǐng)確保修改文件編碼為“系統(tǒng)默認(rèn)”。

在002行加入包含<list>頭文件的代碼:

001 #include <iostream>            002 #include <list>            

在main函數(shù)最前面加入一行定義,以產(chǎn)生一個(gè)“專門用于存儲(chǔ)整數(shù)的list”的變量,變量名為“l(fā)st”。(第1個(gè)字符是字母“l(fā)”還是數(shù)字“1”?你猜得出的,不是嗎?)

006 int main()            {            008    list<int> lst;            ......            

請(qǐng)?jiān)诋?dāng)前代碼的基礎(chǔ)上,請(qǐng)一邊閱讀以下課程,一邊在項(xiàng)目中完成相應(yīng)的代碼。

  • push_front(elem)

在list頭部插入一個(gè)元素。

lst.push_front(10);

lst.push_front(20);

現(xiàn)在,lst中的元素是:20、10。

  • push_back(elem)

在list尾部插入一個(gè)元素。

lst.push_back(8);

lst.push_back(9);

現(xiàn)在,list中的元素是:20、10、8、9。

  • pop_front( )

刪除將list第一個(gè)元素。

lst.pop_front();

現(xiàn)在,lst中的元素是:10、8、9。沒錯(cuò),第一個(gè)元素被丟棄了。

pop_front()僅僅是“丟”掉第一個(gè)元素,并不將第一個(gè)元素返回給函數(shù)的調(diào)用者。

  • pop_back()

刪除list最后一個(gè)元素。

lst.pop_back();

現(xiàn)在,lst中的元素是:10、8。

 

〖課堂作業(yè)〗:理解pop_front和pop_back

以下代碼片段錯(cuò)在哪里:

list<int> lst;

lst.push_back(1);

lst.push_back(2);

int a = lst.pop_front();

int b = lst.pop_back();

 

請(qǐng)對(duì)比隨后的front()與back()函數(shù),二者允許我們得到(但不丟棄)第一個(gè)元素或最后一個(gè)元素。

  • clear()

刪除容器中所有元素。

lst.clear();

現(xiàn)在lst中一個(gè)元素也沒有。

  • front( ) const

返回第一個(gè)元素(“裸元素”,而非迭代器)。

lst.push_back(1);

lst.push_back(2);

int a = lst.front();

cout << a << endl;

a的值是1。同時(shí)lst中的元素現(xiàn)在是:1、2。

front()并不影響容器內(nèi)部的任何數(shù)據(jù),所以它是一個(gè)“常量成員函數(shù)”。

  • back() const

返回最后一個(gè)元素(“裸元素”,而非迭代器)。

int b = lst.back();

cout << b << endl;

b的值是2。

和front()一樣,back()同樣直接返回容器中的元素,而不是迭代器。本例與前例中,lst的類型是:list<int>,所以a和b都被定義成int類型。

  • size() const

返回容器中元素的個(gè)數(shù)。

int count = lst.size();

cout << count << endl;

count值為2。

  • empty() const

判斷容器是否為空(元素個(gè)數(shù)為0)。

如果僅關(guān)心容器是否為空,請(qǐng)調(diào)用此函數(shù)以獲得更好性能,而不要通過:“size() == 0” 判斷。

cout << lst.empty() << endl;

lst.clear();

cout << lst.empty() << endl;

第一行輸出0;第二行輸出1。

 

〖小提示〗:輸出bool類型的值

默認(rèn)狀態(tài)下,cout將bool值視為整數(shù)處理,值false對(duì)應(yīng)0,值true對(duì)應(yīng)1。

 

3.13.5. 常用函數(shù)(二)

  • begin( ) 和end( )

begin()函數(shù)返回list第一個(gè)元素的迭代器。

需要特別注意的是:end() 返回的是最后一個(gè)元素的“下一個(gè)”迭代器。假設(shè)list有3個(gè)元素,那么end()返回了第4個(gè)元素的迭代器,第4個(gè)元素并不真實(shí)存在,它是個(gè)“虛”的元素(請(qǐng)參看“l(fā)ist 內(nèi)存結(jié)構(gòu)示意圖”)。

得到lst的第一個(gè)迭代器代碼如下:

list<int>::iterator iter = lst.begin();

有了一個(gè)迭代器,如果通過它得到對(duì)應(yīng)的元素呢?方法很簡(jiǎn)單——操作一個(gè)“迭代器”和操作堆對(duì)象非常類似——采用“*”操作符。

int a = *iter;

我們也可以通過迭代器來修改對(duì)應(yīng)的元素:

*iter = 1000;

我們來一個(gè)相對(duì)完整的代碼片段,演示如何通過一個(gè)迭代器讀取與修改對(duì)應(yīng)的元素。

list <int> lst;            lst.push_back(1);            lst.push_back(2);            list<int>::iterator  iter = lst.begin();            int a = *iter;            cout << a << endl; //輸出 1            cout << *iter << endl; //同樣輸出1            *iter = 1000;            cout << *iter << endl; //輸出1000            cout << a << endl; //輸出1;            int b = *iter;            cout << b << endl; //輸出1000;            

光說begin()函數(shù)了,end()函數(shù)是不是也可以有相同操作呢?

lst<int>::iterator iter2 = lst.end();            int a = *iter2; //災(zāi)難發(fā)生            *iter2 = 100;   //災(zāi)難發(fā)生            

因?yàn)閑nd()返回的迭代器綁定到一個(gè)“虛擬”的元素。元素總是保存在內(nèi)存中,所謂“虛擬”的元素,可能是一塊“并不真實(shí)存在的內(nèi)塊”,也可能是指一塊“并不屬于你的內(nèi)存”——還不理解嗎?來聽一個(gè)故事吧。

 

〖輕松一刻〗:丁小明家的存款單

有一次,丁小明興沖沖地給我一張1000萬元的存款單,我拿了直奔銀行要求取現(xiàn),結(jié)果那天我鼻青臉腫地回來了。

后來我去丁家,正遇上他家老婆蹲在地上,面前一張板凳,板凳上一個(gè)詭異紙盒,紙盒內(nèi)一撂單據(jù)。他家老婆神情興奮地?cái)?shù)著:“開始!50元、120元、75元5角、60元2角7分……1000萬?結(jié)束?!蔽也粣u地罵了一聲:“虛偽!”。丁家老婆靦腆地沖我家一笑,蓋上了紙盒。我才發(fā)現(xiàn)紙盒上寫了幾行字:

產(chǎn)地:C++標(biāo)準(zhǔn)委員會(huì)

結(jié)構(gòu):std::list 類型: std::list<存款單>

我惡狠狠地?fù)屵^這個(gè)紙盒,翻成底朝天——果然,這個(gè)盒子兩邊都有蓋!我惱怒地打開后面的蓋,天!那張千萬元的單據(jù),居然自動(dòng)跑到另一邊去了!

  • rbegin() / rend()

rbegin() 是list中最后一個(gè)元素的迭代器。而rend()同樣需要引起你的注意:它是第一個(gè)元素之前的那個(gè)虛擬元素的迭代器。

rbegin()、rend()返回的迭代器,和begin()/end()返回類型并不相同,前者稱為“逆向迭代器”,具體類型是:list<元素類型>:: reverse_iterator。

  • insert (pos, elem)

在指定位置插入一個(gè)新元素。pos并不是一個(gè)用來表示元素位置的整數(shù),而是一個(gè)iterator(并且不能是reverse_iterator),表示在指定迭代器的前面,插入新元素。

lst.clear();            lst.push_back(10);            lst.push_back(100);            list<int>::iterator  iter = lst.begin();            ++iter; //iter前進(jìn)1步,指向第二個(gè)元素            lst.insert(iter, 1); //在第二個(gè)元素的位置上,插入新元素            

現(xiàn)在,lst中的元素是:10、1、100。

  • erase(pos)

從容器中刪除pos位置對(duì)應(yīng)的元素。pos同樣是一個(gè)迭代器(但不能是reverse_iterator)。

lst.clear();            lst.push_back(10);            lst.push_back(100);            list<int>::iterator  iter = lst.begin();            ++iter;            lst.erase(iter);            

現(xiàn)在lst中只有一個(gè)元素:10。

 

3.13.6. 常量迭代器

迭代器可以“訪問”到容器中的每個(gè)元素,“訪問”方法即可以是“讀取”,也可以“修改”。和“常量成員函數(shù)”的思路一樣,為了更好的“封裝”效果,STL提供了一種“只讀迭代器”,類名為“const_iterator”及反向版“const_ reverse_iterator”。對(duì)應(yīng)的,“begin()/end()、rbegin()/rend()”也分別有一個(gè)“常量版”。

list<int>::const_iterator  c_iter = lst.begin();  //此時(shí)調(diào)用的是常量版            int a = *c_iter; //正確            *c_iter = 1000;  //錯(cuò)誤!c_iter是只讀版迭代器,不允許修改它綁定的元素            

 

〖課堂作業(yè)〗:lst常用函數(shù)匯總

請(qǐng)將“常用函數(shù)(一)”“常用函數(shù)(二)”、“常量迭代器”三小節(jié)中的所有示例代碼,合并到工程的main.cpp源文件中,并確保沒有編譯錯(cuò)誤。

 

3.13.7. 遍歷list容器

list容器不允許“隨機(jī)”訪問,不過,我們可以得到第一個(gè)元素的 “迭代器”,然后通過“迭代器”不斷前進(jìn),從而訪問到每一個(gè)元素。

下面的代碼,實(shí)現(xiàn)將lst中的每一個(gè)整數(shù),輸出到屏幕上。

001 for (list<int>::const_iterator c_iter = lst.begin();            002       c_iter != lst.end();            003       ++c_iter)            {            005    cout << *c_iter << endl;            }            

for循環(huán)頭被我故意折成三行,因?yàn)樗雌饋碛悬c(diǎn)長(zhǎng),但其實(shí)仍然是這樣三部分:

001行是“初始化語句”。定義了一個(gè)“只讀迭代器”的變量:c_iter,它指向lst的第一個(gè)元素。

002行是“循環(huán)條件判斷”語句。“!=”是C++中用以實(shí)現(xiàn)“不等”判斷的操作符。本循環(huán)不斷進(jìn)行的條件就是:c_iter不等于lst的“結(jié)束迭代器”。

003行,c_iter通過++操作,實(shí)現(xiàn)前進(jìn)到下一元素的位置上。

如果你對(duì)這三行感覺得有些不太理解,那么你需復(fù)習(xí)一下3.12.3 小節(jié)有關(guān)如何遍歷vector的內(nèi)容。

005行,輸出c_iter當(dāng)前指向的元素的值。

由于list擁有“雙向迭代器”,所以這個(gè)次序也可以倒過來,改成從最后一個(gè)元素開始,“倒著”走向第一個(gè)元素。強(qiáng)調(diào)一點(diǎn),對(duì)于一個(gè)逆向迭代器/ reverse_iterator,對(duì)它進(jìn)行“++”操作,就是促使它從容器的尾部往頭部“前進(jìn)”一個(gè)位置,而對(duì)其進(jìn)行“--”操作,則是促使它從容器的頭部往“后退”一個(gè)位置。

001 for (list<int>::const_reverse_iterator c_iter = lst.rbegin();            002     c_iter  !=  (list<int>::const_reverse_iterator) lst.rend();            ++c_iter)            {            cout << *c_iter << endl;            }            

 

〖危險(xiǎn)〗:gcc的一個(gè)BUG

事實(shí)上,002行中的加粗部分,本不必要。然而gcc 4.0(含4.0)以下版本存在錯(cuò)誤,我們不得不加上該段代碼,用于強(qiáng)制告知編譯器我們要的是一個(gè)“const_reverse_iterator”,而非“reverse_iterator”。

4.1以上版本的gcc不存在該問題,然而Code::Blocks自帶的gcc是mingw-gcc3.4.5。

 

變化發(fā)生在001行和002行。你喜歡玩“找碴”游戲嗎?仔細(xì)找找兩段代碼之間的不同。

〖課堂作業(yè)〗:兩種方向遍歷list

請(qǐng)?jiān)谏洗握n堂作業(yè)的基礎(chǔ)上,加入本小節(jié)兩種方向遍歷list的練習(xí)代碼。

 

3.13.8. 實(shí)例:成績(jī)管理系統(tǒng)1.0

〖輕松一刻〗:李老師的出現(xiàn)

夜。

風(fēng)雨夜。

白發(fā)蒼蒼的老者,敲開丁家大門。

來者姓李,20年前丁小明的小學(xué)老師。

他帶著厚厚的一摞試卷……

他想做什么?

 

  • 需求與基本思路

自打小丁完成了“美女大賽管理系統(tǒng)”之后,他就一直在想寫一個(gè)更有意義的軟件。

現(xiàn)在需求來了:老師希望能夠按照試卷的次序,錄入學(xué)生成績(jī),然后依據(jù)學(xué)號(hào)的次序,輸出學(xué)生成績(jī)。小丁簡(jiǎn)單地翻了一下試卷,發(fā)現(xiàn)一個(gè)事實(shí):試卷的次序和學(xué)號(hào)無關(guān)。

李老師說,錄入時(shí),需要輸入學(xué)號(hào)與成績(jī);輸出時(shí),最好能同時(shí)顯示學(xué)號(hào)與姓名。

小丁的思路是:分成兩個(gè)struct,其一包含學(xué)號(hào)、姓名;其二包含學(xué)號(hào)、成績(jī)。二者之間通過“學(xué)號(hào)”構(gòu)成邏輯關(guān)聯(lián):

//學(xué)生            struct Student            {            unsigned int number; //學(xué)號(hào)              string name;            };            //成績(jī)            struct Score            {            unsigned int number; //學(xué)號(hào)               float mark; //分?jǐn)?shù)            };            

學(xué)號(hào)采用“無符號(hào)整數(shù)”(0和正整數(shù))類型,因?yàn)閷W(xué)號(hào)不允許為負(fù)數(shù),為0則有特殊用處。分?jǐn)?shù)采用“float”類型,因?yàn)榇嬖谙?6.5分這樣帶小數(shù)的成績(jī)。

 

〖重要〗:減少信息過度偶合

小丁的思路是正確的。表面看,“學(xué)生”可以擁有一或多個(gè)“成績(jī)”,但“擁有”并不一定適合在類中加入一個(gè)相關(guān)的成員數(shù)據(jù)?!俺煽?jī)”在很多時(shí)候,可以暫時(shí)脫離“學(xué)生”而獨(dú)立進(jìn)行運(yùn)算,比如本例的“錄入成績(jī)”。這種情況下,相關(guān)信息獨(dú)立定義,通過一個(gè)“關(guān)鍵值”來維系兩者的關(guān)系是個(gè)好主意。本例中,這個(gè)關(guān)鍵值就是“學(xué)號(hào)”。

再考慮一個(gè)更為極端的例子:“學(xué)生”有“家長(zhǎng)”,“家長(zhǎng)”有“工作單位”、“工作單位”有“法人代表”。顯然,在“學(xué)生”結(jié)構(gòu)中含有“法人代表”很不合理,這就稱為信息之間的過度偶合,在編程設(shè)計(jì)中,應(yīng)該避免。

 

接著,小丁決定使用vector來保存班級(jí)里的學(xué)生。因?yàn)閷W(xué)生數(shù)據(jù)相對(duì)穩(wěn)定。成績(jī)數(shù)據(jù)則使用list保存,每次錄入成績(jī)時(shí),根據(jù)錄入的學(xué)號(hào),找到合適的位置插入,比如已經(jīng)錄入10、11、14、16號(hào)成績(jī),當(dāng)錄入12號(hào)時(shí),自動(dòng)找到11與14之間插入。既然成績(jī)要不斷地插入,那么采用list確實(shí)很合理。

  • 類定義

新建一個(gè)控制臺(tái)應(yīng)用項(xiàng)目,命名為“HelloSTL_ScoreManage_Ver1”。打開該項(xiàng)目下的main.cpp文件,再通過菜單:“編輯->文件編碼”設(shè)置為“系統(tǒng)默認(rèn)”。

首先加入<list>、<vector>、<string>等頭文件的包含,以及前述兩個(gè)類型的定義。代碼如下:

#include <iostream>            #include <list>            #include <vector>            #include <string>                        using namespace std;            //學(xué)生            struct Student            {            unsigned int number; //學(xué)號(hào)              string name;            };            //成績(jī)            struct Score            {            unsigned int number; //學(xué)號(hào)               float mark; //分?jǐn)?shù)            };            //此處是 main() 函數(shù)的默認(rèn)實(shí)現(xiàn),略……                        

接著,需要一個(gè)“學(xué)生成績(jī)管理/StudentScoreManager”類型,該類型當(dāng)前提供以下三樣功能:

第一、批量錄入學(xué)生基本信息(學(xué)號(hào)、姓名)

第二、批量錄入考試成績(jī)。

第三、以學(xué)號(hào)為次序,輸出考試成績(jī)。

考試時(shí),誰先做完,誰就先交卷,所以交完以后的卷子,可以看成是無序的。那么第三步如何實(shí)現(xiàn)呢?我們現(xiàn)在想到的辦法是:拿到一個(gè)學(xué)號(hào)時(shí),就在無序的list中查找這個(gè)學(xué)號(hào)的成績(jī)。

請(qǐng)?jiān)趍ain()函數(shù)之前,Score類定義之后,插入以下代碼:

//學(xué)生成績(jī)管理類            class StudentScoreManager            {            public:            void InputStudents();            void InputScores();            void OutputScores() const;            private:            vector<Student> students;            list<Score> scores;            };            

OutputScores函數(shù)被聲明為“const”,因?yàn)橥ǔR粋€(gè)用來“顯示”成績(jī)的功能,不應(yīng)該修改類型的任何成員數(shù)據(jù)。想想,你的老師交給你一份學(xué)生成績(jī)表,讓你將它打印,并張貼出來——在這個(gè)過程中,你的成績(jī)由30分變成80分?有這可能嗎?

  • 學(xué)生信息輸入函數(shù)

InputStudents()函數(shù)將用來實(shí)現(xiàn)錄入學(xué)生基本信息。由于通常是對(duì)著“花名冊(cè)”錄入,所以實(shí)現(xiàn)為只能按照學(xué)號(hào)次序錄入——這樣做也有一個(gè)好處:可以不用手工輸入學(xué)號(hào)了。

void StudentScoreManager::InputStudents()            {            037    int number = 1; //學(xué)號(hào)從1開始                        while(true)            {            041        cout << "請(qǐng)輸入" << number << "號(hào)學(xué)生姓名(輸入x表示結(jié)束):";            string name;            getline(cin, name);            047        if (name == "x")            {            break;            }            052        Student student;            053        student.number = number;            054        student.name = name;            056        students.push_back(student);            058        ++number;            }            }            

這又是一個(gè)“死循環(huán)”,不過這次打破循環(huán)的方法比較有趣,041行提示用戶輸入小寫字母‘x’表示退出。具體實(shí)現(xiàn)是在047選擇的if條件判斷。

程序被設(shè)計(jì)成以1號(hào)、2號(hào)、3號(hào)順序輸入學(xué)生姓名,學(xué)號(hào)從1開始,并且在每次錄完一個(gè)學(xué)生之后,學(xué)號(hào)就自動(dòng)加1。這是037行與058兩行所完成的重要任務(wù)。

每次循環(huán),都會(huì)在052行新定義了一個(gè)Student的“棧對(duì)象”,并且在隨后053、054兩行取得必要的值,然后在056行處,被加入到vector中?!皸?duì)象”student會(huì)在每次循環(huán)結(jié)束自動(dòng)消亡,然而,由于push_back()函數(shù)是先復(fù)制一份,再把復(fù)制品扔入vector,所以不必?fù)?dān)心每次錄入的學(xué)生對(duì)象會(huì)人間蒸發(fā)。

  • 成績(jī)錄入函數(shù)

接下來是錄入成績(jī)的成員函數(shù):InputScores()。

void StudentScoreManager::InputScores()            {            while(true)            {            unsigned int number;            cout << "請(qǐng)輸入學(xué)號(hào)(輸入0表示結(jié)束):";            cin >> number;            if (number == 0)            {            break;            }            //簡(jiǎn)單判斷學(xué)號(hào)是否正確:            078        if (number > students.size())            {            cout << "錯(cuò)誤:學(xué)號(hào)必須位于: 1 ~ " << students.size() << " 之間。" << endl;            081            continue;            }            float mark;            cout << "請(qǐng)輸入該學(xué)員成績(jī):";            cin >> mark;            Score score;            score.number = number;            score.mark = mark;            092        scores.push_back(score); //直接加在尾部                }            }            

錄入成績(jī)的代碼,并不比錄入學(xué)生信息復(fù)雜多少。我們耍了同樣的花招來結(jié)束一個(gè)循環(huán),只不過這一次用到學(xué)號(hào)上面:輸入0,表示結(jié)束錄入。

078行我們對(duì)用戶輸入的學(xué)號(hào)做一個(gè)簡(jiǎn)單的合法判斷。學(xué)員的總數(shù)已知是students.size(),如果用戶輸入大于學(xué)員總數(shù)的學(xué)號(hào),被認(rèn)為不合法。這里用到了continue。

092行通過調(diào)用push_back,直接將新錄入的成績(jī)添加在list的尾部,所以list聽成績(jī)存儲(chǔ)并沒有按照學(xué)號(hào)排序。

  • 成績(jī)顯示函數(shù)

我們將遍歷students中的每一個(gè)學(xué)生,輸出他的學(xué)號(hào)和姓名。然后再次通過循環(huán),在scores中找到指定學(xué)號(hào)的成績(jī),如果沒有找到,提輸出:“查無成績(jī)”。

void StudentScoreManager::OutputScores() const            {            120    for (unsigned int i=0; i<students.size(); ++i)            {            122        unsigned int number = students[i].number; //學(xué)號(hào)                        cout << "學(xué)號(hào):" << number << endl;            cout << "姓名:" << students[i].name << endl;            //查找成績(jī):            128        bool found = false;            130        for (list<Score>::const_iterator iter = scores.begin();            iter != scores.end();            ++iter)            {            if (iter->number == number)            {            found = true; //找到了                        cout << "成績(jī):" << iter->mark << endl;            break;            }            }            144        if (found ==false) //沒找到??                    {            cout << "成績(jī):" << "查無成績(jī)。" << endl;            }            }            }            

這段代碼有兩層for循環(huán)。

120行的for循環(huán),遍歷了每個(gè)學(xué)生。122行定義number記住當(dāng)前學(xué)員的學(xué)號(hào)。隨后兩行輸出學(xué)號(hào)和姓名。

128行我們又定義了一個(gè)bool變量:found。通過130行的for循環(huán),遍歷每個(gè)成績(jī),找到指寫學(xué)號(hào)的分?jǐn)?shù),然后輸出。如果沒有找到,則直至循環(huán)結(jié)束,found也不會(huì)被置為真。 隨后144行的判斷條件成立,屏幕輸出相應(yīng)提示。

 

〖危險(xiǎn)〗:當(dāng)心:缺少錯(cuò)誤處理的程序

為了簡(jiǎn)化代碼,前述兩InputXXX函數(shù)對(duì)用戶非法輸入的情況僅做最簡(jiǎn)單的判斷。事實(shí)上,在要求輸入學(xué)號(hào)或者分?jǐn)?shù)時(shí),如果用戶不小心輸入一些字母,比如:abc,程序就會(huì)陷入真正的死循環(huán),此時(shí)必須使用Ctrl+C來強(qiáng)行中斷。如果你想預(yù)防此類錯(cuò)誤,可以每次接受輸入之后,立即判斷cin.fail()是否為真,具體請(qǐng)參看3.12.4節(jié)的實(shí)例。

 



本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
泛型算法 (輸入輸出迭代器和算法綜合介紹)
C++迭代器 iterator詳解
C++ STL基本容器的使用
《C++ Primer》筆記 第10章 泛型算法
第八章 標(biāo)準(zhǔn)模板庫STL(一)
C++ STL中容器的使用全面總結(jié)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服