成员访问运算符
成员访问运算符允许访问其操作数的成员。
运算符 | 运算符名称 | 示例 | 描述 |
---|---|---|---|
[] | 数组下标 | a[b] | 访问数组 a 的第 b 个元素 |
* | 指针解引用 | *a | 解引用指针 a 以访问它所指向的对象或函数 |
& | 取地址 | &a | 创建一个指向对象或函数 a 的指针 |
. | 成员访问 | a.b | 访问结构体或联合体 a 的成员 b |
-> | 通过指针访问成员 | a->b | 访问由 a 指向的结构体或联合体的成员 b |
内容 |
[编辑] 下标
数组下标表达式具有以下形式
指针表达式 [ 整数表达式 ] |
(1) | ||||||||
整数表达式 [ 指针表达式 ] |
(2) | ||||||||
其中
指针表达式 | - | 类型为指向完整对象的指针的 表达式 |
整数表达式 | - | 类型为整数的 表达式 |
下标运算符表达式是一个 左值表达式,其类型为 指针表达式 所指向的对象的类型。
根据定义,下标运算符 E1[E2] 与 *((E1)+(E2)) 完全相同。如果 指针表达式 是一个数组表达式,它将进行 左值到右值转换 并成为指向数组第一个元素的指针。
由于 指针和整数之间的加法 的定义,结果是数组中索引等于 整数表达式 结果的元素(或者,如果 指针表达式 指向某个数组的第 i 个元素,则结果的索引为 i 加上 整数表达式 的结果)。
注意:有关多维数组的详细信息,请参阅 数组。
#include <stdio.h> int main(void) { int a[3] = {1,2,3}; printf("%d %d\n", a[2], // n == 3 2[a]); // same, n == 3 a[2] = 7; // subscripts are lvalues int n[2][3] = {{1,2,3},{4,5,6}}; int (*p)[3] = &n[1]; // elements of n are arrays printf("%d %d %d\n", (*p)[0], p[0][1], p[0][2]); // access n[1][] via p int x = n[1][2]; // applying [] again to the array n[1] printf("%d\n", x); printf("%c %c\n", "abc"[2], 2["abc"]); // string literals are arrays too }
输出
3 3 4 5 6 6 c c
[编辑] 解引用
解引用 或 间接访问 表达式具有以下形式
* 指针表达式 |
|||||||||
其中
指针表达式 | - | 任何指针类型的 表达式 |
如果 指针表达式 是指向函数的指针,则解引用运算符的结果是该函数的函数设计器。
如果 指针表达式 是指向对象的指针,则结果是一个 左值表达式,它指定指向的对象。
解引用空指针、指向对象生命周期之外的对象的指针(悬空指针)、未对齐的指针或具有不确定值的指针是未定义的行为,除非解引用运算符被通过对其结果应用取地址运算符来抵消,如 &*E.
输出
*p = 1 *p = 7
[编辑] 取地址
取地址表达式具有以下形式
& 函数 |
(1) | ||||||||
& 左值表达式 |
(2) | ||||||||
& * 表达式 |
(3) | ||||||||
& 表达式 [ 表达式 ] |
(4) | ||||||||
&
和 *
相互抵消,两者都不会被评估&
和 []
中隐含的 *
相互抵消,只评估 []
中隐含的加法。其中
左值表达式 | - | 任何类型的 左值 表达式,它不是 位域 并且没有 寄存器 存储类 |
取地址运算符生成其操作数的 非左值 地址,适用于初始化指向操作数类型的指针。如果操作数是函数设计器 (1),则结果是指向函数的指针。如果操作数是对象 (2),则结果是指向对象的指针。
如果操作数是解引用运算符,则不会执行任何操作(因此可以将 &* 应用于空指针),只是结果不是左值。
如果操作数是数组索引表达式,则除了数组到指针转换和加法之外,不会执行任何操作,因此 &a[N] 对大小为 N 的数组有效(获得指向最后一个元素之后的指针是可以的,但解引用它是不行的,但在本表达式中解引用被抵消了)。
int f(char c) { return c;} int main(void) { int n = 1; int *p = &n; // address of object n int (*fp)(char) = &f; // address of function f int a[3] = {1,2,3}; int *beg=a, *end=&a[3]; // same as end = a+3 }
[编辑] 成员访问
成员访问表达式具有以下形式
表达式 . 成员名称 |
|||||||||
其中
表达式 | - | 类型为 结构体 或 联合体 的表达式 |
成员名称 | - | 一个 标识符,它命名由 表达式 指定的结构体或联合体的成员 |
成员访问表达式指定由其左操作数指定的 结构体 或 联合体 的命名成员。它具有与其左操作数相同的 值类别。
如果左操作数是 const 或 volatile 限定的,则结果也是限定的。如果左操作数是 原子,则行为是未定义的。
注意:除了命名结构体或联合体类型对象的标识符之外,以下表达式可能具有结构体或联合体类型:赋值、函数调用、逗号运算符、条件运算符 和 复合字面量。
#include <stdio.h> struct s {int x;}; struct s f(void) { return (struct s){1}; } int main(void) { struct s s; s.x = 1; // ok, changes the member of s int n = f().x; // f() is an expression of type struct s // f().x = 1; // Error: this member access expression is not an lvalue const struct s sc; // sc.x = 3; // Error: sc.x is const, can't be assigned union { int x; double d; } u = {1}; u.d = 0.1; // changes the active member of the union }
[编辑] 通过指针访问成员
成员访问表达式具有以下形式
表达式 -> 成员名称 |
|||||||||
其中
表达式 | - | 类型为指向 结构体 或 联合体 的 指针 的表达式 |
成员名称 | - | 一个 标识符,它命名由 表达式 指向的结构体或联合体的成员 |
通过指针访问成员表达式指定由其左操作数指向的 结构体 或 联合体 类型的命名成员。它的值类别始终是 左值
如果左操作数指向的类型是 const 或 volatile 限定的,则结果也是限定的。如果左操作数指向的类型是 原子,则行为是未定义的。
#include <stdio.h> struct s {int x;}; int main(void) { struct s s={1}, *p = &s; p->x = 7; // changes the value of s.x through the pointer printf("%d\n", p->x); // prints 7 }
[编辑] 缺陷报告
以下更改行为的缺陷报告被追溯应用于先前发布的 C 标准。
DR | 应用于 | 已发布的行为 | 正确的行为 |
---|---|---|---|
DR 076 | C89 | 不必要的间接访问不能被 & 抵消 |
已更改为可抵消 |
[编辑] 参考文献
- C17 标准(ISO/IEC 9899:2018)
- 6.5.2.1 数组下标(第 57-58 页)
- 6.5.2.3 结构体和联合体成员(第 58-59 页)
- 6.5.3.2 地址和间接运算符 (p: 59-61)
- C11 标准 (ISO/IEC 9899:2011)
- 6.5.2.1 数组下标 (p: 80)
- 6.5.2.3 结构体和联合体成员 (p: 82-84)
- 6.5.3.2 地址和间接运算符 (p: 88-89)
- C99 标准 (ISO/IEC 9899:1999)
- 6.5.2.1 数组下标 (p: 70)
- 6.5.2.3 结构体和联合体成员 (p: 72-74)
- 6.5.3.2 地址和间接运算符 (p: 78-79)
- C89/C90 标准 (ISO/IEC 9899:1990)
- 3.3.2.1 数组下标
- 3.3.2.3 结构体和联合体成员
- 3.3.3.2 地址和间接运算符
[edit] 参见
常用运算符 | ||||||
---|---|---|---|---|---|---|
赋值 | 自增 自减 |
算术 | 逻辑 | 比较 | 成员 访问 |
其他 |
a = b |
++a |
+a |
!a |
a == b |
a[b] |
a(...) |
C++ 文档 for 成员访问运算符
|