有網友在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 , ...
“...”用於聲明參數個數不定的函數。