本书从为什么学习程序设计语言、常用程序设计语言的演化史、评估程序设计语言结构的标准,以及这些语言基本的实现方法开始讲起,通过不局限于特定语言种类地分析语言结构的设计问题,检测设计选择,以及比较设计可选方案来讲述程序设计语言基本原理。
第12版的更新
本书第12版的目标、总体结构以及方法与之前的11个版本相同。个目标是介绍现代程序设计语言的基本结构,并为读者提供对现有以及未来的程序设计语言进行严格评估的工具。第二个目标是帮助读者做好学习编译器设计的准备,为此,本书深入讨论了程序设计语言的结构,展示了描述语法的形式化方法,并介绍了词法和语法分析的方法。
与第11版相比,第12版有若干变化。为了保持本书内容不落伍,对于某些程序设计语言(尤其是Lua和Objective-C)的讨论,本版本几乎全部删除,而有关较新的程序设计语言Swift的内容则被分别增加到若干章中。
此外,在第6章中新增一节介绍可选类型。在8.3.4节中增加了一些介绍Python中的迭代器的内容。书中还有多处小改动,以对一些讨论内容进行纠正和澄清。
愿景
本书主要描述程序设计语言的基本概念。为此,主要讨论各种语言结构的设计问题,研究一些常见的语言在结构上的设计选择,并对备选设计方案进行严格的比较。
对程序设计语言进行的任何细致研究都无法脱离一些相关的主题,包括描述程序设计语言语法和语义的形式化方法,第3章将介绍这些方法。此外,还必须考虑各种语言结构的实现技术,第4章将讨论词法和语法分析,第10章将介绍子程序链接的实现。本书还将讨论一些其他语言结构的实现技术。
以下各段将概述第12版内容。
章节概述
第1章首先介绍程序设计语言的基本原理,然后讨论用于评价程序设计语言和语言结构的标准,同时,分析影响语言设计的主要因素、语言设计中的权衡以及语言实现的基本方法。
第2章概述本书所讨论的语言的发展过程。虽然没有完整地描述任何一种语言,但是对每种语言的起源、目的和贡献都会进行讨论。这样的历史回顾是很有价值的,因为它为我们理解当代语言设计的实践和理论基础提供了必要的背景。这也推动了对语言设计与评价的进一步研究。因为这本书的其余部分都不依赖于第2章,所以这一章可以独立于其他章节单独阅读。
第3章先介绍用于描述程序设计语言的BNF范式的主要形式化方法。接下来讨论用于描述语言的语法和静态语义的属性文法。然后探讨语义描述的难点,并对三种常见的语义方法(操作语义、指称语义和公理语义)进行简要介绍。
第4章介绍词法分析和语法分析。这一章主要面向那些不设置编译器设计课程的计算机科学院系。与第2章类似,这一章独立于除第3章之外的所有部分。这意味着这一章也可以独立于其他章节单独阅读。
第5~14章详细描述程序设计语言中主要结构的设计问题。对于每一种语言结构,都将讲述几种示例语言的设计选择并对其进行评估。具体来说,第5章介绍变量的一些特性,第6章介绍数据类型,第7章解释表达式和赋值语句,第8章描述控制语句,第9章和第10章讨论子程序及其实现,第11章研究数据抽象机制,第12章深入讨论支持面向对象程序设计的语言特性(继承和动态方法绑定),第13章讨论并发程序单元,第14章讨论异常处理,并简要讨论事件处理。
第15章和第16章描述两种重要程序设计泛型:函数式程序设计与逻辑程序设计。注意,第6章和第8章已经讨论过函数式程序设计语言的某些数据结构和控制构造。第15章介绍Scheme,包括它的一些基本函数、特殊形式、函数形式,以及一些使用Scheme语言编写的简单函数示例。此外,还简要介绍ML、Haskell和F#,以说明函数式程序设计的一些不同方向。第16章介绍逻辑程序设计以及逻辑程序设计语言Prolog。
致授课教师
一般应详细讲解第1章和第3章。对于第2章,尽管学生们会认为其内容很有趣且阅读起来很轻松,但由于缺乏严格的技术内容,我们不建议为其安排比较多的课时。如前所述,由于后续各章中的内容都不依赖于第2章,因此可以跳过该章。如果单独设置了编译器设计课程,那么也不需要讲授第4章。
对于那些具有较为丰富的C 、Java或C#编程经验的学生来说,第5~9章学习起来应该相对容易,而第10~14章的内容更具挑战性,因此需要更加详细地讲授。
第15章和第16章对于大多数低年级学生来说是全新的内容。在理想情况下,应该为需要学习这些内容的学生提供Scheme和Prolog的语言处理器。使用充足的学习材料可以让学生学习程序设计简单一些。
面向本科生开设的课程可能无法涵盖本书后两章中的所有内容,但面向研究生开设的课程应该能够跳过前面几章中有关命令式程序设计语言的内容,这样就能有足够的课时来讨论后两章中的内容。
补充材料
读者可以访问本书的配套网站www.pearson.com/cs-resources来获取一些补充材料,包括:
一套讲义幻灯片。书中的每一章都有配套的幻灯片。
本书中的所有图片。
几种程序设计语言的迷你手册(约100页的教程)。
可供使用的语言处理器
本书所讨论的某些程序设计语言的处理器以及相关信息可在以下网站找到:
C、C 、Fortran和Ada gcc.gnu.org
C#和F# microsoft.com
Java java.sun.com
Haskell haskell.org
Scheme www.plt-scheme.org/software/drscheme
Perl www.perl.com
Python
罗伯特·W. 塞巴斯塔(Robert W. Sebesta) 科罗拉多大学斯普林斯分校计算机科学系荣休副教授,拥有40多年计算机科学课程教学经验,研究兴趣包括程序设计语言的设计和评估以及Web程序设计。
译者序
第12版的变化
前言
致谢
第1章 预备知识1
1.1 掌握程序设计语言概念的必要性1
1.2 程序设计领域3
1.2.1 科学计算应用3
1.2.2 商业应用3
1.2.3 人工智能4
1.2.4 Web软件4
1.3 语言评价标准4
1.3.1 可读性5
1.3.2 可写性9
1.3.3 可靠性9
1.3.4 成本10
1.4 影响语言设计的因素11
1.4.1 计算机体系结构11
1.4.2 程序设计方法学13
1.5 程序设计语言分类14
1.6 语言设计中的权衡14
1.7 实现方法15
1.7.1 编译16
1.7.2 纯解释18
1.7.3 混合实现系统19
1.7.4 预处理程序19
1.8 程序设计环境20
小结20
复习题21
习题21
第2章 主要程序设计语言发展简史23
2.1 Zuse研制的Plankalkl语言23
2.1.1 历史背景23
2.1.2 语言概述25
2.2 伪代码25
2.2.1 短码26
2.2.2 快码26
2.2.3 UNIVAC编译系统27
2.2.4 相关工作27
2.3 IBM 704和Fortran27
2.3.1 历史背景27
2.3.2 设计过程28
2.3.3 Fortran I概述28
2.3.4 Fortran II29
2.3.5 Fortran IV、77、90、95、2003和200829
2.3.6 评价30
2.4 函数式程序设计语言:LISP31
2.4.1 人工智能的开端和列表处理31
2.4.2 LISP的设计过程32
2.4.3 语言概述32
2.4.4 评价33
2.4.5 LISP的两种后继语言34
2.4.6 相关语言34
2.5 迈向成熟的步:ALGOL 6035
2.5.1 历史背景35
2.5.2 早期设计过程35
2.5.3 ALGOL 58概述36
2.5.4 ALGOL 58报告的接受度37
2.5.5 ALGOL 60的设计过程37
2.5.6 ALGOL 60概述37
2.5.7 评价38
2.6 商业处理语言:COBOL39
2.6.1 历史背景39
2.6.2 FLOW-MATIC40
2.6.3 COBOL的设计过程40
2.6.4 评价40
2.7 分时处理的开始:Basic42
2.7.1 设计过程43
2.7.2 语言概述43
2.7.3 评价43
2.8 满足所有人的需求:PL/I46
2.8.1 历史背景47
2.8.2 设计过程47
2.8.3 语言概述48
2.8.4 评价48
2.9 两种早期的动态语言:APL和SNOBOL49
2.9.1 APL的起源及特征49
2.9.2 SNOBOL的起源和特征50
2.10 数据抽象的开端:SIMULA 6750
2.10.1 设计过程50
2.10.2 语言概述50
2.11 正交设计:ALGOL 6850
2.11.1 设计过程51
2.11.2 语言概述51
2.11.3 评价51
2.12 ALGOL系列语言的早期继承者52
2.12.1 简洁的设计:Pascal52
2.12.2 一个轻便的系统语言:C53
2.13 基于逻辑的程序设计:Prolog55
2.13.1 设计过程55
2.13.2 语言概述55
2.13.3 评价56
2.14 历史上规模的语言设计:Ada56
2.14.1 历史背景56
2.14.2 设计过程56
2.14.3 语言概述57
2.14.4 评价58
2.14.5 Ada 95和Ada 200558
2.15 面向对象程序设计:Smalltalk59
2.15.1 设计过程59
2.15.2 语言概述60
2.15.3 评价60
2.16 结合命令式和面向对象的特性:C 61
2.16.1 设计过程61
2.16.2 语言概述62
2.16.3 评价62
2.16.4 Swift:Objective-C的替代品62
2.16.5 另一个相关语言:Delphi63
2.17 基于命令式的面向对象语言:Java63
2.17.1 设计过程63
2.17.2 语言概述64
2.17.3 评价65
2.18 脚本语言66
2.18.1 Perl的起源与特点66
2.18.2 JavaScript的起源与特点67
2.18.3 PHP的起源与特点69
2.18.4 Python的起源与特点69
2.18.5 Ruby的起源与特点70
2.19 .NET旗帜语言:C#70
2.19.1 设计过程70
2.19.2 语言概述71
2.19.3 评价71
2.20 混合标记程序设计语言72
2.20.1 XSLT72
2.20.2 JSP73
小结74
文献注记74
复习题74
习题76
程序设计练习76
第3章 语法和语义描述77
3.1 概述77
3.2 语法描述的一般问题78
3.2.1 语言识别器78
3.2.2 语言生成器79
3.3 语法描述的形式化方法79
3.3.1 Backus-Naur范式与上下文无关文法79
3.3.2 扩展的BNF范式88
3.3.3 文法和识别器90
3.4 属性文法90
3.4.1 静态语义90
3.4.2 基本概念91
3.4.3 属性文法的定义91
3.4.4 内在属性91
3.4.5 属性文法示例91
3.4.6 计算属性值93
3.4.7 评价94
3.5 描述程序的含义:动态语义94
3.5.1 操作语义95
3.5.2 指称语义97
3.5.3 公理语义100
小结110
文献注记110
复习题110
习题111
第4章 词法和语法分析115
4.1 概述115
4.2 词法分析116
4.3 语法分析问题122
4.3.1 语法分析基础122
4.3.2 自顶向下的语法分析器123
4.3.3 自底向上的语法分析器124
4.3.4 语法分析的复杂度124
4.4 递归下降的语法分析124
4.4.1 递归下降的语法分析过程124
4.4.2 LL文法类129
4.5 自底向上的语法分析131
4.5.1 自底向上的语法分析器的语法分析问题131
4.5.2 移进-归约算法133
4.5.3 LR语法分析器133
小结137
复习题138
习题138
程序设计练习139
第5章 名字、绑定与作用域140
5.1 概述140
5.2 名字140
5.2.1 设计问题140
5.2.2 名字形式141
5.2.3 特殊单词141
5.3 变量142
5.3.1 名字142
5.3.2 地址142
5.3.3 类型143
5.3.4 值143
5.4 绑定的概念143
5.4.1 属性到变量的绑定144
5.4.2 绑定类型144
5.4.3 存储绑定和生存期147
5.5 作用域149
5.5.1 静态作用域149
5.5.2 分程序150
5.5.3 声明顺序153
5.5.4 全局作用域153
5.5.5 对静态作用域的评价156
5.5.6 动态作用域156
5.5.7 对动态作用域的评价157
5.6 作用域和生存期157
5.7 引用环境158
5.8 有名常量159
小结161
复习题161
习题162
程序设计练习165
第6章 数据类型167
6.1 概述167
6.2 基本数据类型168
6.2.1 数值类型168
6.2.2 布尔类型170
6.2.3 字符类型171
6.3 字符串类型171
6.3.1 设计问题171
6.3.2 字符串及其运算171
6.3.3 字符串长度选项173
6.3.4 评价173
6.3.5 字符串类型的实现174
6.4 枚举类型175
6.4.1 设计问题175
6.4.2 设计175
6.4.3 评价176
6.5 数组类型177
6.5.1 设计问题177
6.5.2 数组和索引178
6.5.3 下标绑定和数组的种类179
6.5.4 数组初始化180
6.5.5 数组运算181
6.5.6 矩阵数组和锯齿形数组182
6.5.7 切片182
6.5.8 评价183
6.5.9 数组类型的实现183
6.6 关联数组185
6.6.1 结构与运算185
6.6.2 关联数组的实现186
6.7 记录类型186
6.7.1 记录的定义187
6.7.2 记录中字段的引用187
6.7.3 评价188
6.7.4 记录类型的实现188
6.8 元组类型189
6.9 列表类型190
6.10 联合类型192
6.10.1 设计问题192
6.10.2 判别式与自由联合类型192
6.10.3 F#的联合类型193
6.10.4 评价193
6.10.5 联合类型的实现194
6.11 指针和引用类型194
6.11.1 设计问题194
6.11.2 指针运算194
6.11.3 指针的相关问题195
6.11.4 C和C 中的指针196
6.11.5 引用类型198
6.11.6 评价199
6.11.7 指针和引用类型的实现199
6.12 可选类型203
6.13 类型检查203
6.14 强类型204
6.15 类型等价205
6.16 理论和数据类型208
小结209
文献注记210
复习题210
习题211
程序设计练习212
第7章 表达式与赋值语句214
7.1 概述214
7.2 算术表达式214
7.2.1 运算符求值顺序215
7.2.2 运算分量求值顺序219
7.3 重载运算符221
7.4 类型转换222
7.4.1 表达式中的强制转换222
7.4.2 显式类型转换223
7.4.3 表达式错误224
7.5 关系表达式和布尔表达式224
7.5.1 关系表达式224
7.5.2 布尔表达式225
7.6 短路求值226
7.7 赋值语句227
7.7.1 简单赋值227
7.7.2 条件赋值227
7.7.3 复合赋值运算符227
7.7.4 一元赋值运算符228
7.7.5 赋值表达式229
7.7.6 多重赋值229
7.7.7 函数式程序设计语言中的赋值230
7.8 混合方式赋值230
小结231
复习题231
习题232
程序设计练习233
第8章 语句级控制结构234
8.1 概述234
8.2 选择语句235
8.2.1 二路选择语句235
8.2.2 多路选择语句238
8.3 重复语句244
8.3.1 计数控制循环245
8.3.2 逻辑控制循环248
8.3.3 用户定义的循环控制机制249
8.3.4 基于数据结构的迭代250
8.4 无条件分支253
8.5 保护命令254
8.6 结论256
小结256
复习题257
习题257
程序设计练习258
第9章 子程序260
9.1 概述260
9.2 子程序基础260
9.2.1 子程序的一般性质260
9.2.2 基本定义260
9.2.3 参数262
9.2.4 过程与函数265
9.3 子程序的设计问题265
9.4 局部引用环境266
9.4.1 局部变量266
9.4.2 嵌套子程序267
9.5 参数传递方法267
9.5.1 参数传递的语义模型268
9.5.2 参数传递的实现模型268
9.5.3 参数传递方法的实现272
9.5.4 常用语言的参数传递方法272
9.5.5 参数类型检查274
9.5.6 多维数组参数276
9.5.7 设计考量277
9.5.8 参数传递实例277
9.6 子程序作为参数280
9.7 子程序间接调用281
9.8 函数设计问题282
9.8.1 函数的副作用283
9.8.2 返回值类型283
9.8.3 返回值的个数283
9.9 重载子程序283
9.10 泛型子程序284
9.10.1 C 泛型函数285
9.10.2 Java 5.0泛型方法286
9.10.3 C# 2005泛型方法287
9.10.4 F#泛型函数288
9.11 用户定义的重载运算符288
9.12 闭包289
9.13 协同程序290
小结292
复习题293
习题294
程序设计练习295
第10章 子程序实现297
10.1 调用和返回的一般语义297
10.2 简单子程序的实现297
10.3 具有栈动态局部变量的子程序实现299
10.3.1 更复杂的活动记录299
10.3.2 不含递归的例子301
10.3.3 递归302
10.4 嵌套子程序304
10.4.1 基础304
10.4.2 静态链305
10.5 分程序309
10.6 动态作用域的实现310
10.6.1 深层访问310
10.6.2 浅层访问311
小结312
复习题312
习题313
程序设计练习315
第11章 抽象数据类型与封装结构316
11.1 抽象的概念316
11.2 数据抽象简介317
11.2.1 浮点型抽象数据类型317
11.2.2 用户自定义抽象数据类型317
11.2.3 示例318
11.3 抽象数据类型的设计问题319
11.4 语言示例319
11.4.1 C 中的抽象数据类型320
11.4.2 Java中的抽象数据类型325
11.4.3 C#中的抽象数据类型326
11.4.4 Ruby中的抽象数据类型327
11.5 参数化抽象数据类型330
11.5.1 C 330
11.5.2 Java 5.0331
11.5.3 C# 2005333
11.6 封装结构333
11.6.1 概述334
11.6.2 C中的封装334
11.6.3 C 中的封装334
11.6.4 C#程序集335
11.7 命名封装336
11.7.1 C 命名空间336
11.7.2 Java包337
11.7.3 Ruby模块338
小结338
复习题339
习题340
程序设计练习340
第12章 面向对象程序设计支持342
12.1 概述342
12.2 面向对象程序设计342
12.2.1 引言342
12.2.2 继承343
12.2.3 动态绑定344
12.3 面向对象语言的设计问题346
12.3.1 对象的排他性346
12.3.2 子类是否为子类型346
12.3.3 单继承与多继承347
12.3.4 对象的分配和释放347
12.3.5 动态绑定与静态绑定348
12.3.6 嵌套类348
12.3.7 对象的初始化349
12.4 支持面向对象程序设计的特定语言349
12.4.1 Smalltalk349
12.4.2 C 350
12.4.3 Java359
12.4.4 C#362
12.4.5 Ruby363
12.5 面向对象结构的实现366
12.5.1 存储示例数据366
12.5.2 方法调用与方法的动态绑定366
12.6 反射368
12.6.1 概述368
12.6.2 什么是反射368
12.6.3 Java中的反射369
12.6.4 C#中的反射371
小结372
复习题373
习题375
程序设计练习375
第13章 并发376
13.1 概述376
13.1.1 多处理器体系结构377
13.1.2 并发的分类378
13.1.3 使用并发的动机378
13.2 子程序级并发379
13.2.1 基本概念379
13.2.2 并发语言设计382
13.2.3 设计问题382
13.3 信号量382
13.3.1 概述382
13.3.2 合作同步383
13.3.3 竞争同步385
13.3.4 评价386
13.4 管程386
13.4.1 概述386
13.4.2 竞争同步386
13.4.3 合作同步386
13.4.4 评价387
13.5 消息传递387
13.5.1 概述387
13.5.2 同步消息传递的概念388
13.6 Ada并发支持388
13.6.1 基本概念388
13.6.2 合作同步391
13.6.3 竞争同步392
13.6.4 受保护对象393
13.6.5 评价394
13.7 Java线程394
13.7.1 线程类395
13.7.2 优先级397
13.7.3 信号量397
13.7.4 竞争同步397
13.7.5 合作同步398
13.7.6 非阻塞同步401
13.7.7 显式锁401
13.7.8 评价402
13.8 C#线程402
13.8.1 基本线程操作402
13.8.2 同步线程404
13.8.3 评价405
13.9 函数式语言中的并发405
13.9.1 Multi-LISP405
13.9.2 并发ML406
13.9.3 F#406
13.10 语句级并发407
13.10.1 高性能Fortran407
小结409
文献注记410
复习题410
习题411
程序设计练习412
第14章 异常处理和事件处理413
14.1 异常处理概述413
14.1.1 基本概念414
14.1.2 设计问题415
14.2 C 异常处理417
14.2.1 异常处理程序417
14.2.2 异常绑定到处理程序418
14.2.3 延续418
14.2.4 其他设计选择418
14.2.5 示例419
14.2.6 评价420
14.3 Java异常处理420
14.3.1 异常类别421
14.3.2 异常处理程序421
14.3.3 异常绑定到处理程序421
14.3.4 其他设计选择422
14.3.5 示例423
14.3.6 finally子句424
14.3.7 断言425
14.3.8 评价425
14.4 Python和Ruby的异常处理426
14.4.1 Python426
14.4.2 Ruby427
14.5 事件处理概述428
14.6 J