当前位置:首页>java>C语言泛型:你的代码有多“能装”?

C语言泛型:你的代码有多“能装”?

  • 2026-10-21 13:28:52
C语言泛型:你的代码有多“能装”?

前言导读

嘿,学完C语言后对“泛型”还一脸懵逼的同学,举起你们的双手让我看看!别害羞,这就像是学游泳时呛了第一口水——90%的人都经历过。今天,就让我用最不正经的方式,给你讲清楚这个“正经”的概念。

一

什么是泛型?你的代码衣柜

想象一下你的衣柜。如果没有泛型,你的衣柜会是这样的:

// 专门放T恤的柜子

void tshirt_closet(char tshirts[100]) {

    // 只能放T恤

}

// 专门放裤子的柜子  

void pants_closet(int pants[50]) {

    // 只能放裤子

}

// 专门放袜子的柜子

void socks_closet(float socks[200]) {

    // 呃...为什么袜子是浮点数?

}

每次买新衣服都要买个新柜子!这就是没有泛型的世界——代码冗余到怀疑人生。

而泛型就像是你的万能衣柜:

// 理想中的泛型衣柜(可惜C语言原生不支持)

// GenericCloset<T> my_closet;  // 这是C++/Java的写法,别急眼

在C语言里,我们要用各种“歪门邪道”来实现这个万能衣柜。准备好了吗?系好安全带,老司机要发车了!

二

void* —— C语言的“薛定谑的盒子”

void*是C语言里最著名的“我不知道里面是什么”指针。它就像你女朋友说“随便”时的心情——可以是任何东西,但也可能什么都不是。

#include <stdio.h>

#include <string.h>

// 一个能“装”任何东西的盒子

void* mystery_box;

int main() {

    int my_int = 42;

    float my_float = 3.14;

    char my_string[] = "Hello World";

    // 盒子里放个整数

    mystery_box = &my_int;

    printf("盒子里可能是: %d\n", *(int*)mystery_box);

    // 同样的盒子,放个浮点数

    mystery_box = &my_float;

    printf("盒子里可能是: %.2f\n", *(float*)mystery_box);

    // 放个字符串也没问题!

    mystery_box = my_string;

    printf("盒子里可能是: %s\n", (char*)mystery_box);

    // 危险操作:你以为盒子里是整数,其实是字符串

    mystery_box = my_string;

    // printf("灾难发生: %d\n", *(int*)mystery_box); // 段错误警告!

    return 0;

}

void*的问题在于:编译器完全放弃治疗了。它就像个甩手掌柜:“你想放什么就放什么吧,出错别来找我!”

三

宏函数 —— 代码复印机的艺术

如果void*是薛定谑的盒子,那么宏就是C语言的魔法复印机。不过这个复印机有点调皮,经常乱改你的作业。

#include <stdio.h>

// 普通函数:一次只能处理一种类型

int max_int(int a, int b) { return a > b ? a : b; }

float max_float(float a, float b) { return a > b ? a : b; }

// 写20个这样的函数后,你的手已经废了

// 宏函数:一键生成所有版本

#define MAX(type) type max_##type(type a, type b) { \

    printf("调用" #type "版本\n"); \

    return a > b ? a : b; \

}

// 让宏给我们打工

MAX(int)      // 生成 max_int

MAX(float)    // 生成 max_float

MAX(double)   // 生成 max_double

// 甚至能处理自定义类型!

typedef struct { int x, y; } Point;

#define MAX_POINT(p1, p2) ((p1.x + p1.y) > (p2.x + p2.y) ? p1 : p2)

int main() {

    printf("最大值: %d\n", max_int(5, 10));

    printf("最大值: %.2f\n", max_float(3.14, 2.71));

    Point p1 = {1, 2}, p2 = {3, 1};

    Point p3 = MAX_POINT(p1, p2);

    printf("更大的点: (%d, %d)\n", p3.x, p3.y);

    return 0;

}

宏的坑爹之处:它只是文本替换,不做类型检查。比如这个经典翻车现场:

#define SQUARE(x) x * x

