家谱管理系统c++实现

1.背景


家谱:又称族谱、宗谱等。是一种以表谱形式,记载一个家族的世系繁衍及重要人物事迹的书。皇帝的家谱称玉牒,如新朝玉牒、皇宋玉牒。它以记载父系家族世系、人物为中心,由正史中的帝王本纪及王侯列传、年表等演变而来。
家谱是一种特殊的文献,就其内容而言,是中国五千年文明史中具有平民特色的文献,记载的是同宗共祖血缘集团世系人物和事迹等方面情况的历史图籍。家谱属珍贵的人文资料,对于历史学、民俗学、人口学、社会学和经济学的深入研究,均有其不可替代的独特功能。
##2. 项目简介 ##

预备知识:

  1. c++基本语法
  2. 数据结构–树
  3. 正则表达式
  4. mysql数据库基本操作

这些知识只是附加功能用到的

  1. python基本语法
  2. python库:os\opencv\requests\mimetypes\time
  3. http相关知识
  4. 计算机编码的一些常识

系统基本功能(不一定全面):

  1. 创建家谱
  2. 增、删、改、查
  3. 统计
  4. 存档

项目设计思路:
家谱的用户分为管理员与普通用户,普通用户只有查询与查看家谱这两个功能,而管理员就可以对家谱进行修改。创建一个家谱类,对家谱的各种操作就是这个类的各种方法,家谱是根据二叉树来建立,按照“左孩子,右兄弟”这样的结构建立二叉树,所以所有的操作基本都是对二叉树的操作。

3.项目功能介绍


  • 创建家谱
bool Genealogy::Create(Person **p,Person **root)
{
	bool mate;
	int tag;
	*p = new Person;
	if (!(*p))
	{
		return false;
	}
	(*p)->parent = NULL;
	(*p)->firstchild = NULL;
	(*p)->nexsilbing = NULL;
	cout<<"本人:"<info));  //输入相关数据
	cout<<"是否有配偶?(1表示有,0表示没有)"<mate));
	}
	else
	{
		(*p)->mate.name = "无";
	}
	Show(*root,0);
	cout<<"1.添加"<<(*p)->info.name<<"的孩子"<info.name<<"的兄弟姐妹"<info.name<<"的后代及同胞"<firstchild),root);
		if ((*p)->firstchild)
			(*p)->firstchild->parent = *p;
		cout<<"******是否添加"<<(*p)->info.name<<"的兄弟姐妹"<nexsilbing),root);
			if ((*p)->nexsilbing)
				(*p)->nexsilbing->parent = (*p)->parent;
		}
		else
			(*p)->nexsilbing = NULL;
		return true;
		break;
	case 2:
		Create(&((*p)->nexsilbing),root);
		if ((*p)->nexsilbing)
			(*p)->nexsilbing->parent = (*p)->parent;
		cout<<"******是否添加"<<(*p)->info.name<<"的孩子?(1表示是,0表示不添加)"<firstchild),root);
			if ((*p)->firstchild)
				(*p)->firstchild->parent = *p;
		}
		else
			(*p)->firstchild = NULL;
		return true;
		break;
	case 3:
		(*p)->firstchild = NULL;
		(*p)->nexsilbing = NULL;
		return true;
		break;
	default:
		(*p)->firstchild = NULL;
		(*p)->nexsilbing = NULL;
		return true;
		break;
	}
}

家谱管理系统c++实现_第1张图片
建立就是普通的递归建立二叉树操作,不过实现了一些自己的小点子。

  • 显示族谱
void Genealogy::Show(Person* p,int depth)//depth:树的深度
{
	if(p)
	{
		int i;
		for (i = 1;i<=depth;i++)
		{
			cout<<"\t";
		}
		cout<info.name<firstchild,depth+1);
		Show(p->nexsilbing,depth);
	}
}

因为最后我们是要以一个树状图来显示整个家谱,所以需要传入一个depth来确定tab的个数,树每增加一层,depth也就加一。

  • 模糊查询
    该系统中有两种查询方式,一种是精确查询,还有就是将要提到的模糊查询了,模糊查询其实我这里做的比较粗糙,大体思路就是如果家谱中的人名字的前两个字与用户输入的数据一样就输出,其实还可以优化,但是当时不想继续撸代码了。
