中缀表达式计算器

主程序

// entry.c
// 主函数所在的文件。
// 作者:黄铎彦,福州大学软件学院软件工程系 2022 级(4)班,学号 222200428,
// 邮箱 HuangDY2016@outlook.com,编写完成于 2023/1/29。Copyright (C) 2023。
// 本程序使用基于链表的栈和队列,先将用户输入的中缀表达式转化成后缀表达式,
// 再计算转化后的后缀表达式,最后输出计算结果。支持加减乘除、乘方和小括号运算。
// 开发环境:Microsoft Visual Studio Enterprise 2022(64位)版本 17.4.4,
// 工作负荷为 使用 C++ 的桌面开发,C 语言标准为 ISO C17(2018)/std:c17;
// Microsoft Windows 10 家庭版 22H2(内部版本 19045.2486)64位。
// 运行环境:64 位的 Windows,建议为 Windows 10 或更高版本。
// 参考文献:《数据结构与算法分析(C语言描述)》第 2 版,Mark Allen Weiss 著
// 修改记录:
// 2023/2/12 新增了一个错误类型:非法的数字。当用户输入形如 1e 的数字时报错。
// 已知问题:
// 2023/1/30 不能通过 (-0.125)^(1/3) 来计算立方根,它会输出 -nan(ind);
//           按 F7 选择之前输入的表达式再按下回车之后,没反应,再按下回车之后,
//           报告错误:操作数太多。这可能是 Windows 控制台的 Bug。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "calc_expr.h"
static inline void show_usage(void);

int main(void)
{
    Queue q;
    int err = init_q(&q);
    if (err)
    {
        SHOW_ERR("初始化队列失败!\n");
        return NO_MEM;
    }

    show_usage();
    while (1)
    {
        err = in_to_post(&q);
        if (err) SHOW_ERR("中缀转后缀失败,代码 %d。\n\n", err);
        else
        {
#ifndef NDEBUG  // 此宏仅在 Release 模式下有定义
            pri_q(&q); // Debug 模式下用于调试的代码
#endif // !NDEBUG

            double res;
            err = calc_post(&q, &res);
            if (err) SHOW_ERR("计算后缀失败,代码 %d。\n\n", err);
            else     printf("表达式的值为 %g。\n\n", res);
        }
        empty_q(&q);
    }
    return 0;
}

// 提示用户程序的用法
inline void show_usage(void)
{
    puts("欢迎使用表达式计算程序!版本:1.0,作者:黄铎彦。\n"
        "本程序可以计算包含加(+)、减或相反数(-)、乘(*)、除(/)、"
        "乘方(^)和小括号运算符的算式。\n\n您每次只需在英文模式下"
        "输入待计算的表达式(注意:乘号不可省略),\n"
        "按下回车,计算结果就会显示在输入行的下方。\n"
        "您可以一直输入你想计算的表达式,直到您点击关闭按钮退出本程序。\n\n"
        "示例输入:\n1/(2^(1/2)+1)\n\n"
        "示例输出:\n表达式的值为 0.414214。\n\n"
        "如果您在使用过程遇到问题,请联系我,"
        "邮箱:HuangDY2016@outlook.com。祝您使用愉快!\n");
}

依赖的各个文件

头文件

calc_expr.h

// calc_expr.h
// 计算表达式的值
#pragma once
#ifndef _INC_CALC_EXPR
#define _INC_CALC_EXPR

#include "queue.h"
#include <stdio.h>

/*               各种错误代码               */
#define INVALID_OP          -1  // 非法的运算符
#define MISSING_BRACKET_R   -2  // 缺少右括号
#define MISSING_BRACKET_L   -3  // 缺少左括号
#define MISSING_NUM         -4  // 缺少操作数
#define TOO_MANY_NUM        -5  // 过多的操作数
#define INVALID_NUM         -6  // 非法的数字,比如 1e,2.e.。2023/2/12

#define OP_SIZE     sizeof(int)     // 运算符的大小
#define NUM_SIZE    sizeof(double)  // 数的大小

/*              各种运算符              */
#define ADD_OP  '+' // 加法运算符
#define SUB_OP  '-' // 减法运算符
#define NEG_OP  '_' // 负号(与减法运算符区别)
#define MUL_OP  '*' // 乘法运算符
#define DIV_OP  '/' // 除法运算符
#define POW_OP  '^' // 乘方运算符
#define LBRKT   '(' // 左括号
#define RBRKT   ')' // 右括号

