编写一个多线程注册验证程序

题目:

请用vc2010或以上版本编写一个多线程注册验证程序(要求先通过对话框输入若干人的学号和姓名,并保存在文本文件中作为注册记录)。然后,用户输入一个学号,程序能够通过多线程方式与记录比对来验证是否已经注册,并弹出提示框。

首先分析难点有 二

1.数据的处理问题,数据类型、如何保存,如何遍历,写入、读取、

惭愧的是,我在第一个难点卡了好久,说明我学的数据结构和一些基础操作属实垃圾,还需要学习很多。

a. 数据表示
struct logmessage
{
	int id;
	int xuehao;
	string name;
};

数据的处理直接写了一个类来处理,结构体用list容器(双向链表)来存储。

b. 文件处理声明
class CInfoFile
{
public:
	CInfoFile();
	~CInfoFile();
	//添加数据
	void Addline(int xuehao, CString name);
	//读取数据
	void ReadDocline();
	//写入数据
	void WirteDocline();
   //检查数据
	int CheckDocline(int m_studentid);
	list<logmessage> ls;
};

list<logmessage> ls; 初始化了一个logmessage的list容器。

STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分,不用单独安装。
C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。

查看更多STL和list操作看这个网址:http://c.biancheng.net/stl/

c. 文件处理成员的具体难点:
///读取文件数据到链表
void CInfoFile::ReadDocline()
{
	ifstream ifs(_F_login); //输入方式打开文件,文件读操作
	char buf[1024] = { 0 };
	ls.clear();//清空链表内部
	//取出表头
	ifs.getline(buf, sizeof(buf));
	while (!ifs.eof()) //没到文件结尾
	{
		logmessage tmp;
		ifs.getline(buf, sizeof(buf)); //读取一行
		char *sst = strtok(buf, "|"); //以“|”切割
		if (sst != NULL)
		{
			tmp.id = atoi(sst); //id
		}
		else
		{
			break;
		}
		sst = strtok(NULL, "|");
		tmp.xuehao = atoi(sst);	//学号
		sst = strtok(NULL, "|");
		tmp.name = sst;	//姓名
		ls.push_back(tmp); //放在链表的后面
	}

	ifs.close(); //关闭文件
}
c.1 ifstream ifs(_F_login);

查看此链接:https://www.runoob.com/cplusplus/cpp-files-streams.html 注意看下面的笔记

实例化一个ifstream对象ifs并打开文件_F_login,这里_ F_login是定义的一个宏表示地址。为了方便这里也可以使用别的看起来简单些的代码

例如;

ifstream ifs;

ifs.open(_F_login);

c.2 ifs.getline(buf, sizeof(buf));

成员函数getline()是从输入流中读取一行字符,读到终止符时会将’0’存入结果缓冲区中,作为输入的终止。终止符可以是默认的终止符,也可以是定义的终止符。函数的语法结构是:getline(<字符数组chs>,<读取字符的个数n>,<终止符>)

这句意思是获取一行数据赋值给buf字符数组。

c.3 char *strtok(char *str, const char *delim)

说明:首次调用时,s必须指向要分解的字符串,随后调用要把s设成NULL。strtok在s中查找包含在delim中的字符并用NULL(’\0’)来替换,直到找遍整个字符串。返回指向下一个标记串。当没有标记串时则返回空字符NULL。

参考 https://www.runoob.com/cprogramming/c-function-strtok.html

//写入链表数据到文件
void CInfoFile::WirteDocline()
{
	ofstream ofs(_F_login);//输出方式打开文件

	if (ls.size() > 0)	//链表有内容才执行
	{
		ofs << "ID|学号|姓名" << endl; //写入表头

		//通过迭代器取出链表内容,写入文件,以“|”分隔,结尾加换行
		for (list<logmessage>::iterator it = ls.begin(); it != ls.end(); it++)
		{
			ofs << it->id << "|";
			ofs << it->xuehao << "|";
			ofs << it->name << endl;
		}
	}

	ofs.close();//关闭文件
}

上面代码难点主要是迭代器的使用,list容器里面常用的方法,遍历。

//添加数据到链表
void CInfoFile::Addline(int xuehao,CString name)
{
	logmessage tmp;

	if (ls.size() > 0)
	{
		if (!name.IsEmpty() )
		{
			tmp.id = ls.size() + 1;	//id自动加1
			CStringA str;
			str = name;	//CString转CStirngA
			tmp.xuehao = xuehao;
			tmp.name = str.GetBuffer(); //CStirngA转char *,姓名
			
			ls.push_back(tmp);	//放在链表的后面
		}
	}
}
c.4 CStringA str;

参考 https://blog.csdn.net/u011519892/article/details/17286587,这里考虑直接相等也不是不行。。。。为什么这样写,因为这个类主要是参考的别人的文件的。

2.多线程操作

关于多线程的操作,目前只能贴出来代码,具体问题有很多我还没有搞清楚。

