C++ primer 第九章習題

chapter9 順序容器

練習

9.1 節練習

練習9.1

  • 對於下面的程序任務,vector、deque和list哪種容器最爲適合?解釋你的選擇的理由。如果沒有哪一種容器優於其他容器,也請解釋理由。

(a) 讀取固定數量的單詞,將它們按字典序插入到容器中。我們將在下一章中看到,關聯容器更適合這個問題。
(b) 讀取未知數量的單詞,總是將單詞插入到末尾。刪除操作在頭部進行。
© 從一個文件讀取未知數量的整數。將這些數排序,然後將它們打印到標準輸出。

(a)list。由於要求按字典順序插入到容器中,因此需要進行中間插入,使用list較爲合適。

(b)deque。由於需要在頭部和末尾進行插入和刪除操作,而deque是最適合的。

(c)vector。需要對讀入容器中的數量進行排序,而vector支持隨機訪問,更適合用於此任務。

9.2 節練習

練習9.2

  • 定義一個list對象,其元素類型是int的deque。

list<deque<int>> lst;

9.2.1 節練習

練習9.3

  • 構成迭代器範圍的迭代器有何限制?

begin和end必須均指向同一個容器中的元素或尾元素之後的位置。且end不在begin之前。

練習9.4

  • 編寫函數,接受一對指向vector的迭代器和一個int值。在兩個迭代器指定的範圍中查找給定的值,返回一個布爾值來指出是否找到。
bool func(vector<int>::iterator first, vector<int>::iterator secornd, int val){
    if (first < second){
        vector<int>::iterator begin = first, end = second;
    }else{
        vector<int>::iterator begin = second, end = first;
    }
    while(begin <= end){    //begin和end可能指向同一個目標
        if (*begin == val)
            return true;
        ++begin;
    }
    return false;    //如果循環結束依然沒有返回值,說明迭代器範圍內沒有給定的值
}

練習9.5

  • 重寫上一題的函數,返回一個迭代器指向找到的元素。注意,程序必須處理未找到給定值的情況。
int func(vector<int>::iterator first, vector<int>::iterator secornd, int val){
    if (first < second){
        vector<int>::iterator begin = first, end = second;
    }else{
        vector<int>::iterator begin = second, end = first;
    }
    while(begin <= end){    //begin和end可能指向同一個目標
        if (*begin == val)
            return *begin;
        ++begin;
    }
    return -1;    //如果循環結束依然沒有返回值,說明迭代器範圍內沒有給定的值。用-1代表失敗。
}

練習9.6

  • 下面程序有何錯誤?你應該如何修改它?
list<int> lst1;
list<int>::iterator iter1 = lst1.begin(),iter2 = lst1.end();
while (iter1 < iter2) /* ... */

list不是隨機訪問容器,因此迭代器沒有重載小於運算符,故不能使用iter1 < iter2的形式。

改爲iter1 != iter2

9.2.2 節練習

練習9.7

  • 爲了索引int的vector中的元素,應該使用什麼類型?

vector<int>::size_type

練習9.8

  • 爲了讀取string的list中的元素,應該使用什麼類型?如果寫入list,又應該使用什麼類型?

list<string>::const_reference

list<string>::reference

9.2.3 節練習

練習9.9

  • begin和cbegin兩個函數有什麼不同?

當使用auto類型時,cbegin必定返回常量類型,而begin則依據容器類型決定返回常量類型或非常量類型。

練習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 是vector<int>::iterator。
it2 是vector<int>::const_iterator。
//但由於auto僅能作爲一種類型使用,而it1和it2不是同一種類型,因此將發生編譯錯誤。!!!
it3 是vector<int>::const_iterator。
it4 是vector<int>::const_iterator。

9.2.4 節練習

練習9.11

  • 對6種創建和初始化vector對象的方法,每一種都給出一個實例。解釋每個vector包含什麼值。
vector<int> v1;             //默認初始化,不包含任何內容
vector<int> v2 = {1,2,3};   //初始化爲列表中的拷貝。包含1,2,3這3個int值。
vector<int> v3 = v2;        //初始化爲另一個vector對象的拷貝。包含1,2,3這3個int值。
vector<int> v4(v3.begin(), v3.end());    //初始化爲兩個迭代器指定範圍內的元素的拷貝。同上。
vector<int> v5(3);          //包含3個元素,進行了值初始化。3個值爲0的int值。
vector<int> v6(3, 3);       //包含3個值爲3的元素。

練習9.12

  • 對於接受一個容器創建其拷貝的構造函數,和接受兩個迭代器創建拷貝的構造函數,解釋它們的不同。

接受容器創建其拷貝的構造函數,其元素類型和容器類型均必須相同。

接受兩個迭代器創建拷貝的構造函數,其容器不一定相同,同時其元素類型也可以不相同,只要能夠轉換成所需的元素類型即可。

練習9.13

  • 如何從一個list初始化一個vector?從一個vector又該如何創建?編寫代碼驗證你的答案。
list<int> lst(2,2);
vector<int> vi(2,2);
vector<double> v1(lst.begin(), lst.end());
vector<double> v2(vi.begin(), vi.end());

9.2.5 節練習

練習9.14

  • *編寫程序,將一個list中的char 指針(指向C風格字符串)元素賦值給一個vector中的string。
int main() {
	char ca1[] = "Hello, World.";
	list<char *> lst(1, ca1);
	vector<string> v;
	v.assign(lst.begin(), lst.end());
	cout << *v.begin() << endl;
	return 0;
}

9.2.7 節練習

練習9.15

  • 編寫程序,判定兩個vector是否相等。
bool compare(const vector<int>& v1, const vector<int>& v2){
    bool result = true;
    if (v1.size() == v2.size()){
        vector<int>::const_iterator begin1 = v1.begin(), begin2 = v2.begin();
        while(begin1 != v1.end()){
            if (*begin1 != *begin2){
                result = false;
                break;
            }
            ++begin1, ++begin2;
        }
    }
    return result;
}

練習9.16

  • 重寫上一題的程序,比較一個list中的元素和一個vector中的元素。
bool compare(const list<int>& lst1, const vector<int>& v2) {
	bool result = true;
	if (lst1.size() == v2.size()) {
		list<int>::const_iterator begin1 = lst1.begin();
		vector<int>::const_iterator begin2 = v2.begin();
		while (begin1 != lst1.end()) {
			if (*begin1 != *begin2) {
				result = false;
				break;
			}
			++begin1, ++begin2;
		}
	}
	return result;
}

練習9.17

  • 假定c1和c2是兩個容器,下面的比較操作有何限制(如果有的話)?
if (c1 < c2)

首先,c1,c2不可以是無序關聯容器。其次,c1,c2必須是保存相同類型的元素的相同類型的容器。

9.3.1 節練習

練習9.18

  • 編寫程序,從標準輸入讀取string序列,存入一個deque中。編寫一個循環,用迭代器打印deque中的元素。
int main(){
    string str;
    deque<string> deq;
    while(cin >> str)
        deq.push_back(str);
    deque<string>::const_iterator iter = deq.begin();
    while (iter != deq.end()){
        cout << *iter << endl;
        ++iter;
    }
    return 0;
}

練習9.19

  • 重寫上題的程序,用list替代deque。列出程序要做出哪些改變。
int main(){
    string str;
    list<string> deq;
    while(cin >> str)
        deq.push_back(str);
    list<string>::const_iterator iter = deq.begin();
    while (iter != deq.end()){
        cout << *iter << endl;
        ++iter;
    }
    return 0;
}

不做任何改變,用list替代deque即可。

練習9.20

  • 編寫程序,從一個list拷貝元素到兩個deque中。值爲偶數的所有元素都拷貝到一個deque中,而奇數值元素都拷貝到另一個deque中。
