您的位置:首页 > 编程语言 > C语言/C++

C++primer第五版第十二章学习笔记

2015-11-17 09:11 387 查看
练习12.1:在此代码的结尾,b1和b2各包含多少个元素?

StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
StrBlob的data成员是一个指向string的vector的shared_ptr,因此StrBlob的赋值不会拷贝vector的内容,而是多个StrBlob对象共享同一个vector对象。因此经过一些列操作以后,b1和b2均包含4个string。

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

//my_StrBlob.h
#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <memory>
#include <initializer_list>
#include <stdexcept>
using std::vector;
using std::string;
using std::shared_ptr;
using std::make_shared;
using std::initializer_list;
using std::out_of_range;

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() const { return data->empty(); }
void push_back(const string &t) {data->push_back(t); }
void pop_back();
string& front();
const string& front() const;
string& back();
const string& back() const;

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

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

void StrBlob::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();
}

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

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

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

#endif
//my_StrBlob.cc

#include <iostream>
using std::cout;
using std::endl;
#include "my_StrBlob.h"
int main(int argc, char *argv[])
{
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
cout<<b2.size()<<endl;
}
cout<<b1.size()<<endl;
cout<<b1.front()<<" "<<b1.back()<<endl;
const StrBlob b3=b1;
cout<<b3.front()<<" "<<b3.back()<<endl;
return 0;
}



练习12.3:StrBlob需要const版本的push_back和pop_back吗?如果需要,添加进去。否则,解释为什么不需要。push_back和pop_back的语义分别是向StrBlob对象共享的vector对象添加和从其删除元素。因此,我们不应该为其重载const版本,因为常量StrBlob对象是不应该被允许修改共享vector对象的内容的。

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

check定义为私有成员函数,它只会被StrBlob的成员函数调用,而不会被用户程序调用。因此可以保证传递给它的i的值符合要求,而不必进行检查。

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

未编写一个初始化列表参数的显式构造函数,意味着可以进行列表向StrBlob的隐式类型转换,在需要StrBlob的地方可以使用列表进行替代。而且还可以进行拷贝形式的初始化,这令程序编写更为简单方便。

但这种隐式转换并不总是好的。例如,列表中可能并非都是合法的值。再如,对于接受StrBlob的函数,传递给它一个列表,会创建一个临时的StrBlob对象,用列表对其初始化,然后将其传递给函数,当函数完成后,此对象将被丢弃,再也无法访问了。

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

#include <iostream>
#include <vector>
#include <new>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::nothrow;

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

void read_ints(vector<int> *pv)
{
int v;
while(cin>>v)
pv->push_back(v);
}

void print_ints(vector<int> *pv)
{
for (const auto &v : *pv)
cout<<v<<" ";
cout<<endl;
}

int main(int argc, char *argv[])
{
vector<int> *pv=new_vector();
if(!pv){
cout<<"Insufficient memory"<<endl;
return -1;
}

read_ints(pv);
print_ints(pv);

delete pv;
pv = nullptr;

return 0;
}


练习12.7:重做上一题,这次试用shared_ptr而不是内置指针。

#include <iostream>
#include <vector>
#include <memory>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::shared_ptr;
using std::make_shared;

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

void read_ints(shared_ptr<vector<int>> spv)
{
int v;
while(cin>>v)
spv->push_back(v);
}

void print_ints(shared_ptr<vector<int>> spv)
{
for (const auto &v : *spv)
cout<<v<<" ";
cout<<endl;
}

int main(int argc, char *argv[])
{
auto spv = new_vector();

read_ints(spv);
print_ints(spv);

return 0;
}


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

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


从程序片段看,可以猜测程序员的意图是通过new返回的指针值来区分内存分配成功或失败——成功返回一个合法指针,转换为整型是一个非零值,可转换为bool值true;分配失败,p得到nullptr,其整型值是0,可转换为bool值false.

但普通new调用在分配失败时抛出一个异常bad_alloc,而不是返回nullptr,因此程序不能达到预想目的。

