C编程语言中是否有模板?

12

我正在使用C语言编写链表实现,希望使用一种类似于C++的模板的语言特性来使我的工作更简单。

是否存在这样的特性?


1
在这个上下文中,“模板”是什么意思? - cnicutar
2
模板是C++编程语言的一个特性,它允许函数和类与通用类型一起操作。 - user2166576
6个回答

7
C语言没有像C++一样的模板,尽管你可以使用“巧妙”的(或者说是有点奇怪的)#define宏来实现类似的功能。
然而,你可以看看GLib是如何为单向链表或双向链表实现的,例如:单向链表双向链表

截至2021年3月6日更新,"singly linked lists"链接显示为"404未找到"。然而,此时"doubly linked lists"链接仍然有效。 - Ryan Blanchard
很好。谢谢伙计。 - Ryan Blanchard
看起来自那时起链接已经被修正了。 - jasonleonhard

5

模板是C++的特性,但如果您想要一个独立于类型的单向或双向链表实现,可以借助宏来实现,或者您可以在结构中简单地存储void*指针。

当然,在互联网上有很多这样的实现。 @MohamedKALLEL@hyde已经从Linux内核和GLib给出了例子,我只想添加一个关于很好的小库uthash的注释。

它在C中实现了哈希表,但它也有一个utlist.h,它完全使用宏实现了单向和双向(甚至是循环)列表。也就是说,您可以简单地获取此文件,将其包含并使用这些宏,或者根据需要修改它。另外值得注意的是,您可以使用任何数据结构:它只需要具有next指针(在双向链表的情况下还需要prev)。

P.s. 但是请记住,使用宏时始终要牢记:伟大的力量伴随着巨大的责任。宏很强大,但可能变得极其不安全和难以阅读。小心!


