前置知识: C

泛型选择

2 minIntermediate2026/6/14

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;
}