C语言常常让人觉得它所能表达的东西非常有限。它不具有类似第一级函数和模式匹配这样的高级功能。但是C非常简单,并且仍然有一些非常有用的语法技巧和功能,只是没有多少人知道罢了。指定的初始化很多人都知道像这样来静态地初始化数组:int fibs[] = {1, 1, 2, 3, 5};C99标准实际上支持一种更为直观简单的方式来初始化各种不同的集合类数据(如:结构体,联合体和数组)。数组我们可以指定数组的元素来进行初始化。这非常有用,特别是当我们需要根据一组#define来保持某种映射关系的同步更新时。来看看一组错误码的定义,如:
/* Entries may not correspond to actual numbers. Some entries omitted. /
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/
/
#define E2BIG 7
#define EBUSY 8
/
/
#define ECHILD 12
/
… */
现在,假设我们想为每个错误码提供一个错误描述的字符串。为了确保数组保持了最新的定义,无论头文件做了任何修改或增补,我们都可以用这个数组指定的语法。
char err_strings[] = {
[0] = “Success”,
[EINVAL] = “Invalid argument”,
[ENOMEM] = “Not enough memory”,
[EFAULT] = “Bad address”,
/
/
[E2BIG ] = “Argument list too long”,
[EBUSY ] = “Device or resource busy”,
/
/
[ECHILD] = “No child processes”
/
… */
};
这样就可以静态分配足够的空间,且保证最大的索引是合法的,同时将特殊的索引初始化为指定的值,并将剩下的索引初始化为0。结构体与联合体用结构体与联合体的字段名称来初始化数据是非常有用的。假设我们定义:
struct point {
int x;
int y;
int z;
}
然后我们这样初始化struct point:struct point p = {.x = 3, .y = 4, .z = 5};当我们不想将所有字段都初始化为0时,这种作法可以很容易的在编译时就生成结构体,而不需要专门调用一个初始化函数。对联合体来说,我们可以使用相同的办法,只是我们只用初始化一个字段。宏列表C中的一个惯用方法,是说有一个已命名的实体列表,需要为它们中的每一个建立函数,将它们中的每一个初始化,并在不同的代码模块中扩展它们的名字。这在Mozilla的源码中经常用到,我就是在那时学到这个技巧的。例如,在我去年夏天工作的那个项目中,我们有一个针对每个命令进行标记的宏列表。其工作方式如下:
#define FLAG_LIST(_)
_(InWorklist)
_(EmittedAtUses)
_(LoopInvariant)
_(Commutative)
_(Movable)
_(Lowered)
_(Guard)
它定义了一个FLAG_LIST宏,这个宏有一个参数称之为 _ ,这个参数本身是一个宏,它能够调用列表中的每个参数。举一个实际使用的例子可能更能直观地说明问题。假设我们定义了一个宏DEFINE_FLAG,如:
#define DEFINE_FLAG(flag) flag,
enum Flag {
None = 0,
FLAG_LIST(DEFINE_FLAG)
Total
};
#undef DEFINE_FLAG
对FLAG_LIST(DEFINE_FLAG)做扩展能够得到如下代码:
enum Flag {
None = 0,
DEFINE_FLAG(InWorklist)
DEFINE_FLAG(EmittedAtUses)
DEFINE_FLAG(LoopInvariant)
DEFINE_FLAG(Commutative)
DEFINE_FLAG(Movable)
DEFINE_FLAG(Lowered)
DEFINE_FLAG(Guard)
Total
};
接着,对每个参数都扩展DEFINE_FLAG宏,这样我们就得到了enum如下:
enum Flag {
None = 0,
InWorklist,
EmittedAtUses,
LoopInvariant,
Commutative,
Movable,
Lowered,
Guard,
Total
};
接着,我们可能要定义一些访问函数,这样才能更好的使用flag列表:
#define FLAG_ACCESSOR(flag)
bool is##flag() const {
return hasFlags(1 << flag);
}
void set##flag() {
JS_ASSERT(!hasFlags(1 << flag));
setFlags(1 << flag);
}
void setNot##flag() {
JS_ASSERT(hasFlags(1 << flag));
removeFlags(1 << flag);
}

FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
一步步的展示其过程是非常有启发性的,如果对它的使用还有不解,可以花一些时间在gcc –E上。编译时断言这其实是使用C语言的宏来实现的非常有“创意”的一个功能。有些时候,特别是在进行内核编程时,在编译时就能够进行条件检查的断言,而不是在运行时进行,这非常有用。不幸的是,C99标准还不支持任何编译时的断言。但是,我们可以利用预处理来生成代码,这些代码只有在某些条件成立时才会通过编译(最好是那种不做实际功能的命令)。有各种各样不同的方式都可以做到这一点,通常都是建立一个大小为负的数组或结构体。最常用的方式如下:
/* Force a compilation error if condition is false, but also produce a result

  • (of value 0 and type size_t), so it can be used e.g. in a structure
  • initializer (or wherever else comma expressions aren’t permitted). /
    /
    Linux calls these BUILD_BUG_ON_ZERO/_NULL, which is rather misleading. */
    #define STATIC_ZERO_ASSERT(condition) (sizeof(struct { int:-!(condition); }) )
    #define STATIC_NULL_ASSERT(condition) ((void *)STATIC_ZERO_ASSERT(condition) )

/* Force a compilation error if condition is false */
#define STATIC_ASSERT(condition) ((void)STATIC_ZERO_ASSERT(condition))
如果(condition)计算结果为一个非零值(即C中的真值),即! (condition)为零值,那么代码将能顺利地编译,并生成一个大小为零的结构体。如果(condition)结果为0(在C真为假),那么在试图生成一个负大小的结构体时,就会产生编译错误。它的使用非常简单,如果任何某假设条件能够静态地检查,那么它就可以在编译时断言。例如,在上面提到的标志列表中,标志集合的类型为uint32_t,所以,我们可以做以下断言:STATIC_ASSERT(Total <= 32)它扩展为:(void)sizeof(struct { int:-!(Total <= 32) })现在,假设Total<=32。那么-!(Total <= 32)等于0,所以这行代码相当于:(void)sizeof(struct { int: 0 })这是一个合法的C代码。现在假设标志不止32个,那么-!(Total <= 32)等于-1,所以这时代码就相当于:(void)sizeof(struct { int: -1 } )因为位宽为负,所以可以确定,如果标志的数量超过了我们指派的空间,那么编译将会失败。

如果你依然在编程的世界里迷茫,不知道自己的未来规划,对C/C++感兴趣,这里推荐一下我的学习交流圈:684478929,里面都是学习C/C++的,从最基础的C/C++【C++,游戏,黑客技术,网络安全】到网络安全的项目实战的学习资料都有整理,送给每一位前端小伙伴,希望能帮助你更了解前端,学习前端

点击:加入

