C ++ Primer(第五版)第十二章练习答案

解答了C++ Primer第五版第十二章的部分练习题,包括StrBlob类的设计与实现、智能指针的使用及文本查询系统的构建等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

12.1.1 节练习

练习 12.1

在此代码的结尾,b1 和 b2 各包含多少个元素?

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

b1 包含 4 个元素;
b2 被销毁。

练习 12.2

编写你自己的 StrBlob 类,包含 const 版本的 front 和 back。

#ifndef STRBLOB_H_
#define STRBLOB_H_

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

class StrBlob
{
public:
    typedef std::vector<std::string>::size_type size_type;
    StrBlob();
    StrBlob(std::initializer_list<std::string> il);
    size_type size() const { return data->size(); }
    bool empty() const {return data->empty()};
    void push_back(const std::string &t) { data->push_back(t); }
    void pop_back();
    std::string &front();
    std::string &back();

private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i, const std::string &msg) const;
};

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

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

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

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

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

#endif

练习 12.3

StrBlob 需要 const 版本的 push_back 和 pop_back 吗?如果需要,添加进去。否则,解释为什么不需要。

可以,但没有必要。

StrBlob 类的数据成员是一个智能指针,当使用 const 版本时,只是将指针本身固定,但指针所指向的对象可以是改变的,所以,可以使用 const 版本的 push_back 和 pop_back。

看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。

虽然在类的具体实现中,数据成员是一个指向 vector< string> 的智能指针;但由于类的封装,在类的使用者看来,数据成员是 vector< string>,他们并不知道具体的实现使用了智能指针
那么当类的使用者声明类的常量对象时,他们期待的结果是vector< string>的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像 push_back 和 pop_back 这样会改变智能指针所指向的 vector< string> 内容的成员函数,我们不应该声明和定义成 const 版本。这样在类的使用者使用类的常对象时,就不能调用 push_back 和 pop_back 成员函数,不能改变智能指针所指向的 vector< string>的内容了,这正好与类的使用者的意图相符。解释来源

练习 12.4

在我们的 check 函数中,没有检查 i 是否大于0。为什么可以忽略这个检查?

因为 size_type 是无符号类型,一定大于等于 0。

练习 12.5

我们未编写接受一个 initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。

优点:可以隐式转换数据类型;

缺点:转换会耗费资源。

12.1.2 节练习

练习 12.6

编写函数,返回一个动态分配的 int 的vector。将此 vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在 vector 元素中。再将 vector 传递给另一个函数,打印读入的值。记得在恰当的时刻 delete vector。

#include<iostream>
#include<vector>

using namespace std;

vector<int>* create()
{
    return new vector<int>;
}

void write(vector<int>*p)
{
    int i;
    while (cin>>i)
    {
        p->push_back(i);
    }
}

void read(vector<int>*p)
{
    for(const auto i:(*p))
    {
        cout << i << " ";
    }
    cout << endl;
}
int main()
{
    auto p = create();
    write(p);
    read(p);
    delete p;
    return 0;
}

练习 12.7

重做上一题,这次使用 shared_ptr 而不是内置指针。

#include<iostream>
#include<vector>
#include<memory>

using namespace std;

shared_ptr<vector<int>> create()
{
    return make_shared<vector<int>>();
}

void write(shared_ptr<vector<int>> p)
{
    int i;
    while (cin>>i)
    {
        p->push_back(i);
    }
}

void read(shared_ptr<vector<int>> p)
{
    for(const auto i:(*p))
    {
        cout << i << " ";
    }
    cout << endl;
}

int main()
{
    auto p = create();
    write(p);
    read(p);
    return 0;
}

练习 12.8

下面的函数是否有错误?如果有,解释错误原因。

  bool b() 
  {
      int* p = new int;
      // ...
      return p;
  }

函数结果将指针转换为 bool 类型,但动态内存没有释放。

练习 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 指向的对象后,由于 r2 是智能指针,当 r2 原指向的对象没有指针指向时,会自动释放动态内存。

12.1.3 节练习

练习 12.10

下面的代码调用了第 413 页中定义的 process 函数,解释此调用是否正确。如果不正确,应如何修改?

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

正确。

参数使用 shared_ptr< int > ( p ),返回一个 p 的临时拷贝,当调用函数时,p 的计数器将增加到 2,而调用结束时,释放临时指针,p 的计数器会减回 1。

练习 12.11

如果我们像下面这样调用 process,会发生什么?

  process(shared_ptr<int>(p.get()));

