一、程序设计语言概述
(一)程序设计语言的基本概念
1. 程序设计语言的目的
为了书写计算机程序而人为设计的符号语言,用于对计算过程进行描述、组织和推导。
2.低级语言
机器语言:
机器语言是计算机最原始的语言,由0和1的代码构成
计算机在工作的时候只认识机器语言,即0和1的代码
汇编语言:
它用人类容易记忆的语言和符号来表示一组0和1的代码,如ADD-加法;SUB-减法。
面向机器的语言
3.高级语言
功能更强,抽象级别更高,与人们使用的自然语言比较接近。
高级语言与设备硬件结构无关
比如:java、C、C++、PHP、Phthon、、、
4.高级语言到计算机执行的过程
预处理:在高级语言源程序
中插入所有用#include命令
指定的文件
和用#define
声明指定的宏
。
编译:将预处理后的源程序文件
编译生成 相应的汇编语言程序
。
汇编:由汇编程序
将汇编语言源程序文件
转换为 可重定位的机器语言目标代码文件
。
链接:由链接器
将多个可重定位的机器语言目标文件
以及库例程
(如printf()库函数)链接起来
,生成最终的可执行目标文件
。
4.1 解释和编译
翻译程序
是指把高级语言源程序
翻译成机器语言程序
(目标代码) 的软件。
翻译程序有两种:编译程序、解释程序。
编译程序:
高级语言源程序一次全部翻译成目标程序,编译程序生成独立的可执行文件,
直接运行,运行时无法控制源程序,
每次执行程序时,只需执行目标程序
效率高
源程序、编译程序,不需要参与目标程序的执行过程。
编译程序不参与用户程序的运行控制
解释程序:
逐条解释执行,解释程序的执行过程是翻译一句执行一句并且不会生成目标程序(不能生成可执行文件)
用于调试模式,可以控制源程序
因为还需要控制程序,因此执行速度慢,效率低
源程序、解释程序, 需要参与目标程序的执行过程。
编译程序参与用户程序的运行控制
二、程序设计语言的基本成分
1.数据成分
指一种程序设计语言的数据和数据类型。
1.1 数据分类
常量(程序运行时不可改变)、变量(程序运行时可以改变)、
全局量(存储空间在静态数据区分配)、局部量(存储空间在堆栈区分配)。
1.2 基本数据类型
整型、字符型、双精度、单精度浮点型、布尔型
1.2.1 数据类型转变
2.运算成分
指明允许使用的运算符号及运算规则。
包括算术运算、逻辑运算、关系运算、位运算等。
3.控制成分
指明语言允许表述的控制结构。
包括顺序结构、选择结构、循环结构。
4.传输成分
指明语言允许的数据传输方式。
如赋值处理、数据的输入输出等。
5.逻辑表达式 短路计算
三、函数
(一)函数的定义
C程序由一个或多个函数组成,每个函数都有一个名字,其中有且仅有一个名字为main的函数作为程序运行时的起点。
(二)函数的使用
函数的使用涉及3个概念:函数定义、函数声明和函数调用。
1.函数定义
函数定义包括两部分:函数首部和函数体。函数的定义描述了函数做什么和怎么做。
2.函数声明
在首部说明了函数返回值的数据类型、函数的名字和函数运行时所需的参数及类型。函数所实现的功能在函数体部分进行描述。
3.函数调用
函数应该先声明后引用
。如果程序中对一个函数的调用在该函数的定义之前进行,则应该在调用前对被调用函数进行声明。函数原型用于声明函数。
函数调用时实参与形参间交换信息的方法有值调用和引用调用两种。
(1)值调用
若实现函数调用时将实参的值传递给相应的形参,则称为是传值调用。
实参可以是:变量、常量、表达式;(f(n)、f(3)、f(1+1))
实参、形参不能双向传递数据。即:对形参的访问和修改,不影响实参。
在这种方式下形参不能向实参传递信息。
在c语言中,要实现被调用函数对实参的修改,必须用指针作为参数。即调用时需要先对实参进行取地址运算,然后将实参的地址传递给指针形参。其本质上仍属于值调用。这种方式实现了间接内存访问。
(2)引用调用
引用是C++中引入的概念,当形式参数为引用类型时,形参名实际上是实参的别名,函数中对形参的访问和修改实际上就是针对相应实参所做的访问和改变。
实参不可以是:常量、表达式;(f(3)、f(1+1))
实参、形参可以双向传递数据。即:对形参的访问和修改,影响实参。
5.全局变量
全局变量
全局变量是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。
如果同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被“屏蔽”, 即它不起作用。
如果写在文件的开头,可以省略extern。
在静态存储区分配存储空间
四、编译、解释程序翻译阶段
(一)编译和解释过程
1.编译程序处理的过程
编译程序的功能是把用高级语言书写的源程序
翻译成与之等价的目标程序(汇编语言或机器语言)
。
编译程序的工作过程可以分为 6个阶段:词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成,实际的编译器中可能会将其中的某些阶段结合在一起进行处理。
目标代码生成和代码优化不是编译器必须的
2.解释程序
解释程序是另一种语言处理程序,在词法、语法和语义分析方面与编译程序的工作原理基本相同,但在运行时直接执行源程序或源程序的内部形式,即解释程序不产生源程序的目标程序,这点是它与编译程序的主要区别。
解释程序通常可以分成两部分:
第一部分是分析部分,包括通常的词法分析、语法分析和语义分析程序
,经语义分析后把源程序翻译成中间代码,中间代码常采用逆波兰表示形式。
第二部分是解释部分,用来对第一部分产生的中间代码进行解释执行。
图2-5显示了解释程序实现高级语言的三种方式。
1.编译器、解释器的词法分析、语法分析、语义分析三阶段不能少,顺序也不能变
目标代码生成和代码优化不是编译器必须的2.编译过程中,对高级语言程序语句的翻译主要考虑声明语句和可执行语句。对声明语局则是主要是将所需要的信息正确地填入合理组织的
符号表
中;对可执行语句,则是翻译成中间代码或目标代码。
(二)符号表
符号表的作用是记录源程序中各个符号的必要信息,以辅助语义的正确性检查和代码生成,在编译过程中需要对符号表进行快速有效地查找、插入、修改和删除等操作。
符号表的建立可以始于词法分析阶段,也可以放到语法分析和语义分析阶段,但符号表的使用有时会延续到目标代码的运行阶段。
(三)编译器的工作阶段
1.词法分析
源程序
可以简单地被看作是一个多行的字符串
。
词法分析阶段
是编译过程
的第一阶段
,主要任务是对源程序从前到后(从左到右)逐个字符
进行扫描,从中识别出一个个“单词”符号。词法分析程序输出的“单词”常采用二元组的方式,即单词类别和单词自身的值。
词法分析过程依据的是程序语言的词法规则
,即描述“单词”结构的规则。
输入:源程序
输出:记号流
2.语法分析
语法分析的任务是在词法分析的基础上,根据语言的语法规则
将单个单词符号序列分解成各类语法单位,如“表达式”、“语句”和“程序”等。
语法规则
就是各类语法单位的构成规则
。
通过语法分析,确定整个输入串是否构成一个语法上正确的程序。如果源程序中没有语法错误,语法分析后就能正确地构造出其语法树
;否则就指出语法错误,并给出相应的诊断信息。
输入:记号流
输出:语法树(分析树)
词法分析、语法分析,本质上都是对源程序的结构
进行分析。
语法分析阶段可以发现程序中的所有语法错误;编译正确的程序必然不包含语法错误;“除数为0”为动态语义错误,动态语义错误只有运行时才能发现。
3.语义分析
语义分析阶段主要是检查源程序是否存在语义错误,并收集类型信息供后面的代码生成阶段使用,只有语法和语义都正确的源程序
才能翻译成正确的目标代码
。
语义分析的一个主要工作是进行类型分析和检查
。可以发现静态语义
错误。
输入: 语法树(分析树)
不能发现动态语义错误(运行时才能发现),语义分析阶段,不能发现程序中的所有语义错误。
4.中间代码生成
中间代码生成阶段的工作是根据语义分析的输出生成中间代码。
中间代码是一种简单且含义明确的记号系统,可以有若干种形式,它们的共同特征是代码的方式与具体的机器无关
。(可以将不同的高级程序语言翻译成同一种中间代码。中间代码可以跨平台。)
因为中间代码与具体的机器无关,
所以,使用中间代码有利于进行与机器无关的优化处理和提高变异程序的可移植性。
中间代码的设计原则
主要有两点:一是容易生成,二是容易被翻译成目标代码。
语义分析和中间代码生成所依据的是程序设计语言的语义规则
。
4.1常见的中间代码:
后缀式
三地址码
三元式
四元式
树
图
5.代码优化
优化是一个编译器的重要组成部分,优化一般是建立在对程序的控制流和数据流分析
的基础之上,与具体的机器无关。
优化所依据的原则是程序的等价变换规则
。
6.目标代码生成
目标代码生成是编译器工作的最后一个阶段
,其任务是把中间代码
变换成特定机器上的绝对指令代码
、可重定位的指令代码或汇编指令代码
,这阶段的工作与具体的机器密切相关
。
会分配寄存器
(四)动态语义错误
语义分析只能检测出程序的静态语义错误,不能检测出动态的语义错误。
动态的语义错误要到程序运行时才能检测出来。
比如:
除数为0;
循环条件错误导致死循环。
五、编译程序的基本原理
(一)词法分析的工具-正规式
ab*:这个*针对的是b,即b可以出现0次或多次。
(二)有限自动机-词法分析的工具,能正确识别正规集
有限自动机可分为 确定的有限自动机(DFA)
和不确定的有限自动机(BFA)
。
其中确定的有限状态自动机和不确定的有限状态自动机最大的区别
是它们的转移函数不同
确定有限状态自动机对每一个可能的输入只有一个状态的转移
不确定有限状态自动机对每一个可能的输入可以有多个状态转移,接受到输入时从这多个状态转移中非确定地选择一个。
1.确定的有限自动机-DFA
对每一个状态来说识别字符后,转移的状态是唯一的。
2.不确定的有限自动机-NFA
对每一个状态(节点)来说识别字符后,转移的状态是不唯一的。
注意:一次正确的识别:识别完后,最后状态停留在终态。且每一个字符都能识别成功。
3.看图
看上图,从0到1有一个箭头,所以是a;
从0到1到3有箭头,所以是aa。
从3到3有指向自己的箭头,表示循环,则3这个地方可以生成无限个a或者b。
从3到4有箭头,所以是b。
看整体,不管走哪个路径,开头必然是aa(走0-1-3)或者bb(走0-2-3),且结尾必然是b,
(三)上下文无关文法-语法分析
上下文无关文法被广泛的用于各种程序设计语言的语法规则。
1.上下文无关文法(gfg)定义
分 析:从S出发是确定的,排除BC
若V∈N∪V,根据上下文无关文法的特性,V总可以被字符串N∪V自由地替换。但当V= N∪T时,由于非终结符的不唯一性,要构成等式成立,必须要N∪T中的符号串收缩为终结符,即都是T的集合。所以上下文无关文法G描述的语言是从S出发推导出的仅包含T中符号的串的集合。【答案:B】
2.CFG产生语言的基本方法:推导
1.推导的定义
将 产生式左部的非终结符
替换为 右部的文法符号序列
(展开产生式,用标记=>表示),直到得到一个终结符序列
。
推导的符号:=>
推导的输入:产生式左部
推导的输出:一个终结符序列
用(G3.2)产生终结符序列-(id+id)可如下
E => -E by(4)
=> -(E) by(3)
=> -(E+E) by(1)
=> -(id+E) by(5)
=> -(id+id) by(5)
3.最左推导和最右推导
在推导过程中,若每次直接推导均替换句型中最左边
的非终结符,则称为最左推导。
由最左推导产生的句型被称为左句型。
类似的可以定义最右推导(也叫规范推导)与右句型。
最左推导:
E => -E => -(E) => -(E+E) => -(id+E) => -(id+id)
α1 α2 α3 α4 α5 α6
最右推导:
E => -E => -(E) => -(E+E) => -(E+id) => -(id+id)
α1 α2 α3 α4 α5 α6
4.分析树
分析树是推导的图形表示
4.1.定义
对CFG G的句型,分析树被定义为具有下述性质的一棵树。
(1) 根
由开始符号
所标记;
(2) 每个叶子
由一个终结符、非终结符、或ε标记
;
(3) 每个内部结点
由一个非终结符
标记;
(4) 若A是某内部节点的标记,且X1,X2,…,Xn是该节点从左到右
所有孩子的标记,则A→X1X2…Xn是一个产生式
。若A→ε,则标记为A的结点可以仅有一个标记为ε的孩子。
4.2.分析树与语言和文法的关系
每一直接推导(每个产生式),对应一棵仅有父子关系的子树,即产生式左部非终结符“长出”右部的孩子
;
分析树的叶子,从左到右构成G的一个句型。若叶子仅由终结符标记,则构成一个句子
。
4.3.特性
越接近S的文法符号的优先级越低。
最终分析树的形状,仅与文法有关,而与推导方法无关
4.4.推导和分析树
最左推导和最右推导的中间过程
对应的分析树可能不同,因为句型不同
: -(id+E) 或 -(E+id)
但是最终的分析树相同
,因为最终是同一个句子: -(id+id)
中间过程:
最终结果:
5.语法树
对CFG G的句型,表达式的语法树被定义为具有下述性质的一棵树:
(1) 根与内部节点由表达式中的操作符标记;
(2) 叶子由表达式中的操作数标记;
(3)用于改变运算优先级和结合性的括弧,被隐含在语法树的结构中。
6.语法树与分析树的区别
实质上,语法树与分析树的最根本区别在于它们的内部节点(包括根节点):
分析树的内部节点是非终结符,语法树的内部节点是操作符(运算符);
语法树中省略了反映分析过程的非终结符。
(四)中缀式后缀式
1.中缀式
a ? b
2.后缀式(逆波兰式)
a b ?
后缀式又叫逆波兰式。
用栈
求值!
3.中缀转后缀
画出中序遍历的树,然后进行后序遍历
4.后缀转中缀
用栈(先进后出)
遇到数字,入栈;
遇到操作符,从栈中弹出两个数字;
两个数字与运算符结合,再入栈。