【C/C++基本功】C/C++江湖风云录:void* 的江湖传说

《C/C++江湖风云录:void* 的江湖传说》


🏮 第一回:初入江湖 - void* 是何方神圣?

话说在 C/C++ 这偌大的江湖中,有一位人称 "百变星君"void*。它身怀绝技,却深藏不露:

"老夫无门无派,却能化身万物!

指 int 是 int,指 float 是 float,

今日扮作字符串,明日伪装结构体------

但若问老夫本相?无相!"

江湖定位

  • 师承:C 语言老祖宗

  • 绝技:无相指(指向任意类型)

  • 弱点:类型迷魂散(需自行解毒/转换)


🎭 第二回:江湖绝学 - void* 的三大神功

神功一:malloc 易容术

复制代码
void* 易容盒 = malloc(sizeof(啥都行));  // 先变个空盒子
int* 整数脸 = (int*)易容盒;          // 涂脂抹粉变整数
*整数脸 = 42;                       // 搞定!

"此乃动态内存分配之根基,

门派库函数皆用此招传功"


神功二:回调隐身衣

复制代码
void 江湖令(void* 密信) {
    char* 口令 = (char*)密信;  // 揭密需对暗号!
    printf("接头暗号: %s\n", 口令);
}

int main() {
    char 暗号[] = "天王盖地虎";
    江湖令(暗号);  // 传令时化身无相
}

"线程回调、事件处理皆用此道,

穿此衣者,人鬼难辨!"


神功三:容器乾坤袋

复制代码
struct 百宝囊 {
    void* 宝物;      // 能装灵丹(int)也能装秘籍(string)
    char 标签[20];   // 自己记清楚装的啥!
};

// 使用示例:
struct 百宝囊 我的囊;
int 灵丹 = 99;
我的囊.宝物 = &灵丹;
strcpy(我的囊.标签, "九转金丹");

// 取用时...
if (strcmp(我的囊.标签, "九转金丹") == 0) {
    int* 取丹 = (int*)我的囊.宝物;
    printf("服用金丹: %d\n", *取丹);
}

"低级版 C++ 模板,

全凭掌门(程序员)记性好!"


⚔️ 第三回:江湖险恶 - void* 的三大禁忌

走火入魔一:无相乱指

复制代码
float 浮云 = 3.14;
void* 迷魂针 = &浮云;
int* 错认剑 = (int*)迷魂针;  // 走火入魔!
printf("%d", *错认剑);      // 输出玄学数字

"症状:内存错乱,程序癫狂

解药:类型标记/文档注释"


走火入魔二:野指针遁

复制代码
void* 野指针;
{
    int 临时功 = 42;
    野指针 = &临时功;  // 偷学临时变量
} // 临时功已散功!
printf("%d", *(int*)野指针);  // 走火入魔!

"症状:段错误暴毙

解药:malloc 拷贝大法"


走火入魔三:类型欺师

复制代码
struct 武功秘籍 { char 名[20]; int 威力; };
struct 武功秘籍 九阴真经 = {"九阴真经", 99};
void* 盗版书 = &九阴真经;

// 恶徒篡改类型!
char* 假秘籍 = (char*)盗版书;
假秘籍[0] = '八';  // 内存破坏!

"症状:数据七窍流血

解药:strict aliasing 门规"


🏆 第四回:江湖地位 - 各派评说

门派 评语
C 语言派 "我派镇山之宝,虽糙但猛!"
C++ 模板宗 "粗鄙!看我类型安全模板!"(但底层 still 用 void* 实现)
Java 门 "魔教妖术!我 Object 掌门才是正统!"
Python 教 "阿弥陀佛,施主何不放下类型执念?"(动态类型笑而不语)

🌟 终极心法口诀

"无类型,乃大有类型

不执著,方得大自在

------但切记贴好类型标签!"

那我们就来动手写一个生动有趣又接地气 的代码示例,用 void* 实现一个**"万能盒子"(Generic Box)** ,它可以装任何类型的数据 (比如整数、浮点数、字符串),并且之后可以取出来使用

我们会用 void* 来实现这个"盒子"的核心功能,同时演示它是怎么存储和取出不同类型数据的,以及为什么类型安全要靠程序员自己保证


