9.01 對於下面的程序任務,vector, deque和list哪種容器最為合適?解釋你選擇的理由。如果沒有哪一種容器優於其它容器,也請解釋理由。
- 讀取固定數量的單詞,將它們按字典序插入到容器中。我們將在下一章看到,關聯容器更適合這個問題。
- 讀取未知數量的單詞,總是將新單詞插入到末尾。刪除操作在頭部進行。
- 從一個文件中讀取未知數量的整數。將這些整數排序,然后打印到標准輸出。
- 使用list,需要在中間插入,用list效率更高。
- 使用deque。只在頭尾進行操作,deque效率更高。
- 使用vector。排序需要不斷調整位置,vector支持快速隨機訪問,用vector更適合。
9.02 定義一個list對象,其元素類型是int的deque。
list<deque<int>>;
9.03 構成迭代器范圍的迭代器有和限制。
begin和end構成了迭代器范圍:
- 其必須分別指向同一個容器的元素或是尾元素之后的位置。
- 可以通過反復遞增begin來到達end。
9.04 編寫一個函數,接受一對指向vector
bool findVal(vector<int>::const_iterator beg, vector<int>::const_iterator end, int val)
{
while (beg != end) {
if (*beg != val) {
beg ++;
}
else {
return true;
}
}
return false;
}
9.05 重寫上一題的函數,返回一個迭代器指向找到的元素。注意,程序必須處理未找到給定值的情況。
int findVal1(vector<int>::const_iterator beg, vector<int>::const_iterator end, int val)
{
while (beg != end) {
if (*beg != val) {
beg ++;
}
else {
return *beg;
}
}
return *end;
}
9.06 下面程序有何錯誤?你應該如何修改?
list<int> lstl;
list<int>::iterator iter1 = lstl.begin(),
iter2 = lstl.end();
while (iter1 < iter2) /* ... */
迭代器之間沒有大於小於的比較(比較地址的大小沒有必要)。應改為while(iter1 != iter2).
9.07 為了索引int的vector中的元素,應該使用什么類型?
vector<int>::size_type; // 索引應該是為了便於查找,保存元素的位置。應該是unsigned int類型
9.08 為了讀取string的list的元素,應該使用什么類型?如果寫入list,又該使用什么類型?
//讀操作
list<string>::const_iterator iter;
//寫操作
list<string>::iterator iter;
9.09 begin和cbegin兩個函數有什么不同?
begin函數返回的是普通的iterator,而cbegin返回的是const_iterator。
9.10 下面4個對象分別是什么類型?
vector<int> v1;
const vector<int> v2;
auto it1 = v1.begin(), it2 = v2.begin();
auto it3 = v1.cbegin(), it4 = v2.cbegin();
// it1:iterator類型。it2,it3,it4是const_iterator類型。
9.11 對6種創建和初始化vector對象的方法,每一種都給出一個實例。解釋每個vector包含什么值。
vector<int> ivec1; // 空
vector<int> ivec2{1, 2, 3, 4, 5}; // 1, 2, 3, 4, 5
vector<int> ivec3 = {6, 7, 8, 9, 10}; // 6, 7, 8, 9, 10
vector<int> ivec4(ivec2); // 1, 2, 3, 4, 5
vector<int> ivec5 = ivec3; // 6, 7, 8, 9, 10
vector<int> ivec6(10, 4); // 10個4
9.12 對於接受一個容器創建其拷貝的構造函數,和接受兩個迭代器創建拷貝的構造函數,解釋它們的不同。
- 接受一個容器創建其拷貝的構造函數,要求兩個容器類型及元素類型必須匹配。
- 接受兩個迭代器創建拷貝的構造函數,不要求容器類型匹配,而且元素類型也可以不同,只要拷貝的元素能轉換就可以。
9.13 如何用一個list
list<int> ilist = {1, 2, 3, 4, 5};
vector<double> dvec1(ilist.cbegin(), ilist.cend());
vector<int> ivec = {5, 6, 7, 8};
vector<double> dvec2(ivec.cbegin(), ivec.cend());
9.14 編寫程序,將一個list中的char*指針(指向C風格字符串)元素賦值給一個vector中的string。
void test914()
{
list<char*> pch = {"hello world!", "lucy", "mirror"};
vector<string> svec;
svec.assign (pch.cbegin(), pch.cend());
for (auto i : svec) {
cout << i << " ";
}
cout << endl;
}
9.15 編寫程序,判定兩個vector
void test915()
{
vector<int> ivec1{1, 2, 3, 4, 5};
vector<int> ivec2 = {6, 7, 8, 9};
vector<int> ivec3 = {1, 2, 5};
vector<int> ivec4 = {1, 2, 3};
if (ivec1 > ivec2) cout << "ivec1 > ivec2" << endl;
else cout << "ivec1 < ivec2" << endl;
// ivec1 < ivec2
if (ivec1 > ivec3) cout << "ivec1 > ivec3" << endl;
else cout << "ivec1 < ivec3" << endl;
// ivec1 < ivec3
if (ivec1 > ivec4) cout << "ivec1 > ivec4" << endl;
else cout << "ivec1 < ivec4" << endl;
// ivec1 > ivec4
}
9.16 重寫上一題的程序,比較一個list
void test916()
{
vector<int> ivec1{1, 2, 3, 4, 5};
list<int> ilist = {6, 7, 8, 9};
vector<int> ivec2(ilist.cbegin(), ilist.cend());
ivec1 < ivec2 ? cout << "ivec1 < ivec2" << endl : cout << "ivec1 > ivec2" << endl;
}
9.17 假定c1和c2是兩個容器,下面的比較操作有何限制(如果有的話)?
if (c1 < c2)
- 要求c1和c2必須是同類容器,其中元素也必須是同種類型。
- 該容器必須支持比較關系運算。
9.18 編寫程序,從標准輸入讀取string序列,存入一個deque中。編寫一個循環,用迭代器打印deque中的元素。
void test918()
{
deque<string> sdeq;
string word;
while (cin >> word) {
sdeq.push_back(word);
}
for (deque<string>::const_iterator iter = sdeq.cbegin(); iter != sdeq.cend(); iter ++) {
cout << *iter << "\t";
}
cout << endl;
}
9.19 重寫上題程序,用list代替deque。列出程序要做出哪些改變。
- 將deque改為list類型
- iterator的類型也改為list即可。
9.20 編寫程序,從一個list
void test920()
{
list<int> ilist = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
deque<int> even_deq;
deque<int> odd_deq;
for (auto i : ilist) {
if (i % 2) {
odd_deq.push_back(i);
}
else {
even_deq.push_back(i);
}
}
}
9.21 如果我們將308頁中使用insert返回值將元素添加到list中的循環程序改寫為將元素插入到vector中,分析循環將如何工作。
void test921()
{
vector<string> svec;
string word;
auto beg = svec.begin();
while (cin >> word) {
beg = svec.insert(beg, word);
}
}
// 當輸入:hello world lili,輸出為:lili world hello
9.22 假定iv是一個int的vector,下面的程序存在什么錯誤?你將如何修改?
vector<int>::iterator iter = iv.begin(),
mid = iv.begin() + iv.size()/2;
while(iter != mid) {
if (*iter == some_val) {
iv.insert(iter, 2 * some_val);
}
}
修改為:
void test922()
{
vector<int> iv = {1, 3, 5};
iv.reserve(5); // 必須要又足夠的容量,否則size為3,插入一個元素之后size超出,iter就會失效,指向未知地址。
int some_val = 1;
vector<int>::iterator iter = iv.begin(),
mid = iv.begin() + iv.size()/2;
while(iter != mid) {
if (*iter == some_val) {
mid = iv.insert(iter, 2 * some_val); // insert操作要接收返回的迭代器,如果賦給iter,則iter和mid永遠不會相等,因為mid也會隨之改變。mid接收,mid就和iter都指向首元素了
}
else {
mid --;
}
}
9.23 在本節第一個程序(309頁)中,若c.size()為1, 則val, val2, val3和val4的值會是什么?
將會全部指向c容器的首元素。
9.24 編寫程序,分別使用at、下標運算符、front和begin提取一個vector中的第一個元素。在一個空vector上測試你的程序。
void test924()
{
vector<int> ivec;
int val1 = ivec[0]; // segment fault
int val2 = ivec.at(0); // error, out of range, segment fault
int val3 = ivec.front(); // segment fault
int val4 = *ivec.begin(); // segment fault
}
9.25 對於312頁中刪除一個范圍內的元素的程序,如果elem1與elem2相等會發生什么?如果elem2是尾后迭代器,或者elem1和elem2皆為尾后迭代器,又會發生什么?
如果elem1和elem2相等,則不發生刪除操作
如果elem2是尾后迭代器,則刪除elem1之后的元素,返回尾后迭代器。
如果elem1和elem2都是尾后迭代器,則不發生刪除操作。
9.26 使用下面代碼定義的ia,將ia拷貝到一個從vector和一個list中。使用單迭代器版本的erase從list中刪除奇數元素,從vector中刪除偶數元素。
void test926()
{
int ia[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
vector<int> ivec(begin(ia), end(ia));
list<int> ilist(begin(ia), end(ia));
vector<int>::iterator iter = ivec.begin();
while(iter != ivec.end()) {
if (!(*iter%2)) {
iter = ivec.erase(iter);
}else {
iter ++;
}
}
list<int>::iterator beg = ilist.begin();
while(beg != ilist.end()) {
if (*beg%2) {
beg = ilist.erase(beg);
}else {
beg ++;
}
}
}
9.27 編寫程序,查找並刪除forward_list
void test927()
{
int ia[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
forward_list<int> iflst (begin(ia), end(ia));
auto prev = iflst.before_begin();
auto curr = iflst.begin();
while (curr != iflst.end()) {
if (*curr % 2) {
curr = iflst.erase_after(prev);
}
else {
prev = curr;
++curr;
}
}
}
9.28 編寫函數,接受一個forward_list
void insertToFlst(forward_list<string>& sflst, const string& val_in_flst, const string& val_to_insert)
{
auto prev = sflst.before_begin();
auto curr = sflst.begin();
while (curr != sflst.end()) {
if (*curr == val_in_flst) {
sflst.insert_after(curr, val_to_insert);
return;
}
else {
prev = curr;
++ curr;
}
}
sflst.insert_after(curr, val_to_insert);
}
9.29 假定vec包含25個元素,那么vec.resize(100)會做什么?如果接下來調用vec.resize(10)會做什么?
vec.resize(100),會把75個值為0的值添加到vec的結尾。
vec.resize(10),會把vec末尾的90個元素刪除掉。
9.30 接受單個參數的resize版本對元素類型有什么限制(如果有的話)?
如果容器保存的是類類型元素,則使用resize必須提供初始值,或元素類型必須提供一個默認構造函數。
9.31 第316頁中刪除偶數值元素並復制奇數值元素的程序不能用於list或forward_list。為什么?修改程序,使之也能用於這些類型。
void test931_1()
{
list<int> ilist = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto iter = ilist.begin();
while (iter != ilist.end()) {
if (*iter % 2) {
iter = ilist.insert(iter, *iter);
advance(iter, 2); //list和forward_list的迭代器不支持加減操作,因為鏈表的內存不連續,無法通過加減操作來尋址。
} else {
iter = ilist.erase(iter);
}
}
}
void test931_2()
{
forward_list<int> flst = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
auto iter = flst.begin();
auto prev = flst.before_begin();
while (iter != flst.end()) {
if (*iter % 2) {
iter = flst.insert_after(prev, *iter);
advance(prev, 2);
advance(iter, 2);
} else {
iter = flst.erase_after(prev);
}
}
}
9.32 再316頁的程序中,向下面語句這樣調用insert是否合法?如果不合法,為什么?
iter = vi.insert(iter, *iter++);
不合法,因為編譯器不知道應該先執行insert操作,還是執行*iter++操作。或者執行完insert之后,應該先返回,還是對iter進行++操作。
9.33 在本節最后一個例子中,如果不將insert的結果賦予begin,將會發生什么?編寫程序,去掉此賦值語句,驗證你的答案。
程序崩潰,因為迭代器將會失效。
vector<int> ivec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto iter = ivec.begin();
while (iter != ivec.end()) {
++ iter;
/* iter = */ivec.insert(iter, 42);
++ iter;
}
9.34 假定vi是一個保存int的容器,其中有偶數值也要奇數值,分析下面循環的行為,然后編寫程序驗證你的分析是否正確。
iter = vi.begin();
while (iter != vi.end())
if (*iter % 2)
iter = vi.insert(iter, *iter);
++ iter;
不正確,insert是插入到iter之前,返回的是新插入元素的迭代器,因此在插入元素之后,應該加兩次,才能到原始的下一個元素。
auto iter = ivec.begin();
while (iter != ivec.end()) {
if (*iter % 2) {
iter = ivec.insert(iter, *iter);
++ iter;
}
++ iter;
}
9.35 解釋一個vector的capacity和size有何區別?
size是指它已經保存的元素數目,而capacity則是在不分配新的內存空間的前提下最多可以保存的元素。
9.36 一個容器的capacity可能小於它的size嗎?
不可能,當容量不夠,繼續添加元素時,容器會自動擴充容量。capacity>=size。
9.37 為什么list或array沒有capacity成員函數?
因為array時固定長度的。
list不是連續內存,不需要capacity。
9.38 編寫程序,探究在你的標准庫實現中,vector是如何增長的?
void test938()
{
vector<int> ivec;
int value;
while (cin >> value) {
ivec.push_back(value);
cout << "the vector's size = " << ivec.size() << endl;
cout << "the vector's capacity = " << ivec.capacity() << endl;
}
}
// 運行結果如下圖,capacity是雙倍增長的,1,2,4,8,16......
9.39 解釋下面程序片段做了什么?
vector<string> svec;
svec.reserve(1024);
string word;
while (cin >> word) {
svec.push_back(word);
}
svec.resize(svec.size() + svec.size()/2);
先給一個vector分配1024大小的空間,將輸入的值保存進vector。如果輸入的元素大於1024,vector會自動擴容。最后使用resize只能改變vector的size,不能改變其capacity。
9.40 如果上一題的程序讀入了256個詞,在resize之后容器的capacity可能是多少?如果讀入了512個、1000個或1048個詞呢?
elements_num | size | capacity |
---|---|---|
256 | 384 | 1024 |
512 | 768 | 1024 |
1000 | 1500 | 2000 |
1048 | 1572 | 2096 |
當元素數量是1000時,vector自動擴容為元素數量的2倍
9.41 編寫程序,從一個vector
void test941()
{
vector<char> cvec = {'a', 'b', 'c', 'd', 'e'};
string str(cvec.begin(), cvec.end());
cout << str << endl;
}
9.42 假定你希望每次讀取一個字符存入一個string中,而且知道最少需要讀取100個字符,應該如何提高性能。
利用reserve操作預留足夠的內存,這樣就不用在過程中重新分配內存了。
9.43 編寫一個函數,接受三個string參數s,oldVal和newVal.使用迭代器及insert和erase函數將s中所有oldVal替換為newVal。測試你的程序,用它替換通用的簡寫形式,如,將“tho”替換為“though”,將“thru”替換為“through”。
void replaceStr(string& s, string& oldVal, string& newVal)
{
int old_size = oldVal.size();
auto iter_s = s.begin();
auto iter_old = oldVal.begin();
auto iter_new = newVal.begin();
for (iter_s; iter_s <= (s.end()-oldVal.size()+1); ++ iter_s) {
if (s.substr((iter_s-s.begin()), old_size) == oldVal) {
iter_s = s.erase(iter_s, iter_s+old_size); // iter_s指向刪除元素的下一個元素
iter_s = s.insert(iter_s, newVal.begin(), newVal.end()); // iter_s指向插入元素的前一個元素,必須要用iter_s接收返回值,否則原有的迭代器失效。
advance(iter_s, oldVal.size()); // 跳過已經替換的string。
}
}
}
void test943()
{
string str = "abc thru efg thru";
string oldStr = "thru";
string newStr = "through";
replaceStr(str, oldStr, newStr);
cout << str << endl;
}
9.44 重寫上一題的函數,這次使用一個下標和replace。
void replaceStrByReplace(string& s, string& oldVal, string& newVal)
{
int old_size = oldVal.size();
auto iter_s = s.begin();
for (iter_s; iter_s <= (s.end()-oldVal.size()+1); ++ iter_s) {
if (s.substr((iter_s-s.begin()), old_size) == oldVal) {
// s.replace ((iter_s-s.begin()), old_size, newVal);
s = s.replace (iter_s, iter_s+old_size, newVal); // Gcc14編譯器,執行replace之后,iter_s迭代器會失效,必須重新賦值。
iter_s = s.begin();
}
}
}
9.45 編寫一個函數,接受一個表示名字的string參數和兩個分別表示前綴(如“Mr."或”Ms.“)和后綴(Jr或III)的字符串。使用迭代器及insert和append函數將前綴和后綴加到給定的名字中,將生成的新string返回。
string& addToName(string& name, const string& pre, const string& suf)
{
auto beg = name.begin();
string result;
beg = name.insert(beg ,pre.begin(), pre.end());
return name.append (suf);
}
void test945()
{
string pre_name = "Wang";
string after_name = addToName(pre_name, "Miss.", " III");
cout << after_name << endl;
}
9.46 重寫上一題的函數,這次使用位置和長度來管理string,並只使用insert。
void addToName(string& name, const string& pre, const string& suf)
{
auto beg = name.begin();
string result;
beg = name.insert(beg ,pre.begin(), pre.end());
name.insert(name.end()-1, suf.begin(), suf.end());
}
9.47 編寫程序,首先查找string "ab2c3d7R4E6"中的每個數字字符,然后查找其中每個字母字符。編寫兩個版本的程序,第一個要使用find_fisrt_of,第二個要使用find_first_not_of.
// version 1
void test947_1()
{
string str = "ab2c3d7R4E6";
string numbers = "0123456789";
string alphbet = "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST";
int pos;
for (pos = 0; (pos = str.find_first_of(numbers, pos))!= string::npos; ++pos) {
cout << str[pos] << " ";
}
cout << endl;
for (pos = 0; (pos = str.find_first_of(alphbet, pos)) != string::npos; ++pos) {
cout << str[pos] << " ";
}
cout << endl;
}
// version 2: 將for循環寫為:
for (pos = 0; (pos = str.find_first_not_of(alphbet, pos))!= string::npos; ++pos) { cout << str[pos] << " ";
}
for (pos = 0; (pos = str.find_first_of(numbers, pos)) != string::npos; ++pos) {
cout << str[pos] << " ";
}
cout << endl;
9.48 假定name和numbers的定義如325頁所示,number.find(name)返回什么?
string name = "AnnaBelle";
string number = "0123456789";
auto result = number.find(name);
if (result == string::npos) {
cout << "npos" << endl;
}
9.49 如果一個字母延伸要中線之上,如d或f,則稱其有上出頭部分。如果延伸到中線之下,則稱其有下出頭部分。編寫程序,讀入一個單詞文件,輸出最長的既不包含上出頭部分,也不包含下出頭部分的單詞。
void test949()
{
string str = "aacgaesse";
string s = "aceimnorsuvwxz";
int pos1 = 0, pos2 = 0, pos3 = 0, max_len = 0;
string result;
for (pos1; (pos1 = str.find_first_of(s, pos1))!=string::npos; ++ pos1) {
pos2 = pos1;
if((pos2 = str.find_first_not_of(s, pos2)) != string::npos) {
if ((pos2-pos1) > max_len) {
max_len = pos2-pos1;
pos3 = pos1;
pos1 = pos2;
}
} else {
if (max_len < (str.size() - pos1)) {
max_len = str.size() - pos1;
pos3 = pos1;
break;
}
}
}
result = str.substr(pos3, max_len);
cout << result << endl;
}
9.50 編寫程序處理一個vector
void test950()
{
vector<string> svec = {"12", "3", "45"};
int sum_int = 0;
double sum_double = 0.0;
for (auto const i : svec) {
sum_int += std::stoi(i);
sum_double += stod(i);
}
cout << sum_int << endl;
cout << sum_double << endl;
}
9.51 設計一個類,它有三個unsigned成員,分別表示年、月和日。為其編寫構造函數,接受一個表示日期的string參數。你的構造函數應該能處理不同數據格式,如January 1,1900、1/1/1990、Jan 1 1900等。
class Mydate
{
public:
Mydate(const string& d)
{
int format; // January 1,1900 : 1
// 1/1/1990 : 2
// Jan 1 1900 : 3
int tag = 0;
string numbers = "0123456789";
if (d.find_first_of(",") != string::npos) {
format = 1;
} else if (d.find_first_of("/") != string::npos) {
format = 2;
} else if ((d.find_first_of(" ") != string::npos) && (d.find_first_of(",") == string::npos)) {
format = 1;
tag = 1;
}
switch(format) {
case 1:
{
if (d.find("Jan") < d.size()) m_month = 1;
if (d.find("Feb") < d.size()) m_month = 2;
if (d.find("Mar") < d.size()) m_month = 3;
if (d.find("Apr") < d.size()) m_month = 4;
if (d.find("May") < d.size()) m_month = 5;
if (d.find("Jun") < d.size()) m_month = 6;
if (d.find("Jul") < d.size()) m_month = 7;
if (d.find("Aug") < d.size()) m_month = 8;
if (d.find("Sep") < d.size()) m_month = 9;
if (d.find("Oct") < d.size()) m_month = 10;
if (d.find("Nov") < d.size()) m_month = 11;
if (d.find("Dec") < d.size()) m_month = 12;
char ch = ' ';
if (0 == tag) {
ch = ',';
}
m_day = stoi(d.substr(d.find_first_of(numbers), (d.find_first_of(ch) - d.find_first_of(numbers))));
m_year = stoi(d.substr(d.find_last_of(ch)+1, 4));
break;
}
case 2:
m_day = stoi(d.substr(0, d.find_first_of("/")));
m_month = stoi(d.substr(d.find_first_of("/")+1, (d.find_last_of("/") - d.find_first_of("/"))));
m_year = stoi(d.substr(d.find_last_of("/")+1, 4));
break;
default:
m_year = 0;
m_month = 0;
m_day = 0;
break;
}
}
void print()
{
cout << m_year << "-" << m_month << "-" << m_day << endl;
}
private:
unsigned m_year;
unsigned m_month;
unsigned m_day;
};
void test951()
{
Mydate d1("1/12/1997");
Mydate d2("Jan 1 1900");
Mydate d3("January 1,1900");
d1.print();
d2.print();
d3.print();
}
9.52 使用stack處理括號化的表達式。當年看到一個左括號,將其記錄下來。當在一個左括號之后看到一個右括號,從stack中pop對象,直至遇到左括號,將左括號也一起彈出棧。然后將一個值push到棧中,表示一個括號化的表達式已經處理完畢,被其運算結果所替代。
void test952()
{
string str = "2+(1+3)";
int result = 0;
bool flag = false;
stack<char> st;
for (int i = 0; i < str.size(); i++) {
if (str[i] == '(') {
flag = true;
continue;
} else if (str[i] == ')') {
flag = false;
}
if (true == flag) {
st.push(str[i]);
}
}
char ch;
int a, b;
string expression;
while (!st.empty()) {
ch = st.top();
st.pop();
expression += ch;
}
a = stoi(expression.substr(0, 1));
string symbol = expression.substr(1, 1);
b = stoi(expression.substr(2, 1));
if (symbol == "+") {
result = a+b;
}
st.push(result);
cout << result << endl;
}