注冊(cè) 登錄
Office中國(guó)論壇/Access中國(guó)論壇 返回首頁(yè)

ganlinlao的個(gè)人空間 http://m.mzhfr.cn/?230471 [收藏] [復(fù)制] [分享] [RSS]

日志

Freebasic菜鳥(niǎo)初學(xué)Freebasic基礎(chǔ)教程五:宏參數(shù)的字符串化#和宏參數(shù)的連接##符號(hào)

已有 3526 次閱讀2017-5-8 18:44 |個(gè)人分類(lèi):FreeBasic| Freebasic教程, Freebasic入門(mén), Freebasic

關(guān)于#和##

在C語(yǔ)言的宏中,#的功能是將其后面的宏參數(shù)進(jìn)行字符串化操作(Stringfication),簡(jiǎn)單說(shuō)就是在對(duì)它所引用的宏變量 通過(guò)替換后在其左右各加上一個(gè)雙引號(hào)。比如下面代碼中的宏:

#define WARN_IF(EXP)    do{ if (EXP)    fprintf(stderr, "Warning: " #EXP "/n"); }   while(0)

那么實(shí)際使用中會(huì)出現(xiàn)下面所示的替換過(guò)程:

WARN_IF (divider == 0);

被替換為


do {

if (divider == 0)

fprintf(stderr, "Warning" "divider == 0" "/n");

} while(0);

這樣每次divider(除數(shù))為0的時(shí)候便會(huì)在標(biāo)準(zhǔn)錯(cuò)誤流上輸出一個(gè)提示信息。

而##被稱(chēng)為連接符(concatenator),用來(lái)將兩個(gè)Token連接為一個(gè)Token。注意這里連接的對(duì)象是Token就行,而不一定 是宏的變量。比如你要做一個(gè)菜單項(xiàng)命令名和函數(shù)指針組成的結(jié)構(gòu)體的數(shù)組,并且希望在函數(shù)名和菜單項(xiàng)命令名之間有直觀的、名字上的關(guān)系。那么下面的代碼就非常實(shí)用:

struct command

{

char * name;

void (*function) (void);

};

#define COMMAND(NAME) { NAME, NAME ## _command }

// 然后你就用一些預(yù)先定義好的命令來(lái)方便的初始化一個(gè)command結(jié)構(gòu)的數(shù)組了:

struct command commands[] = {

COMMAND(quit),

COMMAND(help),

...

}

COMMAND宏在這里充當(dāng)一個(gè)代碼生成器的作用,這樣可以在一定程度上減少代碼密度,間接地也可以減少不留心所造成的錯(cuò)誤。我們還可以n個(gè)##符號(hào)連接 n+1個(gè)Token,這個(gè)特性也是#符號(hào)所不具備的。比如:

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);

// 這里這個(gè)語(yǔ)句將展開(kāi)為:

//     typedef struct _record_type name_company_position_salary;

關(guān)于...的使用

...在C宏中稱(chēng)為Variadic Macro,也就是變參宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)

// 或者

#define myprintf(templt,args...) fprintf(stderr,templt,args)

第一個(gè)宏中由于沒(méi)有對(duì)變參起名,我們用默認(rèn)的宏__VA_ARGS__來(lái)替代它。第二個(gè)宏 中,我們顯式地命名變參為args,那么我們?cè)诤甓x中就可以用args來(lái)代指變參了。同C語(yǔ)言的stdcall一樣,變參必須作為參數(shù)表的最有一項(xiàng)出 現(xiàn)。當(dāng)上面的宏中我們只能提供第一個(gè)參數(shù)templt時(shí),C標(biāo)準(zhǔn)要求我們必須寫(xiě)成:

myprintf(templt,);

的形式。這時(shí)的替換過(guò)程為:

myprintf("Error!/n",);

替換為:


fprintf(stderr,"Error!/n",);

這是一個(gè)語(yǔ)法錯(cuò)誤,不能正常編譯。這個(gè)問(wèn)題一般有兩個(gè)解決方法。首先,GNU CPP提供的解決方法允許上面的宏調(diào)用寫(xiě)成:

myprintf(templt);

而它將會(huì)被通過(guò)替換變成:

fprintf(stderr,"Error!/n",);

很明顯,這里仍然會(huì)產(chǎn)生編譯錯(cuò)誤(非本例的某些情況下不會(huì)產(chǎn)生編譯錯(cuò)誤)。除了這種方式外,c99和GNU CPP都支持下面的宏定義方式:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