🎁 示例:用 void* 实现一个"万能盒子"(Generic Box)

我们来实现一个简单的结构体 Box,它可以:

  1. 装(store) 任意类型的数据(通过 void* 和内存拷贝)

  2. 取(get) 出数据,并按照你告诉它的类型来使用

⚠️ 注意:因为 C 没有运行时类型检查,所以我们必须手动告诉盒子你存的是什么类型,以及取的时候按什么类型来解读 ,这也是 void* 使用中最关键的一点!


✅ 示例代码(C语言,易懂版)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个万能盒子结构
typedef struct {
    void* data;     // 指向任意类型数据的指针
    size_t size;    // 数据的大小(比如 int 是4字节,double 是8字节)
} Box;

// 装东西进盒子
void box_store(Box* box, void* value, size_t size) {
    // 先释放之前的数据(如果有)
    if (box->data != NULL) {
        free(box->data);
    }

    // 分配新内存并拷贝数据进去
    box->data = malloc(size);
    if (box->data == NULL) {
        perror("Failed to allocate memory");
        exit(EXIT_FAILURE);
    }
    memcpy(box->data, value, size);  // 拷贝内容
    box->size = size;
}

// 取出盒子里的东西
void* box_get(Box* box) {
    return box->data;
}

// 释放盒子资源
void box_free(Box* box) {
    if (box->data != NULL) {
        free(box->data);
        box->data = NULL;
    }
    box->size = 0;
}

// 测试函数
int main() {
    Box myBox;

    // ======================
    // 例子1:存一个整数
    // ======================
    int x = 42;
    box_store(&myBox, &x, sizeof(int));

    // 取出来用,必须转回 int*
    int* px = (int*)box_get(&myBox);
    printf("[整数] 盒子里装的是: %d\n", *px);

    // ======================
    // 例子2:存一个浮点数
    // ======================
    float f = 3.14f;
    box_store(&myBox, &f, sizeof(float));

    float* pf = (float*)box_get(&myBox);
    printf("[浮点数] 盒子里装的是: %.2f\n", *pf);

    // ======================
    // 例子3:存一个字符串
    // ======================
    char* s = "Hello, void*!";
    box_store(&myBox, s, strlen(s) + 1);  // +1 是为了装下 '\0'

    char* ps = (char*)box_get(&myBox);
    printf("[字符串] 盒子里装的是: %s\n", ps);

    // ======================
    // 清理
    // ======================
    box_free(&myBox);

    return 0;
}

🧠 代码解析

🔒 核心思想:

  • 我们的 Box 结构体里有一个 void* data,它可以指向任意数据。

  • 但我们不能只存指针,因为如果外面传进来的是个局部变量的地址(比如 &x),等函数返回后那个地址可能就失效了!所以我们用 malloc 拷贝一份数据放进盒子自己的内存里,这样更安全、更独立。

  • 同时我们记录数据的大小 size,方便正确分配内存和拷贝。

  • 当你要取出数据时,你必须明确地知道当初存进去的是什么类型 ,然后手动转成对应的指针类型,比如 (int*)(float*)(char*)


🖨️ 输出结果

运行上述程序,你会看到类似这样的输出:

复制代码
[整数] 盒子里装的是: 42
[浮点数] 盒子里装的是: 3.14
[字符串] 盒子里装的是: Hello, void*!

是不是感觉这个盒子很神奇?它可以装下各种各样的东西!🎉

但请记住:它之所以"神奇",是因为你在用它时是"有意识地"告诉它该怎样解读数据的。如果解读错了,比如你存了个 int 却用 float 去读,那就等于你把披萨当成鞋子穿,后果自负 😂*


🧩 进阶思考(可选)

