在数据结构的栈应用中,后缀表达式(逆波兰表达式)是经典的实战场景。相较于我们日常使用的中缀表达式,后缀表达式无需括号 即可明确表达运算优先级,极大简化了计算机的表达式解析过程。本文将从后缀表达式的核心概念出发,结合 C 语言完整代码,讲解后缀表达式求值 和中缀表达式转后缀表达式的实现思路,同时对代码进行逐行详细注释,让新手也能轻松理解。
一、核心概念铺垫
1. 中缀表达式
日常书写的表达式形式,运算符位于两个操作数中间,需要通过括号 和运算符优先级 确定运算顺序,例如:x/(i-j)*y、3+4*2。计算机解析中缀表达式时,需要频繁处理优先级和括号,效率较低。
2. 后缀表达式(逆波兰表达式)
运算符位于两个操作数之后,天然无需括号,运算顺序由表达式本身决定,例如:
- 中缀
3+4*2→ 后缀342*+ - 中缀
x/(i-j)*y→ 后缀xij-/y* - 中缀
82/2+56*-(本文测试用例)→ 直接为后缀形式,计算结果为-24
3. 栈的核心作用
无论是后缀表达式求值 还是中缀转后缀,栈都是核心数据结构:
- 求值:栈存储操作数,遇到运算符则弹出两个操作数计算,结果重新入栈;
- 转换:栈存储运算符,遇到操作数直接输出,遇到运算符则根据优先级处理栈内元素,保证运算顺序。
二、后缀表达式求值:原理 + 完整注释代码
1. 求值核心原理
- 初始化一个空栈,用于存储操作数;
- 从左到右遍历后缀表达式的每个字符:
- 若为操作数,直接压入栈中;
- 若为运算符 ,从栈中依次弹出右操作数(op2)和左操作数(op1) (注意弹出顺序,后缀表达式中后弹出的是左操作数),用运算符计算
op1 运算符 op2,将结果压入栈;
- 遍历结束后,栈中仅剩一个元素,即为表达式的计算结果。

