C是一種具有模塊化設計的命令式程式語言,具有簡約、直觀的設計風格,與相對清晰、簡單的語言結構。
在談C的語言結構之前,需要先解釋一些基本元素的含義。
一、表達式表達式是一個或多個變量、常量、函數與運算符按照特定規則的組合,表達式根據特定的優先級與運算符進行計算並返回一個值。
注意:單個變量、常量或函數名也是一個表達式。
以下面表達式為例:
var = fn(1) + 5
其中var、fn、1、5都是表達式,其返回值為自身的值;fn(1)也是一個表達式,返回函數調用的返回值;fn(1) + 5也是一個表達式,返回算術運算的結果;var = fn(1) + 5也是一個表達式,返回賦值號左邊的值,此例中此值被丟棄。
特別地,調用返回值為void類型的函數將返回一個void類型的值,但此值無法被使用,只能丟棄。
1、完整表達式如果一個表達式不是其他表達式的子表達式,則稱這個表達式為「完整表達式」。
以下面幾個語句為例:
var = 1 + 2;
fn(var + 1);
if (var + 1) ;
✪ 表達式語句中的整個表達式為完整表達式,如上面的var = 1 + 2和fn(var + 1),但第二行的var + 1不屬於完整表達式。(函數調用實際上是運算符()對函數指針和參數進行運算)
✪ if、while、switch括號中的表達式以及for括號中的每個分量都是完整表達式,所以第三行的var + 1是完整表達式。
二、副作用除了返回值以外對程序造成的其他影響稱為副作用。比如修改變量的值,執行I/O操作等。
對於如下表達式:
var = 5
表達式的返回值為5,副作用為將5賦值給變量var。
而對於以下表達式:
1 + 2
表達式返回3,沒有副作用。
通常說起「副作用」,總是覺得無關緊要或儘量避免,但對於命令式程式語言來說,副作用才是程序執行的主要目的。
比如我們調用printf函數,我們通常並不關心它的返回值,而是需要它把特定的字符輸出到屏幕,而標準輸出正是這個函數的副作用。
三、語句語句是C的基本執行單元,語句不返回結果,僅執行副作用。語句可分為簡單語句和複合語句。
在C語言中,「;」 不是分隔符(for語句中的 「;」 除外),而是大部分語句的結尾。
申明不屬於語句,因為申明通常不產生副作用,即使有時候會產生副作用(如初始化),但仍不將其視為語句,申明也以「;」結尾。
C有5種語句:
✪ 表達式語句
✪ 跳轉語句
✪ 選擇語句
✪ 循環語句
✪ 標籤語句
1、簡單語句和複合語句簡單語句指內部不包含其他語句的語句。如表達式語句和跳轉語句。最簡單的語句是只有一個 「;」 的空語句。
複合語句的定義與簡單語句相反,即其內部有其他語句。
將幾個語句用 {} 括起來就形成了複合語句「塊」,最簡單的複合語句是空塊 {}。
複合語句可以進行多次複合,比如塊可以嵌套,複合語句的子語句可以是其他複合語句。
C語言沒有else if關鍵字,這種語法結構只是將上一個if語句的else部分複合了另一個if語句,將他們寫在一起是為了使代碼更簡潔。
2、表達式語句表達式語句為一個完整表達式後跟一個分號構成的語句。若表達式為空,就構成了空語句。
表達式語句是最簡單也是最常見的語句。以下語句都是表達式語句:
;
1 + 2;
var = 5;
printf("hello, world\n");
3、跳轉語句跳轉語句用於改變代碼的執行順序。跳轉語句包括continue、break、return、goto語句。
4、選擇語句選擇語句是複合語句,其作用是根據特定表達式的值對程序執行進行跳轉。如if、if else、switch語句。
5、循環語句循環語句是複合語句,其作用是根據特定表達式的值讓一部分代碼反覆執行多次,如while、dowhile、for語句。循環語句也可以通過選擇語句和跳轉語句實現。
6、標籤語句在其他語句前加上標籤即是標籤語句。標籤語句是複合語句,可以在任何語句(包括標籤語句)前添加標籤。
因為申明不是語句,所以不能在申明前添加標籤。對於下面的代碼,gcc給出如下錯誤提示:
lable:
int var = 0;
error: a label can only be part of a statement and a declaration is not a statement
case 標籤是一種特殊的標籤,其標誌是在標籤前的case關鍵字。case標籤只能在switch語句中使用,case標籤允許且只允許標籤名使用整數,並且把標籤的作用域限定在當前的switch語句中。
標籤是語句的一部分,而不只是個記號,所以塊末尾不能是標籤。
比如下面語句:
switch (var) {
case 1:
case 2:
case 3:
;
}
最後的分號是不可以省略的,空語句複合case 3標籤形成標籤語句,然後又複合case 1和case 2,所以這個塊內只有一條完整的複合語句。
四、C語言結構C語言代碼文件包括源文件和頭文件,源文件可以進行編譯和連結,頭文件一般通過預處理指令包含到源文件中使用。
源文件由預處理指令、申明、類型定義、函數定義和注釋組成。
預處理指令和注釋可以出現在源文件的任何位置而不影響其功能,而申明和類型定義的位置決定了其作用域。
申明有時會伴隨定義,定義一定會包含申明。
函數定義由返回值類型、函數名、參數列表和語句塊組成。語句只能出現在函數定義內部。
C源文件必須有且只能有一個main函數,C89規定,main函數的返回值必須為int類型,如果程序正常終止,應返回0。
標準的main函數應寫為 int main(void); 或 int main(int argc, char const *argv[]); 。
五、序列點C語言通過序列點控制副作用的執行。在該點處之前的求值的所有的副作用已經發生,在它之後的求值的所有副作用仍未開始。
序列點的存在一定程度上保證了程序按照預期執行,但仍存在一些未定義的行為。
C中的序列點很少,因為C追求效率,更少的序列點可以給編譯器更多優化的空間。
注意:C中有很多符號同時承擔多種功能,在不同語境下扮演不同的身份。
C的序列點包括:
1、&& 與 || 運算符&& 與 || 運算符會先對左邊的表達式求值並執行副作用。
對 && 運算符來說,只有當左邊表達式的值為1時才對右邊的表達式求值並執行副作用。
這是對程序的一種優化,因為根據「與」邏輯,如果左邊表達式的值為0,則總表達式的值定為0,無需對右邊表達式進行計算。根據這一特性,可以寫出更加符合人類邏輯的代碼。
if (var != 0 && 3 == 100 / var) {}
如果沒有此序列點,則可能會出現0做除數的錯誤。
|| 運算符同理,只有當左邊表達式的值為0時才對右邊的表達式求值並執行副作用。
2、逗號運算符「,」 在C語言中有很多用途,在某些地方它是分隔符,在某些地方它是運算符。比如以下表達式:
var = 1, var = 2
這裡的 「,」 不是分隔符,而是運算符。此逗號運算符的兩邊是兩個賦值表達式,逗號表達式先對左邊的表達式求值並執行副作用,此時var的值被修改為1,之後對右邊的表達式求值並執行副作用,var的值被修改為2,最後,逗號表達式返回右邊表達式的值,即2。
逗號表達式的特性可以使兩個表達式像兩個表達式語句那樣執行,適合用在需要用表達式代替語句塊的地方,如 for 語句的括號內。
3、三元運算符 ? : 中的 ?在 ? 前的表達式求值並執行副作用後,才判斷返回其後哪個表達式的值。並且,如果確定返回某個表達式的值,則不會對另一個表達式求值或執行副作用。
? : 表達式的這個特性使其行為與if else表現一致。
4、完整表達式的末尾完整表達式的末尾也是一個序列點,這保證了表達式語句的副作用按照其書寫順序執行。
同時,根據前面對完整表達式的定義,if、while、switch括號中的表達式以及for括號中的每個分量都是完整表達式,這些表達式的副作用也都會在語句其他部分開始前執行。
5、函數調用與返回函數調用時參數列表中的逗號不是表達式,而是分隔符。
參數列表的求值順序是未定義的,比如 fn(a++, b--),a++和b--的求值順序是未知的,取決於編譯器。
此處的序列點表現為在進入函數前,所有表達式的副作用都已經完成;函數返回時,返回值已經拷貝到調用處。
6、初始化末尾因為初始化是申明的一部分,不屬於語句或表達式,所以不能套用表達式的說法,但其表現是類似的。
如下申明:
int var = 5;
在分號前已經完成副作用,即把var初始化為5。
7、初始化列表中的逗號分隔符初始化列表中的逗號是分隔符而不是運算符。
初始化列表中的表達式按照從左到右的順序求值並執行副作用。
如下代碼:
int var = 0;
int array[] = { var++, var++, var++ };
8、申明中的逗號分隔符申明中的逗號是分隔符而不是運算符。
如下代碼:
int a = 0;
int b = a++, c = a++;
b、c分別被初始化為0、1。
而且,在逗號前的變量已經申明完成,逗號後的則不然。
如下代碼:
int a = 0, b = a; //Correct
int c = d, d = 0; //Error
在申明b前,a已經申明並初始化完成,所以可以用a初始化b。而在申明c時還沒有申明d,所以初始化會報錯。
因為缺少序列點,C會產生很多未定義的行為。最典型的例子是:
int var = 0;
var = var++;
根據優先級,表達式var = var++的值是確定的,然而賦值和自增副作用的執行順序是未定義的,所以var的值是未知的。如果用gcc編譯這段代碼,var的值為0,比較符合預期;但在VC++中,var的值為1。
所以我們應避免在表達式中同時使用某一變量和它的自增表達式。
最後,不管你是轉行也好,初學也罷,進階也可,如果你想學編程~
【值得關注】我的C/C++編程學習交流俱樂部:
【Q群:795246887】
問題答疑,學習交流,技術探討,還有超多編程資源大全,零基礎的視頻也超棒~
點擊下方 ↓ 原文連結即可直接進入~~~