指针声明
声明指针或指向成员的指针类型的变量。
内容 |
[编辑] 语法
指针声明是指其声明符 具有以下形式的任何简单声明
* attr (可选) cv (可选) declarator |
(1) | ||||||||
nested-name-specifier * attr (可选) cv (可选) declarator |
(2) | ||||||||
C
的非静态成员的指针,其类型由声明说明符序列 S
确定。嵌套名称说明符 | - | 名称和作用域解析运算符 :: 的序列 |
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> }
为指向函数的指针定义了相等比较运算符(如果指向同一个函数,则它们比较相等)。
[编辑] 指向成员的指针
[编辑] 指向数据成员的指针
指向类C
的非静态成员对象m
的指针可以用表达式&C::m精确初始化。诸如&(C::m)或&m之类的表达式在C
的成员函数内部不会形成指向成员的指针。
这样的指针可以用作指向成员访问运算符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 }
[编辑] 指向成员函数的指针
指向类C
的非静态成员函数f的指针可以用表达式&C::f精确初始化。诸如&(C::f)或&f之类的表达式在C
的成员函数内部不会形成指向成员函数的指针。
这样的指针可以用作指向成员访问运算符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*的所有指针比较相等。
*空指针常量*可用于将指针初始化为空值或将空值赋给现有指针,它是以下值之一
- 值为零的整数字面量。
|
(自 C++11 起) |
也可以使用宏NULL,它扩展为实现定义的空指针常量。
空指针可以用来指示不存在对象(例如std::function::target()),或者作为其他错误条件指示器(例如dynamic_cast)。通常,接收指针参数的函数几乎总是需要检查该值是否为空,并以不同的方式处理这种情况(例如,当传递空指针时,delete 表达式不做任何事情)。
[编辑] 无效指针
指针值p在求值e的上下文中*有效*,如果满足以下条件之一
- p为空指针值。
- p是指向或超出对象o末尾的指针,并且e在o的存储区域的持续时间内。
如果指针值p在求值e中使用,并且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
[编辑] 常量性
- 如果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
通常,从一个多级指针到另一个多级指针的隐式转换遵循限定转换中描述的规则。
[编辑] 复合指针类型
当比较运算符的操作数或条件运算符的第二个和第三个操作数是指针或指向成员的指针时,将确定一个复合指针类型作为这些操作数的公共类型。
给定两个操作数p1和p2,其类型分别为T1
和T2
,则p1和p2只有在满足以下任何条件时才能具有复合指针类型
|
(直到 C++14) | ||
|
(自 C++14 起) |
p1和p2的*复合指针类型*C
确定如下
|
(直到 C++11) |
|
(自 C++11 起) |
- 否则,如果满足以下所有条件
-
T1
或T2
为“指向 cv1 void 的指针”。 - 另一种类型为“指向 cv2
T
的指针”,其中T
是 对象类型 或 void。
-
-
C
为“指向 cv12 void 的指针”,其中 cv12 是 cv1 和 cv2 的并集。
|
(自 C++17 起) |
- 否则,如果满足以下所有条件
-
T1
为“指向C1
的指针”。 -
T2
为“指向C2
的指针”。 C1
和C2
之一与另一个 引用相关。
-
-
C
为- 如果
C1
与C2
引用相关,则为T1
和T2
的 限定符组合类型,或 - 如果
C2
与C1
引用相关,则为T2
和T1
的限定符组合类型。
- 如果
|
(自 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 *”
[编辑] 缺陷报告
以下更改行为的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
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 | 到达存储区域持续时间的末尾可能会使指针值无效 |
指针有效性基于 评估上下文 |
[编辑] 参见
C 文档 的 指针声明
|