C与泛型

来源:岁月联盟 编辑:猪蛋儿 时间:2012-08-06

【问题描述】泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。泛型旨在解决函数名字冲突的问题。一般认为泛型是高级语言的能力,泛型的实现一般借助高级语言的模板概念。C语言是否能实现泛型呢?答案是能,不过比高级语言困难得多。下面总结两种泛型的实现方法。

【解析】

编译环境:

Fedora 10, gcc版本gcc4.3.2

1 利用函数指针实现泛型

【代码清单】

printtest.c

[html]
#include <stdio.h> 
#include <stdlib.h> 
 
#define DEBUG 0 
 
#define PRINTSTAT( FORMAT, STAT) / 
    printf("**************** " FORMAT " Test ****************/n"/ 
    ,STAT); 
 
#define DEBUG_PRINT(FORMAT, VALUE) printf("File %s line %d: "/ 
            #VALUE " = " FORMAT "/n"/ 
            ,__FILE__, __LINE__,VALUE/ 
            ); 
 
enum { 
    RET_OK, 
    RET_FAIL 
}; 
 
typedef int (*FXPrintFun)(void *data); 
 
int print(FXPrintFun _print, void *data) 

    return _print(data); 

 
static int print_int(void *data) 

    printf("%d", (int)data); 
 
    return RET_OK; 

 
static int print_int2(void *data) 

    printf("%d", *(int*)data); 
 
    return RET_OK; 

 
static int print_float(void *data) 

    printf("%f", *(float*)data); 
 
    return RET_OK; 

 
static int print_str(void *data) 

    printf("%s", (char*)data); 
 
    return RET_OK; 

 
int main(void) 

    int i = 0; 
    float f = 2.6; 
    char *test = "Generics test!"; 
 
    int *pi = &i; 
    float *pf = &f; 
 
#if DEBUG 
    DEBUG_PRINT("%f", f) 
#endif 
 
    PRINTSTAT( "%s", "Integer") 
    for(; i<10; i++) 
        print(print_int, (void *)i); 
    printf("/n"); 
 
    PRINTSTAT( "%s", "Integer") 
    for(i = 0; i<10; i++) 
        print(print_int2, (void *)pi); 
    printf("/n"); 
 
    PRINTSTAT( "%s", "Float") 
    print(print_float, (void *)pf); 
    printf("/n"); 
 
    PRINTSTAT( "%s", "String") 
    print(print_str, (void *)test); 
    printf("/n"); 
 
    return RET_OK; 

#include <stdio.h>
#include <stdlib.h>

#define DEBUG 0

#define PRINTSTAT( FORMAT, STAT) /
 printf("**************** " FORMAT " Test ****************/n"/
 ,STAT);

#define DEBUG_PRINT(FORMAT, VALUE) printf("File %s line %d: "/
   #VALUE " = " FORMAT "/n"/
   ,__FILE__, __LINE__,VALUE/
   );

enum {
 RET_OK,
 RET_FAIL
};

typedef int (*FXPrintFun)(void *data);

int print(FXPrintFun _print, void *data)
{
 return _print(data);
}

static int print_int(void *data)
{
 printf("%d", (int)data);

 return RET_OK;
}

static int print_int2(void *data)
{
 printf("%d", *(int*)data);

 return RET_OK;
}

static int print_float(void *data)
{
 printf("%f", *(float*)data);

 return RET_OK;
}

static int print_str(void *data)
{
 printf("%s", (char*)data);

 return RET_OK;
}

int main(void)
{
 int i = 0;
 float f = 2.6;
 char *test = "Generics test!";

 int *pi = &i;
 float *pf = &f;

#if DEBUG
 DEBUG_PRINT("%f", f)
#endif

 PRINTSTAT( "%s", "Integer")
 for(; i<10; i++)
  print(print_int, (void *)i);
 printf("/n");

 PRINTSTAT( "%s", "Integer")
 for(i = 0; i<10; i++)
  print(print_int2, (void *)pi);
 printf("/n");

 PRINTSTAT( "%s", "Float")
 print(print_float, (void *)pf);
 printf("/n");

 PRINTSTAT( "%s", "String")
 print(print_str, (void *)test);
 printf("/n");

 return RET_OK;
}
Makefile

