成员访问运算符

来自cppreference.com
< c‎ | language

成员访问运算符允许访问其运算数的成员。

运算符 运算符名 示例 描述
[] 数组下标 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]); // 同上, n == 3
    a[2] = 7; // 下标表达式是左值
 
    int n[2][3] = {1,2,3,4,5,6};
    int (*p)[3] = &n[1]; // n 的元素为数组
    int x = n[1][2]; // 再次应用 [] 到数组 n[1]
 
    printf("%c %c\n", "abc"[2], 2["abc"]); // 字符串字面量亦是数组
}

输出:

3 3
c c

解引用

解引用间接表达式拥有形式

* 指针表达式

其中

指针表达式 - 任何指针类型的表达式

指针表达式 为指向函数指针,则解引用运算符的结果为该函数的函数指代器。

指针表达式 为指向对象指针,则结果为指代被指向对象的左值表达式

解引用空指针、指向在生存期外的对象的指针(悬垂指针)、错误对齐的指针或拥有不确定值的指针是未定义行为,除非如在 &*E 中一般,通过应用取址运算符到解引用运算符的结果,将它取消。

#include <stdio.h>
int main(void)
{
    int n = 1;
    int* p = &n;
    printf("*p = %d\n", *p); // *p 的值即为存储于 n 的值
    *p = 7; // *p 是左值
    printf("*p = %d\n", *p);
}

输出:

*p = 1
*p = 7

取址

取址运算符拥有形式

& 函数 (1)
& 左值表达式 (2)
& * 表达式 (3)
& 表达式 [ 表达式 ] (4)
1) 函数地址
2) 对象地址
3) 特殊情况: &* 彼此抵消,均不求值
4) 特殊情况: &[] 中隐含的 * 抵消,只求值 [] 中隐含的加法

其中

左值表达式 - 拥有任何非位域类型且无 register 存储类的左值表达式

取址运算符产生其运算数的非左值地址,适于初始化指向运算数类型的指针。若运算数为函数指代器 ((1)) ,则结果为指向函数指针。若运算数为对象 ((2)) ,则结果为指向对象指针。

若运算数为解引用运算符,则不进行动作(故可以应用 &* 到空指针),除了结果是非左值。

若运算数是数组下标表达式,则不进行数组到指针转换和加法外的动作,故 &a[N] 对大小为 N 的数组合法(可以获得尾后一位置指针,不能解引用它,但此表达式中解引用被取消)。

int f(char c) { return c;}
int main(void)
{
   int n = 1;
   int *p = &n; // 对象 n 的地址
   int (*fp)(char) = &f; // 函数 f 的地址
   int a[3] = {1,2,3};
   int *beg=a, *end=&a[3]; // 同 end = n+3
}

成员访问

成员访问运算符拥有形式

表达式 . 成员名

其中

表达式 - 结构体联合体类型表达式
成员名 - 指名 表达式 所指带的结构体或联合体的成员的标识符

成员访问表达式指代其左运算数所指代的 structunion 的具名成员。它拥有与其左运算数相同的值类别

若左运算为 constvolatile 限定,则结果亦有限定。若左运算数为原子对象,则行为未定义。

注意:除了指名结构体或联合体的标识符,下列表达式亦可拥有结构体或联合体类型:赋值函数调用逗号运算符条件运算符复合字面量

#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 :更改 s 的成员
    int n = f().x; // f() 为 struct s 类型表达式
//  f().x = 1; // 错误:此成员访问表达式非左值
 
    const struct s sc;
//  sc.x = 3;  // 错误: sc.x 为 const ,不能被赋值
 
    union { int x; double d; } u = {1};
    u.d = 0.1; // 更改 union 的活跃成员
}

通过指针的成员访问

成员访问表达式拥有形式

表达式 -> 成员名

其中

表达式 - 指向结构体联合体指针类型表达式
成员名 - 指名 表达式 所指向的结构体或联合体的成员的标识符

通过指针的成员访问表达式指代其左运算数所指向的 structunion 类型的具名成员。其值类别始终为左值

若左运算数所指向的类型有 constvolatile 限定,则结果亦有限定。若左运算数所指向的类型为原子类型,则行为未定义。

#include <stdio.h>
struct s {int x;};
int main(void)
{
    struct s s={1}, *p = &s;
    p->x = 7; // 通过指针更改 s.x 的值
    printf("%d\n", p->x); // 打印 7
}

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C 标准。

缺陷报告 应用于 出版时的行为 正确行为
DR 076 C89 & 无法取消不必要的间接 使之能取消

引用

  • C17 标准(ISO/IEC 9899:2018):
  • 6.5.2.1 Array subscripting (第 57-58 页)
  • 6.5.2.3 Structure and union members (第 58-59 页)
  • 6.5.3.2 Address and indirection operators (第 59-61 页)
  • C11 标准(ISO/IEC 9899:2011):
  • 6.5.2.1 Array subscripting (第 80 页)
  • 6.5.2.3 Structure and union members (第 82-84 页)
  • 6.5.3.2 Address and indirection operators (第 88-89 页)
  • C99 标准(ISO/IEC 9899:1999):
  • 6.5.2.1 Array subscripting (第 70 页)
  • 6.5.2.3 Structure and union members (第 72-74 页)
  • 6.5.3.2 Address and indirection operators (第 78-79 页)
  • C89/C90 标准(ISO/IEC 9899:1990):
  • 3.3.2.1 Array subscripting
  • 3.3.2.3 Structure and union members
  • 3.3.3.2 Address and indirection operators

参阅

常用运算符
赋值 自增
自减
算术 逻辑 比较 成员
访问
其他

a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b

a[b]
*a
&a
a->b
a.b

a(...)
a, b
(type) a
? :
sizeof
_Alignof
(C11 起)