int main(){
    list<int> lst = {1,2,3,4,5,6,7,8,9,10};
    deque<int> odd, even;
    list<int>::iterator iter = lst.begin();
    while (iter != lst.end()){
        if (*iter % 2 == 1)
            odd.push_back(*iter);
        else
            even.push_back(*iter);
		++iter;
    }
    return 0;
}

練習9.21

  • 如果我們將第308頁中使用insert返回值將元素添加到list中的循環程序改寫爲將元素插入到vector中,分析循環將如何工作。

循環依然等價於調用push_front,將標準輸入的字符依次插入vector的頭部,但將非常耗時。

練習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);

1.迭代器在循環中沒有自增,將始終原地踏步。

2.當iv成功插入新元素後,iter,mid都將失效。

修改如下。

	vector<int>::iterator iter = iv.begin();
	auto half = iv.size() >> 1;
	while ((iv.end() - iter) >  half ) {    //利用距離替代mid來判斷
		if (*iter == some_val) {
			iter = iv.insert(iter, 2 * some_val);
			++iter;    //如果插入了新元素,多向後迭代一步
		}
		++iter;
	}

9.3.2 節練習

練習9.23

  • 在本節第一個程序(第309頁)中,若c.size()爲1,則val、val2、val3和val4的值會是什麼?

均爲c中的第一個元素。

練習9.24

  • 編寫程序,分別使用at、下標運算符、front和begin提取一個vector中的第一個元素。在一個空vector上測試你的程序。
int main(){
    vector<int> v;
    int val1 = v.at(0); // terminating with uncaught exception of type std::out_of_range
    int val2 = v[0];          // Segmentation fault: 11
    int val3 = v.front();     // Segmentation fault: 11
    int val4 = *v.begin();    // Segmentation fault: 11
    return 0;
}

9.3.3 節練習

練習9.25

  • 對於第312中刪除一個範圍內的元素的程序,如果elem1與elem2相等會發生什麼?如果elem2是尾後迭代器,或者elem1和elem2皆爲尾後迭代器,又會發生什麼?

如果elem1與elem2相等,則刪除區間爲0,故將不刪除元素。

如果elem2是尾後迭代器,則刪除elem1及之後的全部元素。

若均爲尾後迭代器,則不刪除元素。

練習9.26

  • 使用下面代碼定義的ia,將ia拷貝到一個vector和一個list中。使用單迭代器版本的erase從list中刪除奇數元素,從vector中刪除偶數元素。

  •   int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
      
    
int main() {
	int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
	vector<int> v;
	list<int> lst;
	for (int i : ia) {
		v.push_back(i);
		lst.push_back(i);
	}
	vector<int>::iterator iterV = v.begin();
	while (iterV != v.end()) {
		if (*iterV % 2 != 1)
			iterV = v.erase(iterV);
		else
			++iterV;
	}
	list<int>::iterator iterL = lst.begin();
	while (iterL != lst.end()) {
		if (*iterL % 2)
			iterL = lst.erase(iterL);
		else
			++iterL;
	}
}

9.3.4 節練習

練習9.27

  • 編寫程序,查找並刪除forward_list中的奇數元素。
int main(){
    forward_list<int> 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 % 2)
            curr = flst.erase_after(prev);    //通過前驅刪除當前元素
        else{
            prev = curr;
            ++curr;
        }
    }
    for (auto i : flst)
        cout << i;
}

練習9.28

  • 編寫函數,接受一個forward_list和兩個string共三個參數。函數應在鏈表中查找第一個string,並將第二個string插入到緊接着第一個string之後的位置。若第一個string未在鏈表中,則將第二個string插入到鏈表末尾。
void func(forward_list<string> &flst, string str1, string str2) {
	auto prev = flst.before_begin();
	auto curr = flst.begin();
	while (curr != flst.end()) {
		if (*curr == str1) {
			curr = flst.insert_after(curr, str2);
			return;
		}
		else {
			prev = curr;
			++curr;
		}
	}
	flst.insert_after(prev, str2);
}

