第 10 章

10.1

【出題思路】html

本題練習泛型算法的簡單使用。前端

【解答】ios

泛型算法的使用其實很簡單,記住關鍵一點:泛型算法不會直接調用容器的操做,而是經過迭代器來訪問、修改、移動元素。對於本題,將 vector 的 begin 和 end 傳遞給 count,並將要查找的值做爲第三個參數傳遞給它便可。c++

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <algorithm>

int main() {
    // 10.1
    std::vector<int> ivec = {1, 2, 3, 4, 5, 6, 6, 6, 2};
    std::cout << "ex 10.1: " << std::count(ivec.cbegin(), ivec.cend(), 6)
              << std::endl;

    // 10.2
    std::list<std::string> slst = {"aa", "aaa", "aa", "cc"};
    std::cout << "ex 10.2: " << std::count(slst.cbegin(), slst.cend(), "aa")
              << std::endl;

    return 0;
}
// 運行結果
ex 10.1: 3
ex 10.2: 2

Process finished with exit code 0

10.2

【出題思路】git

理解 「泛型」 的優勢。github

【解答】算法

(代碼已在 10.1 中實現)能夠看到,與上一題對比,程序的變化只在不一樣類型變量的聲明上,而算法的使用部分幾乎沒有任何差別。express

10.3

【出題思路】編程

練習泛型算法的使用。數據結構

【解答】

只讀算法 accumulate 定義在頭文件 numeric 中。accumulate 函數接受三個參數,前兩個指出了須要求和的元素的範圍,第三個參數是和的初值。

#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> ivec;
    int i = 1;
    while (i != 101) {
        ivec.push_back(i);
        ++i;
    }

    // 對 ivec 中的元素求和,和的初始值是 0
    int sum = std::accumulate(ivec.cbegin(), ivec.cend(), 0);
    std::cout << sum << std::endl;

    return 0;
}
// 運行結果
5050

Process finished with exit code 0

10.4

【出題思路】

理解 accumulate 的第三個參數。

【解答】

accumulate 的第三個參數是和的初值,它還決定了函數的返回類型,以及函數中使用哪一個加法運算符。

所以,本題中的調用是錯誤的,第三個參數 0 告知 accumulate,和是整型的,使用整型加法運算符。下面咱們嘗試輸入帶小數的值,函數返回的是一個整數。

正確的調用方法是將 0.0 做爲第三個參數傳遞給 accumulate。

#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<double> v = {-9.21, 3.14};

    // 錯誤的調用方法
    std::cout << std::accumulate(v.cbegin(), v.cend(), 0) << std::endl;
    // 正確的調用方法
    std::cout << std::accumulate(v.cbegin(), v.cend(), 0.0) << std::endl;

    return 0;
}
-5
-6.07

Process finished with exit code 0

10.5

【出題思路】

理解 equal 如何比較元素。

Difference between <string.h> and ?

【解答】

equal 使用 == 運算符比較兩個序列中的元素。string 類重載了 ==,可比較兩個字符串是否長度相等且其中元素對應位相同。而 C 風格字符串本質是 char * 類型,用 == 比較兩個 char * 對象,只是檢查兩個指針值(地址)是否相等,即地址是否相等,而不會比較其中字符是否相同。因此,只有當兩個序列中的指針都指向相同的地址時,equal 纔會返回 true。不然,即便字符串內容徹底相同,也會返回 false。

代碼來自

/*
 * @Brief  In the call to equal on rosters, what
 * would happen if both rosters held C-style strings,
 * rather than library strings?
 * @Answer It's not quite the same as `std::string`
 * Maybe the function 'equal' return true when you
 * make a comparison between two c-style strings containers.
 * Nonetheless, we need to keep in mind that when it comes
 * to comparison of c-style strings, we need to use 'strcmp'
 * but not simply relational operators, for using relational
 * operators is just comparison between the address of two
 * c-style strings but not their values.
*/

#include <iostream>
#include <algorithm>
#include <vector>
#include <list>

int main() {
    char c1[] = "c++ primer";
    char c2[] = "c++ primer";
    std::vector<char *> roster1{c1};
    std::list<char *> roster2{c2};
    std::cout << std::equal(roster1.cbegin(), roster1.cend(), roster2.cbegin()) << std::endl;

    return 0;
}
// 運行結果
0

Process finished with exit code 0

注:只是地址的比較,地址不一樣 equal 返回 0.

10.6

【出題思路】

練習使用 fill_n。

【解答】

