控制流
在计算机科学中,控制流(或控制流)是执行或评估命令程序的单个语句,指令或功能调用的顺序。对明确控制流的强调将势在必行的编程语言与声明性编程语言区分开来。
在命令式编程语言中,控制流语句是一种说明,该语句会导致选择遵循两个或多个路径中的哪一个。对于非图案功能语言,存在功能和语言结构以实现相同的结果,但通常不称为控制流语句。
中断和信号是低级机制,可以以类似于子例程的方式改变控制流,但通常是作为对某些外部刺激或事件的响应(可能异步发生的),而不是在线执行控制流语句。
在机器语言或组装语言的级别上,控制流说明通常通过更改程序计数器来起作用。对于某些中央处理单元(CPU),唯一可用的控制流说明是条件或无条件的分支指令,也称为跳跃。
类别
不同语言支持的控制流语句种类各不相同,但可以按其效果进行分类:
- 在其他语句中继续(无条件分支或跳跃)
- 仅在满足某些条件时执行一组语句(选择 -有条件分支)
- 执行一组语句零或更多次,直到满足某些条件(即,循环 - 与条件分支相同)
- 执行一组遥远的语句,之后控制流的流量通常返回(子例程,协调和连续性)
- 停止程序,防止任何进一步执行(无条件停止)
原语
标签
标签是源代码中分配给固定位置的明确名称或数字,并且可以通过源代码其他位置出现的控制流语句引用。标签标志着源代码中的位置,没有其他效果。
线号是某些语言(例如基本)使用的命名标签的替代品。它们是源代码中每行文本的开始时的全数字。使用这些的语言通常会施加以下约束,即线数必须在以下行中增加价值,但可能不要求它们是连续的。例如,在基本中:
10 LET X = 3
20 PRINT X
在其他语言(例如C和ADA)中,标签是一种标识符,通常出现在一条线的开头,紧随其后的是结肠。例如,在C中:
Success: printf("The operation was successful.\n");
语言Algol 60允许全数字和标识符作为标签(由结肠链接到以下语句),但是如果其他任何其他Algol变体都允许全数字。早期的Fortran编译器仅允许全数字作为标签。从Fortran-90开始,也允许字母数字标签。
去
goto陈述(英语单词的结合以及相应地发音)是控制的无条件转移的最基本形式。
尽管关键字可以根据语言的上层或较低的情况,但通常以:
goto label
goto语句的效果是导致下一个语句被执行是显示在指示标签(或之后)出现的陈述。
许多计算机科学家(尤其是Dijkstra)认为Goto陈述被认为有害。
子例程
子例程的术语各不相同;它们可能被称为例程,过程,功能(尤其是在返回结果的情况下)或方法(尤其是在属于类或类型类的情况下)。
在1950年代,按照当前标准,计算机的记忆非常小,因此子例程主要用于减少程序规模。一块代码曾经编写一次,然后从程序中的其他各个地方使用了很多次。
如今,通过隔离某些算法或隐藏某些数据访问方法来使程序更经常用于帮助使程序更具结构化。如果许多程序员正在从事一个程序,那么子例程就是一种模块化,可以帮助分割工作。
顺序
在结构化的编程中,连续命令的有序测序被认为是基本控制结构之一,该结构被用作与迭代,递归和选择一起的程序的构建块。
最小结构化对照流
1966年5月,Böhm和Jacopini在ACM通信方面发表了一篇文章,该文章表明,与Goto S的任何程序都可以转变为仅涉及选择的无goto形式(如果是其他)和循环(而条件DO XXX),可能是可能的使用重复的代码和/或添加布尔变量(true/false标志)。后来的作者表明,可以通过循环(还有更多布尔变量)代替选择。
这种极简主义是可能的,并不意味着它一定是可取的。毕竟,从理论上讲,计算机只需要一台机器指令(如果结果为负,则从另一个数字中减去一个数字),但是实用的计算机具有数十个甚至数百个机器指令。
Böhm和Jacopini的文章表明,所有程序都可以免费。其他研究表明,具有一个入口和一个出口的控制结构比其他任何形式都容易理解,这主要是因为它们可以在任何地方用作陈述而不会破坏控制流。换句话说,它们是可以组合的。 (后来的开发(例如非严格编程语言)以及最近的可组合软件交易- 延续了此策略,使程序组成的组件更加自由地组合。)
一些学者对Böhm -Jacopini的结果采取了纯粹的方法,并认为甚至指示break
和return
从循环的中间是不好的做法,因为在Böhm -Jacopini证明中不需要它们,因此他们主张所有循环都应具有一个单个出口点。这种纯粹的方法体现在Pascal语言中(设计为1968 - 1969年),直到1990年代中期,该语言是教授学术界入门编程的首选工具。 Böhm -jacopini定理的直接应用可能会导致结构化图表中引入其他局部变量,并且也可能导致某些代码重复。帕斯卡(Pascal)受这两个问题的影响,并且根据埃里克·罗伯茨(Eric S. Roberts)引用的实证研究,学生程序员很难针对帕斯卡(Pascal)制定正确的解决方案,以解决一些简单的问题,包括编写范围以搜索阵列中的元素。罗伯茨(Roberts)引用的亨利·夏皮罗(Henry Shapiro)的1980年研究发现,仅使用帕斯卡(Pascal)提供的控制结构,只有20%的受试者给出了正确的解决方案,而没有受试者为此问题编写不正确的代码,如果允许从该问题中写出回报循环的中间。
实践中的控制结构
大多数带有控制结构的编程语言都有初始关键字,该关键字指示涉及的控制结构的类型。然后,关于控制结构是否具有最终关键字的语言。
- 没有最终关键字: Algol 60 , C , C ++ , Haskell , Java , Pascal , Perl ,Php, PHP , PL/I , Python , Powershell 。这样的语言需要某种方式将语句分组在一起:
- 最终关键字: ADA , APL , Algol 68 , Modula-2 , Fortran 77 ,Mythryl, Visual Basic 。最终关键字的形式各不相同:
- ADA:最终关键字是
end
+空间+初始关键字,例如if
...end if
,loop
...end loop
- APL:最终关键字是
:End
可选 +初始关键字,例如:If
...:End
或者:If
...:EndIf
,Select
...:End
或者:Select
...:EndSelect
但是,如果添加终端条件,则最终关键字变为:Until
- Algol 68,神话:初始关键字向后拼写,例如,
if
...fi
,case
...esac
- Fortran 77:最终关键字是
END
+初始关键字,例如IF
...ENDIF
,DO
...ENDDO
- 模量-2:相同的最终关键字
END
一切 - 视觉基本:每个控制结构都有其自己的关键字。
If
...End If
;For
...Next
;Do
...Loop
;While
...Wend
- ADA:最终关键字是
选择
如果 - 然后(else)语句
条件表达式和条件构造是编程语言的功能,该功能取决于程序员指定的布尔条件是否评估为True或false。
IF..GOTO
。在满足条件时,以非结构化语言发现的表格,模仿典型的机器代码指令,将跳到(goto)标签或行号。IF..THEN..(ENDIF)
。任何简单的语句或嵌套块都可以遵循当时的关键字。这是一个结构化的形式。IF..THEN..ELSE..(ENDIF)
。如上所述,但是如果条件是错误的,则应执行第二个动作。这是最常见的形式之一,有许多变化。有些需要终端ENDIF
,其他人没有。 C和相关的语言不需要终端关键字,或者A“然后”,但确实需要围绕条件的括号。- 有条件的语句可以并且通常嵌套在其他条件语句中。一些语言允许
ELSE
和IF
被合并ELSEIF
,避免需要一系列ENDIF
或复合语句末尾的其他最终语句。
帕斯卡: | ADA : |
---|---|
if a > 0 then
writeln("yes")
else
writeln("no");
| if a > 0 then
Put_Line("yes");
else
Put_Line("no");
end if;
|
C : | 外壳脚本: |
if (a > 0) {
puts("yes");
}
else {
puts("no");
}
| if [ $a -gt 0 ]; then
echo "yes"
else
echo "no"
fi
|
Python : | LISP : |
if a > 0:
print("yes")
else:
print("no")
| (princ
(if (plusp a)
"yes"
"no"))
|
不太常见的变化包括:
- 某些语言(例如Fortran )具有三向或算术,如果数值为正,负还是零。
- 某些语言具有功能形式的
if
语句,例如LISP的cond
. - 某些语言具有运算符的形式
if
声明,例如C的三元运营商。 - Perl补充C风格
if
和when
和unless
. - Smalltalk使用
ifTrue
和ifFalse
消息以实现条件,而不是任何基本语言构造。
案例和开关语句
开关语句(或案例语句或多路分支)将给定值与指定常数进行比较,并根据第一个常数匹配采取行动。通常,如果没有匹配成功,通常需要采取默认操作(“否则”,否则“)。开关语句可以允许编译器优化,例如查找表。在动态语言中,情况可能不限于恒定表达式,并且可能会扩展到模式匹配,如右侧的shell脚本示例,其中*)
将默认情况实现为匹配任何字符串的球体。案例逻辑也可以以功能形式实现,例如在SQL的decode
陈述。
帕斯卡: | ADA : |
---|---|
case someChar of
'a': actionOnA;
'x': actionOnX;
'y','z':actionOnYandZ;
else actionOnNoMatch;
end;
| case someChar is
when 'a' => actionOnA;
when 'x' => actionOnX;
when 'y' | 'z' => actionOnYandZ;
when others => actionOnNoMatch;
end;
|
C : | 外壳脚本: |
switch (someChar) {
case 'a': actionOnA; break;
case 'x': actionOnX; break;
case 'y':
case 'z': actionOnYandZ; break;
default: actionOnNoMatch;
}
| case $someChar in
a) actionOnA ;;
x) actionOnX ;;
[yz]) actionOnYandZ ;;
*) actionOnNoMatch ;;
esac
|
LISP : | fortran : |
(case some-char
((#\a) action-on-a)
((#\x) action-on-x)
((#\y #\z) action-on-y-and-z)
(else action-on-no-match))
| select case (someChar)
case ('a')
actionOnA
case ('x')
actionOnX
case ('y','z')
actionOnYandZ
case default
actionOnNoMatch
end select
|
循环
循环是一系列陈述序列,曾经指定一次,但可以连续多次进行。遵循指定的次数或每一个项目集合,或直到满足某些条件,或无限期地,将代码“内部”循环(以下为XXX显示为XXX )。
在功能编程语言(例如Haskell和Schem)中,递归和迭代过程都用尾部递归程序表示,而不是循环构造的句法。
计数控制循环
大多数编程语言都有用于重复一定次循环的构造。在大多数情况下,计数可以向下而不是向上,并且可以使用1以外的阶跃大小。
FOR I = 1 TO N xxx NEXT I | for I := 1 to N do begin xxx end; |
DO I = 1,N xxx END DO | for ( I=1; I<=N; ++I ) { xxx } |
在这些示例中,如果n <1,则循环的主体可以执行一次(我有一个值1),或者根本没有,具体取决于编程语言。
在许多编程语言中,只能在计数控制的循环中可靠地使用整数。浮点数是由于硬件约束而不准确表示的,因此诸如
for X := 0.1 step 0.1 to 1.0 do
可能会重复9或10次,具体取决于四舍五入错误和/或硬件和/或编译器版本。此外,如果通过重复添加出现X的增量,则累积的舍入误差可能意味着每种迭代中X的值可能与预期序列0.1、0.2、0.3,...,1.0明显不同。
条件控制的循环
大多数编程语言都有用于重复循环的构造,直到某些条件更改为止。一些变化在循环开始时测试条件;其他人最后对其进行测试。如果测试开始时,则可以完全跳过身体;如果是最后,则始终至少执行一次。
DO WHILE (test) xxx LOOP | repeat xxx until test; |
while (test) { xxx } | do xxx while (test); |
控制断裂是普通循环中使用的值更改检测方法,用于触发值组的处理。值在循环中监视,更改程序流向与与之关联的组事件的处理。
DO UNTIL (End-of-File) IF new-zipcode <> current-zipcode display_tally(current-zipcode, zipcount) current-zipcode = new-zipcode zipcount = 0 ENDIF zipcount++ LOOP
收集控制的循环
几种编程语言(例如, ADA , D , C ++ 11 , SmallTalk , PHP , Perl , Object Pascal , Java , C# , Matlab ,Matlab, Visual Basic , Ruby , Python ,Javascript, Javascript , Fortran 95及以后)具有特殊的结构遍历数组的所有元素,或集合或集合的所有成员。
someCollection do: [:eachElement |xxx]. for Item in Collection do begin xxx end; foreach (item; myCollection) { xxx } foreach someArray { xxx } foreach ($someArray as $k => $v) { xxx } Collection<String> coll; for (String s : coll) {} foreach (string s in myStringCollection) { xxx } someCollection | ForEach-Object { $_ } forall ( index = first:last:step... )
Scala具有表达的表达,可以概括收集控制的循环,还支持其他用途,例如异步编程。 Haskell具有表达和综合,共同提供了与Scala中的表达相似的功能。
一般迭代
一般迭代构造,例如Cfor
陈述和普通的LISPdo
形式可用于表达上述任何一种循环,以及其他循环,例如并行对某些集合进行循环。如果可以使用更具体的循环构建体,则通常优先于一般迭代构建体,因为它通常使表达式更清晰。
无限环
无限循环用于确保永远或直到出现特殊条件(例如错误),以确保程序段循环。例如,事件驱动的程序(例如服务器)应该永远循环,处理事件发生,只有在操作员终止该过程时才会停止。
无限循环可以使用其他控制流构建体实现。最常见的是,在非结构化编程中,这是跳回(goto),而在结构化编程中,这是一个无限期的循环(而循环)设置为永无止境,无论是省略条件还是明确将其设置为true,while (true) ...
。一些语言具有用于无限循环的特殊构造,通常是通过从不确定的循环中省略条件。示例包括ADA(loop ... end loop
),fortran(DO ... END DO
), 去 (for { ... }
)和红宝石(loop do ... end
).
通常,无限的环路是通过条件控制的循环中的编程错误而无意间产生的,其中循环条件使用在循环中永不更改的变量。
继续下次迭代
有时,在循环的体内,有一个渴望跳过循环的其余部分,并继续进行循环的下一个迭代。有些语言提供了诸如continue
(大多数语言),skip
,cycle
(fortran)或next
(Perl和Ruby),这将做到这一点。效果是要过早终止最内向的循环主体,然后在下一次迭代中恢复正常。如果迭代是循环中的最后一个,则效果是尽早终止整个循环。
重做电流迭代
某些语言,例如Perl和Ruby,有redo
从一开始就重新启动当前迭代的声明。
重新启动循环
Ruby有一个retry
从初始迭代重新启动整个循环的声明。
早期退出循环
当使用计数控制的循环浏览表时,可能希望在找到所需的项目后立即停止搜索。一些编程语言提供了一个声明,例如break
(大多数语言),Exit
(视觉基本),或last
(perl),这是立即终止当前循环,并在循环后立即将控制转移到该语句。早期循环的另一个术语是循环和半循环。
以下示例是在ADA中完成的,该示例支持循环的早期退出和中间测试。这两个功能都非常相似,并且对两个代码片段进行比较都会显示出区别:早期出口必须与IF语句结合,而中间的条件是一个独立的结构。
with Ada.Text IO;
with Ada.Integer Text IO;
procedure Print_Squares is
X : Integer;
begin
Read_Data : loop
Ada.Integer Text IO.Get(X);
exit Read_Data when X = 0;
Ada.Text IO.Put (X * X);
Ada.Text IO.New_Line;
end loop Read_Data;
end Print_Squares;
Python支持有条件执行代码,具体取决于是否提早退出循环(有一个break
语句)或不通过与循环使用其他条款。例如,
for n in set_of_numbers:
if isprime(n):
print("Set contains a prime number")
break
else:
print("Set did not contain any prime numbers")
这else
上面示例中的子句链接到for
声明,而不是内在if
陈述。两者都是python的for
和while
循环支持这样的其他条款,仅当没有发生循环的早期出口时才能执行。
一些语言支持从嵌套循环中脱颖而出;从理论上讲,这些被称为多层次断裂。一个常见的示例是搜索多维表。这可以通过多级断裂(如bash和php中的n个级别)或通过标记的断裂(爆发并在给定标签上继续)来完成,例如Java和Perl中。多级断裂的替代方案包括单个断裂,以及一个状态变量,该变量经过测试以破坏另一个级别。例外,这些级别被打破了;将嵌套环放置在函数中,并使用返回以效应整个嵌套环的终止;或使用标签和goto语句。 C不包括多层次休息,通常的替代方法是使用goto实现标记的断裂。 Python没有多层次的突破或继续 - 这是在PEP 3136中提出的,并以较高的复杂性不值得极少数合法使用而拒绝。
多层次断裂的概念对理论计算机科学具有一定的兴趣,因为它引起了今天所谓的Kosaraju层次结构。 1973年, S。RaoKosaraju通过证明有可能避免在结构化编程中添加其他变量,从而完善了结构化程序定理,只要允许使用循环中的多级别,多级别的断裂。此外,kosaraju证明了严格的程序层次结构:对于每个整数n ,都存在一个程序,其中包含一个包含深度n的多级突破n的程序,无法将其重写为具有多级别深度的程序,而无需添加添加的多层次突破,而无需添加n变量。
也可以return
在执行循环语句的子例程中,从嵌套环和子例程中脱颖而出。多次休息时间还有其他建议的控制结构,但通常将其作为例外实现。
在他的2004年教科书中,戴维·瓦特(David Watt)使用Tennent的Sequencer概念来解释多层次断裂和返回语句之间的相似性。瓦特(Watt)指出,一类称为“逃生音序器”的序列仪定义为“终止文本封闭命令或过程执行的音序器”,其中包括两个从循环(包括多级别的断开)和返回语句的断裂。但是,正如通常实现的那样,返回序列仪也可能具有(返回)值,而在现代语言中实现的中断序列通常不能。
循环变体和不变的
实际上,循环变体是一个具有初始非负值的整数表达式。在每个循环迭代期间,变体的值必须降低,但在循环正确执行期间绝不能变为负。循环变体用于确保循环终止。
循环不变是一种断言,在第一次循环迭代之前必须是真实的,并且在每次迭代后保持真实。这意味着当循环正确终止时,满足退出条件和循环不变。循环不变性用于监视连续迭代过程中循环的特定属性。
某些编程语言,例如Eiffel ,包含对循环变体和不变的本地支持。在其他情况下,支持是一个附加组件,例如Java建模语言在Java中的循环语句规范。
循环sublanguage
某些LISP方言为描述循环提供了广泛的转数。可以在InterLISP的转换LISP中找到一个早期示例。 Common LISP提供了一个循环宏,可实现这种跨语言。
循环系统交叉引用表
程式设计语言 | 有条件 | 环形 | 提早出口 | 循环继续 | 重做 | 重试 | 正确的设施 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
开始 | 中间 | 结尾 | 数数 | 收藏 | 一般的 | 无穷 | 变体 | 不变 | |||||
艾达 | 是的 | 是的 | 是的 | 是的 | 数组 | 不 | 是的 | 深嵌套 | 不 | ||||
APL | 是的 | 不 | 是的 | 是的 | 是的 | 是的 | 是的 | 深嵌套 | 是的 | 不 | 不 | ||
C | 是的 | 不 | 是的 | 不 | 不 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | |||
C ++ | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | |||
C# | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | ||||
COBOL | 是的 | 不 | 是的 | 是的 | 不 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | |||
常见的LISP | 是的 | 是的 | 是的 | 是的 | 仅内置 | 是的 | 是的 | 深嵌套 | 不 | ||||
D | 是的 | 不 | 是的 | 是的 | 是的 | 是的 | 是的 | 深嵌套 | 深嵌套 | 不 | |||
艾菲尔 | 是的 | 不 | 不 | 是的 | 是的 | 是的 | 不 | 一个级别 | 不 | 不 | 不 | 仅整数 | 是的 |
F# | 是的 | 不 | 不 | 是的 | 是的 | 不 | 不 | 不 | 不 | 不 | |||
Fortran 77 | 是的 | 不 | 不 | 是的 | 不 | 不 | 不 | 一个级别 | 是的 | ||||
Fortran 90 | 是的 | 不 | 不 | 是的 | 不 | 不 | 是的 | 深嵌套 | 是的 | ||||
Fortran 95及以后 | 是的 | 不 | 不 | 是的 | 数组 | 不 | 是的 | 深嵌套 | 是的 | ||||
哈斯克尔 | 不 | 不 | 不 | 不 | 是的 | 不 | 是的 | 不 | 不 | 不 | |||
爪哇 | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | 非本地 | 非本地 | |
JavaScript | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | |||
自然的 | 是的 | 是的 | 是的 | 是的 | 不 | 是的 | 是的 | 是的 | 是的 | 是的 | 不 | ||
OCAML | 是的 | 不 | 不 | 是的 | 数组,列表 | 不 | 不 | 不 | 不 | 不 | |||
php | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | 不 | |||
珀尔 | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | 深嵌套 | 深嵌套 | 是的 | |||
Python | 是的 | 不 | 不 | 不 | 是的 | 不 | 不 | 深嵌套 | 深嵌套 | 不 | |||
rebol | 不 | 是的 | 是的 | 是的 | 是的 | 不 | 是的 | 一个级别 | 不 | 不 | |||
红宝石 | 是的 | 不 | 是的 | 是的 | 是的 | 不 | 是的 | 深嵌套 | 深嵌套 | 是的 | 是的 | ||
标准ML | 是的 | 不 | 不 | 不 | 数组,列表 | 不 | 不 | 不 | 不 | 不 | |||
Visual Basic .NET | 是的 | 不 | 是的 | 是的 | 是的 | 不 | 是的 | 每种循环的水平 | 每种循环的水平 | ||||
电源外壳 | 是的 | 不 | 是的 | 不 | 是的 | 是的 | 不 | ? | 是的 |
- a
while (true)
为此目的不算是无限的循环,因为它不是专用的语言结构。 - A B C D E F G H C
for (init; test; increment)
循环是一种通用的循环构造,而不是专门计数的循环构造,尽管经常用于此构建。 - 通过使用标签和gotos,可以在APL,C,C ++和C#中完成A B C的深度突破。
- PHP 5中添加了对像上的迭代。
- 可以通过在增量列表或发电机上进行迭代来模拟计数循环,例如
range()
. - 可以通过使用异常处理来实现b c d e 。
- A没有特殊的结构,因为
while
功能可用于此。 - A没有特殊的结构,但是用户可以定义一般循环功能。
- A C ++ 11标准引入了基于范围的。在STL中,有一个
std::for_each
模板功能可以在STL容器上迭代并为每个元素调用一个单一函数。该功能也可以在这些容器上构造为宏。 - 在整数间隔内通过迭代实现计数控制的循环;提前退出,包括额外的退出条件。
- 埃菲尔支持一个保留的单词
retry
但是,它用于例外处理,而不是循环控制。 - A需要Java建模语言(JML)行为接口规范语言。
- 一个需要循环变体为整数;不支持转菲斯变体。 [1]
- D支持无限的收藏,以及在这些收藏中迭代的能力。这不需要任何特殊的结构。
- 可以使用深度休息
GO TO
和程序。 - 一个常见的LISP早于通用收集类型的概念。
结构非本地对照流
许多编程语言,尤其是那些喜欢更具动态的编程方式的语言,为非本地控制流提供了构造。这些导致执行流程从给定的上下文中跳出,并在某些预言点上恢复。条件,例外和连续性是非本地控制构建体的三种常见类型;还存在更多异国情调的,例如发电机, coroutines和Async关键字。
状况
PL/I具有大约22个标准条件(例如,Zerodivide订阅端命中),可以升高,并且可以通过以下方式拦截以下情况;程序员还可以定义并使用自己的命名条件。
像非结构化的情况一样,只能指定一个语句,因此在许多情况下,需要一个goto来确定控制流应该在哪里恢复。
不幸的是,某些实现在时空和时间(尤其是订阅)上都有大量开销,因此许多程序员试图避免使用条件。
常见的语法示例:
ON condition GOTO label
例外
现代语言具有专门的结构化结构,用于例外处理,这不依赖于使用GOTO
或(多级)中断或返回。例如,在C ++中可以写:
try {
xxx1 // Somewhere in here
xxx2 // use: '''throw''' someValue;
xxx3
} catch (someClass& someId) { // catch value of someClass
actionForSomeClass
} catch (someType& anotherId) { // catch value of someType
actionForSomeType
} catch (...) { // catch anything not already caught
actionForAnythingElse
}
任何数量和种类的catch
子句可以上述使用。如果没有catch
匹配特定throw
,控制通过子例程调用和/或嵌套块将渗透到匹配catch
找到或直到达到主程序的末尾,此时该程序被强行停止使用合适的错误消息。
通过C ++的影响,catch
是保留用于在当今流行的其他语言(例如Java或c#)中宣布图案匹配异常处理程序的关键字。其他一些语言(例如ADA)使用关键字exception
引入一个异常处理程序,然后甚至可以使用其他关键字(when
在ADA中)进行模式匹配。诸如AppleScript之类的几种语言将占位符纳入例外处理程序语法中,以在发生异常时自动提取几片信息。下面是on error
从苹果构建:
try
set myNumber to myNumber / 0
on error e number n from f to t partial result pr
if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try
戴维·瓦特(David Watt)的2004年教科书还分析了音序器框架中的例外处理(在本文中介绍了《循环的早期退出》部分)。瓦特指出,通常用算术溢出或输入/输出失败(如未找到的文件)来举例说明的异常情况是一种错误,“在某些低级程序单元中被检测到,但[对于哪个],处理程序更自然地位于在高级程序单元中”。例如,程序可能包含几个读取文件的调用,但是找到文件时要执行的操作取决于所讨论的文件的含义(目的),因此无法处理这种异常情况的处理例程位于低级系统代码中。 Watts进一步指出,在呼叫者中引入状态标志测试,因为单exit结构化编程甚至(多exiT)返回序列将导致“应用程序代码往往会因状态标志的测试而变得混乱''和“程序员可能会忘记或懒惰地省略测试状态标志。实际上,默认情况下以状态标志代表的异常情况被忽略!”瓦特指出,与状态标志测试相反,异常具有相反的默认行为,除非程序员以某种方式明确处理例外,否则该程序终止了程序,可能是通过添加明确的代码来忽略它。基于这些参数,瓦特得出结论,跳跃序列仪或逃生序列仪与上面讨论的语义不如专用异常序列仪那样适合。
在Pascal,D,Java,C#和Python A中finally
条款可以添加到try
构造。无论控制如何离开try
内部的代码finally
子句保证执行。当编写必须放弃昂贵资源的代码(例如打开的文件或数据库连接)时,这很有用:
FileStream stm = null; // C# example
try
{
stm = new FileStream("logfile.txt", FileMode.Create);
return ProcessStuff(stm); // may throw an exception
}
finally
{
if (stm != null)
stm.Close();
}
由于这种模式相当普遍,因此C#具有特殊的语法:
using (var stm = new FileStream("logfile.txt", FileMode.Create))
{
return ProcessStuff(stm); // may throw an exception
}
离开后using
- 块,编译器保证stm
对像被释放,在从初始化和释放文件的副作用中抽象时,有效地将变量绑定到文件流。 Python'swith
声明和Ruby的障碍论点File.open
被用来类似的效果。
上面提到的所有语言都定义了标准例外及其所抛出的情况。用户可以自行抛出例外;实际上,C ++允许用户投掷并捕获几乎任何类型,包括基本类型int
,而其他语言(例如Java)并不那么允许。
连续
异步
C#5.0引入了以“直接样式”支持异步I/O的异步关键字。
发电机
发电机,也称为半座位,可以暂时将控制输送到消费者方法,通常使用yield
关键字( 产量描述)。像异步关键字一样,这支持以“直接样式”的方式支持编程。
Coroutines
Coroutines是可以互相控制的函数 - 一种合作多任务的形式而无需线程。
如果编程语言提供连续性或发电机,则可以将Coroutines作为库实现 - 因此,在实践中,Coroutines和Generator之间的区别是技术细节。
非本地对照流截面参考
程式设计语言 | 状况 | 例外 | 发电机/coroutines | 异步 |
---|---|---|---|---|
艾达 | 不 | 是的 | ? | ? |
C | 不 | 不 | 不 | 不 |
C ++ | 不 | 是的 | 是的 | ? |
C# | 不 | 是的 | 是的 | 是的 |
COBOL | 是的 | 是的 | 不 | 不 |
常见的LISP | 是的 | 不 | ? | ? |
D | 不 | 是的 | 是的 | ? |
艾菲尔 | 不 | 是的 | ? | ? |
Erlang | 不 | 是的 | 是的 | ? |
F# | 不 | 是的 | 是的 | 是的 |
去 | 不 | 是的 | 是的 | ? |
哈斯克尔 | 不 | 是的 | 是的 | 不 |
爪哇 | 不 | 是的 | 不 | 不 |
JavaScript | ? | 是的 | 是的 | 是的 |
Objective-C | 不 | 是的 | 不 | ? |
php | 不 | 是的 | 是的 | ? |
pl/i | 是的 | 不 | 不 | 不 |
Python | 不 | 是的 | 是的 | 是的 |
rebol | 是的 | 是的 | 不 | ? |
红宝石 | 不 | 是的 | 是的 | 通过扩展 |
锈 | 不 | 是的 | 实验 | 是的 |
Scala | 不 | 是的 | 通过实验扩展 | 通过实验扩展 |
TCL | 通过迹线 | 是的 | 是的 | 通过事件循环 |
Visual Basic .NET | 是的 | 是的 | 不 | ? |
电源外壳 | 不 | 是的 | 不 | ? |
建议的控制结构
R. Lawrence Clark在1973年的《欺骗数据》文章中提出,goto声明可以被Comefrom声明所取代,并提供了一些有趣的例子。 COME从一种名为Intercal的深奥编程语言实施。
唐纳德·诺斯(Donald Knuth) 1974年的文章“带有陈述的结构化编程”,确定了上面列出的控制结构未涵盖的两种情况,并给出了可以处理这些情况的控制结构的示例。尽管它们的实用性,但这些结构尚未进入主流编程语言。
中间测试的循环
Dahl在1972年提出了以下内容:
loop loop xxx1 read(char); while test; while not atEndOfFile; xxx2 write(char); repeat; repeat;
如果省略了XXX1 ,我们将在顶部进行测试(传统的循环)。如果省略了XXX2 ,我们将在底部进行测试,相当于do the do wher the lin the the lie the the the the the the lie the lie the lie the。如果省略了,我们会得到一个无限的循环。这里可以将这里的构造视为中间检查时循环。因此,这种单个结构可以替代大多数编程语言中的几种构造。
缺乏这种构造的语言通常会使用同等的无限环与破解的习惯模仿它:
while (true) { xxx1 if (not test) break xxx2 }
可能的变体是在测试时允许多个。在循环中,但是使用退出时(请参阅下一节)似乎更好地涵盖了这种情况。
在ADA中,可以使用标准的无限循环(循环-末端环)表示上述循环构造(循环-重复),该循环在中间的子句时具有退出(不要与下一节中的Exitwher语句混淆)。
with Ada.Text_IO;
with Ada.Integer_Text_IO;
procedure Print_Squares is
X : Integer;
begin
Read_Data : loop
Ada.Integer_Text_IO.Get(X);
exit Read_Data when X = 0;
Ada.Text IO.Put (X * X);
Ada.Text IO.New_Line;
end loop Read_Data;
end Print_Squares;
命名循环(例如此示例中的read_data )是可选的,但允许离开几个嵌套环的外环。
嵌套环的多个早期出口/退出
exitwhen EventA or EventB or EventC; xxx exits EventA: actionA EventB: actionB EventC: actionC endexit;
出口当时用于指定XXX中可能发生的事件,通过将事件的名称用作语句来表示它们的发生。当确实发生某些事件时,将执行相关的操作,然后在Endexit之后通过控制通过。这种构造在确定某些情况下适用的情况与为这种情况采取的措施之间提供了非常明显的分离。
从概念上讲,退出类似于异常处理,并且在多种语言中使用了例外或类似的构造。
以下简单示例涉及搜索特定项目的二维表。
exitwhen found or missing; for I := 1 to N do for J := 1 to M do if table[I,J] = target then found; missing; exits found: print ("item is in table"); missing: print ("item is not in table"); endexit;
安全
攻击软件的一种方法是重定向程序的执行流。各种控制流的完整性技术,包括堆栈金丝雀,缓冲溢出保护,阴影堆栈和VTable指针验证,用于防御这些攻击。