第6章:控制流——《实践之路》笔记

聊聊这章都讲了啥

有很多有趣的问题

1.表达式的递归定义:为什么很多概念都是以递归的形式给出的?

递归有一个终结条件,定义最终会递归到一个不可分元素,最小表达式就是一个常量或变量

通常我们是用结构(迭代)的角度考虑问题的,把表达式看做一个个运算对象拼起来的,因为程序就是这么敲出来的哈哈

而迭代的定义通常会趋向于无限,我们可以指出最小的表达式,但无法指出一个表达式最大会是什么样

所以,用递归定义简洁描述了一个表达式是如何逐步分解至不可分的

2.一行代码中发生了什么?为什么分别定义表达式与语句?

求值(表达式完成)

副作用(赋值语句完成)

区分表达式与赋值,就区分了求值与副作用

3.表达式与语句的关系

赋值需要一个新值

求表达式,得到值,赋值保存值

4.把运算符归结到函数

运算符是内部实现的函数,运算对象自然就是实参

这么看来运算符像是自举的结果,定义上更加简洁了

内置的运算应该经过了优化,因为出现频繁,所以决定了算法的效率,应该并不只是自举

5.函数定义与调用发生了什么?

函数签名里说明了运算需要啥数据,巧妇难为无米之炊

调用时,把数据传入函数,需要有效区分传入的实参:位置参数,关键字参数

6.计算的顺序

先计算哪个运算符?优先级

都是同样的运算符?结合律

运算对象的值先求哪个?应用求值,正则求值

7.什么是变量的值模型,引用模型?

名字——地址——值

选两个打包起来

值模型:名字与地址打包

引用模型:地址与值打包

9.要求表达式的上下文,要求语句的上下文?

需要一个值

需要执行的步骤

10.为什么要关注类型?

类型指定操作的

不同类型互操作:可能引起精度损失,或发生错误

11.goto的废弃?

goto+地址很难表达整体意图

12.goto的替代方案

异常处理也是一种语句跳转

13.函数能修改啥?不该修改啥?

参数是否修改?替代修改参数的方案?

函数作用域与其他作用域的相互可见性

14.迭代与递归的作用

程序规模不再局限于程序正文的线性长度

15.迭代器在各种语言中怎么实现?

编程约定,模仿缺失的特征,获得等价的功能

 

引言

顺序执行是命令式语言的核心

函数式语言强调表达式的求值

逻辑式语言藏起控制流

 

6.1表达式求值

表达式递归定义:

1.简单对象(内置类型)、自定义类型实例(支持运算符)

2.运算符与函数应用于运算对象或参数

运算对象或参数也是表达式

 

运算符:可看做简单形式的内部函数

运算对象:运算符的参数

就此把运算符与运算对象归结到了函数的讨论范畴

 

函数调用

函数名(参数列表)

 

前缀,中缀,后缀

 

三元中缀运算符

if  then  else

 

优先级结合性