9.3.5 節練習

練習9.29

  • 假定vec包含25個元素,那麼vec.resize(100)會做什麼?如果接下來調用vec.resize(10)會做什麼?

會默認初始化75個元素添加到vec的末尾。

會將vec中的後90個元素刪除。

練習9.30

  • 接受單個參數的resize版本對元素類型有什麼限制(如果有的話)?

元素類型必須提供一個默認構造函數。

9.3.6 節練習

練習9.31

  • 第316頁中刪除偶數值元素並複製奇數值元素的程序不能用於list或forward_list。爲什麼?修改程序,使之也能用於這些類型。

list和forward_list不支持隨機訪問,因此其迭代器也不支持+=2的加減運算。其原因在於鏈表類型容器的元素存儲是不連續的,無法通過增加固定的數字來移動迭代器。

//forward_list
int main() {
	forward_list<int> lst = { 0,1,2,3,4,5,6,7,8,9 };
	auto curr = lst.begin();
	auto prev = lst.before_begin();
	while (curr != lst.end()) {
		if (*curr % 2) {
			lst.insert_after(prev, *curr);
			prev = curr;
			++curr;
		}
		else
			curr = lst.erase_after(prev);
	}
	for (auto i : lst)
		cout << i << endl;
}
//list
int main() {
	list<int> lst = { 0,1,2,3,4,5,6,7,8,9 };
	auto curr = lst.begin();
	while (curr != lst.end()) {
		if (*curr % 2) {
			curr = lst.insert(curr, *curr);
			++curr;
			++curr;
		}
		else
			curr = lst.erase(curr);
	}
	for (auto i : lst)
		cout << i << endl;
}

練習9.32

  • 在第316頁的程序中,像下面語句這樣調用insert是否合法?如果不合法,爲什麼?

  •   iter = vi.insert(iter, *iter++);
      
    

不合法。雖然上面語句可以正常運行,但是由於在C++中並沒有指定函數形參的入棧順序,因此可能是先輸入iter這個形參,或是先輸入*iter++這個形參。對於這兩種情況而言,得到的結果將會不同。

因此是不合法的。

練習9.33

  • 在本節最後一例中,如果不將insert的結果賦予begin,將會發生什麼?編寫程序,去掉此賦值語句,驗證你的答案。

如果容器是vector或string以及deque,迭代器將會失效,程序崩潰。如果是list將正常運行。

練習9.34

  • 假定vi是一個保存int的容器,其中有偶數值也要奇數值,分析下面循環的行爲,然後編寫程序驗證你的分析是否正確。
iter = vi.begin();
while (iter != vi.end())
    if (*iter % 2)
        iter = vi.insert(iter, *iter);
    ++iter;

遇到奇數時複製該奇數再次插入,並移動至下個元素。

然而iter將被重新賦值爲iter之前的元素,因此移動至原先的下個元素需要自增iter兩次。故將死循環。

9.4 節練習

練習9.35

  • 解釋一個vector的capacity和size有何區別。

size是指vector已經保存的元素的數目,capacity是vector在不分配新的內存空間的前提下它最多可以保存多少元素。

練習9.36

  • 一個容器的capacity可能小於它的size嗎?

不可能

練習9.37

  • 爲什麼list或array沒有capacity成員函數?

list的元素不是以連續的地址存儲的,因此不需要擔心當前內存地址不夠用的問題,只需找到新的可用空間即可。

array是固定長度的數組,其capacity在初始化之初就固定了,因此其成員函數無意義。

練習9.38

  • 編寫程序,探究在你的標準實現中,vector是如何增長的。
int main() {
    vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    v.reserve(50);
    cout << "v: size: " << v.size()
         << " capacity: "  << v.capacity() << endl;
    while (v.size() != v.capacity())
        v.push_back(0);
    cout << "v: size: " << v.size()
         << " capacity: "  << v.capacity() << endl;
    v.push_back(42);
    cout << "v: size: " << v.size()
         << " capacity: "  << v.capacity() << endl;    //增加1/2原有長度
    return 0;
}