[html]
OBJS = printtest.o 
TARGET = printtest 
SRC = printtest.c 
 
all:$(OBJS) 
    gcc -g $(OBJS) -o $(TARGET) 
$(OBJS):printtest.s 
    gcc -g -c printtest.s -o $(OBJS) 
printtest.s:printtest.i 
    gcc -g -S printtest.i -o printtest.s 
printtest.i:$(SRC) 
    gcc -g -E $(SRC) -o printtest.i 
clean: 
    rm *~ *.o *.s *.i $(TARGET) 

OBJS = printtest.o
TARGET = printtest
SRC = printtest.c

all:$(OBJS)
 gcc -g $(OBJS) -o $(TARGET)
$(OBJS):printtest.s
 gcc -g -c printtest.s -o $(OBJS)
printtest.s:printtest.i
 gcc -g -S printtest.i -o printtest.s
printtest.i:$(SRC)
 gcc -g -E $(SRC) -o printtest.i
clean:
 rm *~ *.o *.s *.i $(TARGET)
*此处为了观察代码的编译、汇编、链接过程,Makefile稍显复杂,可直接用下述指令编译

[html] view plaincopyprint?gcc printtest.c -o printtest 

gcc printtest.c -o printtest
 

【运行结果】

[html] 
**************** Integer Test **************** 
0123456789 
**************** Integer Test **************** 
0123456789 
**************** Float Test **************** 
2.600000 
**************** String Test **************** 
Generics test! 

**************** Integer Test ****************
0123456789
**************** Integer Test ****************
0123456789
**************** Float Test ****************
2.600000
**************** String Test ****************
Generics test!
 


上述代码,有一处值得注意,在定义时,提供了两种print_int函数:

[html]
static int print_int(void *data) 

    printf("%d", (int)data); 
 
    return RET_OK; 

 
static int print_int2(void *data) 

    printf("%d", *(int*)data); 
 
    return RET_OK; 

static int print_int(void *data)
{
 printf("%d", (int)data);

 return RET_OK;
}

static int print_int2(void *data)
{
 printf("%d", *(int*)data);

 return RET_OK;
}调用时:

[html]
int i = 0; 
 
int *pi = &i; 
 
PRINTSTAT( "%s", "Integer") 
for(; i<10; i++) 
    print(print_int, (void *)i); 
printf("/n"); 
 
PRINTSTAT( "%s", "Integer") 
for(i = 0; i<10; i++) 
    print(print_int2, (void *)pi); 
printf("/n"); 

int i = 0;

int *pi = &i;

PRINTSTAT( "%s", "Integer")
for(; i<10; i++)
 print(print_int, (void *)i);
printf("/n");

PRINTSTAT( "%s", "Integer")
for(i = 0; i<10; i++)
 print(print_int2, (void *)pi);
printf("/n");
调用print_int时,将int类型强制转换为void *类型;调用print_int2时,将int *类型强制转化为void *类型。令人惊讶的是,上述两种方法,都能输出正确结果。其实,print_int提供的方法只是凑巧而已,因为int是4字节,而void *类型也是4字节,强制转换居然没有发生错误!但如果是float类型,再利用print_int类似的方法就不行了,gcc提示无法进行转换。安全的方式是将int *转换为void *,再在print_int处理函数中将void *转换为int *,然后解引用。

 

2 采用“#”

方法1提供的代码中,已经看出了“#”的威力。下面看看泛型的另一种实现:

【代码清单】

printtest.h

[html]
#ifndef __PRINTTEST_H__ 
#define __PRINTTEST_H__ 
 
#define GNERIC_PRINT(TYPE,FORMAT,SUFFIX) / 
    int print##SUFFIX( TYPE data )/ 
    {/ 
    printf(FORMAT, data);/ 
    return 0;/ 
    } 
 
#endif  

#ifndef __PRINTTEST_H__
#define __PRINTTEST_H__

#define GNERIC_PRINT(TYPE,FORMAT,SUFFIX) /
 int print##SUFFIX( TYPE data )/
 {/
 printf(FORMAT, data);/
 return 0;/
 }

#endif

printtest.c

[html]
#include <stdio.h> 
#include <stdlib.h> 
#include "printtest.h" 
 
