C語言有哪些鮮為人知的特性?

艾凌風發表於2014-07-28

譯註:本文摘編自 Quora 的一個熱門問答貼。 請在linux系統下測試本文中出現的程式碼

Andrew Weimholt 的回覆:

switch語句中的case 關鍵詞可以放在if-else或者是迴圈當中

Brian Bi 的回覆:

1. 宣告緊隨用途之後

理解宣告有一條很簡單的法則,不過不是什麼“從左向右”這種沒道理卻到處宣傳的法則。這一法則的觀點是,一個宣告是要告訴你,你所宣告的物件要如何使用。例如:

更多詳情請看這裡: Brian Bi’s answer to C (programming language): Why doesn’t C use better notation for pointers?

2. 指定初始化:

在C99之前,你只能按順序初始化一個結構體。在C99中你可以這樣做:

這段程式碼首先初始化了foo.z,然後初始化了foo.x. foo.y 沒有被初始化,所以被置為0。
這一語法同樣可以被用在陣列中。以下三行程式碼是等價的:

3. 受限指標(C99):

restrict關鍵詞是一個限定詞,可以被用在指標上。它向編譯器保證,在這個指標的生命週期內,任何通過該指標訪問的記憶體,都只能被這個指標改變。比如,在

編譯器可能會假設,xy 所指的並不是同一個int物件,因為如果它們指向了同一個物件,則x的值將可以通過y修改,這正是你保證不會發生的。因此,將允許編譯器來優化f,就好像函式原本被寫做如下這樣:

如果你違反協議向f傳遞兩個指向同一int物件的指標時,將產生未定義行為。

我猜想,引入這一特性最初的動機之一是想讓C語言在數值計算時可以Fortran一樣快。在Fortran 中,預設假定陣列不會重疊,因此只有你通過restrict 限定詞來顯式的告訴編譯器陣列不能重疊,編譯器才能在C語言中進行這樣的優化。

4. 靜態陣列索引(C99)

中,你向編譯器保證,你傳遞給f 的指標指向一個具有至少10個int 型別元素的陣列的首個元素。我猜這也是為了優化;例如,編譯器將會假定a 非空。編譯器還會在你嘗試要將一個可以被靜態確定為null的指標傳入或是一個陣列太小的時候發出警告。

你不能修改指標a.,這和說明符int * const a.作用是一樣的。然而,當你結合上一段中提到的static 使用,比如在int a[static const 10] 中,你可以獲得一些使用指標風格無法得到的東西。

5. 泛型表示式(C11)

這個表示式會在編譯期間根據控制表示式的型別,在一個含有一個或多個備選方案的集合中做出選擇。下面這個例子可以很好的說明這一切:

因此,如果exprlong double型別的, cbrt(expr) 被轉換為cbrtl(expr),如果是float型別 則轉換為cbrtf(expr) ,或是轉換為cbrt(expr),如果是其他不同的型別(比如說double )。注意,_Generic 可以用在巨集以外的地方,但是用在巨集裡面最好因為C不允許你進行函式過載。

6. wint_t (C99)

我相信大家都知道wint_t 但是 wint_t 到底是個什麼鬼東西呢?

好吧,記住fgetc 實際上並不會返回 char 。它會返回int。顯然這是因為fgetc 必須返回返回一個與其他char 都不同的值,也就是EOF,表示到達檔案末尾。基於相同的原因,fgetwc 並不返回wchar_t。它會返回一個型別,叫做wint_t 可以表示所有無效wchar_t 型別,包括WEOF,來表示到達檔案末尾。

Michal Forišek

下面這段C程式可以準確的列印2的747次方而不產生誤差。這是為什麼呢?

程式:

輸出結果:

答案:

這個問題包含兩個部分。
其一,2的次方可以在double 中被準確的儲存而不產生任何精度上的損失(這一結論直到2^1023都是對的,再往後就會產生上溢,得到一個正無窮的值)