練習9.39

  • 解釋下面程序片段做了什麼:

  •   vector<string> svec;
      svec.reserve(1024);	//爲該vevtor申請1024個元素空間
      string word;
      while (cin >> word)
          svec.push_back(word);
      svec.resize(svec.size() + svec.size() / 2);	//根據vector內元素數目將其擴大0.5倍
      
    

練習9.40

  • 如果上一題的程序讀入了256個詞,在resize之後容器的capacity可能是多少?如果讀入了512個、1000個、或1048個呢?

256:1024,512:1024 resize後數量不超過預留空間,因此capacity不改變。

1000:因爲resize會添加元素進入容器,因此vector將根據具體實現提升capacity,且不低於1500。

1048:因爲resize會添加元素進入容器,因此vector將根據具體實現提升capacity,且不低於1524。

9.5.1 節練習

練習9.41

  • 編寫程序,從一個vector初始化一個string。
vector<char> vc = {'H', 'E', 'L', 'L', 'O'};
string str(vc.begin(), vc.end());

練習9.42

  • 假定你希望每次讀取一個字符存入一個string中,而且知道最少需要讀取100個字符,應該如何提高程序的性能?

預先將該string執行成員函數reserve(100)。這樣就能極大減少初期的擴容操作。

9.5.2 節練習

練習9.43

  • 編寫一個函數,接受三個string參數是s、oldVal 和newVal。使用迭代器及insert和erase函數將s中所有oldVal替換爲newVal。測試你的程序,用它替換通用的簡寫形式,如,將"tho"替換爲"though",將"thru"替換爲"through"。
void func(string &s, const string &oldVal, const string &newVal) {
	string::iterator iter = s.begin();
	auto len = oldVal.size();
    //因爲(iter, iter + len)是個左閉右開區間,因此判斷條件爲iter + len - 1 != s.end()
	while (s.end() - len + 1 != iter) {
		if (oldVal == string(iter, iter + len)) {
			iter = s.erase(iter, iter + len);
			iter = s.insert(iter, newVal.begin(), newVal.end());
			iter += len;
		}
		else
			++iter;
	}
}

練習9.44

  • 重寫上一題的函數,這次使用一個下標和replace。
void func(string &s, const string &oldVal, const string &newVal) {
	string::size_type pos = 0;
	auto len = oldVal.size();
	while (pos + len - 1 != s.size()) {    //如果插入值後,s.size()會變,因此不能把它保存在對象裏
		if (oldVal == s.substr(pos, len)) {
			s.replace(pos, len, newVal);
			pos += len;
		}
		else
			++pos;
	}
}

練習9.45

  • 編寫一個函數,接受一個表示名字的string參數和兩個分別表示前綴(如"Mr.“或"Ms.”)和後綴(如"Jr.“或"III”)的字符串。使用迭代器及insert和append函數將前綴和後綴添加到給定的名字中,將生成的新string返回。
string func(const string & str,const string & prefix,const string & suffix){
    string result(str);
    auto beg = result.begin();
    result.insert(beg, prefix.begin(), prefix.end());
    result.append(suffix);
    return result;
}

練習9.46

  • 重寫上一題的函數,這次使用位置和長度來管理string,並只使用insert。
string func(const string & str, const string & prefix, const string & suffix) {
	string result(str);
	result.insert(0, prefix);
	result.insert(result.size(), suffix);
	return result;
}

9.5.3 節練習

練習9.47

  • 編寫程序,首先查找string"ab2c3d7R4E6"中每個數字字符,然後查找其中每個字母字符。編寫兩個版本的程序,第一個要使用find_first_of,第二個要使用find_first_not_of。