4
如果您使用make来构建软件,您可以使用一种方法来实现类似的结果,即通过调用像sed或awk这样的工具,让make基于您编写的模板生成代码。我已经多次使用了这种方法,虽然它缺乏C++模板提供的灵活性和功能,但它非常透明(不像宏),构建效率很高,并且除了可靠的老式Unix工具(例如makesed)之外,不需要添加任何工具。主要缺点是您将在不同位置(Makefile中的一行)指定查找/替换字符串,而不是在代码中。
请注意,通常最好使用函数指针和void指针编写一个通用的单个代码库。但是,如果您想要通过消除紧密循环中的不必要的函数调用获得额外的性能,则模板可能更好,并且可以按如下方式模拟:
  1. 使用占位符名称编写模板代码,并使用模板名称(例如code.template.c

  2. 使用函数调用您的模板代码编写非模板代码,替换适当的名称,例如my_int_func()my_string_func()

  3. 在需要时,使用#include在非模板代码中包含您的模板代码(例如,如果您的模板将具有inline函数)

  4. 编写Makefile:

    • 从template.c文件生成“真实”的C文件。每个模板目标都会有一个Makefile条目,例如code.generated.csed是一个很好的替换工具,但您也可以使用例如replaceawk或它们的Windows等效工具
    • 根据需要编译生成的C文件并链接生成的对象

例如:
/* Makefile will call sed to replace DATANAME, DATATYPE and SPECIFIER */
void print_DATANAME_data(DATATYPE x) {
  printf("%SPECIFIER\n", x);
}

code.c

#include <stdio.h>
#include "printfuncs.generated.c"

int main() {
  int i = 99;
  print_int_data(99);

  char *s = "hello";
  print_str_data(s);

  float f = 1.234;
  print_float_data(f);
}

Makefile

all: my_program

my_program: code.c
    CC -o $@ code.c

code.c: printfuncs.generated.c

printfuncs.generated.c: code.template.c
    rm -f printfuncs.generated.c
    cat code.template.c | sed 's/DATANAME/int/g;s/DATATYPE/int/g;s/SPECIFIER/i/g;' >> printfuncs.generated.c
    cat code.template.c | sed 's/DATANAME/str/g;s/DATATYPE/char */g;s/SPECIFIER/s/g;' >> printfuncs.generated.c
    cat code.template.c | sed 's/DATANAME/float/g;s/DATATYPE/float/g;s/SPECIFIER/f/g;' >> printfuncs.generated.c

构建

make

这将生成printfuncs.generated.c文件(如果该文件不存在或修改时间早于code.template.c),其内容将如下所示:
/* Makefile will call sed to replace int, int and i */
void print_int_data(int x) {
  printf("%i", x);
}
/* Makefile will call sed to replace str, char * and s */
void print_str_data(char * x) {
  printf("%s", x);
}
/* Makefile will call sed to replace float, float and f */
void print_float_data(float x) {
  printf("%f", x);
}

任何编译错误都指向这个文件,你可以直接修改该文件以使其编译(之后需要更新模板以防止更改丢失),或者编辑模板。在任何情况下,在编辑后,您只需运行make即可再次尝试编译。

运行

./my_program(如果在win上构建,则为my_program.exe


3

是的,有一个list.h。而且它是一个循环链表:

下面的链接包含一个示例,展示如何使用它。

list.h 包含了所有与管理循环链表相关的函数,例如定义、添加头部、添加尾部、删除、遍历循环链表的foreach函数等等...


1
这是针对Linux内核的特定答案,不是关于它在C语言中的一般性回答。我觉得奇怪它被投票赞成了。 - unwind
2
@unwind:我知道 list.h 在 Linux 内核中使用。但是,在我在这个回答中提供的链接中,list.h 是另一个分布式的 list.h,并且它可以在 Linux 的用户空间中移植。我在开发所有用户空间应用程序时都使用它,它运行良好。 - MOHAMED
@unwind 参考我在答案中给出的链接中的这段话作为示例 http://isis.poly.edu/kulesh/stuff/src/klist/ 只需进行很少的修改(删除列表项的硬件预取),我们也可以在我们的应用程序中使用此列表。 这个文件的可用版本可以在这里下载。 - MOHAMED
@unwind,请下次在确认之前不要做出判断。 - MOHAMED
@MohamedKALLEL:这个引用完全没有回应unwind的评论。 - Lightness Races in Orbit

1

请看这个文章

C语言没有静态模板,但是你可以使用宏来模拟它们。

静态模板的最简形式,定义宏:

// define a macro 
#define DEF(type, name, val) type name = val 
// call the macro 
DEF(int, foo, 5); 
// print the called macro 
printf("%d", foo);

这个模板允许将定义基本类型变量所需的代码通用化和抽象化。其目标是使代码在不同类型之间可共享和相似。然而,这个例子在提供语句的简化方面几乎没有什么作用。真正的力量来自于执行更复杂任务的代码。

以下是您可以尝试的剪切粘贴示例

#include <stdio.h>

int main()
{
#define FOREACH(type, start, end, fn)                                    \
    for (type _foreach_var = start; _foreach_var != end; _foreach_var++) \
    {                                                                    \
        fn(_foreach_var);                                                \
    }

#define PRINT_INT(n) printf("%d\n", n)
    // use FOREACH
    FOREACH(int, 0, 5, PRINT_INT)
}
  • 首先我们定义了FOREACH宏。
  • 然后我们可以使用它来循环,例如这里:FOREACH(int, 0, 5, PRINT_INT)

1

你好,我不了解链表,但对于模板函数,你可以使用宏或带有可变数量参数的函数作为示例。以下是一个程序:

#include <stdarg.h>
#include <stdio.h>
#define INT 0
#define STR 1
void foo( int type, ... )
{
    va_list ap;
    int i;
    char *s;
    va_start( ap, type );
    switch( type ) {
    case INT:
        i = va_arg( ap, int );
        printf( "INT: %i\n", i );
        break;
    case STR:
        s = va_arg( ap, char * );
        printf( "STR: %s\n", s );
        break;
    default:
        break;
    }
    va_end( ap );
}
#define SWAP( type, a, b ) {                    \
        type t;                                 \
        t = a;                                  \
        a = b;                                  \
        b = t;                                  \
    }
int main( void )
{
    foo( INT, 3 );
    foo( STR, "baz" );
    int ia = 0, ib = 3;
    SWAP( int, ia, ib );
    printf( "%i %i\n", ia, ib );
    float fa = 0.5, fb = 3.14;
    SWAP( float, fa, fb );
    printf( "%f %f\n", fa, fb );
    return 0;
}

将会产生输出
INT: 3
STR: baz
3 0
3.140000 0.500000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接