a. 线程1是判断数据是已经注册,如果是,弹窗显示已经注册;否,添加至文件并且弹窗显示注册成功
UINT ThreadFunc1(LPVOID param)
{
	THREADDATA* pData = (THREADDATA*)param;
	mutexT.Lock();
	CInfoFile file;
	if (file.CheckDocline(pData->pDlg->m_studentid) == 20)
	{
		pData->pDlg->MessageBox(TEXT("这个学号已经注册过了!"));
	}
	else
	{
		file.ReadDocline();
		file.Addline(pData->pDlg->m_studentid, pData->pDlg->m_studentname);
		file.WirteDocline();
		pData->pDlg->MessageBox(TEXT("注册成功"));
	}
	mutexT.Unlock();
	return 0;
}
b. 线程2是对于进度条的控制和对于输入是否为空的判断程序:
UINT ThreadFunc2(LPVOID lParam)
{
	THREADDATA* pData = (THREADDATA*)lParam;
	mutexT.Lock();
	CInfoFile file1;
	if (pData->pDlg->m_studentid <= 0 || pData->pDlg->m_studentname.IsEmpty())
	{
		pData->pDlg->MessageBox(TEXT("输入内容不能为空"));
        TerminateThread(ThreadFunc1, 0);
	}
	else
	{
		for (int i = 0; i <= 100; i++)
		{
			//更新对应进度条。
			Sleep(200);	//延缓时间
			pData->pDlg->m_process1.SetPos(i);
		}
	}

	mutexT.Unlock();
	return 0;
}
c. 按钮函数
void CMFCpractiseDlg::OnBnClickedOk()
{
//更新数据并且初次判断
	UpdateData(TRUE);
	THREADDATA* pData = new THREADDATA;
	pData->nIndex = 1;
	pData->pDlg = this;
	AfxBeginThread(ThreadFunc2, pData);
	AfxBeginThread(ThreadFunc1, pData);
}
c.1 关于AfxBeginThread(ThreadFunc1, pData)创建线程问题:

参考https://blog.csdn.net/oceanlucy/article/details/7345057

​ https://www.cnblogs.com/shikamaru/p/7676872.html

需要明确的是,使用此方法对于线程函数写法有要求必须是 UINT ThreadFunc2(LPVOID lParam) 并且至少需要传入两个参数,一个为线程句柄,一个是指针。

上面代码中pData的说明如下:

typedef struct ThreadData     //添加的对话框数据结构
{
	CMFCpractiseDlg* pDlg;
	int nIndex;
}THREADDATA;

THREADDATA* pData = new THREADDATA;
pData->nIndex = 1;
pData->pDlg = this;

线程的结束方法使用,挂起,互斥量的使用先不提,后面单独拎出来记录一下多核的操作。

3.图形页面

img

imgimg

img

img

4. 总结:

写完之后我就知道,我的做法是错误的,这样写一篇分析,没人能够复现出来的。于是把代码放上来才是最重要的。

代码链接:https://download.csdn.net/download/yuanjiteng/12263511

代码还需要改进,有很大的改进空间。

遇到问题和解决:

  1. 在线程里面进行数据更新时会报错,无法调用updateData()函数,原因可能是线程里面传入的参数是this指针,含有Dlg,但是updateData()函数是基于Cwnd的。解决方法:把数据更新方法按钮事件里面执行。

  2. 对于线程的思考

(1)理论上来说最佳解决办法是一个线程进行添加,一个线程进行查询,当查询到注册状态(开始或者终止)另外一个添加线程。但是考虑到注册线程中已经有遍历操作,同时可以进行查询,因此直接放到一个线程里里面,另外一个用来判断是否为空和进度条走动。

(2)如果设置三个按钮,一个添加,一个查询,一个取消,对于线程的控制将变得较为简单但是不符合实际逻辑。

(3)对于在一个线程中关闭另外一个线程的函数TerminateThread(ThreadFunc1, 0);似乎无效,貌似因为关闭线程需要一定时间导致。这个在第一题里面打开线程时也遇到过。解决方法是尽量避免使用,网上说最好避免使用此函数,而是通过线程返回来退出。

行添加,一个线程进行查询,当查询到注册状态(开始或者终止)另外一个添加线程。但是考虑到注册线程中已经有遍历操作,同时可以进行查询,因此直接放到一个线程里里面,另外一个用来判断是否为空和进度条走动。

(2)如果设置三个按钮,一个添加,一个查询,一个取消,对于线程的控制将变得较为简单但是不符合实际逻辑。

(3)对于在一个线程中关闭另外一个线程的函数TerminateThread(ThreadFunc1, 0);似乎无效,貌似因为关闭线程需要一定时间导致。这个在第一题里面打开线程时也遇到过。解决方法是尽量避免使用,网上说最好避免使用此函数,而是通过线程返回来退出。

发布了13 篇原创文章 · 获赞 3 · 访问量 2251

猜你喜欢

转载自blog.csdn.net/yuanjiteng/article/details/105039397