/// <summary>
/// 在标准错误流(stderr)中输出错误信息
/// </summary>
/// <param name="__VA_ARGS__">可变参数,包含两部分:
/// <para>1.(必选)存储错误信息的格式字符串(需要自行添加换行符)。</para>
/// <para>2.(可选)与格式字符串中的转换说明相对应的一个或多个参数。</para>
/// <para>当两个部分同时存在时,必须用逗号隔开。</para>
/// </param>
#define SHOW_ERR(...) fprintf(stderr, __VA_ARGS__)

/// <summary>
/// 从标准输入流中读取中缀表达式,将它转化为后缀表达式后存放在队列中
/// <para>
/// 不能保证生成的后缀表达式不会出现缺操作数、操作数过多的错误。但可以识别出
/// 中缀表达式中括号不匹配、运算符非法的错误。出现错误时表达式队列可能非空。
/// </para>
/// <para>函数定义在 in_to_post.c 中</para>
/// </summary>
/// <param name="pq">
/// 存储后缀表达式的队列(已初始化,主调函数中的自动变量)的地址
/// </param>
/// <returns>如果转化过程中没有错误,返回 0;否则返回非 0。</returns>
int in_to_post(Queue* const pq);

/// <summary>
/// 计算后缀表达式的值。空表达式的值为 0。出现错误时表达式队列可能非空。
/// </summary>
/// <para>函数定义在 calc_post.c 中</para>
/// <param name="pq">
/// 存储后缀表达式的队列(已初始化,主调函数中的自动变量)的地址
/// </param>
/// <param name="pval">参数输出:后缀表达式的值</param>
/// <returns>如果计算成功,返回 0;否则说明表达式非法,返回非 0。</returns>
int calc_post(Queue* const pq, double* const pval);

#endif // !_INC_CALC_EXPR

linked_list.h

// linked_list.h
// 带哨兵结点的单向链表。所有非内联函数的定义都在 linked_list.c 中。
#pragma once
#ifndef _INC_LINKED_LIST
#define _INC_LINKED_LIST

#define NO_MEM  1   // 内存申请失败导致的内存不足
#include <stddef.h>
#include <assert.h>

/// <summary>
/// 链表的数据域。
/// <para>
/// 因为所存储的数据类型不明确,所以需要根据数据的大小进行动态内存分配。
/// </para>
/// </summary>
typedef struct Data
{
    /// <summary>
    /// 指向数据的指针
    /// <para>除哨兵结点数据域的这个指针指向空之外,
    /// 其余的都需要动态内存分配</para>
    /// </summary>
    void*   pointer;
    size_t  size;    // 数据的大小
}Data;

/// <summary>
/// 链表的结点。
/// <para>对链表的操作,除初始化需要用指向哨兵结点的指针的指针作参数外,
/// 其余需要用指向哨兵结点的指针作参数。</para>
/// </summary>
typedef struct Node
{
    Data            data;
    struct Node*    next;
}Node;

/// <summary>
/// 初始化链表
/// </summary>
/// <param name="pphead">指向哨兵的指针的地址(非空)</param>
/// <returns>如果成功,返回 0;否则返回 NO_MEM。</returns>
int init_list(Node** const pphead);

/// <summary>
/// 释放链表
/// </summary>
/// <param name="phead">指向哨兵的指针</param>
void free_list(Node* const phead);

/// <summary>
/// 检查链表(已初始化)是否为空
/// </summary>
/// <param name="phead">指向哨兵的指针</param>
/// <returns>如果链表为空,返回真(1);否则返回假(0)。</returns>
inline int is_empty_list(const Node* const phead)
{
    assert(phead != NULL);
    return phead->next == NULL;
}

/// <summary>
/// 删除链表(非空)的第一个结点
/// </summary>
/// <param name="phead">指向哨兵的指针</param>
void del_first(Node* const phead);

