编译原理期末(软考)复习笔记(基础知识+例题+实验代码)

目录

前言

第一章绪论

编译概述

编译程序的结构

第二章 高级语言及其语法描述

 符号以及符号串

上下文无关文法

语法树

第三章词法分析

3.1词法分析器的功能

3.2单词的类型和内部表示

3.2.1单词的类型:

3.2.2单词的内部表示

3.3词法分析器的设计

3.3.1总体设计

3.3.2详细设计

3.3.3状态转化图

 3.4正规表达式与有限自动机

1、正规式和正规集

正规式的运算:

 正规式的等价:

正规式转换为正规文法 

正规文法转换为正规式 

2、确定的有限自动机(DFA)

DFA的定义

​编辑 DFA的矩阵表示

有穷自动机如何识别字符串 

​编辑3、不确定的有限自动机

NFA定义

DFA与NFA的区别:

​编辑 ​编辑

NFA转化为等价的DFA

DFA的化简 



前言

刚好大三下这个学期在学编译原理,就随着教学进度来更新一篇十分全面的笔记。无论是备战期末考试还是面试,软考都可以看一看,网络上有很多全是基础概念定义的博客,老实说不是给自学的人看的,我想写一份可以看懂的笔记,因为我觉得这门课挺难的。毕竟是学生,能力有限,有任何纰漏和错误请评论提醒我,我一定会看,负责任的说,我觉得认真看完这篇帖子再做总结,期末考试肯定是可以轻松过的。大家一起学好基础,共勉!

参考书籍:编译原理与实践(北京邮电大学出版社)

例题:B站,学习通例题

第一章绪论

现在我们学习的大都是使用高级语言来编程,但是电脑是不懂高级语言的,所以如果想要真正在计算机上执行,我们必须要使用相应的翻译程序来将其翻译成机器语言。翻译程序的代表就是编译程序,而开发编译程序所涉及的原理,方法,技术就是这门课程所研究的。

编译概述

我们学习过操作系统,知道计算机系统的语言有机器语言,汇编语言,高级语言。 从机器到汇编到高级,其实是一个不断拉近人类思维与计算机语言的过程,使得我们可以方便清晰地编写自己想要的程序。但是我们编写的语言必须要在计算机上才可以运行,所以我们也必须要找一个方法来实现高级语言和汇编语言转化或机器语言的程序,这种方法就是翻译,也就是我们常说的翻译程序。编译器或编译程序这样的翻译程序可以将源程序转化为目标程序。源程序指代的是Java,c,c++这些我们在学校里会学习到的高级语言,而目目标程序就是汇编或者是机器语言这样的低级语言了。

高级语言除了可以编译执行外,还可以解释执行。如果考简答题的话需要重点掌握。

编译程序和解释程序的主要区别如下:

1、编译程序是将整个源程序翻译成目标程序之后再执行,而且目标程序可以反复执行。

2、解释程序对源程序逐句地翻译执行,不产生目标程序。如果需要再次执行,则必须重新解释源程序。

编译程序的结构

这个基本上就是咱们上课的目录啦。

编译程序的工作由上面的概述可知:输入源程序到输出目标代码。就是这么“简单”

下面是图示:

<为了有清晰地展示效果,我还去下载了一个drawio,不过下载速度实在太慢了,所以还是用网页版了>

从工作过程角度分析,可以将编译程序的工作划分为五个阶段:词法分析阶段、语法分析阶段、语义分析与中间代码生成阶段、代码优化阶段,目标代码生成阶段

这些阶段可以与图中的模块对应起来,各个阶段之间关系是十分紧密的。其实编译程序翻译源程序和人们翻译自然语言之前也是很相似的,下面有一个表格,可以让我们大概了解每一章的学习到底有什么作用。

词法分析是实现编译器的基础,语法分析是实现编译器的关键,也是核心功能。所以学校考试的重点也会放在这一方面,一定要认真学弄懂,一次学懂,期末完全不带怕的,实在不行学会做题也是很棒的。

第二章 高级语言及其语法描述

我们知道语法分析是编译程序的核心,词法分析已经语义分析都是围绕着语法分析而来的。实践证明,上下文无关文法在描述高级语言的语法结构方面具有极大的优势,本书讲重点讨论上下文无关文法的定义、语法分析树、文法的二义性,并对文法的分类做简单的描述。

 符号以及符号串


