表達(dá)式
成都創(chuàng)新互聯(lián)公司為企業(yè)級客戶提高一站式互聯(lián)網(wǎng)+設(shè)計服務(wù),主要包括網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、成都app開發(fā)、小程序設(shè)計、宣傳片制作、LOGO設(shè)計等,幫助客戶快速提升營銷能力和企業(yè)形象,創(chuàng)新互聯(lián)各部門都有經(jīng)驗豐富的經(jīng)驗,可以確保每一個作品的質(zhì)量和創(chuàng)作周期,同時每年都有很多新員工加入,為我們帶來大量新的創(chuàng)意。表達(dá)式和語句是 C 語言中的基礎(chǔ)概念。什么是表達(dá)式呢?表達(dá)式就是由一系列操作符和操作數(shù)構(gòu)成的式子。操作符可以是 C 語言標(biāo)準(zhǔn)規(guī)定的各種算術(shù)運算符、邏輯運算符、賦值運算符、比較運算符等。操作數(shù)可以是一個常量,也可以是一個變量。表達(dá)式也可以沒有操作符,單獨的一個常量甚至是一個字符串,也是一個表達(dá)式。下面的字符序列都是表達(dá)式:
表達(dá)式一般用來數(shù)據(jù)計算或?qū)崿F(xiàn)某種功能的算法。表達(dá)式有2個基本屬性:值和類型。如上面的表達(dá)式2+3,它的值為5。根據(jù)操作符的不同,表達(dá)式可以分為多種類型,如:
語句
語句是構(gòu)成程序的基本單元,一般形式如下:
表達(dá)式 ;
i = 2 + 3 ;
表達(dá)式的后面加一個; 就構(gòu)成了一條基本的語句。編譯器在編譯程序、解析程序時,不是根據(jù)物理行,而是根據(jù)分號 ; 來判斷一條語句的結(jié)束標(biāo)記的。如 i = 2 + 3; 這條語句,你寫成下面的形式也是可以編譯通過的:
i =
2 +
3
;
代碼塊
不同的語句,使用大括號{}括起來,就構(gòu)成了一個代碼塊。C 語言允許在代碼塊里定義一個變量,這個變量的作用域也僅限于這個代碼塊內(nèi),因為編譯器就是根據(jù){}來做入棧出棧操作來管理變量的作用域的。如下面的程序:
int main(void)
{
int i = 3;
printf("i=%d\n",i);
{
int i = 4;
printf("i=%d\n",i);
}
printf("i=%d\n",i);
return 0;
}
運行結(jié)果為:
i=3
i=4
i=3
語句表達(dá)式的定義
GNU C 對 C 標(biāo)準(zhǔn)作了擴展,允許在一個表達(dá)式里內(nèi)嵌語句,允許在表達(dá)式內(nèi)部使用局部變量、for 循環(huán)和 goto 跳轉(zhuǎn)語句。這樣的表達(dá)式,我們稱之為語句表達(dá)式。語句表達(dá)式的格式如下:
({ 表達(dá)式1; 表達(dá)式2; 表達(dá)式3; })
語句表達(dá)式最外面使用小括號()括起來,里面一對大括號{}包起來的是代碼塊,代碼塊里允許內(nèi)嵌各種語句。語句的格式可以是 “表達(dá)式;”這種一般格式的語句,也可以是循環(huán)、跳轉(zhuǎn)等語句。
跟一般表達(dá)式一樣,語句表達(dá)式也有自己的值。語句表達(dá)式的值為內(nèi)嵌語句中最后一個表達(dá)式的值。我們舉個例子,使用語句表達(dá)式求值。
int main(void)
{
int sum = 0;
sum =
({
int s = 0;
for( int i = 0; i < 10; i++)
s = s + i;
s;
});
printf("sum = %d\n",sum);
return 0;
}
在上面的程序中,通過語句表達(dá)式實現(xiàn)了從1到10的累加求和,因為語句表達(dá)式的值等于最后一個表達(dá)式的值,所以在 for 循環(huán)的后面,我們要添加一個 s; 語句表示整個語句表達(dá)式的值。如果不加這一句,你會發(fā)現(xiàn) sum=0?;蛘吣銓⑦@一行語句改為100; 你會發(fā)現(xiàn)最后 sum 的值就變成了100,這是因為語句表達(dá)式的值總等于最后一個表達(dá)式的值。
語句表達(dá)式內(nèi)使用 goto 跳轉(zhuǎn)
在上面的程序中,我們在語句表達(dá)式內(nèi)定義了局部變量,使用了 for 循環(huán)語句。在語句表達(dá)式內(nèi),我們同樣也可以使用 goto 進(jìn)行跳轉(zhuǎn)。
int main(void)
{
int sum = 0;
sum =
({
int s = 0;
for( int i = 0; i < 10; i++)
s = s + i;
goto here;
s;
});
printf("sum = %d\n",sum);
here:
printf("here:\n");
printf("sum = %d\n",sum);
return 0;
}
語句表達(dá)式的亮點在于定義復(fù)雜功能的宏。使用語句表達(dá)式來定義宏,不僅可以實現(xiàn)復(fù)雜的功能,而且還能避免宏定義帶來的歧義和漏洞。下面就以一個宏定義例子,讓我們來見識見識語句表達(dá)式在宏定義中的強悍殺傷力!
假如你此刻正在面試,面試職位是:Linux C 語言開發(fā)工程師。面試官給你出了一道題:
請定義一個宏,求兩個數(shù)的大值。
別看這么簡單的一個考題,面試官就能根據(jù)你寫出的宏,來判斷你的 C 語言功底,來決定給不給你 Offer。
合格
對于學(xué)過 C 語言的同學(xué),寫出這個宏基本上不是什么難事,使用條件運算符就能完成:
#define MAX(x,y) x > y ? x : y
這是最基本的 C 語言語法,如果連這個也寫不出來,估計場面會比較尷尬。面試官為了緩解尷尬,一般會對你說:小伙子,你很棒,回去等消息吧,有消息,我們會通知你!這時候,你應(yīng)該明白:不用再等了,趕緊把這篇文章看完,接著面下家。這個宏能寫出來,也不要覺得你很牛 X,因為這只能說明你有了 C 語言的基礎(chǔ),但還有很大的進(jìn)步空間。比如,我們寫一個程序,驗證一下我們定義的宏是否正確:
#define MAX(x,y) x > y ? x : y
int main(void)
{
printf("max=%d",MAX(1,2));
printf("max=%d",MAX(2,1));
printf("max=%d",MAX(2,2));
printf("max=%d",MAX(1!=1,1!=2));
return 0;
}
測試程序嗎,我們肯定要把各種可能出現(xiàn)的情況都測一遍。這不,測試第4行語句,當(dāng)宏的參數(shù)是一個表達(dá)式,發(fā)現(xiàn)實際運行結(jié)果為 max=0,跟我們預(yù)期結(jié)果 max=1 不一樣。這是因為,宏展開后,就變成了這個樣子:
printf("max=%d",1!=1>1!=2?1!=1:1!=2);
因為比較運算符 > 的優(yōu)先級為6,大于 !=(優(yōu)先級為7),所以展開的表達(dá)式,運算順序發(fā)生了改變,結(jié)果就跟我們的預(yù)期不一樣了。為了避免這種展開錯誤,我們可以給宏的參數(shù)加一個小括號()來防止展開后,表達(dá)式的運算順序發(fā)生變化。這樣的宏才能算一個合格的宏:
#define MAX(x,y) (x) > (y) ? (x) : (y)
中等
上面的宏,只能算合格,但還是存在漏洞。比如,我們使用下面的代碼測試:
#define MAX(x,y) (x) > (y) ? (x) : (y)
int main(void)
{
printf("max=%d",3 + MAX(1,2));
return 0;
}
在程序中,我們打印表達(dá)式 3 + MAX(1, 2) 的值,預(yù)期結(jié)果應(yīng)該是5,但實際運行結(jié)果卻是1。我們展開后,發(fā)現(xiàn)同樣有問題:
3 + (1) > (2) ? (1) : (2);
因為運算符 + 的優(yōu)先級大于比較運算符 >,所以這個表達(dá)式就變?yōu)?>2?1:2,最后結(jié)果為1也就見怪不怪了。此時我們應(yīng)該繼續(xù)修改這個宏:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
使用小括號將宏定義包起來,這樣就避免了當(dāng)一個表達(dá)式同時含有宏定義和其它高優(yōu)先級運算符時,破壞整個表達(dá)式的運算順序。如果你能寫到這一步,說明你比前面那個面試合格的同學(xué)強,前面那個同學(xué)已經(jīng)回去等消息了,我們接著面試下一輪。
良好
上面的宏,雖然解決了運算符優(yōu)先級帶來的問題,但是仍存在一定的漏洞。比如,我們使用下面的測試程序來測試我們定義的宏:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
int i = 2;
int j = 6;
printf("max=%d",MAX(i++,j++));
return 0;
}
在程序中,我們定義兩個變量 i 和 j,然后比較兩個變量的大小,并作自增運算。實際運行結(jié)果發(fā)現(xiàn) max = 7,而不是預(yù)期結(jié)果 max = 6。這是因為變量 i 和 j 在宏展開后,做了兩次自增運算,導(dǎo)致打印出 i 的值為7。
遇到這種情況,那該怎么辦呢? 這時候,語句表達(dá)式就該上場了。我們可以使用語句表達(dá)式來定義這個宏,在語句表達(dá)式中定義兩個臨時變量,分別來暫儲 i 和 j 的值,然后進(jìn)行比較,這樣就避免了兩次自增、自減問題。
#define MAX(x,y)({ \
int _x = x; \
int _y = y; \
_x > _y ? _x : _y; \
})
int main(void)
{
int i = 2;
int j = 6;
printf("max=%d",MAX(i++,j++));
return 0;
}
在語句表達(dá)式中,我們定義了2個局部變量 _x、_y 來存儲宏參數(shù) x 和 y 的值,然后使用 _x 和 _y 來比較大小,這樣就避免了 i 和 j 帶來的2次自增運算問題。
你能堅持到了這一關(guān),并寫出這樣自帶 BGM 的宏,面試官心里可能已經(jīng)有了給你 Offer 的意愿了。但此時此刻,千萬不要驕傲!為了徹底打消面試官的心理顧慮,我們需要對這個宏繼續(xù)優(yōu)化。
優(yōu)秀
在上面這個宏中,我們定義的兩個臨時變量數(shù)據(jù)類型是 int 型,只能比較兩個整型的數(shù)據(jù)。那對于其它類型的數(shù)據(jù),就需要重新再定義一個宏了,這樣太麻煩了!我們可以基于上面的宏繼續(xù)修改,讓它可以支持任意類型的數(shù)據(jù)比較大?。?/p>
#define MAX(type,x,y)({ \
type _x = x; \
type _y = y; \
_x > _y ? _x : _y; \
})
int main(void)
{
int i = 2;
int j = 6;
printf("max=%d\n",MAX(int,i++,j++));
printf("max=%f\n",MAX(float,3.14,3.15));
return 0;
}
在這個宏中,我們添加一個參數(shù):type,用來指定臨時變量 _x 和 _y 的類型。這樣,我們在比較兩個數(shù)的大小時,只要將2個數(shù)據(jù)的類型作為參數(shù)傳給宏,就可以比較任意類型的數(shù)據(jù)了。如果你能在面試中,寫出這樣的宏,面試官肯定會非常高興,他一般會跟你說:稍等,待會 HR 會跟你談待遇問題。
還能不能更牛逼?
如果你想薪水拿得高一點,待遇好一點,此時不應(yīng)該驕傲,你應(yīng)該大手一揮:且慢,我還可以更牛逼!
上面的宏定義中,我們增加了一個type類型參數(shù),來兼容不同的數(shù)據(jù)類型,此時此刻,為了薪水,我們應(yīng)該把這個也省去。如何做到?使用typeof就可以了,typeof是GNU C新增的一個關(guān)鍵字,用來獲取數(shù)據(jù)類型,我們不用傳參進(jìn)去,讓typeof直接獲??!
#define max(x, y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y);\
_x > _y ? _x : _y; })
在這個宏定義中,使用了 typeof 關(guān)鍵字用來獲取宏的兩個參數(shù)類型。干貨在(void) (&x == &y);這句話,簡直是天才般的設(shè)計!一是用來給用戶提示一個警告,對于不同類型的指針比較,編譯器會給一個警告,提示兩種數(shù)據(jù)類型不同;二是,當(dāng)兩個值比較,比較的結(jié)果沒有用到,有些編譯器可能會給出一個warning,加個(void)后,就可以消除這個警告!
此刻,面試官看到你的這個宏,估計會倒吸一口氣:乖乖,果然是后生可畏,這家伙比我還牛逼!你等著,HR待會過來跟你談薪水!
恭喜你,拿到offer了!
語句表達(dá)式,作為 GNU C 對 C 標(biāo)準(zhǔn)的一個擴展,在內(nèi)核中,尤其是在內(nèi)核的宏定義中,被大量的使用。使用語句表達(dá)式定義宏,不僅可以實現(xiàn)復(fù)雜的功能,還可以避免宏定義帶來的一些歧義和漏洞。比如在 Linux 內(nèi)核中,max_t 和 min_t 的宏定義,就使用了語句表達(dá)式:
#define min_t(type, x, y) ({ \
type __min1 = (x); \
type __min2 = (y); \
__min1 < __min2 ? __min1 : __min2; })
#define max_t(type, x, y) ({ \
type __max1 = (x); \
type __max2 = (y); \
__max1 > __max2 ? __max1 : __max2; })
除此之外,在 Linux 內(nèi)核、GNU 開源軟件中,你會發(fā)現(xiàn),還有大量的宏定義使用了語句表達(dá)式。通過本節(jié)教程的學(xué)習(xí),相信大家以后再碰到這種使用語句表達(dá)式定義的宏,肯定就知道是怎么回事了,心中有丘壑,再也不用慌。
本教程根據(jù) C語言嵌入式Linux高級編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號:宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。