c++ primer 第五版第十二章

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

StrBlob b1;
{
    strBlob b2 = {"a", "an", "the"};
    b1 = b2;
    b2.push_back("about");
}
// b1 包含3个元素,b2包含4个元素。

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

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

StrBlob::StrBlob() : data(shared_ptr<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(shared_ptr<vector<string>> il) {}
StrBlob::void check (size_type i, const string& msg) const
{
    if (i >= data.size()) {
        throw out_of_range(msg);
    }
}
string& StrBlob::front()
{
    check (0, "front on empty StrBlob");
    return data->front;
}

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

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

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

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

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

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

如果想加的话确实可以实现,但并没有什么合理的理由。加了编译器也不会报错,因为其并未改变指针(不是指针所指的数据)。const指针完全合法。

https://stackoverflow.com/questions/20725190/operating-on-dynamic-memory-is-it-meaningful-to-overload-a-const-memeber-functi

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

因为size_type是unsigned类型,本身就大于等于0。即使将一个负数给其赋值,它还是大于0.

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

"explicit"关键字将会阻止initializer_list 自动转换为StrBlob。这个设计不容易使用,但可以防止使用出错。

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

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

void readToVector(vector<int>* ivec)
{
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;
        exit(1);
    }

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

void printVector(vector<int>* ivec)
{
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;
        exit(1);
    }

    for (auto i : *ivec) {
        cout << i << " ";
    }
    cout << endl;
}

void test1206()
{
    vector<int> *ivec = applicateVector();
    if (nullptr == ivec) {
        cout << "vector is illegal." << endl;
        exit(1);
    }
    readToVector (ivec);
    printVector (ivec);

    delete ivec;
}

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

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

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

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

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

    for (auto i : *ivec) {
        cout << i << " ";
    }
    cout << endl;
}

void test1207()
{
    shared_ptr<vector<int>> p;
    p = applicateVectorPtr();
    readToVectorPtr(p);
    printVectorPtr(p);
}

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

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

函数定义的返回值类型与实际的返回类型不匹配。int* 会转换为bool类型。这会导致p将没有机会被delete,最终造成内存泄漏。

12.09 解释下面代码执行的结果:

int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
  1. r = q,则r原来的空间没有指针指向,因此也不会被释放,造成内存泄漏。
  2. 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<int>(p.get()));

传给process的是由p.get()初始化的一个新的shared_ptr,与p指向同一块内存。在process函数结束时,新的shared_ptr被释放,p就变成了一个空悬指针。

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.
(c) process (p);			// 不合法,不能将内置指针隐式转换为shared_ptr.
(d) process (shared ptr<int>(p));	// 合法。但不建议这样使用,智能指针和常量指针混用容易引起问题,比如有可能被释放两次。

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

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

使用sp初始化p,p和sp指向同一块内存。delete p之后,这块内存被释放,sp也会被释放,导致同一块内存被释放两次。

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

struct destination
{
    destination(const string& ip, const string& port) : m_ip(ip), m_port(port) {}
    string m_ip, m_port;
};
struct connection
{
    connection(string& ip, string& port) : m_ip(ip), m_port(port) {}
    string m_ip, m_port;
};

connection connect(destination* pdes)
{
    shared_ptr<connection> spConn (new connection(pdes->m_ip, pdes->m_port));
    cout << "create a connection " << spConn.use_count() << " to " << pdes->m_ip << " : " << pdes->m_port << endl;
    return *spConn;
}

void disconnection(connection pConn)
{
    cout << "close the connection " << pConn.m_ip << " : " << pConn.m_port << endl;
}

void end_connection(connection* p)
{
    disconnection(*p);
}

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

void test1214()
{
    destination d ("192.21.4.110", "8088");
    f (&d);
}

12.15 重写第一题的程序,用lambda代替end_connection函数。

// 将函数f改为:
void f(destination* d)
{
    connection c = connect(d);
    shared_ptr<connection> p (&c, [](connection *p) {disconnection(*p);});
}

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