上下文无关文法

 文法是一种描述语言的语法结构的形式规则(即语法规则),这些规则必须准确且易于理解,同时应该具有相当强的描述能力,足以描述各种不同的语法结构。所谓的上下文无关文法是指这样一种文法,它所定义的语法范畴(或语法单位)是完全独立于这种范畴可能出现的环境的。简单来说就是我们在分析表达式时可以只用观察它的语法结构,而不用去考虑它的语境,也就是上下文。本门课无特殊说明,所说的文法均指上下文文法。

语法树

第三章词法分析

词法分析是编译程序的第一阶段,该阶段的主要工作是逐个扫描输入的源程序字符串,识别出字符串中的各个单词,并输出单词的内部表示符号,用以进行语法分析。这就是词法分析器或者直接叫做扫描器吧,这就是它的功能,它作为即将编译的源程序和编译器后续阶段的接口,从上面的编译器结构图也可以看出,源程序指向它,它指向了后面的语法分析,词法分析所做的工作很简单,就是扫描和识别嘛,就和夸克里面的拍照一样,拍一下就可以把某个物体的样子扫描进手机,然后识别出它是个什么东西。所以很简单,完全不用害怕,下面让我们来详细地学习它,并且自己做出来一个词法分析器。

3.1词法分析器的功能

词法分析器的输入是源程序字符串,而源程序是由程序设计语言的单词构成的,单词是程序设计语言不可分割的最小单位。<在英文中有很多字母嘛,这些字母就像是字符,连接起来就是字符串啦,有一些字母连接起来可以变成单词,它拥有实际上的意义,有的则是错误拼写。>词法分析器从左到右逐个字符地对源程序进行扫描,按照语言的定义规则,将字符拼接成为单词。每当识别出一个单词,就产生其种别码,这样源代码就变成了单词符号串的中间形式,然后提交给语法分析程序判别语法是否有问题。

在我们扫描前,我们首先要做一个预处理操作!预处理的工作也是很重要的,它要过滤出程序中一些无用的成分,比如说注释,空格,换行,这些都是给程序员看的,为的是便于阅读,美观简洁,但是他们对于生成目标代码没有任何作用。还有的就是检查有没有非法字符还有一些不符合构词规则的单词。非法符号就像是自然语言中的错字,你不可能自己创作一个字来使用吧,别人是没有办法理解的,当然编译器也无法理解非法的字符。

在编译程序中,词法分析器有两种实现模式:

1、完全独立模式:词法分析器作为编译程序的子系统独立运行一遍,扫描整个源程序,把识别出的单词使用统一的记号序列输出到中间文件,作为语法分析程序的输入。这是本篇文章所用的方法,它调理清晰,而且移植性强。

2、相对独立模式:词法分析器作为语法分析程序的子程序,每当语法分析程序需要一个单词时,就调用该子程序。词法分析程序每得到一次调用,就从源程序文件读入若干个字符,返回一个单词符号给语法分析程序。这种方法我们可以看出,没有中间文件,有利于提高编译程序的效率。

3.2单词的类型和内部表示

3.2.1单词的类型:

我们已经知道了单词是程序设计语言的最小单位,而不是字符,这一点我们需要区分。单词符号是最基本的语法单位,具有确定的语法意义。分为5类;

  1. 关键字:是程序设计语言定义的具有特定意义的名字。
  2. 标识符:用户在设计源程序时自己定义的名字。
  3. 常数:是程序设计语言或用户在设计源程序时定义的。
  4. 运算符:也叫算符,是程序设计语言中定义的符号,一般有逻辑运算符,算术运算符和关系运算符。
  5. 界符:是程序设计语言定义的符号,包括标点符号和一些特殊符号。

3.2.2单词的内部表示

词法分析器输入的是源程序,输出的是单词的内部表示。

其内部表示形式一般为二元式:(单词种别编码,单词的属性值),其中单词的种别编码在语法分析的时候使用,单词的属性值在语义分析和中间代码生成时使用。

单词的种别码表示单词的种类,一般都是用整数来表示的。单词怎么分类,怎么编写种别码没有统一的规定,如果你自己来做一个词法分析器的话,那么你就可以自己定义一个种别码,来表示不同单词属于哪个分类。但是要求就是不同的单词有可以彼此区分且唯一的表示。一般来说,一个程序设计语言的关键字,界符和算符都是固定的,可以采用一符一种的方式,标识符一般归统为一种,而常数按其类型进行分类(整型,实型,字符型,布尔型)。

