一、实验目的

        表达式求值是实现程序设计语言的基本问题之一,也是栈的应用的一个典型例子。一个算术表达式是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。假设操作数是正整数,运算符只含加减乘除等四种二元运算符,界限符有左右括号和表达式起始、如:(7+15)*(23-28/4)。引入表达式起始、结束符是为了方便。设计一个程序,演示算术表达式求值的过程。


二、实验要求

实验要求:

  1. 从文本文件输入任意一个语法正确的(中缀)表达式,显示并保存该表达式。
  2. 利用栈结构,把(中缀)表达式转换成后缀表达式,并以适当的方式展示栈的状态变化过程和所得到的后缀表达式。
  3. 利用栈结构,对后缀表达式进行求值,并以适当的方式展示栈的状态变化过程和最终结果。

  • 设计思想

        程序大体思路十分清晰,分为读入中缀表达式、转换为后缀表达式、后缀表达式求值三个部分。

图2-1 程序总体思路

        使用char类型栈的结构形式来进行表达式的存储,接下来分为三部分进行阐述。

表1 函数定义

函数定义

作用

typedef struct SqStack SqStack;

定义栈的结构

SqStack *initSqStack();

顺序栈的初始化

void SqPush(SqStack *S, elemtype e);

入栈

elemtype SqPop(SqStack *S);

出栈

elemtype SqGetTop(SqStack *S);

获取栈顶元素

int isEmpty(SqStack *S);

判断栈非空

int is_num(char c);

判断是数字

int priority(char c);

计算操作符优先级

void judge_priority(char operator, SqStack * S);

比较操作符优先级

float arr2num(char temp_num[], int num_of_int, int num_of_decimal);

将字符数组转化为数字


1 读入中缀表达式

        使用文件操作,将存储在“arithmetic_expression.txt”中的表达式写入字符数组expr中。其中,表达式有以下要求:

        ①仅包含加减乘除和次方操作,可以使用括号;

        ②表达式间没有空格。

        示例:

图2-2 表达式写入示例


2 转换后缀表达式

2.1 后缀表达式转换规则

使用char类型栈将中缀表达式转化为后缀表达式,具体规则如下:

从头到尾读取中缀表达式的每个对象,按不同情况处理。

  • 运算数:直接输出;
  • 左括号:压入栈内;
  • 右括号:将栈顶的运算符弹出并输出,直至遇到左括号(出栈但不输出);
  • 运算符:
    1. 若优先级大于栈顶运算符,则把它压栈;
    2. 若优先级小于等于栈顶运算符,将栈顶运算符弹出并输出;再与新的栈顶运算符进行比较,直至该运算符优先级大于栈顶运算符优先级为止,然后将该运算符压栈;
  • 当所有对象处理完毕,则把栈内存留的运算符一并输出。

2.2 运算符优先级

入栈前的左括号 > 次方 > 乘 = 除 > 加 = 减 > 入栈后的左括号

具体实现:priority函数

2.3 运算数的判定

由于本程序将运算数扩展到了实数范围,所以不可避免会遇到小数以及多位数,因此,对于运算数的判定规则与处理方式如下:

  • 认为小数点(.)也为运算数;
  • 遍历中缀表达式,若当前对象判定为运算数且下一位也是运算数,正常写入数组;若当前对象判定为运算数且下一位是操作符,则写入字符’e’作为数字的结束。

按照上述规则,对前例的中缀表达式,在result数组中存储为下:

表2 后缀表达式存储示例

9

e

3

2

.

2

e

1

e

-

3

2

e

*

+

0

e

8

9

e

-

2

e

/

+


3 后缀表达式求值

3.1 预处理

        由于数字是以char类型存放在result数组中,并不可以直接进行运算,所以首先通过arr2num函数将char类型数字转化为float类型数字,并给予其标号,将最终结果分别存储在nums(运算数)和processed_expression(运算符及数字标号)数组中。

        处理结果如下所示:

表3 原数组result

9

e

3

2

.

2

e

1

e

-

3

2

e

*

+

0

e

8

9

e

-

2

e

/

+

表4 处理后数组processed_expression

0

1

2

-

3

*

+

4

5

-

6

/

+

        其中,数字0为char类型,代表第1个数字,1代表第二个数字......与此同时,nums数组存储如下:

表5 nums数组

内容

9

32.2

1

32

0

89

2

下标

0

1

2

3

4

5

6

        可以看到,每一个运算数其数字标号与其在nums中的下标是一一对应的,便于索引。

