C++ Primer(第五版)|練習題答案與解析(第十二章:動態內存)

C++ Primer(第五版)|練習題答案與解析(第十二章:動態內存)

本博客主要記錄C++ Primer(第五版)中的練習題答案與解析。
參考:C++ Primer
C++ Primer

練習題12.1

在此代碼的結尾,b1和b2各包含多少元素?

StrBlob b1;
{
    strBlob b2 = {"a", "an", "the"};
    b1 = b2;
    b2.push_back("about");
}

“使用動態內存的原因:讓多個對象共享相同的底層數據。也就是說拷貝的情況雖然發生,但是並不是元素的拷貝,而是將本身的指向拷貝給另一個指針對象,讓這一個對象也指向自己所指向的對象,這樣在本身釋放以後,還有另一個對象指向自身原來所指向的對象。”
所以b1和b2都有4個,但是b2已經沒了,如果接着使用b2會出錯。

練習題12.2

編寫你自己的StrBlob類,包含const版本的front和back。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>
#include <iostream>
using namespace std;
class StrBlob {
public:
    using size_type = vector<string>::size_type;

    StrBlob():data(make_shared<vector<string>>()) { }
    StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw out_of_range(msg);
    }
private:
    shared_ptr<vector<string>> data;
};
int main(){
    const StrBlob csb{ "train", "test", "overfit" };
    StrBlob sb{ "train", "NN", "ML" };
    cout << csb.front() << " " << csb.back() << endl;//csb是const 使用pop_back報錯
    sb.pop_back();
    cout << sb.front() << " " << sb.back() << endl;
    sb.back() = "CNN";
    cout << sb.front() << " " << sb.back() << endl;
    return 0;
}

測試:

train overfit
train NN
train CNN

練習題12.3

StrBlob需要const版本的push_back和pop_back嗎?如果需要,添加進去。否則,解釋爲什麼不需要。

這兩個函數不會對參數進行修改,所以無需加const。

練習題12.4

在我們的check函數中,沒有檢查i是否大於0。爲什麼可以忽略這個檢查?

因爲size_type本身就是unsigned類型的,非負數,即使賦值負數也會轉換成大於0的數。

練習題12.5

我們未編寫接受一個initializer_list explicit參數的構造函數。討論這個設計策略的優點和缺點。

explicit的作用就是抑制構造函數的隱式轉換

  • 優點:不會自動的進行類型轉換,阻止initializer_list自動轉換爲StrBlob,防止出錯。
  • 缺點:必須用構造函數顯示創建一個對象,不夠方便簡單

練習題12.6

編寫函數,返回一個動態分配的int的vector。將此vector傳遞給另一個函數,這個函數讀取標準輸入,將讀入的值保存在vector元素中。再將vector傳遞給另一個函數,打印讀入的值。記得在恰當的時刻delete vector。

#include <iostream>
#include <vector>
using namespace std;
vector<int>* applicateVector()
{
    return new (nothrow) vector<int>();
}
void readToVector(vector<int>* ivec)
{
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;
    }
    int num;
    while (cin >> num) {
        (*ivec).push_back(num);
    }
}
void printVector(vector<int>* ivec)
{
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;

    }
    for (auto i : *ivec) {
        cout << i << " ";
    }
    cout << endl;
}
int main(){
    vector<int> *ivec = applicateVector();//動態分配的int的vector
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;
    }
    readToVector (ivec);//讀取 即輸入
    printVector (ivec);//打印
    delete ivec;
    return 0;
}

測試:

12 12 10 5 3
^Z
12 12 10 5 3

練習題12.7

重做上一題,這次使用shared_ptr而不是內置指針。

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> applicateVectorPtr()
{
    return make_shared<vector<int>>();
}

void readToVectorPtr(shared_ptr<vector<int>> ivec)
{
    if (!ivec) {
        cout << "vector is illegal." << endl;
    }

    int num;
    while (cin >> num) {
        ivec->push_back(num);
    }
}

void printVectorPtr(shared_ptr<vector<int>> ivec)
{
    if (!ivec) {
        cout << "vector is illegal." << endl;
    }

    for (auto i : *ivec) {
        cout << i << " ";
    }
    cout << endl;
}
int main(){
    shared_ptr<vector<int>> p;
    p = applicateVectorPtr();
    readToVectorPtr(p);
    printVectorPtr(p);
    return 0;
}