3.3词法分析器的设计

恭喜你已经掌握了上述的基础知识,这个时候我们该学着做一个词法分析器了,这并不难,我们需要慢慢来。这一章我们还要学习一个很重要的知识——状态转化图

3.3.1总体设计

首先,将源程序输入到输入缓冲区,经过预处理子程序处理一定长度的字符串后送入扫描缓冲区,扫描器从扫描缓冲区识别出一个个单词。当扫描缓冲区中的字符串用完后,在调用预处理子程序,将一段新的字符串送入扫描缓冲区。预处理子程序的主要作用就是删除无用空格,回车键,换行符和注释等。扫描器的重要工作是通过读入的首字符串进行分类,然后继续通过读入的字符识别关键字,识别符,常数,算符,界符。扫描器是词法分析的主要组成部分。

3.3.2详细设计

详细设计这一块重要说的就是扫描器的工作原理啦,首先我们来讲讲预处理子程序,前面我们就知道了它的存在。空白符,回车键,换行符以及注释等字符对于执行程序来说没有实际的用处,所以在构造预处理子程序时我们可以将其删除,并且用一个空格来代替。

3.3.3状态转化图

状态转换图是设计词法分析程序一种很好的方法,它既可以描述单词的结构,也可以识别单词,只要用程序实现了识别单词的状态转换图就可以完成词法分析器。

状态转换图是一个有限图,结点用圆圈表示,称为状态,状态之间用带箭头的弧线连接,称为边。

一张状态转换图值包含有限个状态(结点),其中,有一个是初态(=>指向),至少有一个是终态(双圈表示)。从其实状态输入一个字符,它就会沿着这个字符的边进入下一个状态,重复执行输入,直到进入终结状态。如下面的第一个图,从初始态开始,输入a则进入1终态。从初始态开始输入b则进入2终态。

如果存在一个从起始状态到终结状态的路径,那么将路径上的标记用连接运算符连接在一起形成一个符号串,如果它和输入的符号串相同,则称这个输入符号串可以接受。如果不能进入任何一个终结结点,则称状态转换图不可以识别这个输入字符串。例如下面的第二个图是一个识别整数的状态转换图,从初始状态0开始,读入一个字符,如果是数字则进入状态1,否则标记识别失败,进入状态1后,如果输入是数字,则还在状态1(while循环),如果不是则转向状态2;标记着我们已经读完了一个字符串,但是此时我们多读入了一个不是数字的字符呀,所以我们输入指针必须要回退一个字符,将其退回给扫描缓冲区,因为它是属于下一个单词的,我们再给终态标上*号。

 3.4正规表达式与有限自动机

为了讨论词法程序的自动生成,通常是要引入正规式的概念。这一块我们一定要理解引入的目的到底是什么,这一块我常常怀疑这个东西到底有什么用,经过不断地学习终于找到了答案。

正规式是用表达式的形式来表示单词的构成;正规文法是从产生单词的观点来描述单词;

有限自动机是从识别的观点来描述单词,三者在单词的描述上是等价的。

1、正规式和正规集

