有網友在http://www.cnblogs.com/pmer/archive/2013/03/15/2960809.html 129樓問
“運算式、語句、聲明之間的區別到底是什嗎?”
這個問題對很多人來說確實很模糊,甚至很多出版物中也有很多錯誤的講述,故此本文力圖對此做一詳盡說明和澄清。
運算式(Expression)
根據C標準,運算式(Expression)是運算子(operator)和運算元(operand)所構成的序列,例如"3+2"。最簡單的情形,也可能沒有運算子,例如“3”。
可能需要特意提一下的是,函數調用也是運算式。例如 sqrt(1.0),這裡的“()”是一個運算子,sqrt和1.0則是“()”這個運算子的運算元。
運算式所表達的可能是要求電腦進行值的計算(computation of a value),例如“3+2”這個運算式,表示的就是要求電腦求3+2的值。當然,運算式同時也表示這個計算所得到的值。
運算式還可能用來指明一個資料對象(object)或一個函數(function)。例如,若有 int i; 那麼在 i = 1 這個運算式中的 i 這個子運算式就是指 i 所代表的那個object——一塊連續的記憶體。再比如, 運算式 & printf 中,printf這個子運算式表示的是相應的printf()庫函數。
除此之外,運算式可能產生副效應(side effects),例如printf("ABC") 這個運算式的值是3,其副效應是在標準輸出裝置上連續輸出A、B、C這三個字元。再比如,對於 int i; 運算式 i = 3 的值是3,副效應是 i 所代表的資料對象被寫入了3。
語句(Statement)
C語言語句(Statement)的定義從其功能上來說有些含糊:語句規定的是一種“動作”(action)。這種動作最顯著的特點是規定了如何跳轉或結束。例如,對於int i; "i = 1 ;"這條語句的意義是到“;”位置時 i = 1 這個運算式求值(Evaluate), 即值的計算和副效應必須完成。再比如“return ;”語句規定的是跳轉到另一位置繼續執行。
儘管語句的功能有些含糊,但其文法形式確實極其清晰的。形式上較為簡單的語句有:
運算式語句(expression-statement),形式為:
expression ;
其中的運算式是可選的。這種語句以“;”作為結束標誌。對於初學者來說最常見的運算式語句是調用printf()函數所形成的:printf("Hello World\n");
跳躍陳述式(jump-statement),包括:
goto 語句、continue 語句、break 語句和return 語句。這種語句也以“;”作為結束標誌。
複合陳述式(compound-statement),其一般形式為:
{block-item-list}
其中的block-item-list可以有也可以沒有,可以是聲明,也可以是語句。
有一點特別需要說明,複合陳述式並非以“;”作為結束標誌。這表明語句中不一定有“;”。國內C語言書中有一種常見的陳詞濫調:“一個語句必須在最後有一個分號,分號是語句中不可缺少的組成部分”(譚浩強,《C程式設計》第四版,p58),那絕對在是瞪著眼睛說胡話。因為複合陳述式的最後就不需要有分號。最簡單的複合陳述式只有一對“{}”,根本就沒分號什麼事兒。
其他的語句都是構造性的,即是在其他語句的基礎上構造出來的。比如標號語句(labeled-statement),就是在語句前加一冒號及其他內容:
identifier : statement
case constant-expression : statement
default : statement
選擇語句也是依據類似的原則在語句的基礎上構造的:
if ( expression ) statement
if ( expression ) statement else statement
switch ( expression ) statement
值得一提的是switch語句並不一定是在複合陳述式的基礎上構造的,這和很多人的常識不同。例如,
switch ( i ) case 0:case 1:case 2:printf("ABC\n");
就是一條完全合法的switch語句。 其功能等價於
if (i==0||i==1||i==2) printf("ABC\n");
C語言的迴圈語句同樣是在語句的基礎上構造而成。
while ( expression ) statement
do statement while ( expression ) ;
for ( expressionopt ; expression ; expression ) statement
for ( declaration expression; expression ) statement
在這些語句中,只有do-while語句最後一定是以“;”作為結束標誌。
總之,由於複合陳述式並不是以分號作為結束標誌,而很多語句都是在語句的基礎上進一步構造而成,因此除了運算式語句、跳躍陳述式及do-while語句一定是以分號結束,其他語句則可能以分號結束,也可能不以分號結束,分號並不是語句必須的組成部分。
聲明(Declarations)
除了static_assert declaration(C11),聲明的作用都是向編譯器解釋一個或多個標識符的含義及屬性。
C標準給出的聲明的一般形式為
declaration-specifiers init-declarator-list ;
因此,通常情況下聲明都有“;”。
但實際上,函數定義同時也是聲明(A definition of an identifier is a declaration),在格式上卻與此不符。
儘管功能明確,形式簡單,但聲明其實是C語言中最複雜的成分,至少是之一。這種複雜性被 declarator 漠然地掩蓋起來了。
C語言要求每個聲明至少要聲明一個 declarator 、一個tag或一個枚舉成員。也就是說
int i ;//沒問題,i是declarator
int ; //違法,無declarator
struct t;//合法,聲明了一個tag——t
enum { A };//是合法的,聲明了枚舉成員A
struct { int a ;}; //違背語言要求,可能招致警告。(在C語言早期,並不違反標準)
union { int a ;}; //違背語言要求,可能招致警告。(在C語言早期,並不違反標準)
聲明不是語句(《C Primer Plus》一書把聲明稱為“聲明語句”,明顯是把C++的概念張冠李戴到C上面了)。一個簡單的證明是,聲明無法按照語句規則構成語句。譬如
if( 1 ) int i ; //這在C語言中是不成立的
有些聲明也叫定義(definition),所有的定義都是聲明。包括
導致保留儲存區的對象聲明。最典型的就是局部變數的聲明。
含有函數體的函式宣告,即函數定義。函數定義儘管在形式上不符合由“;”結尾的形式,但同樣是一個聲明,有時也被稱之為外部聲明(external declaration)或外部定義(external definition)。但習慣上,很多人所說的“函式宣告”往往特指那些非定義式的函式宣告。
除此之外,枚舉常量(enumeration constant)和typedef name也是定義。
declaration-specifiers包括儲存類別說明符(storage class specifier),類型說明符(type specifier) ,類型限定符(type qualifier),函數說明符(function specifier),從C11起又增加了一個對齊說明符(alignment specifier)。
和多數人常識不符的是,typedef也屬於儲存類別說明符(storage class specifier)。
把typedef類型定義歸為聲明並把typedef關鍵字作為儲存類別說明符(storage class specifier)只是為了文法描述上的方便。實際上typedef並沒有像其他幾個儲存類別說明符(extern,static,auto,register,_Thread_local(C11))那樣對儲存類別做出任何說明。
在每個聲明中,儲存類別說明符只能出現一次。C11引人新的儲存類別說明符_Thread_local之後,這個說法不再成立了。
類型說明符(type specifier)包括:void、char、short、int、long、float、double、signed、unsigned、_Bool、_Complex、atomic-type-specifier、struct-or-union-specifier、enum-specifier、typedef-name,使用時可能是類型說明符的某種組合,譬如 long double。其中_Bool、_Complex是C99新增的,最初還增加了一個_Imaginary,後來又刪除了。atomic-type-specifier是C11新增的。此外,C11在struct-or-union聲明中正式支援了匿名結構體或聯合體(anonymous structures and unions)。
類型限定符(type qualifier)在C90中只有const和volatile兩個,const借鑒的是C++,volatile則完全是C標準委員會的發明。C99增加了一個restrict,只用於指標類型,提出這個限定符的目的是更好地最佳化。C又增加了一個_Atomic類型限定符。這個限定符也是唯一的一個Atomic type specifiers,這種Atomic 類型說明符是C11新增的內容。
函數說明符(function specifier)是C99之後出現的。C99增加了一個inline這個函數說明符,C11又新增了一個_Noreturn函數說明符用來說明不返回調用者的函數,例如exit()函數。
C11增加的另一個新的聲明說明符是對齊說明符(alignment specifier)。對齊問題不再像以前那樣猶抱琵琶半遮面,而是直接擺到了案頭上。
聲明的init-declarator-list部分由一個或多個用“,”分隔的init-declarator組成。init-declarator可以是一個單獨的declarator或初始化形式declarator = initializer。
最簡單的declarator是一個標識符。這是一種direct-declarator。在direct-declarator前面可以加* type-qualifier-list用以聲明指標。聲明指標時type-qualifier置於*的右側。而在declaration-specifiers中則沒有這樣的次序要求。
direct-declarator還可以具有下面幾種形式:
( declarator )
direct-declarator [ type-qualifier-list assignment-expressionopt ]
direct-declarator [ static type-qualifier-list assignment-expression ]
direct-declarator [ type-qualifier-list static assignment-expression ]
direct-declarator [ type-qualifier-list * ]
direct-declarator ( parameter-type-list )
direct-declarator ( identifier-list )
第一種中的“()”可能改變對declarator的類型解釋(例如:int *p[1];int (*p)[1];)。
自C99起,不再要求聲明數組時[]內是整數常量運算式,可以用變數描述數組尺寸。例如 int a[n]; 這就是所謂的VLA。VLA是C99的正式特性,在C11中則是一個可選特性。編譯器可以支援,也可以不支援。至於[]內的* 、static及type-qualifier-list,只用於聲明函數參數時。
函式宣告“()”內雖然可以有parameter-type-list和 identifier-list兩種形式,但後者其實是一種正在逐步廢棄的形式,即
void f(a)
int a;
{/*……*/}
這種形式。除了在老代碼中可以看到,現在已經很少有人這樣寫了。現代風格的C語言提倡函數原型風格的聲明,即 參數為parameter-type-list這種形式。這種形式有兩種:
parameter-list
或
parameter-list , ...
“...”用於聲明參數個數不定的函數。