Menu
CoddyTech

泛型包装器

本课属于 Coddy C 学习旅程的“面向对象编程”单元。第 50 课,共 61 课。

单独的 void* 可以存储任意数据,但它有一个关键缺陷:你无法追踪它存储的是什么类型。要构建真正实用的通用容器,我们需要将数据指针与类型信息配对。这就是 通用包装器模式的用武之地。

思路很简单:创建一个结构体,将 void* 和一个用于标识所存储类型的枚举绑定在一起。这样,封装器就始终知道如何解释其中的内容。

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;
typedef struct {
    void* data;
    DataType type;
} Wrapper;

存储数据时,你会为实际值分配内存,并记录它的类型。取回数据时,你会在进行强制类型转换前检查类型:

Wrapper wrap_int(int value) {
    int* p = malloc(sizeof(int));
    *p = value;
    return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
    if (w->type == TYPE_INT) {
        printf("%d\n", *(int*)w->data);
    } else if (w->type == TYPE_STRING) {
        printf("%s\n", (char*)w->data);
    }
}

这种模式是用 C 构建类型安全的泛型容器的基础。这个枚举充当运行时类型标签,让你的代码能够安全地决定如何处理存储的数据。

challenge icon

挑战

简单

我们来构建一个类型安全的通用包装器系统,它可以存储整数或字符串,并记录所存储的数据类型,以便之后安全地获取并打印数据。

你要将代码分成三个文件:

  • wrapper.h:定义一个枚举 DataType,其中包含 TYPE_INT 和 TYPE_STRING。然后定义一个结构体 Wrapper,其中包含一个 void* 数据指针和一个 DataType 字段。声明用于创建包装器和打印其内容的函数原型。
  • wrapper.c:实现包装器功能:
    • wrap_int:接收一个整数,为它分配内存,存储该值,并返回带有相应类型标记的 Wrapper
    • wrap_string:接收一个字符串(char*),分配内存并复制字符串,然后返回带有字符串类型标记的 Wrapper
    • print_wrapper:检查类型标记,并按相应方式打印数据(整数原样打印,字符串也原样打印)
    • free_wrapper:释放包装器中动态分配的数据
  • main.c:将所有部分组合起来。读取类型指示符(i 表示整数,s 表示字符串),然后读取值。创建相应的包装器,打印其内容,并释放内存。

你的程序会收到两个输入:

  1. 类型指示符:i 表示整数,s 表示字符串
  2. 要包装的值

输入为 i 和 42 时的示例输出:

42

输入为 s 和 Hello 时的示例输出:

Hello

输入为 i 和 -100 时的示例输出:

-100

输入为 s 和 Generic Programming 时的示例输出:

Generic Programming

记得在头文件中使用包含保护。复制字符串时,使用 strlen 和 strcpy 分配足够的空间,以容纳字符串和空终止符。

亲手试试

#include <stdio.h>
#include <string.h>
#include "wrapper.h"

int main() {
    char type;
    scanf("%c", &type);
    getchar(); // 读取并丢弃换行符
    
    Wrapper w;
    
    if (type == 'i') {
        int value;
        scanf("%d", &value);
        // TODO: 使用 wrap_int 创建整数包装器
        
    } else if (type == 's') {
        char str[256];
        fgets(str, sizeof(str), stdin);
        // 如果末尾有换行符,就将其删除
        str[strcspn(str, "\n")] = '\0';
        // TODO: 使用 wrap_string 创建字符串包装器
        
    }
    
    // TODO: 使用 print_wrapper 输出包装器的内容
    
    // TODO: 使用 free_wrapper 释放包装器占用的内存
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C 在线编译器