測試:

10 9 8 15 12
^Z
10 9 8 15 12

練習題12.8

下面的函數是否有錯誤?如果有,解釋錯誤原因。
bool b() {
int *p = new int;
// …
return p;
}

函數定義的返回值類型與實際的返回類型不匹配。int* 會轉換爲bool類型。這會導致p將沒有機會被delete,最終造成內存泄漏。

練習題12.9

解釋下面代碼執行的結果:

int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
  • r = q,則r原來的空間沒有指針指向,也不會被釋放,因此造成內存泄漏。
  • r2 = q2, q2的引用計數+1,r2的引用計數-1,變爲0,則r2原來指向的那塊空間會被釋放,不會造成內存泄漏。

練習題12.10

下面的代碼調用了413頁中定義的process函數,解釋此調用是否正確,如果不正確,應如何修改?

shared_ptr<int> p (new int(42));
process (shared_ptr<int>(p));

此調用正確。

練習題12.11

如果我們像下面這樣調用process,會發生什麼?
process(shared_ptr(p.get()));

p.get()初始化返回一個內置指針,傳給process的是由p.get()初始化的一個新的shared_ptr,與p指向同一塊內存。在process函數結束時,新的shared_ptr被釋放,p就變成了一個空懸指針。P414。

練習題12.12

p 和q的定義如下,對應接下來的對process的每個調用,如果合法,解釋它做了什麼,如果不合法,解釋錯誤原因:

auto p = new int();
auto sp = make_shared<int>();

(a ) process (sp); 合法,將一個shared_ptr傳給process。
(b ) process(new int()); 不合法,不能進行內置指針隱式轉換爲shared_ptr,P412。
(c ) process (p); 不合法,不能進行內置指針隱式轉換爲shared_ptr,P412。
(d ) process (shared ptr<int>(p)); 合法。但不建議這樣使用,智能指針和常量指針混用容易引起問題,比如有可能被釋放兩次,P413。

練習題12.13

如果執行下面的代碼,會發生什麼?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

使用sp初始化p,p和sp指向同一塊內存。delete p之後,這塊內存被釋放,sp也會被釋放,導致同一塊內存被釋放兩次。P414。

練習題12.14

編寫你自己版本的用shared_ptr管理connection的函數。

#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct connection {
    string ip;
    int port;
    connection(string ip_, int port_):ip(ip_), port(port_){ }
};
struct destination {
    string ip;
    int port;
    destination(string ip_, int port_):ip(ip_), port(port_){ }
};
connection connect(destination* pDest)
{
    shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
    cout << "creating connection(" << pConn.use_count() << ")" << endl;
    return *pConn;
}
void disconnect(connection pConn)
{
    cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << endl;
}
void end_connection(connection *pConn)
{
    disconnect(*pConn);
}
void f(destination &d)
{
    connection conn = connect(&d);
    shared_ptr<connection> p(&conn, end_connection);
    cout << "connecting now(" << p.use_count() << ")" << endl;
}

int main()
{
    destination dest("202.192.01.02", 8888);
    f(dest);
}

測試:

creating connection(1)
connecting now(1)
connection close(202.192.01.02:8888)

練習題12.15

重寫第一題的程序,用lambda代替end_connection函數。

//其中f修改爲:
void f(destination &d)
{
    connection conn = connect(&d);
    shared_ptr<connection> p(&conn, [](connection *p){ disconnect(*p); });
    cout << "connecting now(" << p.use_count() << ")" << endl;
}

練習題12.16

如果你試圖拷貝或賦值unique_ptr,編譯器並不總是能給出易於理解的錯誤信息。編寫包含這種錯誤的程序,觀察編譯器如何診斷這種錯誤。

#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
    unique_ptr<int> p1 (new int(42));
    unique_ptr<int> p2 = p1;
    unique_ptr<int> p3 (p1);
    return 0;
}

在這裏插入圖片描述

練習題12.17

