项目显示视频地址:【基于GEC6818开发板的餐厅点餐系统项目演示】 https://www.bilibili.com/video/BV1aC4y1V79Q/?share_source=copy_web&vd_source=4d0afc24dfef0496a61c04a2665aed83
前言
这是基于GEC6818开发板实现的点餐系统,是个人结合老师的上课内容自行制作,其中封装好的类和库有一些并不是我自己全部写出来的,更多的是在老师的指导下完成的,作为本人第一篇csdn文章,所以分享出来供大家学习讨论。
一、封装各种项目所需要的类
因为该项目涉及到很多功能类,所以在做这个项目之前,我就已经把需要用到的类全部封装好了,比如说线程类,字库类,lcd类,touch触摸屏类,bmp类等等,做这个项目的目的是巩固c++所学的知识,熟悉c++的三大特性,封装、继承、多态,在这个项目中,会大量使用到c++的封装和继承,所以很适合拿来巩固所学。
一些封装好的类如下:thread.cpp(线程类)
#include "thread.h"
void*task(void*arg) //void*arg = this
{
//th 基类的指针 指向的是 派生类对象
Thread*th = (Thread*)arg;
//通过基类的指针 调用的是 派生类对象中的虚函数
th->run();
}
Thread::Thread()
{
//这个标记位用来停止线程
isExit = false;
}
void Thread::start()//void start(TimeThread*this)
{
pthread_create(&th,NULL,task,this);
pthread_detach(th);
}
void Thread::stop()
{
isExit = true;
}
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <pthread.h>
#include <unistd.h>
void*task(void*arg);
//设计一个线程基类,规范 所有的线程 启动线程 停止线程都用统一的接口
class Thread
{
public:
Thread();
//启动线程
void start();
//停止线程
void stop();
//线程要执行的任务,把该虚函数声明为纯虚函数
virtual void run() = 0;
protected:
pthread_t th;
bool isExit;
};
#endif // THREAD_H
bmp.cpp类
#include "bmp.h"
Bmp::Bmp(const char *fileName, int *lcd_p)
{
strcpy(m_fileName,fileName);
m_mp = lcd_p;
//获取文件的属性
getInfo();
}
Bmp::~Bmp()
{
}
void Bmp::getInfo()
{
//2、打开bmp图片文件
int bmpfd = open(m_fileName,O_RDONLY);
if(bmpfd == -1)
{
perror("open bmpfile error");//perror将错误的原因打印出来
return ;
}
//读取bmp图片的文件头 + 信息头数据
BITMAPFILEHEADER fileHead;
BITMAPINFOHEADER infoHead;
read(bmpfd,&fileHead,14);//读取14个字节
read(bmpfd,&infoHead,40);//读取40个字节
m_width = infoHead.biWidth;
m_height = infoHead.biHeight;
std::cout<<"m_width:"<<m_width<<"m_height:"<<m_height<<std::endl;
close(bmpfd);
}
void Bmp::setFileName(const char *fileName)
{
memset(m_fileName,0,sizeof(m_fileName));
strcpy(m_fileName,fileName);
//获取文件的属性
getInfo();
}
void Bmp::setFileName(char *fileName)
{
memset(m_fileName,0,sizeof(m_fileName));
strcpy(m_fileName,fileName);
//获取文件的属性
getInfo();
}
void Bmp::show(int x,int y)
{
char bmpbuf[m_width*m_height*3];
int tempbuf[m_width*m_height];
int i,j;
int bmpfd = open(m_fileName,O_RDONLY);
if(bmpfd == -1)
{
perror("open bmp failed");
return ;
}
lseek(bmpfd,54,SEEK_SET);
read(bmpfd,bmpbuf,m_width*m_height*3);
//颜色值顺序 BGR--ARGB
for(i=0,j=0; i<m_width*m_height; i++,j+=3)
{
tempbuf[i] = 0x00<<24|bmpbuf[j+2]<<16|bmpbuf[j+1]<<8|bmpbuf[j]<<0;
}
//上下颠倒
for(j=0; j<m_height; j++)
{
for(i=0; i<m_width; i++)
{
m_mp[(j+y)*800+i+x] = tempbuf[(m_height-1-j)*m_width+i];
}
}
close(bmpfd);
}
bmp.h
#ifndef BMP_H
#define BMP_H
#include <iostream>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <stdbool.h>
#include "lcd.h"
#define UINT unsigned short
#define DWORD unsigned int
#define LONG unsigned int
#define WORD unsigned short
#define BMP_WIDTH 800
#define BMP_HEIGHT 480
class Bmp
{
//前面14个字节 文件信息头
typedef struct tagBITMAPFILEHEADER{
UINT bfType; //2
DWORD bfSize; //DWORD 4
UINT bfReserved1; //2
UINT bfReserved2; //2
DWORD bfOffBits; //DWORD 4
}BITMAPFILEHEADER;
//40个字节 位图信息头
typedef struct tagBITMAPINFOHEADER{ /* bmih */
DWORD biSize; //DWORD 4
LONG biWidth; //4 图像的宽度
LONG biHeight;//4 图像的高度
WORD biPlanes; //2
WORD biBitCount;//2
DWORD biCompression; //DWORD 4
DWORD biSizeImage; //DWORD 4
LONG biXPelsPerMeter;//4
LONG biYPelsPerMeter;//4
DWORD biClrUsed; //DWORD 4
DWORD biClrImportant; //DWORD 4
}BITMAPINFOHEADER;
public:
Bmp(const char*fileName,int*lcd_p=Lcd::instance()->addr());
~Bmp();
void getInfo();
void setFileName(const char*fileName);
void setFileName(char*fileName);
void show(int x=0,int y=0);
private:
char m_fileName[256];
int m_width;
int m_height;
int *m_mp;
};
#endif // BMP_H
为了避免文章篇幅过长,剩下的类就不一一展示
二、总体思路
在做项目之前,首先我们得先把项目的总体框架想好,既然需要的模块都已经准备好了,那就得会用起来才行,c++是一门面向对象的语言,我们需要把项目的实现分界成一个个对象,上面封装好的类就是一个个对象了,我们现在就需要使用接口把这些对象联系起来。
既然我们的项目是多个界面的,那就得用到多线程,不如一个界面就对应一个线程,所以我们首先需要弄一个界面基类,那就是widge,让这个类继承thread线程基类,后面我们设计各个界面的时候再继承widg类,然后重写thread类的run函数,就可以实现每个界面执行不同的任务,也就是每个不同的界面会有不同的菜品啊什么的,点击的位置也不一样,具体看你怎么设计。
1.设计一个界面基类
widget.cpp代码如下(示例):
#include "widget.h"
Widget::Widget()
{
m_bgName = "./res/bg.bmp";
}
void Widget::run()
{
std::cout<<" Widget::run()"<<std::endl;
while(1)
{
Bmp b1(m_bgName.data());
b1.show();
Touch touch;
int x,y;
touch.wait(x,y);
}
}
void Widget::show()
{
this->start();//启动线程
}
void Widget::exec()
{
this->run();
}
void Widget::setStyleSheet(std::string style)
{
m_bgName = style;
}
wedget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "bmp.h"
#include "touch.h"
#include "thread.h"
#include <string>
#include <iostream>
//界面基类
class Widget:public Thread
{
public:
Widget();
void run() override;
void show();//显示接口 - 线程运行 --界面不会阻塞
void exec();//显示接口---界面会阻塞
void hide();//隐藏
void setStyleSheet(std::string style);//设置界面的背景图片
protected:
std::string m_bgName;// 界面的背景图片
};
#endif // WIDGET_H
2.让自己写的界面类继承界面基类
比如我有一个界面是专门卖饮料的,那这个界面就需要继承widg
比如drink.cpp
#include "drink.h"
Drink::Drink()
{
m_bgName = "./res/4.bmp";
}
void Drink::run()
{
std::cout<<" Drink::run()"<<std::endl;
while(1)
{
int state = 0;
Bmp b1(m_bgName.data());
b1.show();
MFont* mk1 = MFont::instance();
std::string result = std::to_string(Shopnum::chengzhi);
//将string类型的数据转化为const char* 类型的数据,然后用字库进行显示
const char* ret1 = result.c_str();
mk1->show(ret1,24,20,20,-1,445,90,0,0,-16777216);
MFont* mk2 = MFont::instance();
std::string result2 = std::to_string(Shopnum::guozhi);
//将string类型的数据转化为const char* 类型的数据,然后用字库进行显示
const char* ret2 = result2.c_str();
mk2->show(ret2,24,20,20,-1,510,90,0,0,-16777216);
MFont* mk3 = MFont::instance();
std::string result3 = std::to_string(Shopnum::maidong);
//将string类型的数据转化为const char* 类型的数据,然后用字库进行显示
const char* ret3 = result3.c_str();
mk1->show(ret3,24,20,20,-1,445,295,0,0,-16777216);
MFont* mk4 = MFont::instance();
std::string result4 = std::to_string(Shopnum::pijiu);
//将string类型的数据转化为const char* 类型的数据,然后用字库进行显示
const char* ret4 = result4.c_str();
mk2->show(ret4,24,20,20,-1,510,295,0,0,-16777216);
Touch touch;
int x,y;
touch.wait(x,y);
if(x>235 && x<270 && y>235 && y<265)
{
//爆浆鸡爪饭加一
state = 1;
}
else if(x>440 && x<470 && y>225 && y<265)
{
//爆浆鸡爪减一
state = 2;
}
else if(x>500 && x<530 && y>230 && y<260)
{
//爆香大龙虾加一
state = 3;
}
else if(x>700 && x<740 && y>230 && y<260)
{
//爆香大龙虾减一
state = 4;
}
else if(x>235 && x<270 && y>435 && y<480)
{
//麻辣牛蛙加一
state = 5;
}
else if(x>440 && x<470 && y>435 && y<480)
{
//麻辣牛蛙减一
state = 6;
}
else if(x>500 && x<530 && y>435 && y<480)
{
//爆炒猪耳朵加一
state = 7;
}
else if(x>700 && x<740 && y>435 && y<480)
{
//爆炒猪耳朵减一
state = 8;
}
else if(x>35 && x<170 && y>35 && y<95)
{
//热销
state = 9;
}
else if(x>35 && x<170 && y>125 && y<185)
{
//主食
state = 10;
}
else if(x>35 && x<170 && y>215 && y<275)
{
//小菜
state = 11;
}
else if(x>35 && x<170 && y>305 && y<365)
{
//饮料
state = 12;
}
else if(x>35 && x<170 && y>395 && y<455)
{
//购物车
state = 13;
}
switch (state) {
case 1:
std::cout<<"海克斯橙汁加一"<<std::endl;
Shopnum::chengzhi++;
std::cout << "海克斯橙汁数量:"<<Shopnum::chengzhi << std::endl;
break;
case 2:
{
std::cout<<"海克斯橙汁减一"<<std::endl;
if(Shopnum::chengzhi >= 1)
{
Shopnum::chengzhi--;
}
std::cout << "海克斯橙汁数量:"<<Shopnum::chengzhi << std::endl;
break;
}
case 3:
std::cout<<"海克斯果汁加一"<<std::endl;
Shopnum::guozhi++;
std::cout << "海克斯果汁数量:"<<Shopnum::guozhi << std::endl;
break;
case 4:
{
std::cout<<"海克斯果汁减一"<<std::endl;
if(Shopnum::guozhi >= 1)
{
Shopnum::guozhi--;
}
std::cout << "海克斯果汁数量:"<<Shopnum::guozhi << std::endl;
break;
}
case 5:
std::cout<<"海克斯脉动加一"<<std::endl;
Shopnum::maidong++;
std::cout << "海克斯脉动数量:"<<Shopnum::maidong << std::endl;
break;
case 6:
{
std::cout<<"海克斯脉动减一"<<std::endl;
if(Shopnum::maidong >= 1)
{
Shopnum::maidong--;
}
std::cout << "海克斯脉动数量:"<<Shopnum::maidong << std::endl;
break;
}
case 7:
std::cout<<"海克斯啤酒加一"<<std::endl;
Shopnum::pijiu++;
std::cout << "海克斯啤酒数量:"<<Shopnum::pijiu << std::endl;
break;
case 8:
{
std::cout<<"海克斯啤酒减一"<<std::endl;
if(Shopnum::pijiu >= 1)
{
Shopnum::pijiu--;
}
std::cout << "海克斯啤酒数量:"<<Shopnum::pijiu << std::endl;
break;
}
case 9:{
Hot h;
h.exec();
break;
}
case 10:{
Zhushi z;
z.exec();
break;
}
case 11:{
Xiaocai x;
x.exec();
break;
}
case 12:{
break;
}
case 13:{
Shoping s;
s.exec();
break;
}
}
}
}
drink.h
#ifndef DRINK_H
#define DRINK_H
#include "widget.h"
#include "zhushi.h"
#include "photowidget.h"
#include "xiaocai.h"
#include "shoping.h"
#include "hot.h"
#include "shopnum.h"
//继承Widget
class Drink:public Widget
{
public:
Drink();
void run() override;
};
#endif // DRINK_H
别的界面类都是一个道理,这里就不需要进行一一列举。
3.设计一个应用程序管理类
这个类其实就是main.cpp里面的主线程,这个类对象可以什么都不干,但是需要设置一个循环阻塞住主线程,不然main函数里面的主线程退出之后,子线程(也就是界面类)也会跟着退出。
application.cpp
#include "application.h"
Application::Application()
{
}
int Application::exec()
{
int data = 0;
while(1){
::sleep(3);
//std::cout<<"parent pthread:"<<data++<<std::endl;
}
}
上面的exec()会持续不断的睡眠,以达到阻塞的目的。
main.cpp
#include <iostream>
#include "application.h"
#include "mainwidget.h"
#include "shopnum.h"
using namespace std;
int main()
{
Application app;//实例化一个应用程序管理类的对象 --主线程
//实例化一个主界面类的对象 ---子线程 UI线程
MainWidget w;
//显示
w.show();
return app.exec();
}
总结
总体来说整个项目并不难,特别是你把一些功能模块都封装好了以后,要做什么项目需要用到就可以直接拉过来用,并不需要重新编写,相比与c,代码的复用性大大提高,一开始我也觉得这个项目很难,也在各个网站上找别人做的项目来参考,后面发现多数项目的代码都很臃肿,因为很多都是用c语言编写的。后面我决定自己做了,后面在老师的指导下,按照老师给的框架去弄这个项目,弄好了之后发现确实不难,是一个很合适的练习题目,用来熟悉c++语言相当不错。这是我写的第一篇文章,希望不是最后一篇文章,我也想做一个代码大佬,可惜现在还是个小白,那就一步一步开始吧,在这记录我的学习历程,跟码友们一起进步!
项目源代码 提取:
链接:https://pan.baidu.com/s/1MEHaJOfiNy1EYSxPOWaBKw
提取码:zlw6
tcp服务器端源码提取:
链接:https://pan.baidu.com/s/1IygXzR32iRZQP2mUFXJsTw
提取码:zlw6
项目所有东西打包提取
链接:https://pan.baidu.com/s/1vKmyumfmjp8z5bW6IvhuSw
提取码:zlw6