神奇的C語言

      固然下面列出來的幾點都是C的基礎用法,只不過是這些用法可能平時不會被注意。因此不少東西第一次看到的時候,可能會以爲很怪異,可是細細想一想就能很好的理解,也就能更好的清楚C語言的一些特性。可是在具體的編碼過程中,我仍是但願都能老老實實規規矩矩的。由於程序員不須要太多棱角,把代碼寫得規範整潔比耍小聰明要重要得多。下面我列舉了5個例子說明一些問題,若是你是老手看到這些就一笑而過吧,若是是新手,我相信仍是會獲得一些啓發的。程序員

      1. #和##在宏中的做用,以及帶參宏,參數的傳遞問題。數組

      2. 結構體中域的偏移位置的計算問題。函數

      3. 結構體的定義以及初始化的用法。測試

      4. 數組和指針在運算中的等價關係。優化

      5. 數組在棧中的「變異」。編碼

1. 例子:spa

    #include <stdio.h>
    
    #define _mir(a) #a
    int main()
    {
        char * s = _mir(
            struct _st{
                int a;
                int b;
                int c;
            };
        );
        printf( "%s\n", s );
        return 0;
    }

輸出:指針

    struct _st{ int a; int b; int c; };

說明:blog

A) 預編譯中#是將右邊的參數轉成一個字符串,##是將左右兩邊的參數鏈接成一個字符串。例子是#的用法。
B) 宏當中的參數實際上是以逗號(,)分隔的,其餘的字符其實都被當作同一個參數,可是換行和空白其實被處理過了,使參數在同一個行中。有興趣的本身多作些測試吧,這個用法能夠用於要寫包含特殊字符的字符串,省得要寫不少的轉義字符(\),可是中間不能有逗號,呵呵~內存

2. 例子:

    #include <stdio.h>
    
    struct _st{
        int a;
        int b;
        int c;
    };
    
    void main()
    {
        printf( "%d\n", &((struct _st*)0)->b );
    }

輸出:

    4

 說明:

A) &((struct _st*)0)->b 的做用是輸出_st結構體中b的偏移。爲何用0當成指針呢,其實很好理解:若是一個_st結構體的地址是0,那麼b的地址其實就是b在結構體中的偏移。
B) 其實按理,若是先作((struct _st*)0)->b運算,那麼程序確定異常,因此編譯器仍是作了優化的,具體編譯器怎麼作的,我也沒深究。

3. 例子:

    #include <stdio.h>
    
    struct _st{
            int a : 1;
            int b : 1;
            int c : 1;
    }s = {
            .c = 1,
            .b = 0,
            .a = 0
    };
    
    void main()
    {
            printf( "%d %d %d %d\n", sizeof(s), s.a, s.b, s.c );
    }

 輸出:

    4 0 0 -1

 說明:

A) 在結構體的初始化時,能夠指定域進行初始化,如例子中的.c = 1,順序能夠顛倒,這樣作的好處就是可讀性較強,對於大結構的初始化,在閱讀時很方便。缺點就是低版本的編譯器可能不支持。
B) 在結構體的聲明中,能夠指定域的大小,如例子中的int a : 1; 說明a只暫用一個bit,充分展現了C對二進制處理反面的親和力。
C) 爲何s.c輸出是-1,而不是1,其實很簡單,由於0xFFFFFFFF表示的是-1,那麼一個1bit大小的變量,全部位上面都是1,那麼它也表示-1。因此編碼的過程當中,有符號和無符號混用實際上是很危險的一件事情。
4. 例子:

    #include <stdio.h>
    void main()
    {
        int i;
        char a[10]="hello";
        
        0[a] = 'x';
        printf( "%s\n", a );
    
        for( i=0; i<10; i++ )
            printf( "%c", (rand()%10)["0123456789"] );
        printf( "\n" );
       
    }

輸出:

    xello
    1740948824

說明:

A) 0[a] = 'x';是什麼玩意兒?若是寫成a[0]='x';其實你就明白是什麼意思了,可是說白了,a[0]和0[a]在編譯器看來是同樣的。由於數組在作[]運行時,實際上是作指針的加法運行:a[0]等價於*(a+0)。因此0[a]也就等價於*(0+a)是徹底正確的。
B) 循環中功能是輸出一個10位的隨機數。其實也等價於"0123456789"[rand()%10]。這裏"0123456789"的類型是char*,因此指針也支持[]運算,由於[]運算其實就是加法運算。

5. 例子:

    #include <stdio.h>
        
    void func( char a[10] )
    {
        printf( "%d %d\n", &a, &a[0] );
    }
    
    void main()
    {
        char a[10];
        printf( "%d %d\n", &a, &a[0] );
        func( a );
    }

 輸出:

    1638208 1638208
    1638192 1638208

說明:

A) 爲何兩行的結果會不同?在通常狀況下,按個人理解,一個數組a,&a和&a[0]的值是同樣的。可是當a在形參當中時就不同了。例子中,func函數中的a,其實a變量是在func函數的棧當中,在func內部,a其實已經被轉化成char *a,因此&a是表示指針變量a在棧中的地址,而&a[0]表示的是指針指向的內存空間的第一個元素的地址,其實也就是調用者傳入的數組的第一個元素的地址。不知道我說明白了沒有!! B) 這個可能比較難理解,關鍵是明白一點,在數組做爲形參時,是被轉換成指針看待的。

相關文章
相關標籤/搜索