下面的unique_ptr聲明中,哪些是合法的,哪些可能導致後續的程序錯誤?解釋每個錯誤的問題在哪裏。

int ix = 1024, *pi = &ix, *pi2 = new int (2048);
typedef unique_ptr<int> IntP;

(a ) IntP p0 (ix); 不合法,unique_ptr只能綁定到new返回的指針上。P417。報錯:error: invalid conversion from ‘int’ to ‘std::unique_ptr::pointer {aka int*}’
(b )IntP p1 (pi); 編譯時無報錯。但不建議使用,不是new返回的指針。
(c ) IntP p2 (pi2); 合法,unique_ptr必須採用直接初始化,但有可能在運行時產生空懸指針pi2,因爲運行時,p2所指空間可能被釋放,pi2就成爲了空懸指針。
(d )IntP p3 (&ix); 同b,但不建議使用,不是new返回的指針。
(e ) IntP p4 (new int(2048)); 合法,推薦使用
(f )IntP p5 (p2.get()); 不合法,使用get初始化一個智能指針,p2和p5指向同一塊內存,當指針非法,智能指針會自動delete,此時這塊內存會被二次delete。

練習題12.18

shared_ptr爲什麼沒有release成員?

P418。
release的作用是交出指針指向對象的控制權,但是shared_ptr是多對一的關係,一個shared_ptr交出控制權,其它shared_ptr依舊可以控制這個對象。因此這個方法對shared_ptr無意義。

練習題12.19

定義你自己版本的StrBlobPtr,更新StrBlobPtr類,加入恰當的friend聲明及begin和end成員。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
using namespace std;
class StrBlobPtr;
class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class StrBlobPtr;

    StrBlobPtr begin();
    StrBlobPtr end();

    StrBlob():data(make_shared<vector<string>>()) { }
    StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw out_of_range(msg);
    }

private:
    shared_ptr<vector<string>> data;
};

class StrBlobPtr {
public:
    StrBlobPtr():curr(0) { }
    StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz) { }
    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
    string& deref() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    StrBlobPtr& incr() {// 前綴遞增
        check(curr, "increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }

private:
    // 若檢查成功,check返回一個指向vector的shared_ptr.
    shared_ptr<vector<string>> check(size_t i, const string &msg) const {
        auto ret = wptr.lock();
        if (!ret) throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size()) throw out_of_range(msg);
        return ret;
    }
    weak_ptr<vector<string>> wptr;// 保存一個weak_ptr,意味着vector有可能被銷燬。
    size_t curr;// 在數組中當前的位置
};
// begin()和end()函數的定義必須在StrBlobPtr類定義之後,否則會報錯(StrBlobPtr是不完全類型)
StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
    return StrBlobPtr(*this, data->size());
}
int main()
{

    return 0;
}

練習題12.20

編寫程序,逐行讀入一個輸入文件,將內容存入一個StrBlob中,用一個StrBlobPtr打印出StrBlob中的每個元素。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
using namespace std;
class StrBlobPtr;
class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class StrBlobPtr;

    StrBlobPtr begin();
    StrBlobPtr end();

    StrBlob():data(make_shared<vector<string>>()) { }
    StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw out_of_range(msg);
    }

private:
    shared_ptr<vector<string>> data;
};

class StrBlobPtr {
public:
    StrBlobPtr():curr(0) { }
    StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz) { }
    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
    string& deref() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    StrBlobPtr& incr() {// 前綴遞增
        check(curr, "increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }

private:
    // 若檢查成功,check返回一個指向vector的shared_ptr.
    shared_ptr<vector<string>> check(size_t i, const string &msg) const {
        auto ret = wptr.lock();
        if (!ret) throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size()) throw out_of_range(msg);
        return ret;
    }
    weak_ptr<vector<string>> wptr;// 保存一個weak_ptr,意味着vector有可能被銷燬。
    size_t curr;// 在數組中當前的位置
};
// begin()和end()函數的定義必須在StrBlobPtr類定義之後,否則會報錯(StrBlobPtr是不完全類型)
StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
    return StrBlobPtr(*this, data->size());
}
#include <fstream>
#include <iostream>
int main()
{
    StrBlob strb;
    string line;
    StrBlobPtr pbeg, pend;
    ifstream ifs ("input_file.txt");
    if (ifs) {
        while (getline(ifs, line)) {
            strb.push_back(line);
        }
    }
    for (pbeg=strb.begin(), pend=strb.end(); pbeg != pend; pbeg.incr()) {
        cout << pbeg.deref() << endl;
    }
    return 0;
}