可将new int改为new (nothrow) int 来令new在分配失败时不抛出异常,而是返回nullptr。但这仍然不是一个好方法,应该通过捕获异常或是判断返回的指针来判断true或false,而不是依赖类型转换。

练习12.9:解释下面代码的执行结果:

int *q = new int (42), *r = new int(100);
r = q;
auto q2 = make_shared<int> ()42, r2 = make_shared(100);
r2 = q2;
对于普通指针部分,首先分配了两个int型对象,指针分别保存在p和r中。接下来,将指针q的值赋予了r,这带来两个严重的内存管理问题:

1.首先是一个直接的内存泄露的问题,r和q一样都指向42的内存地址,而r中原来保存的地址——100的内存再无指针管理,变成“孤儿内存”,从而造成内存泄漏。

2.其次是一个“空悬指针“的问题。由于r和q指向同一个动态对象,如果程序编写不当,很容易产生释放了其中一个指针,而继续使用另一个指针的问题。继续使用的指针指向的是一块已经释放的内存,是一个空悬指针,继续读写它指向的内存可能导致程序崩溃甚至系统崩溃的严重问题。

而shared_ptr则可很好地解决这些问题。首先,分配了两个共享的对象,分别由共享指针p2和g2指向,因而它们的引用计数均为1.接下来,将q2赋予r2,。赋值操作会将q2指向的对象地址赋予r2,并将r2原来指向的对象的引用计数减1,将q2指向的对象的引用计数加1。这样,前者的引用计数变为0,其占用的内存空间会被释放,不会造成内存泄露。而后者的引用计数变为2,也不会因为r2和q2之一的销毁而释放它的内存空间,因此也不会造成空悬指针的问题。

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

shared_ptr<int> p(new int(42));
process(shared_ptr<int> (p));
此调用是正确的,利用p创建一个临时的shared_ptr赋予了process的参数ptr,p和ptr都指向相同的int对象,引用计数被正确地置为2。process执行完毕后,ptr被销毁,引用计数减1,这是正确的——只有p指向它。

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

process(shared_ptr<int> (p.get()));
此调用是错误的。p.get()获得一个普通指针,指向p所共享的int对象。利用此指针创建一个shared_ptr,而不是利用p创建一个shared_ptr,将不会形成正确的动态对象共享。编译器会认为p和ptr是使用两个地址(虽然他们相等)创建的两个不相干的shared_ptr,而非共享同一个动态对象。这样,两者的引用计数均为1。当process执行完毕后,ptr的引用计数减为0,所管理的内存地址被释放,而此内存就是p所管理的。p成为一个管理空悬指针的shared_ptr。

练习12.12:p和sp的定义如下,对于接下来的对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));

(a)合法。sp是一个共享指针,指向一个int对象。对process的调用会拷贝sp,传递给process的参数ptr,两者都指向相同的int对象,引用计数变为2。当process执行完毕时,ptr被销毁,引用计数变回1.

(b)合法。new创建了一个int对象,指向它的指针被用来创建了一个shared_ptr,传递给process的参数ptr,引用计数为1。当process执行完毕,ptr被销毁,引用计数变为0,临时int对象因而被销毁。不存在内存泄漏和空悬指针的问题。

(c)不合法。不能将int*转换为shared_ptr<int>。

(d)合法,但是错误的程序。p是一个指向int对象的普通指针,被用来创建一个临时shared_ptr,传递给process的参数ptr,引用计数为1。当process执行完毕,ptr被销毁,引用计数变为0,int对象被销毁。p变为空悬指针。

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

auto sp = make_shared<int> ();
auto p = sp.get();
delete p;
第二行用get获取了sp指向的int对象的地址,第三行用delete释放这个地址。这意味着sp的引用计数仍为1,但其指向的int对象已经被释放。sp成为类似空悬指针的shared_ptr。

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

#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
cout<<"open connection"<<endl;
return connection();
}

void disconnect(connection c)
{
cout<<"close connection"<<endl;
}

// 未使用shared_ptr版本
void f(destination &d)
{
cout<<"manange connect directly"<<endl;
connection c = connect(&d);
// 忘记调用disconnect关闭连接
cout<<endl;
}

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

