【C語言入門】C語言的組成結構(基礎完整篇)!


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++編程學習交流俱樂部!【點擊進入】

問題答疑,學習交流,技術探討,還有超多編程資源大全,零基礎的視頻也超棒~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM