【嵌入式】C語言高級編程-語句表達式(03)

00. 目錄

01. C語言的表達式

表達式和語句是 C 語言中的基礎概念。什麼是表達式呢?表達式就是由一系列操作符和操作數構成的式子。操作符可以是 C 語言標準規定的各種算術運算符、邏輯運算符、賦值運算符、比較運算符等。操作數可以是一個常量,也可以是一個變量。表達式也可以沒有操作符,單獨的一個常量甚至是一個字符串,也是一個表達式。下面的字符序列都是表達式:

22 + 33

22
    
a = a + b

i++
    
"hello world"

表達式一般用來數據計算或實現某種功能的算法。表達式有2個基本屬性:值和類型。如上面的表達式2+3,它的值爲5。根據操作符的不同,表達式可以分爲多種類型,如:

  • 關係表達式
  • 邏輯表達式
  • 條件表達式
  • 賦值表達式
  • 算術表達式

等等。

02. C語言的語句

語句是構成程序的基本單元,一般形式如下:表達式;

**表達式的後面加一個; 就構成了一條基本的語句。**編譯器在編譯程序、解析程序時,不是根據物理行,而是根據分號 ; 來判斷一條語句的結束標記的。如 i = 2 + 3; 這條語句,你寫成下面的形式也是可以編譯通過的:

#include <stdio.h>

int main(void)
{
    int i = 0;

    //合法的表達式
    i
    =
    1
    +
    2
    ;

    return 0;
}

03. C語言中的代碼塊

不同的語句,使用大括號{}括起來,就構成了一個代碼塊。C 語言允許在代碼塊裏定義一個變量,這個變量的作用域也僅限於這個代碼塊內,因爲編譯器就是根據{}來做入棧出棧操作來管理變量的作用域的。如下面的程序:

程序示例

#include <stdio.h>

