章节目录

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'; // 然后进行解引用

这将打印:

1
5

接下来一个显而易见的问题是:如果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;
}

该程序打印:

1
2
3
5
7.5
Mollie

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指针的合理用法。但在使用之前,请先确认没有更好(更安全)的语言机制可以完成同样的事情!


19.3 指向指针和动态多维数组的指针

上一节

20.0 函数指针

下一节


本节目录