如果你是 C++ 程序员,应该接触过 C++11 里的 decltype 操作符,它的作用是自动推导表达式的数据类型,以解决泛型编程中有些类型由模板参数决定而难以(甚至不可能)表示的问题。其实这个特性在 C 语言中也早有类似的实现,GNU C 标准中的一个扩展特性 typeof (PS: 不是 typedef)作用与 decltype 类似,我们来看看这个关键字该怎么用。

先来看一个最简单的例子:

// demo 01
int var = 666;
typeof(int *) pvar = &var;
printf("pvar:\t%p\n", pvar);
printf("&var:\t%p\n", &var);
printf("var:\t%d\n", var);
printf("*pvar:\t%d\n", *pvar);

我们先定义了一个 int 型变量 var,然后再定义一个指针型变量指向 var,一般我们就直接 int *xxx = &xx,但是我们为了演示 typeof 的用法,就不要这么直接了,typeof 是自动推导后面 ( ) 里的数据类型,所以 typeof(int *) 直接推导出了 int * 型,用这个类型声明了 pvar 并将其初始化为 var 的地址,输出结果应该就显而易见了,这是在我的机器上的输出:

好吧我承认上面那个例子是吃力不讨好,明明写个 int * 简单又明了,非得加个 typeof 搞得这么晦涩,其实 typeof 的功效在于其能够自动推导表达式类型,比如我们把刚才的 typeof(int *) 改成 typeof(&var),它也会自动推导出 &var 的类型 —— int * 型,你可以自己试一下,原理是一样的,这样的话,当遇到一个非常复杂的表达式我们很难推断其类型的时候,typeof 就很有用了。另外有一点要注意:typeof 是 GNU C 标准里特有的扩展,标准的 ISO C 并没有这个关键字,所以在编译的时候不能加任何 ISO 的 C 标准选项,否则会报错,比如编译上面的代码我加入了一个 -std=c90 的选项,编译器就会有提示一堆 error:

解决的方法很简单,把 -std=c90 改成 -std=gnu90 即 GNU 的标准即可。

再来几个例子,比如

// demo 02
int *pvar = NULL;
typeof(*pvar) var = 999;
printf("var:\t%d\n", var);

这个例子是先定义了一个整型指针变量 pvar,typeof 后面括号里的表达式为*pvar,pvar 的类型为 int * 型,那 *pvar 当然就被解析为 int 型,所以用这个类型声明的变量 var 也是 int 型,就相当于 int var = 999; 输出结果如下:

再来:

// demo 03
int *pvar = NULL;
typeof(*pvar) var[4] = {11, 22, 33, 44};
for (int i = 0; i < 4; i++)
    printf("var[%d]:\t%d\n", i, var[i]);
这次 typeof 解析出来的类型跟上一个一样,区别是 var 是一个包含四个元素的数组,相当于 int var[4] = {...}; 输出如下:

这次来个有点水平的:

// demo 04
typeof(typeof(const char *)[4]) pchar = {"hello", "world", "good", "night"};
for (int i = 0; i < 4; i++)
    printf("pchar[%d]:\t%s\n", i, pchar[i]);

这次看起来就比较复杂了,考验你指针功底的时候到了,他嵌套了两层 typeof,我们一层一层的往外剥,先看最里层,typeof 先解析出一个 const char * 类型,有经验的 C 程序员应该马上就能联想到字符串了吧,而后面又跟着一个 [4],说明这是一个包含四个字符串的数组类型,那么这个类型也就被最外层的 typeof 给解析到了,那么最终的 pchar 也就是这个类型了,相当于 const char *pchar[4] = {...};

再来考验一下你的指针,这次是函数指针:

// demo 05
int add(int param1, int param2) {
    return param1 + param2;
}
 
int sub(int param1, int param2) {
    return param1 - param2;
}
 
int mul(int param1, int param2) {
    return param1 * param2;
}
 