2. 完整注释代码(C 语言)
该代码实现了栈的基本操作(初始化、判空、入栈、出栈、取栈顶),并基于栈完成后缀表达式求值,测试用例为82/2+56*-,计算结果为-24。
cpp
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100 // 定义栈的最大容量,避免溢出
// 定义栈中存储的元素类型为int(操作数为整型)
typedef int ElemType;
// 定义栈的结构体:顺序栈实现
typedef struct
{
ElemType *data; // 动态数组,存储栈的元素
int top; // 栈顶指针,初始为-1(空栈)
}Stack;
// 定义token类型:对表达式中的字符进行分类,方便解析
typedef enum
{
LEFT_PARE, // 左括号 ( ,枚举值0
RIGHT_PARE, // 右括号 ) ,枚举值1
ADD, // 加号 + ,枚举值2
SUB, // 减号 - ,枚举值3
MUL, // 乘号 * ,枚举值4
DIV, // 除号 / ,枚举值5
MOD, // 取模 % ,枚举值6
EOS, // 表达式结束符 \0 ,枚举值7
NUM // 数字操作数 ,枚举值8
} contentType;
char expr[] = "82/2+56*-"; // 待求值的后缀表达式
// 栈的初始化:分配内存,初始化栈顶指针
Stack* initStack()
{
// 为栈结构体分配内存
Stack *s = (Stack*)malloc(sizeof(Stack));
// 为栈的动态数组分配内存,容量为MAXSIZE
s->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
s->top = -1; // 空栈的栈顶指针为-1
return s; // 返回初始化后的栈指针
}
// 判断栈是否为空:栈顶指针为-1则为空
// 返回值:1-空栈,0-非空
int isEmpty(Stack *s)
{
if (s->top == -1)
{
// printf("空的\n"); // 调试用,可注释
return 1;
}
else
{
return 0;
}
}
// 进栈/压栈操作:将元素e压入栈s
// 返回值:1-入栈成功,0-入栈失败(栈满)
int push(Stack *s, ElemType e)
{
// 栈满判断:栈顶指针 >= 最大容量-1
if (s->top >= MAXSIZE - 1)
{
printf("栈满,入栈失败\n");
return 0;
}
s->top++; // 栈顶指针上移一位
s->data[s->top] = e; // 将元素e存入栈顶位置
return 1;
}
// 出栈操作:将栈顶元素弹出,存入*e中
// 返回值:1-出栈成功,0-出栈失败(空栈)
int pop(Stack *s, ElemType *e)
{
// 空栈判断,无法出栈
if (isEmpty(s))
{
printf("空栈,出栈失败\n");
return 0;
}
*e = s->data[s->top]; // 将栈顶元素赋值给*e
s->top--; // 栈顶指针下移一位,完成出栈
return 1;
}
// 获取栈顶元素:将栈顶元素存入*e中,不弹出
// 返回值:1-获取成功,0-获取失败(空栈)
int getTop(Stack *s, ElemType *e)
{
if (isEmpty(s))
{
printf("空栈,无栈顶元素\n");
return 0;
}
*e = s->data[s->top]; // 仅获取栈顶元素,不修改栈顶指针
return 1;
}
// 解析表达式字符:将当前字符转换为对应的token类型
// symbol:存储当前解析的字符,index:表达式遍历的索引(传地址,实现索引自增)
contentType getToken(char *symbol, int *index)
{
*symbol = expr[*index]; // 获取当前索引的字符
*index = *index + 1; // 索引自增,指向下一个字符
// 根据字符类型返回对应的token
switch(*symbol)
{
case '(': return LEFT_PARE;
case ')': return RIGHT_PARE;
case '+': return ADD;
case '-': return SUB;
case '*': return MUL;
case '/': return DIV;
case '%': return MOD;
case '\0': return EOS; // 表达式结束
default: return NUM; // 非上述字符,判定为数字操作数
}
}
// 后缀表达式求值核心函数:基于栈实现计算
// 参数s:初始化后的空栈,返回值:1-计算成功
int eval(Stack *s)
{
char symbol; // 存储当前解析的字符
int op1, op2; // 存储弹出的两个操作数,op1左操作数,op2右操作数
int index = 0; // 表达式遍历的起始索引,从0开始
contentType token; // 存储当前字符的token类型
ElemType result; // 存储最终的计算结果
// 解析第一个字符,获取其token类型
token = getToken(&symbol, &index);
// 遍历表达式,直到遇到结束符EOS
while(token != EOS)
{
// 如果是数字操作数,压入栈中(字符转整型:symbol - '0')
if (token == NUM)
{
push(s, symbol - '0');
}
// 如果是运算符,弹出两个操作数计算,结果入栈
else
{
pop(s, &op2); // 先弹出右操作数
pop(s, &op1); // 后弹出左操作数
// 根据运算符类型计算,结果压入栈
switch(token)
{
case ADD: push(s, op1 + op2); break; // 加法
case SUB: push(s, op1 - op2); break; // 减法
case MUL: push(s, op1 * op2); break; // 乘法
case DIV: push(s, op1 / op2); break; // 除法(整型除法)
case MOD: push(s, op1 % op2); break; // 取模
default: break; // 无其他运算符,无需处理
}
}
// 解析下一个字符,更新token
token = getToken(&symbol, &index);
}
// 遍历结束,栈中仅剩一个元素,即为结果,弹出并打印
pop(s, &result);
printf("后缀表达式%s的计算结果为:%d\n", expr, result);
return 1;
}
// 主函数:程序入口,初始化栈并调用求值函数
int main(int argc, char const *argv[])
{
Stack *s = initStack(); // 初始化栈
eval(s); // 后缀表达式求值
return 0;
}
3. 运行结果
编译并运行代码,输出结果为:

4. 关键注意点
- 操作数弹出顺序:先弹 op2,后弹 op1 ,因为后缀表达式中运算符在操作数后,例如
82/表示8/2,而非2/8; - 字符转整型:数字字符
'0'-'9'减去'0'即可得到对应的整型数值; - 栈的判空 / 判满:所有栈操作前必须做判空 / 判满,避免数组越界和空栈操作。
三、中缀表达式转后缀表达式:原理 + 完整注释代码
1. 转换核心原理
转换的关键是利用栈管理运算符优先级 ,定义栈内优先级 和栈外优先级(优先级数值越大,运算优先级越高),本文定义的优先级规则:
- 左括号
(:栈外优先级最高(20),栈内优先级最低(0),保证括号内的表达式优先处理; - 乘 / 除 / 取模(* / %):优先级 13,高于加 / 减(+ -)的 12;
- 加 / 减(+ -):优先级 12;
- 结束符 EOS:栈内 / 栈外优先级均为 0。
转换步骤:
- 初始化一个栈,栈底压入结束符
EOS,用于遍历结束后处理栈内剩余运算符; - 从左到右遍历中缀表达式的每个字符:
- 若为操作数,直接输出(即为后缀表达式的一部分);
- 若为右括号
),从栈中弹出运算符并输出,直到遇到左括号(,弹出左括号但不输出(消除括号); - 若为其他运算符(含左括号) ,比较栈顶运算符的栈内优先级 和当前运算符的栈外优先级 :
- 若栈顶优先级 ≥ 当前运算符栈外优先级,弹出栈顶运算符并输出,重复比较;
- 若栈顶优先级 < 当前运算符栈外优先级,将当前运算符压入栈;
- 遍历结束后,从栈中依次弹出运算符并输出,直到遇到结束符
EOS,最终输出的序列即为后缀表达式。 
2. 完整注释代码(C 语言)
该代码在栈基本操作的基础上,实现了中缀表达式转后缀表达式 ,测试用例为x/(i-j)*y,转换结果为xij-/y*。
cpp
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100 // 栈的最大容量,防止栈溢出
// 枚举类型:定义表达式中所有字符的类型(token)
// 用于区分运算符、括号、结束符、操作数,方便优先级判断
typedef enum
{
LEFT_PARE, // 0 左括号 (
RIGHT_PARE, // 1 右括号 )
ADD, // 2 加号 +
SUB, // 3 减号 -
MUL, // 4 乘号 *
DIV, // 5 除号 /
MOD, // 6 取模 %
EOS, // 7 字符串结束符 \0
NUM // 8 操作数(字母/数字,如x、y、123)
} contentType;
// 栈存储的元素类型 = 字符类型枚举,统一类型,避免报错
typedef contentType ElemType;
// 栈的结构体定义
typedef struct
{
ElemType* data; // 动态数组,存储栈内元素(运算符/括号/结束符)
int top; // 栈顶指针,-1代表空栈,指向栈顶元素下标
} Stack;
// 待转换的中缀表达式(支持字母变量)
char expr[] = "x/(i-j)*y";
// ------------------- 栈的基础操作函数(带详细注释) -------------------
// 功能:初始化栈,分配内存,设置空栈状态
// 返回值:初始化好的栈指针
Stack* initStack()
{
// 分配栈结构体的内存
Stack* s = (Stack*)malloc(sizeof(Stack));
// 分配栈数据数组的内存(最大容量MAXSIZE)
s->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
s->top = -1; // 栈顶指针置-1,表示空栈
return s;
}
// 功能:判断栈是否为空
// 参数:s-栈指针
// 返回值:1=空,0=非空
int isEmpty(Stack* s)
{
return (s->top == -1);
}
// 功能:入栈操作(将元素压入栈顶)
// 参数:s-栈指针,e-待入栈的元素
// 返回值:1=成功,0=失败(栈满)
int push(Stack* s, ElemType e)
{
// 栈满判断:栈顶指针到达最大下标,无法入栈
if (s->top >= MAXSIZE - 1)
{
printf("栈满,入栈失败\n");
return 0;
}
s->top++; // 栈顶指针上移
s->data[s->top] = e; // 将元素存入栈顶
return 1;
}
// 功能:出栈操作(取出栈顶元素)
// 参数:s-栈指针,e-存储出栈的元素
// 返回值:1=成功,0=失败(空栈)
int pop(Stack* s, ElemType* e)
{
if (isEmpty(s))
{
printf("空栈,出栈失败\n");
return 0;
}
*e = s->data[s->top]; // 取出栈顶元素
s->top--; // 栈顶指针下移
return 1;
}
// ------------------- 表达式处理工具函数 -------------------
// 功能:从表达式中读取一个字符,并返回它的token类型
// 参数:symbol-存储读取到的字符,index-表达式遍历下标(会自动递增)
// 返回值:字符对应的枚举类型
contentType getToken(char* symbol, int* index)
{
*symbol = expr[*index]; // 读取当前下标的字符
*index = *index + 1; // 下标+1,准备读取下一个字符
// 根据字符返回对应的类型
switch (*symbol)
{
case '(': return LEFT_PARE;
case ')': return RIGHT_PARE;
case '+': return ADD;
case '-': return SUB;
case '*': return MUL;
case '/': return DIV;
case '%': return MOD;
case '\0': return EOS;
default: return NUM; // 字母/数字都判定为操作数
}
}
// 功能:将运算符token转换为字符打印输出
// 参数:token-运算符类型
// 返回值:1=打印成功,0=不是运算符
int print_token(contentType token)
{
switch (token)
{
case ADD: printf("+"); break;
case SUB: printf("-"); break;
case MUL: printf("*"); break;
case DIV: printf("/"); break;
case MOD: printf("%%"); break; // %% 转义输出%
default: return 0; // 非运算符,不打印
}
return 1;
}
// ------------------- 中缀转后缀核心函数 -------------------
// 功能:将中缀表达式转换为后缀表达式并输出
// 参数:s-用于存储运算符的栈
void postfix(Stack* s)
{
// 栈内优先级:运算符在栈内时的优先级
// 索引对应token枚举值:( ) + - * / % \0
int in_stack[] = {0, 19, 12, 12, 13, 13, 13, 0};
// 栈外优先级:运算符在表达式中的优先级
int out_stack[] = {20, 19, 12, 12, 13, 13, 13, 0};
contentType token; // 存储当前字符的类型
int index = 0; // 表达式遍历下标,从0开始
ElemType e; // 存储出栈的元素
char symbol; // 存储当前读取的字符
// 初始化栈:压入结束符EOS作为栈底标记,方便最后弹出所有运算符
push(s, EOS);
// 读取第一个字符,获取token类型
token = getToken(&symbol, &index);
// 循环遍历表达式,直到读取到结束符\0
while (token != EOS)
{
// 情况1:当前字符是操作数(字母/数字)→ 直接输出
if (token == NUM)
{
printf("%c", symbol);
}
// 情况2:当前字符是右括号 )
else if (token == RIGHT_PARE)
{
// 弹出栈中运算符,直到遇到左括号 (
while (s->data[s->top] != LEFT_PARE)
{
pop(s, &e);
print_token(e); // 弹出的运算符直接输出
}
pop(s, &e); // 弹出左括号 (,不输出(消除括号)
}
// 情况3:左括号/运算符 → 按优先级入栈
else
{
// 规则:栈顶运算符优先级 ≥ 当前运算符 → 弹出栈顶并输出
while (in_stack[s->data[s->top]] >= out_stack[token])
{
pop(s, &e);
print_token(e);
}
push(s, token); // 当前运算符入栈
}
// 读取下一个字符,更新token
token = getToken(&symbol, &index);
}
// 表达式遍历完毕,弹出栈中剩余的所有运算符
pop(s, &e);
while (e != EOS) // 直到弹出栈底的结束符为止
{
print_token(e);
pop(s, &e);
}
printf("\n"); // 换行,美化输出
}
// ------------------- 主函数:程序入口 -------------------
int main()
{
Stack* s = initStack(); // 初始化运算符栈
printf("中缀表达式:%s\n", expr);
printf("转换后的后缀表达式:");
postfix(s); // 执行中缀转后缀
return 0;
}
3. 运行结果
编译并运行代码,输出结果为:

4. 关键注意点
- 优先级数组:
in_stack和out_stack的索引与 token 枚举值严格对应,不能错位; - 左括号处理:栈外优先级最高,确保能直接入栈;栈内优先级最低,确保括号内的运算符优先处理;
- 操作数兼容:代码中
NUM不仅包含数字,还包含字母(如 x/i/j/y),可直接处理含变量的中缀表达式; - 栈底 EOS:必须在栈底压入 EOS,否则遍历结束后无法终止栈内运算符的弹出。
四、拓展与思考
- 支持多位数操作数 :本文代码仅支持单个数字 / 字母操作数,可扩展
getToken函数,遍历连续的数字字符并转换为整型,实现多位数求值; - 浮点型运算 :将
ElemType从int改为float/double,修改除法为浮点除法,即可支持小数表达式; - 后缀转中缀 :基于栈实现,遇到操作数入栈,遇到运算符弹出两个操作数,拼接为
(op1 运算符 op2)后重新入栈,最终栈内元素即为中缀表达式(会带冗余括号,可后续优化); - 运算符优先级扩展 :可添加平方、开方等运算符,只需在
token枚举中添加,并更新优先级数组即可。
五、总结
后缀表达式是栈的经典应用,其核心优势是消除了括号和优先级的歧义 ,让计算机能以线性方式高效解析表达式。本文通过两个完整的 C 语言代码,分别实现了后缀表达式求值 和中缀转后缀,并对代码进行了逐行注释,核心要点可总结为:
- 求值:栈存操作数,遇运算符弹二算一,结果入栈;
- 转换:栈存运算符,遇操作数直输,遇运算符按优先级处理;
- 栈的操作:所有入栈 / 出栈前必须判空 / 判满,避免程序崩溃。