赞
踩
表达式计算器是一个由C++ with QT编写的应用程序。这个计算器旨在通过引入表达式计算的概念解决Windows默认计算器显示不直观的问题,提供了普通型计算器、科学型计算器的功能,并且可以通过页面管理机制快速添加自定义的新页面。
int64_t
与long double
范围之外的数学运算,其中加减乘除由竖式运算规则实现,为精确解。乘方、开根、对数等运算会被拆解为int64_t
与long double
范围内数字,由QtMath库计算,为泰勒展开实现的近似解。环境需求
CalEngine内置了计算器所用到的算法,包括高精度算法、表达式的检验与计算、随机数生成等。
PageEngine内部是与页面有关的部分,包括一些自定义窗口控件、Windows API部分的封装、页面管理类等。
Pages内部是不同页面的实际实现内容
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.h、MainWindow.cpp、MainWindow.ui(界面文件)
绘制ui界面
①槽函数与信号创建
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; //初始化ui
PageEngine::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; //鼠标是否按下
//-----------------------初始化相关内容------------------------------//
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(); //隐藏历史记录栏
//----------------------页面相关内容---------------------------------//
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);
//----------------------信号与槽相关内容-----------------------------//
connect(mChooseWidget,SIGNAL(emitSelectedModes(int)),this,SLOT(getReturnedMode(int))); //接受功能选择结果
connect(this,SIGNAL(functionChooseBarOpenAnimation()),mChooseWidget,SLOT(showOpenAnimation())); //控制动画开启效果
connect(this,SIGNAL(functionChooseBarCloseAnimation()),mChooseWidget,SLOT(showCloseAnimation())); //控制动画关闭效果
//----------------------接下来是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)
{}
//---------------------接下来是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());
});
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()); //归还焦点
}
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();
}
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内置了计算器所用到的算法,包括高精度算法、表达式的检验与计算、随机数生成等。
1.CalException类继承于QException基类(QException类为可跨线程传输的异常提供了基类)
2.定义了一个名字为ErrorName的枚举,可以通过这个枚举类型定义一个名为mError的枚举变量。
enum枚举
使用方法:
enum
{
yesterday,
nowDay,
tomorrow
};
int main()
{
unsigned char day;
day = nowDay;
printf("day=%u\r\n",day);
}
定义了一个枚举类型,但是没有给枚举类型命名。
像这种枚举呢就是没办法定义枚举变量的,但是呢,这样其实也是可以用的。
我们通过定义一个普通变量,然后把枚举的值赋给他,一样也能输出正确的值,这种方法我个人经常偷懒地用。
enum tDay
{
yesterday,
nowDay,
tomorrow
};
int main()
{
enum tDay day = nowDay;
printf("day=%u\r\n",day);
}
定义了一个名字为tDay的枚举,那么我们就可以通过这个枚举类型定义一个名为day的枚举变量。
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(); //创建空数字
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*创建数字
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; //判断正负数
(从左往右为十分位、百分位、千分位)和整数部分(从左往右为个位,十位,百位),定义两个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
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;
//因为是从高位到低位运算,所以要pushfront
Res.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 1000
const Rational *numA=&A,*numB=&B;
if(A==B)//A和B相等时候直接返回零
{
Res.setNumber("0");
return;
}
if(RationalMath::abs(A)<RationalMath::abs(B))//使得较大的数为被减数
{
//使A恒大于B
numA=&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;
}
else
needValue=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;
}
else
needValue=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;
else
Res.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);
}
//答案去先导零,更新size
Res.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);
//常量兀
static const Rational math_pi;
//自然常量e
static const Rational math_e;
//ln2
static const Rational math_ln2;
//ln5
static const Rational math_ln5;
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分之1
static 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); //阶乘
uint32_t producerC98Rand::getRandom()
{
if(!isSrand)
{
srand((int)time(NULL));
isSrand=true;
}
return rand();
}
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;
}
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;
else
rnd = new producerC11Rand;
}
randomInterface::~randomInterface()
{
delete rnd;
}
uint32_t randomInterface::getRandom(int minNum,int maxNum)
{
long ans=minmax(rnd->getRandom(),minNum,maxNum);
return ans;
}
该文件夹内用于管理页面关键的算法内容
本类是表达式计算中对应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;否则返回false
else 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的内容直接弹出,置空temNum
if(!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,置空temNum
if(!temNum.isEmpty())
{
res.push_back(temNum);
temNum="";
}
//temNum为空,直接将'('压入栈中
opera.push('(');
}
else if(input[i]==')')
{
//遇到')',当temNum不为空时,直接输出temNum,置空temNum
if(!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;
在命名空间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);
}
ExpressionCalculation实现了对表达式的分析与计算,包括了归一化处理、函数表达式识别与转换、表达式中缀转后缀,以及后缀表达式的计算。而ScienceExpressionCalculation是针对科学计算器进行适配的类,是AbstractExpressionCalculation的子类与实例化结果,用于针对科学计算器进行运算。其中需要在父类中重载的函数为***isOperator***、doOperation***和***preTreatment。
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]);
else
break;
}
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);
}
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(); //格式化并更新
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
}
获取系统配色是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);
}
else
throw CalException(ErrorName::CalSystemError);
}
else
throw CalException(ErrorName::CalSystemError);
}
Pages内部是不同页面的实际实现内容
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 = C
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);");
}
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("参数错误");
}
}
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)
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)
1)私有槽函数
private slots:
void on_RanC98button_clicked();
void on_RanC11button_clicked();
void on_RanCPUbutton_clicked();
gitee获取:https://gitee.com/Xzhu_stu/xiaozhu
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。