变量 (程序设计)

程序设计中,变数(英语:Variablescalar)是指一个抽象的储存位址,它含有了被称为一个的某种已知或未知的资讯,并且配对了关联的符号名称。通常使用变数名称参照储存值;将名称和内容分开能让被使用的名称独立于所表示的精确讯息之外。电脑原始码中的识别字能在执行期间绑扎一个,且该变数的值可能在程式执行期间改变。 程序设计中的变数不一定能直接对应到数学中所谓的变数之概念。在程序设计中,变数的值不一定要为方程数学公式之一部分。程序设计中的变数可使用在一段可重复的程序:在一处赋值,然后使用于另一处,接著在一次赋值,且以相同方式再使用一次(见迭代)。程序设计中的变数通常会给定一个较长的名称,以描述其用途;数学中的变数通常较为简洁,只给定一、两个字母,以方便抄写及操作。

一个变数的储存位址可以被不同的识别字所参照,这种情况称之为别名。使用其中一个识别字为变数赋值,将会改变透过另一个识别字存取的值。

编译器必须将代表变数的名称替代成该数据所在的实际位址。变数的名称、类型及位址通常会维持固定,但该位址所储存之数据于程式执行期间则可能会改变。

用标识符引用变量

用标识符引用变量能对变量进行访问,从而读取变量的值,改变变量的值,或者改变变量的属性(如访问权限、状态锁定等)。

例如,一个变量用标识符total_count来引用,设定这个变量的值为1981。如果该变量同时也用标识符x 来引用,通过x将变量值改变为2010,那么读取total_count的值就是2010而不是1981。

如果某种编程语言只允许同一个变量用一个标识符引用,那么“该变量的名字”就是有意义的,否则我们称之为“该变量的名字之一”。例如,在前面的那个例子当中,total_count是这个变量的名字之一,而x是这个变量的另外一个名字。

对变量的操作

指令式编程语言中,变量的值通常能够随时访问或重新赋值。但在逻辑编程语言中,根据参数透明的需求,变量被绑定到表达式并且在它的整个生命周期中保持同一个值。在指令式编程语言中,同样的行为用常量来表达,它和通常的变量存在反差。

根据编程语言的类型系统的不同,变量可能只存储一种特定的数据类型(如整型字符串型)。而另外一种情况,变量的数据类型能根据当前赋值而改变,允许单个变量存储该编程语言支持的任何数据类型。

命名规范

数学当中的量不同,程序设计所用的变量和常量通常都采用多字符的名字,如count或者size。而单个字符的名字一般仅用于辅助性的变量,如ijk常作为数组索引的变量。

一些命名规范是作为语法在语言层面强制执行的。在大多数语言当中,变量名不能以数字开头,不能包含空格符。而标点符号是否允许存在在变量名当中就要视具体语言而定了。很多语言仅仅允许下划线_存在在变量名当中,而禁止其他所有的标点符号;而有些编程语言,特殊字符作为前缀或后缀添加在变量标识符当中来表明变量的类型。变量名的大小写敏感性也要视具体语言而定。大多数现代语言是大小写敏感的,一些较老的语言则不敏感。一些语言保留特定形式的变量名用来内部使用,在很多语言中,以两根下划线开头__的变量名常充当这种角色。

在语言语法基本的限制以外,进一步的命名风格规范也很有必要。在机器码层面,是不会使用变量名的,所以计算机并不关心是否采用了准确的名字。正因为如此,变量名完全是作为程序员的工具而存在,借助这个工具程序员能更容易的编写和理解程序。程序员通常创建编码规范,并且坚持这些规范,帮助对变量命名甚至提供精确的命名规划。较短的名字便于输入,但是描述能力较差;较长的名字使程序更容易读懂,变量的意图更容易理解。尽管如此,冗长的变量名也可能会导致更难理解的代码。

在源代码中

源代码中,变量名是将变量和内存地址绑定的一种方式。变量值以数据对象的形式存储在相应的地址内,这样该数据对象就能通过变量的名字进行访问和修改了。

在电子表格中

电子试算表中,一个单元格可能包含参考其他单元格的公式。这种被参考的单元格就是一种形式的变量,它的值就是被参考的单元格的值。

作用域和生存周期

变量的作用域表示变量在原程序的文本中能被使用的范围。变量的生存周期表示变量在程序运行过程中具有实际意义的值的时间范围。更通俗地说,变量的作用域是变量名字的性质,而变量的生存周期是变量本身的性质。

变量名字的作用域会影响它的生存周期。

作用域是变量语法方面的性质。多数语言对每一个变量(和其他名目实体)定义明确的作用域,这些作用域在同一个程序中可能不同。变量的作用域是指程序中的特定区域,在这些区域中,该变量的名字是有意义的并且变量是“可见的”。在进入作用域时,变量通常开始它的生命周期;而在离开作用域时,变量往往结束了它的生命周期。例如,某个变量的语法作用域仅在特定的语句块或者子程序中。只有在某个函数中能访问的变量则被称为局部变量,在程序的任何一个地方都能引用的变量被称为全局变量

