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() ,先对泛型选择表达式求值得一个指针,然后通过该指针调用它所指向的函数。