Namespaces
Variants

Pointer declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

声明一个指针或指向成员类型的指针变量。

目录

语法

指针声明是任何 声明符 具有以下形式的简单声明

* 属性  (可选) cv限定符  (可选) 声明符 (1)
嵌套名称说明符 * 属性  (可选) cv限定符  (可选) 声明符 (2)
1) 指针声明符 :声明 S * D ; D 声明为指向由 声明说明符序列 S 所确定类型的指针。
2) 指向成员的指针声明符 :声明 S C :: * D ; D 声明为指向 C 的非静态成员的指针,其类型由声明说明符序列 S 确定。
nested-name-specifier - 一个 名称和作用域解析运算符 :: 的序列
attr - (C++11 起) 一组 属性
cv - 适用于正在声明的指针的 const/volatile 限定(不适用于被指向类型,其限定是声明说明符序列的一部分)
declarator - 任意 声明符

不存在指向 引用 的指针,也不存在指向 位域 的指针。 通常而言,未加限定的"指针"表述不包含指向(非静态)成员的指针。

指针

每个指针类型的值均为以下之一:

  • 一个 指向对象或函数的指针 (此时称该指针 指向 该对象或函数),或
  • 一个 指向对象末尾之后的指针 ,或
  • 该类型的 空指针值 ,或
  • 一个 无效指针值

指向对象的指针 表示该对象在内存中所占第一个字节的地址 。指向对象末尾之后的指针 表示该对象所占存储空间结束后第一个字节的地址

请注意,两个表示相同地址的指针可能具有不同的值。

struct C
{
    int x, y;
} c;
int* px = &c.x;   // px的值为"指向c.x的指针"
int* pxe= px + 1; // pxe的值为"指向c.x末尾后位置的指针"
int* py = &c.y;   // py的值为"指向c.y的指针"
assert(pxe == py); // == 测试两个指针是否表示相同地址
                   // 可能触发也可能不触发
*pxe = 1; // 即使断言未触发也属于未定义行为

通过无效指针值进行间接寻址以及将无效指针值传递给释放函数会导致未定义行为。对无效指针值的任何其他使用具有实现定义的行为。某些实现可能定义复制无效指针值会导致系统生成的运行时故障。

指向对象的指针

指向对象的指针可以用 取址运算符 应用于任意对象类型表达式(包括其他指针类型)的返回值进行初始化:

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 s = {1};
int* sp = &s.n;        // 指向 s 成员 int 的指针

指针可以作为内置间接运算符(一元 operator * )的操作数出现,该运算符返回标识被指向对象的 左值表达式

int n;
int* p = &n;     // 指向 n 的指针
int& r = *p;     // 引用绑定到标识 n 的左值表达式
r = 7;           // 将整数值 7 存储到 n 中
std::cout << *p; // 左值到右值的隐式转换从 n 中读取值

指向类对象的指针也可以作为成员访问运算符 operator-> operator->* 的左操作数出现。

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

int a[2];
int* p1 = a; // 指向数组a的第一个元素a[0](一个int)的指针
int b[6][3][8];
int (*p2)[3][8] = b; // 指向数组b的第一个元素b[0]的指针,
                     // 该元素是一个包含3个数组的数组,每个数组包含8个int

由于指针的 派生类到基类 隐式转换,基类指针可以用派生类地址进行初始化:

struct Base {};
struct Derived : Base {};
Derived d;
Base* p = &d;

如果 Derived 多态对象 ,则可通过此类指针进行 虚函数调用

某些 加法、减法 递增和递减 运算符被定义为指向数组元素的指针:这类指针满足 LegacyRandomAccessIterator 要求,并使得C++标准库 算法 能够处理原始数组。

比较运算符 在特定情况下为指向对象的指针定义:表示相同地址的两个指针比较相等,两个空指针值比较相等,指向同一数组元素的指针比较结果与这些元素的数组索引相同,指向具有相同 成员访问权限 的非静态数据成员的指针按照这些成员的声明顺序进行比较。