// 使用shared_ptr的版本
void f1(destination &d)
{
cout<<"use shared_ptr to manage connect"<<endl;
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// 忘记调用disconnect关闭连接
cout<<endl;
}

int main(int argc, char *argv[])
{
destination d;
f(d);
f1(d);

return 0;
}


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

#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
cout<<"open connection"<<endl;
return connection();
}

void disconnect(connection c)
{
cout<<"close connection"<<endl;
}

// 未使用shared_ptr版本
void f(destination &d)
{
cout<<"manange connect directly"<<endl;
connection c = connect(&d);
// 忘记调用disconnect关闭连接
cout<<endl;
}

// 使用shared_ptr的版本
void f1(destination &d)
{
cout<<"use shared_ptr to manage connect"<<endl;
connection c = connect(&d);
shared_ptr<connection> p(&c, [](connection *p){ disconnect(*p);});
// 忘记调用disconnect关闭连接
cout<<endl;
}

int main(int argc, char *argv[])
{
destination d;
f(d);
f1(d);

return 0;
}


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

int ix = 1024, *pi = &ix, *pi2 = new int(20478);
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());

(a)不合法。unique_ptr需要用一个指针初始化,无法将int转换为指针。

(b)合法。可以用一个int*来初始化IntP,但此程序逻辑上是错误的。它用一个普通int变量的地址初始化p1,p1销毁时会释放此内存,其行为是未定义的。

(c)合法。用一个指向动态分配的对象的指针来初始化IntP是正确的。

(d)合法。但存在与(b)相同的问题。

(e)合法。与(c)类似。

(f)合法。但用p2管理的对象的地址来初始化p5,造成两个unique_ptr指向相同的内存地址。当其中一个unique_ptr被销毁(或调用reset释放对象)时,该内存被释放,另一个unique_ptr变为空悬指针。

练习12.18:shared_ptr为什么没有release成员?

unique_ptr独占对象的所有权,不能拷贝和赋值。release操作是用来将对象的所有权转移给另一个unique_ptr的。

而多个shared_ptr可以共享对象的所有权。需要共享时,可以简单拷贝和赋值。因此,并不需要release这样的操作来转移所有权。

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

//my_StrBlob.h
#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <memory>
#include <initializer_list>
#include <stdexcept>
using std::vector;
using std::string;
using std::shared_ptr;
using std::make_shared;
using std::weak_ptr;
using std::initializer_list;
using std::runtime_error;
using std::out_of_range;

// 提前声明,StrBlob中的友类声明所需
class StrBlobPtr;

class StrBlob {
friend class StrBlobPtr;
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<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();
// 元素访问
string& front();
const string& front() const;
string& back();
const string& back() const;

// 提供给StrBlobPtr的接口
StrBlobPtr begin();    //定义了StrBlobPtr后才能定义这两个函数
StrBlobPtr end();
private:
shared_ptr<vector<string>> data;
// 如果data[i]不合法,抛出一个异常
void check(size_type i, const string &msg) const;
};

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

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

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

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

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

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

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

//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr {
friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
StrBlobPtr() : curr(0) { }
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) { }

string& deref() const;
StrBlobPtr& incr();      //前缀递增
StrBlobPtr& decr();      //前缀递减
private:
// 若检查成功,check返回一个指向vector的shared_ptr
shared_ptr<vector<string>> check(size_t, const string&) const;
// 保存一个weak_ptr,意味着底层vector可能会被销毁
weak_ptr<vector<string>> wptr;
size_t curr;
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
auto ret = wptr.lock();    //vector还存在吗?
if (!ret)
throw runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;               //否则,返回指向vector的shared_ptr
}

inline string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];     //(*p)是对象所指的vector
}

// 前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
// 如果curr已经指向容器的尾后位置,就不递增它
check(curr, "increment psat end of StrBlobPtr");
++curr;             //推进当前位置
return *this;
}

