inrcl
inrcl
手把手教你写bug
LNK 2005 LNK1169 找到一个或多个重定义的符号

头文件部分

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

using namespace std;
using line_no = vector<string>::size_type;	//使用using代替typedef

class QueryResult;
class TextQuery
{
	friend class QueryResult;
public:
	TextQuery(ifstream& infile);				//读取给定输入文件的构造函数
	QueryResult query(const string&);			//执行查询的函数,返回结果用QueryResult类来存放

private:
	//vector 存放输入文件的文本
	shared_ptr<vector<string>> vp = make_shared<vector<string>>();
	//set保存单词所有行号,行号-1为在vector中的下标
	map<string, set<line_no>> word_line;
};
inline
TextQuery::TextQuery(ifstream& infile)
{
	string text;
	size_t n = 1;
	while (getline(infile, text))
	{
		vp->push_back(text + "\n");
		istringstream ins(text);
		string word;
		pair<string, set<size_t>> pa;
		while (ins >> word)
		{
			pa.first = word;
			pa.second.insert(n);
			auto p = word_line.insert(pa);	//返回一个pair,first是迭代器指向给定关键字元素
											// second 是bool ,指出插入是否成功
			if (!p.second)					//关键字已在map中,只需在set里添加新的行号
				p.first->second.insert(n);
		}
		n++;
	}
	cout << "文件读取成功。" << endl;
}

class QueryResult
{
public:
	using iter = set<size_t>::iterator;
	QueryResult() = default;
	QueryResult(shared_ptr<vector<string>>& f, pair<string, set<size_t>>& sspa) :wptr(f), wl(sspa) { }
	void print();	//输出结果
	iter QRbegin() { return wl.second.begin(); }	//返回set的迭代器
	iter QRend() { return wl.second.end(); }		//返回set的迭代器
	void setKey(const string& s) { wl.first = s; }	//设置关键字
	set<size_t>& setLines() { return wl.second; }	//返回set
private:
	/* weak_ptr 指向一个由shared_ptr 管理的对象 它不会改变shared_ptr 的引用计数  它是一种弱引用
	由于对象可能不存在 所以在用w 访问对象前 必须先调用lock 函数检查所指向的对象是否仍存在。*/
	weak_ptr<vector<string>> wptr;	//输入文件
	pair<string, set<size_t>> wl;//查询单词和出现行号
	shared_ptr<vector<string>> check() const;
};
inline
shared_ptr<vector<string>> QueryResult::check() const
{
	//在用weak_ptr 访问对象前 必须先调用lock 函数检查所指向的对象是否仍存在。
	auto ret = wptr.lock();//shared_ptr所指vector未销毁,则返回指向对象的shared_ptr,否则返回一个空shared_ptr
	if (!ret)
		throw runtime_error("unbound QueryResult");
	return ret;
}
inline
void QueryResult::print()
{
	auto p = check();
	auto sbe = wl.second.cbegin();
	cout << wl.first << " occurs " << wl.second.size() << " times\n";
	while (sbe != wl.second.cend())
	{
		cout << "\t(line " << *sbe << ") " << (*p)[*sbe - 1];
		sbe++;
	}
	cout << "\t- end -";
}
inline
QueryResult TextQuery::query(const string& word)
{
	auto p = word_line.find(word);//返回指向第一个关键字的迭代器或尾后迭代器
	if (p != word_line.end())
	{
		pair<string, set<size_t>> pa({ p->first,p->second });
		return QueryResult(vp, pa);
	}
	else
		throw runtime_error("key find error");
}

源文件部分

#include "TextQuery.h"
#include <algorithm>
#include <iterator>

