一、思路概要与知识回顾
1.思路概要
①括号匹配检查:
通常用栈来实现。用户输入一个字符串,程序遍历每个字符,遇到左括号就入栈,遇到右括号则检查栈顶是否匹配的左括号,匹配则出栈,否则返回错误。整个字符串处理完后,栈应该为空才算匹配成功。
②交互界面:
用户可以循环输入,需要一个循环结构,比如用while循环,让用户选择继续或退出。这里可能需要处理用户的输入选项,比如输入'y'继续,'n'退出,同时处理其他无效输入的情况。
③错误输入的处理:
用户可能输入数字、字母等其他字符,这些在括号匹配中通常被忽略,需要检查,提示用户输入了非括号字符。应该在程序中处理这些错误输入,比如忽略非括号字符,或者给出警告。
④程序健壮性:
输入处理部分需要避免缓冲区溢出,比如使用fgets来读取输入,而不是gets,这样可以指定最大长度,防止溢出。同时,处理用户的选择输入时,也要考虑可能的多余字符或者无效输入,比如用户输入了多个字符的情况,应该只取第一个有效字符,并清空输入缓冲区。
⑤关于提示信息:
每次输入后,程序应该告诉用户是否匹配成功,并且如果有错误,指出错误的位置或类型。例如,栈未空说明左括号过多,或者遇到不匹配的右括号。
2.知识回顾
①三木操作符
条件表达式:这是一个需要求值的表达式。如果它的值为真(非零),则执行并返回 表达式1
的值;否则,执行并返回 表达式2
的值。
表达式1:当条件表达式为真时,这个表达式会被执行。
表达式2:当条件表达式为假时,这个表达式会被执行。
②toupper函数
toupper()
是 C 语言标准库 <ctype.h>
中的一个函数,用于将小写字母转换为大写字母。
函数定义:
参数:
c
:一个整数,通常是一个字符的 ASCII 值。
返回值:
如果 c
是小写字母(a
到 z
),则返回对应的大写字母(A
到 Z
)。
如果 c
不是小写字母,则返回原值 c
。
③putchar函数
putchar()
是 C 语言标准库 <stdio.h>
中的一个函数,用于向标准输出(通常是屏幕)输出一个字符。
函数定义:
参数:
c
:要输出的字符,通常是以 int
形式传递的 char
类型数据。
返回值:
输出成功时,返回输出的字符(作为 int
类型)。
输出失败时,返回 EOF
(通常定义为 -1
④strchr函数
strchr()
是 C 语言标准库 <string.h>
中的一个函数,用于在字符串中查找指定字符的第一次出现位置。
函数定义:
参数:
str
:要搜索的字符串。
c
:需要查找的字符,通常以 int
形式传递,但实际使用时一般传入一个 char
类型的字符。
返回值:
返回一个指向字符串中首次出现字符 c
的指针。
如果未找到该字符,则返回 NULL
。
⑤fgets函数
fgets()
是 C 语言标准库 <stdio.h>
中的一个函数,用于从输入流中读取一行数据,包括换行符(如果缓冲区足够大)。它常用于读取用户输入或从文件中读取内容。
函数定义:
参数:
str
:指向存储读取数据的缓冲区的指针。
n
:要读取的最大字符数。fgets()
会读取最多 n-1
个字符,并在末尾添加空终止符 \0
。
stream
:指向文件流的指针,通常是标准输入 stdin
、标准输出 stdout
或标准错误 stderr
,也可以是打开的文件。
返回值:
如果读取成功,返回 str
。
如果读取失败(如文件结束或读取错误),返回 NULL
。
⑥strcmp函数
strcmp()
是 C 语言标准库 <string.h>
中的一个函数,用于比较两个字符串。
函数定义:
参数:
str1
:第一个要比较的字符串。
str2
:第二个要比较的字符串。
返回值:
如果 str1
和 str2
相等,返回0。
如果 str1
小于 str2
,返回负数。
如果 str1
大于 str2
,返回正数。
⑦snprintf函数
snprintf()
是 C 语言标准库 <stdio.h>
中的一个函数,用于将格式化后的字符串安全地写入缓冲区。它与 sprintf()
类似,但提供了更好的缓冲区溢出保护。
函数定义:
参数:
str
:指向存储格式化输出的缓冲区的指针。
size
:缓冲区的大小(以字节为单位),snprintf()
最多会写入 size-1
个字符,并自动添加空终止符 \0
。
format
:格式化字符串,包含普通文本和格式说明符(如 %d
、%s
等)。
...
:根据格式说明符提供的参数。
返回值:
snprintf()
返回格式化字符串所需的字节数(不包括空终止符 \0
)。如果返回值小于 size
,表示成功写入整个字符串;如果返回值大于等于 size
,表示缓冲区不足以存储完整的字符串,输出将被截断。
⑦strcspn函数
strcspn()
是 C 语言标准库 <string.h>
中的一个函数,用于计算从字符串起始位置到第一个匹配指定字符集的字符之间的长度。
函数定义:
参数:
str
:要检查的字符串。
charset
:包含要匹配字符的字符串。
返回值:
返回从 str
开始到第一个出现 charset
中的字符为止的字符数。如果 str
中不包含 charset
中的任何字符,则返回 str
的长度。
二、代码实现
1. 栈的结构
栈用于存储括号字符,以便进行括号匹配检查。
字符数组data:
用于存储栈中的括号字符。
整型变量top:
用于指示栈顶位置。
2.初始化栈
初始化的重要性:
在每次进行括号匹配之前,必须确保栈是空的,这样才能正确地记录和检查当前输入的括号序列。如果栈没有正确初始化,可能会残留之前操作的数据,导致错误的匹配结果。
将栈顶指针top
初始化为-1
,表示栈为空。
3.判满判空
-
栈为空的判断逻辑 :栈为空的条件是栈顶指针
top
等于 -1,表示栈中没有元素。在括号匹配检查中,这个判断逻辑用于:-
在程序开始时,初始化栈后,确保栈为空,以便后续的入栈操作能够正确地将左括号压入栈中。
-
在遇到右括号时,需要先检查栈是否为空,如果不为空,才能弹出栈顶元素与右括号进行匹配检查。
-
在括号序列处理完成后,检查栈是否为空,如果为空,说明所有左括号都找到了匹配的右括号;如果不为空,则说明有左括号没有匹配的右括号,导致括号序列不匹配。
-
-
栈为满的判断逻辑 :栈为满的条件是栈顶指针
top
等于MAX_SIZE - 1
,表示栈中元素的数量已达上限,无法再继续入栈。在括号匹配检查中,这个判断逻辑用于:-
当左括号数量过多,栈的容量有限时,防止因不断入栈而导致栈上溢错误,保证程序的正常运行。
-
如果栈满,说明括号嵌套深度超过了程序设定的最大值,此时需要提示用户错误信息,并停止进一步的入栈操作,以避免程序出现异常行为。
-
4.入栈函数
-
入栈操作的逻辑 :入栈操作是栈的基本操作之一,其逻辑如下:
-
首先,检查栈是否已满,通过调用
is_full
函数实现。如果栈已满,说明无法再压入新的元素,此时返回false
,表示入栈失败。 -
如果栈未满,则将栈顶指针
top
递增 1,指向新的栈顶位置,然后将字符c
存入栈顶位置,完成入栈操作,并返回true
,表示入栈成功。
-
-
括号匹配中的应用 :在括号匹配检查中,入栈操作用于处理左括号。当遇到一个左括号时,需要将其压入栈中,以便后续遇到对应的右括号时,能够从栈中弹出该左括号进行匹配检查。如果左括号无法入栈(如栈已满),则说明括号嵌套深度超过了栈的最大容量,此时括号序列不匹配,需要提示错误。
5.出栈函数
-
出栈操作的逻辑 :出栈操作是栈的基本操作之一,其逻辑如下:
-
首先,检查栈是否为空,通过调用
is_empty
函数实现。如果栈为空,说明没有元素可以弹出,此时返回\0
,表示出栈失败。 -
如果栈不为空,则获取栈顶元素(即
s->data[s->top]
),然后将栈顶指针top
递减 1,完成出栈操作,并返回弹出的元素。
-
-
括号匹配中的应用 :在括号匹配检查中,出栈操作用于处理右括号。当遇到一个右括号时,需要从栈中弹出一个左括号进行匹配检查。如果栈为空,则说明没有对应的左括号,此时右括号多余,括号序列不匹配;如果栈不为空,则弹出左括号并检查是否与当前的右括号匹配。如果匹配则继续检查后续括号,否则括号序列不匹配。
5.判断括号函数
判断一个字符是否为括号:
-
括号判断逻辑 :逻辑或运算符 || 的工作原理是,只要有一个条件为真,整个表达式就为真。在 C 语言中,非零值被视为真,零值被视为假。
在这个函数中,多个条件通过 || 连接,每个条件判断字符 c 是否等于某一个括号字符。只要字符 c 是其中一个括号字符,整个表达式就为真,函数返回 true。
如果字符 c 不是任何一个括号字符,所有条件都为假,整个表达式为假,函数返回 false -
括号匹配中的应用 :在括号匹配检查中,只有括号字符是有效的输入。如果输入中有非括号字符,说明输入的括号序列不符合要求,需要记录错误位置和信息,并返回错误码1,提示用户输入错误。
6.检查括号匹配的核心函数
-
定义函数
check_brackets
:-
参数:
-
const char* str
:输入的括号序列字符串。 -
char* error_msg
:用于存储错误信息的缓冲区。 -
int* error_pos
:用于存储错误位置的指针。
-
-
返回值:整数,表示错误码(0表示无错误,其他表示不同类型的错误)。
-
-
声明并初始化栈
s
:-
Stack s;
:声明一个栈结构体变量s
。 -
init(&s);
:调用init
函数初始化栈s
,将栈顶指针top
设置为 -1,表示栈为空。
-
-
计算输入字符串的长度:
-
int len = (int)strlen(str);
:使用strlen
函数计算输入字符串的长度,并将其转换为整型。
-
-
遍历输入字符串的每个字符:
-
for (int i = 0; i < len; i++)
:从索引0开始,遍历到字符串的末尾。 -
char c = str[i];
:获取当前字符c
。
-
-
检查当前字符是否为非括号字符:
-
if (!is_bracket(c))
:调用is_bracket
函数检查字符c
是否为括号。如果c
不是括号字符,进入分支。 -
*error_pos = i;
:将当前字符的位置i
存储到error_pos
指向的变量中。 -
snprintf(error_msg, MAX_INPUT, "发现非括号字符 '%c'", c);
:格式化错误信息,存储到error_msg
指向的缓冲区中。 -
return 1;
:返回错误码1,表示发现非括号字符。
-
-
处理左括号:
-
if (c == '(' || c == '[' || c == '{')
:检查字符c
是否为左括号。如果是,进入分支。 -
if (!push(&s, c))
:调用push
函数将左括号压入栈。如果压入失败(栈满),进入分支。 -
*error_pos = i;
:将当前字符的位置i
存储到error_pos
指向的变量中。 -
snprintf(error_msg, MAX_INPUT, "错误:括号嵌套深度超过限制");
:格式化错误信息,存储到error_msg
指向的缓冲区中。 -
return 4;
:返回错误码4,表示括号嵌套深度超过限制。
-
-
处理右括号:
-
else
:如果字符c
是右括号,进入分支。 -
if (is_empty(&s))
:检查栈是否为空。如果栈为空,进入分支。-
*error_pos = i;
:将当前字符的位置i
存储到error_pos
指向的变量中。 -
snprintf(error_msg, MAX_INPUT, "右括号多余");
:格式化错误信息,存储到error_msg
指向的缓冲区中。 -
return 2;
:返回错误码2,表示右括号多余。
-
-
char top = pop(&s);
:调用pop
函数弹出栈顶的左括号。 -
if ((c == ')' && top != '(') || ...
:检查弹出的左括号是否与当前的右括号匹配。如果不匹配,进入分支。-
*error_pos = i;
:将当前字符的位置i
存储到error_pos
指向的变量中。 -
char expected = ...
:根据弹出的左括号确定期望的右括号。
在这段代码中,三目运算符被嵌套使用,以根据不同的左括号确定对应的右括号。
第一个条件判断:
检查变量top
是否等于'('
。
如果是,expected
被赋值为')'
。
如果不是,继续执行下一个条件判断。
第二个条件判断:
检查变量top
是否等于'['
。
如果是,expected
被赋值为']'
。
如果不是,继续执行下一个条件判断。
默认情况:
如果top
既不是'('
也不是'['
,则默认赋值为'}'
。
逻辑总结:
①如果果top
是'('
,则expected
被设置为')'
。
②如果top
是'['
,则expected
被设置为']'
。
③如果top
是'{'
,则expected
被设置为'}'
。 -
snprintf(error_msg, MAX_INPUT, "期望 '%c'", expected);
:格式化错误信息,存储到error_msg
指向的缓冲区中。 -
return 3;
:返回错误码3,表示括号不匹配。
-
-
-
检查栈是否为空:
-
if (!is_empty(&s))
:循环结束后,检查栈是否为空。如果栈不为空,进入分支。-
*error_pos = len;
:将错误位置设置为字符串的长度(最后一个字符的下一个位置)。 -
snprintf(error_msg, MAX_INPUT, "左括号多余");
:格式化错误信息,存储到error_msg
指向的缓冲区中。 -
return 2;
:返回错误码2,表示左括号多余。
-
-
return 0;
:如果所有括号匹配正确,返回0,表示无错误。
-
7.清理缓冲区函数
清空输入缓冲区,防止之前输入的残留数据影响后续输入操作。
int c = 0;
:定义一个整型变量 c
,用于存储从输入缓冲区读取的字符。
while ((c = getchar()) != '\n' && c != EOF);
:使用 getchar()
不断从输入缓冲区读取字符,直到遇到换行符 ('\n'
) 或文件结束符 (EOF
)。这会清空缓冲区中残留的所有字符。
8.打印错误函数
打印错误指示,帮助用户直观地看到错误发生的位置。
-
printf(" ");
:打印五个空格,用于对齐错误指示,使输出更整洁易读。 -
for (int i = 0; i < pos; i++)
:循环pos
次,根据错误位置进行相应次数的迭代。-
putchar(' ');
:在循环中每次打印一个空格,创建一个由空格组成的行,用于定位错误字符的位置。
-
-
printf("\n");
:打印换行符,结束错误指示行的输出。
9.主函数
①声明变量
input
:用于存储用户输入的括号序列。
error_msg
:用于存储错误信息。
error_pos
:用于存储错误发生的位置。
choice
:用于存储用户是否继续检查的选择。
②打印程序说明
向用户显示程序的功能和使用说明。
③主循环
使用无限循环 while (1)
来持续接受用户输入,直到用户选择退出。
④读取用户输入
提示用户输入括号序列。
使用 fgets
读取用户输入的字符串,MAX_INPUT
限制输入长度。
如果读取失败(如输入流结束),跳出循环。
⑤ 处理退出命令
检查用户是否输入了退出命令 q
或 Q
,如果是,则打印退出信息并跳出循环。
⑥移除换行符
使用 strcspn
函数找到换行符 \n
的位置,并将其替换为字符串结束符 \0
,以去除输入中的换行符。
⑦ 检查输入是否为空
检查输入是否为空字符串,如果是,则提示用户输入不能为空,并跳过后续处理。
⑧检查括号匹配
调用 check_brackets
函数对输入的括号序列进行匹配检查。
根据返回值 result
判断括号序列是否匹配。
⑨输出检查结果
如果 result
为 0,表示括号匹配正确,打印成功信息。
如果 result
不为 0,表示发现错误,打印错误信息、错误位置和输入的括号序列,并调用 print_error_indicator
函数打印错误指示。
⑩ 询问是否继续
提示用户是否继续检查。
读取用户输入的选择。
如果用户输入 y
或 Y
,继续循环。
如果用户输入 n
或 N
,打印退出信息并返回 0 退出程序。
如果输入无效,提示用户重新输入。
代码逻辑图:👇↓↓↓👇
三、完整代码演示
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#define MAX_SIZE 1024
#define MAX_INPUT 2048
typedef struct {
char data[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void init(Stack* s) {
s->top = -1;
}
// 检查栈是否为空
bool is_empty(Stack* s) {
return s->top == -1;
}
// 检查栈是否已满
bool is_full(Stack* s) {
return s->top == MAX_SIZE - 1;
}
// 入栈操作
bool push(Stack* s, char c) {
if (is_full(s)) return false;
s->data[++s->top] = c;
return true;
}
// 出栈操作
char pop(Stack* s) {
return is_empty(s) ? '\0' : s->data[s->top--];
}
// 检查字符是否为括号
bool is_bracket(char c) {
return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}';
}
// 检查括号匹配的核心函数
int check_brackets(const char* str, char* error_msg, int* error_pos) {
Stack s;
init(&s);
int len = (int)strlen(str);
for (int i = 0; i < len; i++) {
char c = str[i];
// 跳过非括号字符
if (!is_bracket(c)) {
*error_pos = i;
snprintf(error_msg, MAX_INPUT, "发现非括号字符 '%c'", c);
return 1;
}
if (c == '(' || c == '[' || c == '{') {
if (!push(&s, c)) {
*error_pos = i;
snprintf(error_msg, MAX_INPUT, "错误:括号嵌套深度超过限制");
return 4;
}
}
else {
if (is_empty(&s)) {
*error_pos = i;
snprintf(error_msg, MAX_INPUT, "右括号多余");
return 2;
}
char top = pop(&s);
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
*error_pos = i;
char expected = (top == '(') ? ')' :( top == '[') ? ']' : '}';
snprintf(error_msg, MAX_INPUT, "期望 '%c'", expected);
return 3;
}
}
}
if (!is_empty(&s)) {
*error_pos = len;
snprintf(error_msg, MAX_INPUT, "左括号多余");
return 2;
}
return 0;
}
// 清空输入缓冲区
void clear_input_buffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
// 打印错误指示
void print_error_indicator(int pos) {
printf(" ");
for (int i = 0; i < pos; i++) {
putchar(' ');
}
printf("^\n");
}
int main() {
char input[MAX_INPUT];
char error_msg[MAX_INPUT];
int error_pos;
char choice;
printf("========== 括号匹配检查程序 ==========\n");
printf("说明:\n1. 支持 () [] {} 三种括号\n2. 输入只能是括号字符\n3. 输入q退出程序\n\n");
while (1) {
printf("请输入需要检查的括号序列:\n");
if (fgets(input, MAX_INPUT, stdin) == NULL) break;
// 处理退出命令
if (strcmp(input, "q\n") == 0 || strcmp(input, "Q\n") == 0) {
printf("程序退出。\n");
break;
}
// 移除换行符
input[strcspn(input, "\n")] = '\0';
// 检查输入是否为空
if (strlen(input) == 0) {
printf("错误:输入不能为空!\n\n");
continue;
}
int result = check_brackets(input, error_msg, &error_pos);
if (result == 0) {
printf("√ 括号匹配正确!\n\n");
}
else {
printf("× 发现错误:%s\n", error_msg);
printf("位置:%d\n", error_pos);
printf("输入:%s\n", input);
print_error_indicator(error_pos);
putchar('\n');
}
// 询问是否继续
while (1) {
printf("继续检查?(y/n): ");
fgets(input, sizeof(input), stdin);
choice = input[0];
if (strchr(input, '\n') == NULL) clear_input_buffer();
if (toupper(choice) == 'Y') {
putchar('\n');
break;
}
else if (toupper(choice) == 'N') {
printf("程序退出。\n");
return 0;
}
else {
printf("无效输入,请重新输入!\n");
}
}
}
return 0;
}
以上是用 C 语言实现——括号匹配检查程序,支持用户输入任意括号、有交互界面、循环判断、健壮性处理错误输入,还要有提示。数据结构学习
❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