赞
踩
目录
大一时我们开设了C++课设,这门课需要我们各自完成一个基于C++的项目。既然是一门亘古不变的课程,便有很多学长流传下来的源代码可以直接照搬。但是他们的程序大多长这样:
作为一名精致的大学生,怎能容许如此简陋的程序出现在自己的电脑上,刚好当时闲得无聊,因此我和我的小伙伴便在学长们的基础上进一步对代码进行了优化和改进。
有背景音乐,支持鼠标点击,有登录系统,且将不同用户的数据分类存放。
主体部分用链表,属于基础范畴,不做解释。以下为拓展功能的实现记录:
EasyX的环境配置非常友好,没有太多烦人的操作,官网直接下载就好。
- #include <graphics.h> // 引用图形库头文件
-
- initgraph(1280, 720); //定义图形窗口大小
以欢迎界面为例:
- #include <graphics.h> // 引用图形库头文件
-
- IMAGE welcome;//创建图片对象 欢迎界面
-
- loadimage(&welcome, L"welcome.png");//导入欢迎界面
- putimage(0, 0, &welcome);//显示欢迎界面
其中,L"welcome.png"是同一目录下的图片名称,字符串前面加L表示该字符串是Unicode字符串,为LPCTSTR类型。即loadimage的第二个参数为LPCTSTR类型。
- #include <graphics.h> // 引用图形库头文件
-
- MOUSEMSG msg;//定义变量,保存鼠标消息
- FlushMouseMsgBuffer();// 清空鼠标消息缓冲区,避免无效鼠标信息带入到正式判断中
-
- while (MouseHit()) //监听鼠标信息;当有鼠标消息的时候执行,可检测连续的鼠标信息
- {
- msg = GetMouseMsg();//获取鼠标消息
- if (WM_LBUTTONDOWN == msg.uMsg)//判断鼠标信息;鼠标左键按下
- {
- if (msg.x > 135 && msg.x < 350 && msg.y > 120 && msg.y < 200)//鼠标点击特定区域,即新建记录按钮所在区域
- {
- if (msg.x > 65 && msg.x < 405 && msg.y > 500 && msg.y < 660)//鼠标点击特定区域,即注册按钮所在区域
- {
- Register(); //调用register函数
- }
- }
- }
- }
- #include <graphics.h> // 引用图形库头文件
-
- char kb; //储存键盘信息
-
-
- if (_kbhit())//监听键盘信息
- {
- kb = _getch();
- if (kb == 27) //esc的ASCII码
- {
- exit(EXIT_FAILURE); //退出程序
- }
- }
- #include <graphics.h> // 引用图形库头文件
-
- setbkmode(TRANSPARENT);//设置字体背景为透明
- settextcolor(COLORREF(RGB(0, 0, 0)));//设置字体颜色为黑色
- settextstyle(20, 0, _T("楷体"));//设置字体大小20,格式楷体
-
- outtextxy(400, 60, _T(" 姓名 电话号码 日期"));
开场动画我采用逐帧显示的原理,用pr剪辑好视频后,首先需要解决如何将一段视频逐帧输出为图片。我采用强大的MATLAB实现此功能,其代码如下:
- obj = VideoReader('C://Users//le//Desktop//output.avi');%输入视频位置
- numFrames = obj.NumberOfFrames;% 帧的总数
- for i = 1 : numFrames
- frame = read(obj,i);%读取第几帧
- % imshow(frame);%显示帧
- imwrite(frame,strcat('E:\Pictures\',num2str(i),'.jpg'),'jpg');% 保存帧
- end
按顺序命名。
其次我发现,如果直接将图片一帧一帧的输出,会产生视频播放时快时慢的问题。因此为了稳定视频播放速度,需要控制循环时间。用如下代码实现:
- while (true) // 主循环,循环监听鼠标键盘信息
- {
- float ftime; //记录时间间隔变量
- DWORD startTime = GetTickCount();//记录开始时间
-
-
- ·········
-
-
- do
- {
- DWORD totalTime = GetTickCount() - startTime;//从开始到现在所经历的时间(毫秒)
- ftime = (float)totalTime; //转换为浮点数
- } while (ftime < 30); //为了精确控制播放每一个图片的时间间隔
-
- }
-
其中,do...while语句便实现控制循环时间的功能。
最后,在循环输出图片时发现, loadimage的第二个参数为LPCTSTR类型,因此需要先定义一个char型数组,然后用sprintf函数用类似C语言的printf语法将不断变化的i变量放入char型路径中,再将整个char类型转换为wchar_t类型送入loadimage函数。(wchar_t类型就是LPCTSTR类型)
- char file_name[128];
- IMAGE image;//播放数组
-
- putimage(0, 0, &image);
- sprintf(file_name, "1\\%d.jpg", i + 1); //加载图片
- int num = MultiByteToWideChar(0, 0, file_name, -1, NULL, 0);// 将char*转为LPCTSTR
- wchar_t* wide = new wchar_t[num];
- MultiByteToWideChar(0, 0, file_name, -1, wide, num);
- loadimage(&image, wide);//将属性改为多字符集,则可写变量
- void USER::Display()
- {
- char file_name[128];
- char kb; //储存键盘信息
- IMAGE image;//播放数组
- //下面进行鼠标交互:
- MOUSEMSG msg;//定义变量,保存鼠标消息
- FlushMouseMsgBuffer();// 清空鼠标消息缓冲区,避免无效鼠标信息带入到正式判断中
- while (true) // 主循环,循环监听鼠标键盘信息
- {
- for (int i = 0; i < 1286; i++)
- {
- float ftime; //记录时间间隔变量
- DWORD startTime = GetTickCount();//记录开始时间
-
- if (_kbhit())//监听键盘信息
- {
- kb = _getch();
- if (kb == 27) //esc的ASCII码
- {
- exit(EXIT_FAILURE); //退出程序
- }
- }
- if (MouseHit()) //监听鼠标信息;当有鼠标消息的时候执行,可检测连续的鼠标信息
- {
- msg = GetMouseMsg();//获取鼠标消息
- if (WM_LBUTTONDOWN == msg.uMsg)//判断鼠标信息;鼠标左键按下
- {
- if (msg.x > 65 && msg.x < 405 && msg.y > 500 && msg.y < 660)//鼠标点击特定区域,即注册按钮所在区域
- {
- Register(); //调用register函数
- }
-
- if (msg.x > 870 && msg.x < 1210 && msg.y > 520 && msg.y < 670)//鼠标点击特定区域,即登陆按钮所在区域
- {
- Login(); //调用login函数
- }
- }
- }
-
- putimage(0, 0, &image);
- sprintf(file_name, "1\\%d.jpg", i + 1); //加载图片
- int num = MultiByteToWideChar(0, 0, file_name, -1, NULL, 0);// 将char*转为LPCTSTR
- wchar_t* wide = new wchar_t[num];
- MultiByteToWideChar(0, 0, file_name, -1, wide, num);
- loadimage(&image, wide);//将属性改为多字符集,则可写变量
-
- do
- {
- DWORD totalTime = GetTickCount() - startTime;//从开始到现在所经历的时间(毫秒)
- ftime = (float)totalTime; //转换为浮点数
- } while (ftime < 30); //为了精确控制播放每一个图片的时间间隔
-
- }
-
-
- }
- }
为了有一个点击的动画效果,我采用了如下代码:
- loadimage(&welcome, L"welcome.png");//导入欢迎界面
- putimage(0, 0, &welcome);//显示欢迎界面
- //下面进行鼠标交互:
- MOUSEMSG msg;//定义变量,保存鼠标消息
- FlushMouseMsgBuffer();// 清空鼠标消息缓冲区,避免无效鼠标信息带入到正式判断中
- while (true) // 主循环,循环监听鼠标信息
- {
- while (MouseHit()) //监听鼠标信息;当有鼠标消息的时候执行,可检测连续的鼠标信息
- {
- msg = GetMouseMsg();//获取鼠标消息
- if (WM_LBUTTONDOWN == msg.uMsg)//判断鼠标信息;鼠标左键按下
- {
- if (msg.x > 135 && msg.x < 350 && msg.y > 320 && msg.y < 395)//鼠标点击特定区域,即显示记录按钮所在区域
- {
- loadimage(&display_png, L"display.png");//导入search图片
- putimage(0, 0, &display_png);//显示display图片
- Sleep(100);//延时,降低CPU占用率,并且做到点击效果
- display_choose(); //调用显示函数
- }
- }
- }
- }
可以看到,在判断鼠标点击之后,还调用了一个图片display.png。
其中,welcome.png图片如下:
display.png图片如下:
这便有了一个比较优美的点击效果。动图如下:
为了创造用户交互窗口,在gui中就不能简单的使用cout、cin函数了。查找资料后调用<windows.h>头文件中的函数,用Windows窗口实现与用户的交互。其中,我使用的函数如下:
InputBox(w_char, num, _T("提示语句"));//num为最大输入字符数
MessageBox(NULL, _T("提示语句"), _T("图窗标题"), uType);//第一个参数为句柄,一般都为NULL
其中,InputBox输入框长这样:
MessageBox的uType参数可为如下各值:
例如学生登录环节:
- int result = MessageBox(NULL, _T("密码错误"), _T("学生登录"), MB_ICONWARNING | MB_SETFOREGROUND | MB_RETRYCANCEL);
-
- if (result == IDCANCEL)
- break; //取消输入密码,退出循环
提示框为:
- mciSendString(_T("open bloomofyouth.mp3 alias mymusic"), NULL, 0, NULL);//打开音乐
- mciSendString(_T("play mymusic repeat"), 0, 0, 0);// 重复播放"bloom of youth.mp3"
其中,open bloomofyouth.mp3 alias mymusic只有bloomofyouth.mp3是文件名,open...alias mymusic是必须有的,虽然我也不知道为什么引号里面的内容也有固定格式。。。
Step1:定义用户类,存放用户信息
- /*定义用户类*/
- class USER
- {
- private:
- int Num;//编号
- wchar_t name[20];
- wchar_t password1[20];
- wchar_t password2[20];
- wchar_t temp;
- public:
- void Register();
- void Login();
- void Display();
- }user[100]; //定义用户数量
可以看到,定义了一个全局数组user[100],可以用来存放100个用户的信息。其中,Register()函数为注册函数,Login()函数为登录函数,Display()函数为主页面函数。
Step2:定义全局变量,便于函数间调用
- int UserNum = 0; //用户的数量,从0开始
- int usernumber; //储存用户的编号
- char fname[20]; //储存用户名
- wchar_t pause; //用于形成输入阻塞
- wchar_t fullname[36]; //全局变量,用于将姓和名合并
- friend_node* head_ptr = NULL; //全局变量,链头指针,初始化为空
- friend_node* current_ptr = NULL; //全局变量,用于指明当前在链表中的位置
Step3:创建一个user.txt文件,规定该文件前4个字节存放当前注册用户的总数,后面按顺序存放每个用户的Num、name、password等信息。读取和存储都按照此规则。
读取部分代码如下:
- FILE* fp;
- fopen_s(&fp, "user.txt", "a+");//若不存在则新建文件
-
- //把注册的ID、key保存到user.txt文件当中
- fread(&UserNum, 4, 1, fp);
- for (int i = 0; i < UserNum; i++)
- {
- fread(&user[i].Num, sizeof(Num), 1, fp);
- fread(&user[i].name, sizeof(name), 1, fp);
- fread(&user[i].password1, sizeof(password1), 1, fp);
- fread(&user[i].password2, sizeof(password2), 1, fp);
- fread(&user[i].temp, sizeof(temp), 1, fp);
- }
- fclose(fp);
存储部分代码如下:
- fopen_s(&fp, "user.txt", "w+");
- fwrite(&UserNum, 4, 1, fp);
- for (int i = 0; i < UserNum; i++)
- {
- fwrite(&user[i].Num, sizeof(Num), 1, fp);
- fwrite(&user[i].name, sizeof(name), 1, fp);
- fwrite(&user[i].password1, sizeof(password1), 1, fp);
- fwrite(&user[i].password2, sizeof(password2), 1, fp);
- fwrite(&user[i].temp, sizeof(temp), 1, fp);
- }
- fclose(fp);
其中,文件操作函数包括在<stdio.h>中。fwrite和fread函数中,第一个参数是数组的指针,第二个参数是数组中每个元素的大小(字节),第三个参数是要存放的数组元素个数,最后一个是要存放/读取的文件指针。代码中,第三个参数我都设为了1,这是因为第一个参数我传输的是一个int型或char型的数的地址,不是数组,所以只有一个元素。
另外,我巧妙的运用了fopen_s函数在没有搜索到文件时自动创建文件的特点,这样当第一次用此程序,文件夹中没有user.txt文件时能够自动创建。其最后一个参数如下表:
所以,利用上述特性,我读取文件时用a+方法,存储文件时用w+方法。
Step4:输入用户名和输入密码部分
输入用户名部分要注意判断和前面的用户名没有重合,输入密码部分要注意有二次验证的过程。注册完后实时更新全局变量UserNum。注册函数整个代码如下:
- void USER::Register()
- {
- FILE* fp;
- fopen_s(&fp, "user.txt", "a+");//若不存在则新建文件
-
- //把注册的ID、key保存到user.txt文件当中
- fread(&UserNum, 4, 1, fp);
- for (int i = 0; i < UserNum; i++)
- {
- fread(&user[i].Num, sizeof(Num), 1, fp);
- fread(&user[i].name, sizeof(name), 1, fp);
- fread(&user[i].password1, sizeof(password1), 1, fp);
- fread(&user[i].password2, sizeof(password2), 1, fp);
- fread(&user[i].temp, sizeof(temp), 1, fp);
- }
- fclose(fp);
- //输入用户名
- int i;
- do {
- InputBox(user[UserNum].name, 20, _T("请输入用户名"));//提示用户输入用户名
- //接下来查找用户信息表中的内容,比较新输入的用户名是否存在,如果存在,系统给出提示
- for (i = 0; i < UserNum; i++)
- {
- if (wcscmp(user[i].name, user[UserNum].name) == 0)
- {
- MessageBox(NULL, _T("该用户已经存在,请重新输入"), _T("创建账户"), MB_ICONSTOP | MB_SETFOREGROUND);//输出提示,提醒用户
- break;//跳出for循环
- //修复了1.0版本用户注册时出现的bug
- }
-
- }
- if (i >= UserNum)//说明没有找到重复的用户名
- break;
- } while (1);//如果用户名重复则一直循环,直到不重复时跳出循环
- //输入用户名函数结束
-
-
-
- /*下面设置用户密码*/
- do
- {
- InputBox(user[UserNum].password1, 20, _T("请设置密码"));//提示用户输入密码
- InputBox(user[UserNum].password2, 20, _T("请确认密码"));//提示用户确认密码
- if (wcscmp(user[UserNum].password1, user[UserNum].password2) != 0)//两次输入密码不相等
- MessageBox(NULL, _T("两次输入不一致,请重新输入"), _T("创建账户"), MB_ICONSTOP | MB_SETFOREGROUND);
- else
- {
- MessageBox(NULL, _T("注册成功!"), _T("创建账户"), MB_SETFOREGROUND | MB_ICONASTERISK);
- //参数分别为:消息框拥有的窗口、消息框的内容、消息框的标题、标志集(多个标志用"与"(|)符号连接)
- //最后的参数表示设置消息框为前景窗口
-
- UserNum++;//代表下一个将要注册的学生用户的编号
- user[UserNum - 1].Num = UserNum;//加一
- break;
- }
- } while (1);
-
- fopen_s(&fp, "user.txt", "w+");
- fwrite(&UserNum, 4, 1, fp);
- for (int i = 0; i < UserNum; i++)
- {
- fwrite(&user[i].Num, sizeof(Num), 1, fp);
- fwrite(&user[i].name, sizeof(name), 1, fp);
- fwrite(&user[i].password1, sizeof(password1), 1, fp);
- fwrite(&user[i].password2, sizeof(password2), 1, fp);
- fwrite(&user[i].temp, sizeof(temp), 1, fp);
- }
- fclose(fp);
- }
Step1:从user.txt中读取所有用户信息(同注册部分,此处省略)
Step2:用户输入用户名和密码,若与user.txt数据库中匹配,则进行登入操作。这部分总代码如下:
- /*登录*/
- void USER::Login()
- {
- int i;
- wchar_t username[20];//定义一个临时存储用户名的字符数组
- wchar_t password[20];//定义一个临时存储密码的字符数组
-
- FILE* fp;
- fopen_s(&fp, "user.txt", "a");
- fclose(fp); //若不存在则新建文件
-
- fopen_s(&fp, "user.txt", "r");
- //把注册的ID、key保存到user.txt文件当中
- fread(&UserNum, 4, 1, fp);
- for (int i = 0; i < UserNum; i++)
- {
- fread(&user[i].Num, sizeof(Num), 1, fp);
- fread(&user[i].name, sizeof(name), 1, fp);
- fread(&user[i].password1, sizeof(password1), 1, fp);
- fread(&user[i].password2, sizeof(password2), 1, fp);
- fread(&user[i].temp, sizeof(temp), 1, fp);
- }
- fclose(fp);
-
- InputBox(username, 10, _T("请输入用户名"));//提示用户输入用户名,输入给临时存储用户名的字符数组
-
- for (i = 0; i < UserNum; i++)
- {
- if (wcscmp(username, user[i].name) == 0)//找到了
- {
- usernumber = i;
- break;//跳出for循环
- }
- }
-
- if (i >= UserNum)//说明没有找到对应用户名
- {
- MessageBox(NULL, _T("该用户不存在"), _T("用户登录"), MB_ICONSTOP | MB_SETFOREGROUND);
- }
- else //找到用户名,输入密码
- {
- USES_CONVERSION;//宏
- char* pa = W2A(username);//返回一个 ASNI标准的多字节字符
- strcpy(fname, pa); //将文件名用用户名表示
- strcat(fname, ".txt");
- do
- {
- InputBox(password, 10, _T("请输入密码"));//提示用户输入密码,输入给临时存储密码的字符数组
-
- if (wcscmp(password, user[usernumber].password1) == 0) //如果密码正确,登录成功
- {
- MessageBox(NULL, _T("登录成功!"), _T("用户登录"), MB_SETFOREGROUND);
-
- friend_node object; //定义一个对象
-
- object.load_list_from_file(); //将数据保存在文件中
- object.handle_choice(); //调用函数执行用户的选择
- break;
-
-
- }
- else
- {
- int result = MessageBox(NULL, _T("密码错误"), _T("学生登录"), MB_ICONWARNING | MB_SETFOREGROUND | MB_RETRYCANCEL);
-
- if (result == IDCANCEL)
- break; //取消输入密码,退出循环
- }
- } while (1);
- }
- }
Step3:用户登入后,首先需要读取该用户的账户数据。
即上述代码中的如下语句:
friend_node object; //定义一个对象
object.load_list_from_file(); //将数据保存在文件中
object.handle_choice(); //调用函数执行用户的选择
break;
读取部分在load_list_from_file()函数里面,此函数代码为:
- void friend_node::load_list_from_file()
- {
- friend_node* new_rec_ptr; //定义一个开辟空间的指针
-
-
- FILE* fp;
- int end_loop = 0; //定义一个判断是否结束读取文件中数据的变量
- fopen_s(&fp, fname, "ab");
- fwrite(_T("END OF FILE"), sizeof(last_name), 1, fp);
- fclose(fp);
- //在文件末尾添加标志,不存在则创建文件
- fopen_s(&fp, fname, "rb");
- do
- {
- new_rec_ptr = new friend_node; // 为节点申请空间
-
- if (new_rec_ptr != NULL) // 检查申请空间是否成功
- {
- fread(new_rec_ptr->last_name, sizeof(last_name), 1, fp); // 从文件中读入'姓'信息
- if ((wcscmp(new_rec_ptr->last_name, _T(" ")) != 0) && (wcscmp(new_rec_ptr->last_name, _T("END OF FILE")) != 0)) //如果没到文件尾部,继续从文件中读取数据
- {
-
- fread(new_rec_ptr->first_name, sizeof(first_name), 1, fp); //从文件中读入'名'信息
-
- fread(new_rec_ptr->phone_num, sizeof(phone_num), 1, fp); //从文件中读入'电话号码'信息
- fread(new_rec_ptr->time_num, sizeof(time_num), 1, fp);
-
- insert_node(new_rec_ptr); //调用函数将记录插入节点
- }
- else // 如果到达文件尾部,释放最近申请的空间,并标记end_loop变量
- {
- delete new_rec_ptr;
- end_loop = 1;
- }
- }
- else // 如果申请空间出错,给出警告,并标记end_loop变量
- {
- cout << "警告: 申请空间出错. 从硬盘读入数据不成功!\n";
- end_loop = 1;
- }
- } while (end_loop == 0); // 循环直到end_loop为1,退出
- fclose(fp); // 关闭文件.
-
- } // 结束 load_list_from_file 函数
这里给每个用户都建立 了一个txt文件用于存放数据,文件名就为用户名.txt。
Step4:读取数据后,进入主界面。
整个程序设计思路简单,但其中蕴含的一些小的技巧和细节是值得深入研究的,一些底层内容(比如数据流的处理,数据文件的写入与读取、链表的建立)都需要一定的C++基础以及逐句分析代码的能力。该程序将上传github(https://github.com/renzhangjun/C-),可直接运行主文件夹中的“电话簿程序2.0.exe”来测试其效果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。