超级炫酷的C语言技巧!

点击蓝字

ad589ab214e4ab30e9423b9625acf29b.png

关注我们

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 } )

因为位宽为负,所以可以确定,如果标志的数量超过了我们指派的空间,那么编译将会失败。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

4438eb5ebfcd4bd89642671ddbbb0105.png

a026deec11c6b3677df422e346424210.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337517.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

webgl 基础渲染demo_WebGL + ThreeJS 实现实时水下焦散 Part 1

知乎视频​www.zhihu.com采用 WebGL 和 ThreeJS 运行实时焦散运算&#xff0c;需要一点相关基础。本文主要介绍焦散的原理以及计算方法原作者https://github.com/martinRenou​github.com代码和原文https://github.com/martinRenou/threejs-caustics​github.com由于本人的笔电…

python代码性能分析_使用memory_profiler对代码进行性能分析会增加执行时间

我正在编写一个简单的应用程序&#xff0c;它将大文本文件拆分为较小的文件&#xff0c;并且我已经编写了2个版本&#xff0c;一个使用列表&#xff0c;另一个使用生成器。我使用memory_profiler模块对这两个版本进行了概要分析&#xff0c;并清楚地显示了生成器版本的更好的内…

超硬核C++BestPractices翻译与阅读笔记

点击蓝字关注我们硬货开始这本书的副标题是&#xff1a;45ish Simple Rules with Specific Action items for better C ,这本书是由大佬推荐的&#xff0c; C学习有必要掌握一下这45条最佳实践&#xff0c; 可以很大程度上提升代码的可读性和健壮性&#xff0c; 而且这本书也不…

redis集成spring_将Redis集成到您的Spring项目中

redis集成spring本文介绍如何通过注释配置将Redis缓存集成到您的spring项目中。 我们将从Gradle配置开始。 我们将使用jedis驱动程序。 group com.gkatzioura.spring version 1.0-SNAPSHOTapply plugin: java apply plugin: eclipse apply plugin: idea apply plugin: spring…

Python3实现翻转二叉树问题

Python3实现翻转二叉树问题翻转一棵二叉树。# 二叉树的结构如下 class TreeNode:def __init__(self, x):self.val xself.left Noneself.right None# 解决方案 class Solution:# 从根节点开始递归翻转其左子树和右子树def invertTree(self, root: TreeNode) -> TreeNode:i…

diskgenius单文件专业版_金蝶KIS专业版系列——系统工具六(业务套打工具)

导读&#xff1a;&#xff08;一&#xff09;问一问1.1.套打的作用1.2.举例&#xff08;二&#xff09;怎么进入套打设计器2.1.入口12.2.入口2&#xff08;三&#xff09;怎么使用套打设计器打开套打模板3.1.打开系统标准套打模板3.2.新建一个空白的套打模板3.3.保存套打模板3.…

面试常问的16个C语言问题,你全会吗?

点击蓝字关注我们金三银四不少小伙伴在找工作&#xff0c;这里我给大家分享一下面试中经常会遇到的一些嵌入式C语言问题&#xff0c;你看看能做到全会吗&#xff1f;1、用预处理指令#define 声明一个常数&#xff0c;用以表明1年中有多少秒&#xff08;忽略闰年问题&#xff09…

高性能 高可用 可弹性伸缩_性能,可伸缩性和活力

高性能 高可用 可弹性伸缩本文是我们名为Java Concurrency Essentials的学院课程的一部分。 在本课程中&#xff0c;您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识&#xff0c;并学习诸如原子性&#xff0c;同步和线程安全性的概念。 在这里查看 &#xff01;…

Python3实现32位整数翻转

Python3实现32位整数翻转给出一个 32 位的有符号整数&#xff0c;你需要将这个整数中每位上的数字进行反转。示例 1:输入: 123 输出: 321示例 2: 输入: -123 输出: -321示例 3: 输入: 120 输出: 21假设我们的环境只能存储得下 32 位的有符号整数&#xff0c;则其数值范围为 […

iptables命令_程序员最有用的linux命令汇总

总结程序员在工作中&#xff0c;最有用的linux命令如下&#xff1a;1、vi/vim 我们须要在服务器上代码一些代码时候&#xff0c;就用vi/vim命令就可以&#xff0c;vim是vi的升级&#xff0c;本色自带代码高亮工具&#xff0c;利于查看。dd 删除光标所在行o 向光标所在行向下增加…

抽象类和接口设计_如何设计类和接口

抽象类和接口设计本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看 &#xf…

详解C++异常

点击蓝字关注我们1、异常概念异常是一种处理错误的方式&#xff0c;当一个函数发现自己无法处理的错误时就可以抛出异常&#xff0c;让函数的直接或间接的调用者处理这个错误。throw: 当问题出现时&#xff0c;程序会抛出一个异常。这是通过使用 throw 关键字来完成的。catch: …

Python3 反转一个单链表

Python3 反转一个单链表反转一个单链表。示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL解题&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val x # …

python怎么改字体_python,tkinter_Tkinter Label 如何改变Label中的文字样式,例如给文字加删除线,python,tkinter - phpStudy...

Tkinter Label 如何改变Label中的文字样式&#xff0c;例如给文字加删除线 如题。未查到Tkinter下&#xff0c;促发条件后&#xff0c;是否能修改label中文字的样式 class Pomodoro_app(Tk): def add_task(self): global time time StringVar() time.set("Start") t…

使用所有对象共有的方法

本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看 &#xff01; 目录 1.简…

C语言基础知识:指针与数组的区别是什么

点击蓝字关注我们在C语言教程中我们使用通过数组名通过偏移和指针偏移都可以遍历数组&#xff0c;那么指针和数组到底有什么区别&#xff1f;&#xff1f;由于数组中的数据在内存中都是连续存放的&#xff0c;数组名默认就是数组的首地址&#xff0c;也是一个特殊的指针&#x…

Python3 三步爬楼梯问题

Python3 三步爬楼梯问题原题地址 https://leetcode-cn.com/problems/three-steps-problem-lcci/ 题目&#xff1a; 三步问题。有个小孩正在上楼梯&#xff0c;楼梯有n阶台阶&#xff0c;小孩一次可以上1阶、2阶或3阶。实现一种方法&#xff0c;计算小孩有多少种上楼梯的方式。…

python去重复功能_消除Python列表重复的几种方法,python,去,一些

做Python123平台上的列表去重题&#xff0c;复述题目&#xff1a; 去除列表中的重复元素&#xff0c;考虑以下几种情况&#xff1a; l [1, 1, 2, 3] l [[1], [1], [2], [3]] l [3, 2, 1, 1] 原文链接&#xff1a; 14025 总结一下网上的方法和我自己想的方法&#xff1a; 不考…

java java se_Java 8 SE可选,严格的方法

java java se大约两周前&#xff0c;Stephen Colebourne提出了使用Optional的实用方法 。 如果您阅读了它&#xff0c;您可能会从我以前的建议中猜到我不同意。 总览 我必须以免责声明开头&#xff0c;然后我将直接解释为什么我认为他的方法不那么理想。 所有不归因于他人的报…

std::thread 还有哪些使用“姿势”?

点击蓝字关注我们C11 线程创建每一个 C11 程序都包含一个主线程即 main() 函数。在 C11 中我们可以通过创建 std::thread 对象来创建新的线程。每个 std::thread 对象都可以与一个线程相关联。需要引用的头文件&#xff1a;1#include <thread>std::thread 的构造函数中接…