int main() {

    int a = 5;

    printf("5的平方: %d\n", SQUARE(a));      // 25,正确

    printf("5+2的平方: %d\n", SQUARE(a+2));  // 5+2 * 5+2=17,错误!

    printf("a++的平方: %d\n", SQUARE(a++));   // 灾难!a被加了两次

    return 0;

}

改进方案:多打括号,保平安:

#define SAFE_SQUARE(x) ((x) * (x))

四

实战!打造泛型容器

理论说太多会睡着,来点实战!让我们用void*打造一个“什么都能装”的动态数组:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <assert.h>

// 泛型动态数组结构

typedef struct {

    void* data;           // 数据指针

    size_t elem_size;     // 每个元素的大小

    size_t capacity;      // 容量

    size_t length;        // 当前长度

} GenericArray;

// 创建数组

GenericArray* ga_create(size_t elem_size, size_t init_capacity) {

    GenericArray* arr = malloc(sizeof(GenericArray));

    arr->elem_size = elem_size;

    arr->capacity = init_capacity > 0 ? init_capacity : 4;

    arr->length = 0;

    arr->data = malloc(elem_size * arr->capacity);

    return arr;

}

// 追加元素

void ga_append(GenericArray* arr, void* element) {

    if (arr->length >= arr->capacity) {

        arr->capacity *= 2;

        arr->data = realloc(arr->data, arr->elem_size * arr->capacity);

    }

    void* dest = (char*)arr->data + arr->length * arr->elem_size;

    memcpy(dest, element, arr->elem_size);

    arr->length++;

}

// 获取元素

void* ga_get(GenericArray* arr, size_t index) {

    if (index >= arr->length) {

        fprintf(stderr, "索引 %zu 超出范围 (0-%zu)\n", index, arr->length-1);

        return NULL;

    }

    return (char*)arr->data + index * arr->elem_size;

}

// 释放数组

void ga_free(GenericArray* arr) {

    free(arr->data);

    free(arr);

}

// 打印数组的宏(需要知道类型)

#define PRINT_ARRAY(arr_ptr, type, format) do { \

    printf("["); \

    for (size_t i = 0; i < (arr_ptr)->length; i++) { \

        type* elem = (type*)ga_get(arr_ptr, i); \

        printf(format, *elem); \

        if (i < (arr_ptr)->length - 1) printf(", "); \

    } \

    printf("]\n"); \

} while(0)

int main() {

    printf("=== 泛型数组演示 ===\n\n");

    // 1. 整数数组

    printf("1. 整数数组:\n");

    GenericArray* int_arr = ga_create(sizeof(int), 2);

    for (int i = 1; i <= 5; i++) {

        ga_append(int_arr, &i);

    }

    PRINT_ARRAY(int_arr, int, "%d");

    // 2. 浮点数数组

    printf("\n2. 浮点数数组:\n");

    GenericArray* float_arr = ga_create(sizeof(float), 3);

    float floats[] = {3.14, 2.71, 1.41, 0.99};

    for (int i = 0; i < 4; i++) {

        ga_append(float_arr, &floats[i]);

    }

    PRINT_ARRAY(float_arr, float, "%.2f");

    // 3. 字符串数组(其实是char*数组)

    printf("\n3. 字符串数组:\n");

    GenericArray* str_arr = ga_create(sizeof(char*), 2);

    char* names[] = {"小明", "小红", "老张", "程序员"};

    for (int i = 0; i < 4; i++) {

        ga_append(str_arr, &names[i]);

    }

    printf("[");

    for (size_t i = 0; i < str_arr->length; i++) {

        char** str_ptr = (char**)ga_get(str_arr, i);

        printf("\"%s\"", *str_ptr);

        if (i < str_arr->length - 1) printf(", ");

    }

    printf("]\n");

    // 4. 结构体数组

    printf("\n4. 学生结构体数组:\n");

    typedef struct {

        char name[20];

        int age;

        float score;

    } Student;

    GenericArray* student_arr = ga_create(sizeof(Student), 2);

    Student s1 = {"张三", 20, 89.5};

    Student s2 = {"李四", 21, 92.0};

    Student s3 = {"王五", 19, 78.5};

    ga_append(student_arr, &s1);

    ga_append(student_arr, &s2);

    ga_append(student_arr, &s3);

    for (size_t i = 0; i < student_arr->length; i++) {

        Student* s = (Student*)ga_get(student_arr, i);

        printf("  学生%d: 姓名=%s, 年龄=%d, 分数=%.1f\n", 

               (int)i+1, s->name, s->age, s->score);

    }

    // 清理内存

    ga_free(int_arr);

    ga_free(float_arr);

    ga_free(str_arr);

    ga_free(student_arr);

    return 0;

}

五

C11的黑科技——_Generic

C11标准带来了_Generic,它是类型选择的智能开关,让泛型编程稍微体面了一点:

#include <stdio.h>

#include <math.h>

// 根据类型选择不同的打印函数

#define PRINT(x) _Generic((x), \

    int: print_int, \

    double: print_double, \

    float: print_float, \

    char*: print_string, \

    default: print_unknown \

)(x)

void print_int(int x) { printf("整数: %d\n", x); }

void print_double(double x) { printf("双精度: %f\n", x); }

void print_float(float x) { printf("浮点数: %f\n", x); }

void print_string(char* x) { printf("字符串: %s\n", x); }

void print_unknown() { printf("未知类型\n"); }

// 更实用的例子:类型安全的比较

#define COMPARE(a, b) _Generic((a), \

    int: compare_int, \

    double: compare_double, \

    char*: compare_string \

)(a, b)

int compare_int(int a, int b) { 

    if (a == b) return 0;

    return a > b ? 1 : -1;

}

int compare_double(double a, double b) {

    double diff = a - b;

    if (fabs(diff) < 1e-10) return 0;

    return diff > 0 ? 1 : -1;

}

int compare_string(char* a, char* b) {

    return strcmp(a, b);

}

int main() {

    printf("=== _Generic演示 ===\n\n");

    PRINT(42);           // 整数

    PRINT(3.1415926);   // 双精度

    PRINT(2.71f);       // 浮点数

    PRINT("Hello");     // 字符串

    printf("\n比较结果:\n");

    printf("比较5和10: %d\n", COMPARE(5, 10));

    printf("比较3.14和2.71: %d\n", COMPARE(3.14, 2.71));

    printf("比较\"apple\"和\"banana\": %d\n", COMPARE("apple", "banana"));

    return 0;

}

六

泛型排序算法——一招鲜吃遍天

真正的泛型威力在于算法复用。比如实现一个泛型快速排序:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

// 泛型交换函数

void swap(void* a, void* b, size_t size) {

    char temp[size];  // C99变长数组

    memcpy(temp, a, size);

    memcpy(a, b, size);

    memcpy(b, temp, size);

}

// 泛型分区函数

int partition(void* arr, int low, int high, size_t elem_size, 

              int (*compare)(const void*, const void*)) {

    char* array = (char*)arr;

    void* pivot = malloc(elem_size);

    memcpy(pivot, array + high * elem_size, elem_size);

    int i = low - 1;

    for (int j = low; j < high; j++) {

        if (compare(array + j * elem_size, pivot) < 0) {

            i++;

            swap(array + i * elem_size, array + j * elem_size, elem_size);

        }

    }

    swap(array + (i + 1) * elem_size, array + high * elem_size, elem_size);

    free(pivot);

    return i + 1;

}

// 泛型快速排序

void generic_quicksort(void* arr, int low, int high, size_t elem_size,

                      int (*compare)(const void*, const void*)) {

    if (low < high) {

        int pi = partition(arr, low, high, elem_size, compare);

        generic_quicksort(arr, low, pi - 1, elem_size, compare);

        generic_quicksort(arr, pi + 1, high, elem_size, compare);

    }

}

// 比较函数

int compare_int(const void* a, const void* b) {

    return *(int*)a - *(int*)b;

}

int compare_double(const void* a, const void* b) {

    double diff = *(double*)a - *(double*)b;

    if (diff > 0) return 1;

    if (diff < 0) return -1;

    return 0;

}

