29-类型变体
16.10.2 类型变体
基本的浮点型数学函数接受 double
类型的参数,并返回 double
类型的值。当然,也可以把 float
或 long double
类型的参数传递给这些函数,它们仍然能正常工作,因为这些类型的参数会被转换成 double
类型。这样做很方便,但并不是最好的处理方式。如果不需要双精度,那么用 float
类型的单精度值来计算会更快些。而且把 long double
类型的值传递给 double
类型的形参会损失精度,形参获得的值可能不是原来的值。为了解决这些潜在的问题,C标准专门为 float
类型和 long double
类型提供了标准函数,即在原函数名后加上 f
或 l
后缀。因此, sqrtf()
是 sqrt()
的 float
版本, sqrtl()
是 sqrt()
的 long double
版本。
利用C11新增的泛型选择表达式定义一个泛型宏,根据参数类型选择最合适的数学函数版本。程序清单16.15演示了两种方法。
程序清单16.15 generic.c
程序
// generic.c -- 定义泛型宏
#include <stdio.h>
#include <math.h>
#define RAD_TO_DEG (180/(4 * atanl(1)))
// 泛型平方根函数
#define SQRT(X) _Generic((X),\
long double: sqrtl, \
default: sqrt, \
float: sqrtf)(X)
// 泛型正弦函数,角度的单位为度
#define SIN(X) _Generic((X),\
long double: sinl((X)/RAD_TO_DEG),\
default: sin((X)/RAD_TO_DEG),\
float: sinf((X)/RAD_TO_DEG)\
)
int main(void)
{
float x = 45.0f;
double xx = 45.0;
long double xxx = 45.0L;
long double y = SQRT(x);
long double yy = SQRT(xx);
long double yyy = SQRT(xxx);
printf("%.17Lf\n", y); // 匹配 float
printf("%.17Lf\n", yy); // 匹配 default
printf("%.17Lf\n", yyy); // 匹配 long double
int i = 45;
yy = SQRT(i); // 匹配 default
printf("%.17Lf\n", yy);
yyy = SIN(xxx); // 匹配 long double
printf("%.17Lf\n", yyy);
return 0;
}
下面是该程序的输出:
6.70820379257202148
6.70820393249936942
6.70820393249936909
6.70820393249936942
0.70710678118654752
如上所示, SQRT(i)
和 SQRT(xx)
的返回值相同,因为它们的参数类型分别是 int
和 double
,所以只能与 default
标签对应。
有趣的一点是,如何让 _Generic
宏的行为像一个函数。 SIN()
的定义也许提供了一个方法:每个带标号的值都是函数调用,所以 _Generic
表达式的值是一个特定的函数调用,如 sinf((X)/RAD_TO_DEG)
,用传入 SIN()
的参数替换 X
。
SQRT()
的定义也许更简洁。 _Generic
表达式的值就是函数名,如 sinf
。函数的地址可以代替该函数名,所以 _Generic
表达式的值是一个指向函数的指针。然而,紧随整个 _Generic
表达式之后的是 (X)
,函数指针(参数)表示函数指针。因此,这是一个带指定的参数的函数指针。
简而言之,对于 SIN()
,函数调用在泛型选择表达式内部;而对于 SQRT()
,先对泛型选择表达式求值得一个指针,然后通过该指针调用它所指向的函数。