譯註:本文摘編自 Quora 的一個熱門問答貼。 請在linux系統下測試本文中出現的程式碼
Andrew Weimholt 的回覆:
switch
語句中的case
關鍵詞可以放在if-else
或者是迴圈當中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
; html-script: false ]switch (a) { case 1:; // ... if (b==2) { case 2:; // ... } else case 3: { // ... for (b=0;b<10;b++) { case 5:; // ... } } break; case 4: |
Brian Bi 的回覆:
1. 宣告緊隨用途之後
理解宣告有一條很簡單的法則,不過不是什麼“從左向右”這種沒道理卻到處宣傳的法則。這一法則的觀點是,一個宣告是要告訴你,你所宣告的物件要如何使用。例如:
1 2 3 4 |
; html-script: false ]int *p; /* *p是int型別的, 因此p是指向int型別的指標 */ int a[5]; /* a[0], ..., a[4] 是int型別的, 因此a是int型別的陣列 */ int *ap[5]; /* *ap[0], .., *ap[4] 是int型別的, 因此ap是包含指向int型別指標的指標陣列 */ int (*pa)[5]; /* (*pa)[0], ..., (*pa)[4] 是int型別的, 因此pa是指向一個int型別陣列的指標 */ |
更多詳情請看這裡: Brian Bi’s answer to C (programming language): Why doesn’t C use better notation for pointers?
2. 指定初始化:
在C99之前,你只能按順序初始化一個結構體。在C99中你可以這樣做:
1 2 3 4 5 6 |
; html-script: false ]struct Foo { int x; int y; int z; }; Foo foo = {.z = 3, .x = 5}; |
這段程式碼首先初始化了foo.z
,然後初始化了foo.x
. foo.y
沒有被初始化,所以被置為0。
這一語法同樣可以被用在陣列中。以下三行程式碼是等價的:
1 2 3 |
; html-script: false ]int a[5] = {[1] = 2, [4] = 5}; int a[] = {[1] = 2, [4] = 5}; int a[5] = {0, 2, 0, 0, 5}; |
3. 受限指標(C99):
restrict
關鍵詞是一個限定詞,可以被用在指標上。它向編譯器保證,在這個指標的生命週期內,任何通過該指標訪問的記憶體,都只能被這個指標改變。比如,在
1 2 3 4 5 6 |
; html-script: false ]int f(const int* restrict x, int* y) { (*y)++; int z = *x; (*y)--; return z; } |
編譯器可能會假設,x
和y
所指的並不是同一個int
物件,因為如果它們指向了同一個物件,則x
的值將可以通過y
修改,這正是你保證不會發生的。因此,將允許編譯器來優化f
,就好像函式原本被寫做如下這樣:
1 2 3 |
; html-script: false ]int f(const int* restrict x, int* y) { return *x; } |
如果你違反協議向f
傳遞兩個指向同一int物件的指標時,將產生未定義行為。
我猜想,引入這一特性最初的動機之一是想讓C語言在數值計算時可以Fortran一樣快。在Fortran 中,預設假定陣列不會重疊,因此只有你通過restrict
限定詞來顯式的告訴編譯器陣列不能重疊,編譯器才能在C語言中進行這樣的優化。
4. 靜態陣列索引(C99)
在
1 2 3 |
; html-script: false ]void f(int a[static 10]) { /* ... */ } |
中,你向編譯器保證,你傳遞給f
的指標指向一個具有至少10個int
型別元素的陣列的首個元素。我猜這也是為了優化;例如,編譯器將會假定a
非空。編譯器還會在你嘗試要將一個可以被靜態確定為null的指標傳入或是一個陣列太小的時候發出警告。
在
1 2 3 |
; html-script: false ]void f(int a[const]) { /* ... */ } |
你不能修改指標a.
,這和說明符int * const a.
作用是一樣的。然而,當你結合上一段中提到的static
使用,比如在int a[static const 10]
中,你可以獲得一些使用指標風格無法得到的東西。
5. 泛型表示式(C11)
這個表示式會在編譯期間根據控制表示式的型別,在一個含有一個或多個備選方案的集合中做出選擇。下面這個例子可以很好的說明這一切:
1 2 3 4 5 |
; html-script: false ]#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \ )(X) |
因此,如果expr
是long 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次方而不產生誤差。這是為什麼呢?
程式:
1 2 3 4 5 6 |
; html-script: false ]#include <stdio.h> #include <math.h> int main() { printf("%.0f\n",pow(2,747)); return 0; } |
輸出結果:
1 |
; html-script: false ]740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328 |
答案:
這個問題包含兩個部分。
其一,2的次方可以在double
中被準確的儲存而不產生任何精度上的損失(這一結論直到2^1023都是對的,再往後就會產生上溢,得到一個正無窮的值)
另外一部分,很多人猜測是語言實現中的某些特殊情況導致的,但是實際上並非如此。的確,當輸入的資料可以被2的某高次方整除時,有一部分程式碼被執行了,但是本質上這只是通常實現工作時的一個副作用。基本上,printf
在列印數字(任何型別)的時候只是做了從二進位制到十進位制的轉換。並且由於結果對於浮點數可能會過大,printf
的內部實現包含和使用一個大整型實現,儘管在C中並沒有大整型這種變數(在gcc原始碼中,vfprintf.c
和dtoa.c
中包含了很多轉換,如果你想要了解可以一看。)
如果你嘗試列印3^474,
程式:
1 2 3 4 5 6 |
; html-script: false ]#include <stdio.h> #include <math.h> int main() { printf("%.0f\n",pow(3,474)); return 0; } |
輸出結果:
1 |
; html-script: false ]14304567688284661153278974752312031583901259203711201647725006924333106634519194823303091330277684776547167093155518867557708479462413116497799842448027156309852771422896137582164841870381535840058702788340257784498862132559872 |
結果仍然是一個很大的數且位數也正確,但是這一次卻不夠精確。這裡會產生一個相對誤差,因為3^474不能以雙精度浮點數準確的表示。準確的數應該是這樣的143045676882846603471…
譯註:在linux系統上是可以的,在windows 64位上後面會有很多0
Utkal Sinha
我發現一些C語言特性或者是小技巧,我覺得只有很少的人知道。
1. 不使用加號來使數字相加
因為printf()
函式返回它所列印的字元的個數,我們可以利用這一點來使數字相加,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
; html-script: false ]#include<stdio.h>; int add(int a,int b){ if(if(a!=0&&b!=0)) return printf("%*c%*c",a,'\r',b,'\r'); else return a!=0?a:b; } int main(){ int A = 0, B = 0; printf("Enter the two numbers to add\n"); scanf("%d %d",&A,&B); printf("Required sum is %d",add(A,B)); return 0; } |
利用位操作同樣也可以做到:
1 2 3 4 5 6 7 |
; html-script: false ]int Add(int x, int y) { if (y == 0) return x; else return Add( x ^ y, (x & y) << 1); } |
2. 條件運算子的用法
通常我們都這樣使用它:
x = (y < 0) ? 10 : 20;
但是同樣也可以這樣用:
(y < 0 ? x : y) = 20;
3. 在一個返回值為void
的函式中寫一個return
語句
1 2 3 4 5 6 7 8 9 |
; html-script: false ]static void foo (void) { } static void bar (void) { return foo(); // 注意這裡的返回語句. } int main (void) { bar(); return 0; } |
4. 逗號表示式的使用
通常逗號表示式會這樣使用:
1 2 3 4 5 |
; html-script: false ] for (int i=0; i<10; i++, doSomethingElse()) { /* whatever */ } |
但是你可以在其他任何地方使用逗號表示式:
1 |
; html-script: false ]int j = (printf("Assigning variable j\n"), getValueFromSomewhere()); |
每條語句都進行了求值,但是表示式的值是最後一個語句的值。
5. 將結構體初始化為0
struct mystruct a = {0};
這將把結構體中全部元素初始化為0
6. 多字元常量
int x = 'ABCD';
這會把x的值設定為0x41424344(或者0x44434241,取決於架構)
7. printf
允許你使用變數來格式化格式說明符本身
1 2 3 4 5 6 7 8 |
; html-script: false ]#include <stdio.h> int main() { int a = 3; float b = 6.412355; printf("%.*f\n",a,b); return 0; } |
*
符號可以達到這一目的
希望這些可以幫助到大家
此致敬禮
Vivek Nagarajan
你可以在奇怪的地方使用#include
如果你寫:
1 2 3 4 5 6 7 |
; html-script: false ]#include <stdio.h> void main() { printf #include "fragment.c" } |
且fragment.c
包含:
1 |
; html-script: false ]("dayum!\n"); |
這完全沒有問題。只要#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擴充套件語法)
1 2 3 4 5 |
; html-script: false ]switch(c) { case 'A' ... 'Z': //do something break; case 1 ... 5 : //do something } |
4. 使用字首ob
來限定常數,使其被當做二進位制數(gcc擴充套件語法)
1 |
; html-script: false ]printf("%d",0b1101); // prints 13 |
5.完全正確的最短的C語言程式
1 |
; html-script: false ]main; |
譯註:雖然編譯沒有error但是卻不能執行
Karan Bansal
scanf()
的力量
假定我們有一個陣列char a[100]
讀取一個字串:
scanf("%[^\n]\n", a);//表示一直讀取直到遇到'\n',並且忽略掉'\n'
讀取字串直到遇到逗號:
scanf("%[^,]", a);//但是這次不會忽略逗號
如果你想忽略掉某個輸入,使用在%
後使用*
,如果你想要得到John Smith
的姓:
1 |
; html-script: false ]scanf("%s %s", temp, last_name); //典型答案,使用一個臨時變數 |
1 2 3 |
; html-script: false ]scanf("%s", last_name); scanf("%s", last_name); // 另一種答案,使用一個變數但是呼叫兩次 `scanf()` |
1 2 |
; html-script: false ]scanf("%*s %s", last); //最佳答案,因為你不需要額外的變數或是呼叫兩次`scanf()` |
順便提一句,你應該非常小心的使用scanf
因為它可能會是你的輸入緩衝溢位!通常你應該使用fgets
和sscanf
而不是僅僅使用scanf
,使用fgets
來讀取一行,然後用sscanf
來解析這一行,就像上面演示的一樣。
Afif Ahmed
~-n
等於n-1
-~n
等於n+1
原因:
當我們寫-n
時,實際上是以補碼形式儲存,所以-n
可以寫成~n + 1
,吧整個式子放在上面表示式的前面你就能明白原因了。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式