1.1 什么是程序设计语言(Programming Language)
定义:程序设计语言是一套表达
计算 过程的符号系统,其表达形式能够同时被
计算机 和
人 所
理解。
计算:计算机能够完成的任何操作
机器可读:语言结构简单,以便于翻译成机器语言。
翻译——有一个确定的,无二义性的算法来实现语言编译;翻译算法的复杂性不能太高
上下文无关文法
人类可读:语言能够对计算机的各种操作进行高度抽象,使得一个对底层硬件一无所知的人也能够用它编写程序。
1.2 程序设计语言中的抽象(Abstraction)
数据抽象:对计算机各种数据属性的抽象,也就是数据类型。常见的有字符串、数、搜索树等。
这些数据类型都是进行计算的对象。
控制抽象:对计算机执行的动作序列的抽象。常见的有循环、分支转移、子程序调用等。
还可以根据
所包含的信息量将抽象划分成各个层次。
基本抽象包含最小的局部计算信息;
结构抽象则包含更多的全局信息,能反映程序的某种结构;
单元抽象(unit abstraction)则包含整个程序或程序包的信息,它能被其他程序所使用。
1.2.1 数据抽象
基本抽象——对数据在计算机内部的具体表现方式进行抽象
例如:整数在计算机内部通常使用反码表示,整数的加法和乘法运算由相应的机器指令直接处理。
数据在内存中存储地址通常被抽象成一个标识符(变量)
数据类型被抽象成一个名词,如int, integer, real, float等
结构抽象——将各种相关的数据抽象为一种数据类型
例如:结构体,数组
单元抽象——大型软件项目中,相关的程序代码通常被封装成一个个功能模块
这种抽象包括了代码访问权限的设置——数据封装或者信息隐藏 例如:
“类”
软件重用
例如:被做成函数库,供其他程序调用的各种图形界面的控件和容器
1.2.2 控制抽象
基本抽象——典型的控制抽象就是将几句相关的机器指令组合成能够被人类理解的抽象语句
例如:x=x+3 表示把x标识的内存区域中的数据取出,加上3后再赋值给由x标识的内存区域
结构抽象——将程序分解成指令集合,并嵌套于管理他们执行的测试语句之中
例如:if-then-else语句,case语句,switch语句
好处:各种控制结构能够相互嵌套
一个更强的结构控制机制——
过程/函数/子程序调用
过程声明与过程激活
单元抽象——进一步将一组相关的过程组合起来构成一个独立的单元
模块、包
单元抽象是对程序的控制机制进行抽象,而数据抽象则是对数据在计算机内部的存储和运算进行抽象。
值得一提的是,几乎所有抽象机制的目的都是为了让人能更容易地读懂程序。
如果某种程序设计语言只是为了描述计算的话,那么它只需要包含图灵机上的那些操作就够了。
图灵机能够完成任何计算机上所执行的运算任务。图灵完备只需要很少的语言机制即可实现。
一个语言是 图灵完备 的,如果它包含整型变量、算术运算和顺序执行语句,其中后者包括赋值、选择和循环语句。
1.3 计算范例
程序设计语言始于对计算机指令的模拟和抽象。因此,计算机的体系结构总是影响着计算机上运行的程序。
冯 · 诺依曼体系结构:有一个能逐条执行存储在内存中的指令序列的中央处理器。
所以基于冯 · 诺依曼体系结构的语言都有一种典型的特征,就是
用变量代表内存中的数据,用赋值对内存中的数据进行操作。
命令式语言/过程式语言:同时具有这三种特性,即
顺序执行,
使用变量表示内存中的数据
和
以赋值语句改变内存数据
冯 · 诺依曼瓶颈:命令式语言的这种
串行执行 和
对单一内存变量进行赋值 的特性 限制了语言对
并行计算 的表示能力。
我们无法使用命令式语言对许多数据同时进行计算,也无法处理那些与执行次序无关的运算。
函数式范例——来源于数学中的lambda演算
逻辑式范例——来自符号逻辑
1.3.1 面向对象程序设计(C++,JAVA)
所谓
对象 ,就是指一组相关的变量和操作这组变量的运算的集合,也就是数据和操作的集合。
面向对象技术将
计算 表示为一组对象之间的交互或通信,每个对象都可以被看做是一台虚拟的计算机,它拥有自己独立的内存区域和操作。
类是具有相同属性的对象的模板,对象是类的实例。
1.3.2 函数式程序设计(Ada,Scheme,Haskell)
函数式计算范例基于函数求值机制。函数式语言也成为
应用式语言。
函数求值是函数式语言最基本的机制,它包括 参数传递,计算 和 结果返回。
函数式计算范例中 没有 变量 和 赋值 的概念。它将注意力集中在数据和函数上,而不是内存区域。
另外,操作的重复执行 不是使用循环刻画,而是使用 递归。
好处:
程序变得和底层计算机结构体系无关
、
函数式编程由于基于数学理论,程序正确性易于证明。
一种编程语言是图灵完备的,如果它包含 整型数据 和 关于整型数据的 算术操作,并且 能够利用已有函数定义新函数。
1.3.3 逻辑式程序设计(Prolog)
函数式计算范例基于符号逻辑。
在逻辑式语言中,程序是由一组结果必须被满足的规则,而不是一组由计算机顺序执行的命令组成。
纯逻辑语言根本不需要诸如循环或选择等控制结构。控制有底层系统直接支持。逻辑语言需要的只是 能够描述计算属性的语句。
因此,逻辑编程有时候也被叫做 声明式编程。
1.4 语言定义
语言的定义大致可以分成两个部分:
语法 和
语义
语法:决定了一些语言要素如何组合在一起构成其他的语言要素。
几乎所有语言都使用上下文无关文法进行语法定义。
语义:很难精确定义。包括
操作语义、
指称语义 和
公理语义。
1.5 语言翻译
直接执行程序的翻译器被称为
解释器,而把程序翻译成另外一种形式的翻译器被称为
编译器。
处理机制:
1)词法分析程序(扫描程序)将源程序从一个没有意义的字符串变成一个有意义的记号流,记号通常包括关键字、标识符和常数。
2)语法分析程序对这些记号的逻辑结构进行分析。
3)语义分析程序根据语法分析的结果生成中间代码或者目标代码。
语言的翻译程序还要维护
程序运行时环境,包括
变量存储位置 和
过程调用堆栈 等信息。
解释器通常将运行时环境的管理作为它对程序执行时控制的一部分。而编译器则通过在目标代码中插入控制命令来间接维护运行时环境。
语言也可以有一个
预编译处理器,它负责处理一些编译开始之前的工作。
程序有各种属性,那些在程序开始执行之前就可以确定的属性称为
程序的静态属性,而那些只有等到程序运行过程中才可以确定的属性称为
程序的动态属性。包含许多动态属性的语言就比较适合解释执行;而包含较多静态属性的语言则适合编译执行。命令式语言有许多静态属性,所以它们大多是编译执行的,而函数式语言和逻辑语言有较多的动态属性,所以他们大多是解释执行的。
语言的静态属性和动态属性对
运行时环境 也有影响。如果一种语言对所有变量内存都是静态分配的,那么可以使用完全静态的运行时环境。相反,针对那些对动态属性要求很高的语言,使用完全动态环境。介于两者之间的就是
同时具有静态和动态特点的堆栈结构 的运行时环境。
效率也会影响选择:
解释器总比编译期慢,因为解释器需要模拟程序在实际机器上的运行情况。编译器可以采用
多变扫描 的方法仔细分析程序结构,从而对目标代码进行优化和加速。
翻译器的一个重要性能指标就是它发现源程序中错误的能力。
错误是按照它们被发现时翻译器所处的阶段进行分类的:
词法错误(词法分析阶段)、
拼写错误(语法分析阶段)、
语义错误。
1.6 语言设计
设计出一种对人而言表达能力非凡且易于理解,对机器而言精确简单且易于翻译的语言是巨大的挑战。
在程序设计语言中 引入抽象的主要目的是控制编程的复杂程度。人类智能同时处理一定量的工作。