决定了求值顺序(还有之后提到的参数求值顺序

 

c语言优先级丰富

类型强制,函数调用,数组下标,记录域选取

 

6.1.1优先级与结合性

优先级

括号  算数  比较  逻辑

 

结合律

左至右

赋值  右至左

 

6.1.2赋值

命令式语言的计算:通过修改内存中的变量

修改的基本操作:赋值

 

赋值的参数:值,变量引用

 

通过修改变量值,影响后续计算

所以赋值是最基本的副作用

 

命令式语言严格区分  表达式与语句

表达式:产生值

语句:产生副作用

 

纯函数式:没有副作用

表达式是引用透明

引用透明:幂等,无副作用

没有副作用,意味着耦合度低,不易出错

 

变量值模型:变量是值的容器,只能改变值:内存中数据

变量引用模型:变量是值的命名引用,改变引用没有改变值

 

左表达式:计算出地址

右表达式:计算出

 

引用模型中需要对变量做解引用取得值

通常实现了隐式解引用来取得值

 

两种模型的区别

被引用的值在原位改变

引用相同值的不同对象

引用不同对象的值相同

 

效率

访问时的间接运算

内存效率:不变对象的多个  副本(本书中用副本表示具体的值)

 

名字——地址——值

 

Java内部类型:值模型

用户类型:引用模型

要求传递内部类型:自动装箱拆箱

 

语言正交性

各个特征正交,任意组合使用,组合中保持意义

 

要求表达式与要求语句的上下文

 

c允许表达式中出现赋值

赋值语句返回值(赋值成功?)

增加了正交性:表达式中运算对象的副作用——赋值,返回值——成功标志 

 

c与c++提供了到布尔类型自动强制:类型安全弱化

 

组合赋值:减少冗余地址计算

!注意表达式地址计算的副作用

 

增量减量运算符

重载后用于下标与指针地址运算

 

前缀:+=的语法糖——赋值的优先级高

 

后缀:减少临时变量

 

多路赋值元组解包

消除了函数的非正交性

 

6.1.3初始化

赋值语句:修改变量

初始化:指定初始值

 

初始值的用处:

子程序中:局部静态变量需要初始值,然后使用

静态分配的变量:在声明上下文中指定初始值,由编译器放入全局内存,避免了赋值开销

避免使用未初始化的值造成错误

 

对内置类型:系统提供相应字面量

更正交——复合类型,自定义类型:需要聚集值——结构化的值

js:对象字面量

 

初始化的时期

初始化只对静态分配的变量节约时间

堆栈中变量只能运行时初始化

 

未初始化问题对应另一种情况:破坏了原有值而之后未分配有效值

如:悬空引用

 

语言可在声明时赋予默认值:内存填零

 

动态检查未初始化:动态语义错误

语义检查的优势:区分未初始化值有效值(默认初始化值为有效值)

效率:需要维护更新每个变量的使用情况

 

java:定义性赋值

所有之前可达路径中都完成了变量的赋值(初始化)

 

自定义类型初始化与赋值

默认值初始化:可能在之后改变对象大小

有效值初始化:避免释放默认值分配的空间

 

6.1.4表达式中的顺序问题=运算符运算顺序+运算对象求值顺序

优先级与结合性定义了运算符的顺序

未定义运算对象的求值顺序(参数列表的求值顺序)

求值顺序的重要性:

副作用:引入歧义

代码改进:公共子表达式

 

java:

更多运行时开销(编译器试图减少)

更清晰的语义

更高的可靠性

 

方法调用使用动态约束,参数检查?

数组下标越界

类型崩溃?

其他语义错误的运行时检查

 

基于运算定律的表达式整理

精度变化溢出运算符副作用等不安全因素引入

 

6.1.5短路求值

表达式

不完全求值

新的求值顺序

代码改进与提高可读写(读到逻辑确定即可)

 

短路求值改变了布尔表达式语义(表达式:求出所有运算对象,按优先级执行运算符)

按位运算——非短路求值

 

用于避免下标越界,避免除0(放在布尔表达式前部)

 

注意布尔表达式产生的副作用

 

延迟,被动求值?

 

在用于确定控制流时,布尔表达式转化为跳转表

 

6.2结构化与非结构化的流程

6.2.1goto的结构化替代品

结构化程序设计:自顶向下设计(逐步精化),代码模块化(可见性控制:导入导出), 结构化类型(集合,指针数组),描述性的变量常量名

##函数参数与返回值:一种可见性控制?其他作用域内变量(全局,外层函数的局部变量:python显示导入)。

##其他可见性控制?各种导入导出动作的联系与差别

return:跳至子程序末尾

break:跳出单次循环

异常:特定外围上下文

 

回卷:修复运行栈上的子程序调用信息

包括:释放并跳出帧栈信息管理(恢复寄存器

 

错误与异常

多层返回假定了被调用方知道传递调用方信息,即返回合适的值

组件未能完成所执行功能的规范——退出至能恢复执行的地方。退出条件:异常

 

结构化的异常处理机制:在可能出现异常的地方放置处理器(一个代码块)

处理器完成修复工作

 

多层返回与异常的工作有很多相似性:

控制转移内层嵌套上下文——>特定外层上下文

运行栈回卷

区别:异常处理内层上下文无法完成工作的情况

 

简单异常处理:辅助变量

 

6.2.2继续

代码地址+引用环境

进入代码地址——恢复环境

一种抽象:可以继续的执行上下文

 

6.3顺序执行

语句顺序执行:也是一种优先级

 

复合语句:加了分隔符的语句列表

块:声明+复合语句

 

无副作用函数:幂等透明:同参数重复调用 返回相同值

 

Ada折中:函数修改全局变量与静态变量,不允许修改参数

 

6.4选择

6.4.1短路条件

else歧义消除:最近匹配

elif:保持平行结构

 

计算布尔表达式不是为了保存值,而是生成转跳码

未显式存入寄存器,而是隐式用于流程控制

机器提供条件分支指令

 

6.4.2case/ switch

整数表达式与编译时常数对比

生成转跳表

转跳表搜索:顺序,散列,折半

查找策略:取决于标号数目范围

处理未出现的标号  default

落入方式:连续落入,或直接中断

 

6.5迭代

迭代与递归使程序规模不局限于程序正文的线性长度

 

迭代:用循环实现,执行为了副作用——修改变量值

迭代次数的确定方式:枚举控制,逻辑控制:更明确的结构语义

 

6.5.1枚举控制

循环下标:初始化,范围,步进量

各分量是否要求编译时确定

是否可以在循环中修改

预先得到迭代次数,存于寄存器

下标的作用域

 

##为何要求编译时确定?效率?

 

引入循环头部——进一步结构化集中循环控制代码解耦合

是否限定循环体中修改循环变量

 

在集合中迭代

 

枚举类型:要求枚举(作为特定类型),允许枚举(支持枚举的类型)

 

6.5.2组合循环:循环嵌套

各层的作用域可见性

语言结构是否引入作用域,以及作用域间的可见性(特别的,全局变量)

 

6.5.3迭代器

实现迭代器对象接口

生成器:迭代器的更一般形式,将枚举回溯 组合

Python生成器——真迭代器

 

真迭代器:容器抽象提供 枚举自己元素的迭代器

#真迭代器:枚举值的独立上下文?

迭代器在前面结束的地方继续

像是控制进程:有自己的程序计数器,降低枚举元素的代码使用元素代码的耦合

 

Python中range迭代器是预定义的?

 

 命令式语言中

实现迭代器设计for循环的特殊形式

与一种在循环中枚举值的机制

这两个概念可以分开

 

C++,java提供了类似python的枚举控制循环

没有yield语句,没有用于枚举值独立的上下文(类似线程)

迭代器只是个对象,提供方法:用于初始化,生成下个下标值,检测结束

不同的调用之间,保持迭代器状态(作为数据成员)

 

真迭代器:显式的数据结构来维护中间状态

迭代器对象:中间状态维护在程序计数器与局部变量(构成可重新唤醒的执行上下文)?具体?

 

c++将迭代器看做指针,自增,间接引用,end:特殊迭代器

运算符重载变量值模型,显式废料收集容器声明泛型,针对特定元素实例化

 

scheme:循环体写成函数,循环下标作为参数函数作为参数传递为迭代器

 

6.5.5逻辑控制循环

何处检查结束条件

先检测,后检测,中间检测

 

6.6递归

允许函数调用自身

迭代与递归算法可以相互转换

迭代修改值

递归不修改值(值作为参数继续传递?)

 

6.6.1迭代与递归

尾递归:调用后没有其他计算函数不需要动态分配栈空间,重复使用当前计算空间

非尾递归——>转化为尾递归,图解?

构造尾递归树?

 

6.6.2应用序求值与正则序求值

应用序求值:调用前求值

正则序求值:实际需要值的时候求值(函数执行,计算参数)

参数在传递给调用函数时未完成求值

传递未求值实参——>实际需要值——>求值,运行函数

 

正则序求值:出现在短路求值按名调用参数

 

应用序求值:清晰高效

正则序求值:产生更快的代码,参数值不需要

 

惰性求值:惰性数据结构,用于组合搜索问题

 

函数声明,传参,可调用对象,调用操作

 

6.7非确定性

非确定性:随意选择顺序依然保持正确

形式化意义上的公平

 

编程约定模仿缺失的特征,获得等价的功能

 

posted on 2018-05-28 15:08  秦梦超  阅读(186)  评论(0编辑  收藏  举报

导航