// 前缀递减,返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{
// 如果curr已经为0,递减它就会产生一个非法下标
--curr;             //递减当前位置
check(-1, "decrement past begin of StrBlobPtr");
return *this;
}

// StrBlob的begin和end成员的定义
inline StrBlobPtr StrBlob::begin()
{
return StrBlobPtr(*this);
}

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

// StrBlobPtr的比较操作
inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
if (l == r)
return (!r || lhs.curr == rhs.curr);
else
return false;
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
return !eq(lhs, rhs);
}
#endif

//main.cc
#include <iostream>
using std::cout;
using std::endl;
#include "my_StrBlob.h"

int main(int argc, char **argv)
{
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
cout<<b2.size()<<endl;
}

cout<<b1.size()<<endl;
cout<<b1.front()<<" "<<b1.back()<<endl;

const StrBlob b3 = b1;
cout<<b1.front()<<" "<<b3.back()<<endl;

for (auto it = b1.begin(); neq(it, b1.end()); it.incr())
cout<<it.deref()<<endl;

return 0;
}


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

#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::ifstream;

#include "my_StrBlob.h"

int main(int argc, char **argv)
{
ifstream in(argv[1]);
if (!in) {
cout<<"Open input file failed"<<endl;
return -1;
}

StrBlob b;
string s;
while (getline(in, s))
b.push_back(s);

for (auto it = b.begin(); neq(it, b.end()); it.incr())
cout<<it.deref()<<endl;

return 0;
}


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

std::string& deref() const
{ return (*check(curr, "dereference past end"))[curr];}
你认为哪个版本更好?为什么?

书中的方式更好一些。将合法性检查与元素获取的返回语句分离开来,代码更清晰易读,当执行到第二条语句时,已确保p是存在的vector,curr是合法的位置,可安全地获取元素并返回。这种清晰的结构也更有利于修改不同的处理逻辑。

而本题中的版本将合法性检查和元素获取及返回合在一条语句中,不易读,也不易修改。

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

#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
using std::string;

int main(int argc, char *argv[])
{
const char *c1 = "Hello";
const char *c2 = "World";

char *r = new char[strlen(c1)+strlen(c2)+1];
strcpy(r, c1);
strcat(r ,c2);
cout<<r<<endl;
string s1 = "hello";
string s2 = "world";
strcpy(r, (s1+s2).c_str());
cout<<r<<endl;
delete [ ] r;
return 0;
}


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

#include <iostream>
#include <cstring>
using std::cin;
using std::endl;
using std::cout;

int main(int argc, char *argv[])
{
char c;
char *r = new char[20];
int l = 0;
while(cin.get(c)) {
if (isspace(c))
break;
r[l++] = c;
if (l == 20) {
cout<<"reach the top size of the array"<<endl;
break;
}
}
r[l] = 0;
cout<<r<<endl;

delete [] r;

return 0;
}


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

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

练习12.26:用allocator重写第427页中的程序。

#include <iostream>
#include <string>
#include <memory>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::allocator;

int main(int argc, char **argv)
{
allocator<string> alloc;
auto const p = alloc.allocate(100);
string s;
string *q = p;
while (cin >> s && q != p + 100)
alloc.construct(q++, s);
const size_t size = q - p;

for (size_t i = 0; i < size; ++i)
cout<<p[i]<<" "<<endl;

while (q != p)
alloc.destroy(--q);
alloc.deallocate(p, 100);

return 0;
}


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

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <cstdlib>
using std::cin;
using std::cerr;
using std::cout;
using std::endl;
using std::ostream;
using std::ifstream;
using std::string;
using std::vector;
using std::map;
using std::set;
using std::istringstream;

using line_no = vector<string>::size_type;
vector<string> file;			//文件每行内容
map<string, set<line_no>> wm;	//单词到行号set的映射

string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr > 1)? word + ending : word;
}

