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

Claire ·
更新时间:2024-09-21
· 678 次阅读

文章目录1.数据的处理问题,数据类型、如何保存,如何遍历,写入、读取、a. 数据表示b. 文件处理声明c. 文件处理成员的具体难点:c.1 ifstream ifs(_F_login);c.2 ifs.getline(buf, sizeof(buf));c.3 **char \*strtok(char \*str, const char \*delim)**c.4 CStringA str;2.多线程操作**a. 线程1是判断数据是已经注册,如果是,弹窗显示已经注册;否,添加至文件并且弹窗显示注册成功****b. 线程2是对于进度条的控制和对于输入是否为空的判断程序:**c. 按钮函数c.1 关于AfxBeginThread(ThreadFunc1, pData)创建线程问题:3.图形页面4. 总结:

题目:

请用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 ls; };

list 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(,,)

这句意思是获取一行数据赋值给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::iterator it = ls.begin(); it != ls.end(); it++) { ofs <id << "|"; ofs <xuehao << "|"; ofs <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 pDlg->m_studentname.IsEmpty()) { pData->pDlg->MessageBox(TEXT("输入内容不能为空")); TerminateThread(ThreadFunc1, 0); } else { for (int i = 0; i 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

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

遇到问题和解决:

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

对于线程的思考

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

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

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

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

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

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


作者:不爱说话的圆圆



验证程序 程序 多线程 线程

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章