命名空间
变体
操作

指针声明

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

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

目录

[编辑] 语法

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

* attr (可选) cv (可选) declarator (1)
nested-name-specifier * attr (可选) cv (可选) declarator (2)
1) 指针声明符:声明S* D;D声明为指向由声明说明符序列S确定的类型的指针。
2) 指向成员的指针声明符:声明S C::* D;D声明为指向C的非静态成员的指针,其类型由声明说明符序列S确定。
嵌套名称说明符 - 一系列名称和作用域解析运算符::
属性 - (C++11 起) 属性列表
cv - 应用于正在声明的指针(而不是指向的类型,其限定符是声明说明符序列的一部分)的const/volatile限定
声明符 - 除引用声明符(没有指向引用的指针)之外的任何声明符。它可以是另一个指针声明符(允许指向指针的指针)

没有指向引用的指针,也没有指向位域的指针。通常,“指针”的提及没有详细说明,不包括指向(非静态)成员的指针。

[编辑] 指针

每个指针类型的值都是以下之一

  • 指向对象或函数的指针(在这种情况下,该指针被称为指向该对象或函数),或
  • 指向对象末尾之后的指针,或
  • 该类型的空指针值,或
  • 无效指针值

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

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

struct C
{
    int x, y;
} c;
 
int* px = &c.x;   // value of px is "pointer to c.x"
int* pxe= px + 1; // value of pxe is "pointer past the end of c.x"
int* py = &c.y;   // value of py is "pointer to c.y"
 
assert(pxe == py); // == tests if two pointers represent the same address
                   // may or may not fire
 
*pxe = 1; // undefined behavior even if the assertion does not fire

通过无效指针值进行间接操作以及将无效指针值传递给解除分配函数会导致未定义行为。任何其他使用无效指针值的行为都是实现定义的。某些实现可能会定义复制无效指针值会导致系统生成的运行时故障。

[编辑] 指向对象的指针

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

int n;
int* np = &n;          // pointer to int
int* const* npp = &np; // non-const pointer to const pointer to non-const int
 
int a[2];
int (*ap)[2] = &a;     // pointer to array of int
 
struct S { int n; };
 
S s = {1};
int* sp = &s.n;        // pointer to the int that is a member of s

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

int n;
int* p = &n;     // pointer to n
int& r = *p;     // reference is bound to the lvalue expression that identifies n
r = 7;           // stores the int 7 in n
std::cout << *p; // lvalue-to-rvalue implicit conversion reads the value from n

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

由于数组到指针的隐式转换,指向数组第一个元素的指针可以用数组类型的表达式进行初始化

int a[2];
int* p1 = a; // pointer to the first element a[0] (an int) of the array a
 
int b[6][3][8];
int (*p2)[3][8] = b; // pointer to the first element b[0] of the array b,
                     // which is an array of 3 arrays of 8 ints

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

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

如果Derived多态的,则此类指针可用于进行虚函数调用

某些加法、减法递增和递减运算符定义用于指向数组元素的指针:此类指针满足LegacyRandomAccessIterator要求,并允许C++库算法与原始数组一起使用。

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