(1)arr2num的实现

        构造char类型的temp_num数组用来临时存放某一个数字,使用flag作为当前数字是否为小数位的标志,使用num_of_decimal作为小数位数的计数器、num_of_int作为整数位数的计数器。

        遍历result字符串:

        ① 如果发现为数字,则将其存入temp_num数组;具体来说,若flag等于0,则说明其为整数位,num_of_int计数器加1,若flag等于1,则说明其为小数位,num_of_decimal计数器加1。

        ② 若发现为’.’,则flag置为1;若发现为’e’,则标志此数字已经写入完成,进行后续处理操作,且将计数器置0,flag置0;

        ③ 若发现为操作符,则忽略。

        以数字32.2为例,其相应变量存储如下:

表6 相关变量

32.2

temp_num

num_of_int

num_of_decimal

3

2

2

2

1

        由此可知每个数字对应的权重。

表7 对应权重

temp_num

3

2

2

权重

10^1

10^0

10^(-1)

故将其分别与权重为相乘再求和即可得到数字,将其存入nums数组中。

(2)标号的必要性

        由于本程序采用的是char类型的栈,因此栈中只可以存放char类型数据。而运算数为float类型,显然不可以存储。因此提出了以标号对应下标的形式,后续操作中入栈出栈的为其标号,对其操作时使用nums[(int)(index-’0’)]即可访问原数据。

        此外,还可以选择新建一个float类型的栈,便可以不额外增加标号对应下标这一步骤,但此操作需要重新编写float栈相关的定义以及一系列函数,会造成代码量的大幅增加和重复,因此不予考虑。

3.2 后缀表达式求值运算

        目前我们得到以下数据:

表8 已有数据

数组

含义

内容

processed_expression

含数字标号

的后缀表达式

0

1

2

-

3

*

+

4

5

-

6

/

+

nums

标号所对应的数字

9

32.2

1

32

0

89

2

        为了方便阐述,下将标号与标号所对应的数字视为同一件事。

        按照如下规则使用栈进行运算:

  • 若是数字,则将其压入栈中;
  • 若是操作符(二目),则取出栈中最顶部的两个数字做对应运算,并将其运算结果压入栈中;
  • 当后缀表达式遍历结束时,栈中元素即为最终结果,出栈。

注:②中将运算结果压入栈中的处理:将计算结果存入nums数组,并给其对应标号。此时可以发现,当运算过程过长时,char类型的下标0~9都被用完,因此接着使用后续字符作为下标,例如,:的ASCII码值为58,’:’-’0’ = 10,故作为下标10使用,以此类推。

        由此我们便可以计算出后缀表达式的值。


三、源代码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define elemtype char
#define MAXSIZE 100       // 栈的最大容量
int count = 0;            // 记录结果中有多少个数
char result[2 * MAXSIZE]; // 存放后缀表达式结果
int num_end_flag;         // 标记上一个输出的为数字

typedef struct SqStack SqStack;                                     // 定义栈的结构
SqStack *initSqStack();                                             // 顺序栈的初始化
void SqPush(SqStack *S, elemtype e);                                // 入栈
elemtype SqPop(SqStack *S);                                         // 出栈
elemtype SqGetTop(SqStack *S);                                      // 获取栈顶元素
int isEmpty(SqStack *S);                                            // 判断非空
int is_num(char c);                                                 // 判读是数字
int priority(char c);                                               // 计算操作符优先级
void judge_priority(char operator, SqStack * S);                    // 比较操作符优先级
float arr2num(char temp_num[], int num_of_int, int num_of_decimal); // 将数组转化为数字