測試:

whrere r u
y dont u send me a pic
k thk 18r

練習題12.21

也可以這樣編寫StrBlobPtr的deref成員:
std::string& deref() const
{
return (*check(curr, “dereference past end”))[curr];
}
你認爲哪個版本更好?爲什麼?

原始版本更好,符合閱讀習慣。

練習題12.22

爲了能讓StrBlobPtr使用const StrBlob,你覺得應該如何修改?定義一個名爲ConstStrBlobPtr的類,使其能夠指向const StrBlob。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
using namespace std;
class ConstStrBlobPtr;//新添加的
class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class ConstStrBlobPtr;

    ConstStrBlobPtr begin() const; // 應該添加 const
    ConstStrBlobPtr end() const; // 應該添加 const

    StrBlob():data(make_shared<vector<string>>()) { }
    StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw out_of_range(msg);
    }

private:
    shared_ptr<vector<string>> data;
};

class ConstStrBlobPtr {//對應位置修改
public:
    ConstStrBlobPtr():curr(0) { }
    ConstStrBlobPtr(const StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz) { }
    bool operator!=(const ConstStrBlobPtr& p) { return p.curr != curr; }//添加const
    const string& deref() const {//返回值添加const
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    ConstStrBlobPtr& incr() {// 前綴遞增
        check(curr, "increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }

private:
    // 若檢查成功,check返回一個指向vector的shared_ptr.
    shared_ptr<vector<string>> check(size_t i, const string &msg) const {
        auto ret = wptr.lock();
        if (!ret) throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size()) throw out_of_range(msg);
        return ret;
    }
    weak_ptr<vector<string>> wptr;// 保存一個weak_ptr,意味着vector有可能被銷燬。
    size_t curr;// 在數組中當前的位置
};
// begin()和end()函數的定義必須在ConstStrBlobPtr類定義之後,否則會報錯(StrBlobPtr是不完全類型)
ConstStrBlobPtr StrBlob::begin() const // 應該添加 const
{
    return ConstStrBlobPtr(*this);
}
ConstStrBlobPtr StrBlob::end() const // 應該添加 const
{
    return ConstStrBlobPtr(*this, data->size());
}
#include <fstream>
#include <iostream>
int main()
{
    return 0;
}

練習題12.23

編寫一個程序,連接兩個字符串字面常量,將結果保存在一個動態分配的char數組中。重寫這個程序,連接兩個標準庫string對象。

#include <iostream>
#include <string>
#include <string.h>
using namespace std;
int main()
{
    // 動態分配的char數組
    char *concatenate_string = new char[strlen("test " "train") + 1]();
    strcat(concatenate_string, "hello ");
    strcat(concatenate_string, "world");
    cout << concatenate_string << std::endl;
    delete [] concatenate_string;

    // 標準庫string
    string str1{ "test " }, str2{ "train" };
    cout << str1 + str2 << std::endl;
}

測試:

hello world
test train

練習題12.24

編寫一個程序,從標準輸入讀取一個字符串,存入一個動態分配的字符數組中。描述你的程序如何處理變長輸入。測試你的程序,輸入一個超出你分配的數組長度的字符串。

#include <iostream>
using namespace std;
int main()
{
    // 需要告訴大小
    cout << "How long do you want the string? ";
    int size{ 0 };
    cin >> size;
    char *input = new char[size+1]();
    cin.ignore();
    cout << "input the string: ";
    cin.get(input, size+1);
    cout << input;
    delete [] input;
    // 測試: 如果長度超過數組大小,我們將丟失超出範圍的字符。
}

測試:

How long do you want the string? 4
input the string: train
trai

練習題12.25

給定下面的new表達式,你應該如何釋放pa?
int *pa = new int[10];

釋放:delete[] pa;數組一定要加[],P425。

練習題12.26

用allocator重寫427頁中的程序。

#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
    allocator<string> alloc;
    auto const p = alloc.allocate(10);
    string s;
    auto q = p;

    while (cin >> s && q != p+10) {
      alloc.construct (q++, s);
    }

    while (q != p) {
        cout << *--q << endl;
        alloc.destroy(q);
    }
    alloc.deallocate (p, 10);
    return 0;
}

測試:

test train cnn overfit
^Z
overfit
cnn
train
test

練習題12.27

TextQuery和QueryResult類只使用了我們已經介紹過的語言和標準庫特性。不要提前看後續章節內容,只用已經學到的知識對這兩個類編寫你自己的版本。

#include <iostream>
#include <string>
#include <memory>
#include <iostream>
#include <fstream>
#include <map>
#include <set>
#include <vector>
using namespace std;
class QueryResult;
class TextQuery
{
public:
    using line_no = vector<string>::size_type;
    TextQuery(ifstream& ifs);
    QueryResult query (const string& word) const;

private:
    shared_ptr<vector<string>> sp_text;
    // 每個單詞到它所出現的行號的映射
    map<string, shared_ptr<set<line_no>>> sp_dictionary;
};

class QueryResult
{
public:
     friend ostream& print (ostream&, const QueryResult&);
public:
    QueryResult(const string& s,
                shared_ptr<set<TextQuery::line_no>> sp_set,
                shared_ptr<vector<string>> sp_vec):
            sought (s), lines (sp_set), file (sp_vec) {}


private:
    string sought;     // 要查找的單詞
    shared_ptr<set<TextQuery::line_no>> lines;       // 出現的行號
    shared_ptr<vector<string>> file;     // 輸入文件
//    vector<string> occur_line;
};
ostream& print (ostream&, const QueryResult&);
int main()
{
    return 0;
}

練習題12.27

TextQuery和QueryResult類只使用了我們已經介紹過的語言和標準庫特性。不要提前看後續章節內容,只用已經學到的知識對這兩個類編寫你自己的版本。

練習題12.28

編寫程序實現文本查詢,不要定義類來管理數據。你的程序應該接受一個文件,並與用戶交互來查詢單詞。使用vector、map和set容器來保存來自文件的數據並生產查詢結果。

練習題12.29

我們曾經用do while 循環來編寫管理用戶交互的循環。用do while重寫本節程序,解釋你傾向於哪個版本,爲什麼。

練習題12.30

定義你自己版本的 TextQuery和QueryResult類,並執行12.3.1節中的runQueries函數。

#include <string>
using std::string;
#include <vector>
using std::vector;
#include <memory>
using std::shared_ptr;
#include <iostream>
#include <fstream>
#include <map>
#include <set>
class QueryResult;
class TextQuery {
public:
    using LineNo = vector<string>::size_type;
    TextQuery(std::ifstream &);//構造函數
    QueryResult query(const string&) const;
private:
    shared_ptr<vector<string>> input;//輸入文件
    //每個單詞到它所在的行號的集合的映射
    std::map<string, shared_ptr<std::set<LineNo>>> result;
};

class QueryResult {
public:
    friend std::ostream& print(std::ostream &, const QueryResult&);
public:
    QueryResult(const string &s, shared_ptr<std::set<TextQuery::LineNo>> set, shared_ptr<vector<string>> v) : word(s), nos(set), input(v) { }
private:
    string word;//查詢的單詞
    shared_ptr<std::set<TextQuery::LineNo>> nos;//出現的行號
    shared_ptr<vector<string>> input;//輸入文件
};

std::ostream& print(std::ostream &, const QueryResult&);
#include <sstream>
#include <algorithm>
//讀取輸入文件並建立單詞到行號的映射
TextQuery::TextQuery(std::ifstream &ifs) : input(new vector<string>)
{
    LineNo lineNo{ 0 };//
    for (string line; std::getline(ifs, line); ++lineNo) {//對文件中的每一行
        input->push_back(line);//保存此文本
        std::istringstream line_stream(line);//將文本分解成單詞
        for (string text, word; line_stream >> text; word.clear()) {//對行中的每個單詞
            // 避免讀一個有標點符號的單詞(如:word,)
            std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);
            //如果使用using namespace std; 則remove_copy_if函數就會報錯,提示無法解析的重載函數類型。
            // 使用引用避免計算shared_ptr的添加。
            //如果單詞不在result中,以之爲下標在reslut中添加一項
            auto &nos = result[word];//nos是一個shared_ptr
            if (!nos) nos.reset(new std::set<LineNo>);//分配給一個新的set
            nos->insert(lineNo);//將行號插入set中
        }
    }
}