string cleanup_str(const string &word)
{
string ret;
for (auto it = word.begin(); it != word.end(); ++it) {
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}

void input_text(ifstream &is)
{
string text;
while (getline(is, text)) {				//对文件中每一行
file.push_back(text);				//保存此行文本
int n = file.size() - 1;			//当前行号
istringstream line(text);			//将文本分解为单词
string word;
while (line>>word) {
// 将当前行号插入到其行号set中
// 如果单词不在wm中,以之为下标在wm中添加一项
wm[cleanup_str(word)].insert(n);
}
}
}

ostream &query_and_print(const string &sought, ostream &os)
{
// 使用find而不是下标运算符来查找单词,避免将单词添加到wm中
auto loc = wm.find(sought);
if (loc == wm.end()) {					//未找到
os<<sought<<" occurs 0 times"<<endl;
}
else {
auto lines = loc->second;			//行号set
os<<sought<<" occurs "<<lines.size()<<" times"<<endl;
for (auto num : lines)				//打印单词出现的每一行
os<<"\t(No "<< num+1<<"lines) "<<*(file.begin()+num)<<endl;
}
return os;
}

void runQueries(ifstream &infile)
{
// infile是一个ifstream,指向我们要查询的文件
input_text(infile);						//读入文本并建立查询map
// 与用户交互:提示用户输入要查询的单词,完成查询并打印结果
while (true) {
cout<<"enter word to look for or q to quit: ";
string s;
// 若遇到文件结尾或用户输入了q时循环终止
if (!(cin>>s) || s == "q") break;
// 指向查询并打印结果
query_and_print(s, cout)<<endl;
}
}

// 程序接受唯一的命令行参数,表示文本文件名
int main(int argc, char **argv)
{
// 打开要查询的文件
ifstream infile;
// 打开文件失败,程序异常退出
if (argc < 2|| !(infile.open(argv[1]), infile)) {
cerr<<"No input file!"<<endl;
return EXIT_FAILURE;
}
runQueries(infile);
return 0;
}


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

do {
cout<<"enter word to look for ,or q to quit: ";
string s;
if (!(cin>>s) || s == "q") break;
query_and_print(s, cout)<<endl;
} while (true);


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

//TextQuery.h
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <vector>
#include <map>
#include <set>
#include <memory>
#include <string>
#include "QueryResult.h"

class QueryResult;
class TextQuery {
public:
typedef std::vector<std::string>::size_type line_no;
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
void display_map();
private:
std::shared_ptr<std::vector<std::string>> file;
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
static std::string cleanup_str(const string &word);<pre name="code" class="cpp">//QueryResult.h
#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <iostream>
#include "my_StrBlob.h"

class QueryResult {
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
typedef std::vector<std::string>::size_type line_no;
typedef std::set<line_no>::const_iterator line_it;
QueryResult(std::string s,
std::shared_ptr<std::set<line_no>> p,
StrBlob f):
sought(s), lines(p), file(f) { }
std::set<line_no>::size_type size() const  { return lines->size(); }
line_it begin() const { return lines->cbegin(); }
line_it end() const   { return lines->cend(); }
StrBlob get_file() { return file; }
private:
std::string sought;
std::shared_ptr<std::set<line_no>> lines;
StrBlob file;
};

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


};#endif



//TextQuery.cc
#include "TextQuery.h"
#include "make_plural.h"

#include <cstddef>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstring>
#include <utility>

using std::size_t;
using std::shared_ptr;
using std::istringstream;
using std::string;
using std::getline;
using std::vector;
using std::map;
using std::set;
using std::cerr;
using std::cout;
using std::cin;
using std::ostream;
using std::endl;
using std::ifstream;
using std::ispunct;
using std::tolower;
using std::strlen;
using std::pair;

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) {
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
}

string TextQuery::cleanup_str(const string &word)
{
string ret;
for (auto it = word.begin(); it != word.end(); ++it) {
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}

QueryResult
TextQuery::query(const string &sought) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
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<<" occurs "<<qr.lines->size()<<" "<<make_plural(qr.lines->size(), "times", "s")<<endl;
for (auto num : *qr.lines)
os<<"\t(line"<<num+1<<")"<<*(qr.file->begin()+num)<<endl;
return os;
}