#define DEBUG 0 
 
#define PRINTSTAT( FORMAT, STAT) / 
    printf("**************** " FORMAT " Test ****************/n"/ 
    ,STAT); 
 
#define DEBUG_PRINT(FORMAT, VALUE) printf("File %s line %d: "/ 
            #VALUE " = " FORMAT "/n"/ 
            ,__FILE__, __LINE__,VALUE/ 
            ); 
 
enum { 
    RET_OK, 
    RET_FAIL 
}; 
 
GNERIC_PRINT(int, "%d", _int) 
GNERIC_PRINT(float, "%f", _float) 
GNERIC_PRINT(char *, "%s", _str) 
 
int main(void) 

    int i = 0; 
    float f = 2.6; 
    char *test = "Generics test!"; 
 
#if DEBUG 
    DEBUG_PRINT("%f", f) 
#endif 
 
    PRINTSTAT( "%s", "Integer") 
    for(; i<10; i++) 
        print_int(i); 
    printf("/n"); 
 
    PRINTSTAT( "%s", "Float") 
    print_float(f); 
    printf("/n"); 
 
    PRINTSTAT( "%s", "String") 
    print_str(test); 
    printf("/n"); 
 
    return RET_OK; 

#include <stdio.h>
#include <stdlib.h>
#include "printtest.h"

#define DEBUG 0

#define PRINTSTAT( FORMAT, STAT) /
 printf("**************** " FORMAT " Test ****************/n"/
 ,STAT);

#define DEBUG_PRINT(FORMAT, VALUE) printf("File %s line %d: "/
   #VALUE " = " FORMAT "/n"/
   ,__FILE__, __LINE__,VALUE/
   );

enum {
 RET_OK,
 RET_FAIL
};

GNERIC_PRINT(int, "%d", _int)
GNERIC_PRINT(float, "%f", _float)
GNERIC_PRINT(char *, "%s", _str)

int main(void)
{
 int i = 0;
 float f = 2.6;
 char *test = "Generics test!";

#if DEBUG
 DEBUG_PRINT("%f", f)
#endif

 PRINTSTAT( "%s", "Integer")
 for(; i<10; i++)
  print_int(i);
 printf("/n");

 PRINTSTAT( "%s", "Float")
 print_float(f);
 printf("/n");

 PRINTSTAT( "%s", "String")
 print_str(test);
 printf("/n");

 return RET_OK;
}

Makefile

[html]
OBJS = printtest.o 
TARGET = printtest 
SRC = printtest.c 
 
all:$(OBJS) 
    gcc -g $(OBJS) -o $(TARGET) 
$(OBJS):printtest.s 
    gcc -g -c printtest.s -o $(OBJS) 
printtest.s:printtest.i 
    gcc -g -S printtest.i -o printtest.s 
printtest.i:$(SRC) 
    gcc -g  -E $(SRC) -o printtest.i 
clean: 
    rm *~ *.o *.s *.i $(TARGET) 

OBJS = printtest.o
TARGET = printtest
SRC = printtest.c

all:$(OBJS)
 gcc -g $(OBJS) -o $(TARGET)
$(OBJS):printtest.s
 gcc -g -c printtest.s -o $(OBJS)
printtest.s:printtest.i
 gcc -g -S printtest.i -o printtest.s
printtest.i:$(SRC)
 gcc -g  -E $(SRC) -o printtest.i
clean:
 rm *~ *.o *.s *.i $(TARGET)
*SRC = printtest.c,而不是SRC=printtest.c printtest.h。后者,不能正确完成预处理,此处颇让人费解。有人能说明原因吗?


或采用

[html]
gcc printtest.h printtest.c -o printtest 

gcc printtest.h printtest.c -o printtest
 进行编译

 

【运行结果】

[html]
**************** Integer Test **************** 
0123456789 
**************** Float Test **************** 
2.600000 
**************** String Test **************** 
Generics test! 

**************** Integer Test ****************
0123456789
**************** Float Test ****************
2.600000
**************** String Test ****************
Generics test!www.2cto.com
 

这种方法看似很炫,但在代码量巨大的情况下,非常容易出错。因此推荐采用函数指针的形式实现泛型。

 

 作者;tandesir
 

图片内容