高精度表达式计算器Qt

表达式计算器是一个由C++ with QT编写的应用程序。这个计算器旨在通过引入表达式计算的概念解决Windows默认计算器显示不直观的问题,提供了普通型计算器、科学型计算器的功能,并且可以通过页面管理机制快速添加自定义的新页面。

特性

  • 普通型计算器提供了基础的表达式四则运算功能,当按下计算按键时计算器将自动解析数学表达式并给出结果。
  • 科学型计算器提供了乘方、开根、对数等运算,同时内置一个函数解析器,能够将表达式中形如sqrt(2)的数学函数解析为数字结果。
  • 随机数生成器提供了三种随机数生成方式,C++98方式、C++11方式与CPU随机数生成器方式。其中前两者都为线性同余法生成的伪随机数,但是分别由C++98与C++11方法实现,后者生成最大范围较大。第三种方法调用RDRAND指令由CPU真随机数生成器生成随机数,此功能需要CPU硬件支持。
  • 计算器历史记录功能,能够显示计算历史记录。
  • 固定显示最前,最大化等窗口功能。
  • 高精度功能,由高精度算法(百度百科、Wiki)实现,可以进行int64_tlong double范围之外的数学运算,其中加减乘除由竖式运算规则实现,为精确解。乘方、开根、对数等运算会被拆解为int64_tlong double范围内数字,由QtMath库计算,为泰勒展开实现的近似解。

开始

环境需求

  • Windows 10及以上 (QT6最低支持)
  • 图标均来源于阿里图库
  • MinGW (项目构建使用版本为11.2.0)
  • 安装QT6(项目构建使用版本为6.2.4)
    • 前往QT官方网站https://www.qt.io/zh-cn/
    • 下载并且安装QT
    • 在GitHub上下载本项目源代码
    • 在QT Creator中打开./cal.pro
    • 构建项目

项目结构

名称空间CalEngine

CalEngine内置了计算器所用到的算法,包括高精度算法、表达式的检验与计算、随机数生成等。

名称空间PageEngine

PageEngine内部是与页面有关的部分,包括一些自定义窗口控件、Windows API部分的封装、页面管理类等。

名称空间Pages

Pages内部是不同页面的实际实现内容

名称空间UI

UI内为此计算器的所有UI界面,包括头文件、源文件与UI文件。

补充知识:

槽是普通的 C++ 成员函数,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
既然槽是普通的成员函数,因此与其它的函数一样,它们也有存取权限。槽的存取权限决定了谁能够与其相关联。同普通的 C++ 成员函数一样,槽函数也分为三种类型,即 public slots、private slots 和 protected slots。

public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确的传递。
protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部。
private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。这适用于联系非常紧密的类。
槽也能够声明为虚函数,这也是非常有用的。

override:如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。

QPropertyAnimation的基本使用QPropertyAnimation类定义了Qt的属性动画。构造一个 QPropertyAnimation 对象。parent 传递给 QObject 的构造函数。

常用接口函数
setTargetObject:设置仿真对象
setPropertyName:设置仿真属性的名称,
setDuration:设置仿真持续的时间
setStartValue:设置初始值
setEndValue:设置结束值
start:开始仿真
currentValue:返回当前值
setKeyValueAt:设置关键点的值
valueChanged:只要仿真追踪的值发生变化,就发送该信号

Qt基础之位置相关的函数

x(),y(),pos()函数都是获取窗体左上角的坐标位置。
frameGeometry()函数获取的是整个窗体的左上顶点和长、宽值。
geometry()函数获取的是窗体内中央区域的左上顶点和长、宽值。
width(),height()函数获取的是中央区域的长、宽值。
rect(),size()函数获取的结果都是对于窗体的中央区域而言。size()函数获取窗体中央区域的长、宽值
rect()函数和geometry()函数相同,返回一个QRect对象,两个函数获取的长、宽值相同。不同的是左顶点,rect()左顶点坐标始终为(0,0),而geometry()其左顶点坐标是相对于父窗体而言。

QPointF与QPoint区别
QPoint表示一个平面上整数精度的点坐标,可以通过*x(),y()*等函数方便的进行存取操作,另外也重载了大量的运算符,使其可以作为一般的常数一样进行运算。另外其也可以表征为向量,可进行向量的相关运算例如乘除以及长度的计算。
QPointF在浮点精度上表征平面上的点,绝大部分操作都是与QPoint相类似的,细微的差别在于运算符重载以及提供了QPoint与QPointF的相互转换。

MainWindow

1.创建ui文件

将一起创建MainWindow.h、MainWindow.cpp、MainWindow.ui(界面文件)
绘制ui界面

2.函数与方法

①槽函数与信号创建

private slots://在这个区内声明的槽意味着只有类自己可以将信号与之相连接。这适用于联系非常紧密的类。//槽也能够声明为虚函数,这也是非常有用的。void on_fixFront_clicked();                                    //窗口强制置顶void on_maximum_clicked();                                     //窗口最大化void on_minimum_clicked();                                     //窗口最小化void on_shutdown_clicked();                                    //关闭程序void on_showHis_clicked();                                     //显示历史记录void on_modeSwitchButton_clicked();                            //打开功能选择栏public slots://在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,//你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确的传递。void getReturnedMode(int selectedMode);                        //从功能选择栏获取选项void updateHistoryPage(QString);                               //跟新历史记录页面signals:void functionChooseBarOpenAnimation();                         //控制功能选择栏开启动画void functionChooseBarCloseAnimation();                        //控制功能选择栏结束动画

②创建鼠标事件

private:void mouseMoveEvent(QMouseEvent *event) override;              //重写鼠标移动事件void mousePressEvent(QMouseEvent *event) override;             //重写鼠标按压事件void mouseReleaseEvent(QMouseEvent *event) override;           //重写鼠标释放事件void resizeEvent(QResizeEvent *event) override;

③属性

    FunctionChooseBar *mChooseWidget;                              //功能选择栏目Ui::MainWindow *ui;                                            //初始化uiPageEngine::PageManager mPagelist;                             //页面管理器int mWindowWidth;                                              //窗口宽度int mWindowHeight;                                             //窗口高度int mLastWidth;                                                //上次窗口宽度int mLastHeight;                                               //上次窗口高度int8_t mMouseState = 0;                                        //鼠标状态QPoint mMousePosition;                                         //鼠标位置bool mIsMaximum = false;                                       //是否最大化bool mIsShowFront = false;                                     //是否置顶显示bool mIsShowHis = false;                                       //是否打开历史栏bool mIsMousePressed = false;                                  //鼠标是否按下

3.实现函数与方法

1)初始化相关内容
    //-----------------------初始化相关内容------------------------------//ui->setupUi(this);this->setAttribute(Qt::WA_TranslucentBackground,true);             //透明背景this->setWindowFlags(Qt::FramelessWindowHint);                     //无边框窗口//鼠标跟踪生效,即使鼠标按键没有被按下,窗口部件也会接收鼠标移动事件。鼠标只要移动就会触发mouseMoveEvent(QMouseEvent *event)ui->centralwidget->setMouseTracking(true);centralWidget()->setMouseTracking(true);                           //开启鼠标轨迹追踪ui->HistoryPage->hide();                                           //隐藏历史记录栏
2)初始化页面相关内容
    //----------------------页面相关内容---------------------------------//mChooseWidget = new FunctionChooseBar;                             //创建功能选择栏mChooseWidget->setParent(this);mChooseWidget->hide();                                             //默认隐藏选择界面mPagelist.addCalPage(new NormalCalculatorPage,ui->NormalCal);      //普通计算器mPagelist.addCalPage(new ScienceCalPage,ui->ScienceCal);           //科学计算器mPagelist.addCalPage(new ProgrammerCalPage,ui->ProgrammerCal);     //程序员计算器mPagelist.addCalPage(new RandomPage,ui->RandomCal);                //随机数计算器mPagelist.setCalPageFocus(0);ui->CalStackedWidget->setCurrentIndex(0);
3)初始化信号与槽的内容
    //----------------------信号与槽相关内容-----------------------------//connect(mChooseWidget,SIGNAL(emitSelectedModes(int)),this,SLOT(getReturnedMode(int)));              //接受功能选择结果connect(this,SIGNAL(functionChooseBarOpenAnimation()),mChooseWidget,SLOT(showOpenAnimation()));     //控制动画开启效果connect(this,SIGNAL(functionChooseBarCloseAnimation()),mChooseWidget,SLOT(showCloseAnimation()));   //控制动画关闭效果
4)WindowAPI相关内容
    //----------------------接下来是WindowAPI相关内容--------------------//WindowsApi::RGBColor tempColor;try{tempColor=WindowsApi::getSystemColor(); //通过WindowsAPI获取系统配色}catch (CalEngine::CalException &exp){tempColor={169,169,169};//默认颜色}try{WindowsApi::setWindowBlurEffect(HWND(winId()));//实现毛玻璃效果,winId()为qt中获得当前窗口句柄的函数}catch (CalEngine::CalException &exp){}
5)qss样式表相关内容
 //---------------------接下来是qss样式表相关内容-----------------------//
