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. python之unittest框架使用

    一.unittest框架 unittest属于python内置的单元测试框架. 二.unittest框架的核心概念 test case:指测试用例.unittest中提供了一个基本类TestCase, ...

  2. myBatis源码解析-二级缓存的实现方式

    1. 前言 前面近一个月去写自己的mybatis框架了,对mybatis源码分析止步不前,此文继续前面的文章.开始分析mybatis一,二级缓存的实现.附上自己的项目github地址:https:// ...

  3. 分别用canvas和css3的transform做出钟表的效果

    两种方式实际上在js上的原理都是一样的.都是获取时间对象,再获取时间对象的时分秒,时分秒乘以其旋转一刻度(一秒.一分.一小时)对应的角度.css3中要赋值于transform:rotate(角度),c ...

  4. Docker Swarm 集群管理利器核心概念扫盲

    Swarm 简介 Docker Swarm 是 Docker 官方推出的容器集群管理工具,基于 Go 语言实现.代码开源在:https://github.com/docker/swarm 使用它可以将 ...

  5. 国产化之路-麒麟V10操作系统安装.net core 3.1 sdk

    随着芯片国产化,操作系统国产化,软件国产化的声浪越来越高,公司也已经把开发项目国产化提上了日程,最近搞来了台长城的国产化电脑主机,用来搞试验,安装的是麒麟V10的操作系统,国产化折腾之路就此开始,用的 ...

  6. Python爬虫实战练习:爬取美团旅游景点评论数据

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 今年的国庆节还有半个月就要来了,相信很多的小伙伴还是非常期待这个小长假的.国庆节是一年中的小 ...

  7. hystrix源码小贴士之之hystrix-metrics-event-stream

    hystrix-metrics-event-stream主要提供了一些servlet,可以让用户通过http请求获取metrics信息. HystrixSampleSseServlet 继承了Http ...

  8. Java并发包之Executors

    概述 Executor.ExecutorService.ScheduledExecutorService.ThreadFactory.Callable的工厂和工具类. 方法 构造一个固定线程数目的线程 ...

  9. Git裸仓库的分支(Active Branch)切换

    Git裸仓库的Active Branch切换方法 在服务器上通过init --bare创建了一个裸仓库作为远程仓库使用,并且存在三个分支(master/kid/develop),但在使用中发现代码虽然 ...

  10. JVM学习(四)JVM调优

    一.调优命令 Sun JDK监控和故障处理命令有jps.jstat.jmap.jhat.jstack.jinfo jps,JVM Process Status Tool,显示指定系统内所有的HotSp ...