練習9.1:對於下面的程序任務,vector、deque和list哪種容器最為適合?解釋你的選擇的理由。如果沒有哪一種容器優於其他容器,也請解釋理由。
(a)讀取固定數量的單詞,將他們按字典順序插入到容器中。我們將在下一章看到關聯容器更適合這個問題。
(b)讀取未知數量的單詞,總是將新單詞插入到末尾。刪除操作在頭部進行。
(c)從一個文件讀取未知數量的整數。將這些數排序,然後將它們打印到標准輸出。
(a) “按字典順序插入到容器中”意味著進行插入排序操作,從而需要在容器內部頻繁進行插入操作,vector在尾部之外的位置差如何刪除元素很慢,deque在頭尾之外的位置插入和刪除元素很慢,而list在任何位置插入、刪除速度都很快。因此這個任務選擇list更為適合。
(b)由於需要在頭、尾分別進行插入、刪除等操作,因此將vector排除在外,deque和list都可以達到很好的性能。如果還需要頻繁進行隨機訪問,則deque更好。
(c)由於整數占用空間很小,且快速的排序算法需頻繁隨機訪問元素,將list排除在外。由於無需在頭部進行插入、刪除操作,因此使用vector即可,無需使用deque。
練習9.2:定義一個list對象,其元素類型是int的deque。
list
練習9.3:構成迭代器范圍的迭代器有何限制?
兩個迭代器begin和end必須指向同一個容器中的元素,或者是容器最後一個元素之後的位置;而且,對begin反復進行遞增操作,可以保證達到end,即end不在begin之前。
練習9.4:編寫函數,接受一對指向vector
#include#include using std::cout; using std::endl; using std::vector; bool find_x(vector ::iterator begin, vector ::iterator end, int x) { for(; begin != end; ++begin) if (*begin == x) return true; return false; } int main() { vector vec={1, 2, 4, 3, 5, 8, 10, 3, 4}; cout< 練習9.5:重寫上一題的函數,返回一個迭代器指向找到的元素。注意,程序必須處理未找到給定值的情況。
#include#include using std::cout; using std::endl; using std::vector; vector ::iterator find_x(vector ::iterator begin, vector ::iterator end, int x) { for(; begin != end; ++begin) if (*begin == x) return begin; return end; } int main() { vector vec={1, 2, 4, 3, 5, 8, 10, 3, 4}; cout< 練習9.6:下面程序有何錯誤?你應該如何修改它?
listlist不支持<運算,只支持遞增遞減、==以及!=操作。可改成iter1 != iter2.lst1; list ::iterator iter1 = lst1.begin(), iter2 = lst1.end(); while(iter1 < iter2) /*. . .*/
練習9.7:為了索引int的vector中的元素,應該使用什麼類型?
使用迭代器類型vector
::iterator來索引int的vector中的元素。
練習9.8:為了讀取string的list中的元素,應該使用什麼類型?如果寫入list又該用什麼類型?
為了讀取string的list中的元素,應使用list
::valus_type,寫入數據需要引用類型,使用list ::reference。
練習9.9:begin和cbegin兩個函數有什麼不同?
cbegin是C++新標准引入來的,用來與auto結合使用。它返回指向容器第一個元素的const迭代器,可以用來只讀地訪問容器,但不能對容器元素進行修改。因此,當不需要寫訪問時,應該使用cbegin。
begin則是被重載過的,有兩個版本:其中一個是const成員函數,也返回const迭代器;另一個則返回普通迭代器,可以對容器元素進行修改。
練習9.10:下面4個對象分別是什麼類型?
vectorv1是int的vector類型,可以修改v1的內容,包括添加、刪除元素以及修改元素等操作。v1; const vector v2; auto it1 = v1.begin(), it2 = v2.begin(); auto it3 = v1.begin(), it4 = v2.cbegin();
v2是int的常量vector類型,其內容不能修改,添加、刪除元素以及修改元素值等均不允許。
it1是普通迭代器,可對容器元素進行讀寫訪問,it2是const迭代器,不能對容器元素進行寫訪問。it3和it4都是const迭代器。
練習9.11:對6種創建和初始化vector對象的方法,每一種都給出一個實例。解釋每個vector包含什麼值。
vector
ilist; 默認初始化,vector為空,容器中尚未有元素。 vector
ilist2(ilist); ilist2初始化為ilist的拷貝,ilist必須與ilist2類型相同。 vector
ilist = {1,2,3.0,4,5,6,7};初始化為列表中元素的拷貝,列表中的元素類型必須與ilist的元素類型相容,這裡必須是與整型相容的數值類型。 vector
ilist3(ilist.begin()+2, ilist.end()-1);ilist3初始化為兩個迭代器指定范圍中的元素的拷貝,范圍中的元素類型必須與ilist的元素類型相容,這裡ilist3被初始化為{3, 4, 5, 6}. vector
ilist4(7); 默認值初始化,ilist4中將包含7個元素,每個元素進行缺省的值初始化,對於int,也就是被賦值為0. vector
ilist5(7, 3); 指定值初始化,ilist5被初始化為包含7個值為3的int。
練習9.12:對於接受一個容器創建其拷貝的構造函數,和接受兩個迭代器創建拷貝的構造函數,解釋它們的不同。
接受一個已有容器的構造函數會拷貝瓷容器中的所有元素,這樣,初始化完成後,我們得到此容器的一個一模一樣的拷貝、當我們確實需要一個容器的完整拷貝時,這種初始化方式非常方便。這要求兩個容器的類型以及其元素leixi9ng必須匹配。
當我們不需要已有容器中的全部元素,而只是想拷貝其中一部分元素時,可使用接受兩個迭代器的構造函數。這不要求容器類型相同,新容器和原容器中的元素類型也可以不同,只要能將要拷貝的元素轉換成要初始化的容器的元素類型即可。
練習9.13:如何從一個list
初始化一個vector ? 從一個vector 又該如何創建?編寫代碼驗證你的答案。 對於這兩個初始化,可以采用范圍初始化的方式來構造vector。
#includeusing std::cin; using std::cout; using std::endl; using std::ends; #include using std::vector; #include using std::list; int main() { list
li={0,1,2,3,4,5,5,6,7,9}; list ::iterator b=li.begin(), e=li.end(); vector ivec1={1,2,3,2,5,7,0,2,5}; for(;b!=e;++b) cout<<*b< ivec2(li.begin(), li.end()); for(auto i:ivec2) cout< ivec3(ivec1.begin(), ivec1.end()); for(vector ::iterator it=ivec3.begin();it!=ivec3.end();++it) cout<<*it< 練習9.14:編寫程序,講一個list中的char *指針元素賦值給一個vector中的string。
#include#include #include #include using std::cout; using std::endl; using std::string; using std::vector; using std::list; int main() { list
slist; char s1[] = "Hello"; char s2[] = "C++"; char s3[] = "Primer!"; slist.push_back(s1); slist.push_back(s2); slist.push_back(s3); vector svec; svec.assign(slist.begin(), slist.end()); cout< 練習9.21:如果我們將第308頁中使用的insert返回值將元素添加到list中的循環程序改寫為將元素插入到vector中,分析循環將如何工作。
在循環之前,vector為空,此時將iter初始化為vector首位置,與初始化為尾位置效果是一樣的。循環中第一次調用insert會將讀取的第一個string插入到iter指向位置之前的位置,即令新元素成為vector的首元素。而insert的返回指向此元素的迭代器,我們將它賦予iter,從而使得iter始終指向vector的首元素。接下來每步亦是如此,將新string插入到vector首元素之前的位置,成為新的首元素,並使iter始終指向vector首。
練習9.22:假定iv是一個int的vector,下面的程序存在什麼錯誤?你將如何修改?
vector循環中未對iter進行遞增操作,iter無法向中點推進。其次,即使加入了++iter語句,由於向iv插入元素後,iter已經失效,++iter也不能起到將迭代器向前推進一個元素的作用。::iterator iter = iv.begin(), mid = iv.begin + iv.size()/2; while(iter != mid) if(*iter == some_val) iv.insert(iter, 2*some_val);
#include#include using std::cout; using std::endl; using std::vector; int main() { vector iv= {1, 1, 1, 1, 1}; int some_val = 1; vector ::iterator iter = iv.begin(); int org_size = iv.size(), i = 0; while(i <= org_size/2) { if(*iter == some_val) { iter = iv.insert(iter, 2*some_val); ++iter; ++iter; } ++i; } for(iter = iv.begin(); iter != iv.end(); ++iter) cout<<*iter< 練習9.23:在本節第一個程序中,若c.size()為1,則val、val2、val3和val4的值會是什麼? 4個變量的值一樣,指向唯一的一個元素。
練習9.24:編寫程序,分別使用at、下標運算符、front和begin提去一個vector中的第一個元素。在一個空vector上測試你的程序。
#includeusing std::cout; using std::endl; #include using std::vector; int main() { vector vec; cout< 練習9.25:對於第312頁中刪除一個范圍內的元素的程序,如果elem1與elem2相等會發生什麼?如果elem2是尾後迭代器,或者elem1和elem2皆為尾後迭代器,又會發生什麼? 如果兩個迭代器elem1和elem2相等,則什麼也不會發生,容器保持不變。哪怕兩個迭代器是指向尾後位置也是如此,程序不會出錯。
因此elem1和elem2都是尾後迭代器時,容器保持不變。
如果elem2為尾後迭代器,eleme1指向之前的合法位置,則會刪除從elem1開始直至容器末尾的所有元素。
練習9.26:使用下面代碼定義的ia,將ia拷貝到一個vector和一個list中。使用單迭代器版本的erase從list中刪除奇數元素,從vector中刪除偶數元素。
int ia[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89}
#includeusing std::endl; using std::cout; #include using std::vector; using std::begin; using std::end; #include using std::list; int main() { int ia[]={0,1,1,2,3,5,8,13,21,55,89}; vector
vec(begin(ia), end(ia)); list lis(begin(ia), end(ia)); auto it=vec.begin(); while(it!=vec.end()) { if(!(*it%2)) it=vec.erase(it); else ++it; } auto ir=lis.begin(); while(ir!=lis.end()) { if(*ir%2) ir=lis.erase(ir); else ++ir; } for(auto i: vec) cout< 練習9.27:編寫程序,查找並刪除forward_list 中的奇元素。 #includeusing std::cout; using std::endl; #include using std::forward_list; int main() { forward_list flst={0,1,2,3,4,5,6,7,8,9}; auto prev=flst.before_begin(); auto curr=flst.begin(); while(curr!=flst.end()) { if(*curr & 1) curr=flst.erase_after(prev); else { prev=curr; ++curr; } } for(auto i: flst) cout<>s1>>s2; find_insert(flst,s1,s2); cout<<"Now the forward_list is: "< 練習9.29:假定vec包含25個元素,那麼vec.resize(100)會做什麼?如果接下來調用vec.resize(10)會做什麼? 調用vec.resize(100)會向vec末尾添加75個元素,這些元素將進行值初始化。接下來調用vec.size(10)會將vec末尾90個元素刪除。
練習9.30:接受單個參數的resize版本對元素類型有什麼限制?(如果有的話)
對於元素時類類型,則單參數resize版本要求該類必須提供一個默認構造函數。
練習9.31第316頁中刪除偶數值元素並復制奇數值元素的程序不能用於list或forward_list。為什麼?修改程序,使之也能用於這些類型。
list和forward_list與其他容器的一個不同是,迭代器不支持加減運算,因為鏈表中元素並非在內存中連續存儲,無法通過地址的加減在元素間遠距離移動,可以多次調用++來實現與迭代器加法相同的效果。
#includeusing std::cout; using std::endl; #include using std::list; #include
using std::forward_list; int main() { list lt={0,1,2,3,4,5,6,7,8,9}; forward_list flst={0,1,2,3,4,5,6,7,8,9}; cout<<"List is :"< ::iterator it=lt.begin(); while(it!=lt.end()) { if(*it%2) { it=lt.insert(it, *it); ++it; ++it; } else { it=lt.erase(it); } } cout<<"Now list is: "< 練習9.32:在第316頁的程序中,向下面語句這樣調用insert是否合法?如果不合法,為什麼? iter = vi.insert(inter. *iter++);
不合法。編譯器在編譯代碼時,首先對*iter++求值,傳遞給insert第二個形參,此時iter已指向當前奇數的下一個元素,因此傳遞給insert的第一個參數是錯誤位置,程序執行會發生混亂。
練習9.33:在本節最後一個例子中,如果不將insert的結果賦予begin,將會發生什麼?編寫程序,去掉此賦值語句,驗證你的答案。
向vector中插入新元素後,原有迭代器都會失效。因此,不將insert()返回的迭代器賦予begin,會使begin失效。繼續使用begin會導致程序崩潰。
練習9.34:假定vi是一個保存int的容器,其中有偶數值也有奇數值,分析下面循環的行為,然後編寫程序驗證你的分析是否正確。
iter = vi.begin(); while (iter != vi.end()) if(*iter % 2) iter = vi.insert(iter, *iter); ++iter;這段代碼的第一個錯誤是忘記使用花括號,使得++iter編程循環後的第一條語句,而非循環所期望的循環體的最後一條語句。除非容器為空,否則陷入死循環。若容器的第一個元素為偶數,布爾表達式為假,if語句真分支不會被執行,iter保持不變。循環繼續執行,真分支仍然不會執行,iter繼續保持不變,如此陷入死循環。若容器中的第一個元素是奇數,insert語句被調用,將該值插入到首元素之前,並將返回的迭代器賦予iter,因此iter指向新首元素,繼續執行循環,會繼續將首元素復制到容器首位置,並令iter指向它,如此陷入死循環。
測試程序:#include正確的做法是將++iter移入循環體,再加一個++iter即可。#include #include using std::string; using std::cin; using std::cout; using std::endl; using std::vector; int main() { vector vi={1, 2, 3, 4, 5, 6, 7, 8, 9}; auto iter = vi.begin(); string tmp; while(iter != vi.end()) { if (*iter % 2) iter = vi.insert(iter, *iter); for(auto begin = vi.begin(); begin !=vi.end(); ++begin) cout<<*begin<<" "; cout< >tmp; } ++iter; return 0; }
練習9.35:解釋一個vector的capacity和size有何區別?
capacity返回已經為vector分配了多大內存空間,而size則返回容器當前已經保存了多少個元素。
練習9.36:一個容器的capacity可能小於它的size嗎?
不可能小於size。
練習9.37:為什麼list和array沒有capacity成員函數?
list是鏈表,當有新元素插入時,會從內存空間分配一個新節點保存它;當從鏈表中刪除元素時,該節點占用的內存空間會被立刻釋放。因此一個鏈表占用的內存空間總是與當前保存的元素所需的空間相等。而array是固定大小數組,內存一次性分配,大小不變,不會變化,因此它們均不需要capacity。
練習9.39:解釋下面的程序片段做了什麼:
vector首先,reserve為svec分配了1024個元素的空間。隨後,循環不斷讀入字符,添加到svec末尾,直到遇到文件結束符。這個過程中,如果讀入的字符串數量不多於1024,則svec的capacity保持不變,不會分配新的內存空間。否則,會按一定規則分配更大的內存空間,並進行字符串的移動。接下來,resize將向svec末尾添加當前字符串數量一半那麼多的新字符串,他們的值都是空串。若空間不夠,會分配足夠容納這些字符串的內存空間。svec; svec.reserve(1024); string word; while (cin >> word) svec.push_back(word); svec.resize(svec.resize() + svec.size()/2); 練習9.40:如果上一題中的程序讀入的256個詞,在resize之後的容器的capacity可能是多少?如果讀入了512個、1000個或1048個詞呢?
若讀入了256個詞,則resize之後容器的capacity僵屍384;若讀入了512個詞,則resize之後的容器capacity僵屍768。若讀入了1000個詞,則resize之後容器的capacity是2048;若讀了1048個詞,還是2048。
練習9.41:編寫程序,從一個vector
初始化一個string。
#includeusing std::vector; #include using std::cout; using std::endl; #include using std::string; int main() { vector cvec={'b','e','a','u','t','i','f','u','l'}; string s(cvec.data(),cvec.size()); cout< 練習9.42:假定你希望每次讀取一個字符存入一個string中,而且知道最少需要讀取100個字符,你應該如何提高程序的性能?
可以用reserve先為string分配100個字符的空間,然後逐個讀取字符,用push_back添加到string末尾。
#include#include #include using std::cin; using std::cout; using std::endl; using std::vector; using std::string; void inputString(string &s) { s.reserve(100); char c; while(cin>>c) s.push_back(c); } int main() { string s; inputString(s); cout< 練習9.43:編寫一個函數,接受三個string參數s、oldVal和newVal。使用迭代器及insert和erase函數將s中所有oldVal替換為newVal。測試你的程序,用它替換普通的簡寫形式,如,將”tho“替換為”though“,將”thru“替換為”through“。#include#include #include using std::cin; using std::cout; using std::cerr; using std::endl; using std::vector; using std::string; void rePlace(string &s, const string &oldVal, const string &newVal) { auto l = oldVal.size(); if (!l) return; auto iter = s.begin(); while (iter <= s.end()-1) { auto iter1 = iter; auto iter2 = oldVal.begin(); while(iter != oldVal.end() && *iter1 == *iter2) { ++iter1; ++iter2; } if(iter2 == oldVal.end()) { iter = s.erase(iter, iter1); if (newVal.size()) { iter2 = newVal.end(); do { --iter2; iter = s.insert(iter, *iter2); } while(iter2 > newVal.begin()); } iter += newVal.size(); } else ++iter; } } int main() { string s = "tho thru tho!"; rePlace(s, "thru", "through"); cout< 練習9.44:重寫上一題的函數,這次使用一個下標和replace。#include#include #include using std::cout; using std::endl; using std::vector; using std::string; void rePlace(string &s, const string &oldVal, const string &newVal) { int p = 0; while((p=s.find(oldVal, p)) !=string::npos) { s.replace(p, oldVal.size(), newVal); p += newVal.size(); } } int main() { string s = "tho thru tho!"; rePlace(s, "thru", "through"); cout< 練習9.45:編寫一個函數,接受一個表示名字的string參數和兩個分別表示前綴(如”Mr.“或”Ms.“)和後綴(如”Jr.“或”III“)的字符串。使用迭代器及insert和append函數將前綴和後綴添加到給定的名字中,將新生成的string返回。#includeusing std::cout; using std::endl; #include using std::string; string combine(string &str,const string &s1, const string &s2) { str.insert(0,s1); str.append(s2); return str; } int main() { string str="Tom",s1="Mr.",s2="Jr."; combine(str,s1,s2); cout< 練習9.46:重寫上一題的函數,這次使用位置和長度來管理string,並只使用insert。 #includeusing std::cout; using std::endl; #include using std::string; #include using std::vector; void name_string(string &name, const string &prefix, const string &suffix) { name.insert(0, " "); name.insert(0, prefix); name.insert(name.size(), " "); name.insert(name.size(), suffix); } int main() { string s = "James Bond"; name_string (s, "Mr.", "II"); cout< 練習9.47:編寫程序,首先查找string ”ab2c3d7R4E6“中每個數字字符,然後查找其中每個字母字符。編寫兩個版本的程序,第一個要使用find_first_of,第二個要使用find_first_of。#include練習9.48:假定name和numbers的定義如325頁所示,numbers.find(name)返回什麼?using std::cout; using std::endl; #include using std::string; void use_find_first_of(string str, string find_str) { auto pos = 0; while((pos = str.find_first_of(find_str,pos)) != string::npos) { cout << "char: " << str[pos] << " index: " << pos << endl; ++pos; } cout << endl; } void use_find_first_not_of(string str , string not_find_str) { auto pos = 0; while((pos = str.find_first_not_of(not_find_str,pos)) != string::npos) { cout << "position: " << pos << " char: " << str[pos] << endl; ++pos; } cout << endl; } int main() { string str = "abc3d7R4E6"; string numbers = "0123456789"; string letters = "abcdRE"; use_find_first_of(str, numbers); use_find_first_of(str, letters); use_find_first_not_of(str, letters); use_find_first_not_of(str, numbers); return 0; } s.find(args)查找s中args第一次出現的位置。因此,對325頁給定的name和numbers,在numbers中不存在與name匹配的字符串,find會返回npos。
練習9.49:如果一個字母延伸到中線之上,如d或f,則稱其有上出頭部分。如果一個字母延伸到中線之下,如p或g,則稱其有下出頭部分。編寫程序,讀入一個單詞文件,輸出最長的既不包含上出頭部分,也不不包含下出頭部分的單詞。
#includeusing std::cin; using std::cout; using std::cerr; using std::endl; #include using std::string; #include using std::ifstream; void find_longest_word(ifstream &in) { string s, longest_word; int max_length = 0; while (in >> s) { if(s.find_first_of("bdfghjklpqty") != string::npos) continue; cout<