题目:
请用vc2010或以上版本编写一个多线程注册验证程序(要求先通过对话框输入若干人的学号和姓名,并保存在文本文件中作为注册记录)。然后,用户输入一个学号,程序能够通过多线程方式与记录比对来验证是否已经注册,并弹出提示框。
首先分析难点有 二
惭愧的是,我在第一个难点卡了好久,说明我学的数据结构和一些基础操作属实垃圾,还需要学习很多。
struct logmessage
{
int id;
int xuehao;
string name;
};
数据的处理直接写了一个类来处理,结构体用list容器(双向链表)来存储。
class CInfoFile
{
public:
CInfoFile();
~CInfoFile();
//添加数据
void Addline(int xuehao, CString name);
//读取数据
void ReadDocline();
//写入数据
void WirteDocline();
//检查数据
int CheckDocline(int m_studentid);
list ls;
};
list
STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分,不用单独安装。
C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。
查看更多STL和list操作看这个网址:http://c.biancheng.net/stl/
///读取文件数据到链表
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(); //关闭文件
}
查看此链接:https://www.runoob.com/cplusplus/cpp-files-streams.html 注意看下面的笔记
实例化一个ifstream对象ifs并打开文件_F_login,这里_ F_login是定义的一个宏表示地址。为了方便这里也可以使用别的看起来简单些的代码
例如;
ifstream ifs;
ifs.open(_F_login);
成员函数getline()是从输入流中读取一行字符,读到终止符时会将’0’存入结果缓冲区中,作为输入的终止。终止符可以是默认的终止符,也可以是定义的终止符。函数的语法结构是:getline(<字符数组chs>,<读取字符的个数n>,<终止符>)
这句意思是获取一行数据赋值给buf字符数组。
说明:首次调用时,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 << 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); //放在链表的后面
}
}
}
参考 https://blog.csdn.net/u011519892/article/details/17286587,这里考虑直接相等也不是不行。。。。为什么这样写,因为这个类主要是参考的别人的文件的。
关于多线程的操作,目前只能贴出来代码,具体问题有很多我还没有搞清楚。
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;
}
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;
}
void CMFCpractiseDlg::OnBnClickedOk()
{
//更新数据并且初次判断
UpdateData(TRUE);
THREADDATA* pData = new THREADDATA;
pData->nIndex = 1;
pData->pDlg = this;
AfxBeginThread(ThreadFunc2, pData);
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;
线程的结束方法使用,挂起,互斥量的使用先不提,后面单独拎出来记录一下多核的操作。
写完之后我就知道,我的做法是错误的,这样写一篇分析,没人能够复现出来的。于是把代码放上来才是最重要的。
代码链接:https://download.csdn.net/download/yuanjiteng/12263511
注:代码仅供参考,学号为20173012的同学别直接用,我已经交了,谢谢doge。
代码还需要改进,有很大的改进空间。
遇到问题和解决:
在线程里面进行数据更新时会报错,无法调用updateData()函数,原因可能是线程里面传入的参数是this指针,含有Dlg,但是updateData()函数是基于Cwnd的。解决方法:把数据更新方法按钮事件里面执行。
对于线程的思考
(1)理论上来说最佳解决办法是一个线程进行添加,一个线程进行查询,当查询到注册状态(开始或者终止)另外一个添加线程。但是考虑到注册线程中已经有遍历操作,同时可以进行查询,因此直接放到一个线程里里面,另外一个用来判断是否为空和进度条走动。
(2)如果设置三个按钮,一个添加,一个查询,一个取消,对于线程的控制将变得较为简单但是不符合实际逻辑。
(3)对于在一个线程中关闭另外一个线程的函数TerminateThread(ThreadFunc1, 0);似乎无效,貌似因为关闭线程需要一定时间导致。这个在第一题里面打开线程时也遇到过。解决方法是尽量避免使用,网上说最好避免使用此函数,而是通过线程返回来退出。