在 C11 标准中,_Generic
关键字为 C 语言引入了轻量级的泛型编程能力。尽管 C 语言不像 C++ 那样支持面向对象编程和模板,但它通过 _Generic
提供了一种在编译时根据表达式的类型选择不同代码路径的方式。这使得 C 语言能够在某种程度上实现类似于泛型编程的设计。
泛型编程是一种编程范式,它允许程序员在编写代码时使用一些将来才会指定的类型。这些类型在代码实例化时作为参数指明。例如,在 C++ 中,可以通过模板来支持泛型编程。
std::vector
, std::list
, std::set
在 C 语言中,虽然没有真正意义上的泛型编程,但 C11 标准中的 _Generic
关键字提供了一种在编译时根据赋值表达式的类型在泛型关联表中选择一个表达式的方法。这样可以将一组功能相同但类型不同的函数抽象为一个统一的接口。
_Generic
关键字的基本语法如下:
_Generic ( assignment-expression , generic-assoc-list )
其中:
assignment-expression
:赋值表达式,可以认为是变量 var
。generic-assoc-list
:泛型关联表,其语法为:type-name : expression, type-name : expression, ..., default : expression
让我们通过一个具体的例子来理解如何使用 _Generic
实现泛型编程。
假设我们想要实现一个 getTypeName
函数,该函数返回变量 var
的类型名称。可以这样写:
#define GET_TYPENAME(var) _Generic((var), \
int: "int", \
char: "char", \
float: "float", \
double: "double", \
char*: "char *", \
default: "other type")
int main(int argc, char const *argv[]) {
int x;
int* x1;
char s[10];
printf("type: x = %s\n", GET_TYPENAME(x));
printf("type: x1 = %s\n", GET_TYPENAME(x1));
printf("type: s = %s\n", GET_TYPENAME(s));
return 0;
}
运行结果:
type: x = int
type: x1 = other type
type: s = char *
首先实现一个整形的动态数组:
#include
#include
#include
// 动态数组结构体
typedef struct {
int capacity; // 数组容量
int count; // 当前元素数量
int data[]; // 零长度数组
} DynamicArray;
// 初始化动态数组
DynamicArray* init_dynamic_array(int initial_capacity) {
// 为结构体和元素分配足够的内存
DynamicArray* array = (DynamicArray*)malloc(sizeof(DynamicArray) + initial_capacity * sizeof(int));
if (array) {
array->capacity = initial_capacity;
array->count = 0;
}
return array;
}
// 增加数组容量
DynamicArray* ensure_capacity(DynamicArray* array, int min_capacity) {
if (min_capacity > array->capacity) {
int new_capacity = (array->capacity * 3) / 2 + 1; // 新容量至少增加50%
if (new_capacity < min_capacity) {
new_capacity = min_capacity;
}
// 重新分配内存以适应新的大小
DynamicArray* new_array = (DynamicArray*)realloc(array, sizeof(DynamicArray) + new_capacity * sizeof(int));
if (new_array) {
new_array->capacity = new_capacity;
return new_array;
}
}
return array; // 如果不需要扩容,返回原数组
}
// 向动态数组中添加元素
void append(DynamicArray** array, int element) {
// 先确保有足够的容量
*array = ensure_capacity(*array, (*array)->count + 1);
// 添加元素
(*array)->data[(*array)->count] = element;
(*array)->count++;
}
// 打印动态数组的内容
void print_array(const DynamicArray* array) {
for (int i = 0; i < array->count; ++i) {
printf("%d ", array->data[i]);
}
printf("\n");
}
// 销毁动态数组
void destroy_dynamic_array(DynamicArray* array) {
free(array);
}
int main() {
int initial_capacity = 5;
printf("sizeof DynamicArray: %ld\n", sizeof(DynamicArray));
DynamicArray* array = init_dynamic_array(initial_capacity); // 初始容量为5
// 添加一些元素
for (int i = 0; i < 10; ++i) {
append(&array, i);
}
// 打印动态数组
print_array(array);
// 销毁动态数组
destroy_dynamic_array(array);
return 0;
}
下面我们再实现一个泛型的动态数组
#include
#include
#include
// 泛型动态数组结构体
typedef struct {
int capacity; // 数组容量
int count; // 当前元素数量
size_t elem_size; // 元素大小
int (*data)[0]; // 零长度数组
} GenericDynamicArray;
// 初始化泛型动态数组
GenericDynamicArray* init_generic_dynamic_array(int initial_capacity, size_t elem_size) {
// 为结构体和元素分配足够的内存
GenericDynamicArray* array = (GenericDynamicArray*)malloc(sizeof(GenericDynamicArray) + initial_capacity * elem_size);
if (array) {
array->capacity = initial_capacity;
array->count = 0;
array->elem_size = elem_size;
array->data = (int(*)[0])(((char*)array) + sizeof(GenericDynamicArray)); // 计算数据起始地址
}
return array;
}
// 增加泛型动态数组容量
void ensure_capacity(GenericDynamicArray *array, size_t min_capacity) {
if (min_capacity > array->capacity) {
size_t new_capacity = (array->capacity * 3) / 2 + 1;
if (new_capacity < min_capacity) {
new_capacity = min_capacity;
}
array = realloc(array, sizeof(GenericDynamicArray) + new_capacity * array->elem_size);
array->capacity = new_capacity;
}
}
// 向泛型动态数组中添加元素
#define append(array, element) _Generic((element), \
int: append_int, \
float: append_float, \
char: append_char \
)(array, element)
// 特化版本的 append 函数
void append_int(GenericDynamicArray *array, int element) {
ensure_capacity(array, array->count + 1);
((int *)array->data)[array->count] = element;
array->count++;
}
void append_float(GenericDynamicArray *array, float element) {
ensure_capacity(array, array->count + 1);
((float *)array->data)[array->count] = element;
array->count++;
}
void append_char(GenericDynamicArray *array, char element) {
ensure_capacity(array, array->count + 1);
((char *)array->data)[array->count] = element;
array->count++;
}
// 打印泛型动态数组的内容
void print_array(const GenericDynamicArray* array, void (*print)(const void *)) {
for (int i = 0; i < array->count; ++i) {
print((char*)array->data + i * array->elem_size);
}
printf("\n");
}
// 泛型打印函数
void generic_print_int(const void *data) {
printf("%d ", *(int *)data);
}
void generic_print_long(const void *data) {
printf("%ld ", *(long *)data);
}
void generic_print_double(const void *data) {
printf("%f ", *(double *)data);
}
void generic_print_char(const void *data) {
printf("%c ", *(char *)data);
}
void generic_print_void(const void *data) {
printf("%p ", data);
}
int main() {
int initial_capacity = 5;
printf("sizeof GenericDynamicArray: %ld\n", sizeof(GenericDynamicArray));
GenericDynamicArray* array_int = init_generic_dynamic_array(initial_capacity, sizeof(int)); // 初始容量为5
// 添加一些整数元素
int int_data[] = {1, 2, 3, 4, 5, 6, 7 , 8, 9};
for (int i = 0; i < sizeof(int_data) / sizeof(int); ++i) {
append(array_int, int_data[i]);
}
// 打印动态数组
print_array(array_int, generic_print_int);
// 销毁动态数组
free(array_int);
GenericDynamicArray* array_char = init_generic_dynamic_array(initial_capacity, sizeof(char)); // 初始容量为5
char char_data[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'};
for (int i = 0; i < sizeof(char_data) / sizeof(char); ++i) {
append(array_char, char_data[i]);
}
print_array(array_char, generic_print_char);
free(array_char);
return 0;
}
这个示例虽然看着比较笨,但是我们仍然实现了append的泛型。
default
子句不是必须的,但在实际应用中建议包含,以处理未预期的类型。generic-assoc-list
中的 expression
仅仅是普通的宏替换,因此可以自由地放置参数位置。通过使用 _Generic
关键字,C 语言可以在一定程度上实现泛型编程,使程序更加灵活和通用。然而,需要注意的是,_Generic
的使用仅限于编译时类型选择,而且表达式只是简单的宏替换。此外,对于复杂的类型和多种参数组合,还需要进一步考虑如何正确处理不同类型之间的转换。
欢迎关注:GarenJian