Pointer declaration
声明一个指针或指向成员类型的指针变量。
目录 |
语法
指针声明是任何 声明符 具有以下形式的简单声明
*
属性
(可选)
cv限定符
(可选)
声明符
|
(1) | ||||||||
嵌套名称说明符
*
属性
(可选)
cv限定符
(可选)
声明符
|
(2) | ||||||||
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 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
和
显式转换
实现,即使基类本身不包含该成员(但在使用该指针进行访问时,最终派生类包含该成员):
指向成员的指针所指向的类型本身可以是指向成员的指针:成员指针可以是多级的,并且在每一级都可以有不同的 cv 限定符。指针和成员指针的混合多级组合也是允许的:
指向成员函数的指针
指向非静态成员函数
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
才能具有复合指针类型:
|
(C++14 前) | ||
|
(C++14 起) |
复合指针类型
C
由
p1
和
p2
按以下方式确定:
|
(C++11 前) |
|
(C++11 起) |
- 否则,若满足以下所有条件:
-
-
T1或T2是“指向 cv1 void 的指针”。 -
另一类型是“指向
cv2
T的指针”,其中T是 对象类型 或 void 。
-
-
C是“指向 cv12 void 的指针”,其中 cv12 是 cv1 与 cv2 的并集。
|
(since C++17) |
- 否则,若满足以下所有条件:
-
-
T1是“指向C1的指针”。 -
T2是“指向C2的指针”。 -
C1和C2中有一个与另一个是 引用相关 的。
-
-
C是-
T1和T2的 限定符合并类型 ,如果C1与C2引用相关,或 -
T2和T1的限定符合并类型,如果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 文档
关于
指针声明
|