void test1215()
{
    unique_ptr<int> p1 (new int(42));
    unique_ptr<int> p2 = p1;
    unique_ptr<int> p3 (p1);
}
// 赋值的错误提示是:error: use of deleted function 'std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
// 拷贝的错误提示:error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'

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返回的指针上。
(b) IntP p1 (pi);		// 编译时无报错,在我的编译器上也可以正确运行。但不建议使用,因为使用智能指针unique_ptr,在检测到指针超出范围时,编译器会调用delete去释放,但pi不是又new分配的,因此就不能使用delete去释放,所以当p1非法时,就会由操作系统抛出错误,造成运行时错误。
(c) IntP p2 (pi2);		// 可以使用,但有可能在运行时产生空悬指针pi2,因为运行时,p2所指空间可能被释放,pi2就成为了空悬指针。
(d) IntP p3 (&ix);		// 同b,编译器不会报错,但无法使用delete释放。
(e) IntP p4 (new int(2048));	// 合法,推荐使用
(f) IntP p5 (p2.get());			// 不合法,使用get初始化一个智能指针,p2和p5指向同一块内存,当指针非法,智能指针会自动delete,此时这块内存会被二次delete。

12.18 shared_ptr为什么没有release成员?

release的作用是交出指针指向对象的控制权,但是shared_ptr是多对一的关系,一个shared_ptr交出控制权,其它shared_ptr依旧可以控制这个对象。因此这个方法对shared_ptr无意义。

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

class StrBlobPtr;

class StrBlob
{
    friend class StrBlobPtr;
public:
    StrBlobPtr begin();
    StrBlobPtr end();
    typedef vector<string>::size_type size_type;

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

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

class StrBlobPtr {
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr (StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    string& deref() const;
    StrBlobPtr& incr();     // 前缀递增
private:
    // 若检查成功,check返回一个指向vector的shared_ptr.
    shared_ptr<vector<string>> check(size_t, const string&) const;
    weak_ptr<vector<string>> wptr;      // 保存一个weak_ptr,意味着vector有可能被销毁。
    size_t curr;        // 在数组中当前的位置
};
StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr (*this);
}

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

// begin()和end()函数的定义必须在StrBlobPtr类定义之后,否则会报错(StrBlobPtr是不完全类型),最好的方法是使用.h文件实现声明和定义分离。

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

void test1220()
{
    ifstream ifs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/infile.txt");
    StrBlob strb;
    string line;
    StrBlobPtr pbeg, pend;

    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;
    }
}

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

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

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

原始的版本更好,其更易读。

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

class ConstStrBlobPtr {
public:
    ConstStrBlobPtr() : curr(0) {}
    ConstStrBlobPtr (const StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    string& deref() const;
    ConstStrBlobPtr& incr();     // 前缀递增
    bool operator!= (const ConstStrBlobPtr& pblob) { return pblob.curr != curr; }
private:
    // 若检查成功,check返回一个指向vector的shared_ptr.
    shared_ptr<vector<string>> check(size_t, const string&) const;
    weak_ptr<vector<string>> wptr;      // 保存一个weak_ptr,意味着vector有可能被销毁。
    size_t curr;        // 在数组中当前的位置
};

要在StrBlob类中加:
friend class ConstStrBlobPtr;
ConstStrBlobPtr cbegin();
ConstStrBlobPtr cend();

测试:
void test1222()
{
    ifstream ifs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/infile.txt");
    StrBlob strb;
    string line;
    ConstStrBlobPtr pcbeg, pcend;

    if (ifs) {
        while (getline(ifs, line)) {
            strb.push_back(line);
        }
    }
    for (pcbeg=strb.cbegin(), pcend=strb.cend(); pcbeg != pcend; pcbeg.incr()) {
        cout << pcbeg.deref() << endl;
    }
}

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

void test1223()
{
    string s1 = "Hello ";
    string s2 = "world";

    int len1 = s1.size(), len2 = s2.size();
    char* c = new char[len1+len2+1]();

    strcat (c, s1.c_str());
    strcat (c, s2.c_str());

    cout << c << endl;
    delete[] c;

    string str = s1 + s2;
    cout << str << endl;
}

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

void test1224()
{
    string str;

    cout << "Please input a string: ";
    getline (cin, str);

    char* cArray = new char[str.size()+1]();
    strcat(cArray, str.c_str());

    cout << cArray << endl;
    delete[] cArray;
}
// 处理变长输入:输入之后再根据输入字符串的长度分配内存,不会超出。

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

int *pa = new int[10];
// 释放:
// delete[] pa;

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

void test1226()
{
    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);
}

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

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

