指针的定义 Ⅱ
上一节提到了一种指针拥有“指向 int[5]
的指针类型”。这种指针类型还出现在下面的代码中:
int a[4][5]{};
cout << a << endl;
这时,a
是一个二维数组,即由 4 个 int[5]
构成的数组。当它被输出时,会隐式转换成指向首元素 a[0]
的指针。然而首元素 a[0]
是 int[5]
类型的数组,所以这时输出的也是一个指向 int[5]
的指针。由此也能看出指向数组的指针也经常出现,但是我们却不会声明并定义这样的指针……
int[5]* p; // 不对,没有这样的语法
int* p[5]; // 这是由 5 个 int* 类型构成的数组
所以接下来我们来学习如何声明并定义指向数组的指针。
指向数组的指针
正确的写法是:
是不是看上去特别诡异。后面的 {nullptr}
是初始化器,如果不带初始化器就声明成 int (*p)[5];
。你问为什么这么古怪?别问,问就是历史遗留。所以当你把二维数组作为函数形参的时候,它实际上是把指向数组的指针作为了形参。
这段代码就使用了这样的语法,其中 int(*)[5]
就是这个该死的指针的类型标识。这个括号是不能省的,一旦省略就变成由 5 个 int*
构成的数组了,而非一个指针。
我很不喜欢这种声明语句,所以这里介绍一个方法:使用类型别名(Type alias)。声明类型别名的语法是这样的:
using 别名 = 类型标识;
比如
这时,ArrayOf5Int
就完全等价于 int[5]
这个类型了。比如:
ArrayOf5Int b{};
就声明了一个数组 b
,它拥有 5 个 int
元素。类似地,我们可以很优雅地声明指向数组的指针:
ArrayOf5Int* p{nullptr};
看上去就正常不少。使用类型别名后,刚刚的代码可以改写成这个样子:
指针的只读性
在 C++ 中,只读变量和非只读变量拥有不同的类型(即 const int
和 int
是两个类型)。所以有
但是请注意,指针 q
本身不是只读的。它只是指向只读变量而已:
如果要声明一个只读的指针,需要把 const
放在 *
的右边。
这时,*p
是 int
类型可以更改,但是 p
只读不能更改——也就是说,p
自始至终只能指向一个确定的变量。
如果你看不惯的话,你也可以用类型别名。
void
的指针
指向 存在一种特殊类型的指针,称为指向 void
的指针:
你可以将 void*
理解为指向任意类型的指针类型。因为任何指针都可以隐式转换到 void*
类型,即 void*
是一个通用的指针类型。
但是,对 void*
类型做算术运算和解地址运算是未定义的。所以就目前而言,它的用途比较小;常见的只有:
通过这个函数,可以输出 char*
指针的值(而非输出字符串)。这是因为发生了从 char*
到 void*
的转换。
当然你也可以直接进行强制类型转换:cout << (void*)p << endl;
。
指向函数的指针
之前讨论的一直是指向变量的指针。实际上,C/C++ 还提供了指向函数的指针,俗称为函数指针。它的声明如下:
返回值类型 (*指针名)(参数列表)初始化器;
比如:
它是一个基类型为函数的指针。其中这个函数必须接收 参数列表
所规定的数量、类型的参数,且返回值类型为 返回值类型
。比如上面示例中的函数指针 ptr
,它可以:
int (*ptr)(int, int){nullptr};
int max(int, int);
int main() {
ptr = &max; // ptr 存储了函数 max 的地址,即指向 max
}
你可以通过函数指针的解地址运算符来调用其所指向的函数:
除此之外,C/C++ 提供了一些“快捷方式”:
- 所有的函数都可以隐式转换到指向自身的指针;
- 函数指针可以不经过解地址运算符,直接调用其所指向的函数。
具体说就是: