值类别
C 中的每个表达式(运算符及其参数、函数调用、常量、变量名等)都由两个独立的属性来描述:类型和值类别。
每个表达式都属于以下三种值类别之一:左值、非左值对象(右值)和函数指示符。
目录 |
[编辑] 左值表达式
左值表达式是任何对象类型(除了void
类型)的表达式,它可能指代一个对象(如果在求值时左值实际上没有指代一个对象,则行为未定义)。换句话说,左值表达式求值为对象标识。此值类别的名称(“左值”)是历史性的,它反映了左值表达式在 CPL 编程语言中作为赋值运算符左侧操作数的使用。
左值表达式可以在以下左值上下文中使用
如果左值表达式在除了sizeof
、_Alignof
或上面列出的运算符之外的任何上下文中使用,任何完整类型的非数组左值都会经历左值转换,这模拟了从对象位置加载值的内存操作。类似地,数组左值在除了sizeof
、_Alignof
、取地址运算符或从字符串字面量初始化数组之外的任何上下文中都会经历数组到指针转换。
const
/volatile
/restrict
-限定符和原子类型的语义仅适用于左值(左值转换会去除限定符并移除原子性)。
以下表达式是左值
- 标识符,包括函数命名参数,前提是它们被声明为指代对象(而不是函数或枚举常量)
- 字符串字面量
- (C99) 复合字面量
- 如果未加括号的表达式是左值,则加括号的表达式
- 如果成员访问(点)运算符的左侧参数是左值,则其结果
- 通过指针
->
运算符的成员访问结果 - 应用于对象指针的间接(一元
*
)运算符的结果 - 下标运算符(
[]
)的结果
[编辑] 可修改左值表达式
可修改左值是任何完整、非数组类型且未被const-限定的左值表达式,如果它是结构体/联合体,则递归地,其成员中没有const-限定的成员。
只有可修改左值表达式才能用作增量/减量运算符的参数,以及赋值和复合赋值运算符的左侧参数。
[编辑] 非左值对象表达式
被称为右值,非左值对象表达式是对象类型的表达式,它们不指代对象,而是指没有对象标识或存储位置的值。不能获取非左值对象表达式的地址。
以下表达式是非左值对象表达式
- 整型、字符和浮点常量
- 所有未指定返回左值的运算符,包括
- 任何函数调用表达式
- 任何类型转换表达式(注意:看起来相似的复合字面量是左值)
- 应用于非左值结构体/联合体的成员访问(点)运算符,f().x, (x,s1).a, (s1=s2).m
- 所有算术、关系、逻辑和位运算符的结果
- 增量和减量运算符的结果(注意:在 C++ 中,前缀形式是左值)
- 赋值运算符的结果(注意:在 C++ 中也是左值)
- 条件运算符(注意:在 C++ 中,如果第二个和第三个操作数都是相同类型的左值,则是左值)
- 逗号运算符(注意:在 C++ 中,如果第二个操作数是左值,则是左值)
- 取地址运算符,即使通过应用于一元
*
运算符的结果而被中和
作为一个特殊情况,void
类型的表达式被假定为非左值对象表达式,它们产生一个没有表示且不需要存储的值。
请注意,具有数组类型成员(可能嵌套)的结构体/联合体右值确实指代一个具有临时生命周期的对象。可以通过索引数组成员或通过数组成员的数组到指针转换获得的指针进行间接访问来访问此对象。
[编辑] 函数指示符表达式
函数指示符(由函数声明引入的标识符)是函数类型的表达式。当在除了取地址运算符、sizeof
和_Alignof
(后两个应用于函数时会产生编译错误)之外的任何上下文中使用时,函数指示符总是转换为非左值函数指针。请注意,函数调用运算符是为函数指针定义的,而不是为函数指示符本身定义的。
[编辑] 参考文献
- C17 标准 (ISO/IEC 9899:2018)
- 6.3.2.1 左值、数组和函数指示符 (p: 40)
- C11 标准 (ISO/IEC 9899:2011)
- 6.3.2.1 左值、数组和函数指示符 (p: 54-55)
- C99 标准 (ISO/IEC 9899:1999)
- 6.3.2.1 左值、数组和函数指示符 (p: 46)
- C89/C90 标准 (ISO/IEC 9899:1990)
- 3.2.2.1 左值和函数指示符
[编辑] 另请参阅
C++ 文档,关于值类别
|