值类别
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++ 文档 关于 值类别
|