typedef struct {

    char name[20];

    int score;

} Student;

int compare_student(const void* a, const void* b) {

    return ((Student*)b)->score - ((Student*)a)->score;  // 降序排列

}

int main() {

    printf("=== 泛型快速排序演示 ===\n\n");

    srand(time(NULL));

    // 1. 排序整数数组

    printf("1. 排序整数数组:\n");

    int ints[10];

    printf("排序前: ");

    for (int i = 0; i < 10; i++) {

        ints[i] = rand() % 100;

        printf("%d ", ints[i]);

    }

    generic_quicksort(ints, 0, 9, sizeof(int), compare_int);

    printf("\n排序后: ");

    for (int i = 0; i < 10; i++) {

        printf("%d ", ints[i]);

    }

    printf("\n\n");

    // 2. 排序浮点数数组

    printf("2. 排序浮点数数组:\n");

    double doubles[8];

    printf("排序前: ");

    for (int i = 0; i < 8; i++) {

        doubles[i] = (rand() % 1000) / 10.0;

        printf("%.1f ", doubles[i]);

    }

    generic_quicksort(doubles, 0, 7, sizeof(double), compare_double);

    printf("\n排序后: ");

    for (int i = 0; i < 8; i++) {

        printf("%.1f ", doubles[i]);

    }

    printf("\n\n");

    // 3. 排序学生结构体数组

    printf("3. 按成绩降序排列学生:\n");

    Student students[5] = {

        {"张三", 85}, {"李四", 92}, {"王五", 78}, {"赵六", 95}, {"钱七", 88}

    };

    printf("排序前:\n");

    for (int i = 0; i < 5; i++) {

        printf("  %s: %d分\n", students[i].name, students[i].score);

    }

    generic_quicksort(students, 0, 4, sizeof(Student), compare_student);

    printf("\n排序后:\n");

    for (int i = 0; i < 5; i++) {

        printf("  %s: %d分\n", students[i].name, students[i].score);

    }

    return 0;

}

七

泛型的黑暗面与生存指南

黑暗面:

▶ 类型安全是传说:编译器完全放弃检查,运行时错误等着你

▶ 调试像侦探破案:错误信息基本没用,全靠printf大法

▶ 可读性差:void*满天飞,看代码像猜谜

▶ 性能损失:类型转换和函数指针调用有开销

生存指南:

▶ 做好防御:使用assert和边界检查

▶ 注释要详细:每个void*都要说明原本类型

▶ 写测试代码:各种类型都要测试一遍

▶ 考虑用C++:如果项目复杂,真的...考虑用C++吧

八

什么时候用泛型?(选择题)

A. 当你有多个相似数据结构时 ✅

B. 当你想装逼时 ❌

C. 当需要算法复用时 ✅

D. 当你不想写重复代码时 ✅

E. 所有情况下都用 ❌(记住,KISS原则:Keep It Simple, Stupid)

结语——泛型是你的工具,不是你的信仰

学完泛型,你现在有了两种超能力:

▲ 写出能处理任何类型的通用代码

▲ 写出能让你debug到凌晨3点的通用bug

C语言的泛型就像瑞士军刀——功能多,但每个功能都不如专用工具好用。它给了你自由,但也给了你足够多的绳子把自己吊死。

最后送大家一句话:泛型虽好,可不要贪杯哦! 在简单和通用之间找到平衡,才是好程序员的标志。

现在,去写点泛型代码吧!记得先保存,别问我为什么知道要提醒你这个...(血泪教训啊!)

关注我们

“三度编程”是一家专注于青少儿编程培训的教育机构,专业培训scratch、python、c++等少儿编程课程,旗下学员多人参加蓝桥杯、中国电子学会、ACT等知名编程赛事,多次获得国、省、市、区、校级竞赛奖状,被誉为“少儿编程十大优秀品牌”“诚信办学单位”“年度影响力青少儿编程品牌”“少儿编程金牌团队”。

微信号|sanducoding001

客服电话 |  15001141507

办公地址 | 苏州昆山吾悦广场

服务地区 |  苏州、上海、北京,及全国各地

最新文章

随机文章