QString centralWidgeStyleSheet=QString("QWidget{background-color: rgba(245,245, 245,220);border:1px solid rgb(")+QString::number(tempColor.r)+QString(",")+QString::number(tempColor.g)+QString(",")+QString::number(tempColor.b)+QString(");}");centralWidget()->setStyleSheet(centralWidgeStyleSheet);//设置带系统配色边框的窗口//构造函数中无法获取正确的窗口大小,所以要延时处理QTimer::singleShot(100,this, [=]() {mPagelist.setCalPageSize(ui->CalStackedWidget->width(),ui->CalStackedWidget->height());});
6)重写鼠标事件
①鼠标移动事件
void MainWindow::mouseMoveEvent(QMouseEvent *event)                              //重写鼠标移动事件
{/** 如果移动鼠标,会发生move事件,button返回Qt::NoButton,buttons返回LeftButton;                    mMouseState == 0* 再按下右键,会发生press事件,button返回RightButton,buttons返回LeftButton | RightButton;        mMouseState == 2* 再松开左键,会发生Release事件,button返回LeftButton,buttons返回RightButton。                    mMouseState == 1*/if(mIsMousePressed)                                                          //默认false,鼠标未按下{if(mMouseState == 0)                                                     //移动位置{QPointF displacement = event->globalPosition() - mMousePosition;     //使用dispalacement接受全局地址与鼠标地址的距离差,用于确定鼠标位置this->move(displacement.toPoint());                                  //toPoint()由浮点精度转化为整数精度}else if(mMouseState == 1)                                                //改变宽度{int displacementX = event->pos().x() - this->rect().width();if(this->rect().width() + displacementX >= 250){//鼠标光标位置在x>=250的位置时可以修改窗口的宽度this->resize(this->rect().width() + displacementX,this->rect().height());}}else if(mMouseState == 2){int displacementY = event->pos().y() - this->rect().height();if(this->rect().height() + displacementY >= 500){//鼠标光标位置在x>=500的位置时可以修改窗口的高度this->resize(this->rect().width(),this->rect().height() + displacementY);}}else{//当鼠标光标位于右下角时可以同时修改窗体的宽度和高度int displacementX = event->pos().x() - this->rect().width();if(this->rect().width() + displacementX >= 250){this->resize(this->rect().width() + displacementX,this->rect().height());}int displacementY = event->pos().y() - this->rect().height();if(this->rect().height() + displacementY >= 500){this->resize(this->rect().width(),this->rect().height() + displacementY);}}mWindowWidth = this->rect().width();mWindowHeight = this->rect().height();}else{//鼠标光标的x,y坐标位置与主窗口位置的距离差的绝对值int _x = abs(event->pos().x() - this->rect().width());int _y = abs(event->pos().y() - this->rect().height());if(_x < 10 && _y < 10){this->setCursor(Qt::SizeFDiagCursor);                              //用于在左上角和右下角对角调整顶层窗口的大小mMouseState = 3;}else if(_x < 5 && _y < 5 && event->pos().y() > ui->TitleBar->height()){this->setCursor(Qt::SizeHorCursor);                                //用于水平调整顶层窗口的大小。mMouseState = 1;}else if(_x > 5 && _y < 5){this->setCursor(Qt::SizeVerCursor);                                //用于垂直调整顶层窗口的大小mMouseState = 2;}else{this->setCursor(Qt::ArrowCursor);                                  //标准箭头光标mMouseState = 0;}}
}
②鼠标按压事件
void MainWindow::mousePressEvent(QMouseEvent *event)                           //重写鼠标按压事件
{ui->TitleBar->resize(this->rect().width() - 10,ui->TitleBar->height());mMousePosition = event->pos();mIsMousePressed = true;                                                    //鼠标按下mPagelist.setCalPageFocus(ui->CalStackedWidget->currentIndex());           //按下事件结束焦点还给窗口
}
③鼠标释放事件
void MainWindow::mouseReleaseEvent(QMouseEvent *event)                         //重写鼠标释放事件
{mIsMousePressed = false;this->setCursor(Qt::ArrowCursor);                                          //标准光标mMouseState = 0;mPagelist.setCalPageFocus(ui->CalStackedWidget->currentIndex());           //归还焦点
}
7)重写私有槽函数
①窗口强制置顶
void MainWindow::on_fixFront_clicked()                                   //窗口强制置顶
{QWindow* pWin = this->windowHandle();if(!mIsShowFront){//改变位置QRect systemRect = QGuiApplication::primaryScreen()->geometry();short systemWidth = systemRect.width();this->setGeometry(systemWidth - mWindowWidth - 100,100,mWindowWidth,mWindowHeight);//调用setWindowFlags接口来实现窗口的置顶和取消//setWindowFlags(nowflags | Qt::WindowStaysOnTopHint);//this->show();pWin->setFlags(Qt::Widget | Qt::WindowStaysOnTopHint);mIsShowFront=true;}else{/** 原方案:* setWindowFlags(nowflags & ~Qt::WindowStaysOnTopHint);* this->show();* 重新调用show()将窗口设置为可见,导致置顶过程中窗口会闪烁* 解决方案* 获取QWidget的window,再对window设置置顶属性!* QWindow* pWin = this->windowHandle();*/pWin->setFlags(Qt::Widget);mIsShowFront=false;}
}
②窗口最大化
void MainWindow::on_maximum_clicked()                                      //窗口最大化
{if(!mIsMaximum){this->showMaximized();mIsMaximum = true;}else{this->showNormal();mIsMaximum = false;}
}
③窗口最小化
void MainWindow::on_minimum_clicked()                                      //窗口最小化
{this->showMinimized();
}
④关闭窗口退出程序
void MainWindow::on_shutdown_clicked()                                     //关闭程序
{qApp->exit();
}
⑤显示历史记录
void MainWindow::on_showHis_clicked()                                      //显示历史记录
{if(mIsShowHis == false){ui->HistoryPage->show();mWindowWidth+=250;mIsShowHis = true;}else{ui->HistoryPage->hide();mWindowWidth-=250;mIsShowHis = false;}this->resize(mWindowWidth,mWindowHeight);
}
⑥打开功能选择栏
void MainWindow::on_modeSwitchButton_clicked()                            //打开功能选择栏
{mChooseWidget->resize(150,this->rect().height());mChooseWidget->show();emit functionChooseBarOpenAnimation();
}
8)重写公共槽函数
①从功能选择栏获取选项
void MainWindow::getReturnedMode(int selectedMode)                       //从功能选择栏获取选项
{ui->CalStackedWidget->setCurrentIndex(selectedMode);this->mPagelist.setCalPageFocus(selectedMode);
}
②更新历史记录界面
void MainWindow::updateHistoryPage(QString willupdate)                    //跟新历史记录页面
{ui->HisBrowser->append(willupdate);
}
③更新主窗口
void MainWindow::resizeEvent(QResizeEvent *event)
{mPagelist.setCalPageSize(ui->CalStackedWidget->width(),ui->CalStackedWidget->height());
}

CalEngine

CalEngine内置了计算器所用到的算法,包括高精度算法、表达式的检验与计算、随机数生成等。

CalException异常检验

1.CalException类继承于QException基类(QException类为可跨线程传输的异常提供了基类)
2.定义了一个名字为ErrorName的枚举,可以通过这个枚举类型定义一个名为mError的枚举变量。
enum枚举
使用方法:

1)直接定义枚举值,然后给普通变量赋值。

enum
{yesterday,nowDay,tomorrow
};
int main()
{unsigned char day;day = nowDay;printf("day=%u\r\n",day);
}

定义了一个枚举类型,但是没有给枚举类型命名。
像这种枚举呢就是没办法定义枚举变量的,但是呢,这样其实也是可以用的。
我们通过定义一个普通变量,然后把枚举的值赋给他,一样也能输出正确的值,这种方法我个人经常偷懒地用。

2)定义带名称的枚举

enum tDay
{yesterday,nowDay,tomorrow
};
int main()
{enum tDay day = nowDay;printf("day=%u\r\n",day);
}

定义了一个名字为tDay的枚举,那么我们就可以通过这个枚举类型定义一个名为day的枚举变量。

3)定义枚举别名

typedef enum
{yesterday,nowDay,tomorrow
}tDay;
int main()
{tDay day;day = nowDay;printf("day=%u\r\n",day);
}

这个代码中通过typedef来定义枚举的别名为tDay,然后我们直接使用tDay day来定义一个enum变量,这种方法用的是最多的
3.有参构造函数CalException和获取错误名称getErrorName()

关键代码:

enum ErrorName
{//默认错误、语法错误、参数错误、括号匹配错误、操作符错误、格式错误、未知错误    CalDefaultError,CalSyntaxError,CalParameterError,CalBracketError,CalOperatorError,CalSystemError,CalUnknownError
};
class CalException:public QException
{
private:ErrorName mError;
public:CalException(const ErrorName inpErr);//获取错误名称ErrorName getErrorName();
};

Rational高精度算法

1.创建数字

    //构造函数Rational();                                                //创建空数字Rational(const Rational &inputNumber);                    //从Rational创建数字Rational(const QString &inputNumber);                      //从字符串创建数字Rational(const int32_t &inputNumber);                      //从int32创建数字Rational(const long double &inputNumber);                  //从double64创建数字Rational(const double &inputNumber);Rational(const char* inputNumber);                         //从char*创建数字

2.实现高精度算法的相关函数

    void setNumber(const QString &inputNumber);                 //设置高精度数bool hasNumber();                                           //检查是否存有高精度数QString getNumber()const;                                   //输出高精度数为字符串QString getDecimal()const;                                  //单独获取小数部分QString getInteger()const;                                  //获取整数部分Rational getOppositeNumber()const;                          //获取相反数void clearNumber();                                         //清除存储的数字void setOppositeNumber();                                   //设置相反数void setPrecision(const int32_t precision);                 //设置精度bool isNegative()const;                                     //判断正负数