C语言编程笔记丨一种很酷的 C 语言技巧相关推荐

  1. c语言 方程改main的值_C语言编程笔记丨编写第一个C语言程序hello world,我教你哇...

    如果用C语言输出:Hello,world!,该如何编写程序? **代码如下:** #include//包含标准库的信息 main()//定义名为main的函数,不接受参数值 {//main函数的语句都 ...

  2. 取消对 null 指针“l”的引用。_C语言编程笔记丨C 语言指针 5 分钟教程

    指针.引用和取值 什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量.在这份教程里"引用"表示计算机内存地址.从指针指向的内存读取数据称作指针的取值 ...

  3. Go语言编程笔记16:存储数据

    Go语言编程笔记16:存储数据 图源:wallpapercave.com 几乎任何程序都绕不开读写数据,只不过具体的数据存储介质和方式有所不同.本篇文章将从多种数据存储方式进行探讨各种存储方式如何实现 ...

  4. Go语言编程笔记18:软件测试

    Go语言编程笔记18:软件测试 图源:wallpapercave.com 软件测试也是软件开发的重要组成部分,本篇文章将探讨如何使用Go的标准库和第三方库对程序进行测试. testing Go的标准库 ...

  5. Go语言编程笔记12:web基础

    Go语言编程笔记12:web基础 图源:wallpapercave.com 开一个新坑,用Go来做web开发.虽然已经从事多年基于LAMP的web开发,但最近学习了Go编程,所以打算借着学习<G ...

  6. c语言程序设计分段定时器,单片机C语言编程定时器的几种表达方式

    原标题:单片机C语言编程定时器的几种表达方式 吴鉴鹰单片机开发板地址 店铺:[吴鉴鹰的小铺] 地址:[https://item.taobao.com/item.htm?_u=ukgdp5a7629&a ...

  7. c语言植入手机系统,一种手机课堂C语言编程系统的制作方法

    本发明属于一种编程系统技术领域,特别涉及一种手机课堂C语言编程系统. 背景技术: 现如今,许多高等院校都开设了程序开发与设计课程,越来越多的学生会了解到什么是编程语言,编程语言能干什么.其中C语言作为 ...

  8. R语言学习笔记——入门篇:第一章-R语言介绍

    R语言 R语言学习笔记--入门篇:第一章-R语言介绍 文章目录 R语言 一.R语言简介 1.1.R语言的应用方向 1.2.R语言的特点 二.R软件的安装 2.1.Windows/Mac 2.2.Lin ...

  9. c语言中手机系统,一种手机课堂C语言编程系统的制作方法

    技术特征: 1.一种手机课堂C语言编程系统,其特征在于:该系统由手机端C语言编译运行单元.嵌入式主机端传输单元.台式机端显示单元和投影仪端显示单元组成:所述手机端C语言编译运行单元.嵌入式主机端传输单 ...

最新文章

  1. Junit单元测试需要知道的一些知识点
  2. react native native module
  3. nysql collation
  4. 王道计算机考研 数据结构 (树与二叉树)
  5. 提升销售人员的信息处理能力
  6. 文档自动排序长短_css 文档流
  7. 2020 操作系统第四天复习(知识点总结)
  8. 中国多媒体大会(ChinaMM 2020) 征文通知
  9. 参数少一半、速度快3倍:最新目标检测核心架构来了
  10. Linux中关于httpd仓库安装的简要步骤
  11. ip pv uv及相应统计shell
  12. 从入坑到入门 | 龙蜥开发者说第2期
  13. Matlab:数据包络分析(DEA)入门教程
  14. matlab中zeros()函数与ones()函数用法
  15. 数据中台02:数据中台架构
  16. 2022-2027年中国肺炎疫苗行业市场运行现状及投资战略研究报告
  17. 在线学习Biopython教程与手册 中文版
  18. 小学生用大数据研究苏轼?多亏有程序员爸爸;冒牌 Chrome 扩展现身官方商店,众多用户中招...
  19. failed to allocate memory 8 解决
  20. 相比传统垂直摄影测量,倾斜摄影测量的独特优势是什么?

热门文章

  1. 编辑php程序推荐的软件,PHP程序员都爱用的开发工具推荐
  2. 训练dnn_[预训练语言模型专题] MT-DNN(KD) : 预训练、多任务、知识蒸馏的结合
  3. 白帽子学Linux教程,网络安全工程师与白帽子***教你Kali Linux***:内网***实战技巧...
  4. java拆分数据查相等_scikit learn:train_test_split,我可以确保在不同的数据集上进行相同的拆分...
  5. 微信 php收藏功能实现,关于微信小程序收藏功能的实现
  6. 安全扫描工具_固件级安全,微软安全工具新增UEFI扫描功能
  7. 生成注释_java基础- Java编程规范与注释
  8. php 原理 阮一峰,全文Feed的终极解决方案
  9. 03Oracle Database 物理结构,逻辑结构
  10. 【NLP_Stanford课堂】语言模型1