编译原理第三章 控制结构

编译原理第三章 控制结构


控制结构:程序员用来规定程序各个成分(语句和程序单元)的执行流程的控制部分

语句级控制结构

语句级控制结构:语言用来构造各种语句执行顺序的机制

语句级控制结构分为三种:

顺序(sequencing)
选择(selection)
重复(repetition)

顺序

语言可用的、最简单的控制结构。顺序运算符(语句结束标记) ;

若干个语句可以通过顺序运算符组在一起作为一个单独的语句.
复合语句括号:
begin … end
或 { … }

选择

选择控制结构允许程序员在某些可选择的语句中选择其中一条来执行。
单选 if then
二选一 if then else
多选一 嵌套if then else 或 case …

单选

max:=a;
if (a<b) then max:=b;
可供选择的语句 max:=b
要么执行
要么不执行

二选一

if (a>b) then max:=a else max:=b;
可供选择的语句
max:=a 和 max:=b
只选择其中一个执行

多选一

选择结构引起二义性
if x>0 then if x<10 then x:=0 else x:=1000

① PL/1的select结构

SELECT:
	  WHEN(A)S1;
	  WHEN(B)S2;
	  WHEN(C)S3;
	  OTHERWISE S4;
  END

②多种语言的case语句:
PASCAL
ALGOL 68
Ada

var operator:char;
operand1,operand2,result:boolean;
……
case operator of
	‘.’: result:=operand1 and operand2;
     ‘+’: result:=operand1 or operand2;
     ‘=’: result:=operand1 = operand2;
end

不同语言case语句的差异:

ALGOL68中,case语句基于整表达式的值,表达式的值为i,则选择第i个分支执行。
PASCAL显式列出可能的值,表达式的值与分支的值相同时,选择该分支。每个分支在case中出现的次序是无关紧要的。
表达式的值不在显式列出的值中时的处理:out 和 otherwise;

Java中Switch语句的选择结构

  switch  (表达式)
      {
            case   常量表达式1 :语句1;
            case   常量表达式2 :语句2;
            …….
            case   常量表达式n :  语句n;
            default : 语句n+1;
      }

switch语句判断条件只能接受byte, char, int, short型,一旦碰到第一次case匹配,就会开始顺序执行以后所有的程序代码,而不管后面的case条件是否匹配,后面case条件下的语句都会被执行,直到碰到break语句为止。

class SampleSwitch 
{ public static void main(String args[])
{ for(int i=0; i<6; i++)    
    switch(i) { 
        case 0:          
            System.out.println("i is zero.");       
            break;
        case 1:          
            System.out.println("i is one.");     
            break;
        case 2:          
            System.out.println("i is two.");     
            break;
        case 3:         
            System.out.println("i is three.");     
            break;
        default:          System.out.println("i is greater than 3."); 
    } 
                                                            }
                   } 



Dijkstra选择结构

if B1→S1
B2 →S2
B3 →S3
……
BN →SN
fi
其中,Bi是布尔表达式,称为卫哨。
若有多个卫哨为真时执行任一Si。

特性:非确定性

重复

计数器制导

当预先知道重复次数时,在循环计数器值的有限集合上重复。
① FORTRAN的DO循环中,用标号控制循环体
DO 7 I=1,10
A(I)=0
B(I)=0
7 CONTINUE

② Pascal的for 语句

计数重复的值可在任何有序集上
for . . . to
for . . . downto

for语句

  for (表达式一;表达式二;表达式三) 
   {
          执行语句;
   }

表达式一:初始化表达式;
表达式二:循环条件表达式;
表达式三:循环后的操作表达式。

//1
for (int i=0; i<10; i++)
{
      System.out.println(i);
}
//2
int i=0;
for (; i<10; i++)
{
      System.out.println(i);
}
//3
int i=0;
for (; i<10;  )
{
      System.out.println(i);
      i++;
}
//4
int i=0;
for (; ; )
{
      if (i >= 10) 
                 break;
      System.out.println(i);
      i++;
}
//四个等效

条件制导

① while 循环:描述0或任意多次的重复
② repeat until循环:至少一次以上的重复

while语句

  while  (条件表达式语句)
      {
          执行语句
      }

int x=1;
While (x<3)
{
    System.out.println(“x=,x);
    x++;
}