QueryResult TextQuery::query(const string& str) const
{
    // 使用靜態只分配一次。
    //如果沒有找到單詞str,將返回一個指向此set的指針
    static shared_ptr<std::set<LineNo>> nodata(new std::set<LineNo>);
    //使用find而不是下標運算符來查找單詞,避免將單詞添加到result中。
    auto found = result.find(str);
    if (found == result.end()) return QueryResult(str, nodata, input);//未找到
    else return QueryResult(str, found->second, input);
}

std::ostream& print(std::ostream &out, const QueryResult& qr)
{
    //如果找到了單詞,打印出出現次數和所有出現的位置
    out << qr.word << " occurs " << qr.nos->size() << (qr.nos->size() > 1 ? " times" : " time") << std::endl;
    //打印單詞出現的每一行
    for (auto i : *qr.nos)//對set中的每一個單詞
    //避免行號從0開始給用戶帶來困惑。
        out << "\t(line " << i+1 << ") " << qr.input->at(i) << std::endl;
    return out;
}
#include <iostream>
void runQueries(std::ifstream &infile)
{
    TextQuery tq(infile);
    while (true) {
        std::cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(std::cin >> s) || s == "q") break;
        print(std::cout, tq.query(s)) << std::endl;
    }
}
void runQueries2(std::ifstream &infile)//do-while
{
    TextQuery tq(infile);
    do {
        std::cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(std::cin >> s) || s == "q") break;
        print(std::cout, tq.query(s)) << std::endl;
    }while(true);
}
int main()
{
    std::ifstream file("DataStory.txt");
    runQueries(file);
    //runQueries2(file);//do-while
    return 0;
}

story:

The Old Man and the Old Cat
An old man has a cat. The cat is very old, too. He runs very quickly. And his teeth are bad. 
One evening, the old cat sees a little mouse. He catches it, but he can’t eat it because his teeth are not strong enough. The mouse runs away.
The old man is very angry. He beats his cat.
He says: “You are a fool cat. I will punish you!” the cat is very sad. 
He thinks:“When I was young, I worked hard for you.
Now you don’t like me because I’m too old to work. You should know you are old, too.

測試:

enter word to look for, or q to quit: he
he occurs 1 time
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.

enter word to look for, or q to quit: cat
cat occurs 4 times
        (line 2) An old man has a cat. The cat is very old, too. He runs very quickly. And his teeth are bad.
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.
        (line 4) The old man is very angry. He beats his cat.
        (line 5) He says: 鈥淵ou are a fool cat. I will punish you!?the cat is very sad.

練習題12.31

如果用vector代替set保存行號,會有什麼差別?哪種方法更好?爲什麼?

set更好,因爲vector保存不會排序,查找結果便不能按照行號的由小到大順序顯示了。

練習題12.32

重寫 TextQuery和QueryResult類,用StrBlob類代替vector 保存輸入文件。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>

using std::vector; using std::string;

class ConstStrBlobPtr;

class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class ConstStrBlobPtr;

    ConstStrBlobPtr begin() const; // should add const
    ConstStrBlobPtr end() const; // should add const

    StrBlob():data(std::make_shared<vector<string>>()) { }
    StrBlob(std::initializer_list<string> il):data(std::make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    std::string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    std::string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const std::string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const std::string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw std::out_of_range(msg);
    }

private:
    std::shared_ptr<vector<string>> data;
};

