C++ Primer 第五版 文本查询程序

新手,代码从官网下载的,自己照着代码写。记录下遇到的错误,与新人分享。网上关于这个程序我看到的可能都是第四版,都是只自定义了1个类。第五版的自定义了2个类。

按照官网代码,略有改动。

TextQuery类的头文件,TextQuery.h

#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <memory>    //shared_ptr类的头文件
#include <string>
#include <vector>
#include <map>
#include <set>
#include <fstream>
#include "QueryResult.h"

class QueryResult;
class TextQuery
{
 public:
	#ifdef TYPE_ALIAS_DECLS
	using line_no = std::vector<std::string>::size_type;
	#else
	typedef std::vector<std::string>::size_type line_no;
	#endif
	TextQuery(std::ifstream&);
<span style="white-space:pre">	</span>
<span style="white-space:pre">	//查找函数,给定单词,返回QueryResult对象</span>
	QueryResult query(const std::string &s) const;   //const表示函数不能修改对象数据成员;使用const在一定程度上可以提高程序的安全性和可靠性
 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 std::string&);
};
#endif
TextQuery的源文件TextQuery.cpp

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

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

using namespace std;

//读取输入文件并建立单词到行号的映射
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) 		//将行中的每个单词输出到word//如果单词不在wm中,以之为下标在wm中添加一行
		{
		 word = cleanup_str(word);

<span style="white-space:pre">		</span> //如果单词不在wm中,以之为下标在wm中添加一行
		 auto &lines = wm[word];	//如果单词不在wm中,以之为下标在wm中添加一项;lines是一个shared_ptr
<span style="white-space:pre">		</span> //以word为关键字在map中查找; 如果没有关键字word,map会自动把word添加为关键字

		 if (!lines)			//word第一次出现时,返回的指针为空
			lines.reset(new set<line_no>);	//指针此时没有所指对象,分配一个新的set
		 lines->insert(n);		//将此行号插入set中
		}
	}
}
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;
}

//如果找到了这个string, query函数构造一个QueryResult, 保存给定的string,TextQuery的file成员以及从wm中提取的set

QueryResult 
TextQuery::query(const string &sought) const
{
<span style="white-space:pre">	//如果未找到sought, 我们将返回什么?在这种情况下,没有可返回的set。
<span style="white-space:pre">	</span>//为了解决此问题,我们定义了一个局部static对象,它是一个指向空的行号set的shared_ptr。当未找到给定单词时,返回此对象的一个拷贝</span>
	static shared_ptr<set<line_no>> nodata(new set<line_no>);

<span style="white-space:pre">	//使用find 而不是下标运算来查找单词,避免将单词添加到wm 中</span>
	auto loc = wm.find(cleanup_str(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(), "time", "s") << endl;
	for (auto num : *qr.lines)
		os << "\t(line " << num + 1 << ")"
		   << *(qr.file->begin() + num) << endl;
	return os;
}

QueryResult的头文件QueryResult.h

#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <set>
#include <vector>
#include <string>
#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;   //指针成员:指向保存单词出现行号的set
	std::shared_ptr<std::vector<std::string>> file;   //指针成员:指向保存输入文件的vector
};
std::ostream &print(std::ostream&, const QueryResult&);   //<span style="color:#ff0000;"><strong>此处print函数为什么不定义为QueryResult的成员函数???不解!</strong></span>
#endif
主函数和类都用到的函数make_pluarl,放在头文件make_pluarl.h中:

#ifndef MAKEPLURAL_H
#define MAKEPLURAL_H
#include <string>
inline
std::string make_plural(size_t ctr, const std::string &word, const std::string ending)
{
	return (ctr == 1)? word: word + ending;
}
#endif

含有main函数的源文件:

#include <iostream>
#include <string>
#include <fstream>
#include <iterator>
#include <map>
#include <vector>
#include "TextQuery.h"
#include "QueryResult.h"
#include "make_plural.h"

using namespace std;

int main()
{
 ifstream infile("C:/Program Files/Microsoft Visual Studio 11.0/Text.txt");   //注意根目录符号易错,特别是从打开路径拷过来的
 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;
	}
 return 0;
}

自己遇到的问题:

函数的重定义

刚开始没有看到make_pluarl函数,看到其他文件中有#include“make_pluarl.h”,于是照着书上写了一个。但是在调试运行时,出现错误:fatal error LNK1169: one or more multiply defined symbols found也就是重定义了这个函数,原因就是QueryResult类的接口函数的实现(在TextQuery.cpp里面)中要用到这个函数,因此TextQuery.cpp包含了头文件make_pluarl.h,主函数的源文件中也包含了make_pluarl.h头文件。这样,多次包含同一个头文件时,头文件中定义的函数就重定义了。然后,重新看从官网下的代码,在第六章里面,找到函数头文件make_pluarl.h,发现它是内联函数(inline)。这个函数的实现我自己写的没有定义为内联函数(inline)。再看看百度内联函数:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。

这个错误是在链接的时候出现的:TextQuery.obj : error LNK2005:。再看这个贴:http://blog.chinaunix.net/uid-23634108-id-2393493.html  关于头文件重复包含以及函数重定义问题(这个题目也很好:为什么用了宏名字定义检测头文件的重复包含可还是有重复定义的错误?)。引用别人回复的一句话:“(函数)放在头文件,对于使用文件而言确实未重复包含。但对整个工程而言,每个使用这个头文件的cpp文件编译生成的obj文件中均编译出了这个函数的实体,这样整个工程链接的时候,报信息。”   以上应该可以从实际引用中看到内联函数和普通函数的区别了。

再引用上个贴的回复的一句话:

把定义可以看成两种:类型定义,数据定义
类型定义不分配内存的,比如类的定义,结构体的定义,它是定义一个数据类型而已
数据定义分配内存,比如类对象定义,变量定义
一个原则:
不分配内存的,放在头文件
分配内存的放在cpp文件,放置重复定义

综上所述,你就可以知道结构体的定义属于类型定义放在头文件,静态数据的定义属于数据定义放在cpp文件
对于函数相同,函数声明你可以看作类型定义,函数实现看作数据定义。


初学者编程容易犯各种错误,不论大小,最痛苦的就是找错。但是没办法,这是个过程,只能每次把错误弄懂。

另外,编程一定要规范,就像声明和定义,是放在头文件还是源文件。平时编写要养成好习惯,好风格。


  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值