void指针
本节阅读量:
void指针也称为泛型指针,是一种特殊类型的指针,可以指向任何数据类型的对象!使用void关键字作为指针类型即可声明void指针:
1
|
void* ptr {}; // ptr 是 void指针
|
void指针可以指向任何数据类型的对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int nValue {};
float fValue {};
struct Something
{
int n;
float f;
};
Something sValue {};
void* ptr {};
ptr = &nValue; // 有效
ptr = &fValue; // 有效
ptr = &sValue; // 有效
|
然而,由于void指针不知道自己所指向对象的类型,因此直接解引用void指针是非法的。也就是说,在解引用之前,必须先将void指针转换为其他指针类型。
1
2
3
4
5
6
7
8
|
int value{ 5 };
void* voidPtr{ &value };
// std::cout << *voidPtr << '\n'; // 非法: 解引用void指针是非法的
int* intPtr{ static_cast<int*>(voidPtr) }; // 不过, 可以将void指针转换为其他指针
std::cout << *intPtr << '\n'; // 然后进行解引用
|
这将打印:
接下来一个显而易见的问题是:如果void指针不知道它所指向的是什么,我们如何知道应该将它强制转换为什么类型?这取决于您的自定义代码逻辑,也可以为void指针附带一个标签。
下面是一个使用void指针的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include <cassert>
#include <iostream>
enum class Type
{
tInt, // 注: 不能直接写 "int" 因为它是关键字, 所以使用 "tInt"
tFloat,
tCString
};
void printValue(void* ptr, Type type)
{
switch (type)
{
case Type::tInt:
std::cout << *static_cast<int*>(ptr) << '\n'; // 转成int指针,然后解引用
break;
case Type::tFloat:
std::cout << *static_cast<float*>(ptr) << '\n'; // 转成float指针,然后解引用
break;
case Type::tCString:
std::cout << static_cast<char*>(ptr) << '\n'; // 转成char指针,然后打印对应的字符串
// std::cout 会将 char* 当作C样式字符串
// 因此这里打印的是对应的字符串
break;
default:
std::cerr << "printValue(): invalid type provided\n";
assert(false && "type not found");
break;
}
}
int main()
{
int nValue{ 5 };
float fValue{ 7.5f };
char szValue[]{ "Mollie" };
printValue(&nValue, Type::tInt);
printValue(&fValue, Type::tFloat);
printValue(szValue, Type::tCString);
return 0;
}
|
该程序打印:
void指针杂项
void指针可以设置为空值:
1
|
void* ptr{ nullptr }; // ptr 是void指针,现在是nullptr
|
由于void指针不知道自己所指向对象的类型,因此delete void指针将导致未定义的行为。如果需要删除void指针,请先使用static_cast将其转换回适当的类型。
不能对void指针进行指针运算。这是因为指针运算要求指针知道所指向对象的大小,这样才能正确地增加或减小指针值。
请注意,并不存在所谓的void引用。这是因为void引用的类型为void&,它完全不知道所引用值的类型。
结论
通常,除非绝对必要,否则最好避免使用void指针,因为它会绕过类型检查。这可能让您在无意中做出没有意义的操作,而编译器不会对此发出警告。例如,以下代码是有效的:
1
2
|
int nValue{ 5 };
printValue(&nValue, Type::tCString);
|
但谁知道结果会是什么呢!
尽管上述函数似乎是一种让单个函数处理多种数据类型的简洁方法,但C++实际上提供了更好的方式来完成相同操作(通过函数重载)。函数重载会保留类型检查,从而帮助防止误用。许多过去使用void指针来处理多种数据类型的场景,现在最好改用模板完成,模板同样提供强类型检查。
不过,偶尔您仍然可能会遇到void指针的合理用法。但在使用之前,请先确认没有更好(更安全)的语言机制可以完成同样的事情!