private:
    std::shared_ptr<std::vector<std::string>> sp_text;
    // 每个单词到它所出现的行号的映射
    std::map<std::string, std::shared_ptr<std::set<line_no>>> sp_dictionary;
};

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


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

std::ostream& print (std::ostream&, const QueryResult&);

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

#include <map>
#include <set>
#include <sstream>
#include <algorithm>
#include <ctype.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <fstream>

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

void test1228()
{
    std::ifstream ifs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/textQuery.cpp");
    std::ofstream ofs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/text.txt");
    string tmp_line;
    vector<string> line_str;
    decltype (line_str.size()) lineNo{0};
    std::map <string, std::set<unsigned int>> dictionary;

    while (getline(ifs, tmp_line)) {
        line_str.push_back(tmp_line);
        ++ lineNo;
        std::istringstream line_stream (tmp_line);
        string unique_word, text;
        for (unique_word, text; line_stream >> text; unique_word.clear()) {
            // 把每行的以空格隔开的单词拷贝到unique_word中,标点符号除外。
            std::remove_copy_if (text.begin(), text.end(), back_inserter(unique_word), ispunct);
            // 把每个出现的单词和行号都放入map中,如果已经存在,则只插入行号。
            dictionary[unique_word].insert(lineNo);
        }
    }

    string s;
    while (true) {
        std::cout << "enter a word to search, or q to quit: ";
        if (!(std::cin >> s) || (s == "q")) {
            break;
        }
        auto found_iter = dictionary.find(s);
        if (found_iter != dictionary.end()) {
            std::cout << s << " occurs " << found_iter->second.size() << " times." << std::endl;
            for (auto i : found_iter->second) {
                std::cout << "\t(line" << i << ")" << line_str.at(i-1) << std::endl;
            }
        }
        else {
            std::cout << "Not found it." << std::endl;
        }
    }
}

这段代码,如果使用using namespace std; 则remove_copy_if函数就会报错,提示无法解析的重载函数类型。

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

// 用do...while改写如下:
do {
        std::cout << "enter a word to search, or q to quit: ";
        if (!(std::cin >> s) || (s == "q")) {
            break;
        }
        auto found_iter = dictionary.find(s);
        if (found_iter != dictionary.end()) {
            std::cout << s << " occurs " << found_iter->second.size() << " times." << std::endl;
            for (auto i : found_iter->second) {
                std::cout << "\t(line" << i << ")" << line_str.at(i-1) << std::endl;
            }
        }
        else {
            std::cout << "Not found it." << std::endl;
        }
    } while (true);
// 使用do...while看起来简洁一点。

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

// 头文件如12.27题所列,cpp文件如下:
#include "textQuery.h"
using namespace std;

TextQuery::TextQuery(std::ifstream& ifs) : sp_text (new vector<string>)
{
    string text;

    while (getline(ifs, text)) {
        sp_text->push_back(text);
        int n = sp_text->size() - 1;    // 当前行号
        istringstream line (text);
        string word;
        while (line >> word) {
            auto &lines = sp_dictionary[word];

            if (!lines) {
                lines.reset (new set<line_no>);
            }
            lines->insert(n);
        }
    }
}

