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.
发布了76 篇原创文章 · 获赞 44 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24739717/article/details/104429350