3.定义两个Vector分别用于存储小数

(从左往右为十分位、百分位、千分位)和整数部分(从左往右为个位,十位,百位),定义两个int32_t类型(int类型)的变量mDecimalSize和mIntegerSize分别指代小数位长度和整数位长度。定义一个布尔类型的mSign用于指代输入数字的正负(0为正1为负)

1字节 int8_t —— char
2字节 int16_t —— short
4字节 int32_t —— int
8字节 int64_t —— long long

4.实现高精度的算法

1)实现序列反转(实现位对齐,方便加法的运算模式)和去除前导零(删除位对位计算时,反转数组中多出的0,因为数组在为存满时,空闲位置默认为0)

//反转序列,方便一些对齐计算
template<class T>
//生成Vector内元素倒序序列并返回
QVector<T> Rational::getReverse(const QVector<T> &A)
{QVector<T> res;//QVector<T>::const_reverse_iterator 手动指定会报错,换成auto自动推导for (auto it = A.rbegin(); it != A.rend(); it++){res.push_back(*it);}return res;
}//删除前导零
void Rational::deleteLeadingZreo(Rational &A)const
{if(A.mIntegerSize>1){for (QVector<int8_t>::const_iterator it = A.mInteger.constBegin(); it != A.mInteger.constEnd()-1; it++){if(*it==0){A.mInteger.pop_front();}else{break;}}}for (QVector<int8_t>::const_reverse_iterator it = A.mDecimal.rbegin(); it != A.mDecimal.rend(); it++){if(*it==0){A.mDecimal.pop_back();}else{break;}}//更新大小A.mDecimalSize = A.mDecimal.size();A.mIntegerSize = A.mInteger.size();
}

2)高精度加法
算法思路:
①把大数存到字符串;
②字符串的每个字符数字都通过ASCII转换存到数组,注意的是要低位存在数组开头:a[i] = s[len-i-1]-‘0’;
③加法进位的算式:
a[i+1] += a[i]/10;
a[i] %= 10;
④数字溢出,长度+1;
⑤反向输出结果;

void Rational::rantionalAdd(const Rational &A, const Rational &B, Rational &Res)const
{//小数位相加-----从左往右为十分位百分位千分位int32_t maxSize = A.mDecimalSize > B.mDecimalSize ? A.mDecimalSize : B.mDecimalSize;int32_t addValue = 0;//进位数for (int32_t i = maxSize - 1; i >= 0; i--){int32_t value = addValue;if (i < A.mDecimalSize)value += A.mDecimal[i];if (i < B.mDecimalSize)value += B.mDecimal[i];addValue = value / 10;//因为是从高位到低位运算,所以要pushfrontRes.mDecimal.push_front(value % 10);}Res.mDecimalSize = maxSize;//整数位相加maxSize = A.mIntegerSize > B.mIntegerSize ? A.mIntegerSize : B.mIntegerSize;//获取相反序列,方便相加进位QVector<int8_t> rIntegerA = getReverse(A.mInteger), rIntegerB = getReverse(B.mInteger);for (int32_t i = 0; i < maxSize; i++){int32_t value = addValue;if (i < A.mIntegerSize)value += rIntegerA[i];if (i < B.mIntegerSize)value += rIntegerB[i];addValue = value / 10;Res.mInteger.push_front(value % 10);}if (addValue > 0){Res.mIntegerSize = maxSize + 1;Res.mInteger.push_front(addValue);}else{Res.mIntegerSize = maxSize;}Res.mSign = A.mSign;deleteLeadingZreo(Res);
}

3)高精度减法
算法思路:
①输入两个大数;
②判断大小,固定s1恒大于s2:
③获取长度;
④字符变整数:a[i] = s1[len1-i-1]-‘0’;
⑤减法运算:
if(a[i]<b[i]){
a[i+1]–; //上位–
a[i]+=10; // 本位+10
}
c[i] = a[i]-b[i];
⑥去除前导零;
⑦反向输出;

void Rational::rantionalSubtract(const Rational &A, const Rational &B, Rational &Res)const
{//测试数据 1.1 100.1  1.1 1000const Rational *numA=&A,*numB=&B;if(A==B)//A和B相等时候直接返回零{Res.setNumber("0");return;}if(RationalMath::abs(A)<RationalMath::abs(B))//使得较大的数为被减数{//使A恒大于BnumA=&B;numB=&A;Res.setOppositeNumber();//设置相反数}//开始计算小数部分int32_t needValue=0;//借位数int32_t maxSize=numA->mDecimalSize>numB->mDecimalSize?numA->mDecimalSize:numB->mDecimalSize;for(int32_t i=maxSize-1;i>=0;i--){int32_t value = -needValue;if (i < numA->mDecimalSize)//防止越位,需要判断一下value+=numA->mDecimal[i];if (i < numB->mDecimalSize)value-=numB->mDecimal[i];if(value<0){value+=10;needValue=1;}elseneedValue=0;Res.mDecimal.push_front(value);}Res.mDecimalSize=maxSize;//更新小数位数大小//开始计算整数部分maxSize=numA->mIntegerSize>numB->mIntegerSize?numA->mIntegerSize:numB->mIntegerSize;QVector<int8_t> rIntegerA = getReverse(numA->mInteger), rIntegerB = getReverse(numB->mInteger);//获取相反序列,方便相减借位for(int i=0;i<maxSize;i++){int32_t value = -needValue;if (i < numA->mIntegerSize)//防止越位,需要判断一下value+=rIntegerA[i];if (i < numB->mIntegerSize)value-=rIntegerB[i];if(value<0){value+=10;needValue=1;}elseneedValue=0;Res.mInteger.push_front(value);}Res.mIntegerSize=maxSize;//更新整数位数大小if(A.mSign==1)//说明两个输入数都为负数{Res.setOppositeNumber();}deleteLeadingZreo(Res);
}

4)高精度乘法
算法思路:
①把大数存到字符串;
②对两数想乘的长度求解。两数相乘最大的值长度不会超过两个数长度之和。 len_max = len1+len2-1;
③字符串的每个字符数字都通过ASCII转换存到数组,注意的是要低位存在数组开头:a[i] = s[len-i-1]-‘0’;
④乘法进位的算式:① c[i+j] += a[i] * b[j]
五对数组c进行加法进位:
c[i+1] += c[i]/10;
c[i] %= 10;
⑥结果溢出
⑦反向输出结果;

void Rational::rantionalMultiply(const Rational &A, const Rational &B, Rational &Res)const
{if(A.mSign^B.mSign)Res.mSign=1;Rational numA(A.getNumber().replace(".","")),numB(B.getNumber().replace(".",""));if(numA.mIntegerSize<numB.mIntegerSize)//确保A为位数多的{numA.setNumber(B.getNumber().replace(".",""));numB.setNumber(A.getNumber().replace(".",""));}//在Res中开辟空间for(int32_t i=0;i<numA.mIntegerSize+numB.mIntegerSize-1;i++)Res.mInteger.push_back(0);int32_t addValue=0;//进位数for(int32_t i=numB.mIntegerSize-1;i>=0;i--){for(int32_t j=numA.mIntegerSize-1;j>=0;j--){//进位数 加入数 现存数int32_t value=addValue+numA.mInteger[j]*numB.mInteger[i]+Res.mInteger[i+j];Res.mInteger[i+j]=value%10;addValue=value/10;}//处理进位while(addValue>0){if(i-1>=0)Res.mInteger[i-1]=addValue%10;elseRes.mInteger.push_front(addValue%10);addValue/=10;}}//小数点移位for(int i=A.mDecimalSize+B.mDecimalSize-1;i>=0;i--){//如果整数位只剩下零或空if((Res.mInteger.size()==1 && Res.mInteger[0]==0) || Res.mInteger.isEmpty()){Res.mDecimal.push_front(0);}else//如果整数还有数字{Res.mDecimal.push_front(Res.mInteger.back());Res.mInteger.pop_back();}}//补零,防止10*0.01特殊情况if(Res.mInteger.isEmpty()){Res.mInteger.push_back(0);}Res.mDecimalSize=Res.mDecimal.size();Res.mIntegerSize=Res.mInteger.size();deleteLeadingZreo(Res);
}

5)高精度除法
算法思路:
①定义存储数组。
②读入数据处理。
③试商过程。
④删除前导 0 。所谓前导零,就是出现类似这样数据 01234,这个 0 实际是不需要的。
⑤输出结果。倒序输出减法的结果数组 C,因为我们的个位是存储在下标为 0 的地方。

