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 pointp ={.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++不易,此路应携手前行。

欢迎关注我的编程公众號【草莓味狸猫】!

如果你想跟着小编一起学编程的话!

可以来我的C语言C++编程学习基地,【点击进入】!还有(源码,零基础教程,项目实战教学视频)!

 

【C语言C++编程学习笔记】一种很酷的 C 语言技巧,灵活运用编程技巧让你写代码事半功倍!的更多相关文章

  1. go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE

    go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE Go语言是谷歌2009发布的专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速 ...

  2. 转 网络编程学习笔记一:Socket编程

    题外话 前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公司使用的一些控件的开发,浏览器兼容性搞死人:但主要是因为这段时间一直在看html5的东西,看到web socket时觉得很有 ...

  3. MySQL基础之事务编程学习笔记

    MySQL基础之事务编程学习笔记 在学习<MySQL技术内幕:SQL编程>一书,并做了笔记.本博客内容是自己学了<MySQL技术内幕:SQL编程>事务编程一章之后,根据自己的理 ...

  4. Bash脚本编程学习笔记08:函数

    官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...

  5. Bash脚本编程学习笔记07:循环结构体

    本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文<Bash脚本编程学习笔记03:算术运算>中我有说明不要使用,不过自己忘记了.大家还是尽量使用 ...

  6. DirectX 11游戏编程学习笔记之8: 第6章Drawing in Direct3D(在Direct3D中绘制)(习题解答)

            本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com         注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...

  7. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  8. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  9. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  10. 【Visual C++】游戏编程学习笔记之六:多背景循环动画

    本系列文章由@二货梦想家张程 所写,转载请注明出处. 本文章链接:http://blog.csdn.net/terence1212/article/details/44264153 作者:ZeeCod ...

随机推荐

  1. IDAPython 安装和设置(windows+linux)

    安装步骤: 我采用的是IDA 6.8 windows安装: 机器上安装了Python,到Python的官网—http://www.python.org/getit/下载2.7的安装包.注意对应操作系统 ...

  2. Jenkins下Vue自动部署(一)

    1,服务器上安装docker http://www.runoob.com/docker/ubuntu-docker-install.html?tdsourcetag=s_pctim_aiomsg 2, ...

  3. Linux curl携带cookie测试接口

    问题: 休息在家,被告知要启动测试环境的一个定时任务,但是服务器在内网,连上vpn只能访问内网的开发环境,无法访问测试环境,于是进开发环境服务器,ping测试环境的ip,发现是通的,于是想到通过开发环 ...

  4. 初识ABP vNext(9):ABP模块化开发-文件管理

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 创建模块 模块开发 应用服务 运行模块 单元测试 模块使用 最后 前言 在之前的章节中介绍过ABP扩展实体,当时在用户 ...

  5. [LeetCode]105. 从前序与中序遍历序列构造二叉树(递归)、108. 将有序数组转换为二叉搜索树(递归、二分)

    题目 05. 从前序与中序遍历序列构造二叉树 根据一棵树的前序遍历与中序遍历构造二叉树. 注意: 你可以假设树中没有重复的元素. 题解 使用HashMap记录当前子树根节点在中序遍历中的位置,方便每次 ...

  6. java安全编码指南之:字符串和编码

    目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...

  7. 基于DDD+微服务的开发实战(1)

    1 DDD是什么? DDD是领域驱动设计,是Eric Evans于2003年提出的,离现在有17年. 2 为什么需要DDD 当软件越来越复杂,实际开发中,大量的业务逻辑堆积在一个巨型类中的例子屡见不鲜 ...

  8. Promise场景实例之图片加载

    所有图片加载完再添加到页面 { // 所有图片加载完再添加到页面 function loadImg(src) { return new Promise(function (resolve, rejec ...

  9. 阿里巴巴内部Java成长笔记,首次曝光!真的香!

    前言 关于技术人如何成长的问题,一直以来都备受关注,因为程序员职业发展很快,即使是相同起点的人,经过几年的工作或学习,会迅速拉开极大的差距,所以技术人保持学习,提升自己,才能够扛得住不断上赶的后浪,也 ...

  10. 永远不要在代码中使用“User”这个单词

    ​ 当你意识到你在项目开始时做的轻量.简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上.现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预 ...