当前位置:嗨网首页>书籍在线阅读

29-类型变体

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

16.10.2 类型变体

基本的浮点型数学函数接受 double 类型的参数,并返回 double 类型的值。当然,也可以把 floatlong double 类型的参数传递给这些函数,它们仍然能正常工作,因为这些类型的参数会被转换成 double 类型。这样做很方便,但并不是最好的处理方式。如果不需要双精度,那么用 float 类型的单精度值来计算会更快些。而且把 long double 类型的值传递给 double 类型的形参会损失精度,形参获得的值可能不是原来的值。为了解决这些潜在的问题,C标准专门为 float 类型和 long double 类型提供了标准函数,即在原函数名后加上 fl 后缀。因此, 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) 的返回值相同,因为它们的参数类型分别是 intdouble ,所以只能与 default 标签对应。

有趣的一点是,如何让 _Generic 宏的行为像一个函数。 SIN() 的定义也许提供了一个方法:每个带标号的值都是函数调用,所以 _Generic 表达式的值是一个特定的函数调用,如 sinf((X)/RAD_TO_DEG) ,用传入 SIN() 的参数替换 X

SQRT() 的定义也许更简洁。 _Generic 表达式的值就是函数名,如 sinf 。函数的地址可以代替该函数名,所以 _Generic 表达式的值是一个指向函数的指针。然而,紧随整个 _Generic 表达式之后的是 (X) ,函数指针(参数)表示函数指针。因此,这是一个带指定的参数的函数指针。

简而言之,对于 SIN() ,函数调用在泛型选择表达式内部;而对于 SQRT() ,先对泛型选择表达式求值得一个指针,然后通过该指针调用它所指向的函数。