使用 get 初始化智能指针时,传递的是内置指针,这样初始化的新智能指针与原智能指针 p 是相互独立的,他们的计数器都为 1;

当函数调用结束时,临时拷贝会删除,此时临时的计数器减为 0,会释放它指向的内存,而 p 指针会变成一个空悬指针。

练习 12.12

p 和 q 的定义如下,对于接下来的对 process 的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));

合法。将智能指针作为参数传递给 process;

不合法。内置指针不能隐式转换为智能指针;

不合法。同上;

合法。但调用结束会将 p 变为空悬指针。

练习 12.13

如果执行下面的代码,会发生什么?

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

sp 变成空悬指针。

12.1.4 节练习

练习 12.14

编写你自己版本的用 shared_ptr 管理 connection 的函数。

void end_connection(connection *p) { disconnect(*p); }
void f(destination &d)
{
    connection c = connect(&d);
    shared_ptr<connection> p(&c, end_connection);
}

练习 12.15

重写第一题的程序,用 lambda (参见10.3.2节,第346页)代替 end_connection 函数。

void f(destination &d)
{
    connection c = connect(&d);
    shared_ptr<connection> p(&c, [](connection *p) { disconnect(*p); });
}

12.1.5 节练习

练习 12.16

如果你试图拷贝或赋值 unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。

#include<iostream>
#include<memory>

using namespace std;

int main()
{
    unique_ptr<int> p1(new int(12));
    // unique_ptr<int> p2(p1);
    // unique_ptr<int> p3 = p1;
    unique_ptr<int> p4;
    p4.reset(p1.release());
    return 0;
}
unique_ptr<int> p3 = p1;
declared here
       unique_ptr(const unique_ptr&) = delete;

练习 12.17

下面的 unique_ptr 声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);
(b) IntP p1(pi);
(c) IntP p2(pi2);
(d) IntP p3(&ix);
(e) IntP p4(new int(2048));
(f) IntP p5(p2.get());

非法。应该绑定在动态指针上;

编译通过,后续错误。由于 pi 不是 new 出的动态指针,在离开作用域时,调用 delete 销毁报错;

编译通过,后续错误。绑定了 unique_ptr 后,当销毁智能指针时,会释放内存,则 pi2 会变成空悬指针;

编译通过,后续错误。同(b);

合法。

编译通过,后续错误。出现空悬指针和两次 delete。

练习 12.18

shared_ptr 为什么没有 release 成员?

release 将放弃指针的控制权,并返回指针,用来给 unique_ptr 转移控制权,而 shared_ptr 可以有多个指针同时指向一个对象,可以直接赋值。

12.1.6 节练习

练习 12.19

定义你自己版本的 StrBlobPtr,更新 StrBlob 类,加入恰当的 friend 声明以及 begin 和 end 成员。

#ifndef STRBLOB_H_
#define STRBLOB_H_

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

class StrBlobPtr;

class StrBlob
{
public:
    friend class StrBlobPtr;

    typedef std::vector<std::string>::size_type size_type;
    StrBlob();
    StrBlob(std::initializer_list<std::string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const std::string &t) { data->push_back(t); }
    void pop_back();
    std::string &front();
    std::string &back();
    const std::string& front() const;
	const std::string& back() const;
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){}
StrBlob::StrBlob(std::initializer_list<std::string> il):data(std::make_shared<std::vector<std::string>>(il)){}

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

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

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

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

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; }
    std::string &deref() const;
    StrBlobPtr &incr();
private:
    std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string &) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::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::string &StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr &StrBlobPtr::incr()
{
    check(curr, "increment past end if StrBlobPtr");
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr(*this);
}

StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}    
#endif

练习 12.20

编写程序,逐行读入一个输入文件,将内容存入一个 StrBlob 中,用一个 StrBlobPtr 打印出 StrBlob 中的每个元素。

#include<iostream>
#include<fstream>
#include"StrBlob.h"

using namespace std;

int main()
{
    ifstream in("text.txt");
    StrBlob s;
    string str;
    while (getline(in,str))
    {
        s.push_back(str);
    }
    for (auto beg = s.begin(); beg != s.end();beg.incr())
    {
        cout << beg.deref() << endl;
    }

    return 0;
}

练习 12.21

也可以这样编写 StrBlobPtr 的 deref 成员:

  std::string& deref() const {
  return (*check(curr, "dereference past end"))[curr];
  }