/// <summary>
/// 拷贝数据到新结点中
/// </summary>
/// <param name="pnew">使用 malloc 函数新建的结点(非空)</param>
/// <param name="pdata">指向数据的指针(非空)</param>
/// <param name="data_size">数据的大小</param>
/// <returns>如果操作成功,返回 0;否则说明空间不够,返回 NO_MEM。</returns>
int cpy_data(Node* pnew, const void* const pdata, const size_t data_size);

/// <summary>
/// 如果链表非空,清空链表中除哨兵结点外的所有结点及内容;否则什么也不做
/// </summary>
/// <param name="phead">指向哨兵的指针</param>
void empty_list(Node* const phead);

#endif // !_INC_LINKED_LIST

queue.h

// queue.h
// 基于链表的队列。所有非内联函数的定义都在 queue.c 中。
#pragma once
#ifndef _INC_QUEUE
#define _INC_QUEUE

#include "linked_list.h"
#include <string.h>

/// <summary>
/// 队列类型。
/// <para>将此类型作为函数参数时,应传指针。</para>
/// </summary>
typedef struct Queue
{
    Node* pqlist; // 将链表中指向哨兵结点的指针抽象为队列中的元素列表
    Node* front, * rear;
}Queue;

/// <summary>
/// 初始化队列
/// </summary>
/// <param name="pq">队列(主调函数中的自动变量)的地址</param>
/// <returns>如果成功,返回 0;否则返回 NO_MEM。</returns>
int init_q(Queue* const pq);

/ <summary>
/ 释放队列
/ </summary>
/ <param name="pq">队列(主调函数中的自动变量)的地址</param>
//void free_q(Queue* const pq);

/// <summary>
/// 入队
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <param name="pdata">指向数据的指针(非空)</param>
/// <param name="data_size">数据的大小</param>
/// <returns>如果操作成功,返回 0;否则说明空间不够,返回 NO_MEM。</returns>
int en_q(Queue* const pq, const void* const pdata, const size_t data_size);

/// <summary>
/// 检查指针 <paramref name="pq"/> 指向的队列(已初始化)是否为空队列
/// </summary>
/// <returns>如果为空队列,返回真(1);否则返回假(0)。</returns>
inline int is_empty_q(const Queue* const pq)
{
    return pq->front == NULL;
}

/// <summary>
/// 获取队列头部结点的数据的大小
/// </summary>
/// <param name="pq">队列(非空,主调函数中的自动变量)的地址</param>
/// <returns>队头第一个结点的数据大小</returns>
inline size_t get_first_size(const Queue* const pq)
{
    assert(!is_empty_q(pq));
    return pq->front->data.size;
}

/// <summary>
/// 读取队头数据。警告:本函数不检查缓冲区的大小!
/// </summary>
/// <param name="pq">队列(非空,主调函数中的自动变量)的地址</param>
/// <param name="buf">待将队头数据写入的缓冲区</param>
inline void get_front(const Queue* const pq, void* const buf)
{
    assert(!is_empty_q(pq));
#define FRONT_DATA (pq->front->data)
    memcpy(buf, FRONT_DATA.pointer, FRONT_DATA.size);
#undef  FRONT_DATA
}

/// <summary>
/// 删除队头元素
/// </summary>
/// <param name="pq">队列(非空,主调函数中的自动变量)的地址</param>
void de_q(Queue* const pq);

#ifndef NDEBUG

/// <summary>
/// 打印队列内容
/// </summary>
/// <param name="pq">队列(非空,主调函数中的自动变量)的地址</param>
void pri_q(Queue* pq);

#endif // !NDEBUG

/// <summary>
/// 如果队列非空,则清空队列中的内容;否则什么也不做。
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
void empty_q(Queue* pq);

#endif // !_INC_QUEUE

stack.h

// stack.h
// 基于链表的栈。所有非内联函数的定义都在 stack.c 中。
#pragma once
#ifndef _INC_STACK
#define _INC_STACK

#include "linked_list.h"
#include <string.h>

/// <summary>
/// 栈的类型。
/// <para>作为函数参数时,初始化时应传指针,其他时候直接传变量。</para>
/// </summary>
typedef Node* Stack;

/// <summary>
/// 初始化栈
/// </summary>
/// <param name="pstack">指向栈的指针</param>
/// <returns>如果成功,返回 0;否则返回 NO_MEM。</returns>
inline int init_stack(Stack* const pstack)
{
    return init_list(pstack);
}