fill_n 接受一個迭代器,指出起始位置,還接受一個整數,指出設置的元素數目,第三個參數則是要設置的值。

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> ivec{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    fill_n(ivec.begin(), ivec.size(), 0);

    for (int i : ivec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
// 運行結果
0 0 0 0 0 0 0 0 0 0 

Process finished with exit code 0

10.7

【出題思路】

進一步理解泛型算法的特色。

【解答】

(a)是錯誤的。由於泛型算法的一個基本特色是:算法老是經過迭代器操做容器,所以不能直接向/從容器添加、刪除元素(但能夠讀、寫),沒法改變容器大小。所以,對於 copy 算法,要求目標序列至少要包含與源序列同樣多的元素。而此程序中,vec 進行缺省初始化,它是空的,copy 沒法進行。如需改變容器大小,須要使用一類特殊的稱爲插入器的迭代器。咱們能夠將第三個參數改成 back_inserter(vec) ,經過它,copy 算法便可將 lst 中元素的拷貝插入到 vec 的末尾。

copy(lst.begin(), lst.end(), back_inserter(vec));

(b)這段程序仍然是錯誤的。粗看起來,reserve 爲 vec 分配了至少能容納 10 個 int 的內存空間,調用 fill_n 時,vec 已有足夠空間。但泛型算法對於容器的要求並非有足夠的空間。而是足夠的元素。此時 vec 仍然爲空,沒有任何元素。而算法又不具有向容器添加元素的能力,所以 fill_n 仍然失敗。這裏,咱們仍是須要使用 back_inserter 來讓 fill_n 有能力向 vec 添加元素。

copy(back_inserter(vec), 10, 0);

10.8

【出題思路】

深刻理解泛型算法的這一特色。

【解答】

嚴格來講,標準庫算法根本不知道有 「容器」 這個東西。它們只接受迭代器參數,運行於這些迭代器之上,經過這些迭代器來訪問元素。

所以,當你傳遞給算法普通迭代器時,這些迭代器只能順序或隨機訪問容器中的元素,形成的效果就是算法只能讀取元素、改變元素的值、移動元素,但沒法添加或刪除元素。

但當咱們傳遞給算法插入器,例如 back_inserter 時,因爲這類迭代器能調用下層容器的操做來向容器插入元素,形成的算法執行的效果就是向容器中添加了元素。

所以,關鍵要理解:標準庫算法歷來不直接操做容器,它們只操做迭代器,從而間接訪問容器。能不能插入和刪除元素,不在於算法,而在於傳遞給它們的迭代器是否具備這樣的能力。

10.9

【出題思路】

本題練習重排元素的算法。

【解答】

unique 「消除」 重複值的方式並非刪除值重複的元素,執行 unique 後,容器的元素數目並未改變。不重複元素以後的位置上的元素的值是未定義的。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

void elimDups(std::vector<std::string> &words) {
    for (std::string &s : words)    // 無須拷貝字符串
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    std::sort(words.begin(), words.end());
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    std::vector<std::string>::iterator end_unique = std::unique(words.begin(), words.end());
    for (auto iter = words.begin(); iter != words.end(); ++iter)
        std::cout << *iter << " ";
    std::cout << words.size() << std::endl;

    words.erase(end_unique, words.end());
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    return;
}

int main() {
    std::vector<std::string> svec = {"cc", "bbbb", "zz", "aa", "aa", "ccc"};
    elimDups(svec);

    return 0;
}
// 運行結果
cc bbbb zz aa aa ccc 6
aa aa bbbb cc ccc zz 6
aa bbbb cc ccc zz  6
aa bbbb cc ccc zz 5

Process finished with exit code 0

10.10

【出題思路】

讓讀者對語言的設計有本身的思考。

【解答】

泛型算法的一大優勢是 「泛型」,也就是一個算法可用於多種不一樣的數據類型,算法與所操做的數據結構分離。這對編程效率的提高是很是巨大的。

要作到算法與數據結構分離,重要的技術手段就是使用迭代器做爲二者的橋樑。算法從不操做具體的容器,從而也就不存在與特定容器綁定,不適用於其餘容器的問題。算法只操做迭代器,由迭代器真正實現對容器的訪問。不一樣容器實現本身特定的迭代器(但不一樣迭代器是相容的),算法操做不一樣迭代器就實現了對不一樣容器的訪問。

所以,並非算法應該改變或不改變容器的問題。爲了實現與數據結構的分離,爲了實現通用性,算法根本就不知道容器的存在。算法訪問數據的惟一通道是迭代器。是否改變容器大小,徹底是迭代器的選擇和責任。當咱們向 fill_n 傳遞 back_inserter 時,雖然最終效果是向容器添加了新元素,但對 fill_n 來講,根本不知道這回事兒。它仍然像往常同樣(經過迭代器)向元素賦予新值,只不過此次是經過 back_inserter 來賦值,而 back_inserter 選擇將新值添加到了容器而已。

10.11

【出題思路】

練習向算法傳遞謂詞來定製操做,理解穩定排序的概念。

【解答】

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

bool isShorter(const std::string &s1, const std::string &s2) {
    return s1.size() < s2.size();
}

void elimDups(std::vector<std::string> &words) {
    // 輸出本來單詞的順序,單詞間使用空格分割
    for (std::string &s : words)    // 無須拷貝字符串
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    // 按字典序排序 words,以便查找重複單詞
    std::sort(words.begin(), words.end());
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    std::vector<std::string>::iterator end_unique =
            std::unique(words.begin(), words.end());
    for (auto iter = words.begin(); iter != words.end(); ++iter)
        std::cout << *iter << " ";
    std::cout << words.size() << std::endl;

    // 刪除重複單詞
    words.erase(end_unique, words.end());
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;

    // 按長度排序,長度相同的單詞維持字典序
    std::stable_sort(words.begin(), words.end(), isShorter);
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    // 將 svec 中的單詞按字典序排序,刪除重複單詞
    // 而後再按長度排序,長度相同的單詞維持字典序
    elimDups(svec);

    return 0;
}
// 運行結果
the quick red fox jumps over the slow red turtle 10
fox jumps over quick red red slow the the turtle 10
fox jumps over quick red slow the turtle the  10
fox jumps over quick red slow the turtle 8
fox red the over slow jumps quick turtle 8

Process finished with exit code 0

10.12

【出題思路】

練習定義和使用謂詞。

【解答】

咱們將 compareIsbn 定義爲一個二元爲此,接受兩個 Sales_data 對象,經過 isbn 成員函數獲取 ISBN 編號,若前者小於後者返回真,不然返回假。

inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.isbn() < rhs.isbn();
}

主程序中,將 compareIsbn 做爲第三個參數傳遞給 sort,便可實現銷售數據按 ISBN 號排序。

程序實現以下所示:

Sales_data.h

#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H

// Definition of Sales_data class and related functions goes here
#include <iostream>

// 頭文件不該包含 using 聲明
// using namespace std;

class Sales_data {
public:
    // 4 個構造函數
    Sales_data() = default;
    Sales_data(const std::string &book) : bookNo(book) {}
    Sales_data(const std::string &book, const unsigned num,
               const double sellp, const double salep);
    Sales_data(std::istream &is);

    std::string isbn() const { return bookNo; }
    std::istream &read(std::istream &is, Sales_data &item);

private:                            // 定義私有數據成員
    std::string bookNo;             // 書籍編號,隱士初始化爲空串
    unsigned units_sold = 0;        // 銷售量,顯示初始化爲 0
    double sellingprice = 0.0;      // 原始售價,顯示初始化爲 0.0
    double saleprice = 0.0;         // 實售價格,顯示初始化爲 0.0
    double discount = 0.0;          // 折扣,顯示初始化爲 0.0
};

inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.isbn() < rhs.isbn();
}

Sales_data::Sales_data(const std::string &book, const unsigned num,
                       const double sellp, const double salep) {
    bookNo = book;
    units_sold = num;
    sellingprice = sellp;
    saleprice = salep;
    if (sellingprice != 0)
        discount = saleprice / sellingprice;    // 計算實際折扣
}

Sales_data::Sales_data(std::istream &is) {
    read(is, *this);
}

std::istream& Sales_data::read(std::istream &is, Sales_data &item) {
    is >> item.bookNo >> item.units_sold >> item.sellingprice
       >> item.saleprice;
    return is;
}

#endif //TEST_SALES_DATA_H

main.cpp

#include <vector>
#include "Sales_data.h"

int main() {
    Sales_data d1("0-201-78345-X");
    Sales_data d2("0-201-78345-X");
    Sales_data d3("0-201-78345-B", 100, 128, 109);
    Sales_data d4("0-201-78345-A");
    Sales_data d5("0-201-78345-C");
    Sales_data d6("0-201-78345-A");
    std::vector<Sales_data> v = {d1, d2, d3, d4, d5, d6};

    std::sort(v.begin(), v.end(), compareIsbn);

    for (const auto &element : v)
        std::cout << element.isbn() << std::endl;

    return 0;
}
// 運行結果
0-201-78345-A
0-201-78345-A
0-201-78345-B
0-201-78345-C
0-201-78345-X
0-201-78345-X

Process finished with exit code 0

10.13

【出題思路】

練習定義和使用一元謂詞。

【解答】

本題要求謂詞判斷一個 string 對象的長度是否大於等於 5,而不是比較兩個 string 對象,所以它應是一個一元謂詞。其餘與上一題基本相似。但須要注意,咱們應該保存 partition 返回的迭代器 pivot,打印範圍 [svec.begin(), pivot) 中的字符串。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

bool predicate(const std::string &s) {
    return s.size() >= 5;
}

