指针声明
声明指针或指向成员的指针类型的变量。
内容 |
[编辑] 语法
指针声明是任何简单声明,其声明符的形式为
* attr (可选) cv (可选) 声明符 |
(1) | ||||||||
nested-name-specifier * attr (可选) cv (可选) 声明符 |
(2) | ||||||||
S
确定的类型的 C
的非静态成员的指针。nested-name-specifier | - | 一个 名称和作用域解析运算符 :: 的序列 |
attr | - | (自 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::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'; // 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 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
和 显式转型进行相反方向的转换,从指向派生类的数据成员的指针转换为指向明确的非虚基类的数据成员的指针,即使基类没有该成员(但最派生的类有,当指针用于访问时)
指向成员的指针的指向类型本身可以是指向成员的指针:指向成员的指针可以是多级的,并且可以在每个级别上以不同的方式进行 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 }
[编辑] 成员函数指针
指向非静态成员函数 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; // 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_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
[编辑] 空指针
每种类型的指针都有一个特殊值,称为该类型的空指针值。值为 null 的指针不指向对象或函数(解引用空指针的行为是未定义的),并且与同一类型的所有值也为 null 的指针比较相等。
空指针常量可用于将指针初始化为 null 或将 null 值分配给现有指针,它是以下值之一
- 值为零的整数文字。
|
(自 C++11 起) |
宏 NULL 也可以使用,它扩展为实现定义的空指针常量。
空指针可用于指示对象的缺失(例如 std::function::target()),或作为其他错误条件指示符(例如 dynamic_cast)。通常,接收指针参数的函数几乎总是需要检查该值是否为 null,并以不同的方式处理该情况(例如,当传递空指针时,delete 表达式不执行任何操作)。
[编辑] 无效指针
如果满足以下条件之一,则指针值 p 在求值 e 的上下文中有效
- p 是一个空指针值。
- p 是一个函数指针。
- p 是指向对象 o 或在其末尾之后的指针,并且 e 在 o 的存储区域的持续时间内。
如果在求值 e 中使用了指针值 p,并且 p 在 e 的上下文中无效,则
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
[编辑] 常量性 (Constness)
- 如果 cv 出现在指针声明中的
*
之前,它是声明说明符序列的一部分,并应用于指向的对象。 - 如果 cv 出现在指针声明中的
*
之后,它是声明符的一部分,并应用于正在声明的指针。
语法 (Syntax) | 含义 (meaning) |
---|---|
const T* | 指向常量对象的指针 (pointer to constant object) |
T const* | 指向常量对象的指针 (pointer to constant object) |
T* const | 指向对象的常量指针 (constant pointer to object) |
const T* const | 指向常量对象的常量指针 (constant pointer to constant object) |
T const* const | 指向常量对象的常量指针 (constant pointer to constant object) |
// 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
一般来说,从一个多级指针到另一个多级指针的隐式转换遵循限定转换中描述的规则。
[编辑] 复合指针类型 (Composite pointer type)
当比较运算符的操作数或条件运算符的第二和第三操作数是指针或指向成员的指针时,将确定一个复合指针类型作为这些操作数的公共类型。
给定两个操作数 p1 和 p2,分别具有类型 T1
和 T2
,只有当满足以下任何条件时,p1 和 p2 才能具有复合指针类型
|
(直到 C++14)(until C++14) | ||
|
(自 C++14 起)(since C++14) |
p1 和 p2 的复合指针类型 C
确定如下
|
(直到 C++11)(until C++11) |
|
(自 C++11 起) |
- 否则,如果满足以下所有条件
-
T1
或T2
是 “指向 cv1 void 的指针”。 - 另一种类型是 “指向 cv2
T
的指针”,其中T
是一个对象类型或 void。
-
-
C
是 “指向 cv12 void 的指针”,其中 cv12 是 cv1 和 cv2 的并集。
|
(自 C++17 起)(since C++17) |
- 否则,如果满足以下所有条件
-
T1
是 “指向C1
的指针”。 -
T2
是 “指向C2
的指针”。 C1
和C2
中有一个与另一个引用相关。
-
-
C
是- 如果
C1
与C2
引用相关,则为T1
和T2
的限定组合类型,或者 - 如果
C2
与C1
引用相关,则为T2
和T1
的限定组合类型。
- 如果
|
(自 C++17 起)(since 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*; // 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 *”
[编辑] 缺陷报告 (Defect reports)
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 (Applied to) | 已发布行为 (Behavior as published) | 正确行为 (Correct behavior) |
---|---|---|---|
CWG 73 | C++98 | 指向对象的指针永远不会比较等于 (a pointer to an object never compares equal) 指向数组末尾后一个位置的指针 (to a pointer to one past the end of an array) |
对于非空和非函数指针,(for non-null and non-function pointers,) 比较它们代表的地址 (compare the addresses they represent) |
CWG 903 | C++98 | 任何求值为 0 的整型常量表达式都是空指针常量 (any integral constant expression that) evaluates to 0 was a null pointer constant |
仅限于值为 0 的整数 (limited to integer) literals with value 0 |
CWG 1438 | C++98 | 以任何方式使用无效指针的行为是未定义的 (the behavior of using an invalid pointer) value in any way was undefined |
间接引用和 (behaviors other than indirection and) 传递给释放函数的行为除外,其他行为是实现定义的 (passing to deallocation functions) are implementation-defined |
CWG 1512 (N3624) |
C++98 | 复合指针类型的规则不完整,因此 (the rule of composite pointer type was incomplete, and thus) 不允许在 int** 和 const int** 之间进行比较 (did not allow comparison between int** and const int**) |
已完善 (made complete) |
CWG 2206 | C++98 | 指向 void 的指针和指向 (a pointer to void and a pointer to) 函数的指针具有复合指针类型 (function had a composite pointer type) |
它们没有这种类型 (they do not have such a type) |
CWG 2381 | C++17 | 在确定复合指针类型时,不允许函数指针转换 (function pointer conversions were not allowed) when determining the composite pointer type |
允许 (allowed) |
CWG 2822 | C++98 | 到达存储区域持续时间的末尾可能会使指针值无效 (reaching the end of the duration of a region) of storage could invalidate pointer values |
指针有效性基于 (pointer validity is based) 评估上下文 (on the evaluation context) |
CWG 2933 | C++98 | 指向函数的指针始终无效 (pointers to functions were always invalid) | 它们始终有效 (they are always valid) |
[编辑] 参见 (See also)
C 文档 关于 指针声明 (Pointer declaration)
|