void Rational::rantionalDivide(const Rational &A, const Rational &B, Rational &Res,int32_t precision)const
{//测试数据 -1 -1  1 1.2//判断除零if(B.getNumber()=="0"){throw CalException(CalParameterError);}//处理符号if(A.mSign^B.mSign)Res.mSign=1;//除数化整Rational numA=A,numB=B;numA.mSign=0;numB.mSign=0;for(int i=0;i<B.mDecimalSize;i++){numB.mInteger.push_back(numB.mDecimal.front());numB.mDecimal.pop_front();if(numA.mDecimal.size()>0){numA.mInteger.push_back(numA.mDecimal.front());numA.mDecimal.pop_front();}else{numA.mInteger.push_back(0);}}//更新大小,去除前导零numA.mIntegerSize=numA.mInteger.size();numB.mIntegerSize=numB.mInteger.size();numA.mDecimalSize=numA.mDecimal.size();numB.mDecimalSize=numB.mDecimal.size();deleteLeadingZreo(numA);deleteLeadingZreo(numB);//开始除法计算//整数部分Rational nowValue("0");//记录当前被除的值for(int i=0;i<numA.mIntegerSize;i++){nowValue=nowValue*Rational("10")+Rational(numA.mInteger[i]);if(nowValue<numB){Res.mInteger.push_back(0);continue;}int8_t j=1;for(j=1;j<=9;j++){if(numB*Rational(j)>nowValue){break;}}Res.mInteger.push_back(j-1);nowValue=nowValue-numB*Rational(j-1);}//小数部分for(int i=0;i<numA.mDecimalSize;i++){nowValue=nowValue*Rational("10")+Rational(numA.mDecimal[i]);if(nowValue<numB){Res.mDecimal.push_back(0);continue;}int8_t j=1;for(j=1;j<=9;j++){if(numB*Rational(j)>nowValue){break;}}Res.mDecimal.push_back(j-1);nowValue=nowValue-numB*Rational(j-1);}//增加部分for(int i=0;i<precision;i++){if(nowValue.getNumber()=="0")//说明除尽{break;}nowValue=nowValue*Rational("10");if(nowValue<numB){Res.mDecimal.push_back(0);continue;}int8_t j=1;for(j=1;j<=9;j++){if(numB*Rational(j)>nowValue){break;}}Res.mDecimal.push_back(j-1);nowValue=nowValue-numB*Rational(j-1);}//答案去先导零,更新sizeRes.mIntegerSize=Res.mInteger.size();Res.mDecimalSize=Res.mDecimal.size();deleteLeadingZreo(Res);
}

5.运算符重载

    //运算符重载bool operator>(const Rational &A)const;                     //重载大于号bool operator>=(const Rational &A)const;                    //重载大于等于号bool operator<(const Rational &A)const;                     //重载小于号bool operator<=(const Rational &A)const;                    //重载小于等于号bool operator==(const Rational &A)const;                    //重载等于号bool operator!=(const Rational &A)const;                    //重载不等于号Rational operator+(const Rational &A)const;                 //重载加法Rational operator-(const Rational &A)const;                 //重载减法Rational operator*(const Rational &A)const;                 //重载乘法Rational operator/(const Rational &A)const;                 //重载除法void operator=(const Rational &A);                          //重载赋值号 深拷贝Rational& operator=(Rational &&A);                          //重载赋值号 浅拷贝operator QString();                                         //重载强制类型转换void operator+=(const Rational &A);void operator-=(const Rational &A);void operator*=(const Rational &A);void operator/=(const Rational &A);

RationalMath算术算法

1.设置运算的基本字符

//常量兀
static const Rational math_pi;
//自然常量e
static const Rational math_e;
//ln2
static const Rational math_ln2;
//ln5
static const Rational math_ln5;

2.算术算法

    static Rational abs(const Rational &A);                                     //绝对值static Rational ceil(const Rational &A);                                    //向上取整static Rational floor(const Rational &A);                                   //向下取整static Rational pow(Rational A,int32_t n);                                  //A的n方static Rational pow(const Rational &A,const Rational &B);                   //A的B方static Rational exp(const Rational &A);                                     //A次方static Rational exp_decimal(const Rational &A);                             //A分之1static Rational ln(const Rational &A);                                      //ln函数static Rational log(const Rational &A,const Rational &B);                   //log函数static Rational sqrt(const Rational &A);                                    //平方根static Rational sqrt(const Rational &Base,const Rational &Exp);static Rational factorial(const Rational &A);                               //阶乘

RandomProducer随机数算法

1.C98随机数实现

uint32_t producerC98Rand::getRandom()
{if(!isSrand){srand((int)time(NULL));isSrand=true;}return rand();
}

2.CPU随机数实现(内嵌汇编代码实现CPU的随机数)

uint32_t producerCPURand::getRandom()
{long flag=0;//内嵌汇编用法asm volatile("movl $1, %%eax \n" "cpuid \n" "and $0x20000000, %%eax \n" "test $0, %%eax \n" "jnz L \n" "movl $1,%0 \n" "L: \n" "movl $1,%0 \n":"=r"(flag)::"memory");/** R"(* 功能代码放在EAX寄存器* movl $1, %eax* cpuid* and $0x20000000, %eax* test $0, %eax* jnz L* movl $1,%[op1]* L:* )"*/if (!flag){throw CalException(CalSystemError);return 0;}/* asm(* R"(* .section .data* result : .int 0* .section .text* rdrand %eax* movabs %eax, result* )"* );*/uint32_t result;asm volatile("rdrand %%eax \n" "movl %%eax,%0 \n":"=r"(result)::"memory");return result;
}

3.C11随机数实现

uint32_t producerC11Rand::getRandom()
{if(randQue.empty()){std::default_random_engine c11random((int)time(NULL));for(short i=0;i<=100;i++)randQue.push_back(c11random());}long ans=randQue.front();randQue.pop_front();return ans;
}

4.实现用户接口类

randomInterface::randomInterface(AbstractRandomProducer::ENUM_RAND_METHOD choosedMethod)
{if (choosedMethod==AbstractRandomProducer::C98Rand)rnd = new producerC98Rand;else if (choosedMethod==AbstractRandomProducer::CPURand)rnd = new producerCPURand;elsernd = new producerC11Rand;
}randomInterface::~randomInterface()
{delete rnd;
}uint32_t randomInterface::getRandom(int minNum,int maxNum)
{long ans=minmax(rnd->getRandom(),minNum,maxNum);return ans;
}

ExpressionCalculation

该文件夹内用于管理页面关键的算法内容

1.AbstractExpressionCalculation

本类是表达式计算中对应NormalExpressionCalculation和ScienceExpressionCalculation创建的抽象类,它所有的虚函数都可以有选择性的在子类中重写,用于针对性的实例化子类。当实例化子类时,抽象类也会跟随着实例化;实现了对表达式的分析与计算,包括了归一化处理、函数表达式识别与转换、表达式中缀转后缀,以及后缀表达式的计算。同时,此类还针对普通计算器和科学型计算器进行了区别与适配。
补充知识点

QHash<QChar,int32_t> mOptPriority;
是用来存储(键,值)对的工具,并提供快速查找与键相关联的值的功能。
QHash提供了比QMap更快的查找。
在遍历QMap时,元素总是按键排序。在QHash时,元素在QHash内部的顺序是无序的。QMap内部是有序的QMap的键类型必须含有< ()运算符。
QHash的键类型必须能提供==()运算符和一个名为QHash()的全局散列函数(参见QHash)
向散列中插入元素的方法是使用insert():
hash.insert("twelve", 12);
要查找一个值,可以使用operator或value():
int num1 = hash["thirteen"];
int num2 = hash.value("thirteen");

注意事项: 变量mOptPriority决定了数学表达式中不同运算符的优先级,由于不同种类计算器中运算符均不同,所以该变量需要在具体的子类构造函数中进行赋值,赋值后所定义的值将以哈希值的形式存储于变量中。

1)合法性检验
存在非法字符、运算符错误、括号匹配错误、语法错误以及未知错误,当检测到错误时,将会抛出一个CalException型的异常。确保输入的表达式均为合法,保障后续计算能正常运行。

    AbstractExpressionCalculation::AbstractExpressionCalculation(QString inputExp){mExpression=inputExp;}bool AbstractExpressionCalculation::legitimacyTest(const QString &input)                     //legitimacy合法性测试{//判断表达式长度int n=input.size();//用于括号检验int k=0;for(int i=0;i<n;i++){//如果是左括号if(input[i] =='('){k++;if(i==n-1){//表达式末尾是左括号throw CalException(CalBracketError);}//如果是空括号组“()”else if(input[i+1] ==')'){throw CalException(CalBracketError);}
//isNumber(): 如果字符是数字(number_*类别,而不仅仅是0-9),则返回true;否则返回falseelse if(i!=0 && input[i-1].isNumber()){throw CalException(CalOperatorError);}}else if(input[i] ==')') //如果是右括号{k--;}if(k<0){//一旦出现右括号数量大于左括号直接判错throw CalException(CalBracketError);}}//检查括号是否正确匹配if(k!=0){//如果k = 0 左右括号相互匹配,k != 0出现多余的左右括号,抛出异常throw CalException(CalBracketError);}for(int i=0;i<n;i++){if(isOperator(input[i])){if(i==n-1)//表达式末尾是运算符{//操作符异常throw CalException(CalOperatorError);}//运算符后必须是数字或左括号else if(!( (input[i+1].isNumber()) ||input[i+1]=='(') ){throw CalException(CalOperatorError);}//表达式“1++1”之类//运算符不是数字和右括号或者为空else if(i==0 || (!input[i-1].isNumber() && input[i-1]!=')') ){throw CalException(CalOperatorError);}}}//检测是否有未处理成功的函数for(int i=0;i<n;i++){//isLetter(): 如果字符是字母(letter_*类别),则返回true;否则返回false。if(!isOperator(input[i]) && input[i].isLetter()){throw CalException(CalOperatorError);}if(input[i]==','){throw CalException(CalOperatorError);}}return 1;//表达式无误}

