C语言指针的初始化以及什么是空指针和void指针
指针(pointer)是对数据对象或函数的一种引用。指针有多种用途,例如定义“传址调用”函数,它还可以实现动态数据结构,例如链表和树。接下来我们来介绍如何初始化一个指针。
可以用下列方式来初始化一个指针:
(1) 一个空指针常量。
(2) 指向相同类型的指针,或者指向具有较少限定符修饰的相同类型。
(3) 如果需初始化的指针不是函数指针,可以使用 void 指针进行初始化(同上,需初始化的指针可以是指向具有更多限定符类型的指针)。
指针如果不具有动态存储周期,则必须用常量表达式来初始化,例如取址运算的结果,或者使用数组或函数的名称。
当初始化指针时,除非是上面说明的情况,否则不会发生隐式类型转换。然而,可以将一个指针值显式地转换成另一个指针类型。例如,若想一个字节接着一个字节地读取对象,可以将其地址转换成 char 指针,指向此对象的第一个字节:
所有指针在定义时如果没有初始化,则空指针默认作为它们的初始化值。如果需初始化的指针不是函数指针,可以使用 void 指针进行初始化。那么什么是空指针和 void 指针呢?
空指针有别于其他指向对象或函数的有效指针。因此,当返回值为指针的函数出现执行失败的情况时,它通常会使用空指针作为返回值。标准函数 fopen()正是这样的一个例子,如果在指定的模式下打开某文件失败时,该函数会返回一个空指针。
如果有必要的话,空指针会被隐式地转换成其他指针类型,以进行赋值运算或者是进行 == 或 != 的比较运算。因此,上述例子不需要使用转型运算符。
若想声明一个可以接收任何类型指针参数的函数,可以将所需的参数设定为指向 void 的指针。当调用这样的函数时,编译器会隐式地将对象指针参数转换成 void 指针。常见的例子如标准函数 memset(),它被声明在头文件 string.h 中,其原型如下:
函数 memset()将 c 的值赋值到从地址 s 开始的 n 个内存字节中。例如,下面的函数调用会将 0 值赋值到结构变量 record 中的每个字节:
实参 &record 具有 struct Data* 类型。在函数调用中,实参被转换成形参类型,即 void*。
编译器会在必要的地方把 void 指针转换为对象指针。例如,在下面的语句中,函数 malloc()返回一个 void 指针,它的值是已分配内存的语句块的地址。这样的赋值操作会把 void 指针转换成 int 指针:
指针初始化
具有动态存储周期的指针变量,开始是没有定义值的,除非它们在声明的同时进行了显式地初始化。在语句块内定义的所有变量,只要没有被声明为 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) );
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算