泛型选择
C11 _Generic泛型选择
概述
C11 标准引入了 _Generic 泛型选择表达式,允许根据表达式的类型在编译时选择不同的值。这是C语言实现泛型编程的基础工具,类似于其他语言中的模式匹配或函数重载。_Generic 常与宏结合使用,实现类型安全的泛型函数。
基础概念
_Generic 语法
_Generic(表达式,
类型1: 结果1,
类型2: 结果2,
...
default: 默认结果
)
_Generic 在编译时根据表达式的类型选择对应的分支,返回该分支的值。表达式的值本身不会被求值,只有其类型被检查。
类型匹配规则
- 匹配的是表达式的类型,不是值的类别
- 不会进行隐式类型转换,必须精确匹配
default分支是可选的,但如果没有匹配且没有 default,编译会报错- 限定符(如
const)会影响匹配
快速上手
获取类型名称
#include <stdio.h>
// 根据类型返回类型名称字符串
#define type_name(x) _Generic((x), \
char: "char", \
signed char: "signed char", \
unsigned char: "unsigned char", \
short: "short", \
int: "int", \
long: "long", \
float: "float", \
double: "double", \
char *: "char*", \
int *: "int*", \
void *: "void*", \
default: "other" \
)
int main(void) {
printf("42 的类型: %s\n", type_name(42)); // int
printf("3.14 的类型: %s\n", type_name(3.14)); // double
printf("'a' 的类型: %s\n", type_name('a')); // int(字符字面量是int)
printf("\"hi\" 的类型: %s\n", type_name("hi")); // char*
return 0;
}
泛型打印函数
#include <stdio.h>
// 为每种类型定义打印函数
void print_int(int x) { printf("%d", x); }
void print_long(long x) { printf("%ld", x); }
void print_double(double x) { printf("%f", x); }
void print_string(const char *x) { printf("%s", x); }
void print_char(char x) { printf("%c", x); }
// 泛型打印宏
#define print(x) _Generic((x), \
int: print_int, \
long: print_long, \
double: print_double, \
char*: print_string, \
const char*: print_string, \
char: print_char \
)(x)
int main(void) {
print(42); printf("\n"); // 输出: 42
print(3.14); printf("\n"); // 输出: 3.140000
print("Hello"); printf("\n"); // 输出: Hello
print('A'); printf("\n"); // 输出: A
return 0;
}
详细用法
泛型数学函数
#include <stdio.h>
#include <math.h>
// 泛型求绝对值
#define abs_val(x) _Generic((x), \
int: abs, \
long: labs, \
long long: llabs, \
float: fabsf, \
double: fabs, \
long double: fabsl \
)(x)
// 泛型求平方根
#define sqrt_val(x) _Generic((x), \
float: sqrtf, \
double: sqrt, \
long double: sqrtl \
)(x)
int main(void) {
printf("abs(-5) = %d\n", abs_val(-5)); // 5
printf("abs(-5L) = %ld\n", abs_val(-5L)); // 5
printf("abs(-3.14) = %f\n", abs_val(-3.14)); // 3.14
printf("sqrt(2.0) = %f\n", sqrt_val(2.0)); // 1.414214
return 0;
}
泛型容器操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 泛型比较函数
#define compare(a, b) _Generic((a), \
int: compare_int, \
double: compare_double, \
const char*: compare_string \
)((a), (b))
int compare_int(int a, int b) { return a - b; }
int compare_double(double a, double b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
int compare_string(const char *a, const char *b) {
return strcmp(a, b);
}
int main(void) {
printf("compare(3, 5) = %d\n", compare(3, 5)); // -2
printf("compare(3.14, 2.72) = %d\n", compare(3.14, 2.72)); // 1
printf("compare(\"abc\", \"def\") = %d\n", compare("abc", "def")); // -3
return 0;
}
泛型最大值/最小值
#include <stdio.h>
// 泛型 MAX
#define MAX(a, b) _Generic((a), \
int: max_int, \
long: max_long, \
double: max_double, \
float: max_float \
)((a), (b))
static inline int max_int(int a, int b) { return a > b ? a : b; }
static inline long max_long(long a, long b) { return a > b ? a : b; }
static inline double max_double(double a, double b) { return a > b ? a : b; }
static inline float max_float(float a, float b) { return a > b ? a : b; }
int main(void) {
printf("MAX(3, 5) = %d\n", MAX(3, 5)); // 5
printf("MAX(100L, 200L) = %ld\n", MAX(100L, 200L)); // 200
printf("MAX(3.14, 2.72) = %f\n", MAX(3.14, 2.72)); // 3.14
return 0;
}
常见场景
场景一:类型安全的泛型打印
#include <stdio.h>
#include <stdint.h>
// 泛型格式化打印
#define print_value(x) do { \
_Generic((x), \
int: printf("int: %d\n", (x)), \
unsigned int: printf("unsigned int: %u\n", (x)), \
long: printf("long: %ld\n", (x)), \
unsigned long: printf("unsigned long: %lu\n", (x)), \
float: printf("float: %f\n", (x)), \
double: printf("double: %f\n", (x)), \
char: printf("char: %c\n", (x)), \
char *: printf("string: %s\n", (x)), \
const char *: printf("string: %s\n", (x)), \
void *: printf("pointer: %p\n", (x)), \
default: printf("unknown type\n") \
); \
} while (0)
int main(void) {
print_value(42);
print_value(3.14);
print_value("hello");
print_value((void *)0x1000);
return 0;
}
场景二:泛型序列化
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 泛型写入二进制数据
#define write_binary(fp, x) _Generic((x), \
int: fwrite(&(x), sizeof(int), 1, (fp)), \
double: fwrite(&(x), sizeof(double), 1, (fp)), \
float: fwrite(&(x), sizeof(float), 1, (fp)), \
char: fwrite(&(x), sizeof(char), 1, (fp)) \
)
// 泛型读取二进制数据
#define read_binary(fp, x) _Generic((x), \
int*: fread((x), sizeof(int), 1, (fp)), \
double*: fread((x), sizeof(double), 1, (fp)), \
float*: fread((x), sizeof(float), 1, (fp)), \
char*: fread((x), sizeof(char), 1, (fp)) \
)
int main(void) {
FILE *fp = fopen("data.bin", "wb");
if (!fp) return 1;
int age = 25;
double score = 95.5;
write_binary(fp, age);
write_binary(fp, score);
fclose(fp);
// 读取
fp = fopen("data.bin", "rb");
int read_age;
double read_score;
read_binary(fp, &read_age);
read_binary(fp, &read_score);
fclose(fp);
printf("年龄: %d, 成绩: %.1f\n", read_age, read_score);
return 0;
}
场景三:泛型哈希函数
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 整数哈希
uint32_t hash_int(int val) {
val = ((val >> 16) ^ val) * 0x45d9f3b;
val = ((val >> 16) ^ val) * 0x45d9f3b;
val = (val >> 16) ^ val;
return (uint32_t)val;
}
// 字符串哈希(djb2算法)
uint32_t hash_string(const char *str) {
uint32_t hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c;
}
return hash;
}
// 指针哈希
uint32_t hash_ptr(const void *ptr) {
uintptr_t val = (uintptr_t)ptr;
val = (val >> 16) ^ val;
return (uint32_t)val;
}
// 泛型哈希宏
#define hash(x) _Generic((x), \
int: hash_int, \
const char *: hash_string, \
char *: hash_string, \
void *: hash_ptr, \
default: hash_ptr \
)(x)
int main(void) {
printf("hash(42) = %u\n", hash(42));
printf("hash(\"hello\") = %u\n", hash("hello"));
return 0;
}
注意事项
类型必须精确匹配
_Generic 不会进行隐式类型转换,必须精确匹配类型:
#include <stdio.h>
#define test(x) _Generic((x), \
int: "int", \
double: "double" \
)
int main(void) {
printf("%s\n", test(42)); // int
printf("%s\n", test(42.0)); // double
// printf("%s\n", test(42L)); // 编译错误:long 没有匹配项
// printf("%s\n", test(42.0f)); // 编译错误:float 没有匹配项
return 0;
}
const 和 volatile 影响匹配
// const int 和 int 是不同的类型
const int x = 42;
// _Generic(x, int: "int") // 编译错误!x 是 const int
// 需要添加 const 分支
#define type_name(x) _Generic((x), \
int: "int", \
const int: "const int", \
int *: "int*", \
const int *: "const int*" \
)
字符字面量的类型
在C语言中,字符字面量的类型是 int,不是 char:
// 'a' 的类型是 int,不是 char
_Generic('a', int: "int", char: "char") // 匹配 "int"
数组与指针
数组作为表达式时会退化为指针:
int arr[10];
// _Generic(arr, int *: "pointer") // 匹配 int*
// _Generic(arr, int[10]: "array") // 不匹配
进阶用法
实现泛型栈
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 泛型栈宏
#define DEFINE_STACK(type) \
typedef struct { \
type *data; \
size_t size; \
size_t capacity; \
} Stack_##type; \
\
Stack_##type stack_##type##_create(void) { \
Stack_##type s = {NULL, 0, 0}; \
return s; \
} \
\
void stack_##type##_push(Stack_##type *s, type val) { \
if (s->size >= s->capacity) { \
s->capacity = s->capacity ? s->capacity * 2 : 4; \
s->data = realloc(s->data, s->capacity * sizeof(type)); \
} \
s->data[s->size++] = val; \
} \
\
type stack_##type##_pop(Stack_##type *s) { \
return s->data[--s->size]; \
} \
\
type stack_##type##_top(Stack_##type *s) { \
return s->data[s->size - 1]; \
} \
\
void stack_##type##_free(Stack_##type *s) { \
free(s->data); \
s->data = NULL; \
s->size = s->capacity = 0; \
}
// 为 int 和 double 生成栈
DEFINE_STACK(int)
DEFINE_STACK(double)
int main(void) {
Stack_int si = stack_int_create();
stack_int_push(&si, 10);
stack_int_push(&si, 20);
stack_int_push(&si, 30);
printf("栈顶: %d\n", stack_int_top(&si)); // 30
printf("弹出: %d\n", stack_int_pop(&si)); // 30
stack_int_free(&si);
Stack_double sd = stack_double_create();
stack_double_push(&sd, 3.14);
stack_double_push(&sd, 2.72);
printf("栈顶: %f\n", stack_double_top(&sd)); // 2.72
stack_double_free(&sd);
return 0;
}
使用 _Generic 实现类型特征
#include <stdio.h>
#include <stdbool.h>
// 判断是否为整数类型
#define is_integer(x) _Generic((x), \
char: true, \
signed char: true, \
unsigned char: true, \
short: true, \
unsigned short: true, \
int: true, \
unsigned int: true, \
long: true, \
unsigned long: true, \
long long: true, \
unsigned long long: true, \
default: false \
)
// 判断是否为浮点类型
#define is_floating(x) _Generic((x), \
float: true, \
double: true, \
long double: true, \
default: false \
)
// 判断是否为指针类型
#define is_pointer(x) _Generic((x), \
void *: true, \
char *: true, \
int *: true, \
default: false \
)
int main(void) {
printf("42 是整数: %s\n", is_integer(42) ? "是" : "否"); // 是
printf("3.14 是整数: %s\n", is_integer(3.14) ? "是" : "否"); // 否
printf("3.14 是浮点: %s\n", is_floating(3.14) ? "是" : "否"); // 是
return 0;
}
模拟函数重载
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 为不同类型实现 to_string
void int_to_string(int val, char *buf, size_t size) {
snprintf(buf, size, "%d", val);
}
void double_to_string(double val, char *buf, size_t size) {
snprintf(buf, size, "%.2f", val);
}
void string_to_string(const char *val, char *buf, size_t size) {
snprintf(buf, size, "%s", val);
}
// 泛型 to_string 宏
#define to_string(val, buf, size) _Generic((val), \
int: int_to_string, \
double: double_to_string, \
const char *: string_to_string, \
char *: string_to_string \
)((val), (buf), (size))
int main(void) {
char buf[64];
to_string(42, buf, sizeof(buf));
printf("int: %s\n", buf);
to_string(3.14159, buf, sizeof(buf));
printf("double: %s\n", buf);
to_string("Hello", buf, sizeof(buf));
printf("string: %s\n", buf);
return 0;
}