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去的记忆,大家如果看到这对栈的印象还有点模糊的话,可以多看几遍,用这个例子来演示可以更好的了理解栈的具体实现操作。

希望能帮到大家~

posted @ 2022-06-18 23:43  竹等寒  阅读(11)  评论(0编辑  收藏  举报  来源