int main() {
    std::vector<std::string> svec = {"cccccc", "iii", "zz", "bbbbb", "c"};
    for (auto &s : svec)        // 無須拷貝字符串
        std::cout << s << " ";
    std::cout << std::endl;

    std::vector<std::string>::iterator pivot = std::partition(svec.begin(),
            svec.end(), predicate);

    for (auto &s : svec)
        std::cout << s << " ";
    std::cout << std::endl;
    for (std::vector<std::string>::iterator iter = svec.begin();
    iter != pivot; ++iter) {
        std::cout << *iter << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 運行結果
cccccc iii zz bbbbb c 
cccccc bbbbb zz iii c 
cccccc bbbbb 

Process finished with exit code 0

10.14

【出題思路】

練習定義和使用簡單的 lambda。

【解答】

因爲此 lambda 無須使用所在函數中定義的局部變量,因此捕獲列表爲空。參數列表爲兩個整型。返回類型由函數體惟一的語句 —— 返回語句推斷便可。

#include <iostream>

using std::cout;
using std::endl;

int main() {
    auto sum = [] (int a, int b) { return a + b; };
    cout << sum(1, 2) << endl;
}
// 運行結果
3

Process finished with exit code 0

10.15

【出題思路】

練習定義和使用簡單的 lambda。

【解答】

因爲須要計算所在函數的局部 int 和本身的 int 參數的和,該 lambda 需捕獲所在函數的局部 int。參數列表爲一個整型(int b)。返回類型仍由返回語句推斷。

#include <iostream>

using std::cout;
using std::endl;

void add(int a) {
    // lambda 捕獲它所在 add 函數中的變量 a
    auto sum = [a] (int b) { return a + b; };
    cout << sum(4) << endl;     // b = 4

}
int main() {
    add(1);     // 調用函數 add,形參 a = 1
    add(2);     // 調用函數 add,形參 a = 2

    return 0;
}
// 運行結果
5
6

Process finished with exit code 0

下邊咱們再寫一個 10.14 和 10.15 練習的另外一個例子(可能更有助理解 lambda):

#include <iostream>

using std::cout;
using std::endl;

int main() {
    int i = 2;
    
    auto add1 = [] (int lhs, int rhs) { return lhs + rhs; };
    // lambda 捕獲它所在 main 函數中的變量 i
    auto add2 = [i] (int num) { return i + num; };


    cout << "add1(1, 2): " << add1(1, 2) << endl;
    cout << "add2(3): " << add2(3) << endl;

    return 0;
}
// 運行結果
add1(1, 2): 3
add2(3): 5

Process finished with exit code 0

捕獲列表只用於局部非 static 變量。lambda 能夠直接使用局部 static 變量和在它所在函數以外聲明的名字。

10.16

【出題思路】

繼續練習 lambda。

【解答】

本題與練習 10.11 不一樣的地方是,使用了 lambda 而不是謂詞。並且函數在功能上也稍加擴展。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word, const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    // 按字典序排序 words,以便查找重複單詞
    sort(words.begin(), words.end());
    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 刪除重複單詞
    words.erase(end_unique, words.end());
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 將 words 按字典序排序,刪除重複單詞
    elimDups(words);
    // 按長度排序,長度相同的單詞維持字典序
    stable_sort(words.begin(), words.end(),
            [] (const string &a, const string &b) {
        return a.size() < b.size();
    }
    );
    // 獲取一個迭代器,指向第一個知足 size() >= sz 的元素
    auto wc = find_if(words.begin(), words.end(),
            [sz] (const string &a) {
        return a.size() >= sz;
    }
    );
    // 計算知足 size() >= sz 的元素的數目
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印長度大於等於給定值的單詞,每一個單詞後面接一個空格
    for_each(wc, words.end(),
            [] (const string &s) {
        cout << s << " ";
    }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    // 按字典序打印 svec 中長度不小於 4 的單詞
    biggies(svec, 4);

    return 0;
}
// 運行結果
5 words of length 4 or longer
over slow jumps quick turtle 

Process finished with exit code 0

10.17

【出題思路】

繼續練習 lambda,體會其與謂詞的區別。

【解答】

此 lambda 比較兩個給定的 Sales_data 對象,所以捕獲列表爲空;有兩個 Sales_data 對象引用的參數;函數體部分則與 compareIsbn 相同。

Sales_data.h 頭文件與 10.12 徹底一致。主函數所在文件 main.cpp 代碼改動以下(程序輸出結果與 10.12 同樣):

#include <vector>
#include "Sales_data.h"

//inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
//    return lhs.isbn() < rhs.isbn();
//}

int main() {
    Sales_data d1("978-7-121-15535-3");
    Sales_data d2("978-7-121-15535-212");
    Sales_data d3("978-7-121-15535-2", 100, 128, 109);
    Sales_data d4("978-7-121-15535-1");
    Sales_data d5("978-7-121-15535-210");
    Sales_data d6("978-7-121-15535-12");
    std::vector<Sales_data> v = {d1, d2, d3, d4, d5, d6};

    // std::sort(v.begin(), v.end(), compareIsbn);
    std::sort(v.begin(), v.end(),
            [] (const Sales_data &lhs, const Sales_data &rhs) {
        return lhs.isbn() < rhs.isbn();
    }
    );

    for (const auto &element : v)
        std::cout << element.isbn() << std::endl;

    return 0;
}

10.18

【出題思路】

理解 find_if 和 partition 的不一樣。

【解答】

對於本題而言,若使用 find_if,要求序列已按字符串長度遞增順序排列好序。find_if 返回第一個長度 >= sz 的字符串的位置 wc,則全部知足長度 >= sz 的字符串位於範圍 [wc, end) 之間。

而 partition 不要求序列已排序,它對全部字符串檢查長度是否 >= sz,將知足條件的字符串移動到序列前端,不知足條件的字符串移動到知足條件的字符串以後,返回知足條件的範圍的尾後迭代器。所以知足條件的字符串位於範圍 [begin, wc) 之間。

所以,在 partition 以前再也不須要 stable_sort。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    // 按字典序排序 words,以便查找重複單詞
    sort(words.begin(), words.end());
    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 刪除重複單詞
    words.erase(end_unique, words.end());
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 將 words 按字典序排序,刪除重複單詞
    elimDups(words);
    // 獲取一個迭代器,指向最後一個知足 size() >= sz 的元素以後的位置
    auto wc = partition(words.begin(), words.end(),
                        [sz] (const string &a) {
                            return a.size() >= sz;
                        }
    );
    // 計算知足 size() >= sz 的元素的數目
    auto count = wc - words.begin();
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印長度大於等於給定值的單詞,每一個單詞後面接一個空格
    for_each(words.begin(), wc,
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    // 按字典序打印 svec 中長度不小於 4 的單詞
    biggies(svec, 4);

    return 0;
}
// 運行結果
5 words of length 4 or longer
turtle jumps over quick slow 

Process finished with exit code 0

下面放一個本身調試的時候,打印程序中間執行過程當中,容器中單詞的變化狀態。相比上邊的程序,只是增長了打印語句而已(純粹是爲了觀察程序執行狀態)。程序以下所示:

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

void output_words(vector<string> &words) {
    // 打印容器中的內容,方便觀察程序執行狀態
    for (auto &s : words)
        std::cout << s << " ";
    cout << words.size() << endl;
}

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    output_words(words);

    // 按字典序排序 words,以便查找重複單詞
    sort(words.begin(), words.end());
    output_words(words);

    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    output_words(words);

    // 刪除重複單詞
    words.erase(end_unique, words.end());
    output_words(words);
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 將 words 按字典序排序,刪除重複單詞
    elimDups(words);

    // 獲取一個迭代器,指向最後一個知足 size() >= sz 的元素以後的位置
    auto wc = partition(words.begin(), words.end(),
                        [sz] (const string &a) {
                            return a.size() >= sz;
                        }
    );
    output_words(words);
    // 計算知足 size() >= sz 的元素的數目
    auto count = wc - words.begin();
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印長度大於等於給定值的單詞,每一個單詞後面接一個空格
    for_each(words.begin(), wc,
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    biggies(svec, 4);
    return 0;
}
// 運行結果
the quick red fox jumps over the slow red turtle 10
fox jumps over quick red red slow the the turtle 10
fox jumps over quick red slow the turtle the  10
fox jumps over quick red slow the turtle 8
turtle jumps over quick slow red the fox 8
5 words of length 4 or longer
turtle jumps over quick slow 

Process finished with exit code 0

10.19

【出題思路】

理解 stable_partition 和 partition 的不一樣。

【解答】

將上一題程序中的 partition 換爲 stable_partition 便可。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

void output_words(vector<string> &words) {
    // 打印容器中的內容,方便觀察程序執行狀態
    for (auto &s : words)
        std::cout << s << " ";
    cout << words.size() << endl;
}

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    output_words(words);

    // 按字典序排序 words,以便查找重複單詞
    sort(words.begin(), words.end());
    output_words(words);

    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    output_words(words);

    // 刪除重複單詞
    words.erase(end_unique, words.end());
    output_words(words);
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 將 words 按字典序排序,刪除重複單詞
    elimDups(words);

    // 獲取一個迭代器,指向最後一個知足 size() >= sz 的元素以後的位置
    auto wc = stable_partition(words.begin(), words.end(),
                               [sz] (const string &a) {
                                   return a.size() >= sz;
                               }
    );
    output_words(words);
    // 計算知足 size() >= sz 的元素的數目
    auto count = wc - words.begin();
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印長度大於等於給定值的單詞,每一個單詞後面接一個空格
    for_each(words.begin(), wc,
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    biggies(svec, 4);
    return 0;
}
// 運行結果
// stable_partition 算法執行前、後容器中內容分別對應 四、5 行
the quick red fox jumps over the slow red turtle 10
fox jumps over quick red red slow the the turtle 10
fox jumps over quick red slow the turtle the  10
fox jumps over quick red slow the turtle 8
jumps over quick slow turtle fox red the 8
5 words of length 4 or longer
jumps over quick slow turtle 

Process finished with exit code 0

注:

上一題 partition 算法執行前、後容器中內容爲:

fox jumps over quick red slow the turtle 
turtle jumps over quick slow red the fox

本題 stable_partition 算法執行前、後容器中內容爲:

fox jumps over quick red slow the turtle 
jumps over quick slow turtle fox red the

10.20

【出題思路】

練習 count_if 算法的使用。

【解答】

若只統計容器中知足必定條件的元素的個數,而不打印或者得到這些元素的話。直接使用 count_if 便可,無須進行 unique、sort 等操做。

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

void output_words(vector<string> &words) {
    // 打印容器中的內容,方便觀察程序執行狀態
    for (auto &s : words)
        std::cout << s << " ";
    cout << words.size() << endl;
}

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    output_words(words);

    // 統計知足 size() > sz 的元素的個數
    auto count = count_if(words.begin(), words.end(),
                          [sz] (const string &a) {
                              return a.size() > sz;
                          }
    );
    output_words(words);
    cout << count << " " << make_plural(count, "word", "s")
         << " of length longer than " << sz << endl;
}

int main() {
    std::vector<std::string> svec = {"determined", "quick", "nuclear", "fox",
                                     "negotiations", "Things", "accelerating"};
    biggies(svec, 6);
    return 0;
}
// 運行結果
determined quick nuclear fox negotiations Things accelerating 7
determined quick nuclear fox negotiations Things accelerating 7
4 words of length longer than 6

Process finished with exit code 0

10.21

【出題思路】

練習 lambda 改變捕獲變量值的方法。

【解答】

若 lambda 須要改變捕獲的局部變量的值,須要在參數列表以後、函數體以前使用 mutable 關鍵字。對於本題,因爲 lambda 有兩個返回語句(i 大於 0 時返回 false,等於 0 時返回 true),還須要顯示指定 lambda 的返回類型 —— 使用尾置返回類型,在參數列表後使用 -> bool 。注意,正確的順序是 mutable -> bool 。因爲 i 的初始值爲 5,程序執行後會打印 5 個 0 和 1 個 1.

#include <iostream>
#include <algorithm>

using namespace std;

void mutable_lambda() {
    int i = 5;
    auto f = [i] () mutable -> bool {
        if (i > 0) {
            --i;
            return false;
        } else
            return true;
    };

    for (int j = 0; j < 6; ++j)
        cout << f() << " ";
    cout << endl;
}

int main() {
    mutable_lambda();

    return 0;
}
// 運行結果
0 0 0 0 0 1 

Process finished with exit code 0

10.22

【出題思路】

本題練習用函數代替 lambda 的方法。

【解答】

當 lambda 不捕獲局部變量時,用函數代替它是很容易的。但當 lambda 捕獲局部變量時就不那麼簡單了。由於在這種狀況下,一般是算法要求可調用對象(lambda)接受的參數個數少於函數所需的參數個數,lambda 經過捕獲的局部變量來彌補這個差距,而普通函數是沒法辦到的。

解決方法是使用標準庫中的 bind 函數在實際工做函數外作一層 「包裝」 —— 它接受一個可調用對象 A,即實際的工做函數,返回一個新的可調用對象 B,供算法使用。A 後面是一個參數列表,即傳遞給它的參數列表。其中有一些名字形如 _n 的參數,表示程序調用 B 時傳遞給它的第 n 個參數。也就是說,算法調用 B 時傳遞較少的參數(_n),B 再補充其餘一些值,造成更長的參數列表,從而解決算法要求的參數個數比實際工做函數所需參數個數少的問題。

注意:_n 定義在命名空間 std::placeholders 中。

#include <vector>
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>


using std::string;
using std::vector;
using std::cout;
using std::endl;
using std::placeholders::_1;

void output_words(vector<string> &words) {
    // 打印容器中的內容,方便觀察程序執行狀態
    for (auto &s : words)
        std::cout << s << " ";
    cout << words.size() << endl;
}

bool check_size(const string &s, string::size_type sz) {
    return s.size() <= sz;
}

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    output_words(words);

    // 統計知足 size() <= sz 的元素的個數
    auto count = count_if(words.begin(), words.end(), bind(check_size, _1, 6));
    output_words(words);
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or smaller" << endl;
}