许多实现还提供对随机来源指针的严格全序,例如,如果它们在连续虚拟地址空间中实现为地址。那些不提供(例如,指针并非所有位都是内存地址的一部分,必须忽略进行比较,或者需要额外计算,或者指针和整数之间不是一对一关系)的实现,则提供std::less的特化以保证此特性。这使得可以将所有随机来源的指针用作标准关联容器(如std::setstd::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'; // prints 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; // same as &f

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

void (a[10])(int);  // Error: array of functions
void (&a[10])(int); // Error: array of references
void (*a[10])(int); // OK: array of pointers to functions

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

using F = void(int); // named type alias to simplify declarations
F a[10];  // Error: array of functions
F& a[10]; // Error: array of references
F* a[10]; // OK: array of pointers to functions

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

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;  // pointer p is pointing to f
int (&r)() = *p; // the lvalue that identifies f is bound to a reference
r();             // function f invoked through lvalue reference
(*p)();          // function f invoked through the function lvalue
p();             // function f invoked directly through the pointer

函数指针可以从重载集中初始化,重载集可以包括函数、函数模板特化和函数模板,如果只有一个重载与指针类型匹配(有关详细信息,请参见重载函数的地址

template<typename T>
T f(T n) { return n; }
 
double f(double n) { return n; }
 
int main()
{
    int (*p)(int) = f; // instantiates and selects f<int>
}

相等比较运算符定义用于函数指针(如果指向同一函数,则它们比较相等)。

[编辑] 指向成员的指针

[编辑] 指向数据成员的指针

指向非静态成员对象m的指针,它是类C的成员,可以用表达式&C::m精确初始化。诸如&(C::m)C成员函数内的&m等表达式不会形成指向成员的指针。

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

struct C { int m; };
 
int main()
{
    int C::* p = &C::m;          // pointer to data member m of class C
    C c = {7};
    std::cout << c.*p << '\n';   // prints 7
    C* cp = &c;
    cp->m = 10;
    std::cout << cp->*p << '\n'; // prints 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'; // prints 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'; // okay: prints 7
 
    Base b;
    std::cout << b.*bp << '\n'; // undefined behavior
}

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

struct A
{
    int m;
    // const pointer to non-const member
    int A::* const p;
};
 
int main()
{
    // non-const pointer to data member which is a const pointer to non-const member
    int A::* const A::* p1 = &A::p;
 
    const A a = {1, &A::m};
    std::cout << a.*(a.*p1) << '\n'; // prints 1
 
    // regular non-const pointer to a const pointer-to-member
    int A::* const* p2 = &a.p;
    std::cout << a.**p2 << '\n'; // prints 1
}

[编辑] 指向成员函数的指针

指向类C的非静态成员函数f的指针可以用表达式&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; // pointer to member function f of class C
    C c;
    (c.*p)(1);                  // prints 1
    C* cp = &c;
    (cp->*p)(2);                // prints 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); // okay: prints 1
 
    Base b;
    (b.*bp)(2); // undefined behavior
}

指向成员函数的指针可以用作回调或函数对象,通常在应用std::mem_fnstd::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

[编辑] 空指针

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

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

  • 值为零的整数文字。
(C++11 起)

也可以使用宏NULL,它扩展为实现定义的空指针常量。

零初始化值初始化也将指针初始化为其空值。

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

[编辑] 无效指针

如果满足以下任一条件,则指针值p在评估e的上下文中是有效的

  • p是空指针值。
  • p是指向函数的指针。
  • p是指向对象o或其末尾之后的指针,并且eo的存储区域的持续时间内。

如果在评估e中使用指针值p,并且pe的上下文中无效,则

int* f()
{
    int obj;
    int* local_ptr = new (&obj) int;
 
    *local_ptr = 1; // OK, the evaluation “*local_ptr” is
                    // in the storage duration of “obj”
 
    return local_ptr;
}
 
int* ptr = f();  // the storage duration of “obj” is expired,
                 // therefore “ptr” is an invalid pointer in the following contexts
 
int* copy = ptr; // implementation-defined behavior
*ptr = 2;        // undefined behavior: indirection of an invalid pointer
delete ptr;      // undefined behavior: deallocating storage from an invalid pointer

[编辑] 常量性

  • 如果cv在指针声明中出现在*之前,它是声明说明符序列的一部分,并应用于指向的对象。
  • 如果cv在指针声明中出现在*之后,它是声明符的一部分,并应用于正在声明的指针。
语法 含义
const T* 指向常量对象的指针
T const* 指向常量对象的指针
T* const 指向对象的常量指针
const T* const 指向常量对象的常量指针
T const* const 指向常量对象的常量指针
// pc is a non-const pointer to const int
// cpc is a const pointer to const int
// ppc is a non-const pointer to non-const pointer to const int
const int ci = 10, *pc = &ci, *const cpc = pc, **ppc;
// p is a non-const pointer to non-const int
// cp is a const pointer to non-const int
int i, *p, *const cp = &i;
 
i = ci;    // okay: value of const int copied into non-const int
*cp = ci;  // okay: non-const int (pointed-to by const pointer) can be changed
pc++;      // okay: non-const pointer (to const int) can be changed
pc = cpc;  // okay: non-const pointer (to const int) can be changed
pc = p;    // okay: non-const pointer (to const int) can be changed
ppc = &pc; // okay: address of pointer to const int is pointer to pointer to const int
 
ci = 1;    // error: const int cannot be changed
ci++;      // error: const int cannot be changed
*pc = 2;   // error: pointed-to const int cannot be changed
cp = &ci;  // error: const pointer (to non-const int) cannot be changed
cpc++;     // error: const pointer (to const int) cannot be changed
p = pc;    // error: pointer to non-const int cannot point to const int
ppc = &p;  // error: pointer to pointer to const int cannot point to
           // pointer to non-const int

通常,从一个多级指针到另一个多级指针的隐式转换遵循限定符转换中描述的规则。

[编辑] 复合指针类型

比较运算符的操作数或条件运算符的第二和第三操作数是指针或指向成员的指针时,复合指针类型被确定为这些操作数的通用类型。

给定两个操作数p1p2,分别具有类型T1T2p1p2只有在满足以下任一条件时才能具有复合指针类型

  • p1p2都是指针。
  • p1p2中的一个是指针,另一个操作数是空指针常量。
  • p1p2都是空指针常量,并且T1T2中至少有一个是非整数类型。
(C++11 起)
(直到C++14)
  • T1T2中至少有一个是指针类型、指向成员的指针类型或std::nullptr_t
(C++14 起)

p1p2复合指针类型C确定如下

  • 如果p1空指针常量,则CT2
  • 否则,如果p2是空指针常量,则CT1
(C++11 前)
  • 如果p1p2都是空指针常量,则Cstd::nullptr_t
  • 否则,如果p1是空指针常量,则CT2
  • 否则,如果p2是空指针常量,则CT1
(C++11 起)
  • 否则,如果满足以下所有条件
  • T1T2是“指向cv1 void的指针”。
  • 另一个类型是“指向cv2 T的指针”,其中T对象类型void
C是“指向cv12 void的指针”,其中cv12cv1cv2的并集。
  • 否则,如果满足以下所有条件
  • T1T2是“指向函数类型F1的指针”。
  • 另一个类型是“指向noexcept函数类型F2的指针”。
  • F1F2除了noexcept之外相同。
C是“指向F1的指针”。
(C++17 起)
  • 否则,如果满足以下所有条件
  • T1是“指向C1的指针”。
  • T2是“指向C2的指针”。
  • C1C2中有一个与另一个引用相关
C
  • 如果C1C2引用相关,则为T1T2限定符组合类型,或
  • 如果C2C1引用相关,则为T2T1的限定符组合类型。
  • 否则,如果满足以下所有条件
  • T1T2是“指向C1的类型为F1的成员的指针”。
  • 另一个类型是“指向C2的类型为noexcept函数类型F2的成员的指针”。
  • C1C2中有一个与另一个引用相关。
  • F1F2除了noexcept之外相同。
C
  • 如果C1C2引用相关,则为“指向C2的类型为F1的成员的指针”,或
  • 如果C2C1引用相关,则为“指向C1的类型为F1的成员的指针”。
(C++17 起)
  • 否则,如果满足以下所有条件
  • T1是“指向C1的非函数类型M1的成员的指针”。
  • T2是“指向C2的非函数类型M2的成员的指针”
  • M1M2除了顶层cv限定符外相同。
  • C1C2中有一个与另一个引用相关。
C
  • 如果C1C2引用相关,则为T2T1的限定符组合类型,或
  • 如果C2C1引用相关,则为T1T2的限定符组合类型。
  • 否则,如果T1T2相似类型,则CT1T2的限定符组合类型。
  • 否则,p1p2没有复合指针类型,需要确定C此类类型的程序是格式错误的。
using p = void*;
using q = const int*;
// The determination of the composite pointer type of “p” and “q”
// falls into the [“pointer to cv1 void” and “pointer to cv2 T”] case:
// cv1 = empty, cv2 = const, cv12 = const
// substitute “cv12 = const” into “pointer to cv12 void”:
// the composite pointer type is “const void*”
 
using pi = int**;
using pci = const int**;
// The determination of the composite pointer type of “pi” and “pci”
// falls into the [pointers to similar types “C1” and “C2”] case:
// C1 = int*, C2 = const int*
// they are reference-related types (in both direction) because they are similar
// the composite pointer type is the qualification-combined type
// of “p1” and “pc1” (or that of “pci” and “pi”): “const void* const *”

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 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 文档指针声明