【問題描述】泛型是一種特殊的類型,它把指定類型的工作推遲到客戶端代碼聲明並實例化類或方法的時候進行。泛型旨在解決函數名字沖突的問題。一般認為泛型是高級語言的能力,泛型的實現一般借助高級語言的模板概念。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