生存周期,则是变量在运行时的性质。在运行时,每次变量与值的绑定都具有自己的生存周期。绑定的生存周期是程序执行过程中的一段时间,在这段时间内,变量始终被关联到相同的值或者内存位置。在闭包的情况中,运行中的程序可能进入和离开某个生存周期很多次。

在一些代码段中,在一个变量的作用域中可能未被赋值,或者它的值已经被销毁掉了。这类变量常被称为“生存周期外”或者“未绑定”。在很多语言中,试图使用未绑定的变量是一个错误。在其他语言中,这种行为会产生不可预期的结果,这样的变量可能被分配一个新的值。与之对照的是,一个变量绑定到一个超过他作用域的生存周期是被允许的,如Lisp的闭包和C语言的静态局部变量。当程序再次执行到变量的作用域时,变量能再次被使用,但还保持上一次的值。

为了提高空间效率,变量需要的存储空间可能要等到变量第一次使用时才申请,不再使用后就删除。为了避免浪费空间,如果变量声明了但不实际使用,编译器通常会向程序员发出警告。

使变量的作用域尽可能的小,被认为是一个好的编程方式,这样程序的不同部分就不会因为意外的改变对方的变量而互相影响了。实现上述目标的通常技术是让程序的不同部分使用不同名字空间,或者通过动态变量作用使用各自的私有变量

很多程序设计语言使用保留的值(如NULL)表示没有初始化的变量。

类型

静态类型语言中,如JavaML,每个都变量有一个类型,也就是说只有给定种类的值能存储到该变量中。一个基本类型的变量只能保存基本类型的值。一个类类型的变量能保存空值NULL,或者保存该类型或其子类型的对象。一个接口类型的变量能保存空值NULL,或者该接口的任何一个实现。一个数组类型能保存空值NULL或者一个数组。

动态类型语言中,如Python,是值,而不是变量来携带类型信息。在Common Lisp中,这两种情况同时存在:变量在编译时具有一个类型(如果没有声明,就假设这个类型为超类型T);值也有具有一个类型,该类型可以在运行时进行检查和识别。

变量的类型也允许在编译时多态决定。但是,这和面向对象的函数调用(在C++中称为虚函数)的多态不同。

变量常常保存简单的数据,如整数字符串。但有些程序设计语言允许变量同时表示多种数据类型。这些语言一般也允许函数参数多态,其函数对变量的操作可同时适用于多种数据类型。例如,函数length可以求一个列表的长度,如果length类型签名中包含一个类型变量,就可以实现参数多态。这样,求列表中的元素个数就与列表元素的类型无关了。

参数

函数的形式参数也被称为变量。例如如下的C++代码段:

int AddTwo(int x)
{
    return x + 2;
}

AddTwo(5);  // 结果为7

变量x是“形参”,因为当函数被调用时会被给定一个值。整数5是“实参”,它给x一个值。在多数语言中,函数参数具有局部的作用域。这里的变量x只能在AddTwo函数中有效(尽管如此,其他函数也可以使用自己的变量x)。

内存分配

变量的内存空间分配和它们值的表示方法是多种多样的,这种区别体现在语言之间,也体现在给定语言的内部使用上。很多语言都实现了局部变量的空间分配方式。局部变量保存在调用堆栈上,其生存周期维持在单个函数中,函数返回时这些内存会自动被回收。(更一般的讲,变量的名字是和一些特定的连续内存块的地址绑定,对变量的操作其实是对相应的内存块进行操作。)对于巨大或者编译时不知道大小的数据,更常用的方法是使用“引用”。 这时记录是值的地址而不是值本身,它们是从一种被称为“”的内存池中分配的。

绑定的变量具有值,一个抽象的值。在程序执行时,变量的值用计算机内存中存储的一些数据对象来表示。程序,或者说运行时环境,必须为每个数据对象设置内存。由于内存是有限的,为了安置每一个数据对象,当数据对象不再表示某个变量的值时,相应的内存会被回收并重新使用。

在堆中分配的对象必须被释放掉,特别是当对象不再被需要时。在具有垃圾回收机制的语言(如C#JavaLisp)中,当变量出了其作用域再也不能被引用时运行环境会自动地回收对象。在不具有垃圾回收机制的语言当中,如C语言,程序必须显式地分配内存,而且使用结束后还要释放内存,如果不这样做则会造成内存泄漏。在这种情况下,程序运行过程中会逐渐消耗,最终因为内存耗尽而崩溃。

当一个变量指向动态创建的数据结构时,可能其中一些部分只能通过变量间接地访问。在这种环境下,垃圾回收器(或者类似的语言特性)必须处理当变量回收时只有一部分内存能够重新回收的情况。

名字替换