/// <summary>
/// 释放栈 <paramref name="stack"/> 的所有空间
/// </summary>
inline void free_stack(const Stack stack)
{
    free_list(stack);
}

/// <summary>
/// 检查栈 <paramref name="stack"/>(已初始化)是否为空
/// </summary>
/// <returns>如果为空,返回真(1);否则返回假(0)。</returns>
inline int is_empty_stack(const Stack stack)
{
    return is_empty_list(stack);
}

/// <summary>
/// 进栈
/// </summary>
/// <param name="stack">栈(已初始化)</param>
/// <param name="pdata">指向数据的指针(非空)</param>
/// <param name="data_size">数据的大小</param>
/// <returns>如果操作成功,返回 0;否则说明空间不够,返回 NO_MEM。</returns>
int push(const Stack stack, const void* const pdata, const size_t data_size);

/// <summary>
/// 读取栈顶数据。警告:本函数不检查缓冲区的大小!
/// </summary>
/// <param name="stack">栈(非空)</param>
/// <param name="buf">待将栈顶数据写入的缓冲区</param>
inline void get_top(const Stack stack, void* const buf)
{
    assert(!is_empty_stack(stack));
#define TOP_DATA (stack->next->data)
    memcpy(buf, TOP_DATA.pointer, TOP_DATA.size);
#undef  TOP_DATA
}

/// <summary>
/// 删除栈顶元素
/// </summary>
/// <param name="stack">栈(非空)</param>
inline void pop(const Stack stack)
{
    del_first(stack);
}

#endif // !_INC_STACK

源文件

calc_post.c

// calc_post.c
#include "calc_expr.h"
#include "stack.h"
#include <math.h>
#include <stdlib.h>

/// <summary>
/// 检查错误代码是否非零。如果非零则结束函数,返回该错误代码。
/// </summary>
/// <param name="err_code">错误代码</param>
#define CHKERR(err_code) if (err_code) return err_code

#pragma region 计算后缀表达式所需的内部函数原型

/// <summary>
/// 如果队头是数字,就调用本函数将它压入栈中。
/// </summary>
/// <param name="pq">
/// 保存后缀表达式的队列(非空,主调函数中的自动变量)的地址
/// </param>
/// <param name="s">处理和保存操作数用的栈(已初始化)</param>
/// <returns>操作成功返回 0,出错返回非 0 值。</returns>
static int num_proc(Queue* const pq, Stack s);

/// <summary>
/// 如果队头是运算符,就调用本函数,从栈中弹出两个数来运算,再将结果压入栈中。
/// </summary>
/// <param name="pq">
/// 保存后缀表达式的队列(非空,主调函数中的自动变量)的地址
/// </param>
/// <param name="s">处理和保存操作数用的栈(已初始化)</param>
/// <returns>操作成功返回 0,出错返回非 0 值。</returns>
static int op_proc(Queue* const pq, Stack s);

/// <summary>
/// 从操作数栈的栈顶弹出一个操作数
/// </summary>
/// <param name="s">操作数栈</param>
/// <param name="pnum">参数输出:操作数的地址</param>
/// <returns>操作成功返回 0;栈为空时说明缺少操作数,返回非 0。</returns>
static int pop_num(Stack s, double* pnum);

#pragma endregion

int calc_post(Queue* const pq, double* const pval)
{
    assert(pval != NULL);
    *pval = 0;
    Stack s;    // 存放操作数用的栈,元素都是 double 型
    int err = init_stack(&s);
    while (!err && !is_empty_q(pq))
    {
        size_t fs = get_first_size(pq); // 队头元素数据的大小
        if      (fs == NUM_SIZE)    err = num_proc(pq, s);
        else if (fs == OP_SIZE)     err = op_proc(pq, s);
        else                        assert(0);
    }
    if (!err && !is_empty_stack(s))
    {
        pop_num(s, pval);
        if (!is_empty_stack(s))
        {
            SHOW_ERR("操作数太多!\n");
            err = TOO_MANY_NUM;
        }
    }
    free_stack(s);
    return err;
}

#pragma region 计算后缀表达式所需的内部函数定义

int num_proc(Queue* const pq, Stack s)
{
    double n;
    get_front(pq, &n);
    de_q(pq);
    return push(s, &n, NUM_SIZE);
}