int main() {
    std::vector<std::string> svec = {"determined", "quick", "nuclear", "fox",
                                     "negotiations", "Things", "accelerating"};
    biggies(svec, 6);
    return 0;
}
// 運行結果
determined quick nuclear fox negotiations Things accelerating 7
determined quick nuclear fox negotiations Things accelerating 7
3 words of length 6 or smaller

Process finished with exit code 0

10.23

【出題思路】

理解 bind 函數的使用。

【解答】

咱們拿上題來講明:

bool check_size(const string &s, string::size_type sz);
auto f = bind(check_size, _1, 6)

注:A 對應 check_size;B 對應 f

bind 是可變參數的。它接受的第一個參數是一個可調用對象,即實際工做函數 A,返回供算法使用的新的可調用對象 B。若 A 接受 x 個參數,則 bind 的參數個數應該是 x+1,即除了 A 外,其餘參數應一一對應 A 所接受的參數。這些參數中有一部分來自於 B(_n) ,另一些來自於所處函數的局部變量。

10.24

【出題思路】

本題繼續練習 bind 的使用。

【解答】

解題思路與練習 10.22 相似。

對於 bind 返回的可調用對象,其惟一參數是 vector 中的元素,所以 _1 應該是 bind 的第三個參數,即 check_size 的第二個參數,而給定 string 應做爲 bind 的第二個即 check_size 的第一個參數。

#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>

using namespace std;
using std::placeholders::_1;

bool check_size(const string &s, int sz) {
    return s.size() < sz;
}

void biggies(vector<int> &ivec, const string &s) {
    // 查找第一個大於等於 s 長度的數值
    auto p = find_if(ivec.begin(), ivec.end(),
            bind(check_size, s, _1));

    // 打印結果
    cout << "第" << p - ivec.begin() + 1 << "個數" << *p
         << "大於" << s << "的長度" << endl;
}