许多实现还提供随机来源指针的 严格全序关系 ,例如当指针被实现为连续虚拟地址空间中的地址时。对于那些不提供此类严格全序的实现(例如指针并非所有位都用于表示内存地址而必须在比较时忽略某些位,或需要额外计算,或指针与整数非一一对应关系的情况),会提供 std::less 针对指针的特化版本以保障该特性。这使得所有随机来源的指针都能作为键值用于标准关联容器,例如 std::set std::map

指向 void 的指针

指向任何类型对象的指针都可以 隐式转换 为指向(可能带有 cv限定符 的) void 的指针;指针值保持不变。反向转换需要使用 static_cast 显式转换 ,才能获得原始指针值:

int n = 1;
int* p1 = &n;
void* pv = p1;
int* p2 = static_cast<int*>(pv);
std::cout << *p2 << '\n'; // 输出 1

如果原始指针指向某个多态类型对象中的基类子对象, dynamic_cast 可用于获取指向最派生类型完整对象的 void * 指针。

指向 void 的指针与指向 char 的指针具有相同的大小、表示方式和对齐要求。

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

函数指针

函数指针可以用非成员函数或静态成员函数的地址进行初始化。由于 函数到指针 的隐式转换,取址运算符是可选的:

void f(int);
void (*p1)(int) = &f;
void (*p2)(int) = f; // 与 &f 相同

与函数或函数引用不同,函数指针是对象,因此可以存储在数组中、被复制、赋值等。

void (a[10])(int);  // 错误:函数数组
void (&a[10])(int); // 错误:引用数组
void (*a[10])(int); // 正确:函数指针数组

注意:涉及函数指针的声明通常可以通过类型别名进行简化:

using F = void(int); // 命名类型别名以简化声明
F a[10];  // 错误:函数数组
F& a[10]; // 错误:引用数组
F* a[10]; // 正确:函数指针数组

指向函数的指针可用作 函数调用运算符 的左操作数,这会调用被指向的函数:

int f(int n)
{
    std::cout << n << '\n';
    return n * n;
}
int main()
{
    int (*p)(int) = f;
    int x = p(7);
}

解引用函数指针会产生标识被指向函数的左值:

int f();
int (*p)() = f;  // 指针 p 指向函数 f
int (&r)() = *p; // 标识函数 f 的左值被绑定到引用
r();             // 通过左值引用调用函数 f
(*p)();          // 通过函数左值调用函数 f
p();             // 直接通过指针调用函数 f

指向函数的指针可以从重载集合初始化,该集合可能包含函数、函数模板特化和函数模板,前提是只有一个重载与指针类型匹配(详见 重载函数地址 ):

template<typename T>
T f(T n) { return n; }
double f(double n) { return n; }
int main()
{
    int (*p)(int) = f; // 实例化并选择 f<int>
}

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

指向成员的指针

指向数据成员的指针

指向类 C 中非静态成员对象 m 的指针,只能通过表达式 & C :: m 进行初始化。诸如 & ( C :: m ) 或在 C 的成员函数内部的 & m 等表达式均不能构成指向成员的指针。

此类指针可用作 成员访问运算符 operator. * operator - > * 的右操作数:

struct C { int m; };
int main()
{
    int C::* p = &C::m;          // 指向类C的数据成员m的指针
    C c = {7};
    std::cout << c.*p << '\n';   // 输出7
    C* cp = &c;
    cp->m = 10;
    std::cout << cp->*p << '\n'; // 输出10
}

指向可访问的明确非虚基类数据成员的指针,可以 隐式转换 为指向派生类中同一数据成员的指针:

struct Base { int m; };
struct Derived : Base {};
int main()
{
    int Base::* bp = &Base::m;
    int Derived::* dp = bp;
    Derived d;
    d.m = 1;
    std::cout << d.*dp << ' ' << d.*bp << '\n'; // 输出 1 1
}