2)中缀转后缀(加快计算器的运算速度)
补充知识:
①后缀表达式严格按照从左到右进行计算的模式 符合计算机运行方式
②而中缀表达式需要计算机遇到符号后向后扫描一位 若为括号或优先级更高的操作符还需要向后继续扫描
中缀表达式便于人们的理解与计算,后缀表达式便于计算机的运算(如二叉树、堆栈的方法计算)
③ 因此在读取一个中缀表达式后,我们得办法将他转化为后缀表达式。
④提高计算机的运算效率

    QQueue<QString> AbstractExpressionCalculation::infixToPostfix(const QString &input)//中缀转后缀{QString temNum; //用于后缀表达式QQueue<QString> res; //用于存储输出值QStack<QChar> opera; //用于存储运算符int n=input.size();//该循环负责将input栈内元素全部分类 将中缀表达式的每个元素一一取出比较进行分类for(int i=0;i<n;i++)//该循环负责将input栈内元素全部分类{if(input[i] ==' ') //如果是空格{i++; //跳过该字符}//如果是数字(0-9和'.')else if ((input[i]<='9' && input[i]>='0') || input[i]=='.'){//将符合条件的数字(0-9和'.')直接接在temNum后temNum.append(input[i]);}else if(isOperator(input[i]))// 如果是运算符{//遇到操作符时,temNum不为空,将temNum的内容直接弹出,置空temNumif(!temNum.isEmpty()){res.push_back(temNum);temNum = "";}if(opera.empty()){//如果栈为空,不用考虑优先级直接存入运算符opera.push(input[i]);}else{while(!opera.empty()){if(mOptPriority[opera.top()]>=mOptPriority[input[i]]){//如果元素input[i]的优先级小于栈顶元素的优先级的时候,直接输出运算符res.push_back(opera.pop());}else{break;}}//元素input[i]的优先级大于栈顶元素的优先级的时候,将运算符压入栈内opera.push(input[i]);}}else if(input[i]=='('){//遇到'(',当temNum不为空时,直接输出temNum,置空temNumif(!temNum.isEmpty()){res.push_back(temNum);temNum="";}//temNum为空,直接将'('压入栈中opera.push('(');}else if(input[i]==')'){//遇到')',当temNum不为空时,直接输出temNum,置空temNumif(!temNum.isEmpty()){res.push_back(temNum);temNum="";}QChar popedOpt;while(!opera.empty()){popedOpt=opera.pop();if(popedOpt=='(')break;//栈不为空,依次弹出栈内的运算符,直到遇到'('res.push_back(popedOpt);}}else{return res;}}if(!temNum.isEmpty()){//循环结束后,temNum不为空,则输出temNum并置空res.push_back(temNum);temNum="";}while(!opera.empty()){//循环结束,栈不为空,将栈内运算符全部出栈res.push_back(opera.pop());}return res;}

3)计算结果

    //计算结果QString AbstractExpressionCalculation::postfixEvaluation(QQueue<QString> inputExp){QStack<Rational> calStack;for(QString &i:inputExp)//迭代一些容器时很方便,不用写迭代器{if(isOperator(i)){Rational numa,numb;QString numA=calStack.pop();QString numB=calStack.pop();calStack.push(doOperation(numA,numB,i));}else{calStack.push(i);}}return calStack.pop().getNumber();}

4)外部调用算法
标志着表达式计算的开始,同时该函数会先后调用合法性检验函数、函数识别函数、中缀转后缀函数,以及后缀表达式计算函数。所以在进行计算时仅需调用该函数即可,节省了繁琐且重复的函数调用过程。

    //外部调用,开始运算QString AbstractExpressionCalculation::startCalculate(int32_t *success){preTreatment();//判断表达式中输入字符是否为运算符。在该类中此函数为虚函数。*success=legitimacyTest(mExpression);QString ans=postfixEvaluation(infixToPostfix(mExpression));return ans;}

5)其他相关算法

protected:
//对负数进行预处理,在识别到负数时,该函数会在负数前补充“0”。使负数非负化,减去了表达式计算时对负数的额外处理过程,也对表达式计算速度进行优化。
virtual void preTreatment();
public:
//判断表达式中输入字符是否为运算符。在该类中此函数为虚函数。
virtual bool isOperator(const QString &inputalpha)=0;
//对后缀表达式栈进行计算,具体过程为:返回值 = 栈顶值 运算符 栈次顶值。其中运算符包括一般运算符与函数运算符。 在该类中此函数为虚函数。
virtual QString doOperation(const QString &numA,const QString &numB,const QString &opt)=0;

2.NormalExpressionCalculation

在命名空间CalEngine中,实现判断操作符和实现操作符的运算
1)实现父类AbstractExpressionCalculation的有参构造(传入表达式,同时对运算符优先级进行赋值)

NormalExpressionCalculation::NormalExpressionCalculation(QString inputExp):AbstractExpressionCalculation(inputExp)//存入输入表达式
{mOptPriority.insert('(',1);mOptPriority.insert('+', 2);mOptPriority.insert('-', 2);mOptPriority.insert('*', 3);mOptPriority.insert('/', 3);mOptPriority.insert('^', 4);
}

2)判断输入字符是否为运算符(其中普通计算器中含有:“ + ”、“ - ”、“ * ”、“ / ”和 “ ^ ”共五种运算符)

bool NormalExpressionCalculation::isOperator(const QString &inputalp)
{if(inputalp=='+' || inputalp=='-' || inputalp=='*' || inputalp=='/' || inputalp=='^')return true;return false;
}

3)根据运算符进行相应的运算(输入的操作符错误时,抛出操作符异常;使用QString接受运算结束的内容)

QString NormalExpressionCalculation::doOperation(const QString &numA,const QString &numB,const QString &opt)
{Rational A(numB),B(numA);if(opt=="+")return (A+B).getNumber();if(opt=="-")return (A-B).getNumber();if(opt=="*")return (A*B).getNumber();if(opt=="/")return (A/B).getNumber();if(opt=="^")return RationalMath::sqrt(A).getNumber();throw CalException(CalOperatorError);
}

3.ScienceExpressionCalculation

ExpressionCalculation实现了对表达式的分析与计算,包括了归一化处理、函数表达式识别与转换、表达式中缀转后缀,以及后缀表达式的计算。而ScienceExpressionCalculation是针对科学计算器进行适配的类,是AbstractExpressionCalculation的子类与实例化结果,用于针对科学计算器进行运算。其中需要在父类中重载的函数为isOperatordoOperationpreTreatment
1)构造函数,传入表达式,同时对运算符优先级进行赋值

    ScienceExpressionCalculation::ScienceExpressionCalculation(QString inputExp):AbstractExpressionCalculation(inputExp){mOptPriority.insert('(',1);mOptPriority.insert('+', 2);mOptPriority.insert('-', 2);mOptPriority.insert('*', 3);mOptPriority.insert('/', 3);mOptPriority.insert('^', 4);mOptPriority.insert('s', 4);mOptPriority.insert('L', 4);mOptPriority.insert('p', 4);mOptPriority.insert('l', 4);mOptPriority.insert('!', 4);}

2)函数转换函数
由于在表达式中缀转后缀与计算的过程中,表达式需要为“运算符A 运算符 运算数B”的形式(二元运算表达式),因此在此函数中将会将数学函数归一化为上述形式。识别到对应函数的字符时,调用具体的函数转换函数,对该函数进行转换,将原函数转换结果为上述形式,进而继续进行后续计算。

    QString ScienceExpressionCalculation::functionTranslate(const QString &inputStr){QString res;int32_t n=inputStr.size();int cnt=0;//记录转换过的函数个数for(int32_t i=0;i<n;i++){if(inputStr[i].isLetter()){QString optname;//提取符号int32_t j=0;for(j=i;j<n;j++){if(inputStr[j].isLetter())optname.append(inputStr[j]);elsebreak;}if(isOperator(optname))//如果是转换好的运算符{res.append(optname);continue;}if(j<n && inputStr[j]!='('){throw CalException(CalBracketError);}//开始转换if(optname=="sqrt"){res.append(translateSqrt(inputStr,&j));}else if(optname=="pow"){res.append(translatePow(inputStr,&j));}else if(optname=="log"){res.append(translateLog(inputStr,&j));}else if(optname=="ln"){res.append(translateLn(inputStr,&j));}else//运算符未命中{throw CalException(CalOperatorError);}cnt++;i=j;//防止重复遍历}else{res.append(inputStr[i]);}}if(cnt!=0){res=functionTranslate(res);}return res;}

3)开方函数转换,sqrt(A,B)转换为 A s B。

    QString ScienceExpressionCalculation::translateSqrt(const QString &inputStr, int32_t *j){QString res;int32_t n=inputStr.size();//记录位置int32_t dotpos=0,braLpos=*j,braRpos=0,branum=0;//逗号位置,左括号位置,右括号位置,括号数量for((*j)++;*j<n;(*j)++){if(*j==n-1 && inputStr[*j]!=')'){throw CalException(CalBracketError);}else if(inputStr[*j]==',' && branum==0){dotpos=*j;}else if(inputStr[*j]==')' && branum==0){braRpos=*j;break;}else if(inputStr[*j]==')'){branum--;}else if(inputStr[*j]=='('){branum++;}}//根据位置转换res.append("((");res.append(inputStr.mid(braLpos+1, dotpos-braLpos-1));res.append(")s(");res.append(inputStr.mid(dotpos+1, braRpos-dotpos-1));res.append("))");return res;}

4)指数函数转换,pow(A,B)转换为 A p B。