int main() {
    vector<int> iv = {3, 2, 4, 5, 7};

    biggies(iv, "C++");
    biggies(iv, "Primer");

    return 0;
}
// 運行結果
第3個數4大於C++的長度
第5個數7大於Primer的長度

Process finished with exit code 0

10.25

【出題思路】

本題繼續練習 bind 的使用。

【解答】

把習題 10.18 稍加改動便可。改動處:

  1. 添加 #include <functional>
  2. 添加 using std::placeholders::_1;
  3. 添加 check_size 函數
  4. lambda 替換爲 bind

程序以下所示:

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <functional>

using std::string;
using std::vector;
using std::cout;
using std::endl;
using std::placeholders::_1;

bool check_size(const string &s, string::size_type sz) {
    return s.size() >= sz;
}

// 若是 ctr 的值大於 1,返回 word 的複數形式
string make_plural(size_t ctr, const string &word,
                   const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    // 按字典序排序 words,以便查找重複單詞
    sort(words.begin(), words.end());
    // unique 重排輸入範圍,使得每一個單詞只出現一次
    // 排列在範圍的前部,返回指向不重複區域以後一個位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 刪除重複單詞
    words.erase(end_unique, words.end());
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 將 words 按字典序排序,刪除重複單詞
    elimDups(words);
    // 獲取一個迭代器,指向最後一個知足 size() >= sz 的元素以後的位置
    auto wc = partition(words.begin(), words.end(),
            bind(check_size, _1, sz));
    // 計算知足 size() >= sz 的元素的數目
    auto count = wc - words.begin();
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印長度大於等於給定值的單詞,每一個單詞後面接一個空格
    for_each(words.begin(), wc,
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    // 按字典序打印 svec 中長度不小於 4 的單詞
    biggies(svec, 4);

    return 0;
}
// 運行結果
5 words of length 4 or longer
turtle jumps over quick slow 

Process finished with exit code 0

10.26

【出題思路】

理解插入迭代器的概念,以及幾種插入迭代器的不一樣。

【解答】

在書中前文,咱們已經學習了一種插入迭代器 back_inserter(書中 \(P_{341}\),查看書後邊索引很容易找到相關知識點)。插入迭代器又稱插入器,本質上是一種迭代器適配器。如前所述,標準庫算法爲了保證通用性,並不直接操做容器,而是經過迭代器來訪問容器元素。所以,算法不具有直接向容器插入元素的能力。而插入器正是幫助算法實現向容器插入元素的機制。

除了 back_inserter,標準庫還提供了另外兩種插入器:front_inserter 和 inserter。三者的差別在於如何向容器插入元素:back_inserter 調用 push_back;front_inserter 調用 push_front;inserter 則調用 insert。顯然,這也決定了它們插入元素位置的不一樣。back_inserter 老是插入到容器尾元素以後;front_inserter 老是插入到容器首元素以前;而 inserter 則是插入到給定位置(做爲 inserter 的第二個參數傳遞給它)以前。所以,須要注意這些特色帶來的元素插入效果的差別。例如,使用 front_inserter 向容器插入一些元素,元素最終在容器中的順序與插入順序相反,但 back_inserter 和 inserter 則不會有這個問題。

10.27

【出題思路】

本題練習 unique_copy 的使用,以及使用插入迭代器幫助算法(unique_copy)實現向容器插入新元素。

【解答】

本題要求將 vector 中不重複元素按原有順序拷貝到空的 list 中,所以使用 back_inserter 便可。須要注意的是,與 unique 同樣,unique_copy 也要求在源容器中重複元素是相鄰存放的。所以,若 vector 重複元素未相鄰存放,unique_copy 就會失敗。穩妥的方法是先對 vector 排序。

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

using namespace std;

int main() {
    vector<int> ivec{1, 2, 2, 3, 4, 5, 5, 6};
    list<int> ilst;

    unique_copy(ivec.begin(), ivec.end(), back_inserter(ilst));

    for (auto v : ilst)
        cout << v << " ";

    return 0;
}
// 運行結果
1 2 3 4 5 6 
Process finished with exit code 0

【其餘解題思路】

因爲要保持原順序,顯然使用 inserter 也是能夠的,將 back_inserter(ilst) 替換爲 inserter(ilst, ilst.begin()) 便可。

10.28

【出題思路】

進一步理解三種插入迭代器的差別,並練習使用它們。

【解答】

若三個目的容器均爲空,則顯然 inserter 和 back_inserter 的輸出結果是:"1 2 3 4 5 6 7 8 9",而 front_inserter 的結果是 「9 8 7 6 5 4 3 2 1」。但若是目的容器不空,則 inserter 的結果取決於傳遞給它的第二個參數(一個迭代器)指向什麼位置。

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

using namespace std;

int main() {
    vector<int> ivec{1, 2, 3, 4, 5, 6, 7, 8, 9};
    list<int> ilst1, ilst2, ilst3;

    unique_copy(ivec.begin(), ivec.end(), inserter(ilst1, ilst1.begin()));
    for (auto v : ilst1)
        cout << v << " ";
    cout << endl;

    unique_copy(ivec.begin(), ivec.end(), back_inserter(ilst2));
    for (auto v : ilst2)
        cout << v << " ";
    cout << endl;

    unique_copy(ivec.begin(), ivec.end(), front_inserter(ilst3));
    for (auto v : ilst3)
        cout << v << " ";
    cout << endl;

    return 0;
}
// 運行結果
1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9 
9 8 7 6 5 4 3 2 1 

Process finished with exit code 0

10.29

【出題思路】

本題練習流迭代器的簡單使用。

【解答】

雖然流不是容器,但標準庫提供了經過迭代器訪問流的方法。聲明一個流迭代器時,須要指出所綁定的流。對於本題,首先打開一個文本文件,將此文件的流做爲參數提供給流迭代器的構造函數便可。當默認構造流迭代器時,獲得一個尾後迭代器,對應文件結束。

#include <iostream>
#include <vector>
#include <fstream>
#include <iterator>

using namespace std;

int main(int argc, char *argv[]) {
    if (argc != 2) {
        cerr << "請給出文件名" << endl;
        return -1;
    }
    ifstream in(argv[1]);
    if (!in) {
        cerr << "沒法打開輸入文件" << endl;
        return -1;
    }

    // 建立流迭代器從文件讀入字符串
    istream_iterator<string> in_iter(in);
    // 尾後迭代器
    istream_iterator<string> eof;
    vector<string> words;
    while (in_iter != eof)
        words.push_back(*in_iter++);    // 存入 vector 並遞增迭代器

    for (auto word : words)
        cout << word << " ";
    cout << endl;

    return 0;
}

運行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 爲 ../data

注:../data 即爲文件 data 的文件名及其相對路徑(是相對於可執行程序所在目錄的相對路徑)。

並在文件 data 中寫入以下內容:

It’s hard to recall a newly elected freshman representative to
Congress who has made a bigger impact than Alexandria Ocasio-Cortez.

運行程序,程序執行結果以下所示:

It’s hard to recall a newly elected freshman representative to Congress who has made a bigger impact than Alexandria Ocasio-Cortez. 

Process finished with exit code 0

10.30

【出題思路】

本題練習輸入流迭代器的使用。

【解答】

使用流迭代器從標準輸入讀取整數序列的程序與上一題相似,建立流迭代器時指出是 int,並用 cin 代替文件流對象便可。

用 copy 將整數寫到標準輸出,須要聲明一個輸出流迭代器,做爲第三個參數傳遞給 copy。將 cout 傳遞給輸出流迭代器的構造函數,copy 便可將整數寫到標準輸出。將 " " 做爲第二個參數傳遞給輸出流迭代器的構造函數,表示在每一個輸出以後寫一個空格,從而將整數分隔開來輸出。

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using namespace std;

int main() {
    // 建立流迭代器從標準輸入讀入整數
    istream_iterator<int> in_iter(cin);
    // 尾後迭代器
    istream_iterator<int> eof;
    vector<int> ivec;
    while (in_iter != eof)
        ivec.push_back(*in_iter++);     // 存入 vector 並遞增迭代器

    sort(ivec.begin(), ivec.end());

    ostream_iterator<int> out_iter(cout, " ");
    copy(ivec.begin(), ivec.end(), out_iter);

    return 0;
}
// 運行結果
// 第一行爲我在控制檯的輸入
3 2 6 4 8 6 5 9 0 1 7
^D
0 1 2 3 4 5 6 6 7 8 9 
Process finished with exit code 0

注:可能你會遇到按組合鍵 command + d 無論用?!請參考下邊帖子:

clion控制檯如何終止輸入,而不終止程序

爲何若是我從鍵盤輸入EOF Clion不要在Run窗口上打印程序的輸出?

若是還很差使,在按組合鍵 command + d 前,對輸入的內容換行(Enter)。即,輸入完測試數據 -> 按 Enter -> 按組合鍵 command + d

10.31

【出題思路】

繼續練習輸出流迭代器的使用,並複習 unique_copy 算法的使用。

【解答】

用 unique_copy 替代上題的 copy 便可。

#include <iostream>
#include <vector>
#include <iterator>

using namespace std;

int main() {
    // 建立流迭代器從標準輸入讀入整數
    istream_iterator<int> in_iter(cin);
    // 尾後迭代器
    istream_iterator<int> eof;
    vector<int> ivec;
    while (in_iter != eof)
        ivec.push_back(*in_iter++);     // 存入 vector 並遞增迭代器

    sort(ivec.begin(), ivec.end());

    ostream_iterator<int> out_iter(cout, " ");
    unique_copy(ivec.begin(), ivec.end(), out_iter);

    return 0;
}
// 運行結果
3 2 6 4 8 6 5 9 0 1 7
^D
0 1 2 3 4 5 6 7 8 9 
Process finished with exit code 0

10.32

【出題思路】

繼續練習流迭代器的使用,並複習算法的使用。

【解答】

  1. 讀取交易記錄存入 vector 的代碼與前兩題相似。

  2. 修改 練習 1.20 的 Sales_item.h 頭文件中的函數 compareIsbn,將 return lhs.isbn() == rhs.isbn(); 改成 return lhs.isbn() < rhs.isbn();

  3. 將 compareIsbn 做爲第三個參數傳遞給 sort,便可實現將交易記錄按 ISBN 排序。

  4. 本題要求將 ISBN 相同的交易記錄累加,而 find 算法查找與給定值相同的容器元素,須要逐個查找須要累加的元素,顯然性能太差。咱們使用 find_if ,並構造以下 lambda:

    [item] (const Sales_item &item1) { return item1.isbn() != item.isbn(); }

    做爲第三個參數傳遞給 find_if,從而查找到第一個 ISBN 與當前交易 l 記錄的不一樣的記錄 r(ISBN 相同的範圍的尾後位置)。

  5. 接下來調用 accumulate 便可實現範圍 [l, r) 間交易記錄的累加。

  6. 最後將 l 移動到 r,繼續循環,計算下一段交易記錄的查找和累加。

Sales_item.h

/*
 * This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 * 
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 * 
 * 
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 * 
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address: 
 * 
 *     Pearson Education, Inc.
 *     Rights and Permissions Department
 *     One Lake Street
 *     Upper Saddle River, NJ  07458
 *     Fax: (201) 236-3290
*/

/* This file defines the Sales_item class used in chapter 1.
 * The code used in this file will be explained in
 * Chapter 7 (Classes) and Chapter 14 (Overloaded Operators)
 * Readers shouldn't try to understand the code in this file
 * until they have read those chapters.
*/

#ifndef SALESITEM_H
// we're here only if SALESITEM_H has not yet been defined 
#define SALESITEM_H

// Definition of Sales_item class and related functions goes here
#include <iostream>
#include <string>

class Sales_item {
// these declarations are explained section 7.2.1, p. 270 
// and in chapter 14, pages 557, 558, 561
    friend std::istream& operator>>(std::istream&, Sales_item&);
    friend std::ostream& operator<<(std::ostream&, const Sales_item&);
    friend bool operator<(const Sales_item&, const Sales_item&);
    friend bool
    operator==(const Sales_item&, const Sales_item&);
public:
    // constructors are explained in section 7.1.4, pages 262 - 265
    // default constructor needed to initialize members of built-in type
    Sales_item(): units_sold(0), revenue(0.0) { }
    Sales_item(const std::string &book):
            bookNo(book), units_sold(0), revenue(0.0) { }
    Sales_item(std::istream &is) { is >> *this; }
public:
    // operations on Sales_item objects
    // member binary operator: left-hand operand bound to implicit this pointer
    Sales_item& operator+=(const Sales_item&);

    // operations on Sales_item objects
    std::string isbn() const { return bookNo; }
    double avg_price() const;
// private members as before
private:
    std::string bookNo;      // implicitly initialized to the empty string
    unsigned units_sold;
    double revenue;
};

// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{ return lhs.isbn() < rhs.isbn(); }

// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);

inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    // must be made a friend of Sales_item
    return lhs.units_sold == rhs.units_sold &&
           lhs.revenue == rhs.revenue &&
           lhs.isbn() == rhs.isbn();
}

inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs); // != defined in terms of operator==
}

// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

// assumes that both objects refer to the same ISBN
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
    Sales_item ret(lhs);  // copy (|lhs|) into a local object that we'll return
    ret += rhs;           // add in the contents of (|rhs|) 
    return ret;           // return (|ret|) by value
}

std::istream&
operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price;
    // check that the inputs succeeded
    if (in)
        s.revenue = s.units_sold * price;
    else
        s = Sales_item();  // input failed: reset object to default state
    return in;
}

std::ostream&
operator<<(std::ostream& out, const Sales_item& s)
{
    out << s.isbn() << " " << s.units_sold << " "
        << s.revenue << " " << s.avg_price();
    return out;
}

double Sales_item::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
}
#endif

main.cpp

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>    // sort
#include <numeric>      // accumulate
#include "Sales_item.h"

int main() {
    std::vector<Sales_item> v;
    std::istream_iterator<Sales_item> in_iter(std::cin);
    std::istream_iterator<Sales_item> eof;

    // 讀入 ISBN 號、售出的冊數以及銷售價格,存入 vector
    while (in_iter != eof)
        v.push_back(*in_iter++);

    if (v.empty()) {
        // 沒有輸入!敬告讀者
        std::cerr << "No data?!" << std::endl;
        return -1;      // 表示失敗
    }

    // 將交易記錄按 ISBN 排序
    sort(v.begin(), v.end(), compareIsbn);

    auto l = v.begin();
    while (l != v.end()) {
        auto item = *l;      // 相同 ISBN 的交易記錄中的第一個
        // 在後續記錄中查找第一個 ISBN 與 item 不一樣者
        auto r = find_if(l + 1, v.end(),
                [item] (const Sales_item &item1) {
            return item1.isbn() != item.isbn();
        });
        // 將範圍 [l, r) 間的交易記錄累加並輸出
        // 輸出格式:ISBN、售出的冊數、總銷售額和平均價格
        std::cout << std::accumulate(l, r, Sales_item(l -> isbn())) << std::endl;
        // l 指向下一段交易記錄中的第一個
        l = r;
    }

    return 0;
}
// 0-201-78345-X 3 20.00
// 0-201-78345-X 2 22.00
// 0-201-78345-B 2 25.00
// 0-201-78345-A 5 24.00
// 0-201-78345-A 7 22.00
// 上邊這幾行爲測試數據
// 運行結果
0-201-78345-X 3 20.00
0-201-78345-X 2 22.00
0-201-78345-B 2 25.00
0-201-78345-A 5 24.00
0-201-78345-A 7 22.00^D
0-201-78345-A 12 274 22.8333
0-201-78345-B 2 50 25
0-201-78345-X 5 104 20.8

