Qt教程-Qt Creator开发实例-计算器

经过前面的几篇文章,相信大家对于 Qt Creator 这个 IDE 以及 Qt 基本的使用方法有所了解。这篇文章我们使用已经了解过的技术简单的定制一个计算器。

这里我们基于Qt Creator中定制GUI程序04(国际化多语言)中提供的工程在继续往下开发。
相信大家都用过 Windows 下的计算器,作为一个计算器,我们需要一个用来输入和显示结果的文本框(QTextEdit),我们还需要一些数字符号的按钮(QPushButton)。这些都是常用的控件,我们可以直接在 Qt Creator 上拖出来。

我们这里对整个窗口使用垂直布局,最上方放置了一个文本框,下方我们放置了一个 QGridLayout(珊格)布局在布局中放置了很多个按钮分别表示数字0-9和加减乘除等按钮。
放置后我们需要调整标识控件的名称(objectName 属性),控件显示的内容(比如 QPushButton 中的 text 属性),控件自动缩放的属性(sizePolicy),并为每个按钮控件设置快捷键(shortcut 属性)。
设置完成后结果如下图所示。

如果我们想对用户按下按钮作出反应,常规的做法是,为每个按钮注册Clicked的信号。不过很显然我们一个一个注册实在是太麻烦了。这里我们使用了一个偷懒的办法,按钮组(QButtonGroup)。如果我们需要处理界面上多个类似功能的按钮,我们可以把这些按钮组成一个组,然后为一个组设置一个响应函数就可以了。

在这里需要注意的是 Qt Creator 不能直接对 buttonGroup 组本身设置槽函数,所以需要手动创建一个槽函数并手动连接到信号。
这里我选择在界面初始化时 connect buttonGroup 的 buttonClicked 信号到一个 MainWindow 中的 Button_clicked 函数。

1
2
3
4
QObject::connect(ui->buttonGroup,
(void (QButtonGroup::*)(QAbstractButton *))&QButtonGroup::buttonClicked
, this
, &MainWindow::Button_clicked);

这样组内的任意按钮产生 Clicked 事件都会调用 Button_clicked 这个函数,我们可以看到 Button_clicked 函数传入了一个 QAbstractButton 对象的指针,这个指针用来确定组中具体是哪个按钮被按下了。
在计算器上,如果用户按下的按钮是数字,则显示在文本框里,如果是符号,则展开清屏或者计算操作。
这里我直接给出实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void MainWindow::Button_clicked(QAbstractButton *ojbect_button)
{
QString button_text=ojbect_button->text();
QString display_text=ui->textEdit->toPlainText();
if(display_text.length()>20)
{
ui->textEdit->setText("ERROR");
return;
}
if(display_text=="ERROR")
{
ui->textEdit->setText("");
display_text.clear();
}
if(button_text == "C")
{
ui->textEdit->setText("");
}
else if(button_text == "back")
{
display_text.remove(display_text.length()-1,display_text.length());
ui->textEdit->setText(display_text);
}
else if(button_text == "=")
{
QString str_rpn_exp=calc_string_to_rpn_string(display_text);
QString str_display=calc_rpn_string(str_rpn_exp);
ui->textEdit->setText(str_display);
}
else
{
display_text+=button_text;
ui->textEdit->setText(display_text);
}
}

