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. Mybatis-日志

    日志 目录 日志 1. 日志工厂 1. STDOUT_LOGGING标准日志输出 2. LOG4J 1. 先导入Log4J的包 2. log4j.properties 3. 配置log4j为日志的实现 ...

  2. 二、Git安装--Windows

    Git安装配置 在使用Git前我们需要先安装 Git.Git 目前支持 Linux/Unix.Solaris.Mac和 Windows 平台上运行. Git 各平台安装包下载地址为:http://gi ...

  3. 趣图:调试bug进行时

      扩展阅读 趣图:大神写实,左脚程序继续运行,右脚程序调试 趣图:Bug 多了,总有一个会把你坑了 趣图:领导在旁,只求代码无Bug

  4. AOP参数校验

      1.切面依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  5. UOS服务器常见问题

    UOS服务器安装完成之后,xshell工具无法远程, 描述 我安装完系统后,用xshell工具无法远程,必须在系统上才能进去我是通过centos7的kvm虚拟工具上刷的统信的系统,安装方式: 最小化安 ...

  6. Spring学习(三)--Spring的IOC

    1.BeanFactory和FactoryBean BeanFactory是一个接口类,定义了IOC容器最基本的形式,提供了IOC容器所应该遵守的基本服务契约. FactoryBean是一个能产生或者 ...

  7. 报表和仪表板在线设计器Stimulsoft Designer 最新版发布

    Stimulsoft Designer是统一的Stimulsoft框架的一部分,该框架包括用于生成报表和分析数据的引擎.报表设计器和查看器. 您可以在计算机上创建报表,继续使用在线设计器在云中对其进行 ...

  8. 【原创】xenomai内核解析--实时IPC概述

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1.概述 2.Real-time IPC 2. ...

  9. LRU Cache & Bloom Filter

    Cache 缓存 1. 记忆 2. 空间有限 3. 钱包 - 储物柜 4. 类似背代码模板,O(n) 变 O(1)     LRU Cache 缓存替换算法 1. Least Recently Use ...

  10. windows10 + docker利用文件映射进行编程开发

    0. 以安装swoole框架"easyswoole"举例,建议使用powershell或者cmder输入命令   1. 首先准备好window10专业版开启Hyper-V,然后下载 ...