相反方向的转换,从派生类数据成员指针到明确非虚基类数据成员指针的转换,可以通过 static_cast 显式转换 实现,即使基类本身不包含该成员(但在使用该指针进行访问时,最终派生类包含该成员):

struct Base {};
struct Derived : Base { int m; };
int main()
{
    int Derived::* dp = &Derived::m;
    int Base::* bp = static_cast<int Base::*>(dp);
    Derived d;
    d.m = 7;
    std::cout << d.*bp << '\n'; // 正常:输出 7
    Base b;
    std::cout << b.*bp << '\n'; // 未定义行为
}

指向成员的指针所指向的类型本身可以是指向成员的指针:成员指针可以是多级的,并且在每一级都可以有不同的 cv 限定符。指针和成员指针的混合多级组合也是允许的:

struct A
{
    int m;
    // 指向非常量成员的常量指针
    int A::* const p;
};
int main()
{
    // 指向数据成员的非const指针,该数据成员是指向非常量成员的常量指针
    int A::* const A::* p1 = &A::p;
    const A a = {1, &A::m};
    std::cout << a.*(a.*p1) << '\n'; // 输出 1
    // 指向常量成员指针的常规非const指针
    int A::* const* p2 = &a.p;
    std::cout << a.**p2 << '\n'; // 输出 1
}

指向成员函数的指针

指向非静态成员函数 f 的指针(该函数是类 C 的成员)只能通过表达式 & C :: f 进行初始化。诸如 & ( C :: f ) C 成员函数内部的 & f 等表达式不能构成指向成员函数的指针。

此类指针可用作 成员访问运算符 operator. * operator - > * 的右操作数。 生成的表达式 仅能作为函数调用运算符的左操作数使用:

struct C
{
    void f(int n) { std::cout << n << '\n'; }
};
int main()
{
    void (C::* p)(int) = &C::f; // 指向类C成员函数f的指针
    C c;
    (c.*p)(1);                  // 输出 1
    C* cp = &c;
    (cp->*p)(2);                // 输出 2
}


指向基类成员函数的指针可以 隐式转换 为指向派生类相同成员函数的指针:

struct Base
{
    void f(int n) { std::cout << n << '\n'; }
};
struct Derived : Base {};
int main()
{
    void (Base::* bp)(int) = &Base::f;
    void (Derived::* dp)(int) = bp;
    Derived d;
    (d.*dp)(1);
    (d.*bp)(2);
}

反向转换,从派生类成员函数指针到明确非虚基类成员函数指针的转换,允许使用 static_cast 显式转换 ,即使基类不具有该成员函数(但在使用该指针进行访问时,最终派生类具有该成员函数):

struct Base {};
struct Derived : Base
{
    void f(int n) { std::cout << n << '\n'; }
};
int main()
{
    void (Derived::* dp)(int) = &Derived::f;
    void (Base::* bp)(int) = static_cast<void (Base::*)(int)>(dp);
    Derived d;
    (d.*bp)(1); // 正常:输出 1
    Base b;
    (b.*bp)(2); // 未定义行为
}

指向成员函数的指针可用作回调函数或函数对象,通常在应用 std::mem_fn std::bind 之后使用:

#include <algorithm>
#include <cstddef>
#include <functional>
#include <iostream>
#include <string>
int main()
{
    std::vector<std::string> v = {"a", "ab", "abc"};
    std::vector<std::size_t> l;
    transform(v.begin(), v.end(), std::back_inserter(l),
              std::mem_fn(&std::string::size));
    for (std::size_t n : l)
        std::cout << n << ' ';
    std::cout << '\n';
}

输出:

1 2 3

空指针

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

一个 空指针常量 可用于将指针初始化为空值或将空值赋给现有指针,它是以下值之一:

  • 值为零的整数字面量。
(since C++11)

NULL 同样可以使用,它扩展为一个由实现定义的 null 指针常量。

零初始化 值初始化 同样会将指针初始化为其空值。

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

无效指针