Dijkstra的卫哨命令表示法
do B1→S1
B2→S2
. . . . . .
BN→SN
od

重复执行直到没有Bi为真。若有多个Bi为真,任选一个Si执行。

顺序、选择和重复可以帮助程序员组织语句的控制流程,是基本控制工具。

顺序、选择、重复是一定意义的抽象;

抽象控制结构

顺序是按程序计数器提供的顺序获得指令的一种抽象。

选择和重复是对显式修改程序计数器的值的抽象—无条件转移和条件转移
控制既简单又有效。

抽象控制结构比显式控制结构:修改指令计数器的低级控制机制更好。
面向问题:程序员通过使用顺序、选择和重复的一般模式就能较好地表达控制语句执行的意图。

高级语言控制结构最终要由条件转移和无条件转移的低级代码实现。
与程序员无关,将由编译器生成有效的机器代码,因而采用高度抽象概念有利于程序设计。

大多数程序设计语言提供goto语句,这是随意修改程序计数器值的抽象。
Dijkstra于1968年发表了著名论文,论述了goto语句对程序设计的影响。
结论:包含许多goto语句的程序隐含许多出错的机会。
大多数语言提供了丰富的控制结构,通常也提供了goto语句。
语言提供怎样的控制结构?
对这个问题仍然有争议。
一些语言( 如Java )吸取了 Diikstra 的建议,废弃 goto语句,使程序逻辑更加清晰。
但为了实现控制转移,特别设计了 break 语句和 continue 语句,从而实现“有节制”地使用 goto 语句。

Bohm 和 Jacopini 于 1966 年在理论上证明,使用顺序、选择和重复就可对计算机所有可能的算法进行编码。
因此,这 3 种结构组成了控制结构的有效集合。然而,仅仅使用这 3 种控制结构写出的程序不太直观。

实际上,增加一些控制结构的表达方式(如:增加多重选择 case ,do-while ,repeat-until 等),
虽然在理论上是多余的,但这样的确能提高程序的可写性和可读性。

单元级控制结构

单元级控制结构:规定程序单元之间控制流程的机制

四种单元级控制结构:显式调用从属单元、异常处理、协同程序、并发单元

显式调用

这种类型的调用覆盖所有的子程序:
FORTRAN 语言的子程序和函数
PASCAL 语言的函数和过程
C语言的函数

都属于显式调用。

每个子程序(函数、过程)都有一个名字,通过名字进行程序单元的调用。
执行调用语句时将控制转向被调用单元,被调用单元执行完后,又将控制返回主调用单元,并继续执行紧跟在调用语句后面的语句(返回地址)。

当控制从调用单元转向被调用单元时,还可进行参数传递。
参数传递可实现单元之间的通信。
单元之间的通信也可以通过全局变量或非局部变量来进行(副作用)

参数传递

每次调用允许传递不同的数据(实际参数),为单元间的通信提供了灵活性。也提高了程序的可读性和可修改性。

位置参数绑定方式很方便。在参数比较多,又允许多个参数省略的情况下,很容易出错。
设N=10 ,且第2,4,5,6,7,9个形参对应的实参都省略,那么
call S(Al,A3,A8,A10)

很可能把逗号的位置弄错。
多一个逗号或少一个逗号都会造成形参与实参的绑定错误,
同时也降低了程序的可读性。

为了克服上述缺点,有些语言显式列出相应实参与形参的绑定关系。
call S(A1=>F1,A3=>F3,A8=>F8,A10=>F10)

在参数可省略(缺省参数)的情况下,
一般都要在过程(或函数)首部做出专门的规定:
指出以什么样的特定值(缺省值)来替代省略的实参。

在子程序调用中,副作用和别名也会影响程序的可读性,容易导致程序出错。

对非局部环境的修改

①副作用降低了程序的可读性

②副作用限制了数学运算律的使用

​ 如:w:=x+f(x,y)+z

x、y为变参
z为全局变量

③副作用影响目标代码的优化

​ 如:u:=x+z+f(x,y)+f(x,y)+x+z

在单元激活期间,两个变量表示(共享)同一数据对象对非局部环境的修改。
①FORTRAN的EQUIVALENCE语句, EQUIVALENCE(A,B)

② Pascal的变参使得形参和实参共享同一数据对象