int op_proc(Queue* const pq, Stack s)
{
    int op;
    get_front(pq, &op);
    de_q(pq);
    double right;   // 右操作数
    int err = pop_num(s, &right);
    CHKERR(err);

    double res;
    if (op == NEG_OP) res = -right;
    else
    {
        double left;    // 左操作数
        err = pop_num(s, &left);
        CHKERR(err);

        switch (op)
        {
        case ADD_OP: res = left + right;               break;
        case SUB_OP: res = left - right;               break;
        case MUL_OP: res = left * right;               break;
        case DIV_OP: res = left / right;               break;
        case POW_OP: res = pow(left, right);           break;
        default    : assert(0);
        }
    }
    if (!err) push(s, &res, NUM_SIZE);
    return err;
}

int pop_num(Stack s, double* pnum)
{
    if (!is_empty_stack(s))
    {
        get_top(s, pnum);
        pop(s);
        return 0;
    }
    SHOW_ERR("缺少操作数!\n");
    return MISSING_NUM;
}

#pragma endregion

in_to_post.c

// in_to_post.c
#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include "calc_expr.h"
#include "stack.h"

#pragma region 计算中缀表达式所需的类型和宏定义

typedef enum Op_rank // 运算符的优先级,值越大,优先级越高
{
    add_sub, mul_div, 
    neg, // 负号的优先级
    power, bracket
}Op_rank;

/// <summary>
/// 在栈非空且栈顶不为左括号并且附加条件成立的情况下,
/// 将栈顶运算符不断弹出到后缀表达式队列中。
/// <para>
/// 在此宏中,已弹出的栈顶运算符记为 top_op,可以在附加条件中引用。
/// </para>
/// </summary>
/// <param name="stack">处理和保存运算符用的栈(已初始化)</param>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <param name="additional_condition">
/// 附加条件,不做要求应该填 true(1)。
/// </param>
#define POP_UNTIL_LEFT_BRACKET(pq, stack, additional_condition) \
{ \
    int top_op = 0; \
    while (!is_empty_stack(stack) && \
        (get_top(stack, &top_op), top_op != '(') && (additional_condition)) \
    { \
        pop(stack); \
        en_q(pq, &top_op, OP_SIZE); \
    } \
}

// 如果当前输入行中还有字符,则使用此宏将包括回车在内的剩余字符读掉。
#define EAT_LINE while (getchar() != '\n')

#pragma endregion

#pragma region 计算中缀表达式所需的内部函数原型

/// <summary>
/// 获取运算符的优先级
/// </summary>
/// <param name="op_ch">
/// 用字符表示的运算符,必须是加、减、乘、除、负、乘方和圆括号中的一个
/// </param>
/// <returns>
/// Op_rank 枚举值,值越大,优先级越高。
/// <para>如果运算符非法,则断言失败,返回 INVALID_OP。</para>
/// </returns>
static Op_rank get_op_rank(const int op_ch);

/// <summary>
/// 比较 <paramref name="op1"/> 和 <paramref name="op2"/> 这两个运算符的优先级
/// </summary>
/// <returns>
/// 如果 <paramref name="op1"/> 的优先级比 <paramref name="op2"/> 高,返回正数;
/// 如果两个运算符的优先级相等,返回 0;
/// 如果 <paramref name="op1"/> 的优先级比 <paramref name="op2"/> 低,返回负数。
/// </returns>
static inline int cmp_rank(const int op1, const int op2);

/// <summary>
/// 判断运算符是否合法
/// </summary>
/// <param name="op">用字符存储的运算符</param>
/// <returns>
/// 如果运算符是加(+)减(-)乘(*)除(/)乘方(^)和圆括号中的任何一个,
/// 返回真;否则返回假。
/// </returns>
static inline int is_valid_op(const int op);

/// <summary>
/// 当前输入的一行没有字符可读时的处理程序。
/// 如果栈非空,则将栈中元素依次弹出,放入队列。
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <param name="s">处理和保存运算符用的栈(已初始化)</param>
/// <returns>仅在括号未成对出现时返回非 0 值,正常情况下返回 0。</returns>
static int no_more_ch_proc(Queue* const pq, Stack s);