QString ScienceExpressionCalculation::translatePow(const QString &inputStr, int32_t *j){QString res;int32_t n=inputStr.size();//记录位置int32_t dotpos=0,braLpos=*j,braRpos=0,branum=0;//逗号位置,左括号位置,右括号位置,括号数量for((*j)++;*j<n;(*j)++){if(*j==n-1 && inputStr[*j]!=')'){throw CalException(CalBracketError);}else if(inputStr[*j]==',' && branum==0){dotpos=*j;}else if(inputStr[*j]==')' && branum==0){braRpos=*j;break;}else if(inputStr[*j]==')'){branum--;}else if(inputStr[*j]=='('){branum++;}}//根据位置转换res.append("((");res.append(inputStr.mid(braLpos+1, dotpos-braLpos-1));res.append(")p(");res.append(inputStr.mid(dotpos+1, braRpos-dotpos-1));res.append("))");return res;}

5)对数函数转换,log(A,B)转换为 A L B。

    QString ScienceExpressionCalculation::translateLog(const QString &inputStr, int32_t *j){QString res;int32_t n=inputStr.size();//记录位置int32_t dotpos=0,braLpos=*j,braRpos=0,branum=0;//逗号位置,左括号位置,右括号位置,括号数量for((*j)++;*j<n;(*j)++){if(*j==n-1 && inputStr[*j]!=')'){throw CalException(CalBracketError);}else if(inputStr[*j]==',' && branum==0){dotpos=*j;}else if(inputStr[*j]==')' && branum==0){braRpos=*j;break;}else if(inputStr[*j]==')'){branum--;}else if(inputStr[*j]=='('){branum++;}}//根据位置转换res.append("((");res.append(inputStr.mid(braLpos+1, dotpos-braLpos-1));res.append(")L(");res.append(inputStr.mid(dotpos+1, braRpos-dotpos-1));res.append("))");return res;}

6)自然对数函数转换,ln(A)转换为 A l 0(l为小写L),其中0是为了保证“运算符A 运算符 运算数B”格式而设计的占位符,实际运算中并不需要。

    QString ScienceExpressionCalculation::translateLn(const QString &inputStr, int32_t *j){QString res;int32_t n=inputStr.size();//记录位置int32_t braLpos=*j,braRpos=0,branum=0;//左括号位置,右括号位置,括号数量for((*j)++;*j<n;(*j)++){if(*j==n-1 && inputStr[*j]!=')'){throw CalException(CalBracketError);}else if(inputStr[*j]==')' && branum==0){braRpos=*j;break;}else if(inputStr[*j]==')'){branum--;}else if(inputStr[*j]=='('){branum++;}}//根据位置转换res.append("((");res.append(inputStr.mid(braLpos+1, braRpos-braLpos-1));res.append(")l(");res.append("0))");return res;}

7)判断输入字符是否为运算符。其中普通计算器中含有:“ + ”、“ - ”、“ * ”、“ / ”、 “ ^ ”、“ p ”、“ s ”、“ L ”、 “ ! ”和“ l ”共9种运算符。

    QString ScienceExpressionCalculation::doOperation(const QString &numA,const QString &numB,const QString &opt){Rational A(numB),B(numA);if(opt=="+")return (A+B).getNumber();if(opt=="-")return (A-B).getNumber();if(opt=="*")return (A*B).getNumber();if(opt=="/")return (A/B).getNumber();if(opt=="^")return (RationalMath::pow(A,B)).getNumber();if(opt=="L")return (RationalMath::log(A,B)).getNumber();if(opt=="l")return (RationalMath::ln(A)).getNumber();if(opt=="p")return (RationalMath::pow(A,B)).getNumber();if(opt=="s")return (RationalMath::sqrt(A,B)).getNumber();if(opt=="!")return (RationalMath::factorial(A)).getNumber();throw CalException(CalOperatorError);}

4.ProgrammerExpressionCalculation

PageEngine

1.MainDisplayLineEdit

MainDisplayLineEdit是显示表达式的LineEdit控件,创建并负责维护表达式与显示格式。无论是键盘事件输入的字符,还是界面控件按钮输入的字符,都会通过调用setExpression()等函数的方式输入。同时这个函数会记录并且显示计算结果的错误状态,自动调整字号以适应界面大小。
①私有属性

QFont mDispFont;                                                     //显示字体
bool mHasError;                                                      //错误信息标识
bool mIsEmptyExp;                                                    //为空表达式

②公共槽函数

void on_linedit_textedited(QString);                               //文本改变槽函数

1)获取和设置表达式

//获取当前存储表达式
QString getExpression();
//设置表达式并且调用changeTextToFormat()函数自动修改表达式格式
void setExpression(const QString &inputExp);

2)表达式的增添、删除和清空

//在表达式末尾追加字符并调用changeTextToFormat()函数自动修改表达式格式
void appendExpression(const QString &willappend);
//在pos位置插入表达式并调用changeTextToFormat()函数自动修改表达式格式
void insertExpression(const QString &willappend,const int32_t pos);
//在表达式末尾删减字符并调用changeTextToFormat()函数自动修改表达式格式
void chopExpression(unsigned int par);
//清除表达式并调用changeTextToFormat()函数自动修改表达式格式
void clearExpression();
//删除表达式并调用changeTextToFormat()函数自动修改表达式格式
void removeExpression(const int32_t pos,const int32_t n);

3)获取和设置错误信息

void setError(const QString &inputExp);                             //设置表达式错误信息状态
bool getHasError();                                                 //获取表达式错误信息状态

4)changeTextToFormat()格式化与更新表达式
修改表达式格式,主要有以下几个内容。将空表达式设置为0,在空表达式中追加字符后自动删除0。当被设置为显示错误信息的状态下追加字符时删除错误信息,并且接触错误状态。自动适应屏幕宽度,防止字符超出屏幕范围

void changeTextToFormat();                                          //格式化并更新

2.PageManager

PageManager提供了对计算器页面的统一管理,实现批量添加页面、设置页面大小与快速设置焦点等功能。同时此类为QObject,可以使用Qt的内存管理机制。在计算器中,有许多个页面,如普通型计算器页面、科学型计算器页面等,这些页面最终都要嵌入主窗口之中。本类旨在简化页面的创建过程,通过使用QVector存储页面指针,统一对页面进行管理。实现批量添加页面、设置页面大小与快速设置焦点等功能。同时此类为QObject,凭借QT强大的内存管理机制,此类实例所属所有子界面实例都会在父实例析构时自动回收内存
私有属性

QVector<QWidget*> mCalPages;
QObject *mParentWindow;

1)PageManager
构造函数,将传入的父实例指针记录
补充知识:
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的,而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
构造函数前面加上explicit,此时构造函数不能用于隐式转换和复制初始化,explicit关键字的作用就是防止类构造函数的隐式自动转换
隐式转换的坑:
对于有些情况来说,隐式转换是我们希望的。但在大部分情况下,隐式转换非常容易带来各种问题。
1.首先隐式转换不是错误,编译器不会报错,会给后面的调试带来巨大的不方便。
2.其次,隐式转换是编译器的自主行为,是在我们没有察觉的情况下发生的,除非是我们有明确的隐式转换需求,否则一般都不是我们希望发生。
3.同时,隐式转换还会让代码变得难以阅读,尤其是当有函数冲在的时候,很难判断此时到底是哪个函数被调用。

explicit PageManager(QObject *parent = nullptr);

2)新增页面并设置尺寸和获取焦点

void PageManager::addCalPage(QWidget *willadd,QWidget *parent)
{willadd->setParent(parent);mCalPages.append(willadd);//m_calPages.last()->setParent(parent);
}void PageManager::setCalPageSize(int32_t xx, int32_t yy)
{for(QWidget* page:mCalPages){page->resize(xx,yy);}
}void PageManager::setCalPageFocus(int32_t widgetIndex)
{if(widgetIndex>=mCalPages.size()){throw CalException(ErrorName::CalParameterError);}mCalPages[widgetIndex]->activateWindow();//激活 默认窗口,是设置焦点的前提mCalPages[widgetIndex]->setFocus();//将焦点交给子窗口,激活子窗口keypressevent
}

3.WindowApi

获取系统配色是Dwmapi.dll中的函数DwmGetColorizationColor()的二次封装,返回一个结构体代表RGB颜色值。窗口模糊效果是user32.dll中SetWindowCompositionAttribute()函数的二次封装,给予一个窗口句柄,将这个窗口设置模糊效果
1)获取系统配色(参考)

WindowsApi::RGBColor WindowsApi::getSystemColor()
{myColorFunction DwmGetColorizationColor;DWORD *color =new DWORD;BOOL opaque = FALSE;HMODULE dwmapimodule;dwmapimodule=LoadLibrary(L"Dwmapi.dll");DwmGetColorizationColor = myColorFunction(GetProcAddress(dwmapimodule, "DwmGetColorizationColor"));HRESULT hr = DwmGetColorizationColor(color, &opaque);WindowsApi::RGBColor gettedColor;if(hr==0){gettedColor={(*color >> 16) % 256, (*color >> 8) % 256 , (*color) % 256};delete color;return gettedColor;}else{delete color;throw CalException(ErrorName::CalSystemError);}delete color;
}

2)设置窗口透明效果(参考)