你认为哪个版本更好?为什么?

原版好一些,更容易读懂。

练习 12.22

为了能让 StrBlobPtr 使用 const StrBlob,你觉得应该如何修改?定义一个名为 ConstStrBlobPtr 的类,使其能够指向 const StrBlob。

修改 ConstStrBlobPtr 的构造函数,接受参数 const StrBlob &a,同时将 begin 和 end 成员函数改为 const 成员函数。

#ifndef STRBLOB_H_
#define STRBLOB_H_

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

class ConstStrBlobPtr;

class StrBlob
{
public:
    friend class ConstStrBlobPtr;

    typedef std::vector<std::string>::size_type size_type;
    StrBlob();
    StrBlob(std::initializer_list<std::string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const std::string &t) { data->push_back(t); }
    void pop_back();
    std::string &front();
    std::string &back();
    const std::string& front() const;
	const std::string& back() const;
    ConstStrBlobPtr begin() const;
    ConstStrBlobPtr end() const;

private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){}
StrBlob::StrBlob(std::initializer_list<std::string> il):data(std::make_shared<std::vector<std::string>>(il)){}

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

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

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

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

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; }
    std::string &deref() const;
    ConstStrBlobPtr &incr();
private:
    std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string &) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> ConstStrBlobPtr::check(std::size_t i, const std::string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
    {
        throw std::runtime_error("unbound ConstStrBlobPtr");
    }
    if (i>=ret->size())
    {
        throw std::out_of_range(msg);
    }
    return ret;
}

std::string &ConstStrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

ConstStrBlobPtr &ConstStrBlobPtr::incr()
{
    check(curr, "increment past end if ConstStrBlobPtr");
    ++curr;
    return *this;
}

ConstStrBlobPtr StrBlob::begin()

> 这里是引用

{
    return ConstStrBlobPtr(*this);
}

ConstStrBlobPtr StrBlob::end()
{
    auto ret = ConstStrBlobPtr(*this, data->size());
    return ret;
}    
#endif

12.2.1 节练习

练习 12.23

编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的 char 数组中。重写这个程序,连接两个标准库 string 对象。

#include<iostream>
#include<new>
#include<string>
#include<cstring>

using namespace std;

int main()
{
    char a[] = "aaa";
    char b[] = "bbb";
    char *p1 = new char[strlen(a)+strlen(b)+1];
    strcpy(p1, a);
    strcat(p1, b);
    cout << p1 << endl;
    delete[] p1;

    string str1 = "aaa", str2 = "bbb";
    string *p2 = new string;
    *p2 = str1 + str2;
    cout << *p2 << endl;
    delete p2;
    
    return 0;
}

练习 12.24

编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。

#include<iostream>
#include<cstring>

using namespace std;

int main()
{   
    string str;
    cin >> str;
    char *p = new char[str.size()+1];
    strcpy(p, str.c_str());
    cout << p << endl;
    delete[] p;
    return 0;
}

练习 12.25

给定下面的 new 表达式,你应该如何释放 pa?

  int *pa = new int[10];
delete[] pa;

12.2.2 节练习

练习 12.26

用 allocator 重写第 427 页中的程序。

#include<iostream>
#include<memory>
#include<string>

using namespace std;

int main()
{
    allocator<string> alloc;
    auto const p = alloc.allocate(6);
    auto q=p;
    string s;
    while (cin>>s&&q!=p+6)
    {
        alloc.construct(q++,s);
    }
    
    while (q!=p)
    {
        alloc.destroy(--q);
    }
    alloc.deallocate(p, 6);

    return 0;
}

12.3.1 节练习

练习 12.27

TextQuery 和 QueryResult 类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。

TextQuery.h

#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_

#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的

class TextQuery
{
public:
    using line_no = vector<string>::size_type;
    TextQuery(ifstream &);
    QueryResult query(const string &) const;

private:
    shared_ptr<vector<string>> file; // 输入文件
    // 每个单词到它所在的行号的集合映射
    map<string, shared_ptr<set<line_no>>> wm;
};

// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(ifstream &is) : file(new vector<string>)
{
    string text;
    while (getline(is, text))
    {                             // 对文件中每一行
        file->push_back(text);    // 保存此行文本
        int n = file->size() - 1; // 当前行号
        istringstream line(text); // 将文本分解为单词
        string word;
        while (line >> word)        // 对行中每个单词
        {                           // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
            auto &lines = wm[word]; // lines 是一个 shared_ptr
            if (!lines)             // 在我们第一次遇到这个单词时,此指针为空
            {
                lines.reset(new set<line_no>); // 分配一个新的 set
            }
            lines->insert(n); //将此行号插入 set 中
        }
    }
}

