泛型包装器
本课属于 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 构建类型安全的泛型容器的基础。这个枚举充当运行时类型标签,让你的代码能够安全地决定如何处理存储的数据。
挑战
简单我们来构建一个类型安全的通用包装器系统,它可以存储整数或字符串,并记录所存储的数据类型,以便之后安全地获取并打印数据。
你要将代码分成三个文件:
wrapper.h:定义一个枚举DataType,其中包含TYPE_INT和TYPE_STRING。然后定义一个结构体Wrapper,其中包含一个void*数据指针和一个DataType字段。声明用于创建包装器和打印其内容的函数原型。wrapper.c:实现包装器功能:wrap_int:接收一个整数,为它分配内存,存储该值,并返回带有相应类型标记的 Wrapperwrap_string:接收一个字符串(char*),分配内存并复制字符串,然后返回带有字符串类型标记的 Wrapperprint_wrapper:检查类型标记,并按相应方式打印数据(整数原样打印,字符串也原样打印)free_wrapper:释放包装器中动态分配的数据
main.c:将所有部分组合起来。读取类型指示符(i表示整数,s表示字符串),然后读取值。创建相应的包装器,打印其内容,并释放内存。
你的程序会收到两个输入:
- 类型指示符:
i表示整数,s表示字符串 - 要包装的值
输入为 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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C 在线编译器