int main() {
    int (*func[3]) (int, int) = {add, sub, mul};
    typeof(func[0](1, 1)) sum = 100;
    typeof(func[1](1, 1)) dif = 101;
    typeof(func[2](1, 1)) pro = 102;
 
    printf("sum:\t%d\n", sum);
    printf("dif:\t%d\n", dif);
    printf("pro:\t%d\n", pro);
    return 0;
}

这个 demo 中先定义了三个函数,这三个函数都是返回值为 int 类型,并且接受两个 int 型的参数,然后在 main 函数中定义了一个函数指针数组 func,并用上面三个函数名将其初始化,然后我们来看底下的第一个 typeof  会推导出什么类型,func[0] 就是指 add 这个函数,后面的括号里跟了两个参数,说白了就是简单的 add(1, 1) 的调用,而 add 会返回一个 int 型值,所以最终推导出的类型就是 int 型,其它两个都是同理,所以 sum、dif、pro 其实就是三个整型数,相当于 int sum = 100; int dif = 101; int pro = 102; 好吧,我承认这个 demo 有点坑,而且这个例子举得不恰当,输出结果就是它们分别的值:

我们再看看它在宏定义中的应用:

// demo 06
#define pointer(T)  typeof(T *)
#define array(T, N) typeof(T[N])
 
int main() {
    array(pointer(char), 4) pchar = {"hello", "world", "good", "night"};
    for (int i = 0; i < 4; i++)
        printf("pchar[%d]:\t%s\n", i, pchar[i]);
    return 0;
}

这里用到了宏函数,pointer(T) 会被替换为 typeof(T *),也就是说 pointer 后面跟某个类型的名字,经过预处理之后就会变成用 typeof 解析相应类型的指针类型,而 array 后面跟一个类型名和一个整数,然后 typeof 就会解析为该类型的一个数组,这样 main 函数中的 array(pointer(char), 4),pointer(char) 首先会被解析为 char * 型,然后外层的 array 会再被解析为包含 4 个 char * 元素的数组类型,所以就相当于 char *pchar[4] = {...}; 输出结果如下:

好了,啰嗦了这么多,typeof 这个关键字总算是知道用来干什么了吧,感觉好像语法挺晦涩的,而且没有什么实际用途,那好吧,我再让大伙看一看实际项目中的一个例子:

/*
 * 选自 linux-2.6.7 内核源码
 * filename: linux-2.6.7/include/linux/kernel.h
 */
#define min(x,y) ({ \
    typeof(x) _x = (x);    \
    typeof(y) _y = (y);    \
    (void) (&_x == &_y);        \
    _x < _y ? _x : _y; })
上面这个例子是选自 linux 2.6.7 内核中 include/linux/kernel.h 这个头文件,宏定义 min 的作用是从两个相同类型的对象中选取一个最小的,它接受两个参数 x 和 y,后面的宏替换部分就用 typeof 定义两个变量 _x 和 _y,并分别赋值为 x y,这里用 typeof 的作用就是可以让 min 接受任何类型的参数而不必局限于某一个单一类型,这有点泛型编程的味道了,最后一个语句 _x < _y ? _x : _y; 用了一个条件运算符来返回二者之中最小的,中间还有一句 (void) (&_x == &_y); 看起来好像是废话,其实这句话是有特殊用意的,因为我们不能保证你在使用 min 的时候传入的两个参数都是相同的类型,这时候就需要做一个检测,而 C 语言不支持直接 typeof(_x) == typeof(_y) 这样的操作,所以就取其地址,用指针类型来比较,如果两个指针的类型不一致,编译器就会产生警告以达到检测的效果,至于前面的 (void),是因为仅表达式 &_x == &_y 本身是没有意义的,如果没有这个 (void) 编译器同样会警告:statement with no effect [-Wunused-value],无效的语句,如果不想看到这个警告,那就在前面加个 (void) 忽略掉。

参考资料:

GNU 官方手册:https://gcc.gnu.org/onlinedocs/gcc/Typeof.html

crifan 的个人网站:http://www.crifan.com/order_min__macro_definition_void_amp__x__amp__y_the_meaning_of/

-

详解 GNU C 标准中的 typeof 关键字相关推荐

  1. 详解Java多线程编程中LockSupport类的线程阻塞用法

    转载自  详解Java多线程编程中LockSupport类的线程阻塞用法 LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport实际 ...

  2. 详解MindManager 15中文版中格式刷工具

    2019独角兽企业重金招聘Python工程师标准>>> 制作MindManager思维导图时,其中很多资源来源于不同的外部资料,因为来源不同,复制粘贴的格式就可能不同. 巧妙使用Mi ...

  3. python 标准输出_详解Python的标准输入输出

    本篇文章给大家分享的是详解Python的标准输入输出,内容挺不错的,希望可以帮助到有需要的朋友 一.标准输入输出 1.打印到屏幕 产生输出的最简单方法是使用print语句,可以通过用逗号分隔零个或多个 ...

  4. python英语字典程序修改_详解如何修改python中字典的键和值

    我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...

  5. 详解Linux2.6内核中基于platform机制的驱动模型

    原文地址:详解Linux2.6内核中基于platform机制的驱动模型 作者:nacichan [摘要]本文以Linux 2.6.25 内核为例,分析了基于platform总线的驱动模型.首先介绍了P ...

  6. 详解在Visual Studio中使用git版本系统 [转]

    详解在Visual Studio中使用git版本系统    作者:掷鸡蛋者 , 发布于2012-6-21   这篇教程的预期,是希望没有任何版本使用基础的新手也可以掌握,所以细节较多,不当之处,欢迎指 ...

  7. php 内存池,内存详解: 详解PHP内存池中的存储层_php

    php的内存管理器是分层(hierarchical)的.这个管理器共有三层:存储层(storage).堆(heap)层和 emalloc/efree 层.存储层通过 malloc().mmap() 等 ...

  8. python字典修改键所对应值_详解如何修改python中字典的键和值

    我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...

  9. Android Studio 安装详解及安装过程中出现的问题解决方案

    Android Studio 安装详解及安装过程中出现的问题解决方案 一,Android Studio安装包下载, 首先到官网下载,就是去Android Studio中文社区官网下载你的平台需要的安装 ...

最新文章

  1. mybatis整合spring下的的各种配置文件
  2. Python 中的高级斗技,让函数返回结果的技巧
  3. PowerDesigner使用教程 —— 概念数据模型 (转)
  4. Android 8.0 linux内核,在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序---Android8.0版本实现-对照老罗版本...
  5. isis协议_ISIS与OSPF之间有什么差异?
  6. ORACLE OEM
  7. OSX: Mac不睡眠的排查
  8. java自动化键盘组合键_SELENIUM自动化模拟键盘快捷键操作实现解析
  9. python发送qq邮件失败_python发送QQ邮件
  10. python替换ppt文本_Python操作PPT实现自动查找替换
  11. 游戏美术设计中,最难的角色人物如何设计才能吸睛?
  12. xiaoxin juju needs help - 组合公式
  13. 如何读博士-2021.06.12
  14. 雷军《小米创业思考》读书笔记
  15. Oracle Database 12c新特性 In-Database Archiving数据库内归档
  16. 这款 SQL自动检查神器,吊炸天的功能,真TMD多!!
  17. 什么?还在用delete删除数据《死磕MySQL系列 九》
  18. 口水了,各大互联网大厂年终奖一览表!
  19. 输入一个包含若干数据的列表,先将列表中的数由小到大进行排序,然后将值为负数的元素进行平方运算
  20. 关于c语言如何判断日期是否合法?\\有这样的日期吗?

热门文章

  1. dorado 刷新_5.dorado查询开发:使用flushData方法 (T1)
  2. 性能测试方案_MeterSphere案例分享丨基于JMeter的性能测试方案演进之路
  3. php插件 pycharm_原来Pycharm中有这么多好用的插件|Pycharm精选插件
  4. centos系统下安装python3以及pip3
  5. python-三元运算
  6. 单引号内的双引号内的双引号怎么写
  7. 生成树的计数 Matrix-Tree(矩阵树)定理
  8. 采用Flume实时采集和处理数据
  9. android四中启动模式
  10. 工单系统的设计与实现(4)