对应龙书中1.6节
一、静态与动态特性(Static vs Dynamic)
1.1 基本定义
-
静态问题(Static Issues) :在编译时就能确定和处理的问题
-
动态问题(Dynamic Issues) :需要在运行时才能解决的问题
1.2 静态特性示例
cpp
// C/Java中的声明作用域
int x; // 编译时确定作用域
static class MyClass { // 静态类对象的位置在编译时确定
// ...
}
1.3 动态特性示例
cpp
void function() {
int local_var; // 局部变量的位置在运行时确定(栈分配)
}
1.4 关键区别
特性类型 | 处理时机 | 示例 | 影响 |
---|---|---|---|
静态 | 编译时 | 作用域、静态类型检查 | 编译效率、错误检测 |
动态 | 运行时 | 局部变量分配、多态 | 运行灵活性、性能开销 |
二、环境与状态(Environments and States)
2.1 两层映射模型

上图展示了名字到值的两阶段映射
2.2 环境(Environment)
-
功能 :将名字(Names) 映射到存储位置(Locations)
-
性质:相对静态,在编译时部分确定
-
示例:变量名到内存地址的映射
2.3 状态(State)
-
功能 :将存储位置(Locations) 映射到值(Values)
-
性质:高度动态,在运行时不断变化
-
示例:内存地址存储的实际数据值
2.4 实际意义
cpp
int x = 10; // 环境:x → 内存地址0x1000
// 状态:0x1000 → 值10
x = 20; // 状态变化:0x1000 → 值20(环境不变)
三、静态作用域与块结构(Static Scopes/Block Structures)
3.1 静态作用域规则

3.2 最近嵌套规则(Most Closely Nested Rule)
-
名字查找顺序:从最内层块开始,逐层向外
-
遮蔽(Shadowing):内层声明遮蔽外层同名声明
-
作用域确定:在编译时通过程序结构确定
3.3 块结构特性
-
嵌套性:块可以嵌套在其他块中
-
局部性:块内声明只在块内有效
-
确定性:作用域在编译时完全确定
四、显式访问控制(Explicit Access Control)
4.1 访问修饰符
-
Public:公开访问,任何代码都可访问
-
Private:私有访问,只在类内部可访问
-
Protected:保护访问,类及其子类可访问
4.2 封装性设计
cpp
class MyClass {
public int publicVar; // 任何地方可访问
private int privateVar; // 仅本类内可访问
protected int protVar; // 本类及子类可访问
}
4.3 设计目标
-
信息隐藏:隐藏实现细节
-
接口隔离:明确公开的API
-
维护性:减少外部依赖带来的影响
五、动态作用域(Dynamic Scope)
5.1 定义规则
"A use of a name x refers to the declaration of x in the most recently called procedure with such a declaration."
最近调用原则 :名字x引用的是最近调用的包含x声明的过程中的声明。
5.2 与静态作用域对比
cpp
// 静态作用域示例
int x = 1;
void foo() { printf("%d", x); } // 总是输出1
// 动态作用域伪代码
int x = 1;
void bar() { int x = 2; foo(); } // 如果动态作用域,foo()输出2
5.3 实际应用场景
-
宏扩展(Macro Expansion)
-
面向对象编程中的方法解析
-
某些脚本语言(如早期LISP)
5.4 动态作用域的问题
-
不可预测性:行为依赖于运行时调用序列
-
调试困难:相同代码在不同上下文行为不同
-
维护复杂:难以理解程序逻辑
六、参数传递机制(Parameter Passing Mechanisms)
6.1 传值调用(Call by Value)
cpp
void f(int x) {
x = x + 1; // 修改形参x
print(x); // 输出增加后的值
}
int main() {
int y = 1;
f(y + 1); // 传递表达式y+1的值
// y的值不变,仍为1
}
特点:
-
传递实际参数的副本
-
函数内修改不影响原始变量
-
适用于基本数据类型
6.2 传引用调用(Call by Reference)
cpp
void swap(int &x, int &y) { // C++引用参数
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 1, b = 2;
swap(a, b); // a和b的值被交换
}
应用场景:
-
数组传递
-
指针参数
-
类对象传递(大多数面向对象语言)
特点:
-
传递实际参数的引用
-
函数内修改影响原始变量
-
适用于需要修改参数值的场景
6.3 传名调用(Call by Name)
cpp
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 1, y = 2;
int z = MAX(++x, y); // 展开为: ((++x) > (y) ? (++x) : (y))
// x可能被多次求值(这里x变为3)
}
特点:
-
类似宏替换,参数表达式在调用点展开
-
可能多次求值同一表达式
-
现代语言较少使用(Algol 60特性)
七、别名问题(Aliasing)
7.1 别名定义
"An interesting consequence of call-by-reference parameter passing. It is possible that two formal parameters can refer to the same location."
别名:多个名字指向同一个内存位置。
典型示例
cpp
void q(int x[], int y[]) {
x[10] = 2; // 修改x
// 如果x和y是同一数组,y[10]也变为2
}
void p() {
int a[100];
q(a, a); // 传递同一数组的两个别名
}
7.2 别名产生场景
-
传引用调用:多个参数指向同一对象
-
指针别名:不同指针指向同一内存
-
数组重叠:数组切片或子数组
7.3 别名的问题
-
优化障碍:编译器难以确定内存独立性
-
正确性风险:意外修改共享数据
-
调试困难:难以追踪数据流
解决方案
cpp
// 使用const限制修改
void q(const int x[], int y[]) {
// x[]只读,避免意外修改
y[10] = 2;
}
// 使用restrict关键字(C99)
void q(int *restrict x, int *restrict y) {
// 向编译器保证x和y不重叠
x[10] = 2;
}
八、核心概念总结
静态vs动态的权衡
方面 | 静态优势 | 动态优势 |
---|---|---|
性能 | 编译时优化 | 运行时灵活性 |
错误检测 | 早期发现 | 适应性强 |
确定性 | 行为可预测 | 支持多态 |
作用域规则演进
动态作用域(早期语言) → 静态作用域(现代语言) → 显式访问控制(面向对象)
参数传递发展
传名调用(Algol) → 传值调用(函数式) → 传引用调用(面向对象)
现代语言设计趋势
-
默认安全:传值为主,显式传引用
-
静态检查:尽可能在编译时发现问题
-
明确语义:减少隐式行为和别名
-
封装性:通过访问控制管理复杂性
这些基础概念为理解编程语言设计、编译器实现和程序正确性提供了理论基础,是计算机科学教育的核心组成部分。