bool Genealogy::Search(Person* p,const string name,int tag)	//模糊查询
{
	if(p)
	{
		if(name.length()>=4&&p->info.name.length()>=4)//如果名字长度大于四,就比较前四个
		{
			if(name.substr(0,4)==p->info.name.substr(0,4))
			{
				Display(p);
				Search(p->firstchild,name,tag);
				Search(p->nexsilbing,name,tag);
				return true;
			}
		}
		else
		{
			if(name==p->info.name)
			{
				Display(p);
				return true;
			}
			else
			{
				Search(p->firstchild,name,tag);
				Search(p->nexsilbing,name,tag);
				return true;
			}
		}
	}
	return true;
}
  • 存档
    原本设计存档是存在数据库中,但是由于某些原因选择写在文件中,文件怎么写就很讲究了,因为我下次还要查看文档,并对存好的文档进行修改之类的,所以就不能仅仅把数据随便写进去,还要考虑到下次读该文件的时候,需要以该文件建立一棵二叉树。先上代码:
bool Store(Person* p,ofstream& outfile)
{
	if(p)
	{
		outfile<<"普通节点:"<info.name<info.gender<info.height<info.job<info.birthplace<info.birthday<info.live<info.deathday<info.age<info.education<info.great<mate.name!="无")
		{
			outfile<<"配偶:"<mate.name<mate.gender<mate.height<mate.job<mate.birthplace<mate.birthday<mate.live<mate.deathday<mate.age<mate.education<mate.great<firstchild,outfile);
		Store(p->nexsilbing,outfile);
		return true;
	}
	else
	{
		outfile<<"无节点"<

本来这里可以写一个函数避免代码冗杂的,但是先忽略(因为赶时间)
代码先放在这里,我们要结合这下面这个功能一起说为什么这么存文件。

  • 根据文件创建族谱
    这一部分可以算是花的时间最多的,主要是出了一个小bug,找了几个小时。
    先看一下代码:
bool Genealogy::Create(Person **p,ifstream& infile,streampos dir)//从文件读书据并创建树
{
	string sign;
	string name,job,birthplace,birthday,deathday,education;
	string height;
	string age;
	string live,gender,great;
	//streampos sp = infile.tellg();	//定位文件指针
	streampos sp;
	infile.seekg(dir);//读指针定位
	if(!getline(infile,sign))
		return true;
	if(sign=="普通节点:")
	{
		*p = new Person;
		if (!(*p))
		{
			return false;
		}
		(*p)->parent = NULL;
		(*p)->firstchild = NULL;
		(*p)->nexsilbing = NULL;
		getline(infile,name);
		getline(infile,gender);
		getline(infile,height);
		getline(infile,job);
		getline(infile,birthplace);
		getline(infile,birthday);
		getline(infile,live);
		getline(infile,deathday);
		getline(infile,age);
		getline(infile,education);
		getline(infile,great);

		(*p)->info.name = name;
		(*p)->info.gender = stoi(gender);
		(*p)->info.height = stod(height);
		(*p)->info.job = job;
		(*p)->info.birthplace = birthplace;
		(*p)->info.birthday = birthday;
		(*p)->info.live = stoi(live);
		(*p)->info.deathday = deathday;
		(*p)->info.age = stoi(age);
		(*p)->info.education = education;
		(*p)->info.great = stoi(great);
		
		getline(infile,sign);
		if(sign=="配偶:")
		{
			getline(infile,name);
			getline(infile,gender);
			getline(infile,height);
			getline(infile,job);
			getline(infile,birthplace);
			getline(infile,birthday);
			getline(infile,live);
			getline(infile,deathday);
			getline(infile,age);
			getline(infile,education);
			getline(infile,great);

			(*p)->mate.name = name;
			(*p)->mate.gender = stoi(gender);
			(*p)->mate.height = stod(height);
			(*p)->mate.job = job;
			(*p)->mate.birthplace = birthplace;
			(*p)->mate.birthday = birthday;
			(*p)->mate.live = stoi(live);
			(*p)->mate.deathday = deathday;
			(*p)->mate.age = stoi(age);
			(*p)->mate.education = education;
			(*p)->mate.great = stoi(great);
		}
		(*p)->mate.name = "无";	//没有配偶就将配偶名字置为无
		sp = infile.tellg();
		Create(&((*p)->firstchild),infile,sp);
		if((*p)->firstchild)
			(*p)->parent = *p;
		sp = infile.tellg();
		Create(&((*p)->nexsilbing),infile,sp);
		if((*p)->nexsilbing)
			(*p)->parent = (*p)->parent;

	}
	else
	{
		*p = NULL;
		return true;
	}
	
}

这里做了太多重复性的工作,请看官忽略(毕竟为了赶时间)
这里有个重点,那就是文件指针的定位,毕竟这是一个递归创建树的过程,所以每次我创建好一个节点后必须知道创建下一个节点是从文件的哪个位置开始读取数据(总不可能每次都从文件开始处读吧)。除此之外还有一点需要注意,我们用getline()读数据返回值都是char*型的,但是想年龄这些数据时int型,所以一定要进行数据类型转化。

参考文档:
文件流指针的定位
文件流操作
数据类型转换
转化为int,有两种方式:

string s = “123”;

int c = atoi(s.c_str());

或者

int c = stoi(s);

将string转化为double,也是两种方式。

string s = “123.5”;

double c = atof(s.c_str())

或者

double c = stod(s);

bug:
从文件建立树过后,我查看家谱就是这幅造型了:
家谱管理系统c++实现_第2张图片
很明显是出现了死循环,当时没有动脑子,就开始打断点debug,看了好几遍都觉得建立树的过程没有毛病。但是我忽略了一个细节,就两行代码:

家谱管理系统c++实现_第3张图片
这里的代码是我复制的我程序中其它地方的,然后没有注意,结构出现了严重的自己链接到自己的情况。下面是改过后的代码
家谱管理系统c++实现_第4张图片
这次真的是血淋淋的教训

  • 添加家族成员
    添加家族成员有两种添加方式,一个是添加某一个家族成员的子女,另一个就是添加某个家族成员的兄弟姐妹,详细代码就不贴了。

4.干货


很多地方都涉及当用户输入合法性的检测,这也是很多安全问题产生的根源,基本上所有的安全问题都是输入输出流的问题

  • 正则表达式判断用户输入的日期格式是否正确
    正则表达式是个强大的东西,如果读者不了解或是忘了可以在这里熟悉一下:
    http://blog.csdn.net/luoweifu/article/details/42613533
    正则表达式匹配中文

匹配中文字符的正则表达式: [\u4e00-\u9fa5]
匹配双字节字符(包括汉字在内):[^\x00-\xff]

我在建立族谱时有一个对出生日期与死亡日期的输入,所以就需要检查用户输入的数据是否满足要求。

bool Detect(string temp)	//检测日期是否正确
{
	const regex pattern("\\d{2,4}[-\.]\\d{0,2}[-\.]\\d{0,2}");
	return regex_match(temp,pattern);
}

这里的正则并不是限制的很严格,毕竟考虑到家谱中很多人的出生日期不详细。

  • 普通输入检测
    如果我们想要int型数据用户却输入了char型,如果不处理自然会出错。
    所以必须做一个检测,代码如下(重载Detect函数):
void Detect(bool *temp)
{
	while(!(cin>>*temp))
	{
		cin.clear(); //清除cin
		//清除缓冲区的不合法字符
		while(cin.get()!='\n'){
			continue;
		}
		cout<<"提示:输入有误\n请重新输入:"<>*temp)) 
	{
		cin.clear();
		while(cin.get()!='\n'){
			continue;
		}
		cout<<"提示:输入有误\n请重新输入:"<>*temp))
	{
		cin.clear();
		while(cin.get()!='\n'){
			continue;
		}
		cout<<"提示:输入有误\n请重新输入:"<

5.一些脑洞

原本的是准备将用户数据(账号密码)存在数据库中,登录就去验证输入是否正确,但是由于一些原因,就没有做。还有就是用爬虫去网络上爬一些现成的家谱存进数据库,然后用户可以自己选择查看那些家谱,但是网络上并没有现成的东西,所以只好放弃,所以也就没有用数据库,只是用文件来存储我们的数据。但是我还是想到了一个我一直想玩的东西-----人脸识别解锁。

准备工作
opencv+numpy安装

参考资料:
opencv+numpy安装与配置
安装过程中也出了一点问题(opencv与numpy版本不兼容)
家谱管理系统c++实现_第5张图片
解决办法:
http://blog.csdn.net/tchchan/article/details/76177505
http://blog.csdn.net/baobao3456810/article/details/52177316

具体实现:这里写链接内容

github地址:家谱管理系统
源码下载地址:这里写链接内容

你可能感兴趣的:(c++,python,二叉树)