值 (计算机科学)

表达式在计算机科学无法进一步评估

计算机科学中,(英语:Value)是一无法进一步求值表达式[1]例如,表达式“1 + 2”不是一个值,因为它可以被化简为表达式“3”。表达式“3”不能够继续化简,因此它是一个值。表达式既有类型(type)属性,又有值分类(value categories)属性。两种属性彼此独立。[2]也就是说,对每一种类型的表达式,都有各种值分类。

大多数编程语言支持几种常见的值。

赋值:左值和右值

一些语言使用左值l-value)和右值r-value)的概念。左值具有确定的、可以被获得的内存地址。这意味着左值可以是变量,也可以是对指向特定内存地址的指针解引用(dereference)的结果。例如C语言的表达式(4 + 9),在执行时,计算机生成一个整数值13,但因为程序没有明确指定这个13如何在计算机中存储,所以这个表达式产生一个右值。另一方面,如果一个C程序声明了一个变量x并将x赋值为13,那么表达式(x)的值是13,并且是一个左值。

在C语言中,术语“左值”最初表示可以被赋值(即位于赋值运算符左侧)的对象,但由于“const”被加入到语言中,这类对象现在被称作“可更改的左值”。C++中,左值和右值是表达式的分类,一个表达式必然是左值或右值之一。C++11把上述左值性(lvalueness)扩充为更复杂的值类别(value category),包含左值(lvalue),纯右值(prvalue)和临终值(xvalue)三个基本分类(fundamental classification),一个表达式必然是三者之一。

左值和右值的概念最早由CPL程序语言的一篇论文引入。[3] 这时定义的左值是可以放在赋值号左侧赋予新值的对象;[4]右值是可以放在赋值号右侧读出其值的对象。在B语言中,左值和右值作为文法的元素被明确。在C语言中,文法不再出现左值和右值的区别,左值的概念被保留在语义规则中,而“右值”在ISO C中被视为“值”的同义词。

C++语言引入了const限定的对象。const对象可以取地址,但是不能被赋值;而一些右值对象也可以出现在赋值号的左边被赋值。[5]因此,截至C++03标准,把具有标识(identity)的表达式规定为左值,不具有标识的表达式规定为右值。因而,名字、指针、引用等是左值,是命名对象,具有确定的内存地址;字面量、临时对象等为右值,右值仅在创建它的表达式中可以被访问。函数名字是左值(在C语言中规定它既不是左值也不是右值),数组名是常量左值,但是在大多数表达式中函数名字与数组名字自动隐式转换为右值。右值的生存期短暂,所以需要用左值去捕捉右值。把右值复制(copy)到左值上是常见操作。

