C语言:使用链式栈检测txt文件中的括号匹配
便捷目录
前言
因为我这里用了比较多全局变量,如果不想用这么多全局变量,如果你觉得不顺手或者不顺眼,你可以不用,但是在其他函数需要用到这些函数的时候需要你加多几个参数进来。
举个栗子: 比如a这个全局变量,现在看他不顺眼,放在Main函数里面,我的另一个函数里面需要用到a,但是Main函数的变量不能在其他函数用,这时候需要把其他函数多加一个参数,把a传进去。
本程序最终会完成的任务
i. 如果都匹配,则返回都匹配。
ii. 若不匹配,则返回第几行第几列不匹配。
iii. 将不匹配的行打印出来。
栈的理解
栈(stack)是一个先进后出的一个线性表
(一般)使用尾插法:
## 那么↓ ##
表头是栈底
表尾是栈顶
本博客就是应用栈进行匹配括号的方式,平时我们一般人都不会察觉括号有这个规律,其实我也是一样,接触了算法后才知道括号可以用栈来实现配对。具体为什么,下面我会详细解释。
代码运行过程的解释
说明
首先说明一下栈的括号匹配,我们只需要把左括号进栈,右括号不用进栈,因为我们知道了左括号时,当遇到右括号的时候直接进行匹配就好,如果匹配成功就直接出栈,如果前面已经出栈了一个左括号,过程中又遇到左括号继续将他进栈,继续往后匹配,没有影响,其实如果你能理解这里的话你就会感叹栈的妙处~ 然后就直至全部出栈成功的话就代表配对成功
**(我这个单身狗都还没有配对成功) **
代码思想 (重要部分)
(该程序中只对英文的括号进行配对,即对中文括号配对无效)
希望阅读者一定要好好的理解下这个执行过程,如果看不懂代码就必须要看懂这部分,看懂这部分再去看代码就so easy了,加油!
①首先对于文件内容的扫描时,先判断是否遇到了左括号如果遇到了,首先把当前左括号的所有信息记录下来,即记录所在的行和列。
( ## 别急待会在全局变量和结构体代码部分中讲怎么弄这个 ## )
完成上述左括号进栈操作后将指向栈的位置往后移动,继续扫描文件中的括号。
②需要知道的是,当我们每次换行都要记录这行的文件开头位置,也就是说当你扫描到换行符了,立马用fgetpos函数记录下现在的位置。我们暂时把这个变量成为position。
③如果遇到了右括号,这时候要做的是与左括号进行匹配,
a 如果配对成功就左括号出栈 :然后继续扫描下一个右括号。
b 如果遇到的右括号是错误的 :那么就要记录这个右括号的行和列,还要用fgetpos函数对这个右括号所在的txt文件位置进行记录。我们暂时把这个变量成为curr。
④记录完成后,立马回到这个右括号所在文件的这一行的开头,也就是回到position所记录的位置,接着就是把该行内容用一个数组进行存储该行的错误信息(因为我们要实现的功能是打印错误的行信息)
⑤录入信息完成之后,还记得用fgetpos函数记录的右括号curr位置吗,没错这个时候派上用场了,回到这个位置之后重复刚刚的操作,重复的进栈和出栈。
全局变量和结构体代码
下面的结构体就是纯纯为记录左括号位置而服务的,
所以结构体里面包含了行和列还有存放的字符变量。
right[51]:存放匹配错误的右括号信息
left[51]:存放匹配错误左括号的信息
r_index = 0:记录存放数组的右括号下标,也是记录右括号的错误数量
l_index = 0:记录存放数组的左括号下标,也是记录左括号的错误数量
P_bracket temp = NULL:这个是栈表头 (也是左括号进栈的链式栈)
t_index = 0:记录链式栈的下标
//剩下的就是存放错误信息了,比较好理解就不赘述了,代码有注释
切记一定要用二维数组来存放这些错误信息。
/*括号链表*/
typedef struct _bracket{
int column;//列
int row;//行
char BRA;//括号字符
struct _bracket *next;
}bracket, *P_bracket;//先给五十个空间
P_bracket temp = NULL;
bracket right[51], left[51];// ;,temp[100]
int r_index = 0, l_index = 0, t_index = 0;
char err_information[51][200];
//存放错误信息,这里你可以修改存放的错误信息空间
int err_index = 0;//错误信息数组的下标
进栈:创建链表空间函数
如果不懂具体实现的过程,可以跳转至这篇文章 -> 最全链表剖析–点击博客
代码如下,正常的添加链表空间的操作
void ADD_LinkBracket_Stack(P_bracket *L)//二级指针 ,链式栈,这里采用头插法,出的时候也要头出
{
P_bracket temp = (*L), new_room;
if((*L) == NULL)
{
(*L) = (P_bracket)malloc(sizeof(bracket));
if(!(*L))
{
PF(括号链表分配失败!);
exit(1);//程序异常退出
}
(*L)->next = NULL;
}
else
{
new_room = (P_bracket)malloc(sizeof(bracket));
if(!new_room)
{
PF(括号链表分配失败!);
exit(1);//程序异常退出
}
new_room->next = temp;
(*L) = new_room;
}
}
出栈:删除链表空间函数
如果不懂具体实现的过程,可以跳转至这篇文章 -> 最全链表剖析–点击博客
代码如下,正常的删除链表空间的操作
void Out_LinkBracket_Stack(P_bracket *L)
//头出,因为进栈是头插,所以出栈也要头出
{
P_bracket temp = (*L), cur;
if(temp->next)//判断不为空栈
{
cur = temp->next;
free(temp);
(*L) = cur;//头部释放后,应该把头部修改成下一个
}
//if((*L) == NULL) (*L) = NULL;
}
释放申请的链式栈空间
如果不懂具体实现的过程,可以跳转至这篇文章 -> 最全链表剖析–点击博客
代码如下,正常的释放链表空间的操作
void Release_linkBracket_Stack(P_bracket *L)
{
P_bracket t = (*L);
while(t)
{
t = t->next;
free((*L));
(*L) = t;
}
}
如何实现检查括号配对函数
具体的实现过程已经在前面解释过了,如果看懂了运行过程解释的话,看下面代码就轻而易举。
补充:bool open = true; //一定要赋值为true,因为如果没有出现错误也能判断返回一个true。
这个是用来判断是否出现过错误信息,如果出现了,在整个函数结束之前返回false表示出现了括号匹配错误,否则返回true。
如果还不懂,恕我无能。你也可以来问我,我看到信息都会很乐意的为大家解答疑问喔!
bool Check_Bracket_link(FILE*fp, P_bracket *Check_B)
{
int row = 1, column = 1,j;
char ch;
P_bracket temp = (*Check_B);
bool open = true;//用来判断是否有错误信息
fpos_t position, curr;
//ADD_LinkBracket_Stack(&temp);//先初始化一个空间
fgetpos(fp, &position);//先记录第一行的位置
while(!feof(fp))
{
ch = fgetc(fp);
column++;//列数加一
if(ch == '\n')//没有遇到错误括号的时候,用这个来记录文件位置
{
//行数加一
row++;
column = 1;
fgetpos(fp, &position);//记录下一行的行开头
}
if(ch== '<' || ch== '{' || ch== '(' || ch== '[')
{
ADD_LinkBracket_Stack(&temp);//开辟一个新空间,等待下一个括号进入PF(测试测试!);
temp->BRA = ch;
temp->column = column;
temp->row = row;
}
if(ch=='>' || ch=='}' || ch==')' || ch==']')
{
if(temp && ((temp->BRA=='<' && ch == '>' )||
(temp->BRA=='{' && ch == '}' )||
(temp->BRA=='(' && ch == ')' )||
(temp->BRA=='[' && ch == ']' ) ))
{
//从中学到,判断条件是从左到右进行判断,所以要把temp是否为空先放在左边,不然如果为空,又进行temp的指针判断就会程序异常退出
Out_LinkBracket_Stack(&temp);//出栈,头部出
}
else
{
open = false;//代表出现错误了
fgetpos(fp, &curr); //记录当前右括号的位置,录入完错误信息后退出循环要继续返回到这个位置扫描
if(temp)
{//因为有可能开局就只有右边的括号而没有左括号,所以要判断一下,所以在输出的时候就要判断左括号的行坐标是否大于右括号的坐标
left[l_index].column = temp->column;
left[l_index].row = temp->row;
l_index++;//单独在里面加加,不能放在外面,因为要输出左括号的时候要用到,如果没有大于0就代表是没有对应的左括号
}
right[r_index].column = column;
right[r_index].row = row;
r_index++;
fsetpos(fp, &position);
while(!feof(fp))//存行信息的循环,记得最后把数组移位,进行下次错误信息存储
{
ch = fgetc(fp);
err_information[err_index][j++] = ch;
if(ch == '\n')//这个是遇到错误括号匹配的时候的条件判断,用来记录文件位置
{
fsetpos(fp, &curr);//返回上次错误的位置
err_information[err_index][j] = '\0';//变为字符串方便打印
j = 0;//j变回0,以便下次继续使用
err_index++;//字符串空间移动下一位
break;
}
}
}
}
}
if(open == false) return false;
else return true;
}
完整实现代码
#include<stdio.h>
#include<ctype.h>
#include<stdbool.h>
#include<stdlib.h>
/*括号链表*/
typedef struct _bracket{
int column;//列
int row;//行
char BRA;//括号字符
struct _bracket *next;
}bracket, *P_bracket;//先给五十个空间
P_bracket temp = NULL;
bracket right[51], left[51];// ;,temp[100]
int r_index = 0, l_index = 0, t_index = 0;
char err_information[51][200];
//存放错误信息,这里你可以修改存放的错误信息空间
int err_index = 0;//错误信息数组的下标
bool Check_Bracket_link(FILE*fp, P_bracket *Check_B);
void ADD_LinkBracket_Stack(P_bracket *L);
void Out_LinkBracket_Stack(P_bracket *L);
void Release_linkBracket_Stack(P_bracket *L);
int main()
{
FILE *fp;
int i;
bool Judge_err;
fp = fopen("D:/D/C.txt", "r");//括号
//Judge_err = Check_Bracket_Sq(fp);//完成了搜索,然后需要包装函数将其输出
Judge_err = Check_Bracket_link(fp, &temp);//完成了搜索,然后需要包装函数将其输出
fclose(fp);
if(Judge_err) printf("都匹配!");
else
{
if(l_index > 0)
for(i = 0; i < l_index; i++)
{
printf("第%d个错误信息坐标\n", i+1);
if(left[i].row < right[i].row)
printf("左括号:(%d,%d)\n", left[i].row, left[i].column);
printf("右括号:(%d,%d)\n", right[i].row, right[i].column);
printf("错误信息行:%s\n",err_information[i]);
}
else
{
for(i = 0; i < r_index; i++)
{
printf("第%d个错误信息坐标\n", i+1);
printf("左括号:无对应括号\n");
printf("右括号:(%d,%d)\n", right[i].row, right[i].column);
printf("错误信息行:%s\n",err_information[i]);
}
}
}
//需要一个函数把链表里面的东西置空,不然当你做一个循环的时候会持续叠加数量
Release_linkBracket_Stack(&temp); //养成好习惯,当不用的时候就要把申请的空间释放掉
err_index = 0;
l_index = 0;
r_index = 0;
return 0;
}
bool Check_Bracket_link(FILE*fp, P_bracket *Check_B)
{
int row = 1, column = 1,j;
char ch;
P_bracket temp = (*Check_B);
bool open = true;//用来判断是否有错误信息
fpos_t position, curr;
//ADD_LinkBracket_Stack(&temp);//先初始化一个空间
fgetpos(fp, &position);//先记录第一行的位置
while(!feof(fp))
{
ch = fgetc(fp);
column++;//列数加一
if(ch == '\n')//没有遇到错误括号的时候,用这个来记录文件位置
{
//行数加一
row++;
column = 1;
fgetpos(fp, &position);//记录下一行的行开头
}
if(ch== '<' || ch== '{' || ch== '(' || ch== '[')
{
ADD_LinkBracket_Stack(&temp);//开辟一个新空间,等待下一个括号进入PF(测试测试!);
temp->BRA = ch;
temp->column = column;
temp->row = row;
}
if(ch=='>' || ch=='}' || ch==')' || ch==']')
{
if(temp && ((temp->BRA=='<' && ch == '>' )||
(temp->BRA=='{' && ch == '}' )||
(temp->BRA=='(' && ch == ')' )||
(temp->BRA=='[' && ch == ']' ) ))
{
//从中学到,判断条件是从左到右进行判断,所以要把temp是否为空先放在左边,不然如果为空,又进行temp的指针判断就会程序异常退出
Out_LinkBracket_Stack(&temp);//出栈,头部出
}
else
{
open = false;//代表出现错误了
fgetpos(fp, &curr); //记录当前右括号的位置,录入完错误信息后退出循环要继续返回到这个位置扫描
if(temp)
{//因为有可能开局就只有右边的括号而没有左括号,所以要判断一下,所以在输出的时候就要判断左括号的行坐标是否大于右括号的坐标
left[l_index].column = temp->column;
left[l_index].row = temp->row;
l_index++;//单独在里面加加,不能放在外面,因为要输出左括号的时候要用到,如果没有大于0就代表是没有对应的左括号
}
right[r_index].column = column;
right[r_index].row = row;
r_index++;
fsetpos(fp, &position);
while(!feof(fp))//存行信息的循环,记得最后把数组移位,进行下次错误信息存储
{
ch = fgetc(fp);
err_information[err_index][j++] = ch;
if(ch == '\n')//这个是遇到错误括号匹配的时候的条件判断,用来记录文件位置
{
fsetpos(fp, &curr);//返回上次错误的位置
err_information[err_index][j] = '\0';//变为字符串方便打印
j = 0;//j变回0,以便下次继续使用
err_index++;//字符串空间移动下一位
break;
}
}
}
}
}
if(open == false) return false;
else return true;
}
void ADD_LinkBracket_Stack(P_bracket *L)//二级指针 ,链式栈,这里采用头插法,出的时候也要头出
{
P_bracket temp = (*L), new_room;
if((*L) == NULL)
{
(*L) = (P_bracket)malloc(sizeof(bracket));
if(!(*L))
{
printf("括号链表分配失败!");
exit(1);//程序异常退出
}
(*L)->next = NULL;
}
else
{
new_room = (P_bracket)malloc(sizeof(bracket));
if(!new_room)
{
printf("括号链表分配失败!");
exit(1);//程序异常退出
}
new_room->next = temp;
(*L) = new_room;
}
}
void Out_LinkBracket_Stack(P_bracket *L)//头出,因为进栈是头插,所以出栈也要头出
{
P_bracket temp = (*L), cur;
if(temp->next)//判断不为空栈
{
cur = temp->next;
free(temp);
(*L) = cur;//头部释放后,应该把头部修改成下一个
}
//if((*L) == NULL) (*L) = NULL;
}
void Release_linkBracket_Stack(P_bracket *L)
{
P_bracket t = (*L);
while(t)
{
t = t->next;
free((*L));
(*L) = t;
}
}
结尾
栈这个东西真的还是蛮多用处的,我做这篇文章的初衷也是想要帮自己回忆一下这些si去的记忆,大家如果看到这对栈的印象还有点模糊的话,可以多看几遍,用这个例子来演示可以更好的了理解栈的具体实现操作。
希望能帮到大家~
本文来自博客园,作者:竹等寒,转载请注明原文链接。