void WindowsApi::setWindowBlurEffect(HWND hwnd)
{HMODULE blurHuser;myBlurFunction setWindowCompositionAttribute;blurHuser = GetModuleHandle(L"user32.dll");//获取已经载入进程空间的模块句柄if(blurHuser==NULL)blurHuser=LoadLibrary(L"user32.dll");//如果进程空间未载入user32则手动载入,当然user32是默认载入的这句没啥用if(blurHuser){setWindowCompositionAttribute = myBlurFunction(GetProcAddress(blurHuser, "SetWindowCompositionAttribute"));//从动态库中取出函数if(setWindowCompositionAttribute){//DWORD gradientColor = DWORD(0x50FFFFFF);ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0};WINDOWCOMPOSITIONATTRIBDATA setting;setting.Attrib = WCA_ACCENT_POLICY;setting.pvData = &accent;setting.cbData = sizeof(accent);setWindowCompositionAttribute(hwnd, &setting);}elsethrow CalException(ErrorName::CalSystemError);}elsethrow CalException(ErrorName::CalSystemError);
}

Pages

Pages内部是不同页面的实际实现内容

1.NormalCalculatorPage

NormalCalcutorPage是普通计算器的的实际内容,更新历史函数、键盘事件、重设宽度事件、计算结果函数和ui设计等。

1)ui设计布局
利用值传递lambda表达式的方式连接ui界面中的按键事件,并绑定相关事件和函数。

