C语言指针的初始化以及什么是空指针和void指针

指针(pointer)是对数据对象或函数的一种引用。指针有多种用途,例如定义“传址调用”函数,它还可以实现动态数据结构,例如链表和树。接下来我们来介绍如何初始化一个指针。

指针初始化

具有动态存储周期的指针变量,开始是没有定义值的,除非它们在声明的同时进行了显式地初始化。在语句块内定义的所有变量,只要没有被声明为 static,就具有动态存储周期。所有指针在定义时如果没有初始化,则空指针默认作为它们的初始化值。

可以用下列方式来初始化一个指针:
(1) 一个空指针常量。
(2) 指向相同类型的指针,或者指向具有较少限定符修饰的相同类型。
(3) 如果需初始化的指针不是函数指针,可以使用 void 指针进行初始化(同上,需初始化的指针可以是指向具有更多限定符类型的指针)。

指针如果不具有动态存储周期,则必须用常量表达式来初始化,例如取址运算的结果,或者使用数组或函数的名称。

当初始化指针时,除非是上面说明的情况,否则不会发生隐式类型转换。然而,可以将一个指针值显式地转换成另一个指针类型。例如,若想一个字节接着一个字节地读取对象,可以将其地址转换成 char 指针,指向此对象的第一个字节:
double x = 1.5;
char *cPtr = &x;          // 错误:类型不匹配;没有隐式转换
char *cPtr = (char *)&x;  // 正确:cPtr指向x的第一个字节

所有指针在定义时如果没有初始化,则空指针默认作为它们的初始化值。如果需初始化的指针不是函数指针,可以使用 void 指针进行初始化。那么什么是空指针和 void 指针呢?

空指针

当把一个空指针常量转换为指针类型时,所得到的结果就是空指针(null pointer)空指针常量(null pointer constant)是一个值为 0 的整数常量表达式,或者是一个 void* 类型的表达式。在头文件 stdlib.h、stdio.h 以及其他头文件中,宏 NULL 被定义为空指针常量。

空指针有别于其他指向对象或函数的有效指针。因此,当返回值为指针的函数出现执行失败的情况时,它通常会使用空指针作为返回值。标准函数 fopen()正是这样的一个例子,如果在指定的模式下打开某文件失败时,该函数会返回一个空指针。
#include <stdio.h>
/* ... */
FILE *fp = fopen( "demo.txt", "r" );
if ( fp == NULL )           // 也可以被写成:if ( !fp )
{
  // 错误:无法打开demo.txt文件进行读取
}

如果有必要的话,空指针会被隐式地转换成其他指针类型,以进行赋值运算或者是进行 == 或 != 的比较运算。因此,上述例子不需要使用转型运算符。

void 指针

指向 void 的指针,或者简称为 void 指针(void pointer),是类型为 void* 的指针。因为没有对象类型是 void,所以 void* 被称为万能指针类型。换句话说,void 指针可以代表任何对象的地址,但不代表该对象的类型。若想获取内存中的对象,必须先把 void 指针转换为合适的对象指针。

若想声明一个可以接收任何类型指针参数的函数,可以将所需的参数设定为指向 void 的指针。当调用这样的函数时,编译器会隐式地将对象指针参数转换成 void 指针。常见的例子如标准函数 memset(),它被声明在头文件 string.h 中,其原型如下:
void *memset( void *s, int c, size_t n );

函数 memset()将 c 的值赋值到从地址 s 开始的 n 个内存字节中。例如,下面的函数调用会将 0 值赋值到结构变量 record 中的每个字节:
struct Data { /* ... */ } record;
memset( &record, 0, sizeof(record) );

实参 &record 具有 struct Data* 类型。在函数调用中,实参被转换成形参类型,即 void*。

编译器会在必要的地方把 void 指针转换为对象指针。例如,在下面的语句中,函数 malloc()返回一个 void 指针,它的值是已分配内存的语句块的地址。这样的赋值操作会把 void 指针转换成 int 指针:
int *iPtr = malloc( 1000 * sizeof(int) );