Namespaces
Variants

Pointer declaration

From cppreference.net

指针是一种对象类型,它引用函数或其他类型的对象,并可能添加限定符。指针也可能不引用任何对象,这种情况由特殊的空指针值表示。

目录

语法

声明语法 中,指针声明的 类型说明符 序列指定了被指向的类型(可以是函数或对象类型,也可以是不完整类型),而 声明符 具有以下形式:

* 属性说明符序列  (可选) 限定符  (可选) 声明符

其中 declarator 可以是命名所声明指针的标识符,包括另一个指针声明符(这将表示指向指针的指针):

float *p, **pp; // p 是指向 float 的指针
                // pp 是指向 float 指针的指针
int (*fp)(int); // fp 是指向函数类型 int(int) 的指针

出现在 * 和标识符(或其他嵌套声明符)之间的 限定符 用于限定正在声明的指针类型:

int n;
const int * pc = &n; // pc 是指向 const int 的非 const 指针
// *pc = 2; // 错误:不经过强制转换无法通过 pc 修改 n
pc = NULL; // 正确:pc 本身可以被修改
int * const cp = &n; // cp 是指向非 const int 的 const 指针
*cp = 2; // 允许通过 cp 修改 n
// cp = NULL; // 错误:cp 本身不可被修改
int * const * pcp = &cp; // 指向非 const int 的 const 指针的非 const 指针

attr-spec-seq (C23) 是一个可选的 属性 列表,应用于所声明的指针。

说明

指针用于实现间接访问,这是一种普遍存在的编程技术;它们可用于实现按引用传递语义、访问具有 动态存储期的对象、实现"可选"类型(通过空指针值)、结构体间的聚合关系、回调函数(通过函数指针)、泛型接口(通过void指针)等诸多功能。

指向对象的指针

指向对象的指针可以用 取址运算符 应用于对象类型表达式(可能不完整)的结果进行初始化:

int n;
int *np = &n; // 指向int的指针
int *const *npp = &np; // 指向常量指针的非const指针,该常量指针指向非常量int
int a[2];
int (*ap)[2] = &a; // 指向int数组的指针
struct S { int n; } s = {1}
int* sp = &s.n; // 指向s成员int的指针

指针可以作为 解引用运算符 (一元 * )的操作数出现,该运算符返回 左值 标识被指向对象:

int n;
int* p = &n; // 指针 p 指向 n
*p = 7; // 将 7 存储到 n 中
printf("%d\n", *p); // 左值到右值转换从 n 中读取值

指向 结构体 联合体 类型对象的指针也可以作为 通过指针访问成员 运算符 -> 的左操作数出现。

由于 数组到指针 的隐式转换,数组首元素的指针可以通过数组类型的表达式进行初始化:

int a[2];
int *p = a; // 指向 a[0] 的指针
int b[3][3];
int (*row)[3] = b; // 指向 b[0] 的指针

某些 加法、减法 复合赋值 递增和递减 运算符被定义为用于指向数组元素的指针。

比较运算符 在以下情况下为对象指针定义:表示相同地址的两个指针比较结果相等,两个空指针值比较结果相等,指向同一数组元素的指针比较结果与这些元素的数组索引顺序一致,以及指向结构体成员的指针按照这些成员的声明顺序进行比较。

许多实现还提供随机来源指针的 严格全序关系 ,例如当它们被实现为连续("平坦")虚拟地址空间中的地址时。

指向函数的指针

指向函数的指针可以用函数的地址进行初始化。由于 函数到指针转换 规则,取址运算符是可选的:

void f(int);
void (*pf1)(int) = &f;
void (*pf2)(int) = f; // 与 &f 相同

与函数不同,函数指针是对象,因此可以存储在数组中、被复制、被赋值、作为参数传递给其他函数等。

指向函数的指针可用于 函数调用运算符 的左侧:这将调用被指向的函数:

#include <stdio.h>
int f(int n)
{
    printf("%d\n", n);
    return n * n;
}
int main(void)
{
    int (*p)(int) = f;
    int x = p(7);
}

解引用函数指针会产生指向函数的函数指示符:

int f();
int (*p)() = f;    // 指针 p 指向函数 f
(*p)(); // 通过函数指示符调用函数 f
p();    // 直接通过指针调用函数 f

相等比较运算符 为指向函数的指针定义(若指向同一函数则比较结果为相等)。

由于 函数类型的兼容性 会忽略函数参数的顶层限定符,因此仅参数顶层限定符不同的函数指针可以互换:

int f(int), fc(const int);
int (*pc)(const int) = f; // 正确
int (*p)(int) = fc;       // 正确
pc = p;                   // 正确

指向 void 的指针

指向任何类型对象的指针都可以 隐式转换 为指向 void 的指针(可选地带有 const volatile 限定符),反之亦然:

int n=1, *p=&n;
void* pv = p; // int* 转换为 void*
int* p2 = pv; // void* 转换为 int*
printf("%d\n", *p2); // 输出 1

void指针用于传递未知类型的对象,这在泛型接口中很常见: malloc 返回 void * qsort 要求用户提供的回调函数接受两个 const void * 参数。 pthread_create 要求用户提供的回调函数接受并返回 void * 。在所有情况下,调用方都有责任在使用前将指针转换为正确的类型。

空指针

每种类型的指针都有一个特殊值,称为该类型的 空指针值 。值为空的指针不指向任何对象或函数(解引用空指针是未定义行为),并且与所有同为 值的同类型指针比较结果相等。

要将指针初始化为空或将空值赋给现有指针,可使用空指针常量( NULL ,或任何其他值为零的整型常量)。 静态初始化 同样会将指针初始化为其空值。

空指针可以表示对象不存在,也可用于指示其他类型的错误条件。通常,接收指针参数的函数几乎总是需要检查该值是否为空,并以不同方式处理这种情况(例如,当传入空指针时, free 不会执行任何操作)。

注释

尽管任何指向对象的指针 都可以被转换 为指向不同类型对象的指针,但解引用与对象声明类型不符的指针几乎总是未定义行为。详见 严格别名规则

可以向通过指针访问对象的函数指示这些指针不会发生别名引用。详见 restrict 说明。

(since C99)

数组类型的左值表达式,在大多数上下文中使用时,会经历向数组首元素指针的 隐式转换 。详见 数组 章节说明。

char *str = "abc"; // "abc" 是一个 char[4] 数组,str 是指向 'a' 的指针

指向 char 的指针常被 用于表示字符串 。要表示有效的字节字符串,指针必须指向作为 char 数组元素的 char,并且在大于或等于该指针所引用元素索引的某个位置,必须存在值为零的 char。

参考文献

  • C23 标准 (ISO/IEC 9899:2024):
  • 6.7.6.1 指针声明符 (p: TBD)
  • C17 标准 (ISO/IEC 9899:2018):
  • 6.7.6.1 指针声明符 (p: 93-94)
  • C11 标准 (ISO/IEC 9899:2011):
  • 6.7.6.1 指针声明符 (p: 130)
  • C99标准(ISO/IEC 9899:1999):
  • 6.7.5.1 指针声明符(第115-116页)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 3.5.4.1 指针声明符

参见

C++ 文档 关于 指针声明