int main(void)
{
    int var = 3;

    printf("var = %d\n", var);

    //代碼塊
    {
        int var = 88;
        printf("代碼塊 var = %d\n", var);
    }


    printf("var = %d\n", var);

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 5hello.c  
deng@itcast:~/tmp$ ./a.out  
var = 3
代碼塊 var = 88
var = 3

04. C語言中的語句表達式

GNU C 對 C 標準作了擴展,允許在一個表達式裏內嵌語句,允許在表達式內部使用局部變量、for 循環和 goto 跳轉語句。這樣的表達式,我們稱之爲語句表達式。語句表達式的格式如下:

({ 表達式1; 表達式2; 表達式3; ... 表達式n;})

語句表達式最外面使用小括號()括起來,裏面一對大括號{}包起來的是代碼塊,代碼塊裏允許內嵌各種語句。語句的格式可以是 “表達式;”這種一般格式的語句,也可以是循環、跳轉等語句。

**跟一般表達式一樣,語句表達式也有自己的值。語句表達式的值爲內嵌語句中最後一個表達式的值。**我們舉個例子,使用語句表達式求值。

程序示例

#include <stdio.h>

int main(void)
{
    int sum = 0;


    sum = 
    ({
        int sum = 0;

        for (int i = 0; i < 100; i++)
        {
            sum = sum + i;
        }

        sum;

    });

    printf("sum = %d\n", sum);

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
sum = 4950

在上面的程序中,通過語句表達式實現了從0到99的累加求和,因爲語句表達式的值等於最後一個表達式的值,所以在 for 循環的後面,我們要添加一個 sum; 語句表示整個語句表達式的值。如果不加這一句,你會發現 sum=0。或者你將這一行語句改爲100; 你會發現最後 sum 的值就變成了100,這是因爲語句表達式的值總等於最後一個表達式的值。

goto語句和語句表達式結合使用

在語句表達式內,我們同樣也可以使用 goto 進行跳轉。

程序示例

#include <stdio.h>

int main(void)
{
    int sum = 0;


    sum = 
    ({
        int sum = 0;

        for (int i = 0; i < 100; i++)
        {
            sum = sum + i;

            if (i == 10)
                goto loop;
        }

        sum;

    });

    printf("sum = %d\n", sum);
loop:
    printf("goto loop\n");
    printf("sum = %d\n", sum);

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
goto loop
sum = 0

05. 宏中使用語句表達式

語句表達式的亮點在於定義複雜功能的宏。使用語句表達式來定義宏,不僅可以實現複雜的功能,而且還能避免宏定義帶來的歧義和漏洞。下面就以一個宏定義例子,讓我們來看看語句表達式在宏定義中的應用!

曾經面試的過程中,面試官給我出了一道題:

請定義一個宏,求兩個數的最大值

別看這麼簡單的一個考題,面試官就能根據你寫出的宏,來判斷你的 C 語言功底,來決定給不給你 Offer。

初級程序員

對於學過 C 語言的同學,寫出這個宏基本上不是什麼難事,使用條件運算符就能完成:

#define  MAX(x, y)  x > y ? x : y

這是最基本的 C 語言語法,如果連這個也寫不出來,估計場面會比較尷尬。面試官爲了緩解尷尬,一般會對你說:小夥子,你很棒,回去等消息吧,有消息,我們會通知你!這時候,你應該明白:不用再等了,趕緊把這篇文章看完,接着面下家。這個宏能寫出來,也不要覺得你很牛 X,因爲這隻能說明你有了 C 語言的基礎,但還有很大的進步空間。

其實上面的寫法在語法上面沒有什麼問題,但是在實際中有bug。

程序示例

#include <stdio.h>

#define MAX(x, y) x > y ? x : y

int main(void)
{
    printf("Max = %d\n", MAX(1, 2));
    printf("Max = %d\n", MAX(2, 1));
    printf("Max = %d\n", MAX(2, 2));
    printf("Max = %d\n", MAX(1 != 1,  1 != 2));

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
Max = 2
Max = 2
Max = 2
Max = 0

測試程序的過程中,我們肯定要把各種可能出現的情況都測一遍。測試第10行語句,當宏的參數是一個表達式,發現實際運行結果爲 Max = 0,跟我們預期結果 Max = 1 不一樣。這是因爲,宏展開後,就變成了這個樣子:

printf("Max = %d\n", 1 != 1 > 1 != 2 ? 1 != 1 : 1 != 2);

因爲比較運算符 > 的優先級爲6,大於 !=(優先級爲7),所以展開的表達式,運算順序發生了改變,結果就跟我們的預期不一樣了。爲了避免這種展開錯誤,我們可以給宏的參數加一個小括號()來防止展開後,表達式的運算順序發生變化。這樣的宏才能算一個合格的宏:

#define  MAX(x, y)  (x) > (y) ? (x) : (y)

程序示例

#include <stdio.h>

#define MAX(x, y) (x) > (y) ? (x) : (y)

int main(void)
{
    printf("Max = %d\n", MAX(1, 2));
    printf("Max = %d\n", MAX(2, 1));
    printf("Max = %d\n", MAX(2, 2));
    printf("Max = %d\n", MAX(1 != 1,  1 != 2));

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
Max = 2
Max = 2
Max = 2
Max = 1

上面的宏,只能算合格,但還是存在漏洞。比如,我們使用下面的代碼測試:

程序示例

#include <stdio.h>

#define MAX(x, y) (x) > (y) ? (x) : (y)

int main(void)
{
    printf("max = %d\n", 3 + MAX(1, 2));

    return 0;
}

測試結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
max = 1

在程序中,我們打印表達式 3 + MAX(1, 2) 的值,預期結果應該是5,但實際運行結果卻是1。我們展開後,發現同樣有問題:

3 + (1) > (2) ? (1) : (2);

因爲運算符 + 的優先級大於比較運算符 >,所以這個表達式就變爲4 > 2 ? 1 : 2,最後結果爲1也就見怪不怪了。此時我們應該繼續修改這個宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括號將宏定義包起來,這樣就避免了當一個表達式同時含有宏定義和其它高優先級運算符時,破壞整個表達式的運算順序。如果你能寫到這一步,說明你比前面那個面試的同學強,前面那個同學已經回去等消息。

中級程序員

上面的宏,雖然解決了運算符優先級帶來的問題,但是仍存在一定的漏洞。比如,我們使用下面的測試程序來測試我們定義的宏:

程序示例

#include <stdio.h>

#define MAX(x, y) ((x) > (y) ? (x) : (y))

int main(void)
{
    int i = 2;
    int j = 6;

    printf("max = %d\n", MAX(i++, j++));

    return 0;
}

執行結果

deng@itcast:~/tmp$ vim 6.c  
deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
max = 7

在程序中,我們定義兩個變量 i 和 j,然後比較兩個變量的大小,並作自增運算。實際運行結果發現 max = 7,而不是預期結果 max = 6。這是因爲變量 i 和 j 在宏展開後,做了兩次自增運算,導致打印出 i 的值爲7。

遇到這種情況,那該怎麼辦呢? 這時候,語句表達式就該上場了。我們可以使用語句表達式來定義這個宏,在語句表達式中定義兩個臨時變量,分別來暫儲 i 和 j 的值,然後進行比較,這樣就避免了兩次自增、自減問題。

程序示例

#include <stdio.h>

#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\n", MAX(i++, j++));

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
max = 6

在語句表達式中,我們定義了2個局部變量 _x、_y 來存儲宏參數 x 和 y 的值,然後使用 _x 和 _y 來比較大小,這樣就避免了 i 和 j 帶來的2次自增運算問題。

高級程序員

在上面這個宏中,我們定義的兩個臨時變量數據類型是 int 型,只能比較兩個整型的數據。那對於其它類型的數據,就需要重新再定義一個宏了,這樣太麻煩了!我們可以基於上面的宏繼續修改,讓它可以支持任意類型的數據比較大小:

程序示例

#include <stdio.h>

#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 = %lf\n", MAX(float, 3.33, 4.44));


    return 0;
}

執行結果

deng@itcast:~/tmp$ ./a.out  
max = 6
max = 4.440000

在這個宏中,我們添加一個參數:type,用來指定臨時變量 _x 和 _y 的類型。這樣,我們在比較兩個數的大小時,只要將2個數據的類型作爲參數傳給宏,就可以比較任意類型的數據了。

上面的宏定義中,我們增加了一個type類型參數,來兼容不同的數據類型,其實我們還有更加牛逼的語法,typeof是GNU C新增的一個關鍵字,用來獲取數據類型,我們不用傳參進去,讓typeof直接獲取!

程序示例

#include <stdio.h>

#define MAX(x, y) ({    \
    typeof(x) _x = x;   \
    typeof(y) _y = y;   \
    (void)(&_x == &_y);  \
    _x > _y ? _x : _y;  \
    })


int main(void)
{
    int i = 2;
    int j = 6;


    printf("max = %d\n", MAX(i++, j++));
    printf("max = %lf\n", MAX(3.33, 4.44));

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 6.c  
deng@itcast:~/tmp$ ./a.out  
max = 6
max = 4.440000

在這個宏定義中,使用了 typeof 關鍵字用來獲取宏的兩個參數類型。乾貨在**(void) (&x == &y);**這句話,簡直是天才般的設計!一是用來給用戶提示一個錯誤,對於不同類型的指針比較,編譯器會給一個錯誤,提示兩種數據類型不同;二是,當兩個值比較,比較的結果沒有用到,有些編譯器可能會給出一個warning,加個(void)後,就可以消除這個警告!

06. Linux內核應用示例

Linux內核中使用語句表達式非常多。

#define min(x,y) ({ \
    typeof(x) _x = (x); \
    typeof(y) _y = (y); \
    (void) (&_x == &_y);    \
    _x < _y ? _x : _y; })

#define max(x,y) ({ \
    typeof(x) _x = (x); \
    typeof(y) _y = (y); \
    (void) (&_x == &_y);    \
    _x > _y ? _x : _y; })

#define min_t(type, a, b) min(((type) a), ((type) b))
#define max_t(type, a, b) max(((type) a), ((type) b))

07. 附錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章