指针值 p 在评估 e 的上下文中是 有效的 ,当且仅当满足以下任一条件:

  • p 是一个空指针值。
  • p 是一个指向函数的指针。
  • p 是指向对象 o 或其末端之后的指针,且 e 处于 o 存储区域的生存期内。

如果指针值 p 在求值 e 中被使用,且 p e 的上下文中无效,则:

int* f()
{
    int obj;
    int* local_ptr = new (&obj) int;
    *local_ptr = 1; // 正确:表达式“*local_ptr”处于
                    // “obj”的存储持续期内
    return local_ptr;
}
int* ptr = f();  // “obj”的存储持续期已结束,
                 // 因此“ptr”在以下上下文中为无效指针
int* copy = ptr; // 实现定义行为
*ptr = 2;        // 未定义行为:对无效指针进行解引用
delete ptr;      // 未定义行为:通过无效指针释放存储空间

常量性

  • cv 出现在指针声明中的 * 之前,它属于声明说明符序列,并作用于被指向的对象。
  • cv 出现在指针声明中的 * 之后,它属于 声明符 部分,并作用于正在被声明的指针本身。
语法 含义
const T * 指向常量对象的指针
T const * 指向常量对象的指针
T * const 指向对象的常量指针
const T * const 指向常量对象的常量指针
T const * const 指向常量对象的常量指针
// pc 是指向 const int 的非 const 指针
// cpc 是指向 const int 的 const 指针
// ppc 是指向 const int 的非 const 指针的非 const 指针
const int ci = 10, *pc = &ci, *const cpc = pc, **ppc;
// p 是指向非 const int 的非 const 指针
// cp 是指向非 const int 的 const 指针
int i, *p, *const cp = &i;
i = ci;    // 正确:const int 的值可以复制到非 const int
*cp = ci;  // 正确:可通过 const 指针修改指向的非 const int
pc++;      // 正确:指向 const int 的非 const 指针可以修改
pc = cpc;  // 正确:指向 const int 的非 const 指针可以修改
pc = p;    // 正确:指向 const int 的非 const 指针可以修改
ppc = &pc; // 正确:指向 const int 指针的地址就是指向 const int 的指针的指针
ci = 1;    // 错误:const int 不可修改
ci++;      // 错误:const int 不可修改
*pc = 2;   // 错误:指向的 const int 不可修改
cp = &ci;  // 错误:指向非 const int 的 const 指针不可修改
cpc++;     // 错误:指向 const int 的 const 指针不可修改
p = pc;    // 错误:指向非 const int 的指针不能指向 const int
ppc = &p;  // 错误:指向 const int 的指针的指针不能指向
           // 指向非 const int 的指针

通常来说,多级指针之间的隐式转换遵循 限定转换 中描述的规则。

复合指针类型

比较运算符 的操作数,或 条件运算符 的第二、第三操作数中存在指针或成员指针时,会确定一个复合指针类型作为这些操作数的公共类型。

给定两个操作数 p1 p2 ,其类型分别为 T1 T2 ,当且仅当满足以下任一条件时, p1 p2 才能具有复合指针类型:

  • p1 p2 均为指针。
  • p1 p2 中一个为指针,另一个操作数为空指针常量。
  • p1 p2 均为空指针常量,且 T1 T2 中至少有一个为非整型类型。
(C++11 起)
(C++14 前)
  • T1 T2 中至少有一个为指针类型、成员指针类型或 std::nullptr_t
(C++14 起)

复合指针类型 C p1 p2 按以下方式确定:

  • p1 空指针常量 ,则 C T2
  • 否则,若 p2 是空指针常量,则 C T1
(C++11 前)
  • p1 p2 均为 空指针常量 ,则 C std::nullptr_t
  • 否则,若 p1 是空指针常量,则 C T2
  • 否则,若 p2 是空指针常量,则 C T1
(C++11 起)
  • 否则,若满足以下所有条件:
  • T1 T2 是“指向 cv1 void 的指针”。
  • 另一类型是“指向 cv2 T 的指针”,其中 T 对象类型 void
