编译原理:实验二、集合与线性表操作
(一). 实验目的
编译器处理源程序文件的过程中,集合、链表、栈是经常出现的数据结构。
与《数据结构》课程关注集合、线性表的编程实现不同,本实验关注它们的使用。在Java语言中,集合和线性表分别就是Java工具类Util包中的Set和List两个接口。特别的,本实验关注HashSet、ArrayList(或Vector)和Stack(它们分别是集合、链表和栈的实现类)的使用,为后续实验做好准备。
(二). 预备知识
1. 集合和线性表的基本操作
集合和线性表(链表和栈)的基本操作是《数据结构》课程的基础知识,这里不一一进行介绍。
2. Collection
在Java中,集合容器(Collection)是一个对象的容器,可以存放一组对象,主要用来组织和管理存放在容器中的对象。
注:Java中,集合Set继承了Collection,因此Collection很难翻译。目前较多情况下仍将Collection译为“集合”,如“Java Collection Framework”,一般译为“Java集合框架”。但要注意,此时的“集合”指Collection。其它情况下,集合指Set。
其中,常用的几个实现类如下:
HashSet — 使用散列算法实现Set接口
TreeSet — 使用平衡二叉树算法实现SortedSet接口
ArrayList — 使用数组存放对象来实现List接口
Vector — 与ArrayList的区别在于Vector是同步、线程安 全的,而ArrayList是异步、线程不安全的。性能上,Vector比ArrayList低。
Stack — 上图没有画出,它是Vector的直接子类,实现了栈的基本操作。
LinkedList — 使用双向链表来实现List接口
3. 泛型(Generic)
注:泛型与范型同音,但意义相差甚远。
范型(Paradigm)的范,指一种规范的、可作为模范的样式、范例,也译为范式。如OO范式等,包含了面向对象编程许多方面的内容,与面向过程的范式作为区别。
泛型(Generic)的泛,指的是泛化的含义,因为Generic是“类属的”的意思。在C++中,泛型就是“模板(Template)”。因为同音的缘故,许多网络上的文章,把“泛型”误为“范型”,要特别注意区分。泛型的例子如下:
Set<Object>,一个存放元素是“Object”的集合;
Set<String>,一个存放元素是“String”的集合;
Stack<Integer>,一个存放元素是“Integer”的集合;
Stack<aUserDefinedClass>,一个存放由用户定义的类的集合
4. 集合与线性表基本操作的使用
通常使用形如 Set<Integer> si=new HashSet<Integer>();来声明一个存放整数的集合(注:类Integer可换成其它自定义的类)。
然后,可使用si.add(new Integer(5));来向集合中插入一个元素“5”;再使用si.contains(new Integer(5))来判断集合中是否包含元素“5”;再使用si.remove(new Integer(5));来从集合中删除元素“5”。
若要遍历访问集合si中的全部元素,则需要使用迭代器Iterator。
通过Iterator<Integer> it=si.iterator();取得si的迭代器,再通过判断while(it.hasNext()){ //是否遍历到最后一个元素,若是最后元素,
//则hasNext为假,否则为真
Integer i=it.next(); //访问当前元素
//do something,对当前元素i进行操作
}。
线性表的声明、操作与集合的声明、操作非常类似,只是换了一个类名而已。如声明Stack<String> ss=new Stack<String>;为一个存放字符串的栈,然后可通过ss.peek()、ss.push(aString)、ss.pop()来访问栈顶元素、进行入栈和出栈操作。
需要特别注意的是,集合Set不重复保存相同元素。那么,Set要如何做,才能判断一个元素是否跟另一个元素相同?答案就是o.equals(e)。如果元素o、e都不为空,并且o.equals(e),那么集合Set就认为o和e是相同的元素。对基本数据类型,如Integer、String等,因为语言已经实现了对应的equals方法,所以可以直接使用。若集合中的元素是自定义的类,那么,则需要考虑应如何实现equals方法。在C++的STL库中,则要实现的是小于等于的LE方法。
(三). 实验内容
1. 字符集合
a) 声明一个存放非终结符的集合sc。考虑应拿什么数据类型或数据结构保存非终结符?
b) 从文件“NonTerminal.txt”中读取所有的非终结符。文件内容如下:
a, b, c, d.
也即使用逗号分隔非终结符,使用句话表示输入结束。
c) 使用迭代器遍历读入的非终结符,按一行输出一个非终结符的方式,显示在屏幕上,并同时写入到另外一个文件中。
2. 产生式
a) 产生式是形如“A è aB”之类的式子,由产生式左侧和产生式右侧2个部分组成,中间的“è”用处在于显示的时候可以分隔2端,并没有特别含义。如果我们只处理2型和3型文法,即上下文无关文法和线性文法,那么,产生式应如何设计怎样的数据结构来保存产生式。换句话说,产生式类应如何设计?
b) 特别的,产生式类中,equals(或LE)方法,应如何实现?
3. 产生式栈
a) 声明一个存放产生式的栈。产生式使用内容2中的产生式类。
b) 从文件“Production.txt”中读取所有的产生式。文法的内容如下:
A -> aB
C -> sdfdf
也即一行一个产生式,产生式中间的分隔符是“->”(或其它,可自己再定义),一直到文件结束。
将读入的产生式,按栈先进后出(First In Last Out FILO)的次序,一行一个在屏幕上显示出来,并写入到另外一个文件中。