指针定义
了解空指针(void pointers),这是在C语言中实现泛型编程的关键概念。
简介
到目前为止,我们已经使用过各种类型的指针,例如:
- 指向
int的指针 - 指向
float的指针 - 指向
char的指针 - 指向自定义数据类型(通过结构体定义)的指针
一个限制是,不同类型的指针通常不兼容。例如,一个 int 类型的指针通常无法指向一个浮点数。这一特性引出了一个有趣的问题:当我们希望编写一个与数据类型无关的函数时该怎么办?因为处理逻辑完全相同,我们不得不为每种数据类型创建多个几乎相同的函数,唯一的区别只是参数的数据类型。
举个例子,来看一下 memcpy 函数。它的作用是将一块内存中的数据复制到另一块内存中。我们并不关心这些数据的具体类型,只需要逐字节进行复制即可。那么我们应该如何实现它?是否需要为每种数据类型都写一个 memcpy_int、memcpy_float 等函数?
答案是否定的!这样做显然不是良好的代码设计。不仅可能需要创建数量无限的重复函数,而且我们还必须记住在不同场景下使用正确的版本。
解决方案是使用 void 指针。
void 指针
void 指针(void*)是一种可以指向任何其他指针的指针。它没有具体的类型(不像指向 int 的指针那样明确指向 int)。我们可以将其看作是一个占位符或通配符指针,它能接受任意类型的指针。
考虑以下代码,在 第11行 创建了一个 void 指针。我们用一个 int 指针和一个 float 指针对其进行初始化。代码能够成功编译,这证明了 void* 可以指向任意类型 T*(其中 T 是一种数据类型,如 int、float)。
1#include <stdio.h> 2 3int main() 4{ 5 int x = 15; 6 int* intPtr = &x; // intPtr 只能指向 int 7 8 float y = 16.5; 9 float* floatPtr = &y; // floatPtr 只能指向 float 10 11 void* voidPtr = intPtr; // voidPtr 可以指向任何类型 12 voidPtr = floatPtr; 13 14 return 0; 15}
void* 的限制
我们不能直接解引用 void 指针,也不能对 void 指针进行指针运算。在使用之前,必须将 void* 转换回具体的指针类型。这种转换可以通过类型强制转换来完成。
注意: 某些编译器可能允许对
void*进行指针运算,假设void的大小为1。但这种情况通常无实际意义,也不符合标准 C 规范。请避免对void*进行指针运算,否则代码将不具备跨编译器的可移植性。
例如,下面的代码无法编译:
1#include <stdio.h> 2 3int main() 4{ 5 int x = 5; 6 void* ptr = &x; // 创建一个指向 x 的 void 指针 7 8 // 不能直接解引用 void 指针 9 *ptr = 7; 10 printf("%d\n", *ptr); 11 12 // 可能因编译器而异,但不符合标准 C。 13 // 不要对 void 指针进行指针运算。 14 ptr = ptr + sizeof(int); 15 16 return 0; 17}
类型转换
要使用 void 指针,必须将其转换回原始的数据类型。这可以通过类型强制转换实现。
进行类型转换时,在变量前用括号指定目标类型。例如,将浮点值 5.0 转换为整型,可以写作 (int)5.0f。
因此,将 void* 转换为 int*,可以写作 (int*)voidPtr。
看下面这段代码。我们定义了一个函数 printUsingVoidPointer (第3行),它接收一个 void* 参数,将其转换回 int* 类型,然后打印该指针所指向的值。
我们在 第10行 执行了类型转换。请记住,我们不能直接解引用 void*,必须先进行类型转换。
1#include <stdio.h> 2 3void printUsingVoidPointer(void *ptr) 4{ 5 if(ptr == NULL) 6 { 7 return; 8 } 9 10 int* actualPtr = (int*)ptr; 11 printf("%d\n", *actualPtr); 12} 13 14int main() 15{ 16 int x = 15; 17 int* intPtr = &x; 18 19 void* voidPtr = intPtr; 20 21 printUsingVoidPointer(voidPtr); 22 return 0; 23}
输出结果为 15,这是正确的。
注意,我们也可以写成 int* actualPtr = ptr;,而不需要显式写出 (int*)。因为编译器能理解我们的意图——将 void* 赋值给 int*,所以会自动推断出需要类型转换。
然而,如果希望在一行代码中完成转换并打印,就必须使用类型转换:
1printf("%d\n", *((int*)ptr));