class ConstStrBlobPtr {
public:
    ConstStrBlobPtr():curr(0) { }
    ConstStrBlobPtr(const StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz) { } // should add const
    bool operator!=(ConstStrBlobPtr& p) { return p.curr != curr; }
    const string& deref() const { // return value should add const
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    ConstStrBlobPtr& incr() {
        check(curr, "increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }

private:
    std::shared_ptr<vector<string>> check(size_t i, const string &msg) const {
        auto ret = wptr.lock();
        if (!ret) throw std::runtime_error("unbound StrBlobPtr");
        if (i >= ret->size()) throw std::out_of_range(msg);
        return ret;
    }
    std::weak_ptr<vector<string>> wptr;
    size_t curr;
};
using std::shared_ptr;
#include <iostream>
#include <fstream>
#include <map>
#include <set>

class QueryResult;
class TextQuery {
public:
    TextQuery(std::ifstream &);
    QueryResult query(const string&) const;
private:
    StrBlob file;
    std::map<string, shared_ptr<std::set<StrBlob::size_type>>> result;
};

class QueryResult {
public:
    friend std::ostream& print(std::ostream &, const QueryResult&);
public:
    QueryResult(const string &s, shared_ptr<std::set<StrBlob::size_type>> set, const StrBlob& f) : word(s), nos(set), file(f) { }
private:
    string word;
    shared_ptr<std::set<StrBlob::size_type>> nos;
    StrBlob file;
};

std::ostream& print(std::ostream &, const QueryResult&);
#include <sstream>
#include <algorithm>

TextQuery::TextQuery(std::ifstream &ifs)
{
    StrBlob::size_type lineNo{ 0 };
    for (string line; std::getline(ifs, line); ++lineNo) {
        file.push_back(line);
        std::istringstream line_stream(line);
        for (string text, word; line_stream >> text; word.clear()) {
            // avoid read a word followed by punctuation(such as: word, )
            std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);
            // use reference avoid count of shared_ptr add.
            auto &nos = result[word];
            if (!nos) nos.reset(new std::set<StrBlob::size_type>);
            nos->insert(lineNo);
        }
    }
}

QueryResult TextQuery::query(const string& str) const
{
    // use static just allocate once.
    static shared_ptr<std::set<StrBlob::size_type>> nodate(new std::set<StrBlob::size_type>);
    auto found = result.find(str);
    if (found == result.end()) return QueryResult(str, nodate, file);
    else return QueryResult(str, found->second, file);
}

std::ostream& print(std::ostream &out, const QueryResult& qr)
{
    out << qr.word << " occurs " << qr.nos->size() << (qr.nos->size() > 1 ? " times" : " time") << std::endl;
    for (auto i : *qr.nos) {
        ConstStrBlobPtr p(qr.file, i);
        out << "\t(line " << i+1 << ") " << p.deref() << std::endl;
    }
    return out;
}
#include <iostream>
void runQueries(std::ifstream &infile)
{
    TextQuery tq(infile);
    while (true) {
        std::cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(std::cin >> s) || s == "q") break;
        print(std::cout, tq.query(s)) << std::endl;
    }
}
int main(){
    std::ifstream file("DataStory.txt");
    runQueries(file);
    return 0;
}

測試:

enter word to look for, or q to quit: he
he occurs 1 time
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.

enter word to look for, or q to quit: cat
cat occurs 4 times
        (line 2) An old man has a cat. The cat is very old, too. He runs very quickly. And his teeth are bad.
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.
        (line 4) The old man is very angry. He beats his cat.
        (line 5) He says: 鈥淵ou are a fool cat. I will punish you!?the cat is very sad.

練習題12.33

在第15章中我們將擴展查詢系統,在QueryResult類中將會需要一些額外的成員。添加名爲begin和end的成員,返回一個迭代器,指向一個給定查詢返回行號的set中的位置。再添加一個名爲get_file的成員,返回一個shared_ptr,指向QueryResult對象中的文件。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>

using std::vector; using std::string;

class ConstStrBlobPtr;

class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class ConstStrBlobPtr;

    ConstStrBlobPtr begin() const; // should add const
    ConstStrBlobPtr end() const; // should add const

    StrBlob():data(std::make_shared<vector<string>>()) { }
    StrBlob(std::initializer_list<string> il):data(std::make_shared<vector<string>>(il)) { }

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    std::string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    std::string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const std::string& front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const std::string& back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {
        if (i >= data->size()) throw std::out_of_range(msg);
    }

private:
    std::shared_ptr<vector<string>> data;
};

