指针的定义 Ⅰ
上节提到了,指针是存放地址的变量。下面我们来学习如何声明并定义一个指针:
基类型* 指针名 初始化器;
比如:
int* address{&c};
我来解释一下。当你声明并定义一个指针的时候,你需要提供一个称为 基类型
的东西,此时这个指针存储的地址只能是 基类型
这个类型变量的地址。比如:
int c{76};
int d{42};
int* address{&c}; // address 的基类型为 int*,所以它只存储 int 型变量的地址
address = &d; // 将 int 型变量 d 的地址赋值给 address
short e{1};
address = &e; // 错误:address 不能存储 short 型变量的地址
当一个指针的基类型为 T
时,称这个指针的类型为指向 T
的指针类型。类似地,如果一个指针存储了变量 a
的地址,则称这个指针指向变量 a
。
如果用图示画出来,就是:
事实上,地址本身就包含了基类型的信息。在后文中,我有时也会称一个地址的基类型为某某某,所以不必纠结于这些用词。
关于初始化
指针也是变量,因此它的声明方式和普通变量一致:初始化器也是可选的。因此你完全可以
这样不写初始化器。但是问题来了,如果不写初始化器,这样的表达式:
*a = 3;
*b = 'A';
会造成什么后果呢?答案是未知的。因为你根本不知道没有初始化时,a
这个指针里存放的到底是那个地址,*a
是个什么东西根本无法得知。如果 a
或者 b
指向了非法的存储空间,然后你又对这片存储空间进行了修改,可能会导致很严重的后果(甚至可能影响到操作系统)。所以我们认为不初始化指针是非常危险的。
为了解决这个问题,C++ 提供了一个称为空指针(Null pointer)的字面量:nullptr
。它可以转换为指向任何类型的指针类型:
尽管此时访问 *a
和 *b
仍然是非法的(会导致运行时错误),但是它不会造成任何危险。所以,我在这里建议:定义指针时必须初始化,若无合适的初始化值则初始化为 nullptr
。
nullptr
是指针类型的零值,即零初始化时采用的值。事实上,
nullptr
不是指针类型的字面量;它的类型为 空指针类型std::nullptr_t
。
关于类型标识
根据之前类型标识的概念,我们可以得知指针类型的类型标识是 T*
,其中 T
是基类型。不过由于指针类型属于复合类型,所以在同时声明并定义多个指针时,倘若
int* a, b;
这样写,只有 a
是一个 int*
类型的指针,而 b
只是一个 int
型变量。这一点和数组的声明很相似,但是不容易看出。如果你想将 a
和 b
都声明为指针,你需要
int *a, *b;
这样来做。看上去有一点别扭——其中 *
不是变量名的一部分,而是表示引入的名字是一个指针。这种奇怪的语法是 C 语言遗留下来的糟粕,不要太纠结就是。所以这里我建议,声明并定义指针时尽量一句一个:
这样就好很多。
关于指针声明符中
*
的位置,附录中提供了更多的讨论与解释。