//这是一个抽象基类,具体的查询类从中派生,所有成员都是private 的
class QueryBase
{
	friend class Query;
protected:
	//using line_no = TextQuery::line_no;	//用于eval 函数
	virtual ~QueryBase() = default;
private:
	//返回与当前Query 匹配的QueryResult
	virtual QueryResult eval(TextQuery&) const = 0;	//纯虚函数
	//rep 表示查询的一个string
	virtual string rep() const = 0;							//包含纯虚函数的类称之为抽象类,不能创建对象
};
//这是一个管理QueryBase 继承体系的接口类
class Query
{
	//这些运算符需要访问接收shared_ptr 的构造函数,而该函数是私有的,所以需要声明为友元函数
	friend Query operator~(const Query&);
	friend Query operator&(const Query&, const Query&);
	friend Query operator|(const Query&, const Query&);
public:
	Query(const string&);
	/* Query 操作这两个成员使用它的Query_Base 指针来调用各自的Query_Base 虚函数,
		实际调用的版本由q 所指对象的实际类型决定 */
	QueryResult eval(TextQuery& t) const { return q->eval(t); }
	string rep() const { return q->rep(); }
private:
	Query(shared_ptr<QueryBase> query): q(query) { }	//shared_ptr 支持派生类向基类的类型转换,shared_ptr<QueryBase>可以绑定到它的派生类上 
	shared_ptr<QueryBase> q;
};
ostream& 
operator<<(ostream& os, const Query& query)
{
	//Query::rep 通过它的 QueryBase指针对rep()进行虚调用
	return os << query.rep();
}
//查找一个给定的string 它是在给定的TextQuery 对象上实际执行查询的唯一一个操作
class WordQuery: public QueryBase
{
	friend class Query;	 //Query 使用WordQuery 构造函数
	WordQuery(const string& s) :query_word(s){ }
	//具体的类:WordQuery 将定义所有继承而来的纯虚函数
	QueryResult eval(TextQuery& t) const { return t.query(query_word); }
	string rep() const { return query_word; }
	string query_word;	//要查找的单词
};
inline	Query::Query(const string& s) : q(new WordQuery(s)){ }	//新分配一个WordQuery 对象,并令指针指向新分配的对像

class NotQuery: public QueryBase
{
	friend Query operator~(const Query&);
	NotQuery(const Query& q):query(q) { }
	//具体的类:NotQuery 将定义所有继承而来的纯虚函数
	QueryResult eval(TextQuery&) const;
	string rep() const {return "~(" + query.rep() + ")";}	//query.rep() 会执行一个虚调用
	Query query;
};

Query operator~(const Query& operand)
{
	return shared_ptr<QueryBase>(new NotQuery(operand));//隐式的使用接受一个shared_ptr<QueryBase> 的Query的构造函数 
}
//抽象基类,它保存操作两个运算对象的查询对象所需的数据
class BinaryQuery: public QueryBase
{
protected:
	BinaryQuery(const Query& l, const Query& r, string s): lhs(l), rhs(r), opSym(s) { }

	//抽象类:BinaryQuery不定义eval
	string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; }
	Query lhs, rhs;	//两个运算对象
	string opSym;	//运算符的名字
};

class AndQuery: public BinaryQuery
{
	friend Query operator&(const Query&, const Query&);
	AndQuery(const Query& left, const Query& right): BinaryQuery(left, right, "&") { }
	//具体的类:AndQuery 继承了rep 并且定义了其他纯虚函数
	QueryResult eval(TextQuery&) const;
};
inline Query operator&(const Query& lhs, const Query& rhs)
{
	return shared_ptr<QueryBase>(new AndQuery(lhs, rhs));//dynamic_cast<Derived*>(pb)) 子类向父类的强制转换
}

class OrQuery : public BinaryQuery
{
	friend Query operator|(const Query&, const Query&);
	OrQuery(const Query& left, const Query& right) : BinaryQuery(left, right, "|") { }
	//具体的类:AndQuery 继承了rep 并且定义了其他纯虚函数
	QueryResult eval(TextQuery&) const;
};
inline Query operator|(const Query& lhs, const Query& rhs)
{//和~运算符一样,&和|运算符也返回一个绑定到新分配对象上的shared_ptr。return语句负责将shared_ptr转换成Query对象。
	return shared_ptr<QueryBase>(new OrQuery(lhs, rhs));
}

QueryResult OrQuery::eval(TextQuery& text) const			//返回运算对象查询结果的并集
{
	//通过Query 成员lhs 和rhs 进行的虚调用
	//调用eval返回每个运算对象的QueryResult
	auto right = rhs.eval(text), left = lhs.eval(text);
	//auto ret_line = make_shared<set<line_no>>(left.QRbegin(), left.QRend());
	left.setLines().insert(right.QRbegin(), right.QRend());	//将右侧对象行号拷贝到左侧对象set中
	left.setKey(rep());										//重设左侧对象关键字 为带运算符和操作对象形式
	return left;											//左右侧对象文件相同,故不操作文件
}

QueryResult AndQuery::eval(TextQuery& text) const			//返回运算对象查询结果的交集
{
	//通过Query 成员lhs 和rhs 进行的虚调用,以获得运算对象的查询结果set
	auto right = rhs.eval(text), left = lhs.eval(text);
	//保存left和right 交集的set
	auto ret_line = make_shared<set<line_no>>();
	//set_intersection 标准库算法,将两个输入序列中共同出现的元素写入到目的位置中
	set_intersection(left.QRbegin(), left.QRend(), right.QRbegin(), right.QRend(), inserter(*ret_line, ret_line->begin()));
	left.setLines().swap(*ret_line);	//将行号的交集与左侧对象set交换
	left.setKey(rep());					//重设左侧对象关键字 为带运算符和操作对象形式
	return left;						//左右侧对象文件相同,故不操作文件
}

QueryResult NotQuery::eval(TextQuery& text) const			//返回运算对象查询的结果的set 中不存在的行
{
	//通过Query 成员对eval 进行的虚调用
	auto result = query.eval(text);
	//开始时结果set 为空
	auto ret_line = make_shared<set<line_no>>();
	//我们必须在运算对象出现的所有行中进行迭代
	auto beg = result.QRbegin(), end = result.QRend();
	//对于输入文件的每一行,如果该行不在result 中,则将其添加到ret_line
	auto sz = result.setLines().size();
	for (size_t i = 0; i != sz; i++)
	{
		//如果我们还没有处理完result 的所有行
		//检查当前行是否存在
		if (beg == end || *beg != i)
			ret_line->insert(i);	//如果不在result 当中,添加这一行
		else if (beg != end)
			++beg;					//否则继续获取result 下一行(如果有的话)
	}
	result.setKey(rep());			//重设关键字 为带运算符和操作对象形式
	result.setLines().swap(*ret_line);	//将最终的结果set 放入到result 中
	return result;
}

int main()
{
	try {
		ifstream infile("input.txt");
		TextQuery t1(infile);
		Query q = Query("fiery") & Query("bird") | Query("wind");
		q.eval(t1).print();
	}
	catch (runtime_error err)
	{
		cout << err.what();
	}
	return 0;
}

txt文件内容 ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟

Alice Emma has long flowing red hair .
Her Daddy says when the wind blows
through her hair, it looks almost alive ,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
“Daddy ,shush, there is no such thing,”
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, “I mean, Daddy, is there?”

txt文件内容 ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟ ☟

http://inrcl.cn/wp-content/uploads/2020/04/批注-2020-04-02-180333.jpg

因为在头文件里定义函数,但没有把函数声明为内联,vs2019就直接报错 ☟ ☟ ☟ ☟

http://inrcl.cn/wp-content/uploads/2020/04/批注-2020-04-02-175045-1024x256.jpg
http://inrcl.cn/wp-content/uploads/2020/04/批注-2020-04-02-175111-1024x211.jpg

在所有的函数定义前加inline 声明后,成功实现 0 error。

http://inrcl.cn/wp-content/uploads/2020/04/批注-2020-04-02-175246-1024x633.jpg

[分析] :编译器在处理程序的过程中,在编译时对函数的定义并不关心,只关心声明。编译器在预处理阶段会将头文件展开, 即#include 所包含的内容。在编译完成后编译器开始链接各个文件,此时在头文件中定义的函数在每个文件中(头文件和源文件中) 各有一份函数的定义 ,在本项目中,即在TextQuery.h 和Query_Base_A_Query.cpp 中有两份函数的定义,因此编译器就会报出重定义的错误。

[总结] :在 .h 的文件中定义函数,显然是一个好办法,同时请一定要去掉 inline 关键字。

相关资料: https://blog.csdn.net/M_jianjianjiao/article/details/84109955

anyShare分享到:

发表评论

textsms
account_circle
email

inrcl

手把手教你写bug
一定要多在头文件中定义函数,而且头文件里一定不能使用 inline 关键字。
扫描二维码继续阅读
2020-04-02