基础
#
注意
比较语言,共通处(抽象的元知识)是要点
在历史上判断设计者意图 # 利于了解知道的根基
不同规则,只在特点语言中合理 # 如0在ruby为真
历史
EDSAC # 1949,纸带
FORTRAN # 1954, 中缀表达式, 运算符优先级、结合性
FORTH # 1958, 没有语法,后缀表达式,语法树
LISP # 1958, 括号,前缀表达式,语法树
语法
引入优先级和左右结合
规则不冲突是困难的
vector<vector<int> > # c++的语法缺陷, >>是位运算,必须加空格
结构化 # 60年代
if # 汇编是判断再向后跳代码, if使可读性好
while # 可读了反复执行的if
for # 可读了数值渐增的while
foreach # 可读了集合遍历
函数
作用
便于理解 # 组织划分部门
便于再利用 # 再利用无代码成本
用了跳转命令和返回命令 # 从记录函数前后地址到函数记录返回地址
栈记录多级调用 # 解决多级调用返回地址被覆盖问题
递归 # 处理嵌套数据结构时,代码的嵌套结构
错误处理
历史
UNIVACI # 1950, 溢出时中断(interrupt)跳转到000
COBOL # 1959, 两种类型错误,用两关键字处理
PL/I # 1964
先定义出错处理代码。编程时引入on语句goto到处理代码, 不检查返回值
可定义新错误类型, 可用signal condition主动出错
john goodenough # 1975,论文
程序员可能忘处理异常、在不正确位置处理、处理不正确类型异常
应该声明可能抛出的异常、将可能出错结构括起来的语句结构
CLU # 1975, begin ... end 后 except 错误类型, 再写错误处理语句
C++ # 1983, 1984-1989多次讨论,try{}catch{}, throw抛异常(没用raise和signal, 因为它们已经使用了)
Windows NT 3.1 # 1993, 使用finally
Java # 1995, 引入finally
D # 2001, 作用域守护取代finally, 写在初始化语句后面scope(exit) unlock(m)
方式
返回值 # 遗漏错误,可读性下降
异常处理 # 函数多出口
必执行代码 # 成对操作无遗漏,不使用goto执行同段代码
finally # java, ruby, python
析构函数 # c++, RAII(资源获取即初始化, resource acquisition is initialization)
何时使用异常 # 有不同的规则。缺少参数,js不抛异常。数据越界,js返回undefined, ruby返回nil
出错立刻抛异常 # 错误优先(fail first), 早发现问题
异常传递 # 一层层函数向上传递,都无处理时程序异常退出
上层函数不了解下层异常细节
不了解下层调用不能捕获全部异常 # java用检查型异常解决, 但很麻烦
取名
编号到取名 # 对照表实现, 变量名或函数名
实现
整个程序共用一个对照表 # perl无声明变量, 全局作用域、全局变量
# 1994 javascript
解决冲突
更长变量名
作用域
动态作用域 # 1991 perl4 local变量
# 1958 lisp
之前存变量原值,之后用原值覆盖变量
中间函数调用时,变量不是原值, 要看调用前所有代码 # 全局污染
静态作用域 # 1975 scheme,调用时建专用对照表(属于调用而非函数),查找变量更优先。
# 又叫字面作用域(lexical scope), 因为和字面范围一致
# 1991 perl5 my变量
# 1991 python
# 1994 javascript val变量
# 1995 ruby
python
内置作用域 # 语言提供,如js的全局对象作用域
全局作用域 # 当前文件字面
局部作用域 # 函数
赋值即定义
嵌套函数直接找全局作用域,不字面上找外部函数作用域 # 2.0问题, 2001年2.1修复
不能改变外部变量 # 2006年3.0 nonlocal声明为外部变量
类型
比特列标记类型,解释成不同数据
整数
excess-3(加三码) # UNIVACI, 4位表示0到9
二进制 # 1983, 任天堂计算机8位表示整数。目前32位和64位表示整数
八进制(3位切分), 十六进制(4位切分)
实数
单独记小数点左移位数 # 定点数,不好实现和计算。
银行用加三码和定点数
前位段表示数,后位段表示小数点位置 # 浮点数,也可表示大整数
IEEE 754 # 有误差
第一位符号
中8位是指数(-127-128), 负向左移正向右移,-127代表0, 128代表无限大
后23位是尾数,从左到右代表1/2, 1/4, 1/8...
二-十进制码 # 用二进制表示十进制,加三码是一种,无误差
发展
变量名表示类型 # FORTRAN, I-N开头表示整数,其它表示浮点数
声明类型
隐式类型转换 # 整数+浮点数,FORTRAN出错,c都转换为浮点数, 整数除法舍弃小数
ML(1973年)中, 整数除法用x div y, 小数除法用 x / y
python3.0(2008年)中, 不带舍去除法用 x / y, 带舍去除法用 x // y
用户定义类型 # c中的结构体, c++中函数成为类型,用户实现的类型称为类
类型即功能 # 访问控制(公开、非公开)
接口 # 不包含实现细节的类型
异常成为类型 # CLU和Java
类型实现所有功能 # 未实现。类型一致,功能就成立,没有bug
类型不能表达的:数据处理时间,处理用内存,是否可以在线程中操作等
总称型(部分可变类型) # 类型为参数创建类型,c++的模板,java的泛型,haskell的类型构造器
动态类型 # 类型信息和数值看作整体, 静态类型把变量名、内存地址、内存里的内容类型作为整体
内存中同等类型对待,其中再细分类型
灵活,运行时确定类型,但不能执行前编译检查bug
类型推导 # 最早OCaml和Haskell这类ML语言擅长,现Scala等语言也越来越多
目标是证明程序没有bug
容器
语言中用语不共通 # haskell列表是链表,不可变,元组是放不同类型的列表
数组、链表
字典(散列、关联数组) # 字典散列或树实现
树
字符串
字符集和字符编码 # 有的认为就按效率特异化编码,有的认为应标准化
摩斯码 # 长短组合
博多码 # 5位一字符,先通知字符种类
EDSAC # 5位一字符,shift切换,内容和博多码不同。用5孔纸带
ASCII # 7位
EBCDIC # IBM,8位
ISO-xxx # 区域化
魔术注释符 # 告诉语言处理器编码,特殊记号事先写明
unicode # 统一
字符串
c语言一字符8位,定义字符为ASCII或EBCDIC。字符串不知长度,nul字符终止,没nul时可能内存中越界读取
pascal一字符8位,带长度
java一字符16位, 定义字符为unicode
python2 ASCII码环境下,字符当作ASCII码,可以自动转换成unicode
python3中""是unicode码, b""是字节列串,要显示转换类型,否则报错
ruby一字符8位,追加编码信息
面向对象 # 不同语言中面向对象意义不同
# goto因强大让人困惑,退出历史。面向对象、Trait也有这因素
两种立场
c++, 类是用户自定义类型,Simula语言的继承机制是关键
smalltalk, 类让人痛苦,不要继承,不同状态对象传消息来通信
历史 # ALGOL产生model思想(1958年), Simula , Smalltalk, C++, Java
类在大部分语言中不是不可或缺的
Java: 类是部件,将其组装就是程序设计
归集方法和建模的发展 # 围绕实现多实例问题
强关联元素分组存放,便于理解
module # 关联函数集中, 1978年Modula-2引入, python, ruby叫模块,java, perl叫包
初始化散列, 再作为参数传入包中函数,函数修改散列
包提供初始化函数, 返回散列, 该函数成为构造函数(java叫工厂方法)。但使用包函数都要传入散列
bless函数(perl)绑定包和散列产生blessed hash对象,它的方法对应包方法, curry了散列做参数
包的初始化函数自己绑定, 返回blessed hash对象
变量和函数放入散列 # js对象
函数放入散列 # 函数成为一等公民(first class citizen),可赋值给变量。FORTRAN66中字符串还不是一等公民
# first-class function的思想来自Scheme语言
函数中通过this隐式获取自身做散列 # perl中显示获得散列
创建构造函数,返回以上散列。但返回的散列上都定义了新的函数
把函数单独放置在包或对象, 使用时很麻烦
引入原型概念,对象变量查找作用域在原型链中扩展 # 这里是委托方式的原型,也可以在实例化时通过负责来实现。原型变更的处理,不同语言有差异
# prototype-based的思想来自Self语言
定义new f()运算, 函数f的原型是以上散列,多个new的新对象共享了散列的函数
新对象原型指向函数f的原型
以新对象为this,执行f
闭包(closure) # 维持内部作用域状态的函数, 作用域呈封闭状态
类
分类/分组 # 1965年ALGOL提出
用户定义类型 # 1979年c++, 参考Simula
最初c语言结构体
声明和定义类方法 # Smalltalk方法调用是传送消息,调未定义方法是否出错由该类决定
作用 # 成为一种模具
生成器 # module和散列只有该作用
可行操作的功能说明(类型、泛型)
代码再利用的单位(继承)
继承
实现策略
父类实现一般化, 子类是父类的专门化
共享部分提取 # 子类不是父类的一种, 函数思想考虑问题
差异实现 # 覆盖变更部分,为了再利用使实现更轻松,不倾向使用
问题
多层级问题
向上不好找方法定义
修改方法时,向下影响子类 # 如动态作用域问题
里氏替换原则 # 1987年提出,对父类成立的条件,一定对子类成立。为了维护父子关系间的一致性,继承是is-a关系
实际编程中,子类功能增加常打破里氏替换原则,无论是在开始设计上避免还是在开发中放弃继承都很麻烦
多重继承 # 东西常常不属于一个分类,java禁止多重继承
问题
多父类成员名冲突
委托(delegation) # 聚合(aggregation),咨询(consultation), 不用多重继承,把原父类对象作为子类成员,后发展出依赖注入
接口多重继承 # Java引入,php5(2004年)引入
按顺序搜索
深度优先 # python2.1, 菱形继承中,第一层父类的值会覆盖第二层右边父类的值
C3线性化 # 1996年提出,python2.3, perl6默认, 对类编号,子类先于父类检查, 优先检查先书写的类
混入式处理(mix-in) # 扁平成新类, 该类不能创建实例,python XxxMixIn类, ruby类单一继承,模块混入
Trait # 2002年Trait论文,Squeak最早引入, scala, perl6的Roll, php5.4, ruby2.0的mix method
类作用:创建实例(要求全面, 大的类),再利用单元(小的类)冲突
把再利用单元特别化
ruby模块混入名称冲突时, 使用最后的模块, Trait会报错 # Smalltalk的Squeak处理器可取方法别名,可指定不参与冲突
scala声明创建实例需要的方法, 另一Trait声明提供的方法,组合匹配后可创建实例
对Trait改写定义新的Trait(继承), Trait组合成新Trait
原理
#
gc
# garbage collector
为什么gc
减少编程工作量
减少内存泄露
安全性
定位
引用计数器(reference counting, java1.1废弃) # 给每个对象设置计数器,有引用+1, 失效-1
不能解决循环引用问题 # A, B互相引用不回收
计数器修改开销大
根搜索算法(使用) # 从GC Roots对象起点,向下搜索,路径成为Reference Chain, 不可达对象不可用
GC Roots包括
虚拟机栈(栈帧中本地变量表)中的引用对象
方法区域中的类静态引用对象
方法区域中常量引用对象
本地方法栈JNI(native方法)引用的对象
回收算法
标记-清除(mark-sweep) # DVM(Dalvik Virtual Machine, 安卓用)使用的算法
# 效率不高,清除后产生大量不连续空间
标记所有要回收的对象
清除标记对象
复制(copying)
# 实现简单,效率高。内存利用率不高
# 用于新生代,两块比例8:1
内存分成相等两块,使用其中一块
回收时,存活对象复制到另一块,这块整个清理掉
标记-整理(mark-compact)
# 适合老年代
把存活对象往内存一端移动
回收边界外内存
分代收集(generational collection)
新生代用复制算法
老年代用标记-整理算法
4种引用类型回收 # java强引用、软引用、弱引用、虚引用
方法区回收
废弃的常量 # 看引用计数
无用的类
实例都已经回收
加载该类的ClassLoader已回收
该类的class对象没有被引用,无法反射该类的方法
异步编程
#
事件 # 高阶函数的优势
解耦业务逻辑
问题
异常处理 # 不能同步catch
回调嵌套过深
单线程模型中,业务处理阻塞全局
异步转同步
实现
事件发布订阅
回调函数事件化(钩子机制)
cps: continuation-passing style
在函数式编程中, 多传一个参数k明确控制continuation
promise/deferred # promise/A, promise/B, promise/D模型
# promise在外部暴露接口(可变部分), deferred在内部维护状态(不可变部分)
状态
未完成,完成,失败
方向只能未完成->完成, 未完成->失败
状态转化不能更改
api
then()
done()
all() # 所有成功成功,一失败失败
any()
流程控制库
尾触发 # 传next()函数
async库(node.js)
series() # 串行
parallel() # 并行
waterfall() # 串行传结果
auto() # 计算依赖顺序执行
bagpipe库(node.js) # 限制并发, 任务可排队或拒绝, 超时控制
编译
#
# 流程
词法分析,语法分析,语义分析,中间代码生成,中间代码优化,目标代码生成,表格管理,错误处理
语义分析 -> 类型检查/推导 -> 代码优化 -> 机器码生成
# 中间数据结构, 比如AST
预处理,连接程序,装入程序,调试程序
# 文法
G = (Vn, Vt, S, P)
# 终极符号, 非终级符号, 一个特殊非终级符号,产生式
类型
短语(0), 对应图灵机(TM)
上下文相关(1), 对应线性有界自动机(LBA)
上下文无关(2), 对应下推自动机(PDA)
线性文法、正则文法、正规文法,对应有限自动机(FA)
# 无法控制自返数
# 状态机(FA)
确定状态机(DFA)
非确定状态机(NFA)
# 同状态可多种转移
DFA与NFA互相转换
# 词法分析
状态转换矩阵法
# 语法分析
# 源代码读入、解析成语法树
自顶向下
# 最左推导建立语法树
# first集,follow集,predict集
不回溯方法
递归下降
LL(1)
# 从左输入符号、产生左推导、每次读一个字符。LL(k)特例
自底向上
# 从左读, 从右向前归约
简单优先关系
# 运算符优先关系矩阵
LR(k)
# 从左输入,最右推导
LR(0)
# 只看栈顶状态,有分析动作冲突
SLR(1)
# LR(0)加向前看展望符,不能分析所有文法
LR(1)
# LR(0)的每个推导加一个向前搜索符,状态太多
LALR(1)
# LR(1)中同向前搜索符的状态合并
# 语义分析
抽象语法树
符号表
# 动态规划记录变量的综合信息
局部化处理
# 压栈变量作用域
# 中间代码生成
后缀式(逆波兰式)
三地址
# 操作符两变量地址,结果地址
四元式操作符
# 地址加,赋值,过程调用,类型转换,算术、逻辑、关系运算的存储
语法制导
# 中间代码产生式后拼上语义程序,在语法分析中遇到动作马上处理
类型检查
下标变量
# 如数组下标,同上全用四元式表示
# 中间代码优化
常量表达式
a = 1, b = 2, c = a + b, 则只记c = 3
公共表达式
a = b * c, d = b * c, 则只记a
循环不变式外提
while k < 0 do b * c, 则b * c外提只计算一次
基本块
# 一块语句要么全执行,要么全不执行
消减运算强度
如加法代替乘法
复写传播
a = b, 后a, b不再变值,用a替代b
无用代码消除
数学优化(恒等变换)
如a + 0 = a, a * 1 = a, a ^ 2 = a * a, a / 1 = a, 0 / a = 0
窥孔优化
对目标代码中短指令序列局部改进,如删除重复,控制流优化,代数化简,使用特殊指令等
全局优化
对整个程序控制流和数据分析再优化,如常量表达式全局优化
# 运行时时空管理
内存划分
存储
引用的库的代码
目标代码
静态变量
栈区
# 函数调用,中断现场
堆区
存储策略
静态分配
#编译时分配固定存储单元
动态分配
栈
堆
活动记录
保存局部变量,中间结果,临时变量,过程调用,控制信息等
专用寄存器
调用链
# 保存下一个调用的起始地址
动态链
# 保存前一个调用的起始地址
访问环境
# 记录闭包起始地址
display表
# 过程需要的所有非局部数据所在的过程活动记录的起始地址
全局display表
静态链
# 指向外层过程的活动记录的地址地址
# 目标代码生成
生成的语言
机器语言
可重定位的机器语言
# 由连接器装配后生成机器语言
# 多数用这种,如c语言
汇编语言
指令选择
虚拟机
寄存器分配
四元式翻译