字母表∑上的正规式用来描述一种称为正规集的语言。下面是定义

  1. ε和∅都是字母表∑上的一个正规式,他们所表示的正规集分别为{ε}和∅
  2. 对于任何的a∈∑,a是∑的一个正规式,它所表示的正规集为{a};
  3. 假设U和V都是∑上的正规式,它们所表示的正规集分别为L(U)和L(V),那么,(U|V)、(U•V)和U*也都是正规式,它们所表示的正规集有L(U)∪L(V,L(U)L(V),(L(U))*

 其中|读作或,.读作连接,*读作闭包,优先级为 * > •> |

正规式的运算:

 

 

 

 正规式的等价:

正则文法: 

正规式转换为正规文法 

 

正规文法转换为正规式 

2、确定的有限自动机(DFA)

有限自动机也叫做有穷自动机,是一种识别正规集的装置,它能够识别正规文法所定义的语言和正规式所表示的集合。 

DFA的定义

 DFA的矩阵表示

有穷自动机如何识别字符串 

3、不确定的有限自动机

NFA定义

DFA与NFA的区别:

不确定的有限自动机是确定有限自动机DFA的一般化,其定义与DFA类似,不确定的有限自动机的基本特征在于,当一个状态遇到同一个输入符号时,可能有多种不同的转换。

 

NFA转化为等价的DFA

我们可以看出DFA是NFA的特例,那么如何从NFA得到等价的DFA呢?这需要用到NFA的确认化技术。

我们知道,NFA M所能识别的字符串的全体记为L(M),对于任意的两个有限自动机M和N,若L(M)=L(N),则称它们是等价的。

对于每个NFA N,都存在着一个DFA M,使得L(N)=L(M);与某一NFA等价的DFA不唯一。


  从NFA构造等价的DFA,我们通常使用的是子集法,其基本的思路是:DFA的每一个状态与NFA的一组状态相对应,用DFA的一个状态去记录在NFA读入一个输入符号后可能达到的状态集合。下面先介绍一下与状态集合I有关的几个运算。

个人认为,这一块NFA转DFA的内容是比较难懂的,但是通过概念,我们可以得到一个有规律的执行步骤,梳理好这个我们再次用例题来彻底学会它。

重点:

第一个步骤:由于NFA的初态和终态结点可能有好几个,而且每条弧上的标记可能是∑*上的一个字符串,因此我们的目标就是改造成只有一个初态结点和一个终态结点,且每条弧上只能是单个输入符号或者ε的状态图。具体的做法是:

1、增加一个状态X和Y,使其成为唯一的初态结点和终态结点。我们不是有好几个嘛,那就全部导向它,然后就变成只有一个啦,很好理解。从X结点引弧ε到原来的初始结点,从原来的终态结点引ε到Y结点。

2、我们可以根据一个替换规则,不断重复,这样弧上面的字符串就变成一个单个字符或者ε。

这样我们就完成了第一步,十分简单,第二个步骤:后面就用我们上面学到的子集法对NFA进行确定化。具体做法如下:

1、对∑={a1,a2........ak},构造一个k+1列的状态转换表,列为状态,行为输入字符。是不是很像前面学习过的状态转换矩阵。我们将这个表的首行首列置为ε_Closure(X),X为第一步我们化简过后的那个开始状态。

2、若某行的第一列的状态已确认为I(状态集合),则计算第i+1(i=1,2,3....k)列的值为Iai(ai是下标哈),然后,检查该行上所有的状态子集,看其是否已经在第一列出现。若没有出现,则将其添加到后面的空行上。重复这一过程,知道所有的状态子集均在第一列中出现为止。

3、将每个状态子集视为一个全新的状态,即可得到一个DFA。初态就是首行首列的状态,终态即使含有原有终态的所有状态。

第二个步骤的定义我可以感觉到有一些晦涩,没关系,我们来看一道例题,你马上就可以掌握这个方法。

怎么样,是不是已经完全懂了它是什么意思,《编译原理与实践》这本书写得真不错,推荐需要学习的同学去买来看一看,也不是很厚。 

DFA的化简 

 DFA化简指的是,许找一个状态数最少得的DFA M',使得L(M)=L(M')。

每个正规集都可以用一个状态数最少得DFA所识别,如果不考虑状态名所带来的差异,可以说这个DFA是唯一的。

化简是消去DFA中多余或无用的状态,并且合并等价状态的过程。所谓多余状态是指,从初始状态出发,读入任何输入串都无法到达的那个状态,或者说没有通路可以到达终态。

好的,我们再来讲如何化简:化简的基本思想是DFA M的状态分成一些不相交的子集,使得任何不同子集中的状态都是可以区别的,而同一个子集中的状态都是等价的。是不是就是上面提到的分类合并。然后,从每个子集中选出一个状态作为代表,同时消去其他等价状态。这种方法被称为:“分割法”。具体过程是:

1、将M的所有状态分成两个子集,终态集和非终态集。

2、观察每个子集,若发现某个子集中的状态不等价,则将其划分为两个集合。

3、重复第二步,继续观察已得到的每个子集,知道没有任何一个子集需要继续划分为止。此时的DFA中的状态被分割成了若干个互不相交的子集。

4、从每个子集中选出一个状态作为代表,即可得到最简的DFA

很容易理解,我们来看一道例题

4月5日更新至此

  • 9
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值