/// <summary>
/// 读到右括号时的处理程序。不断弹出栈顶的元素,直到弹出一个左括号。
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <param name="s">处理和保存运算符用的栈(已初始化)</param>
/// <returns>仅在括号未成对出现时返回非 0 值,正常情况下返回 0。</returns>
static int rbracket_proc(Queue* const pq, Stack s);

/// <summary>
/// 除乘方和右括号之外的运算符处理程序。
/// 不断弹出栈顶的运算符并放入队列,直到遇到优先级更低的运算符或者左括号,
/// 最后将新运算符入栈。
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <param name="s">处理和保存运算符用的栈(已初始化)</param>
/// <param name="new_op">新遇到的的运算符,非乘方或右括号</param>
/// <returns>仅在正常情况下返回 0。</returns>
static int other_op_proc(Queue* const pq, Stack s, const int new_op);

// 跳过输入中的空格或水平制表符,
// 读取并返回下一个既不是空格也不是水平制表符的字符。
static int ch_after_blank(void);

/// <summary>
/// 从输入中读取 double 型数字,并放入队列中。
/// </summary>
/// <param name="pq">队列(已初始化,主调函数中的自动变量)的地址</param>
/// <returns>仅在正常情况下返回 0。</returns>
static int num_proc(Queue* pq);

#pragma endregion

int in_to_post(Queue* const pq)
{
    Stack s;    // 存放运算符用的栈,元素都是整型
    int err     = init_stack(&s);
    int unary   = true; // 读取到的加号或减号是否为一元的正/负号
    int ch;
    while (!err && (ch = ch_after_blank()) != '\n')
    {
        if (isdigit(ch))
        {
            ungetc(ch, stdin);
            unary   = false;    // 数字后面的加号或减号一定是二元的
            err     = num_proc(pq);
        }
        else if (is_valid_op(ch))
        {
            // 在下面对各种运算符的处理中,
            // 由于乘方和负号是右结合的,所以必须直接压栈。

            if (ch == POW_OP)
            {
                unary   = true;
                err     = push(s, &ch, OP_SIZE);
            }
            else if (ch == RBRKT)
            {
                unary   = false;
                err     = rbracket_proc(pq, s);
            }
            else if ((ch == SUB_OP || ch == ADD_OP) && !unary 
                || ch == MUL_OP || ch == DIV_OP || ch == LBRKT)
            {
                unary   = true;
                err     = other_op_proc(pq, s, ch);
            }
            else if (ch == SUB_OP && unary)
            {
                ch  = NEG_OP;
                err = push(s, &ch, OP_SIZE);
            }
            else assert(ch == ADD_OP && unary); // 忽略正号
        }
        else
        {
            SHOW_ERR("%c 不是合法的运算符!\n", (char)ch);
            EAT_LINE;
            err = INVALID_OP;
        }
    }
    if (!err) err = no_more_ch_proc(pq, s);
    free_stack(s);
    return err;
}

#pragma region 计算中缀表达式所需的内部函数定义

Op_rank get_op_rank(const int op_ch)
{
    switch (op_ch)
    {
    case ADD_OP: case SUB_OP: return add_sub;
    case NEG_OP:              return neg;
    case MUL_OP: case DIV_OP: return mul_div;
    case POW_OP:              return power;
    case LBRKT : case RBRKT : return bracket;
    default:                  assert(0); return INVALID_OP;
    }
}

inline int cmp_rank(const int op1, const int op2)
{
    return get_op_rank(op1) - get_op_rank(op2);
}

inline int is_valid_op(const int op)
{
    return strchr("+-*/^()", op) != NULL;
}

int no_more_ch_proc(Queue* const pq, Stack s)
{
    POP_UNTIL_LEFT_BRACKET(pq, s, true);
    if (!is_empty_stack(s))
    {
        SHOW_ERR("缺少右括号!\n");
        return MISSING_BRACKET_R;
    }
    return 0;
}

int rbracket_proc(Queue* const pq, Stack s)
{
    POP_UNTIL_LEFT_BRACKET(pq, s, true);
    if (is_empty_stack(s))
    {
        SHOW_ERR("缺少左括号!\n");
        EAT_LINE;
        return MISSING_BRACKET_L;
    }
    pop(s); // 弹出左括号
    return 0;
}

int other_op_proc(Queue* const pq, Stack s, const int new_op)
{
    assert(new_op != POW_OP && new_op != RBRKT);
    POP_UNTIL_LEFT_BRACKET(pq, s, cmp_rank(new_op, top_op) <= 0);
    return push(s, &new_op, OP_SIZE);
}

int ch_after_blank(void)
{
    int ch;
    while (isblank(ch = getchar()));
    return ch;
}

int num_proc(Queue* pq)
{
    double num;
    if (scanf("%lf", &num) != 1) // 2023/2/12
    {                            // 增加此 if 语句判断块
        SHOW_ERR("非法的数字!\n");
        EAT_LINE;
        return INVALID_NUM;
    }

    return en_q(pq, &num, NUM_SIZE);
}

#pragma endregion

linked_list.c

// linked_list.c
#include "linked_list.h"
#include <stdlib.h>
#include <string.h>

int init_list(Node** const pphead)
{
    assert(pphead != NULL);
    *pphead = malloc(sizeof(Node));
    if (*pphead == NULL) return NO_MEM;
    (*pphead)->data.pointer = (*pphead)->next = NULL;
    (*pphead)->data.size    = 0;
    return 0;
}

void free_list(Node* const phead)
{
    empty_list(phead);
    free(phead);
}

void del_first(Node* const phead)
{
    assert(!is_empty_list(phead));
    Node* tmp   = phead->next;
    phead->next = tmp->next;
    free(tmp->data.pointer);
    free(tmp);
}

int cpy_data(Node* pnew, const void* const pdata, const size_t data_size)
{
    assert(pnew != NULL && pdata != NULL);
    pnew->data.size     = data_size;
    pnew->data.pointer  = malloc(data_size);
    if (pnew->data.pointer == NULL) return NO_MEM;
    memcpy(pnew->data.pointer, pdata, data_size);
    return 0;
}

void empty_list(Node* const phead)
{
    Node* next;
    for (Node* p = phead->next; p != NULL; p = next)
    {
        next = p->next;
        free(p->data.pointer);
        free(p);
    }
    phead->next = NULL;
}

queue.c

// queue.c
#include "queue.h"
#include <stdio.h>
#include <stdlib.h>

int init_q(Queue* const pq)
{
    assert(pq != NULL);
    pq->front = pq->rear = NULL;
    return init_list(&pq->pqlist);
}

//void free_q(Queue* const pq)
//{
//    assert(pq != NULL);
//    free_list(pq->pqlist);
//    pq->front = pq->rear = NULL;
//}

int en_q(Queue* const pq, const void* const pdata, const size_t data_size)
{
    assert(pq != NULL && pq->pqlist != NULL && pdata != NULL);
    Node* pnew = malloc(sizeof(Node));
    if (pnew == NULL) return NO_MEM;

    pnew->next = NULL;
    if (is_empty_q(pq))
        pq->front = pq->rear = pq->pqlist->next = pnew;
    else
    {
        pq->rear->next  = pnew;
        pq->rear        = pnew;
    }

    return cpy_data(pnew, pdata, data_size);
}

void de_q(Queue* const pq)
{
    pq->front = pq->front->next;
    if (pq->front == NULL) pq->rear = NULL;
    del_first(pq->pqlist);
}

void empty_q(Queue* pq)
{
    assert(pq != NULL);
    empty_list(pq->pqlist);
    pq->front = pq->rear = NULL;
}

#ifndef NDEBUG

void pri_q(Queue* pq)
{
    for (Node* p = pq->front; p != NULL; p = p->next)
    {
        if (p->data.size == sizeof(int)) 
            printf("%c ", *(char*)p->data.pointer);
        else                            
            printf("%g ", *(double*)p->data.pointer);
    }
    putchar('\n');
}

#endif // !NDEBUG

stack.c

// stack.c
#include "stack.h"
#include <stdlib.h>
#include <assert.h>

int push(const Stack stack, const void* const pdata, const size_t data_size)
{
    assert(stack != NULL && pdata != NULL);
    Node* pnew = malloc(sizeof(Node));
    if (pnew == NULL) return NO_MEM;

#define PHEAD stack
    pnew->next  = PHEAD->next;
    PHEAD->next = pnew;
#undef PHEAD

    return cpy_data(pnew, pdata, data_size);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄铎彦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值