這時(shí),##這個(gè)連接符號(hào)充當(dāng)?shù)淖饔镁褪钱?dāng)__VAR_ARGS__為空的時(shí)候,消除前面的那個(gè)逗號(hào)。那么此時(shí)的翻譯過(guò)程如下:

myprintf(templt);

被轉(zhuǎn)化為:


fprintf(stderr,templt);

這樣如果templt合法,將不會(huì)產(chǎn)生編譯錯(cuò)誤。 這里列出了一些宏使用中容易出錯(cuò)的地方,以及合適的使用方式。
錯(cuò)誤的嵌套-Misnesting

宏的定義不一定要有完整的、配對(duì)的括號(hào),但是為了避免出錯(cuò)并且提高可讀性,最好避免這樣使用。
由操作符優(yōu)先級(jí)引起的問(wèn)題-Operator Precedence Problem

由于宏只是簡(jiǎn)單的替換,宏的參數(shù)如果是復(fù)合結(jié)構(gòu),那么通過(guò)替換之后可能由于各個(gè)參數(shù)之間的操作符優(yōu)先級(jí)高于單個(gè)參數(shù)內(nèi)部各部分之間相互作用的操作符優(yōu)先級(jí),如果我們不用括號(hào)保護(hù)各個(gè)宏參數(shù),可能會(huì)產(chǎn)生預(yù)想不到的情形。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么

a = ceil_div( b & c, sizeof(int) );

將被轉(zhuǎn)化為:

a = ( b & c  + sizeof(int) - 1) / sizeof(int);

// 由于+/-的優(yōu)先級(jí)高于&的優(yōu)先級(jí),那么上面式子等同于:

a = ( b & (c + sizeof(int) - 1)) / sizeof(int);

這顯然不是調(diào)用者的初衷。為了避免這種情況發(fā)生,應(yīng)當(dāng)多寫(xiě)幾個(gè)括號(hào):

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分號(hào)-Semicolon Swallowing

通常情況下,為了使函數(shù)模樣的宏在表面上看起來(lái)像一個(gè)通常的C語(yǔ)言調(diào)用一樣,通常情況下我們?cè)诤甑暮竺婕由弦粋(gè)分號(hào),比如下面的帶參宏:

MY_MACRO(x);

但是如果是下面的情況:

#define MY_MACRO(x) {    /* line 1 */    /* line 2 */    /* line 3 */ }

//...

if (condition())

MY_MACRO(a);

else

{...}

這樣會(huì)由于多出的那個(gè)分號(hào)產(chǎn)生編譯錯(cuò)誤。為了避免這種情況出現(xiàn)同時(shí)保持MY_MACRO(x);的這種寫(xiě)法,我們需要把宏定義為這種形式:

#define MY_MACRO(x) do {

/* line 1 */    /* line 2 */    /* line 3 */ } while(0)

這樣只要保證總是使用分號(hào),就不會(huì)有任何問(wèn)題。
Duplication of Side Effects

這里的Side Effect是指宏在展開(kāi)的時(shí)候?qū)ζ鋮?shù)可能進(jìn)行多次Evaluation(也就是取值),但是如果這個(gè)宏參數(shù)是一個(gè)函數(shù),那么就有可能被調(diào)用多次從而達(dá)到不一致的結(jié)果,甚至?xí)l(fā)生更嚴(yán)重的錯(cuò)誤。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))

//...

c = min(a,foo(b));

這時(shí)foo()函數(shù)就被調(diào)用了兩次。為了解決這個(gè)潛在的問(wèn)題,我們應(yīng)當(dāng)這樣寫(xiě)min(X,Y)這個(gè)宏:

#define min(X,Y) ({    typeof (X) x_ = (X);    typeof (Y) y_ = (Y);    (x_ < y_) ? x_ : y_; })

({...})的作用是將內(nèi)部的幾條語(yǔ)句中最后一條的值返回,它也允許在內(nèi)部聲明變量(因?yàn)樗ㄟ^(guò)大括號(hào)組成了一個(gè)局部Scope)。

評(píng)論 (0 個(gè)評(píng)論)

facelist doodle 涂鴉板

您需要登錄后才可以評(píng)論 登錄 | 注冊(cè)

QQ|站長(zhǎng)郵箱|小黑屋|手機(jī)版|Office中國(guó)/Access中國(guó) ( 粵ICP備10043721號(hào)-1 )  

GMT+8, 2025-7-13 03:16 , Processed in 0.060491 second(s), 17 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回頂部