C++primer第五版第十二章学习笔记
2015-11-17 09:11
387 查看
练习12.1:在此代码的结尾,b1和b2各包含多少个元素?
练习12.2 编写你自己的StrBlob类,包含const版本的front和back。
练习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。
练习12.7:重做上一题,这次试用shared_ptr而不是内置指针。
练习12.8:下面的函数是否有错误?如果有,解释错误原因。
从程序片段看,可以猜测程序员的意图是通过new返回的指针值来区分内存分配成功或失败——成功返回一个合法指针,转换为整型是一个非零值,可转换为bool值true;分配失败,p得到nullptr,其整型值是0,可转换为bool值false.
但普通new调用在分配失败时抛出一个异常bad_alloc,而不是返回nullptr,因此程序不能达到预想目的。
可将new int改为new (nothrow) int 来令new在分配失败时不抛出异常,而是返回nullptr。但这仍然不是一个好方法,应该通过捕获异常或是判断返回的指针来判断true或false,而不是依赖类型转换。
练习12.9:解释下面代码的执行结果:
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函数,解释此调用是否正确。如果不正确,应如何修改?
练习12.11:如果我们像下面这样调用process,会发生什么?
练习12.12:p和sp的定义如下,对于接下来的对process的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因。
(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:如果执行下面的代码,会发生什么?
练习12.14:编写你自己版本的用shared_ptr管理connection的函数。
练习12.15:重写第一题的程序,用lambda代替end_connection函数。
练习12.17:下面的unique_ptr声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。
(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成员。
练习12.20:编写程序,逐行读入一个输入文件,将内容存入一个StrBlob中,用一个StrBlobPtr打印出StrBlob中的每个元素。
练习12.21:也可以这样编写StrBlobPtr的deref成员:
书中的方式更好一些。将合法性检查与元素获取的返回语句分离开来,代码更清晰易读,当执行到第二条语句时,已确保p是存在的vector,curr是合法的位置,可安全地获取元素并返回。这种清晰的结构也更有利于修改不同的处理逻辑。
而本题中的版本将合法性检查和元素获取及返回合在一条语句中,不易读,也不易修改。
练习12.23:编写一个程序,连接两个字符串字面值常量,将结果保存在一个动态分配的char数组中。重写这个程序,连接两个标准库string对象。
练习12.24:编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长。测试你的程序,输入一个超出你分配的数组长度的字符串。
练习12.25:给定下面的new表达式,你应该如何释放pa?
练习12.26:用allocator重写第427页中的程序。
练习12.28:编写程序实现文本查询,不要定义类来管理数据,你的程序应该接受一个文件,并与用户交互来查询单词。使用vector、map和set容器来保存来自文件的数据并生成查询结果。
练习12.29:我们曾经用do while循环来编写管理用户交互的循环。用do while重写本节程序,解释你倾向去哪个版本,为什么。
练习12.30:定义你自己版本的TextQuery和QueryResult类并执行12.3.1节中的runQueries函数。
};#endif
练习12.31:如果用vector代替set保存行号,会有什么差别?哪种方法更好?为什么?
vector更好。因为,虽然vector不会维护元素值的序,set会维护关键字的序,但我们是逐行读入文本的,因此每个单词出现的行号是自然按升序加入到容器中的,不必特意用关联容器来保证行号的升序。从性能角度,set是基于红黑树实现的,插入操作时间复杂度为O(logn),而vector的push_back可达到常量时间。
练习12.32:重写TextQuery和QueryResult类,用StrBlob代替vector<vector>保存输入文件。
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笔记之关联容器的使用详解
- 如何更好的利用《C++ Primer》学习C++?
- c++primer plus 2.7编程练习作业
- C++primer 第十章 单词转换程序 运行不了解决办法
- 头文件中包含const与非const对象
- 几个标准库类型的简单使用
- C++Primer _vector _习题3.13
- C++Primer _vector _习题3.14
- Sales_item.h (C++ Primer 第五版)
- forward_list未定义+=符号,而改用advance()函数改变迭代器
- C++ primer 笔记
- C++Primer学习笔记
- 重读c++primer (第五版) -- 引子
- (福利)”C++Primer笔记“和”Java经典入门笔记“
- C++复习学习提纲
- [C++ Primer]第一章 快速入门
- C++primer 阅读笔记-模板与泛型编程(效率与灵活性+)
- C++primer阅读笔记-模板与泛型编程(重载与模板)
- C++primer阅读笔记-模板与泛型编程(模板实参推断)
- C++primer阅读笔记-重载运算与类型转换(可调用对象与function)