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指针完全合法。
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;
- r = q,则r原来的空间没有指针指向,因此也不会被释放,造成内存泄漏。
- r2 = q2, q2的引用计数+1,r2的引用计数-1,变为0。r2原来指向的那块空间会被释放。
12.10 下面的代码调用了413页中定义的process函数,解释此调用是否正确,如果不正确,应如何修改?
shared_ptr<int> p (new int(42));
process (shared_ptr<int>(p));
// 此调用正确。
12.11 如果我们像下面这样调用process,会发生什么?
process(shared_ptr<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;
}