曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 2436|回复: 0
打印 上一主题 下一主题

计算器(增强版,支持括号)

[复制链接]

716

主题

734

帖子

2946

积分

超级版主

Rank: 9Rank: 9Rank: 9

积分
2946
跳转到指定楼层
楼主
发表于 2013-7-9 18:27:18 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
该版本计算器代码支持计算式中有括号的运算,比如可以计算 (((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 中的子串复制到之前分配的内存空间中。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-15 15:40 , Processed in 0.071130 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表