本文由 伯樂在線 - Lingfeng Ai 翻譯自 Quora。
譯註:本文摘編自 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
|
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
|
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
|
struct
Foo { int
x; int
y; int
z; }; Foo foo = {.z = 3, .x = 5}; |
這段代碼首先初始化了foo.z
,然後初始化了foo.x
. foo.y
沒有被初始化,所以被置爲0。
這一語法同樣可以被用在數組中。以下三行代碼是等價的:
1
2
3
|
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
|
int
f( const int * restrict x,
int * y) { (*y)++; int
z = *x; (*y)--; return
z; } |
編譯器可能會假設,x
和y
所指的並不是同一個int
對象,因爲如果它們指向了同一個對象,則x
的值將可以通過y
修改,這正是你保證不會發生的。因此,將允許編譯器來優化f
,就好像函數原本被寫做如下這樣:
1
2
3
|
int
f( const int * restrict x,
int * y) { return
*x; } |
如果你違反協議向f
傳遞兩個指向同一int對象的指針時,將產生未定義行爲。
我猜想,引入這一特性最初的動機之一是想讓C語言在數值計算時可以Fortran一樣快。在Fortran 中,默認假定數組不會重疊,因此只有你通過restrict
限定詞來顯式的告訴編譯器數組不能重疊,編譯器才能在C語言中進行這樣的優化。
4. 靜態數組索引(C99)
在
1
2
3
|
void
f( int a[ static
10]) { /* ... */ } |
中,你向編譯器保證,你傳遞給f
的指針指向一個具有至少10個int
類型元素的數組的首個元素。我猜這也是爲了優化;例如,編譯器將會假定a
非空。編譯器還會在你嘗試要將一個可以被靜態確定爲null的指針傳入或是一個數組太小的時候發出警告。
在
1
2
3
|
void
f( int a[ const ]) { /* ... */ } |
你不能修改指針a.
,這和說明符int * const a.
作用是一樣的。然而,當你結合上一段中提到的static
使用,比如在int a[static const 10]
中,你可以獲得一些使用指針風格無法得到的東西。
5. 泛型表達式(C11)
這個表達式會在編譯期間根據控制表達式的類型,在一個含有一個或多個備選方案的集合中做出選擇。下面這個例子可以很好的說明這一切:
1
2
3
4
5
|
#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
|
#include <stdio.h> #include <math.h> int
main() { printf ( "%.0f\n" , pow (2,747)); return
0; } |
輸出結果:
1
|
740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328 |
答案:
這個問題包含兩個部分。
其一,2的次方可以在double
中被準確的保存而不產生任何精度上的損失(這一結論直到2^1023都是對的,再往後就會產生上溢,得到一個正無窮的值)
另外一部分,很多人猜測是語言實現中的某些特殊情況導致的,但是實際上並非如此。的確,當輸入的數據可以被2的某高次方整除時,有一部分代碼被執行了,但是本質上這只是通常實現工作時的一個副作用。基本上,printf
在打印數字(任何類型)的時候只是做了從二進制到十進制的轉換。並且由於結果對於浮點數可能會過大,printf
的內部實現包含和使用一個大整型實現,儘管在C中並沒有大整型這種變量(在gcc源代碼中,vfprintf.c
和dtoa.c
中包含了很多轉換,如果你想要了解可以一看。)
如果你嘗試打印3^474,
程序:
1
2
3
4
5
6
|
#include <stdio.h> #include <math.h> int
main() { printf ( "%.0f\n" , pow (3,474)); return
0; } |
輸出結果:
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
|
#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
|
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
|
static
void foo ( void ) { } static
void bar ( void ) { return
foo(); // 注意這裏的返回語句. } int
main ( void ) { bar(); return
0; } |
4. 逗號表達式的使用
通常逗號表達式會這樣使用:
1
2
3
4
|
for
( int i=0; i<10; i++, doSomethingElse()) { /* whatever */ } |
但是你可以在其他任何地方使用逗號表達式:
1
|
int
j = ( printf ( "Assigning variable j\n" ), getValueFromSomewhere()); |
每條語句都進行了求值,但是表達式的值是最後一個語句的值。
5. 將結構體初始化爲0
struct mystruct a = {0};
這將把結構體中全部元素初始化爲0
6. 多字符常量
int x = 'ABCD';
這會把x的值設置爲0×41424344(或者0×44434241,取決於架構)
7. printf
允許你使用變量來格式化格式說明符本身
1
2
3
4
5
6
7
8
|
#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
|
#include <stdio.h> void
main() { printf #include "fragment.c"
} |
且fragment.c
包含:
1
|
( "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
|
switch (c) { case
'A' ... 'Z' :
//do something break ; case
1 ... 5 : //do something } |
4. 使用前綴ob
來限定常數,使其被當做二進制數(gcc擴展語法)
1
|
printf ( "%d" ,0b1101);
// prints 13 |
5.完全正確的最短的C語言程序
1
|
main; |
譯註:雖然編譯沒有error但是卻不能執行
Karan Bansal
scanf()
的力量
假定我們有一個數組char a[100]
讀取一個字符串:scanf("%[^\n]\n", a);//表示一直讀取直到遇到'\n',並且忽略掉'\n'
讀取字符串直到遇到逗號:scanf("%[^,]", a);//但是這次不會忽略逗號
如果你想忽略掉某個輸入,使用在%
後使用*
,如果你想要得到John Smith
的姓:
1
|
scanf ( "%s %s" , temp, last_name);
//典型答案,使用一個臨時變量 |
1
2
3
|
scanf ( "%s" , last_name); scanf ( "%s" , last_name); // 另一種答案,使用一個變量但是調用兩次 `scanf()` |
1
2
|
scanf ( "%*s %s" , last); //最佳答案,因爲你不需要額外的變量或是調用兩次`scanf()` |
順便提一句,你應該非常小心的使用scanf
因爲它可能會是你的輸入緩衝溢出!通常你應該使用fgets
和sscanf
而不是僅僅使用scanf
,使用fgets
來讀取一行,然後用sscanf
來解析這一行,就像上面演示的一樣。
Afif Ahmed
~-n
等於n-1
-~n
等於n+1
原因:
當我們寫-n
時,實際上是以補碼形式儲存,所以-n
可以寫成~n + 1
,吧整個式子放在上面表達式的前面你就能明白原因了。