int main() {
	string str("ab2c3d7R4E6");
	string letter("abcdRE");
	string number("23467");
	string::size_type pos = 0;
	while ((pos = str.find_first_of(number, pos)) != string::npos) {
		cout << "found number at index: " << pos << ", element is " << str[pos] << endl;
		++pos;
	}
	pos = 0;
	while ((pos = str.find_first_of(letter, pos)) != string::npos) {
		cout << "found letter at index:" << pos << ", element is " << str[pos] << endl;
		++pos;
	}
	pos = 0;
	while ((pos = str.find_first_not_of(letter, pos)) != string::npos) {
		cout << "found number at index:" << pos << ", element is " << str[pos] << endl;
		++pos;
	}
	pos = 0;
	while ((pos = str.find_first_not_of(number, pos)) != string::npos) {
		cout << "found letter at index:" << pos << ", element is " << str[pos] << endl;
		++pos;
	}
}

練習9.48

  • 假定name和numbers的定義如325頁所示,numbers.find(name)返回什麼?

string::npos

練習9.49

  • 如果一個字母延伸到中線之上,如d 或 f,則稱其有上出頭部分(ascender)。如果一個字母延伸到中線之下,如p或g,則稱其有下出頭部分(descender)。編寫程序,讀入一個單詞文件,輸出最長的既不包含上出頭部分,也不包含下出頭部分的單詞。
int main(int argc, char const * argv[]){
    string noneed("bdfhkltgjpqy");
    string word, result;
    fstream& in(argv[1]);
    while(in >> word){
        if (word.find_first_in(noneed) == string::npos && word.size() > result.size())
            result = word;
    }
    cout << result;
}

9.5.5 節練習

練習9.50

  • 編寫程序處理一個vector,其元素都表示整型值。計算vector中所有元素之和。修改程序,使之計算表示浮點值的string之和。
int main() {
	vector<string> v = { "1", "2", "3", "4", "5" };
	int resultI = 0;
	for (const string& str : v)
		resultI += stoi(str);
	cout << resultI << endl;
	float resultF = 0.0;
	for (const string& str : v)
		resultF += stof(str);
	cout << resultF << endl;
}

練習9.51

  • 設計一個類,它有三個unsigned成員,分別表示年、月和日。爲其編寫構造函數,接受一個表示日期的string參數。你的構造函數應該能處理不同的數據格式,如January 1,1900、1/1/1990、Jan 1 1900 等。
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Date
{
	friend void print(Date& item);
private:
	unsigned year;
	unsigned month;
	unsigned date;
public:
	Date(const string& s);
	unsigned change_to_digit(const string& s);
};
unsigned Date::change_to_digit(const string& s)
{
	string numbers{ "0123456789" };
	if (s.find_first_of(numbers) != string::npos)
		return stoi(s);
	vector<string> v{ "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" };
	decltype(v.size()) loc = 0;
	for (decltype(v.size()) i = 0; i != v.size(); ++i)
		if (s.find(v[i]) != string::npos)
		{
			loc = i + 1;	break;
		}
	return loc;
}
Date::Date(const string& s)
{
	string punct{ " /," };
	auto pos = s.find_first_of(punct);
	month = change_to_digit(s.substr(0, pos));
	++pos;
	auto pos2 = s.find_first_of(punct, pos);
	date = stoi(s.substr(pos, pos2 - pos));
	++pos2;
	auto pos3 = s.find_first_of(punct, pos2);
	year = stoi(s.substr(pos2, pos3 - pos2));
}

void print(Date& item)
{
	cout << item.year << " " << item.month << " " << item.date << endl;
}
int main()
{
	Date date1("January 1,1990");
	print(date1);
	Date date2("1/1/1990");
	print(date2);
	Date date3("Jan 1 1990");
	print(date3);
	system("pause");
}

9.6 節練習

練習9.52

  • 使用stack處理括號化的表達式。當你看到一個左括號,將其記錄下來。當你在一個左括號之後看到一個右括號,從stack中pop對象,直至遇到左括號,將左括號也一起彈出棧。然後將一個值(括號內的運算結果)push到棧中,表示一個括號化的(子)表達式已經處理完畢,被其運算結果所替代。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章