听说C语言也可以“泛型”

都说泛型是C++的一大特性,我大C语言表示不服


再探类型

C/C++类型由以下各项组成:

  • 内存里的’0’、’1’序列
  • ‘0’、’1’串占用的内存大小
  • 作为操作数时如何解释与输出位模式(如cout、printf、强制类型转换)
  • 作为操作数时如何修改位模式(如+、-、*、/)

“泛型”原理

只需要提供内存首地址,类型大小。利用上面提出的前两点,直接操作内存,就能正确实现泛型。而且不需要为每个类型重新生成代码,克服了C++的代码膨胀问题。

热身——swap泛型实现

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void swap(void *vp1, void *vp2, int size)
{
char buffer[size];
memcpy(buffer, vp1, size);
memcpy(vp1, vp2, size);
memcpy(vp2, buffer, size);
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
double a = 2.5;
double b = 4.5;
int c = 100;
int d = 200;
printf("a = %f, b = %f\nc = %d, d = %d\n", a, b, c, d);
swap(&a, &b, sizeof(double));
swap(&c, &d, sizeof(int));
printf("a = %f, b = %f\nc = %d, d = %d\n", a, b, c, d);
return 0;
}

终端输出

1
2
3
4
a = 2.500000, b = 4.500000
c = 100, d = 200
a = 4.500000, b = 2.500000
c = 200, d = 100


进阶——泛型Stack

头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// stack.h
typedef struct
{
void *elems;
int elemSize;
int logicalLength;
int allocLength;
void (*freefn)(void *); // 用于释放元素时,元素不需要释放则是NULL
} stack;
void StackNew(stack *s, int elemSize, void (*freefn)(void *)); // 构造函数
void StackDispose(stack *s); // 析构函数
void StackPush(stack *s, void *elemAddr);
void StackPop(stack *s, void *elemAddr);

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// stack.c
#include "stack.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
void StackNew(stack *s, int elemSize, void (*freefn)(void *))
{
assert(elemSize > 0);
s->elemSize = elemSize;
s->logicalLength = 0;
s->allocLength = 4;
s->elems = malloc(4 * elemSize);
s->freefn = freefn;
assert(s->elems != NULL);
}
void StackDispose(stack *s)
{
if (s->freefn != NULL)
{
for (int i = 0; i < s->logicalLength; i++)
{
s->freefn((char *)s->elems + i * s->elemSize);
}
}
free(s->elems);
}
static void StackGrow(stack *s) // 供StackPush使用的辅助函数
{
s->allocLength *= 2;
s->elems = realloc(s->elems, s->allocLength * s->elemSize);
}
void StackPush(stack *s, void *elemAddr)
{
if (s->logicalLength == s->allocLength)
{
StackGrow(s);
}
void *target = (char *)s->elems + s->logicalLength * s->elemSize;
memcpy(target, elemAddr, s->elemSize);
s->logicalLength++;
}
void StackPop(stack *s, void *elemAddr)
{
assert(s->logicalLength > 0);
s->logicalLength--;
void *source = (char *)s->elems + s->logicalLength * s->elemSize;
memcpy(elemAddr, source, s->elemSize);
}

用户代码示例

  • String

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "stack.h"
    void StringFree(void *elem)
    {
    free(*(char **)elem);
    }
    int main()
    {
    stack stringStack;
    StackNew(&stringStack, sizeof(char *), StringFree);
    char *a;
    a = strdup("aaa");
    StackPush(&stringStack, &a);
    a = strdup("bbb");
    StackPush(&stringStack, &a);
    a = strdup("ccc");
    StackPush(&stringStack, &a);
    a = strdup("ddd");
    StackPush(&stringStack, &a);
    char *getString;
    StackPop(&stringStack, &getString);
    printf("%s\n", getString);
    free(getString);
    StackPop(&stringStack, &getString);
    printf("%s\n", getString);
    free(getString);
    StackDispose(&stringStack);
    return 0;
    }

    终端输出

    1
    2
    ddd
    ccc
  • Int

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <stdio.h>
    #include "stack.h"
    int main()
    {
    stack intStack;
    StackNew(&intStack, sizeof(int), NULL);
    for (int i = 0; i < 9; i++)
    {
    StackPush(&intStack, &i);
    }
    for (int i = 0; i < 4; i++)
    {
    int getInt;
    StackPop(&intStack, &getInt);
    printf("%d ", getInt);
    }
    putchar('\n');
    StackDispose(&intStack);
    return 0;
    }

    终端输出

    1
    8 7 6 5