③C++的引用参数
③变参和全局变量表示同一数据对象时,也会引起别名
④别名也影响编译器生成优化的代码
a:=(x-yz)+w / * 若a与x、y或z中任
b:=(x-y
z)+u 一个是别名 * /

⑤别名的消除
.废除可能引起别名的结构
.限制使用指针、变参、全局变量、数组等

隐式调用单元异常处理

异常是指导致程序正常执行中止的事件,要靠发信号来引发,用异常条件来表示,并发出相应的信号,引发相应的程序。隐式地将控制从一个单元转向到另一个单元,通常有于异常处理

早期语言中除 PL/1外,通常没有专门的异常条件及异常处理程序。
后期开发的语言提供了异常处理机制,使涉及异常事件的处理独立出来.
不包括在程序的主流程中,以保证程序的逻辑按基本算法进行。

异常处理要考虑的问题

(1)异常如何说明,它的作用域是什么?
(2)异常如何发生(或如何发信号)?
(3)发出异常信号时,如何规定要执行的单元(异常处理程序)?
(4)发出异常时,如何绑定相应的异常处理程序?
(5)处理异常之后,控制流程转向何处?

​ 在这些问题中,问题(5)的解决对语言处理异常机制的能力和可使用性有很大的影响。

方法1:

语言设计中可能的基本选择是,相应的异常处理程序执行完之后,允许控制返回发生异常事件的执行点。
在这种情况下,异常处理程序可对执行的程序进行“修补”,终止相应的异常事件,以便程序继续正常地执行。

缺点:

解决了程序继续执行的问题,
但并未真正消除发生异常的因素。

方法2:

相应的异常处理程序执行完之后,终止引起异常的程序单元的执行,由异常处理程序进行控制的转移。
从概念上说,这意味着引起异常的单元不能恢复执行;从实现的观点来看,这意味着删除异常单元的活动记录。

C语言的出错处理

实现出错处理的方法是将用户函数与出错处理程序紧密地结合起来,
但将造成出错处理使用的不方便和难以接受。
用C标准库的assert宏进行出错处理
使用allege函数在运行时检查错误。

allege函数对一些小型程序很方便,对于复杂的大型程序,所编写的出错处理程序也将更加复杂。

C++语言的异常处理

异常处理是C++的一个主要特征,
它提出了出错处理更加完美的方法。
设置陷阱 抛出异常 捕获异常
try throw catch

C++的异常处理语句的格式如下:

try  {  … }
  catch (异常类型1) { 异常1处理程序 }
  catch (异常类型2) {异常2处理程序}
     ……
  catch (异常类型n) {异常n处理程序} 

java异常处理

Try{
    //可能发现异常的语句块
}catch(异常类型,e){
   //发生异常时候的执行语句块
} finnally{
  //不管是否发生异常都执行的语句块
}

SIMULA 67语言协同程序
定义:两个或两个以上程序单元之间交错地执行,这样的程序称为协同程序。
例如:设有程序单元C1和C2,由C1开始执行,当执行到C1的“resume C2”命令时,显示激活C2,并将C1的当前执行点的现场保存起来,将控制C2的执行点;
若C2执行到某个“resume C1”语句,将C2的当前执行点现场保存,恢复C1的执行,继续执行下去……
C1和 C2似乎在并行地执行,我们将这种执行称为伪并行。
实际上, C1 和 C1是在交错地执行,它是并行的一种低级形式。
常规的子程序机制不能描述并行执行的程序单元
CLU 和 SIMULA67 等语言设置了描述这种交错执行过程的机制。

并发单元

诸程序单元并行活动

进程:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
进程的特点:
动态性:进程具有动态的地址空间
独立性:各进程的地址空间相互独立,除非采用进程间通信手段,
并发性、异步性
结构化

进程与程序的区别
进程是动态的,程序是静态的:
程序是有序代码的集合
进程是程序的执行

进程是暂时的,程序的永久的:
进程是一个状态变化的过程
程序可长久保存

进程与程序的组成不同:
进程的组成包括程序、数据和进程控制块(即进程状态信息)

进程与程序的对应关系:
通过多次执行,一个程序可对应多个进程;
通过调用关系,一个进程可包括多个程序。

发布了81 篇原创文章 · 获赞 27 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43309286/article/details/104557592
今日推荐