C++11标准引入了右值引用数据类型与移动语义,因而左值与右值的定义发生了很大变化。右值引用变量绑定到右值上,延长了右值对应的临时对象的生存期。移动语义把临时对象的内容移动(move)到左值对象上。因而在C++11,对于值的分类,要考虑标识(identity)与可移动性(movability),二者的组合产生了五种分类:

  • 基础值类型
    • 左值lvalue:可以用取地址运算符&获取地址的表达式。也可定义为非临时对象或非成员函数。具有标识,但不可移动。这也是C++03的经典左值。可用于初始化左值引用。可以有不完备类型(incomplete type)。包括:
      • 作用域中的变量名与函数名,不论其类型。因此,具名的右值引用,即具有右值引用类型的变量,也是左值表达式,这符合一般规律,不是特例。
      • 函数调用表达式或重载运算符表达式,如果其返回类型为左值引用或者是到函数类型的右值引用。 [6]
      • 内置的先增(前缀++)、先减(前缀--)、解引用(dereference)、赋值、复合赋值、下标(除了数组临终值)、成员访问(除了临终值的非静态非引用成员、成员枚举值、非静态成员函数),通过数据成员指针的访问且左端操作数为左值、逗号运算符且右端的操作数为左值、三元条件运算符(ternary conditional)且第二与第三操作数为左值。
      • 到左值引用类型的类型转换表达式。
      • 字符串字面量(string literal)
      • 类型转换表达式,转换为到函数的右值引用
    • 临终值xvalue(expiring value):具有标识,并且可以移动。对应的对象接近生存期结束,但其内容尚未被移走。可以多态;非类对象可以cv限定。包括:
      • 函数调用或重载的运算符表达式,如果返回类型是到对象的右值引用[6]
      • 类型转换表达式,转换为右值引用,如static_cast<T&&>(val)或(T&&)val
      • 访问xvalue的非静态类成员。
      • 指向数据成员的指针表达式,第一操作数是xvalue
    • 纯右值prvalue:不具有标识,但可以移动。对应临时对象或不对应任何对象的值。纯右值不能是多态的;临时对象的动态类型是表达式类型;非类且非数组的纯右值不能是const限定的;不能有不完备类型(除了void)。包括:
      • 字面量(除了字符串字面量)。
      • 函数调用或重载的运算符表达式,如果返回类型不是引用。[6]
      • 内置后增、后减、算术与逻辑运算符、比较运算符、取地址运算符、访问成员枚举值、访问非静态成员函数、访问右值的非静态非引用数据成员、访问右值的数据成员指针或非静态函数成员指针、逗号运算符且右端操作数为右值、三元条件运算符且第二或第三操作数不是左值。
      • 类型转换表达式,转换为非引用类型。
      • Lambda表达式
  • 广义左值glvalue:具有标识。包括左值与临终值。可以多态、动态类型。
  • 右值rvalue:可以移动。包括濒死值与纯右值。不能通过&运算符取地址。

C++的非静态成员函数调用表达式(obj.func与ptr->func),非静态成员函数指针调用表达式(obj.*mfp与ptr->*mfp)被当作纯右值,但是不能用于初始化引用,不能做函数实参,仅仅能用作函数调用表达式左边的操作数,如(pobj->*ptr)(args)。

返回void的函数调用表达式、到void的类型转换表达式、throw表达式被当作纯右值。但是不能用于初始化引用,不能做函数实参。可用于某些上下文环境中(如单独作为一行语句、逗号操作符的左端表达式等),或返回void的函数的return语句中。此外,throw表达式可用作三元条件操作符的第二或第三操作数。

位段(bit field)表达式是左值,但不能用&运算符取地址,不能绑定到非常量左值引用。常量左值引用可以用位域左值初始化,但实际上是另行分配绑定了一个对象。

汇编语言

值可以是一个给定的数据类型,例如一个字符串,一个数字,一个单一的字母等几乎任何类型的数据。

有些处理器支持多种尺寸的立即数,例如8位或16位,每一种指令形式采用独特的操作码和助记符。如果一个程序员提供的数据值不适合,汇编器将会出现“超出范围”的错误消息。大多数汇编器允许一个立即数被表示为ASCII十进制十六进制八进制二进制数据。因此,ASCII字符'A'和65、0x41是一样的。字符串的字节序在不同处理器之间可能不同,取决于汇编器和计算机体系结构。

参考资料

  1. ^ Mitchell 1996,第92页.
  2. ^ "Value categories",in cppreference.com. [2014-07-27]. (原始内容存档于2020-11-08). 
  3. ^ [D.W., Barron, J.N., Buxton, D.F., Hartley, E. Nixon, C. Strachey: The main features of CPL, published in The Computer Journal (1963) 6 (2): 134-143. fulltext: http://comjnl.oxfordjournals.org/content/6/2/134.full.pdf+html页面存档备份,存于互联网档案馆)]
  4. ^ “Anything that can appear on the left-hand side of an assignment expression is an lvalue.”
  5. ^ 例如,右值对象的数据类型重定义了赋值运算符。
  6. ^ 6.0 6.1 6.2 《C++语言标准》5.2.2.10:A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.

外部链接