该版本计算器代码支持计算式中有括号的运算,比如可以计算 (((3+2)*5 - 10)*8 - 1/4)/2 。
代码:
[C++] 纯文本查看 复制代码
#include <iostream> // input/output stream
#include <cstdlib> // For the exit() function
#include <cctype> // For the isdigit() function
#include <cstring> // For the strcpy() function
using std::cin;
using std::cout;
using std::endl;
void eatspaces(char* str); // 去除输入算数式中的空格
double expr(char* str); // 计算算数式
double term(char* str, int& index); // 分析算数式
double number(char* str, int& index); // 将输入字符转换为数字并解析括号
char* extract(char* str, int& index); // 提取括号中的表达式
const int MAX(80); // 支持表达式的输入最大长度,包含 '\0'
int main()
{
char buffer[MAX] = {0}; // 保存输入的算数式
cout << endl
<< "Welcome to your friendly calculator."
<< endl
<< "Enter an expression, or an empty line to quit."
<< endl;
for(;;)
{
cin.getline(buffer, sizeof buffer); // 读入
eatspaces(buffer); // 将输入的算数式中的空白符去除
if(!buffer[0]) // 当输入一个空行时退出程序
return 0;
cout << "\t= " << expr(buffer) // 输出算数式的值
<< endl << endl;
}
}
// 将字串中的空白符去除
void eatspaces(char* str)
{
int i(0);
int j(0);
while((*(str + i) = *(str + j++)) != '\0')
if(*(str + i) != ' ')
i++;
return;
}
// 计算算数式的值
double expr(char* str)
{
double value(0.0); // 保存结果
int index(0); // 字串的索引
value = term(str, index); // 获得第一个操作数
for(;;) // 做加减法运算,直到整个算数式结束。算数式中可能会有加减法和乘除法的混合,先计算乘除部分,剩下的加减使用该循环全部计算
{
switch(*(str + index++)) // 查看当前的字符是什么
{
case '\0': // 如果已经到了算数式末尾,那么返回计算结果
return value;
case '+': // 加法运算,取得加数
value += term(str, index);
break;
case '-': // 减法运算,取得减数
value -= term(str, index);
break;
default: // 输入的运算表达式错误
cout << endl
<< "Arrrgh!*#!! There's an error"
<< endl;
exit(1);
}
}
}
// 获取表达式中的算术因子
double term(char* str, int& index)
{
double value(0.0);
value = number(str, index); // 将字符转换为数字并解析括号
// 该 while 循环用来计算连乘或连除
while(true)
{
if(*(str + index) == '*') // 是否为乘号,是的话,则再由 number() 取得乘数,最后计算结果
value *= number(str, ++index);
else if(*(str + index) == '/') // 是否为除号,是的话,则再由 number() 取得除数,最后计算结果
value /= number(str, ++index);
else
break;
}
return value;
}
// 识别输入计算式中的数字
double number(char* str, int& index)
{
double value(0.0); // 保存结果
if(*(str + index) == '(') // 是否为左括号
{
char* psubstr(nullptr); // 指向括号内的计算式子串的指针
psubstr = extract(str, ++index); // 析出括号内的计算式子串
value = expr(psubstr); // 计算子串的值,由于该子串仍有可能含有括号,因此这里是递归调用
delete[]psubstr; // 清除子串空间
return value; // 返回子串计算式的值
}
// 至少需要一个数字
if(!isdigit(*(str + index)))
{ // 如果输入的不是数字则出错
cout << endl
<< "Arrrgh!*#!! There's an error"
<< endl;
exit(1);
}
while(isdigit(*(str + index))) // 数字字符串转换为十进制
value = 10*value + (*(str + index++) - '0');
// 如果是非数字,那么检查是否是小数点
if(*(str + index) != '.')
return value; // 如果不是小数点,那么返回转换值
double factor(1.0); // 计算浮点数
while(isdigit(*(str + (++index))))
{
factor *= 0.1; // 小数点每往后一位就要乘以一个 0.1
value = value + (*(str + index) - '0')*factor; // 加上小数部分
}
return value; // 返回结果
}
// 析出括号之间的计算式子串
// (requires cstring)
char* extract(char* str, int& index)
{
char buffer[MAX]; // 用来存储计算式子串的临时空间
char* pstr(nullptr); // 指向一段新开内存的指针,最后会将其返回,并计算该空间中的计算式的值
int numL(0); // 计算左括号的个数
int bufindex(index); // 保存开始索引值
// 比如 ((3+2)*3 - 4)*5 + 10)/5 这个算数式, 处理完后 buffer[] 里面会存进 (3+2)*3 - 4)*5 + 10 这个子串
//
do
{
buffer[index - bufindex] = *(str + index);
switch(buffer[index - bufindex])
{
case ')':
if(0 == numL) // numL 从第 2 个 '(' 还是累计, 如果为 0 时说明已经到了最末一个 ')'
{
size_t size = index - bufindex;
buffer[index - bufindex] = '\0'; // 将 ')' 代替为 '\0'
++index;
pstr = new char[index - bufindex]; // 分配一段内存空间
if(!pstr)
{
cout << "Memory allocation failed,"
<< " program terminated.";
exit(1);
}
strcpy_s(pstr, index-bufindex, buffer); // 将 buffer[] 里的子串拷贝到新开的空间中
return pstr; // 返回空间指针
}
else
numL--; // 减 '(' 计数
break;
case '(':
numL++; // '(' 计数加一
break;
}
} while(*(str + index++) != '\0'); // 循环
cout << "Ran off the end of the expression, must be bad input."
<< endl;
exit(1);
}
这里说明一下 extract() 函数,首先声明了一个 char 数组,用来临时存放计算式的子串。我们不能把 buffer[ ] 的地址返回给主函数,因为它只是个局部数组变量,在函数退出时会被销毁。因此,我们就从堆中分配了一些内存,并声明了一个 char 型的指针变量 pstr ,它用来指向该分配的内存,在函数结束时将其返回,这样被分配内存中的子串就会安然无恙。
numL 是一个计数器,它用来记录计算式字符串中左括号中的数量。
index 的初始值存储在 bufindex 变量中,我们使用 index 来索引数组 buffer 。
在 do-while 循环中,每次循环迭代都会将一个字符从 str 复制到 buffer ,同时检查该字符是左括号还是右括号。如果是左括号,那么 numL 加 1;如果是右括号,并且 numL 不为 0 时,使 numL 减 1 。如果找到右括号时 numL 等于 0,那么表明已经抵达该子串的末尾,并用 '\0' 来代替 buffer 内子串中的 ')' ,然后分配一个足够的内存来容纳该子串,接着使用 cstring 头文件中的 strcpy_s() 函数将 buffer 中的子串复制到之前分配的内存空间中。 |