void TextQuery::display_map()
{
auto iter = wm.cbegin(), iter_end = wm.cend();
for ( ; iter != iter_end; ++iter) {
cout << "word: " << iter->first << " {";
auto text_locs = iter->second;
auto loc_iter = text_locs->cbegin(), loc_iter_end = text_locs->cend();
while (loc_iter != loc_iter_end)
{
cout << *loc_iter;
if (++loc_iter != loc_iter_end)
cout << ", ";
}

cout << "}\n";
}
cout << endl;
}


//make_plural.h
#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H
#include <string>
using std::string;

string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr > 1)? word + ending : word;
}
#endif


//QueryResult.h
#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <iostream>

class QueryResult {
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
typedef std::vector<std::string>::size_type line_no;
typedef std::set<line_no>::const_iterator line_it;
QueryResult(std::string s,
std::shared_ptr<std::set<line_no>> p,
std::shared_ptr<std::vector<std::string>> f):
sought(s), lines(p), file(f) { }
std::set<line_no>::size_type size() const  { return lines->size(); }
line_it begin() const { return lines->cbegin(); }
line_it end() const   { return lines->cend(); }
std::shared_ptr<std::vector<std::string>> get_file() { return file; }
private:
std::string sought;
std::shared_ptr<std::set<line_no>> lines;
std::shared_ptr<std::vector<std::string>> file;
};

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


//querymain.cc
#include <string>
using std::string;

#include <fstream>
using std::ifstream;

#include <iostream>
using std::cin; using std::cout; using std::cerr;
using std::endl;

#include <cstdlib>

#include "TextQuery.h"
#include "make_plural.h"

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(int argc, char **argv)
{
ifstream infile;
if (argc < 2 || !(infile.open(argv[1]), infile)) {
cerr << "No input file!" << endl;
return EXIT_FAILURE;
}
runQueries(infile);
return 0;
}


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

vector更好。因为,虽然vector不会维护元素值的序,set会维护关键字的序,但我们是逐行读入文本的,因此每个单词出现的行号是自然按升序加入到容器中的,不必特意用关联容器来保证行号的升序。从性能角度,set是基于红黑树实现的,插入操作时间复杂度为O(logn),而vector的push_back可达到常量时间。

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

//QueryResult.h
#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <iostream>
#include "my_StrBlob.h"

class QueryResult {
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
typedef std::vector<std::string>::size_type line_no;
typedef std::set<line_no>::const_iterator line_it;
QueryResult(std::string s,
std::shared_ptr<std::set<line_no>> p,
StrBlob f):
sought(s), lines(p), file(f) { }
std::set<line_no>::size_type size() const  { return lines->size(); }
line_it begin() const { return lines->cbegin(); }
line_it end() const   { return lines->cend(); }
StrBlob get_file() { return file; }
private:
std::string sought;
std::shared_ptr<std::set<line_no>> lines;
StrBlob file;
};

std::ostream &print(std::ostream&, const QueryResult&);
#endif
//TextQuery.h
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <vector>
#include <map>
#include <set>
#include <memory>
#include <string>
#include <fstream>
#include "QueryResult.h"

class QueryResult;
class TextQuery {
public:
typedef std::vector<std::string>::size_type line_no;
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
void display_map();
private:
StrBlob file;
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
static std::string cleanup_str(const string &word);
};
#endif
//TextQuery.cc
#include "TextQuery.h"
#include "make_plural.h"

#include <cstddef>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstring>
#include <utility>

using std::size_t;
using std::shared_ptr;
using std::istringstream;
using std::string;
using std::getline;
using std::vector;
using std::map;
using std::set;
using std::cerr;
using std::cout;
using std::cin;
using std::ostream;
using std::endl;
using std::ifstream;
using std::ispunct;
using std::tolower;
using std::strlen;
using std::pair;

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) {
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
}

string TextQuery::cleanup_str(const string &word)
{
string ret;
for (auto it = word.begin(); it != word.end(); ++it) {
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}

QueryResult
TextQuery::query(const string &sought) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
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<<" occurs "<<qr.lines->size()<<" "<<make_plural(qr.lines->size(), "times", "s")<<endl;
for (auto num : *qr.lines)
os<<"\t(line"<<num+1<<")"<<qr.file.begin().deref(num)<<endl;
return os;
}