Process finished with exit code 0

10.33

【出題思路】

本題經過一個稍大的例子鞏固輸入和輸出流迭代器的使用。

【解答】

程序從命令行接受三個文件名參數,所以程序首先判斷參數數目是否爲 4(包括程序名)。

而後依次打開輸入文件和兩個輸出文件,再用這三個流初始化一個輸入流迭代器和兩個輸出流迭代器。注意,第一個流迭代器輸出以空格間隔的奇數,將 " " 做爲第二個參數傳遞給構造函數;第二個流迭代器輸出以換行間隔的偶數,將 "\n" 做爲第二個參數傳遞給構造函數。

隨後在循環中經過輸入流迭代器讀取文件中的整數,直至到達文件末尾。讀入每一個整數後,判斷它是奇數仍是偶數,分別寫入兩個輸出文件。

#include <iostream>
#include <fstream>
#include <iterator>

using namespace std;

int main(int argc, char *argv[]) {
    if (argc != 4) {
        cout << "用法:10_33.exe in_file "
                "out_file1 out_file2" << endl;
        return -1;
    }

    ifstream in(argv[1]);
    if (!in) {
        cout << "打開輸入文件失敗!" << endl;
        exit(1);
    }

    ofstream out1(argv[2]);
    if (!out1) {
        cout << "打開輸出文件 1 失敗!" << endl;
        exit(1);
    }

    ofstream out2(argv[3]);
    if (!out2) {
        cout << "打開輸出文件 2 失敗!" << endl;
        exit(1);
    }

    // 建立流迭代器從文件讀入整數
    istream_iterator<int> in_iter(in);
    // 尾後迭代器
    istream_iterator<int> eof;
    // 第一個輸出文件以空格間隔整數
    ostream_iterator<int> out_iter1(out1, " ");
    // 第二個輸出文件以換行間隔整數
    ostream_iterator<int> out_iter2(out2, "\n");
    while (in_iter != eof) {
        if (*in_iter & 1)
            *out_iter1++ = *in_iter;        // 奇數寫入第一個輸出文件
        else
            *out_iter2++ = *in_iter;        // 偶數寫入第二個輸出文件
        ++in_iter;
    }
    return 0;
}

運行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 爲 ../in_file ../out_file1 ../out_file2

注:../in_file ../out_file1 ../out_file2 即爲文件 in_file,out_file1 和 out_file2 的文件名及其相對路徑(是相對於可執行程序所在目錄的相對路徑)。

並在文件 in_file 中寫入以下測試數據(out_file1 和 out_file2 爲空文件):

4 5 7 3 2 1 0 9 6 8

運行程序後,輸出文件 out_file1 和 out_file2 內容分別爲:

out_file1 :

5 7 3 1 9

out_file2 :

4
2
0
6
8

10.34

【出題思路】

本題練習反向迭代器的簡單使用。

【解答】

咱們能夠用 (c)rbegin 獲取反向遍歷的起始位置(實際上是容器的末尾元素位置),用 (c)rend 獲取尾後迭代器(首元素以前的位置)。經過這兩個迭代器控制循環,便可實現對容器的反向遍歷。注意,循環中向前移動迭代器仍然用 ++,而非 --。

#include <iostream>
#include <iterator>
#include <vector>

using namespace std;

int main() {
    vector<string> svec{"Welcome", "To", "C++"};

    for (auto r_iter = svec.crbegin(); r_iter != svec.crend(); ++r_iter)
        cout << *r_iter;
    cout << endl;

    return 0;
}
// 運行結果
C++ToWelcome

Process finished with exit code 0

10.35

【出題思路】

體會反向迭代器和普通迭代器的差別。

【解答】

若使用普通迭代器反向遍歷容器,首先經過 (c)end 得到容器的尾後迭代器,循環中遞減該迭代器,直到它與 (c)begin 相等爲止。但須要注意的是,遍歷所用迭代器的初值爲尾後位置,終值爲 (c)begin 以後的位置(首元素以前的位置)。也就是說,在每一個循環步中,它指向的都是咱們要訪問的元素以後的位置。所以,咱們在循環中首先將其遞減,而後經過它訪問容器元素,而在循環語句的第三個表達式中就再也不遞減迭代器了。

顯然,對於反向遍歷容器,使用反向迭代器比普通迭代器更清晰易懂。

#include <iostream>
#include <iterator>
#include <vector>

using namespace std;

int main() {
    vector<string> svec{"Welcome", "To", "C++"};

    for (auto iter = svec.cend(); iter != svec.cbegin(); )
        cout << *(--iter);
    cout << endl;

    return 0;
}
// 運行結果
C++ToWelcome

Process finished with exit code 0

10.36

【出題思路】

練習反向迭代器和算法的結合。

【解答】

藉助反向迭代器,能夠擴展算法的能力。例如,使用普通迭代器,find 能查找給定值在容器中第一次出現的位置。若是要查找最後一次出現的位置,還使用普通迭代器的話,代碼會很複雜。但藉助反向迭代器,find 能夠逆序遍歷容器中的元素,從而 「第一次出現位置」 實際上也就是正常順序的最後一次出現位置了。

注意:

  1. 因爲 list 是鏈表結構,元素不連續存儲,其迭代器不支持算術運算。所以,程序中用一個循環來計數位置編號(count)。
  2. 因爲程序計數的是正向位置編號,所以,須要將 find 找到的反向迭代器 pos 轉換爲普通迭代器(使用 base 成員函數)。但要注意,反向迭代器與普通迭代器的轉換是左閉合區間的轉換,而非精確位置的轉換。pos.base() 指向的並不是最後一個 0,而是它靠近容器尾方向的鄰居。所以,首先將 pos 向容器首方向靠近一個位置(++),而後再調用 base,獲得的就是指向最後一個 0 的普通迭代器了。讀者能夠嘗試對本例畫出相似圖 10.2 所示的迭代器位置關係圖。
#include <iostream>
#include <iterator>
#include <list>
#include <algorithm>

using namespace std;

int main() {
    list<int> ilst = {0, 3, 7, 1, 0, 0, 4};
    // 利用反向迭代器查找最後一個 0
    auto pos = find(ilst.crbegin(), ilst.crend(), 0);
    // 將反向迭代器 pos 向鏈表頭方向靠近一個位置,pos 轉換爲普通迭代
    // 器 pos.base() 時,將回到最後一個 0 的位置
    ++pos;
    int count = 1;      // 計數最後一個 0 在鏈表中的位置,從 1 開始
    for (auto iter = ilst.cbegin(); iter != pos.base(); ++iter, ++count) {}
    cout << "最後一個0在第" << count << "個位置" << endl;

    return 0;
}
// 運行結果
最後一個0在第6個位置

