变量、存储、绑定与作用域的深入解析
发布时间: 2025-08-17 01:20:54 阅读量: 1 订阅数: 4 

### 变量、存储、绑定与作用域的深入解析
在编程的世界里,变量、存储、绑定与作用域是至关重要的概念,它们影响着程序的运行和开发。下面将详细探讨这些概念。
#### 1. 表达式的副作用与命令表达式
在编程中,有些表达式会产生更新变量的副作用。我们先来看命令表达式。假设需要编写一个表达式来计算多项式 $c_nx^n + \cdots + c_2x^2 + c_1x + c_0$,给定 $x$、$n$ 和系数数组 $c_i$。解决方案可以是递归或迭代的。递归方案需要定义和调用递归函数,我们可能希望避免这种方式。而迭代方案面临的问题是,虽然迭代命令很常见,但迭代表达式并不常见,所以需要一种包含命令的表达式,即命令表达式。
例如,下面的假设命令表达式可以计算上述多项式:
```c
{
float p = c[n];
for (int i = n - 1; i >= 0; i--)
p = p * x + c[i];
p
}
```
这里假设使用类似 C 的符号。在详细说明声明并执行 “{” 后的命令后,“}” 之前的子表达式将被计算,以确定命令表达式产生的值。
在 ADA 中,函数过程的主体实际上就是一个命令表达式。这允许在计算函数结果时使用赋值和迭代,但也可能使函数调用产生副作用(可能是无意的)。实际上,任何类型的命令表达式都可能产生副作用。
例如,考虑 C 函数调用 “getchar(f)”,它从文件 f 中读取一个字符并返回该字符。这个函数调用对文件变量 f 有副作用。因此,以下代码:
```c
enum Gender {female, male};
Gender g;
if (getchar(f) == 'F')
g = female;
else if (getchar(f) == 'M')
g = male;
else ...
```
具有误导性,因为读取了两个不同的字符并分别与 'F' 和 'M' 进行比较。
副作用可能会给表达式计算带来不确定性。考虑任何形式为 “$E_1 \otimes E_2$” 的表达式,其中 $\otimes$ 是二元运算符,并且 $E_1$ 有影响 $E_2$ 计算的副作用,反之亦然;该表达式的计算结果取决于子表达式 $E_1$ 和 $E_2$ 的计算顺序。一些编程语言允许以任意顺序计算子表达式,从而产生不确定性。其他语言则通过坚持从左到右计算子表达式来避免这种不确定性。
总的来说,表达式中的副作用往往会使程序难以理解,无节制地使用副作用是不良的编程习惯。
#### 2. 面向表达式的语言
面向表达式的语言是一种命令式或面向对象的语言,其中表达式和命令没有区别。计算任何表达式都会产生一个值,并且可能也会有副作用。ALGOL68 和 ML(在一定程度上,类似 C 的语言)是面向表达式语言的例子。
这种设计的一个好处是避免表达式和命令之间的重复。面向表达式的语言不需要同时有函数过程和普通过程,也不需要同时有条件表达式和条件命令。
在面向表达式的语言中,赋值 “$V = E$” 可以定义为产生 $E$ 的值,并将该值存储在 $V$ 产生的变量中。由于赋值本身就是一个表达式,我们还可以写 “$V_1 = (V_2 = E)$”,换句话说,我们可以免费实现多重赋值。不过,一些面向表达式的语言对赋值的定义不同。例如,ML 赋值表达式实际上产生 0 元组 ()。
对于跳过或循环,没有明显的结果。通常,面向表达式的语言将跳过或循环定义为产生一个中性值,如 0 或 ()。
因此,面向表达式的语言通过消除表达式和命令之间的区别,实现了一定的简单性和一致性。但为什么传统的命令式语言仍然保留这种区别呢?这是一个编程风格的问题。虽然在传统语言中表达式可能有副作用,但程序员对副作用的使用在非正式场合是不被提倡的(实际上,ADA 的设计者最初曾试图禁止副作用,但未成功)。另一方面,面向表达式的语言积极鼓励使用副作用,导致了一种隐晦的编程风格,类似 C 的语言就是很好的例子:
```c
while ((ch = getchar(f)) != NUL)
putchar(ch);
```
#### 3. 编译器对变量的存储分配
每个变量在其生命周期内都占用存储空间。该存储空间必须在变量生命周期开始时分配,并且可以在变量生命周期结束时或之后的任何时间释放。每个变量占用的存储空间量取决于其类型,因此编译器必须知道每个变量的类型,因为编译器无法预测它将包含的实际值。在大多数编程语言中,每个变量的类型必须显式声明,或者编译器必须能够推断其类型。
##### 3.1 全局和局部变量的存储
全局变量的生命周期是程序的整个运行时间,因此可以为每个全局变量分配一个固定的存储空间。
局部变量的生命周期是声明该变量的块的一次激活。局部变量的生命周期是嵌套的,因此可以在栈中为它们分配存储空间。
例如,考虑以下情况:变量 $x_1$ 和 $x_2$ 是 main 的局部变量,它们的生命周期是程序的整个运行时间,即 $x_1$ 和 $x_2$ 在程序运行期间一直占用存储空间。变量 $y_1$ 和 $y_2$ 是过程 $P$ 的局部变量,它们的生命周期是 $P$ 的一次激活,因此在调用 $P$ 时必须为 $y_1$ 和 $y_2$ 分配存储空间,并在 $P$ 返回时释放。同样,在调用过程 $Q$ 时必须为局部变量 $z$ 分配存储空间,并在 $Q$ 返回时释放。总体效果是,分配给局部变量的存储空间以栈的方式扩展和收缩。
一般来说,局部变量的存储
0
0
相关推荐