另外一部分,很多人猜測是語言實現中的某些特殊情況導致的,但是實際上並非如此。的確,當輸入的資料可以被2的某高次方整除時,有一部分程式碼被執行了,但是本質上這只是通常實現工作時的一個副作用。基本上,printf 在列印數字(任何型別)的時候只是做了從二進位制到十進位制的轉換。並且由於結果對於浮點數可能會過大,printf 的內部實現包含和使用一個大整型實現,儘管在C中並沒有大整型這種變數(在gcc原始碼中,vfprintf.cdtoa.c 中包含了很多轉換,如果你想要了解可以一看。)

如果你嘗試列印3^474,

程式:

輸出結果:

結果仍然是一個很大的數且位數也正確,但是這一次卻不夠精確。這裡會產生一個相對誤差,因為3^474不能以雙精度浮點數準確的表示。準確的數應該是這樣的143045676882846603471

譯註:在linux系統上是可以的,在windows 64位上後面會有很多0

Utkal Sinha

我發現一些C語言特性或者是小技巧,我覺得只有很少的人知道。

1. 不使用加號來使數字相加

因為printf() 函式返回它所列印的字元的個數,我們可以利用這一點來使數字相加,程式碼如下:

利用位操作同樣也可以做到:

2. 條件運算子的用法

通常我們都這樣使用它:
x = (y < 0) ? 10 : 20;
但是同樣也可以這樣用:
(y < 0 ? x : y) = 20;

3. 在一個返回值為void 的函式中寫一個return 語句

4. 逗號表示式的使用

通常逗號表示式會這樣使用:

但是你可以在其他任何地方使用逗號表示式:

每條語句都進行了求值,但是表示式的值是最後一個語句的值。

5. 將結構體初始化為0

struct mystruct a = {0};

這將把結構體中全部元素初始化為0

6. 多字元常量

int x = 'ABCD';

這會把x的值設定為0x41424344(或者0x44434241,取決於架構)

7. printf 允許你使用變數來格式化格式說明符本身

* 符號可以達到這一目的

希望這些可以幫助到大家
此致敬禮

Vivek Nagarajan

你可以在奇怪的地方使用#include

如果你寫:

fragment.c 包含:

這完全沒有問題。只要#include 包含完整可解析的C表示式,前處理器並不在意它放在什麼位置。

Vipul Mehta

1. printf 格式限定符中指定(POSIX擴充套件語法)

printf("%4$d %3$d %2$d %1$d", 1, 2, 3, 9); //將會列印9 3 2 1

2. 在scanf 中忽略輸入輸入

scanf("%*d%d", &a);// 如果輸入1 2,則只會得到2

3. 在switch 中使用範圍(gcc擴充套件語法)

4. 使用字首ob 來限定常數,使其被當做二進位制數(gcc擴充套件語法)

5.完全正確的最短的C語言程式

譯註:雖然編譯沒有error但是卻不能執行

Karan Bansal

scanf()的力量

假定我們有一個陣列char a[100]
讀取一個字串:
scanf("%[^\n]\n", a);//表示一直讀取直到遇到'\n',並且忽略掉'\n'

讀取字串直到遇到逗號:
scanf("%[^,]", a);//但是這次不會忽略逗號

如果你想忽略掉某個輸入,使用在% 後使用*,如果你想要得到John Smith 的姓:

順便提一句,你應該非常小心的使用scanf 因為它可能會是你的輸入緩衝溢位!通常你應該使用fgetssscanf 而不是僅僅使用scanf,使用fgets 來讀取一行,然後用sscanf 來解析這一行,就像上面演示的一樣。

Afif Ahmed

~-n 等於n-1
-~n 等於n+1

原因:
當我們寫-n時,實際上是以補碼形式儲存,所以-n 可以寫成~n + 1,吧整個式子放在上面表示式的前面你就能明白原因了。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

C語言有哪些鮮為人知的特性? C語言有哪些鮮為人知的特性?

相關文章