QueryResult TextQuery::query(const std::string& word) const
{
    // 如果未找到sought, 返回一个指向此set的指针。
    static shared_ptr<set<line_no>> nodata (new set<line_no>);
    auto loc = sp_dictionary.find(word);

    if (loc == sp_dictionary.end()) {
        return QueryResult(word, nodata, sp_text);
    }
    else {
        return QueryResult(word, loc->second, sp_text);
    }
}

ostream& print(ostream& os, const QueryResult& qr)
{
    os << qr.sought << " occurs " << qr.lines->size() << " times." << endl;

    for (auto i : *qr.lines) {
        os << "\t(line " << i+1 << qr.file->at(i) << endl;
    }
    return os;
}

void runQueries (ifstream &infile)
{
    TextQuery tq (infile);

    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 ifs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/textQuery.cpp");
    runQueries(ifs);
    return 0;
}

这个程序未过滤符号,因此查找的时候如果前后有标点符号,也会算另一个单词。如本文件中的text,本应有两处,但第二处插入到map中是text)),因此实际只能找到一个text。过滤标点符号的版本如12.28题。

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

set更好,因为vector保存不会排序,查找结果便不能按照行号的由小到大顺序显示了。

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

如下题 ~

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

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

private:
    StrBlob sp_text;
    // 每个单词到它所出现的行号的映射
    std::map<std::string, std::shared_ptr<std::set<StrBlob::size_type>>> sp_dictionary;
};

class QueryResult
{
public:
     friend std::ostream& print (std::ostream&, const QueryResult&);
public:
    std::set<StrBlob::size_type>::iterator begin() const { return lines->begin(); }
    std::set<StrBlob::size_type>::iterator end() const { return lines->end(); }
    std::shared_ptr<StrBlob> getFile() const { return make_shared<StrBlob>(file); }
    QueryResult(const std::string& s,
                std::shared_ptr<std::set<StrBlob::size_type>> sp_set,
                StrBlob sb):
            sought (s), lines (sp_set), file (sb) {}

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

std::ostream& print (std::ostream&, const QueryResult&);
.cpp文件
#include "textQuery_StrBlob.h"
using namespace std;

TextQuery::TextQuery(std::ifstream& ifs)
{
    string text;
    StrBlob::size_type n = 0;
    while (getline(ifs, text)) {
        sp_text.push_back(text);
        n = sp_text.size() - 1;    // 当前行号
        istringstream line (text);
        string word;
        while (line >> word) {
            auto &lines = sp_dictionary[word];

            if (!lines) {
                lines.reset (new std::set<StrBlob::size_type>);
            }
            lines->insert(n);
        }
    }
}

QueryResult TextQuery::query(const std::string& word) const
{
    // 如果未找到sought, 返回一个指向此set的指针。
    static shared_ptr<set<StrBlob::size_type>> nodata (new set<StrBlob::size_type>);
    auto loc = sp_dictionary.find(word);

    if (loc == sp_dictionary.end()) {
        return QueryResult(word, nodata, sp_text);
    }
    else {
        return QueryResult(word, loc->second, sp_text);
    }
}

ostream& print(ostream& os, const QueryResult& qr)
{
    os << qr.sought << " occurs " << qr.lines->size() << " times." << endl;

    for (auto iter = qr.begin(); iter != qr.end(); ++ iter) {
        ConstStrBlobPtr p (*qr.getFile(), *iter);
        os << "\t(line " << *iter+1 << ") " << p.deref() << endl;
    }
    return os;
}

void runQueries (ifstream &infile)
{
    TextQuery tq (infile);

    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 ifs ("C:/Users/tutu/Documents/code/cpp_primer/ch12/textQuery.cpp");
    runQueries(ifs);
    return 0;
}
posted @ 2018-09-06 14:35  安月月  阅读(2180)  评论(1编辑  收藏  举报