Process finished with exit code 0

10.37

【出題思路】

深刻理解反向迭代器和普通迭代器間的差別及相互轉換。

【解答】

反向迭代器和普通迭代器的轉換是左閉合區間的轉換。

對 10 個元素的 vector ivec,包含位置 3~7 之間元素的迭代器區間以下所示:

0       1       i1->2       3       4       5       6       i2->7       8       9

第一個迭代器是 ivec.begin() + 2,第二個迭代器指向位置 8,即 ivec.begin() + 7

當將這兩個迭代器轉換爲反向迭代器時,位置以下:

0       re->1       2       3       4       5       rb->6       7       8       9

雖然與正向迭代器的位置不一樣,但左閉合區間 [rb, re) 仍然對應位置 3~7 之間的元素。顯然,普通迭代器和反向迭代器間的這種錯位,偏偏是由於標準庫的範圍概念是左閉合區間形成的。

另外,注意 back_inserter 和流迭代器的使用。

#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <algorithm>

using namespace std;

int main() {
    ostream_iterator<int> out_iter(cout, " ");
    vector<int> ivec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 用流迭代器和 copy 輸出 int 序列
    copy(ivec.cbegin(), ivec.cend(), out_iter);
    cout << endl;

    list<int> ilst;
    // 將 ivec[2],也就是第 3 個元素的位置轉換爲反向迭代器
    // vector 是連續存儲的,能夠進行迭代器的加減
    vector<int>::reverse_iterator re(ivec.begin() + 2);
    // 將 ivec[7],也就是第 8 個元素的位置轉換爲反向迭代器
    vector<int>::reverse_iterator rb(ivec.begin() + 7);
    // 用反向迭代器將元素逆序拷貝到 list
    copy(rb, re, back_inserter(ilst));
    copy(ilst.cbegin(), ilst.cend(), out_iter);
    cout << endl;

    return 0;
}
// 運行結果
0 1 2 3 4 5 6 7 8 9 
6 5 4 3 2 

Process finished with exit code 0

【其餘解題思路】

也能夠經過 (c)rbegin() 得到反向迭代器的首位置(正向的尾元素),而後正確計算偏移量,來得到正確的反向範圍,但計算上須要很是當心。

#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <algorithm>

using namespace std;

int main() {
    ostream_iterator<int> out_iter(cout, " ");
    vector<int> ivec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 用流迭代器和 copy 輸出 int 序列
    copy(ivec.cbegin(), ivec.cend(), out_iter);
    cout << endl;

    list<int> ilst;
    copy(ivec.crbegin() + ivec.size() - 7, ivec.crend() - 3 + 1,
            back_inserter(ilst));
    copy(ilst.cbegin(), ilst.cend(), out_iter);
    cout << endl;

    return 0;
}
// 運行結果
0 1 2 3 4 5 6 7 8 9 
6 5 4 3 2 

Process finished with exit code 0

10.38

【出題思路】

理解 5 種迭代器及它們的差別。

【解答】

  • 輸入迭代器:只讀,不寫;單遍掃描,只能遞增;還支持相等性斷定運算(==!=)、解引用運算符(*)(只出如今賦值運算符右側)和箭頭運算符(->)。
  • 輸出迭代器:只寫,不讀;單遍掃描,只能遞增;支持解引用運算符(*)(只出如今賦值運算符左側)。
  • 前向迭代器:可讀、寫;多遍掃描;只能遞增;支持全部輸入、輸出迭代器的操做。
  • 雙向迭代器:可讀、寫;多遍掃描;可遞增、遞減;支持全部向前迭代器操做。
  • 隨機訪問迭代器:可讀、寫;多遍掃描;支持所有迭代器運算,除了上述迭代器類別支持的操做外,還有:
    1. 比較兩個迭代器相對位置的關係運算符(<<=>>=
    2. 迭代器和一個整數值的加減運算(++=--=)令迭代器在序列中前進或後退給定整數個元素
    3. 兩個迭代器上的減法運算符(-)獲得其距離
    4. 下標運算符

10.39

【出題思路】

理解經常使用容器的迭代器類型。

【解答】

list 上的迭代器是雙向迭代器,vector 上的迭代器是隨機訪問迭代器。

注:vector 在內存中是連續存儲的,因此才能夠用隨機訪問迭代器。list 鏈表在內存中不是連續存儲的。

10.40

【出題思路】

理解算法對迭代器類型的要求。

【解答】

  • copy 要求前兩個參數至少是輸入迭代器,表示一個輸入範圍。它讀取這個範圍中的元素,寫入到第三個參數表示的輸出序列中,所以第三個參數至少是輸出迭代器。
  • reverse 要反向處理序列,所以它要求兩個參數至少是雙向迭代器。
  • unique 順序掃描元素,覆蓋重複元素,所以要求兩個參數至少是前向迭代器。「至少」 意味着能力更強的迭代器是可接受的。

10.41

【出題思路】

理解標準庫算法的命名規範。

【解答】

  1. 將範圍 [beg, end) 值等於 old_val 的元素替換爲 new_val。
  2. 將範圍 [beg, end) 知足謂詞 pred 的元素替換爲 new_val。
  3. 將範圍 [beg, end) 的元素拷貝到目的序列 dest 中,將其中值等於 old_val 的元素替換爲 new_val。
  4. 將範圍 [beg, end) 的元素拷貝到目的序列 dest 中,將其中知足謂詞 pred 的元素替換爲 new_val。

10.42

【出題思路】

練習鏈表的特殊操做。

【解答】

本題要使用鏈表專用的 sort 和 unique 算法,與泛型算法的不一樣點有以下兩點:

  1. 它們是以鏈表類的成員函數形式實現的(而非 algorithm 庫),所以使用方式是在鏈表對象上調用它們,也並不須要迭代器參數指出處理的序列。
  2. 因爲是以成員函數形式實現的,是直接操做容器而非經過迭代器訪問容器元素,所以這些算法具備修改容器的能力(添加、刪除元素)。例如,unique 會調用 erase 直接真正刪除重複元素,容器的大小會變小,而不是像泛型 unique 算法那樣只是覆蓋重複元素,並不改變容器大小。所以程序已再也不須要調用 erase 了。

建議讀者好好體會通用算法(泛型算法)專用算法(特定容器算法)之間的差別,包括上述使用方式上的差別,以及從庫的開發者的角度思考兩種方式的差別。

#include <iostream>
#include <list>

using std::string;
using std::list;
using std::cout;
using std::endl;

void output_words(list<string> &words) {
    // 打印容器中的內容,方便觀察程序執行狀態
    for (auto &s : words)
        std::cout << s << " ";
    cout << words.size() << endl;
}

void elimDups(std::list<std::string> &words) {
    output_words(words);

    // 按字典序排序 words,以便查找重複單詞(相同的單詞緊挨着)
    words.sort();
    output_words(words);

    // unique 調用 erase 刪除同一個值的連續拷貝,使得每一個單詞只出現一次
    words.unique();
    output_words(words);

    // 打印單詞,每一個單詞後面接一個空格。功能和函數 output_words 同樣
    for_each(words.begin(), words.end(),
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::list<std::string> slist = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    elimDups(slist);
    return 0;
}
// 運行結果
the quick red fox jumps over the slow red turtle 10
fox jumps over quick red red slow the the turtle 10
fox jumps over quick red slow the turtle 8
fox jumps over quick red slow the turtle 

Process finished with exit code 0
相關文章
相關標籤/搜索