我们简单的解释一下,通过 ojbect_button 指针我们可以调用 text 函数获取控件的 text 属性,通过 text 属性我们就可以知道是哪一个按钮被按下。如果是数字则直接添加到 textEdit 文本框中,如果是符号则进行相关处理。通过 setText 函数我们可以设置文本框显示的内容。
这里为了避免未知的问题,我限制了表达式的长度为20个字符,超过会显示 ERROR,大家可以自行修改此处。
我们可以看到当等号按钮被按下时,这里调用了 calc_string_to_rpn_string 和 calc_rpn_string 这两个函数。这两个函数用于把我们常见的中缀表达式转换成逆波兰表达式(也叫后缀表达式),最后在计算逆波兰表达式得到结果。
把中缀表达式转换成逆波兰式是为了更方便我们进行计算,这方面网络上有非常多的资料,我不过多赘述,直接给出有关的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
bool is_number(char ch)
{
if(ch >= '0' &amp;&amp; ch <= '9')
return true;
if(ch == '.')
return true;
return false;
}
QString calc_string_to_rpn_string(QString str_express)
{
QStack<char> stack_symbol;
QString str_rpnexp = "";
for(int npos = 0; npos < str_express.length(); npos++)
{
char ch = str_express[npos].toLatin1();
if(is_number(ch) == true)
{
str_rpnexp += ch;
npos++;
while(npos < str_express.length())
{
ch = str_express[npos].toLatin1();
if(is_number(ch) == true)
{
str_rpnexp += ch;
npos++;
}
else
{
break;
}
}
str_rpnexp += '#';

}
//other symbool
if(ch == '+' || ch == '-')
{
if(stack_symbol.isEmpty() != true &amp;&amp; stack_symbol.top() != '(')
{
do
{
str_rpnexp += stack_symbol.pop();
}
while(stack_symbol.isEmpty() != true &amp;&amp; stack_symbol.top() != '(');
}
stack_symbol.push(ch);
}
else if(ch == '*' || ch == '/')
{
if(stack_symbol.isEmpty() != true
&amp;&amp; (stack_symbol.top() == '*' || stack_symbol.top() == '/')
)
{
do
{
str_rpnexp += stack_symbol.pop();
}
while(stack_symbol.isEmpty() != true &amp;&amp; stack_symbol.top() != '(');
str_rpnexp += ch;
}
stack_symbol.push(ch);

}
else if(ch == '(')
{
stack_symbol.push(ch);
}
else if(ch == ')')
{
while(stack_symbol.isEmpty() != true)
{
ch = stack_symbol.pop();
if(ch != '(')
{
str_rpnexp += ch;
}
}
}
}
while(stack_symbol.isEmpty() != true)
{
char ch = stack_symbol.pop();
if(ch != '(')
{
str_rpnexp += ch;
}
}
return str_rpnexp;
}

QString calc_rpn_string(QString str_rpn_express)
{
QStack<double> stack_num;
for(int npos = 0; npos < str_rpn_express.length(); npos++)
{
char ch = str_rpn_express[npos].toLatin1();
if(is_number(ch) == true)
{

QString str_num;
str_num += ch;
npos++;
while(npos < str_rpn_express.length())
{
ch = str_rpn_express[npos].toLatin1();
if(is_number(ch) == true)
{
str_num += ch;
npos++;
}
else
{
break;
}
}
bool b_conversion = false;
double do_num = str_num.toDouble(&amp;b_conversion);
if(b_conversion == false)
{
return "ERROR";
}
stack_num.push(do_num);
}
if(ch == '#')
{
continue;
}
else
{
if(stack_num.length() < 2)
{
return "ERROR";
}
double b = stack_num.pop();
double a = stack_num.pop();
switch(ch)
{
case '+':
stack_num.push(a + b);
break;
case '-':
stack_num.push(a + b);
break;
case '*':
stack_num.push(a * b);
break;
case '/':
stack_num.push(a / b);
break;
default:
return "ERROR";
}
}
}
if(stack_num.isEmpty() == true)
{
return "ERROR";
}
double calc = stack_num.pop();
return QString("").setNum(calc);
}

这里我们简单的说明一下。
QString 这个类与 std::string 虽然有些类似但是也有一些区别。
我们都知道 std::string 实际上就像个 std::vector 是一个单字节的字符串实现。很显然 char 类型容纳不了所有的汉字,为了能储存显示汉字(或者其他的语言) std::string 在不同平台可能有不同的编码实现,比如在 Linux 上 std::string 中的内容采用的是 UTF-8 编码,而在 Windows 下使用的则是 ANSI 编码(对于中文版的 Windows 来说就是 GBK 编码)。如果全都是 UTF-8 可能没有什么问题,但是对不同语言版本的 Windows 编码就成为了大难题。
如果开发者想一劳永逸的解决各个语言版本的编码兼容性,Microsoft 推荐大家使用 UNIOCDE 编码(对于高于 Windows 2000 的系统版本,就是 UTF-16 编码,至于低于 Windows 2000 的系统版本,我建议选择拒绝兼容)但是使用 UTF-16 编码则不能直接与 Linux 系统兼容。
多系统的兼容很显然是一件非常麻烦的事情,作为传统的开发方法,开发者必须考虑每个平台的特点作出取舍或者写上一堆转换编码的函数互相转换。
在 Qt 中 QString 这个类为我们提供了统一的解决方案,QString 内部采用了 UNIOCDE 编码,这样使得任何文字都能正确的储存在 QString 中,在 Windows 上也可以直接获得良好的兼容性。当然作为牺牲,如果想用我们熟悉的 char 来解决问题,需要使用 toLatin1 函数转换成 Latin1 编码。
QStack 和 std::stack 类似,用来作为栈这种数据结构使用,熟悉栈的同学应该对 pop,push,top 等操作一目了然。
最后我们可以编译看运行结果。运行结果如下图所示。

当用户按下等号后将会计算出正确结果14。
工程源代码下载地址:
/uploads/public/qt_tutorial/qt_calculator_FCE2BED0.7z