class QueryResult
{
    friend ostream &print(ostream &, const QueryResult &);

public:
    QueryResult(string s, shared_ptr<set<TextQuery::line_no>> p, shared_ptr<vector<string>> f) : sought(s), lines(p), file(f) {}

private:
    string sought;                             // 查询单词
    shared_ptr<set<TextQuery::line_no>> lines; // 出现的行号
    shared_ptr<vector<string>> file;           // 输入文件
};

QueryResult TextQuery::query(const string &sought) const
{
    // 如果未找到 sought,我们将返回一个指向此 set 的指针
    static shared_ptr<set<line_no>> nodata(new set<line_no>);
    // 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
    auto loc = wm.find(sought);
    if (loc==wm.end())
    {
        return QueryResult(sought, nodata, file); // 未找到
    }
    else
    {
        return QueryResult(sought, loc->second, file);
    }
}

ostream &print(ostream &os,const QueryResult &qr)
{
    // 如果找到了单词,打印出现的次数和所以出现的位置
    os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << endl;
    // 打印单词出现的每一行
    for(auto num:*qr.lines) // 对 set 中每个单词
    {
        // 避免行号从 0 开始给用户带来的困惑
        os << "\t(line" << num + 1 << ")" << *(qr.file->begin() + num) << endl;
    }
    return os;
}

#endif

练习 12.28

编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用 vector、map 和 set 容器来保存来自文件的数据并生成查询结果。

#include "TextQuery.h"
#include <iostream>

void runQueries(ifstream &f)
{
    TextQuery tq(f);
    while (true)
    {
        cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(cin >> s) || s == "q")
            break;
        print(cout, tq.query(s)) << endl;
    }
}

int main()
{
    ifstream file("words.txt");
    runQueries(file);
    return 0;
}

练习 12.29

我们曾经用 do while 循环来编写管理用户交互的循环。用 do while 重写本节程序,解释你倾向于哪个版本,为什么?

#include "TextQuery.h"
#include <iostream>

void runQueries(ifstream &f)
{
    TextQuery tq(f);
    do
    {
        cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(cin >> s) || s == "q")
            break;
        print(cout, tq.query(s)) << endl;
    } while (true);
}

int main()
{
    ifstream file("words.txt");
    runQueries(file);
    return 0;
}

12.3.2 节练习

练习 12.30

定义你自己版本的 TextQuery 和 QueryResult 类,并执行 12.3.1节中的 runQueries 函数。

同 12.27 12.28

练习 12.31

如果用 vector 代替 set 保存行号,会有什么差别?哪个方法更好?为什么?

set 可以保证元素唯一性,同一行单词多次出现只记录一次行号。

练习 12.32

重写 TextQuery 和 QueryResult类,用 StrBlob 代替 vector 保存输入文件。

#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_

#include "StrBlob.h"
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>

class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的

class TextQuery
{
public:
    using line_no = std::vector<std::string>::size_type;
    TextQuery(std::ifstream &);
    QueryResult query(const std::string &) const;

private:
    StrBlob file; // 输入文件
    // 每个单词到它所在的行号的集合映射
    std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(std::ifstream &is)
{
    std::string text;
    while (getline(is, text))
    {                                  // 对文件中每一行
        file.push_back(text);          // 保存此行文本
        int n = file.size() - 1;       // 当前行号
        std::istringstream line(text); // 将文本分解为单词
        std::string word;
        while (line >> word)        // 对行中每个单词
        {                           // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
            auto &lines = wm[word]; // lines 是一个 shared_ptr
            if (!lines)             // 在我们第一次遇到这个单词时,此指针为空
            {
                lines.reset(new std::set<line_no>); // 分配一个新的 set
            }
            lines->insert(n); //将此行号插入 set 中
        }
    }
}

class QueryResult
{
    friend std::ostream &print(std::ostream &, const QueryResult &);

public:
    QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, StrBlob f) : sought(s), lines(p), file(f) {}

private:
    std::string sought;                                  // 查询单词
    std::shared_ptr<std::set<TextQuery::line_no>> lines; // 出现的行号
    StrBlob file;                                        // 输入文件
};

