C++ Primer 0x0C 练习题解

📔 C++ Primer 0x0C 练习题解

更好的阅读体验(实时更新与修正)

推荐阅读 《C++ Primer 5th》知识点总结&练习题解

12.1 动态内存与智能指针

12.1.1 shared_ptr 类

12.1 在此代码的结尾,b1b2 各包含多少个元素?

StrBlob b1;
{
	StrBlob b2 = {"a", "an", "the"};
	b1 = b2;//b1,b2共享相同的元素
	b2.push_back("about");//b1,b2都有4个元素
}//b2被析构了,但是不影响b1

如果两个对象共享底层数据,当某个对象被销毁是,我们不能单方面地销毁底层数据

12.2 编写你自己的StrBlob 类,包含const 版本的 frontback

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

class StrBlob{
public:
	typedef std::vector<std::string>::size_type size_type;

	StrBlob();
	StrBlob(std::initializer_list<std::string> il);

	size_type size()const{return data->size();}
	bool empty()const{return data->empty();}

	void push_back(const std::string &t){data->push_back(t);}
	void pop_back();

	std::string& front();
	std::string& front()const;

	std::string& back();
	std::string& back()const;

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

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

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

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

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


int main(){
	const StrBlob a{"this","is","a","const","StrBlob"};
	StrBlob b{"this","is","not","a","const","StrBlob"};

	std::cout << a.front() << " " << a.back() << std::endl;
	std::cout << b.front() << " " << b.back() << std::endl;

	return 0;
}

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

不需要,push_backpop_back会改变对象内容

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

size_type是无符号整型,传负数会变成正数

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

优点:不使用explicitinitializer_list可以隐式转换为StrBlob,这个逻辑上是可行的

缺点:如果我们确实需要用initializer_list,那么编译器会自动转为StrBlod

12.1.2 直接管理内存

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

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

std::vector<int>* alloc_vector(){
	return new std::vector<int>();
}

void read_vector(std::vector<int>* p){
	for(int x;std::cin >> x;){
		p->push_back(x);
	}
}

void print_vector(const std::vector<int>* p){
	for(auto i:*p)std::cout << i << std::endl;
}

int main(){
	auto p = alloc_vector();

	read_vector(p);
	print_vector(p);
	
	delete p;

	return 0;
}

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

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

using T = std::vector<int>;
std::shared_ptr<T> alloc_vector(){
	return std::make_shared<T>();
}

void read_vector(std::shared_ptr<T> p){
	for(int x;std::cin >> x;){
		p->push_back(x);
	}
}

void print_vector(const std::shared_ptr<T> p){
	for(auto i:*p)std::cout << i << std::endl;
}

int main(){
	auto p = alloc_vector();
	read_vector(p);
	print_vector(p);
	
	return 0;
}

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

bool b() {
	int* p = new int;
	// ...
	return p;//被强制转为 bool 没有释放 p 指向的对象
}

12.9 解释下面代码执行的结果。

int *q = new int(42), *r = new int(100);//q 指向 42,r 指向100
r = q;//r q 都指向 42,r 指向 100的内存空间没有释放,内存泄漏
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;//r2,q2都指向42, 100的内存空间不被r2引用的时候自动释放

12.1.3 shared_ptr 和 new 结合使用

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

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

正确,传入的shared_ptr<int>(p)创建一个临时对象,和p引用同一个对象,引用计数为2

函数结束,临时的智能指针被销毁,此时引用计数为1

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

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

p.get()返回一个内置指针

shared_ptr<int>(p.get())创建一个新的智能指针对象,传入的时候引用计数为1,传出的时候引用计数为0,指向的内存被释放,此时p变成了空悬指针

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

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);//合法,传入的时候引用计数为2,结束的时候引用计数为1
(b) process(new int());//不合法,内置指针不能隐式转为智能指针
(c) process(p);//不合法,内置指针不能隐式转为智能指针
(d) process(shared_ptr<int>(p));//对于这个函数而言可以,但是会造成p成为空悬指针

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

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

sp指向的内存空间已经被释放,再用sp操作会出错

12.1.4 智能指针和异常

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

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

