在 C11 标准中,_Generic 关键字让 C 语言如同 C++ 等面向对象程序设计语言一样,支持轻量级的泛型编程设计。
泛型编程(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
C++通过模板来支持泛型编程,比如下面这段代码,在定义add()函数时候并没有明确指出其返回类型、参数类型。
#include
template <class T>
T add(T a,T b){
T ret = a + b;
std::cout<< a << " + " << b <<" = " << ret << std::endl;
return ret;
}
int main(){
add(1,2); // 整数相加
add(1.2,2.3); // 浮点数相加
return 0;
}
C语言本身不支持真正意义上的泛型编程,但是却在一定程度上可以“实现泛型编程”,即C11标准的 _Generic 关键字(C++不支持该关键字)。
_Generic 关键字提供了一种在 编译时 根据 赋值表达式 的类型在 泛型关联表 中选择一个表达式的方法,因此可以将一组不同类型却功能相同的函数抽象为一个统一的接口,以此来实现泛型,语法形式如下:
_Generic ( assignment-expression , generic-assoc-list )
assignment-expression:赋值表达式,可以认为是变量var
generic-assoc-list:泛型关联表,其语法为:
type-name : expression, type-name : expression, ..., default : expression
是不是看起来很懵,换成人话就是下面的格式:
_Generic((var), type1 : ..., type2 : ..., ……, default : ...)
这里需要注意两点:
举个例子,要想实现getTypeName(var)函数,该函数的作用是返回变量var的类型,可以这样写:
#define GET_TYPENAME(var) _Generic((var),\
int:"int", \
char:"char", \
float:"float", \
double:"double", \
char *:"char *", \
default:"other type")
int main(int argc, char const *argv[])
{
int x;
int* x1;
char s[10];
printf("type: x = %s\n",GET_TYPENAME(x));
printf("type: x1 = %s\n",GET_TYPENAME(x1));
printf("type: s = %s\n",GET_TYPENAME(s));
return 0;
}
测试一下,运行结果:
type: x = int
type: x1 = other type
type: s = char *
现在我们来实现最开始c++模板的add()函数。由于c语言不支持重载,所以我们要先定义两个不同类型的add()函数,再去使用 _Generic
int add_int(int a,int b)
{
printf("%d + %d = %d\n",a,b,a+b);
return a+b;
}
float add_float(float a,float b)
{
printf("%f + %f = %f\n",a,b,a+b);
return a+b;
}
void unsupport()
{
printf("unsupport type\n");
}
#define ADD(x,y) _Generic((x),\
int:add_int(x,y),\
float:add_float(x,y),\
default:unsupport())
int main(int argc, char const *argv[])
{
ADD(1,2);
ADD(1.1f,2.2f);
return 0;
}
运行结果:
1 + 2 = 3
1.100000 + 2.200000 = 3.300000
我们可以注意到此时调用者并不需要区分两个被加数的类型就可以统一调用ADD。不过这里有一个确定就是只判断了一个变量x的类型,如果两个变量x,y的类型不一样时仍然会会出现错误。比如下面这种情况:
ADD(1,1.1);
运行结果:
1 + 1 = 2
其次,由于 "generic-assoc-list中的expression仅仅是普通的宏替换“ ,_Generic语句中的参数也可以放在后面,也可以写成下面这种(这句没写default):
#define ADD1(x,y) _Generic((x),int:add_int,float:add_float)(x,y)
继续上面的GET_TYPENAME代码,
int main(void)
{
int x = 1;
GET_TYPENAME(x++);
GET_TYPENAME(++x);
GET_TYPENAME(x*=2);
GET_TYPENAME(x = 10)
printf("x = %d\n",x);
}
运行结果:
x = 1
可见, 在泛型_Generic的语句中,++ 、*=、 = 等操作都不会让变量发生改变 。
来看另一段代码,来自百度百科对C11中_Generic关键字的例程。
int main(void)
{
int a=10;
int b=0,c=0;
_Generic(a+0.1f,int:b,float:c,default:b)++;
printf("b=%d,c=%d\n",b,c);
_Generic(a+=1.1f,int:b,float:c,default:b)++;
printf("a=%d,b=%d,c=%d\n",a,b,c);
}
运行结果:
b=0,c=1
a=10,b=1,c=1
解释:
a为int,a + 0.1f会被转换成float,所以_Generic(泛型)发现传入的是float,所以执行float的内容,整个_Generic宏替换为c,最终变成c++,printf格式化输出b=0,c=1
a为int,a+=1.1f执行了+=操作并不会改变a的值,执行完后_Generic(泛型)才进行判断a的类型,因为a的类型为int,所以执行int的操作,最终变成b++,printf格式化输出a=10,b=1,c=1