class ConstStrBlobPtr {
public:
    ConstStrBlobPtr():curr(0) { }
    ConstStrBlobPtr(const StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz) { } // should add const
    bool operator!=(ConstStrBlobPtr& p) { return p.curr != curr; }
    const string& deref() const { // return value should add const
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    ConstStrBlobPtr& incr() {
        check(curr, "increment past end of StrBlobPtr");
        ++curr;
        return *this;
    }

private:
    std::shared_ptr<vector<string>> check(size_t i, const string &msg) const {
        auto ret = wptr.lock();
        if (!ret) throw std::runtime_error("unbound StrBlobPtr");
        if (i >= ret->size()) throw std::out_of_range(msg);
        return ret;
    }
    std::weak_ptr<vector<string>> wptr;
    size_t curr;
};
using std::shared_ptr;
#include <iostream>
#include <fstream>
#include <map>
#include <set>
class QueryResult;
class TextQuery {
public:
    TextQuery(std::ifstream &);
    QueryResult query(const string&) const;
private:
    StrBlob file;
    std::map<string, shared_ptr<std::set<StrBlob::size_type>>> result;
};

class QueryResult {
public:
    using ResultIter = std::set<StrBlob::size_type>::iterator;
    friend std::ostream& print(std::ostream &, const QueryResult&);
public:
    QueryResult(const string &s, shared_ptr<std::set<StrBlob::size_type>> set, const StrBlob& f) : word(s), nos(set), file(f) { }
    ResultIter begin() const { return nos->begin(); }
    ResultIter end() const { return nos->end(); }
    shared_ptr<StrBlob> get_file() const { return std::make_shared<StrBlob>(file); }
private:
    string word;
    shared_ptr<std::set<StrBlob::size_type>> nos;
    StrBlob file;
};

std::ostream& print(std::ostream &, const QueryResult&);
#include <sstream>
#include <algorithm>

TextQuery::TextQuery(std::ifstream &ifs)
{
    StrBlob::size_type lineNo{ 0 };
    for (string line; std::getline(ifs, line); ++lineNo) {
        file.push_back(line);
        std::istringstream line_stream(line);
        for (string text, word; line_stream >> text; word.clear()) {
            // avoid read a word followed by punctuation(such as: word, )
            std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);
            // use reference avoid count of shared_ptr add.
            auto &nos = result[word];
            if (!nos) nos.reset(new std::set<StrBlob::size_type>);
            nos->insert(lineNo);
        }
    }
}

QueryResult TextQuery::query(const string& str) const
{
    // use static just allocate once.
    static shared_ptr<std::set<StrBlob::size_type>> nodate(new std::set<StrBlob::size_type>);
    auto found = result.find(str);
    if (found == result.end()) return QueryResult(str, nodate, file);
    else return QueryResult(str, found->second, file);
}

std::ostream& print(std::ostream &out, const QueryResult& qr)
{
    out << qr.word << " occurs " << qr.nos->size() << (qr.nos->size() > 1 ? " times" : " time") << std::endl;
    for (auto it = qr.begin(); it != qr.end(); ++it) {
        ConstStrBlobPtr p(*qr.get_file(), *it);
        out << "\t(line " << *it + 1 << ") " << p.deref() << std::endl;
    }
    return out;
}
#include <iostream>

void runQueries(std::ifstream &infile)
{
    TextQuery tq(infile);
    while (true) {
        std::cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(std::cin >> s) || s == "q") break;
        print(std::cout, tq.query(s)) << std::endl;
    }
}
int main(){
    std::ifstream file("DataStory.txt");
    runQueries(file);
    return 0;
}
enter word to look for, or q to quit: he
he occurs 1 time
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.

enter word to look for, or q to quit: cat
cat occurs 4 times
        (line 2) An old man has a cat. The cat is very old, too. He runs very quickly. And his teeth are bad.
        (line 3) One evening, the old cat sees a little mouse. He catches it, but he can鈥檛 eat it because his teeth are not strong enough. The mouse runs away.
        (line 4) The old man is very angry. He beats his cat.
        (line 5) He says: 鈥淵ou are a fool cat. I will punish you!?the cat is very sad.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章