struct connection{//连接需要的信息
	std::string ip;
	int port;
	connection(std::string i,int p):ip(i),port(p){}
};

struct destination{//正在连接什么
	std::string ip;
	int port;
	destination(std::string i,int p):ip(i),port(p){}
};

connection connect(destination* pDest){//打开连接
	std::shared_ptr<connection> pConn(new connection(pDest->ip,pDest->port));
	std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
	return *pConn;
};

void disconnect(connection pConn){//关闭给定连接
	std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;	
};

void end_connection(connection* pConn){//自定义删除器
	disconnect(*pConn);
}

void f(destination &d){
	connection c = connect(&d);
	std::shared_ptr<connection>p(&c,end_connection);
	std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main(){
	destination dest("220.181.111.111",10086);
	f(dest);
	
	return 0;
}

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

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

struct connection{//连接需要的信息
	std::string ip;
	int port;
	connection(std::string i,int p):ip(i),port(p){}
};

struct destination{//正在连接什么
	std::string ip;
	int port;
	destination(std::string i,int p):ip(i),port(p){}
};

connection connect(destination* pDest){//打开连接
	std::shared_ptr<connection> pConn(new connection(pDest->ip,pDest->port));
	std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
	return *pConn;
};

void disconnect(connection pConn){//关闭给定连接
	std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;	
};

void f(destination &d){
	connection c = connect(&d);
	std::shared_ptr<connection>p(&c,[](connection* pConn){disconnect(*pConn);});
	std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main(){
	destination dest("220.181.111.111",10086);
	f(dest);

	return 0;
}

12.1.5 unique_ptr

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

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

int main(){
	std::unique_ptr<std::string>p(new std::string("abc"));
	// std::unique_ptr<std::string>q(p);//拷贝
    // unique_ptr' has been explicitly marked deleted here unique_ptr(const unique_ptr&) = delete;
	std::unique_ptr<std::string>r;
	//r = p;//赋值
	//candidate function not viable: no known conversion from 'std::unique_ptr<std::string>' (aka 'unique_ptr<basic_string<char>>') to 'std::nullptr_t' (aka 'nullptr_t') for 1st argumentoperator=(nullptr_t) noexcept
	return 0;
}

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 时,需要将其绑定到一个new 返回的指针上。
(c) IntP p2(pi2);//合法,但pi2可能会变成空悬指针
(d) IntP p3(&ix);//不合法,当 p3 被销毁时,它试图释放一个栈空间的对象。
(e) IntP p4(new int(2048));//合法
(f) IntP p5(p2.get());//不合法,p5 和 p2 指向同一个对象,但是他们互相独立,p5被销毁的时候也释放了p2指向的对象,p2变成空悬

12.18 shared_ptr 为什么没有 release 成员?

release放弃对指针的控制权,返回指针

unique_ptr一个对象只能被一个指针控制,需要释放控制权来给其他指针

shared_ptr允许多个指针指向同一个对象,不需要转移控制权

12.1.6 weak_ptr

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

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

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>


class StrBlobPtr;

class StrBlob{
public:
	friend class StrBlobPtr;
	typedef std::vector<std::string>::size_type size_type;

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

	size_type size()const{return data->size();}
	bool empty()const{return data->empty();}

	void push_back(const std::string &t){data->push_back(t);}
	void pop_back(){check(0,"pop_back on empty StrBlod");data->pop_back();};

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

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

	StrBlobPtr begin();
	StrBlobPtr end();

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


class StrBlobPtr{
public:
	StrBlobPtr():curr(0){}
	StrBlobPtr(StrBlob &a,std::size_t sz = 0):wptr(a.data),curr(sz){}

	bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

	std::string& deref() const{
		auto p = check(curr,"dereference past end");
		return (*p)[curr];
	}
	StrBlobPtr& incr(){
		check(curr,"increment past end of StrBlobPtr");
		++curr;
		return *this;
	}

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

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

int main(int argc,char** argv){
	std::ifstream ifs(argv[1]);
	StrBlob s_blod;
	for(std::string s;std::getline(ifs,s);){
		s_blod.push_back(s);
	}
	for(StrBlobPtr iter = s_blod.begin(); iter != s_blod.end(); iter.incr()){
		std::cout << iter.deref() << std::endl;
	}

	return 0;
}

12.21 也可以这样编写 StrBlobPtrderef 成员:

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

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

原来的好,容易看懂

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

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>


class StrBlobPtr;
class ConstStrBlobPtr;

class StrBlob{
public:
	friend class StrBlobPtr;
	friend class ConstStrBlobPtr;
	typedef std::vector<std::string>::size_type size_type;

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

	size_type size()const{return data->size();}
	bool empty()const{return data->empty();}

	void push_back(const std::string &t){data->push_back(t);}
	void pop_back(){check(0,"pop_back on empty StrBlod");data->pop_back();};

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

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

	StrBlobPtr begin();
	StrBlobPtr end();

	ConstStrBlobPtr cbegin();
	ConstStrBlobPtr cend();

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


class StrBlobPtr{
public:
	StrBlobPtr():curr(0){}
	StrBlobPtr(StrBlob &a,std::size_t sz = 0):wptr(a.data),curr(sz){}

	bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

	std::string& deref() const{
		auto p = check(curr,"dereference past end");
		return (*p)[curr];
	}
	StrBlobPtr& incr(){
		check(curr,"increment past end of StrBlobPtr");
		++curr;
		return *this;
	}

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

class ConstStrBlobPtr{
public:
	ConstStrBlobPtr():curr(0){}
	ConstStrBlobPtr(const StrBlob &a,std::size_t sz = 0):wptr(a.data),curr(sz){}

	bool operator!=(const ConstStrBlobPtr& p) { return p.curr != curr; }

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

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

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

ConstStrBlobPtr StrBlob::cbegin(){return ConstStrBlobPtr(*this);}
ConstStrBlobPtr StrBlob::cend(){return ConstStrBlobPtr(*this,data->size());}

int main(int argc,char** argv){
	std::ifstream ifs(argv[1]);
	StrBlob s_blod;
	for(std::string s;std::getline(ifs,s);){
		s_blod.push_back(s);
	}
	for(ConstStrBlobPtr iter = s_blod.cbegin(); iter != s_blod.cend(); iter.incr()){
		std::cout << iter.deref() << std::endl;
	}

	return 0;
}

12.2 动态数组

12.2.1 new 和数组

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

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>
#include <cstring>

int main(){
	const char* s1="abc";
	const char* s2="def";
	unsigned len = strlen(s1)+strlen(s2)+1;
	char* s3 = new char[len]();
	
	std::strcat(s3,s1);
	std::strcat(s3,s2);
	std::cout << s3 << std::endl;

	delete[] s3;
	return 0;
}
#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>
#include <cstring>

int main(){
	std::string s1="abc",s2="def";
	unsigned len = s1.size()+s2.size()+1;
	char* s3 = new char[len]();
	
	std::strcat(s3,s1.c_str());
	std::strcat(s3,s2.c_str());
	std::cout << s3 << std::endl;

	delete[] s3;
	return 0;
}

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

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>
#include <cstring>

int main(){
	unsigned size = 0;
	std::cin >> size;
	char* s = new char[size+1];

	std::cin.ignore();
	std::cin.get(s,size+1);
	
	std::cout << s << std::endl;
	delete[] s;
	return 0;
}

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

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

12.2.2 allocator 类

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

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>
#include <cstring>

int main(){
	unsigned int n = 0;
	std::cin >> n;

	std::allocator<std::string> alloc;
	auto const p = alloc.allocate(n);

	std::string *q = p;
	std::string s;
	while(std::cin >> s && q != p+n){
		alloc.construct(q++,s);
	}
	
	while(q!=p){
		std::cout << *--q << " ";
		alloc.destroy(q);
	}
	alloc.deallocate(p,n);

	return 0;
}

12.3 使用标准库:文本查询程序

12.3.1 文本查询程序设计

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

头文件 TQ.hpp

#ifndef TQ_H
#define TQ_H

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


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

	QueryResult query(const std::string &sought)const;

private:
	std::shared_ptr<std::vector<std::string>> file;
	std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

class QueryResult{
public:
	friend std::ostream& print(std::ostream &os,const QueryResult &result);//打印结果
public:
	QueryResult()= default;
	QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>>p,
		std::shared_ptr<std::vector<std::string>>f):sought(s),lines(p),file(f){}

private:
	std::string sought;	//查询单词
	std::shared_ptr<std::set<TextQuery::line_no>>lines;//出现的行号
	std::shared_ptr<std::vector<std::string>>file;//输入文件
};
std::string make_plural(const int count,const std::string word,const std::string &suf);
std::ostream& print(std::ostream &os,const QueryResult &result);

#endif

头文件实现TQ.cpp

#include "TQ.hpp"

TextQuery::TextQuery(std::ifstream &ifs):file(new std::vector<std::string>){
	std::string text;
	while (getline(ifs, text)){//text保存每一行
		file->push_back(text);
		int n = file->size() - 1;
		std::istringstream line(text);
		std::string word;
		while (line >> word){//word保存一行的每个单词
			auto &lines = wm[word];//看word映射过去的指针是否存在
			if (!lines)lines.reset(new std::set<line_no>);//指针不存在分配一个新的set
			lines->insert(n);//将此行号插入set
		}
	}
}

QueryResult TextQuery::query(const std::string &sought)const{
	static std::shared_ptr<std::set<line_no>>nodata(new std::set<line_no>);
	auto loc = wm.find(sought);
	if(loc == wm.end()){
		return QueryResult(sought,nodata,file);
	}else{
		return QueryResult(sought,loc->second,file);
	}
}
std::string make_plural(const int count,const std::string word,const std::string &suf){
	return count>1?(word+suf):word;
}
std::ostream& print(std::ostream &os,const QueryResult &result){
	os << result.sought <<" occurs " << result.lines->size() << " "
		<< make_plural(result.lines->size(),"time","s") << std::endl;
	for(auto num:*result.lines){
		os << "\t(line " << num+1 <<") "
			<< *(result.file->begin()+num) << std::endl;
	}
	return os;
}

主函数Test.cpp

#include "TQ.hpp"

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

int main(int argc,char **argv){
	std::ifstream ifs(argv[1]);
	runQueries(ifs);
	return 0;
}

数据文件wordsfile.txt

Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. 
Catch the trade winds in your sails. 
Explore. Dream. Discover. 

编译链接

clang++ -c TQ.cpp
clang++ -c Test.cpp
clang++ -o main Test.o TQ.o
clang++ -c TQ.cpp
./main wordsfile.txt

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

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

using std::vector;using std::set;using std::map;
using std::shared_ptr;using std::ifstream;using std::string;
using std::istringstream;

int main(int argc,char** argv){
	ifstream ifs(argv[1]);
	vector<string>input;
	map<string,set<vector<string>::size_type>>dict;
	vector<string>::size_type lineNo = 0;

	for(string line;std::getline(ifs,line);++lineNo){
		input.push_back(line);
		istringstream line_stream(line);
		for (string text, word; line_stream >> text; word.clear()){
			std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);//去除标点符号
			dict[word].insert(lineNo);
		}
	}


	do{
		std::cout << "enter word to look for , or q to quit: ";
		std::string s;
		if(!(std::cin >> s)|| s == "q")break;
		auto found = dict.find(s);
		if(found != dict.end()){
			std::cout << s << " occurs " << found->second.size() << (found->second.size() > 1 ? " times" : " time") << std::endl;
			for (auto i : found->second)
			std::cout << "\t(line " << i + 1 << ") " << input.at(i) << std::endl;
		}else{
			std::cout << s << " occurs 0 time" << std::endl;
		}
		
	}while(true);
	
	return 0;
}

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

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

do while看起来顺眼哈哈,不过平常都是用while

12.3.2 文本查询程序类的定义

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

见练习 12.27

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

set更好,可以保证行号有序不重复

vector可能重复

12.32 重写 TextQueryQueryResult类,用StrBlob 代替 vector<string> 保存输入文件。

StrBlob.hpp

#ifndef StrBlob_H
#define StrBlob_H

#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
#include <exception>
#include <fstream>


class StrBlobPtr;

class StrBlob{
public:
	friend class StrBlobPtr;
	typedef std::vector<std::string>::size_type size_type;

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

	size_type size()const{return data->size();}
	bool empty()const{return data->empty();}

	void push_back(const std::string &t){data->push_back(t);}
	void pop_back(){check(0,"pop_back on empty StrBlod");data->pop_back();};

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

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

	StrBlobPtr begin();
	StrBlobPtr end();

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


class StrBlobPtr{
public:
	StrBlobPtr():curr(0){}
	StrBlobPtr(StrBlob &a,std::size_t sz = 0):wptr(a.data),curr(sz){}

	bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

	std::string& deref() const{
		auto p = check(curr,"dereference past end");
		return (*p)[curr];
	}
	StrBlobPtr& incr(){
		check(curr,"increment past end of StrBlobPtr");
		++curr;
		return *this;
	}

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

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

#endif

TQ.hpp

#ifndef TQ_H
#define TQ_H

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


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

	QueryResult query(const std::string &sought)const;

private:
	std::shared_ptr<StrBlob> file;
	std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

class QueryResult{
public:
	friend std::ostream& print(std::ostream &os,const QueryResult &result);//打印结果
public:
	QueryResult()= default;
	QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>>p,
		std::shared_ptr<StrBlob>f):sought(s),lines(p),file(f){}

private:
	std::string sought;	//查询单词
	std::shared_ptr<std::set<TextQuery::line_no>>lines;//出现的行号
	std::shared_ptr<StrBlob>file;//输入文件
};
std::string make_plural(const int count,const std::string word,const std::string &suf);
std::ostream& print(std::ostream &os,const QueryResult &result);

#endif

TQ.cpp

#include "TQ.hpp"

TextQuery::TextQuery(std::ifstream &ifs):file(new StrBlob){
	std::string text;
	while (getline(ifs, text)){//text保存每一行
		file->push_back(text);
		int n = file->size() - 1;
		std::istringstream line(text);
		std::string word;
		while (line >> word){//word保存一行的每个单词
			auto &lines = wm[word];//看word映射过去的指针是否存在
			if (!lines)lines.reset(new std::set<line_no>);//指针不存在分配一个新的set
			lines->insert(n);//将此行号插入set
		}
	}
}

QueryResult TextQuery::query(const std::string &sought)const{
	static std::shared_ptr<std::set<line_no>>nodata(new std::set<line_no>);
	auto loc = wm.find(sought);
	if(loc == wm.end()){
		return QueryResult(sought,nodata,file);
	}else{
		return QueryResult(sought,loc->second,file);
	}
}
std::string make_plural(const int count,const std::string word,const std::string &suf){
	return count>1?(word+suf):word;
}
std::ostream& print(std::ostream &os,const QueryResult &result){
	os << result.sought <<" occurs " << result.lines->size() << " "
		<< make_plural(result.lines->size(),"time","s") << std::endl;
	for(auto num:*result.lines){
		StrBlobPtr p(*result.file,num);
		os << "\t(line " << num+1 <<") "
			<< p.deref() << std::endl;
	}
	return os;
}

Test.cpp

#include "TQ.hpp"

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

int main(int argc,char **argv){
	std::ifstream ifs(argv[1]);
	runQueries(ifs);
	return 0;
}

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

class QueryResult{
public:
	friend std::ostream& print(std::ostream &os,const QueryResult &result);//打印结果
	using Iter = std::set<TextQuery::line_no>::iterator;	
public:
	QueryResult()= default;
	QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>>p,
		std::shared_ptr<StrBlob>f):sought(s),lines(p),file(f){}
		
public:
	Iter begin() const { return lines->begin(); }
	Iter end() const { return lines->end(); }
	std::shared_ptr<std::vector<std::string>> get_file() const 
	{ 
		return std::make_shared<std::vector<std::string>>(file); 
	}

private:
	std::string sought;	//查询单词
	std::shared_ptr<std::set<TextQuery::line_no>>lines;//出现的行号
	std::shared_ptr<StrBlob>file;//输入文件
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值