ui->setupUi(this);
connect(ui->num0, &QPushButton::clicked, [=](){}
connect(ui->num1, &QPushButton::clicked, [=](){}
connect(ui->num2, &QPushButton::clicked, [=](){}
connect(ui->num3, &QPushButton::clicked, [=](){}
connect(ui->num4, &QPushButton::clicked, [=](){}
connect(ui->num5, &QPushButton::clicked, [=](){}
connect(ui->num6, &QPushButton::clicked, [=](){}
connect(ui->num7, &QPushButton::clicked, [=](){}
connect(ui->num8, &QPushButton::clicked, [=](){}
connect(ui->num9, &QPushButton::clicked, [=](){}
connect(ui->bckSpace, &QPushButton::clicked, [=](){}
connect(ui->opAdd, &QPushButton::clicked, [=](){}
connect(ui->opSub, &QPushButton::clicked, [=](){}
connect(ui->opMulti, &QPushButton::clicked, [=](){}
connect(ui->opDiv, &QPushButton::clicked, [=](){}
connect(ui->opBrckL, &QPushButton::clicked, [=](){}
connect(ui->opBrckR, &QPushButton::clicked, [=](){}
connect(ui->opDot, &QPushButton::clicked, [=](){}
connect(ui->opMod, &QPushButton::clicked, [=](){}
connect(ui->opSqr, &QPushButton::clicked, [=](){}
connect(ui->opClear, &QPushButton::clicked, [=](){}
connect(ui->opEqual, &QPushButton::clicked, [=](){}
//连接历史窗口
QTimer::singleShot(100,this, [=]() {
connect(this,SIGNAL(updateHistoryPage(QString)),this->window(),SLOT(updateHistoryPage(QString)));
});

2)updateHisLabel(更新历史窗口)
管理历史窗口的更新,定义QString的容器去接收计算的结果,在输出时用QString容器willupdate接收输出的结果
补充知识:
dispFont.setStyleStrategy(QFont::PreferAntialias);
样式策略告诉字体匹配算法应该使用哪种类型的字体来查找合适的默认系列
PreferAntialias 抗锯齿
PreferDefault 默认风格
PreferBitmap 图字体
PreferDevice 设备字体
PreferOutline 轮廓字体(与图字体相反)
ForceOutline 强制使用轮廓字体
NoAntialias 不对字体进行反锯齿处理
NoSubpixelAntialias 尽量避免字体上的亚像素抗锯齿
NoFontMerging 如果为某个书写系统选择的字体不包含要求绘制的字符,则Qt会自动选择包含该字符的外观相似的字体
PreferNoShaping

void NormalCalculatorPage::updateHisLabel(QString willupdate)
{//字符串内容处理if(willupdate.isEmpty()==true)willupdate.append("0");NormalExpressionCalculation calengine(ui->displayCur->getExpression());int32_t isSuccessful=false;//定义一个string类型容器QString res;//使用res去接收运算的结果res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);//输出格式为A + B = Cwillupdate=willupdate.replace('/', "÷").replace('*', "×").append("=").append(res);int displayLen=ui->displayHis->width()-10;QFont dispFont("Microsoft Yahei",32);//设置字体dispFont.setStyleStrategy(QFont::PreferAntialias);for(int i=16;i>=1;i--){dispFont.setPointSize(i);QFontMetrics fm(dispFont);int pixelWid=fm.horizontalAdvance(willupdate);if(displayLen>pixelWid)break;}ui->displayHis->setFont(dispFont);ui->displayHis->setText(willupdate);ui->displayHis->setStyleSheet("color: rgb(70, 70, 70);");
}

3)按键事件

void NormalCalculatorPage::keyPressEvent(QKeyEvent *event)
{switch (event->key()){case Qt::Key_0:ui->displayCur->appendExpression("0");break;case Qt::Key_1:ui->displayCur->appendExpression("1");break;case Qt::Key_2:ui->displayCur->appendExpression("2");break;case Qt::Key_3:ui->displayCur->appendExpression("3");break;case Qt::Key_4:ui->displayCur->appendExpression("4");break;case Qt::Key_5:ui->displayCur->appendExpression("5");break;case Qt::Key_6:ui->displayCur->appendExpression("6");break;case Qt::Key_7:ui->displayCur->appendExpression("7");break;case Qt::Key_8:ui->displayCur->appendExpression("8");break;case Qt::Key_9:ui->displayCur->appendExpression("9");break;case Qt::Key_ParenLeft:ui->displayCur->appendExpression("(");break;case Qt::Key_ParenRight:ui->displayCur->appendExpression(")");break;case Qt::Key_Percent:ui->displayCur->appendExpression("%");break;case Qt::Key_AsciiCircum:ui->displayCur->appendExpression("^2");break;case Qt::Key_Plus:ui->displayCur->appendExpression("+");break;case Qt::Key_Minus :ui->displayCur->appendExpression("-");break;case Qt::Key_Asterisk:ui->displayCur->appendExpression("*");break;case Qt::Key_Slash:ui->displayCur->appendExpression("/");break;case Qt::Key_Period:ui->displayCur->appendExpression(".");break;case Qt::Key_Backspace:ui->displayCur->chopExpression(1);break;case Qt::Key_Delete:ui->displayCur->clearExpression();break;case Qt::Key_Enter://两者都是回车键case Qt::Key_Return:calResult();break;}
}

4)重新设定窗口大小

//当widget大小改变时自动调用更新当前字符宽度
void NormalCalculatorPage::resizeEvent(QResizeEvent *event)
{ui->displayCur->changeTextToFormat();
}

5)结果计算

//计算结果
void NormalCalculatorPage::calResult()
{if(ui->displayCur->getHasError()==true){return;}updateHisLabel(ui->displayCur->getExpression());//上传历史记录QString equation=ui->displayHis->text();NormalExpressionCalculation calengine(ui->displayCur->getExpression());int32_t isSuccessful=false;QString res;try{res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);}catch (CalException &exp){if(exp.getErrorName()==ErrorName::CalBracketError){ui->displayCur->setError("括号匹配错误");}else if(exp.getErrorName()==ErrorName::CalOperatorError){ui->displayCur->setError("运算符错误");}else if(exp.getErrorName()==ErrorName::CalParameterError){ui->displayCur->setError("参数错误");}else if(exp.getErrorName()==ErrorName::CalSyntaxError){ui->displayCur->setError("语法错误");}else{ui->displayCur->setError("未知错误");}}emit updateHistoryPage(equation+ui->displayCur->text());
}

6)分数计算槽函数(1/num)

void NormalCalculatorPage::on_opInv_pressed()
{bool resultOfTran=false;double num=(ui->displayCur->text()).toDouble(&resultOfTran);if(resultOfTran==true){//qDebug()<<1/num;ui->displayCur->setExpression(QString::number(1/num));}else{ui->displayCur->setError("参数错误");}
}

2.ScienceCalPage

1)ui设计布局
利用值传递lambda表达式的方式连接ui界面中的按键事件,并绑定相关事件和函数。

connect(ui->num0, &QPushButton::clicked, [=](){}
connect(ui->num1, &QPushButton::clicked, [=](){}
connect(ui->num2, &QPushButton::clicked, [=](){}
connect(ui->num3, &QPushButton::clicked, [=](){}
connect(ui->num4, &QPushButton::clicked, [=](){}
connect(ui->num5, &QPushButton::clicked, [=](){}
connect(ui->num6, &QPushButton::clicked, [=](){}
connect(ui->num7, &QPushButton::clicked, [=](){}
connect(ui->num8, &QPushButton::clicked, [=](){}
connect(ui->num9, &QPushButton::clicked, [=](){}
connect(ui->bckSpace, &QPushButton::clicked, [=](){}
connect(ui->opAdd, &QPushButton::clicked, [=](){}
connect(ui->opSub, &QPushButton::clicked, [=](){}
connect(ui->opMulti, &QPushButton::clicked, [=](){}
connect(ui->opDiv, &QPushButton::clicked, [=](){}
connect(ui->opBrckL, &QPushButton::clicked, [=](){}
connect(ui->opBrckR, &QPushButton::clicked, [=](){}
connect(ui->opDot, &QPushButton::clicked, [=](){}
connect(ui->opMod, &QPushButton::clicked, [=](){}
connect(ui->opFact, &QPushButton::clicked, [=](){}
connect(ui->opClear, &QPushButton::clicked, [=](){}
connect(ui->opEqual, &QPushButton::clicked, [=](){}
connect(ui->opPow, &QPushButton::clicked, [=](){}
connect(ui->opSqrt, &QPushButton::clicked, [=](){}
connect(ui->opLog, &QPushButton::clicked, [=](){}
connect(ui->opLn, &QPushButton::clicked, [=](){}
connect(ui->opPi, &QPushButton::clicked, [=](){}
connect(ui->opE, &QPushButton::clicked, [=](){}

2)计算结果

void ScienceCalPage::calResult()
{if(ui->displayCur->getHasError()==true){return;}updateHisLabel(ui->displayCur->getExpression());//上传历史记录QString equation=ui->displayHis->text();ScienceExpressionCalculation calengine(ui->displayCur->getExpression());int32_t isSuccessful=false;QString res="";try{res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);}catch (CalException &exp){if(exp.getErrorName()==ErrorName::CalBracketError){ui->displayCur->setError("括号匹配错误");}else if(exp.getErrorName()==ErrorName::CalOperatorError){ui->displayCur->setError("运算符错误");}else if(exp.getErrorName()==ErrorName::CalParameterError){ui->displayCur->setError("参数错误");}else if(exp.getErrorName()==ErrorName::CalSyntaxError){ui->displayCur->setError("语法错误");}else{ui->displayCur->setError("未知错误");}}emit updateHistoryPage(equation+ui->displayCur->text());
}

3)更新历史窗口

void ScienceCalPage::updateHisLabel(QString willupdate)
{//字符串内容处理if(willupdate.isEmpty()==true)willupdate.append("0");ScienceExpressionCalculation calengine(ui->displayCur->getExpression());int32_t isSuccessful=false;QString res;res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);willupdate=willupdate.replace('/', "÷").replace('*', "×").append("=").append(res);int displayLen=ui->displayHis->width()-10;QFont dispFont("Microsoft Yahei",32);dispFont.setStyleStrategy(QFont::PreferAntialias);for(int i=16;i>=1;i--){dispFont.setPointSize(i);QFontMetrics fm(dispFont);int pixelWid=fm.horizontalAdvance(willupdate);if(displayLen>pixelWid)break;}ui->displayHis->setFont(dispFont);ui->displayHis->setText(willupdate);ui->displayHis->setStyleSheet("color: rgb(70, 70, 70);");
}

4)键盘事件和重设尺寸事件

void ScienceCalPage::keyPressEvent(QKeyEvent *event)
void ScienceCalPage::resizeEvent(QResizeEvent *event)

3.RandomPage

1)私有槽函数

private slots:void on_RanC98button_clicked();void on_RanC11button_clicked();void on_RanCPUbutton_clicked();

->getExpression());//上传历史记录
QString equation=ui->displayHis->text();
ScienceExpressionCalculation calengine(ui->displayCur->getExpression());
int32_t isSuccessful=false;

QString res="";
try
{res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);
}
catch (CalException &exp)
{if(exp.getErrorName()==ErrorName::CalBracketError){ui->displayCur->setError("括号匹配错误");}else if(exp.getErrorName()==ErrorName::CalOperatorError){ui->displayCur->setError("运算符错误");}else if(exp.getErrorName()==ErrorName::CalParameterError){ui->displayCur->setError("参数错误");}else if(exp.getErrorName()==ErrorName::CalSyntaxError){ui->displayCur->setError("语法错误");}else{ui->displayCur->setError("未知错误");}}emit updateHistoryPage(equation+ui->displayCur->text());

}


3)更新历史窗口```c++
void ScienceCalPage::updateHisLabel(QString willupdate)
{//字符串内容处理if(willupdate.isEmpty()==true)willupdate.append("0");ScienceExpressionCalculation calengine(ui->displayCur->getExpression());int32_t isSuccessful=false;QString res;res=calengine.startCalculate(&isSuccessful);ui->displayCur->setExpression(res);willupdate=willupdate.replace('/', "÷").replace('*', "×").append("=").append(res);int displayLen=ui->displayHis->width()-10;QFont dispFont("Microsoft Yahei",32);dispFont.setStyleStrategy(QFont::PreferAntialias);for(int i=16;i>=1;i--){dispFont.setPointSize(i);QFontMetrics fm(dispFont);int pixelWid=fm.horizontalAdvance(willupdate);if(displayLen>pixelWid)break;}ui->displayHis->setFont(dispFont);ui->displayHis->setText(willupdate);ui->displayHis->setStyleSheet("color: rgb(70, 70, 70);");
}

4)键盘事件和重设尺寸事件

void ScienceCalPage::keyPressEvent(QKeyEvent *event)
void ScienceCalPage::resizeEvent(QResizeEvent *event)

3.RandomPage

1)私有槽函数

private slots:void on_RanC98button_clicked();void on_RanC11button_clicked();void on_RanCPUbutton_clicked();

源代码

gitee获取:https://gitee.com/Xzhu_stu/xiaozhu

Qt实战项目:高精度表达式计算器Qt相关推荐

  1. 迅为-i.MX6ULL开发板-QT实战项目DHT11网络编程实战练习(一)

    迅为-i.MX6ULL开发板-QT实战项目DHT11&网络编程实战练习(一) 发布时间:2021-7-2 09:49    发布者:落风 |编辑 文章目录1 项目前准备2 软件流程图3 Lin ...

  2. Android 实战项目:简单计算器

    文章目录 实战项目:简易计算器 1.需求分析 2.界面设计 3.关键代码 1.输入按键的合法性校验 2.执行运算并显示计算结果 简单计算器 - 详细操作步骤 总结 实战项目:简易计算器 1.需求分析 ...

  3. 计算机软件实习项目一 简单计算器 (Qt实现计算器界面) 12-5

    UI用户界面 使用Qt Designer对计算器的界面进行制作 目录 UI用户界面 界面设计 功能描述 界面浏览 新功能 1.小数点前后自动补0 2.加减乘除自动切换 3.不允许的操作按了不会生效 4 ...

  4. QT实战项目 - 随意听音乐闹钟

    目录 前言 一.随意听音乐闹钟是什么? 二.效果图 三.源码 1.下载歌曲 2.播放 四.源码地址 前言 办公室中午午休结束闹铃,每次都需要手动去选择一首喜欢的歌曲,然后点击播放,一首歌放完,再关闭播 ...

  5. Qt实战项目学习2———学生管理系统(1)

    注:参照编程小鱼酱教程 特别感谢博主教学 一.登陆界面设计及主界面设计 1.登录按钮实现 登陆界面自定义信号  并发射信号 signals://成功登录发送信号void sendLoginSucces ...

  6. 【Qt实战派学习群】 建立啦!

    建了一个Qt学习群,供大家交流学习. 群文件提供博客源码免费下载! 还有大量Qt实战项目源码.程序员工具.电子书等学习资料! 群号码:1149411109 (若满加2群:917341904) 群名称: ...

  7. vs和clion工具下建立qt widgets项目

    准备工作 1 QT官网下载QT并安装,注意要注册才能看到下载链接. 2 微软官网下载VS Community并安装 3 Jetbrains官网下载Clion并安装 vs工具下建立qtwidgets项目 ...

  8. QT实战-计算器-刘桂林-专题视频课程

    QT实战-计算器-152人已学习 课程介绍         使用Qt Creator开发一个具有运算逻辑以及交互逻辑的计算器案例. 课程收益     帮助同学们快速学习和入门Qt. 讲师介绍      ...

  9. 一、Qt初尝试,做一个QT计算器《QT 入门到实战》

    学习目标 了解 qt 的基本信息 了解 qt 的下载及安装 了解创建一个基本 qt 项目的流程 了解信号与槽 通过示例了解信号与槽的设置与编写 了解控件添加的方式 了解控件如何使用代码获取其文本 了解 ...

最新文章

  1. 达尔文进化奖_使用Kydavra GeneticAlgorithmSelector将达尔文进化应用于特征选择
  2. 将NetBeans代码模板弯曲到我的意愿
  3. SharePoint 2010 技术参数(整理)
  4. PHP的SESSION使用,文件上传下载等
  5. ref的mysql_ref:Mysql授权远程登陆
  6. thinkphp 模板 php函数调用,thinkphp模版调用函数方法
  7. 二十个让你泪流满面的瞬间
  8. C++调用编译好的darknet来进行物体监测
  9. 如何用wordpress搭建个人博客
  10. 2021-08-24fine-tuning 模型
  11. “微积分7天搞定”学习记录
  12. 用例图分析---学生成绩管理系统
  13. 广日电梯主板灯说明_广日MAX电梯维修手册.pdf
  14. 数学竞赛书单该怎么选?金牌牛娃用的书都在这里了!
  15. 不知道华为手机识别图片文字怎么弄?2个识别方法收好了
  16. NVIDIA Jetson TK1学习与开发(一):NVIDIA Jetson TK1介绍
  17. html表格标题标签_HTML标题标签
  18. 静态库与共享库制作,及区别
  19. 465端口发邮件php,在centos上打开端口465 for smtp的问题
  20. mysql数据库用sql语句在指定的一个字段后面添加一个字段

热门文章

  1. NXP SJA1124 spi转lin芯片驱动
  2. 为什么从事大数据行业,一定要学习Python?
  3. 利用有限元数值模拟技术辅助静电场学习
  4. ( 蚁群算法总结)何涛
  5. Python函数和装饰器
  6. 利用html表单制作个人简历
  7. 【C语言】七段显示器输出数字
  8. 8种常用排序算法稳定性分析
  9. 大数据时代之市场调研的必要性
  10. 超调量怎么计算公式_超调量如何计算?