void TextQuery::display_map()
{
auto iter = wm.cbegin(), iter_end = wm.cend();
for ( ; iter != iter_end; ++iter) {
cout << "word: " << iter->first << " {";
auto text_locs = iter->second;
auto loc_iter = text_locs->cbegin(), loc_iter_end = text_locs->cend();
while (loc_iter != loc_iter_end)
{
cout << *loc_iter;
if (++loc_iter != loc_iter_end)
cout << ", ";
}

cout << "}\n";
}
cout << endl;
}


//StrBlob.h
#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <memory>
#include <initializer_list>
#include <stdexcept>

using namespace std;

// 提前声明,StrBlob中的友类声明所需
class StrBlobPtr;

class StrBlob {
friend class StrBlobPtr;
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<string> il);
StrBlob(vector<string> *p);
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();
// 元素访问
string& front();
const string& front() const;
string& back();
const string& back() const;

// 提供给StrBlobPtr的接口
StrBlobPtr begin();    //定义了StrBlobPtr后才能定义这两个函数
StrBlobPtr end();
// const版本
StrBlobPtr begin() const;
StrBlobPtr end() const;
private:
shared_ptr<vector<string>> data;
// 如果data[i]不合法,抛出一个异常
void check(size_type i, const string &msg) const;
};

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

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

inline string& StrBlob::front()
{
// 如果vector为空,check会抛出一个异常
check(0, "front on empty StrBlob");
return data->front();
}

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

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

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

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

//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr {
friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
StrBlobPtr() : curr(0) { }
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) { }
StrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) { }

string& deref() const;
string& deref(int off) const;
StrBlobPtr& incr();      //前缀递增
StrBlobPtr& decr();      //前缀递减
private:
// 若检查成功,check返回一个指向vector的shared_ptr
shared_ptr<vector<string>> check(size_t, const string&) const;
// 保存一个weak_ptr,意味着底层vector可能会被销毁
weak_ptr<vector<string>> wptr;
size_t curr;
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
auto ret = wptr.lock();    //vector还存在吗?
if (!ret)
throw runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;               //否则,返回指向vector的shared_ptr
}

inline string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];     //(*p)是对象所指的vector
}

inline string& StrBlobPtr::deref(int off) const
{
auto p = check(curr + off, "dereference past end");
return (*p)[curr + off];	//(*p)是对象所指的vector
}

// 前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
// 如果curr已经指向容器的尾后位置,就不递增它
check(curr, "increment psat end of StrBlobPtr");
++curr;             //推进当前位置
return *this;
}

// 前缀递减,返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{
// 如果curr已经为0,递减它就会产生一个非法下标
--curr;             //递减当前位置
check(-1, "decrement past begin of StrBlobPtr");
return *this;
}

// StrBlob的begin和end成员的定义
inline StrBlobPtr StrBlob::begin()
{
return StrBlobPtr(*this);
}

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

// const版本
inline StrBlobPtr StrBlob::begin() const
{
return StrBlobPtr(*this);
}

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

// StrBlobPtr的比较操作
inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
if (l == r)
// 则两个指针都是空,或者指向相同的元素时,他们相等
return (!r || lhs.curr == rhs.curr);
else
return false;   	//若指向不同vector,则不可能相等
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
return !eq(lhs, rhs);
}
#endif


//make_plural.h
#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H
#include <string>
using std::string;

string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr > 1)? word + ending : word;
}
#endif


//mainquery.cc
#include <string>
using std::string;

#include <fstream>
using std::ifstream;

#include <iostream>
using std::cin; using std::cout; using std::cerr;
using std::endl;

#include <cstdlib>

#include "TextQuery.h"
#include "make_plural.h"

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(int argc, char **argv)
{
ifstream infile;
if (argc < 2 || !(infile.open(argv[1]), infile)) {
cerr << "No input file!" << endl;
return EXIT_FAILURE;
}
runQueries(infile);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++primer