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++編程學習交流俱樂部!【點擊進入】
問題答疑,學習交流,技術探討,還有超多編程資源大全,零基礎的視頻也超棒~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/251351.html
標籤:C