int main()
{
    int i = 0;
    char expr[MAXSIZE];
    SqStack *stack = initSqStack();

    /*--- 将表达式读取到expr数组中 ---*/
    FILE *expression = fopen("./arithmetic_expression.txt", "r");
    if (!expression)
    {
        printf("fail to open!\n");
    }
    else
    {
        for (i = 0; !feof(expression); i++)
        {
            expr[i] = fgetc(expression); //将文件内容赋给expr数组
        }
        fclose(expression);
    }
    expr[i] = '\0';
    int len = strlen(expr) - 1;
    printf("The infix expression read is: ");
    printf("%s\n", expr);
    printf("----------------------------------------------------------------------------------\n");
    printf("Infix to suffix process:\n");

    /*--- 使用栈将中缀表达式转化为后缀表达式 ---*/
    num_end_flag = 0;
    for (i = 0; i < len; i++)
    {
        // 数字 直接输出
        if (is_num(expr[i]))
        {
            printf("Step%d: '%c' is number, output\n\n", i + 1, expr[i]);
            result[count] = expr[i];
            count++;
            if (!is_num(expr[i + 1]))
            {
                result[count] = 'e';
                count++;
            }
        }
        else
        {
            // 操作符 进行处理
            printf("Step%d: '%c' is operator, process\n", i + 1, expr[i]);

            // 左括号 压入栈中
            if (expr[i] == '(')
            {
                printf("- '(' has the highest priority, push it into stack\n\n");
                SqPush(stack, expr[i]);
                continue;
            }

            // 右括号 出栈直至左括号
            if (expr[i] == ')')
            {
                printf("- pop stack until meet '('\n");
                char temp = SqGetTop(stack);
                while (temp != '(')
                {
                    result[count] = temp; // 将出栈结果存储
                    count++;
                    printf("- '%c' out of stack\n", temp);
                    SqPop(stack);
                    temp = SqGetTop(stack);
                }
                SqPop(stack); // 将'('出栈,不输出
                printf("- '(' out of stack\n\n");
                continue;
            }
            // 普通运算符 比较优先级
            judge_priority(expr[i], stack);
            printf("\n");
        }
    }
    // 所有元素处理结束 逐个出栈
    printf("All operator has processed, output the operators in the stack in turn\n");
    while (!isEmpty(stack))
    {
        char temp = SqGetTop(stack);
        result[count] = temp;
        count++;
        printf("- output '%c'\n", temp);
        SqPop(stack);
    }
    // 输出后缀表达式结果
    printf("\n----------------------------------------------------------------------------------\n");
    printf("The postfix expression is: ");
    for (i = 0; i < count; i++)
        if (result[i] == 'e')
            printf(" ");
        else
        {
            if (is_num(result[i]))
                printf("%c", result[i]);
            else
                printf("%c ", result[i]);
        }
    printf("\n");

    /* 进行运算前的预处理,将字符转化为数字 */
    // char result[MAXSIZE] = {"9e32.2e1e-3e2e^*+0e89e-2e/+"};
    len = strlen(result);
    char temp_num[MAXSIZE]; // 用于存放需处理的数字
    int count_temp_num = 0;
    float nums[MAXSIZE];                // 用于存放处理完的数字们
    char processed_expression[MAXSIZE]; //处理后的表达式(数字使用下标代替)
    int individuals = 0;                // 处理后的表达式中元素的个数
    int index = 0;                      // 数字的下标  num_i
    int flag = 0;                       // 作为已经是小数的标志
    int num_of_decimal = 0;             // 小数的位数
    int num_of_int = 0;                 // 整数的位数

    for (i = 0; i < len; i++)
    {
        //如果是数字,则进行处理
        if (is_num(result[i]))
        {
            if (result[i] == '.')
                flag = 1;
            else
            {
                if (flag)
                    num_of_decimal++;
                else
                    num_of_int++;
                temp_num[count_temp_num] = result[i];
                count_temp_num++;
            }
        }
        else
        {
            // 若不是数字,则将运算符存起来
            if (result[i] != 'e')
            {
                processed_expression[individuals] = result[i];
                individuals++;
            }
            // 如果是e,则说明之前为数字,处理之前的数组
            else
            {
                float number = arr2num(temp_num, num_of_int, num_of_decimal);
                nums[index] = number;
                processed_expression[individuals] = index + '0';
                individuals++;
                index++;
                num_of_decimal = 0;
                num_of_int = 0;
                flag = 0;
                count_temp_num = 0;
            }
        }
    }

    /* 后缀表达式求值 */
    printf("\n----------------------------------------------------------------------------------\n");
    printf("Evaluation of suffix expression:\n\n");
    len = strlen(processed_expression);
    float temp_result[MAXSIZE];
    int temp_index = 0;
    for (i = 0; i < len; i++)
    {
        printf("Step%d ", i + 1);
        if (is_num(processed_expression[i]))
        {
            printf("number:%f, push it into stack\n\n", nums[(int)(processed_expression[i] - '0')]);
            SqPush(stack, processed_expression[i]);
        }
        else
        {
            printf("operator:%c, caculate\n", processed_expression[i]);
            char num2 = SqPop(stack);
            char num1 = SqPop(stack);
            printf("- get the top element of the stack, num2: %f\n", nums[(int)(num2 - '0')]);
            printf("- get the top element of the stack, num1: %f\n", nums[(int)(num1 - '0')]);
            float res12;
            switch (processed_expression[i])
            {
            case '-':
                res12 = nums[(int)(num1 - '0')] - nums[(int)(num2 - '0')];
                printf("- caculate %f - %f\n", nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                break;
            case '+':
                res12 = nums[(int)(num1 - '0')] + nums[(int)(num2 - '0')];
                printf("- caculate %f + %f\n", nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                break;
            case '*':
                res12 = nums[(int)(num1 - '0')] * nums[(int)(num2 - '0')];
                printf("- caculate %f * %f\n", nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                break;
            case '/':
                res12 = nums[(int)(num1 - '0')] / nums[(int)(num2 - '0')];
                printf("- caculate %f / %f\n", nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                break;
            case '^':
                res12 = pow(nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                printf("- caculate %f ^ %f\n", nums[(int)(num1 - '0')], nums[(int)(num2 - '0')]);
                break;
            default:
                break;
            }
            char res_name = index + '0';
            nums[index] = res12;
            index++;
            SqPush(stack, res_name);
            printf("- push the caculate result %f into stack\n\n", res12);
        }
    }
    int final_index = (int)(SqGetTop(stack) - '0');
    float final_res = nums[final_index];
    printf("Calculation completed\n");
    printf("The calculation results result is %f\n", final_res);
    printf("\n----------------------------------------------------------------------------------\n");
    printf("%s = %f\n\n", expr, final_res);
    system("pause");
    return 0;
}

/* 将数组转化为数字 */
float arr2num(char temp_num[], int num_of_int, int num_of_decimal)
{
    int p = 0;
    float res = 0;
    int temp;
    for (p = 0; p < num_of_int; p++)
    {
        temp = temp_num[p] - '0';
        res += (float)(temp * pow(10, num_of_int - p - 1));
    }
    if (num_of_decimal != 0)
    {
        for (p = num_of_int; p < num_of_int + num_of_decimal; p++)
        {
            temp = temp_num[p] - '0';
            res += (float)(temp * pow(10, -p + num_of_int - 1));
        }
    }
    return res;
}

/* 判断是否是数字 */
int is_num(char c)
{
    if (c >= '0' && c <= '9' || c == '.' || (c >= ':' && c <= 'd' && c != '^'))
        return 1;
    else
        return 0;
}

/* 比较运算符的优先级 */
void judge_priority(char operator, SqStack * S)
{
    int p_oper = priority(operator); // 运算符的优先级
    int p_stack;                     // 栈顶运算符的优先级
    if (!isEmpty(S))
    {
        p_stack = priority(SqGetTop(S));
    }
    else
    {
        p_stack = 0; // 若栈顶为空,直接置为最低优先级0
    }

    // 优先级大于栈顶运算符,压栈
    if (p_oper > p_stack)
    {
        printf("- the priority of '%c' is higher than the top('%c') of stack, push it into stack\n", operator, SqGetTop(S));
        SqPush(S, operator);
        return;
    }
    else
    {
        // 优先级小于等于栈顶运算符,输出栈顶,直到优先级大于栈顶
        do
        {
            printf("- the priority of '%c' is less than or equal to the top('%c') of stack, pop the top out\n", operator, SqGetTop(S));
            result[count] = SqGetTop(S);
            count++;
            SqPop(S);

            // 重新计算栈顶优先级
            if (!isEmpty(S))
                p_stack = priority(SqGetTop(S));
            else
                p_stack = 0;
        } while (p_oper <= p_stack);
        printf("- the priority of '%c' is higher than the top('%c') of stack, push it into stack\n", operator, SqGetTop(S));
        SqPush(S, operator);
    }
}

/* 计算运算符的优先级 */
int priority(char c)
{
    switch (c)
    {
    case '+':
        return 1;
    case '-':
        return 1;
    case '*':
        return 2;
    case '/':
        return 2;
    case '(':
        return 0;
    case '^':
        return 3;
    default:
        return 0;
    }
}

/*------------------- 栈的顺序存储 -------------------*/
typedef struct SqStack
{
    elemtype *base; // 栈底指针
    elemtype *top;  // 栈顶指针 - 指向最高元素的上一个
    int stacksize;  // 栈可用的最大容量
} SqStack;

/* 顺序栈的初始化(创建) */
SqStack *initSqStack()
{
    SqStack *S = (SqStack *)malloc(sizeof(SqStack));
    S->base = (elemtype *)malloc(sizeof(elemtype) * MAXSIZE); // 栈底指向一个拥有MAXSIZE个元素大小的空间
    S->top = S->base;                                         // 初始化栈顶=栈底
    S->stacksize = MAXSIZE;
    return S;
}

/* 顺序栈的入栈 */
void SqPush(SqStack *S, elemtype e)
{
    if (S->top - S->base == S->stacksize)
    {
        printf("stack overflow!");
        return;
    }
    *(S->top) = e;
    S->top += 1;
}

/* 出栈 */
elemtype SqPop(SqStack *S)
{
    if (S->top == S->base)
    {
        printf("stack is empty!");
        return -1;
    }
    S->top--;
    return *(S->top);
}

/* 获取栈顶元素 */
elemtype SqGetTop(SqStack *S)
{
    if (S->top == S->base)
    {
        printf("stack is empty! ");
        return -1;
    }
    return *(S->top - 1);
}

/* 判断非空 */
int isEmpty(SqStack *S)
{
    if (S->top == S->base)
        return 1;
    else
        return 0;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