如果你想让这个盒子更智能一点,比如:

  • 自动记住存的是什么类型 (比如加个 enum 类型标记)

  • 支持更多复杂类型(如结构体)

  • 提供类型安全的"取数据"接口(比如 box_get_int()box_get_float()

那你可以继续扩展这个例子,甚至用 C++ 的模板/继承机制来做得更优雅(但那就不是 void* 的范畴啦 😄)。


✅ 总结一下本例

概念 本例中的体现
void* 的作用 作为通用数据指针,可以指向任意类型(int、float、字符串等)
为什么用 void* 我们事先不知道用户要存什么类型,所以用通用指针来接纳所有类型
如何保证正确使用 程序员自己记录类型信息,并在取出时手动转换为正确的指针类型
数据安全 我们用 malloc + memcpy 把数据拷贝到盒子内部,避免外部数据失效问题
应用场景 模拟通用容器、回调参数、自定义数据包装等

好的!咱们今天就用最生动、最接地气的方式,带你彻底搞懂 C/C++ 中那个神秘又万能的 void*,它就像是 C/C++ 世界里的"瑞士军刀"或者"万能插座",啥都能插(哦不,是啥都能指)------但用的时候你得心里有点数,不然容易"触电"(出错)!


一、void* 是个啥?🤔

先看名字:

  • void 的意思是"无"、"空"

  • * 表示这是个指针

  • 所以 void* 就是:一个不知道指向啥类型的指针,俗称"无类型指针"或"万能指针"

你可以把它想象成:

一个没有标签的快递包裹,它装了东西,但快递单上没写里面是手机、零食还是砖头。你拿到这个包裹(指针)时,只知道它指向某个内存地址,但完全不知道里面装的是啥类型的数据!


二、为什么要有 void*?它能干啥?🔧

一句话总结:

void* 是为了实现"通用性"和"灵活性"而存在的,尤其是在你不知道未来会指向什么类型,或者要让一段代码能适配很多类型的时候。


三、举个栗子 🌰,从生活到代码

场景 1:快递站的故事(比喻)

假设你是一家快递站的工作人员,每天有人来寄东西,但寄的东西五花八门:有的是书,有的是衣服,有的是一台电脑。

问题是:你收包裹的时候,不可能为每一种物品都准备一个专门的货架吧?

于是你决定:

所有包裹统一放到一种**"通用包裹盒"**里,盒子上只记录地址(内存地址),不记录里面是啥。

这个"通用包裹盒"就是 void*

你收到包裹后,不能直接拆开用(不能直接解引用 void*,必须先看看寄件人给你留的纸条(类型信息),然后决定怎么拆(做类型转换)。


场景 2:C 语言中的 malloc(真实代码)

在 C 语言里,你想动态申请一块内存来存数据,比如存整数、浮点数或者结构体,但你不知道用户将来要存啥,对吧?

于是 C 标准库就很聪明地想:

"我提供一个函数,它只负责分配内存,至于你要放啥,你自己决定!所以我返回一个 void*,你爱转成啥类型就转成啥类型!"

复制代码
int* arr = (int*)malloc(10 * sizeof(int));  // 分配能存10个int的内存
  • malloc 返回的是 void*,它只是说:"我给了你一块内存,地址在这儿,你说了算!"

  • 你告诉编译器:"嘿,我把这块内存当做 int 数组来用!" 所以你做了强制转换 (int*)

如果没有 void*malloc 就得为每种类型都写一个版本,比如 malloc_intmalloc_double... 那得疯!


四、void* 的实际应用场景 🛠️

✅ 1. 动态内存分配(malloc / calloc / realloc)

复制代码
void* p = malloc(100);  // 分配100字节,谁知道你要存啥呢?
// 使用前必须转换为具体类型,比如:
int* int_p = (int*)p;
*int_p = 42;

这就是 void* 最常见的用法之一:作为通用内存块的返回值,让程序员自己决定怎么用。


✅ 2. 通用函数参数(比如回调函数、排序、线程函数等)

比如 C 标准库中的 qsort(快速排序函数),它要对任意类型数组排序,但它不知道你排的是 intfloat 还是结构体啊!

怎么办?用 void* 接收任意数据,再搭配一个比较函数,由程序员自己决定怎么比!

复制代码
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
  • base 是待排序数组的首地址,类型未知 → 用 void*

  • 比较函数也是接收两个 void*,内部再强制转换成实际类型来比较

是不是很灵活?但也很考验程序员!


✅ 3. 与 C 语言库交互 / 跨语言调用

很多 C 语言写的库(或者操作系统 API)为了通用性,参数或返回值经常用 void*,表示"我给你一块内存,你想当啥用就当啥用"。

例如:

  • 线程创建函数(如 POSIX 的 pthread_create)可能让你传一个 void* 参数,线程函数也返回 void*

  • 图形库、硬件驱动、网络库等常用 void* 表示"用户数据"或"上下文"


✅ 4. 自己写通用容器(比如简易版 C++ vector)

假如你自己写一个可以存放任意类型数据的数组(类似 C++ 的 vector<T>),在 C 语言里你可能会这样设计:

复制代码
struct MyArray {
    void** data;  // 存放一堆 void*,每个指针指向某个实际数据
    int size;
};

这样你的数组就能装任何东西啦!当然,取出来用的时候,你得自己记得每个元素是什么类型,然后手动转回去。


五、void* 的注意事项 ⚠️(别踩坑!)

虽然 void* 很强大,但就像核能一样,用得好造福人类,用不好就炸了 😅

❗ 1. 不能直接解引用!

复制代码
void* p = malloc(sizeof(int));
*p = 10;  // ❌ 编译错误!不能解引用 void*

你必须先转换为具体类型:

复制代码
int* pi = (int*)p;
*pi = 10;  // ✅ 正确

❗ 2. 类型安全靠你自己!

void* 不会检查你转换得对不对,如果你把一个 double* 转成 int*,然后强行读取,那就是未定义行为(UB),程序可能崩溃、乱输出,或者老板发火 😂


❗ 3. C++ 中尽量少用,有更好的选择!

C++ 引入了更安全、更优雅的机制,比如:

  • 模板(Templates)👉 类型安全,自动推导

  • 继承 + 多态(基类指针)👉 面向对象方式实现通用行为

  • std::any(C++17)👉 类型安全的"任意类型"容器

  • std::variant(C++17)👉 可以是几种类型中的一种

但在一些底层操作、与 C 交互、或者你要极度灵活时,void* 仍然是你的好朋友。


六、终极总结 🎯

项目 说明
void* 是什么? 一个不知道指向啥数据类型的指针,也叫"通用指针"或"无类型指针"
能直接用吗? 不能直接解引用,必须转为具体类型指针后才能访问数据
为啥存在? 为了灵活性和通用性,尤其是在不确定未来数据类型时
常见使用场景 malloc 返回值、通用函数参数、回调机制、与 C 库交互、自己写通用容器等
优点 灵活、通用,能与各种类型协作
缺点 不安全,容易误用,缺乏类型检查
C++ 建议 尽量用模板、多态等替代,但在需要兼容 C 或底层操作时仍可使用

七、打个比方总结 😂

void* 想象成一个魔法口袋 ,你可以往里面塞任何东西(整数、结构体、甚至另一只口袋),但当你想掏东西出来用的时候,你必须清楚地告诉别人(或自己):嘿,这个其实是啥!否则你可能掏出一个砖头,还以为是个蛋糕!

所以,void* 很强大,但用它的时候,请一定:

✅ 记得你存进去的是什么类型

✅ 转换指针类型时要准确

✅ 操作时心中有数,不要乱来


相关推荐
脚踏实地的大梦想家2 小时前
【Go】P19 Go语言并发编程核心(三):从 Channel 安全到互斥锁
开发语言·安全·golang
逻极2 小时前
Rust数据类型(下):复合类型详解
开发语言·后端·rust
星释2 小时前
Rust 练习册 12:所有权系统
开发语言·后端·rust
tianyuanwo2 小时前
Rust开发完全指南:从入门到与Python高效融合
开发语言·python·rust
wydaicls2 小时前
C语言完成Socket通信
c语言·网络·websocket
ShineWinsu2 小时前
对于数据结构:堆的超详细保姆级解析—上
数据结构·c++·算法·计算机·二叉树·顺序表·
im_AMBER3 小时前
Leetcode 46
c语言·c++·笔记·学习·算法·leetcode
民乐团扒谱机3 小时前
脉冲在克尔效应下的频谱展宽仿真:原理与 MATLAB 实现
开发语言·matlab·光电·非线性光学·克尔效应
yuan199973 小时前
基于扩展卡尔曼滤波的电池荷电状态估算的MATLAB实现
开发语言·matlab