C 是“指向 cv12 void 的指针”,其中 cv12 cv1 cv2 的并集。
  • 否则,若满足以下所有条件:
  • T1 T2 是“指向函数类型 F1 的指针”。
  • 另一类型是“指向 noexcept 函数类型 F2 的指针”。
  • F1 F2 仅在 noexcept 说明符上存在差异。
C 是“指向 F1 的指针”。
(since C++17)
  • 否则,若满足以下所有条件:
  • T1 是“指向 C1 的指针”。
  • T2 是“指向 C2 的指针”。
  • C1 C2 中有一个与另一个是 引用相关 的。
C
  • T1 T2 限定符合并类型 ,如果 C1 C2 引用相关,或
  • T2 T1 的限定符合并类型,如果 C2 C1 引用相关。
  • 否则,若满足以下所有条件:
  • T1 T2 是“指向 C1 成员函数的指针,函数类型为 F1 ”。
  • 另一类型是“指向 C2 成员 noexcept 函数的指针,函数类型为 F2 ”。
  • C1 C2 中有一个与另一个引用相关。
  • F1 F2 除 noexcept 外完全相同。
C
  • “指向 C2 成员函数的指针,类型为 F1 ”,若 C1 C2 引用相关,或
  • “指向 C1 成员函数的指针,类型为 F1 ”,若 C2 C1 引用相关。
(C++17 起)
  • 否则,若满足以下所有条件:
  • T1 是“指向 C1 的非函数类型 M1 成员的指针”。
  • T2 是“指向 C2 的非函数类型 M2 成员的指针”
  • M1 M2 除顶层 cv 限定符外完全相同。
  • C1 C2 中一个与另一个存在引用关联关系。
C
  • C1 C2 存在引用关联,则为 T2 T1 的限定符合并类型;或
  • C2 C1 存在引用关联,则为 T1 T2 的限定符合并类型。
  • 否则,如果 T1 T2 相似类型 ,则 C T1 T2 的限定组合类型。
  • 否则, p1 p2 不具有复合指针类型,需要确定 C 为此类类型的程序是病式的。
using p = void*;
using q = const int*;
// 确定“p”和“q”的复合指针类型属于
// [“指向 cv1 void 的指针”和“指向 cv2 T 的指针”]情形:
// cv1 = 空,cv2 = const,cv12 = const
// 将“cv12 = const”代入“指向 cv12 void 的指针”:
// 复合指针类型为“const void*”
using pi = int**;
using pci = const int**;
// 确定“pi”和“pci”的复合指针类型属于
// [指向相似类型“C1”和“C2”的指针]情形:
// C1 = int*,C2 = const int*
// 由于它们是相似类型,因此是双向引用相关类型
// 复合指针类型是“p1”和“pc1”(或“pci”和“pi”)
// 的限定组合类型:“const int**”

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 适用标准 发布时行为 修正后行为
CWG 73 C++98 指向对象的指针永远不会与
指向数组末尾后一位置的指针相等比较
对于非空且非函数指针,
比较它们所表示的地址
CWG 903 C++98 任何计算结果为0的整型常量表达式
都是空指针常量
限制为值为0的
整数字面量
CWG 1438 C++98 以任何方式使用无效指针值
的行为都是未定义的
除间接寻址和传递给
释放函数之外的行为
由实现定义
CWG 1512
( N3624 )
C++98 复合指针类型规则不完整,因此
不允许 int ** const int ** 之间的比较
使其完整
CWG 2206 C++98 指向 void 的指针和指向
函数的指针具有复合指针类型
它们不具有此类类型
CWG 2381 C++17 确定复合指针类型时不允许
函数指针转换
允许
CWG 2822 C++98 达到存储区域生存期结束时
可能使指针值失效
指针有效性基于
求值上下文
CWG 2933 C++98 函数指针始终无效 它们始终有效

参见

C 文档 关于 指针声明