QueryResult TextQuery::query(const std::string &sought) const
{
    // 如果未找到 sought,我们将返回一个指向此 set 的指针
    static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>);
    // 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
    auto loc = wm.find(sought);
    if (loc == wm.end())
    {
        return QueryResult(sought, nodata, file); // 未找到
    }
    else
    {
        return QueryResult(sought, loc->second, file);
    }
}

std::ostream &print(std::ostream &os, const QueryResult &qr)
{
    // 如果找到了单词,打印出现的次数和所有出现的位置
    os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << std::endl;
    // 打印单词出现的每一行
    for (auto num : *qr.lines) // 对 set 中每个单词
    {
        ConstStrBlobPtr p(qr.file, num);
        // 避免行号从 0 开始给用户带来的困惑
        os << "\t(line " << num + 1 << ") " << p.deref() << std::endl;
    }
    return os;
}
#endif

12.32.cpp

#include "TextQuery2.h"
#include <iostream>

using namespace std;

void runQueries(ifstream &f)
{
    TextQuery tq(f);
    do
    {
        cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(cin >> s) || s == "q")
            break;
        print(cout, tq.query(s)) << endl;
    } while (true);
}

int main()
{
    ifstream file("words.txt");
    runQueries(file);
    return 0;
}

练习 12.33

在第15章中我们将扩展查询系统,在 QueryResult 类中将会需要一些额外的成员。添加名为 begin 和 end 的成员,返回一个迭代器,指向一个给定查询返回的行号的 set 中的位置。再添加一个名为 get_file 的成员,返回一个 shared_ptr,指向 QueryResult 对象中的文件。

#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_

#include "StrBlob.h"
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>

class QueryResult; // 为定义函数 query 的返回类型,这个定义是必须的

class TextQuery
{
public:
    using line_no = std::vector<std::string>::size_type;
    TextQuery(std::ifstream &);
    QueryResult query(const std::string &) const;

private:
    StrBlob file; // 输入文件
    // 每个单词到它所在的行号的集合映射
    std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

// 读取输入文件并建立单词到行号的映射
TextQuery::TextQuery(std::ifstream &is)
{
    std::string text;
    while (getline(is, text))
    {                                  // 对文件中每一行
        file.push_back(text);          // 保存此行文本
        int n = file.size() - 1;       // 当前行号
        std::istringstream line(text); // 将文本分解为单词
        std::string word;
        while (line >> word)        // 对行中每个单词
        {                           // 如果单词不在 wm 中,以之为下标在 wm 中添加一项
            auto &lines = wm[word]; // lines 是一个 shared_ptr
            if (!lines)             // 在我们第一次遇到这个单词时,此指针为空
            {
                lines.reset(new std::set<line_no>); // 分配一个新的 set
            }
            lines->insert(n); //将此行号插入 set 中
        }
    }
}

class QueryResult
{
    friend std::ostream &print(std::ostream &, const QueryResult &);

public:
    QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, StrBlob f) : sought(s), lines(p), file(f) {}
    std::set<TextQuery::line_no>::iterator begin() const { return lines->begin(); }
    std::set<TextQuery::line_no>::iterator end() const { return lines->end(); }
    std::shared_ptr<StrBlob> get_file() const { return std::make_shared<StrBlob>(file); }

private:
    std::string sought;                                  // 查询单词
    std::shared_ptr<std::set<TextQuery::line_no>> lines; // 出现的行号
    StrBlob file;                                        // 输入文件
};

QueryResult TextQuery::query(const std::string &sought) const
{
    // 如果未找到 sought,我们将返回一个指向此 set 的指针
    static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>);
    // 使用 find 而不是下标运算符来查找单词,避免将单词添加到 wm 中!
    auto loc = wm.find(sought);
    if (loc == wm.end())
    {
        return QueryResult(sought, nodata, file); // 未找到
    }
    else
    {
        return QueryResult(sought, loc->second, file);
    }
}

std::ostream &print(std::ostream &os, const QueryResult &qr)
{
    // 如果找到了单词,打印出现的次数和所有出现的位置
    os << qr.sought << " occers " << qr.lines->size() << " " /* << make_plural(qr.lines->size(), "time", "s") */ << std::endl;
    // 打印单词出现的每一行
    for (auto num : *qr.lines) // 对 set 中每个单词
    {
        ConstStrBlobPtr p(qr.file, num);
        // 避免行号从 0 开始给用户带来的困惑
        os << "\t(line " << num + 1 << ") " << p.deref() << std::endl;
    }
    return os;
}
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值