C語(yǔ)言常常讓人覺得它所能表達(dá)的東西非常有限。它不具有類似第一級(jí)函數(shù)和模式匹配這樣的高級(jí)功能听想。但是C非常簡(jiǎn)單腥刹,并且仍然有一些非常有用的語(yǔ)法技巧和功能,只是沒有多少人知道罷了汉买。
☆ 指定的初始化
很多人都知道像這樣來(lái)靜態(tài)地初始化數(shù)組:
int fibs[] = {1,1,2,3,5};
C99標(biāo)準(zhǔn)實(shí)際上支持一種更為直觀簡(jiǎn)單的方式來(lái)初始化各種不同的集合類數(shù)據(jù)(如:結(jié)構(gòu)體衔峰,聯(lián)合體和數(shù)組)。
☆ 數(shù)組
我們可以指定數(shù)組的元素來(lái)進(jìn)行初始化蛙粘。這非常有用垫卤,特別是當(dāng)我們需要根據(jù)一組#define來(lái)保持某種映射關(guān)系的同步更新時(shí)。來(lái)看看一組錯(cuò)誤碼的定義组题,如:
/* 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
/* ... */
現(xiàn)在葫男,假設(shè)我們想為每個(gè)錯(cuò)誤碼提供一個(gè)錯(cuò)誤描述的字符串抱冷。為了確保數(shù)組保持了最新的定義崔列,無(wú)論頭文件做了任何修改或增補(bǔ),我們都可以用這個(gè)數(shù)組指定的語(yǔ)法旺遮。
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"
/* ... */
};
這樣就可以靜態(tài)分配足夠的空間赵讯,且保證最大的索引是合法的,同時(shí)將特殊的索引初始化為指定的值耿眉,并將剩下的索引初始化為0边翼。
☆ 結(jié)構(gòu)體與聯(lián)合體
用結(jié)構(gòu)體與聯(lián)合體的字段名稱來(lái)初始化數(shù)據(jù)是非常有用的。假設(shè)我們定義:
struct point {
int x;
int y;
int z;
然后我們這樣初始化struct point:
struct pointp ={.x =3, .y =4, .z =5};
當(dāng)我們不想將所有字段都初始化為0時(shí)鸣剪,這種作法可以很容易的在編譯時(shí)就生成結(jié)構(gòu)體组底,而不需要專門調(diào)用一個(gè)初始化函數(shù)。
對(duì)聯(lián)合體來(lái)說(shuō)筐骇,我們可以使用相同的辦法债鸡,只是我們只用初始化一個(gè)字段。
☆ 宏列表
C中的一個(gè)慣用方法铛纬,是說(shuō)有一個(gè)已命名的實(shí)體列表厌均,需要為它們中的每一個(gè)建立函數(shù),將它們中的每一個(gè)初始化告唆,并在不同的代碼模塊中擴(kuò)展它們的名字棺弊。這在Mozilla的源碼中經(jīng)常用到,我就是在那時(shí)學(xué)到這個(gè)技巧的擒悬。例如模她,在我去年夏天工作的那個(gè)項(xiàng)目中,我們有一個(gè)針對(duì)每個(gè)命令進(jìn)行標(biāo)記的宏列表懂牧。其工作方式如下:
#define FLAG_LIST(_)? ? ? ? ? ? ? ? ? \
? ? _(InWorklist)? ? ? ? ? ? ? ? ? ? ? \
? ? _(EmittedAtUses)? ? ? ? ? ? ? ? ?? \
? ? _(LoopInvariant)? ? ? ? ? ? ? ? ?? \
? ? _(Commutative)? ? ? ? ? ? ? ? ? ?? \
? ? _(Movable)? ? ? ? ? ? ? ? ? ? ? ?? \
? ? _(Lowered)? ? ? ? ? ? ? ? ? ? ? ?? \
? ? _(Guard)
}
它定義了一個(gè)FLAG_LIST宏缝驳,這個(gè)宏有一個(gè)參數(shù)稱之為 _ ,這個(gè)參數(shù)本身是一個(gè)宏,它能夠調(diào)用列表中的每個(gè)參數(shù)用狱。舉一個(gè)實(shí)際使用的例子可能更能直觀地說(shuō)明問題运怖。假設(shè)我們定義了一個(gè)宏DEFINE_FLAG,如:
#define DEFINE_FLAG(flag) flag,
?? enum Flag {
? ? ?? None = 0,
? ? ?? FLAG_LIST(DEFINE_FLAG)
? ? ?? Total
?? };
#undef DEFINE_FLAG
對(duì)FLAG_LIST(DEFINE_FLAG)做擴(kuò)展能夠得到如下代碼:
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
};
接著夏伊,對(duì)每個(gè)參數(shù)都擴(kuò)展DEFINE_FLAG宏摇展,這樣我們就得到了enum如下:
enum Flag {
None = 0,
InWorklist,
EmittedAtUses,
LoopInvariant,
Commutative,
Movable,
Lowered,
Guard,
Total
};
接著,我們可能要定義一些訪問函數(shù)溺忧,這樣才能更好的使用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
一步步的展示其過程是非常有啟發(fā)性的咏连,如果對(duì)它的使用還有不解,可以花一些時(shí)間在gcc –E上鲁森。
☆ 編譯時(shí)斷言
這其實(shí)是使用C語(yǔ)言的宏來(lái)實(shí)現(xiàn)的非常有“創(chuàng)意”的一個(gè)功能祟滴。有些時(shí)候,特別是在進(jìn)行內(nèi)核編程時(shí)歌溉,在編譯時(shí)就能夠進(jìn)行條件檢查的斷言垄懂,而不是在運(yùn)行時(shí)進(jìn)行,這非常有用痛垛。不幸的是草慧,C99標(biāo)準(zhǔn)還不支持任何編譯時(shí)的斷言。
但是匙头,我們可以利用預(yù)處理來(lái)生成代碼漫谷,這些代碼只有在某些條件成立時(shí)才會(huì)通過編譯(最好是那種不做實(shí)際功能的命令)。有各種各樣不同的方式都可以做到這一點(diǎn)蹂析,通常都是建立一個(gè)大小為負(fù)的數(shù)組或結(jié)構(gòu)體舔示。最常用的方式如下:
/* 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)計(jì)算結(jié)果為一個(gè)非零值(即C中的真值),即! (condition)為零值电抚,那么代碼將能順利地編譯惕稻,并生成一個(gè)大小為零的結(jié)構(gòu)體。如果(condition)結(jié)果為0(在C真為假)喻频,那么在試圖生成一個(gè)負(fù)大小的結(jié)構(gòu)體時(shí)缩宜,就會(huì)產(chǎn)生編譯錯(cuò)誤。
它的使用非常簡(jiǎn)單甥温,如果任何某假設(shè)條件能夠靜態(tài)地檢查锻煌,那么它就可以在編譯時(shí)斷言。例如姻蚓,在上面提到的標(biāo)志列表中宋梧,標(biāo)志集合的類型為uint32_t,所以狰挡,我們可以做以下斷言:
STATIC_ASSERT(Total<=32)
它擴(kuò)展為:
(void)sizeof(struct { int:-!(Total <=32)? })
現(xiàn)在捂龄,假設(shè)Total<=32释涛。那么-!(Total <= 32)等于0,所以這行代碼相當(dāng)于:
(void)sizeof(struct { int:0 })
這是一個(gè)合法的C代碼【氩祝現(xiàn)在假設(shè)標(biāo)志不止32個(gè)唇撬,那么-!(Total <= 32)等于-1,所以這時(shí)代碼就相當(dāng)于:
(void)sizeof(struct {int: -1} )
因?yàn)槲粚挒樨?fù)展融,所以可以確定窖认,如果標(biāo)志的數(shù)量超過了我們指派的空間,那么編譯將會(huì)失敗告希。
自學(xué)C/C++不易扑浸,此路應(yīng)攜手前行。
歡迎關(guān)注我的編程公眾號(hào)【草莓味貍貓】燕偶!??
如果你想跟著小編一起學(xué)編程的話喝噪!
可以來(lái)下面的C語(yǔ)言C++編程學(xué)習(xí)基地!
還有(源碼指么,零基礎(chǔ)教程酝惧,項(xiàng)目實(shí)戰(zhàn)教學(xué)視頻)!