C語言

本页使用了标题或全文手工转换,现处于香港繁体模式
求聞百科,共筆求聞
C語言
C程式語言》,第一部介紹C語言的書籍
編程範型程序式指令式編程程序式)、結構化編程
設計者丹尼斯·里奇(Dennis Ritchie)
實作者丹尼斯·里奇(Dennis Ritchie)和肯·湯普遜(Ken Thompson)
釋出時間1972年
穩定版本
ISO/IEC 9899:2018C18
(2018年6月 )
作業系統跨平台
主要實作產品
ClangGCCMSVCTurbo CWatcom C
受啟發於
BBCPLCPL)、ALGOL 68[1]匯編語言PL/IFORTRAN
施影響於
大量
如:awkBitCcshC++C#
DJavaJavaScriptObjective-CPerlPHPRust

C是一種通用的程式語言,廣泛用於系統軟件應用軟件的開發。於1969年至1973年間,為了移植與開發UNIX作業系統,由丹尼斯·里奇肯·湯普遜,以B語言為基礎,在貝爾實驗室設計、開發出來。

C語言具有高效、靈活、功能豐富、表達力強和較高的可移植性等特點,在程式設計中備受青睞,成為最近25年使用最為廣泛的程式語言[2]。目前,C語言編譯器普遍存在於各種不同的作業系統中,例如Microsoft WindowsmacOSLinuxUnix等。C語言的設計影響了眾多後來的程式語言,例如C++Objective-CJavaC#等。

二十世紀八十年代,為了避免各開發廠商用的C語言的語法產生差異,由美國國家標準局為C語言訂定了一套完整的國際標準語法,稱為ANSI C,作為C語言的標準。二十世紀八十年代至今的有關程式開發工具,一般都支援符合ANSI C的語法。

概述

特性

  • C語言是一個有結構化程式設計、具有變數作用域(variable scope)以及遞迴功能的程序式語言。
  • C語言傳遞參數均是以值傳遞(pass by value)[3],另外也可以傳遞指標(a pointer passed by value)。
  • 不同的變數類型可以用結構體(struct)組合在一起。[4]
  • 只有32個保留字(reserved keywords),使變數、函數命名有更多彈性。[4]
  • 部份的變數類型可以轉換,例如整數型和字元型變數。[4]
  • 透過指標(pointer),C語言可以容易的對記憶體進行低階控制。[4]
  • 編譯預處理(preprocessor)讓C語言的編譯更具有彈性。[4]

歷史

20世紀70年代,肯·湯姆森為了使其設計的Unix系統更加高效,使用B語言的變種(即C語言)重寫了Unix。在1978年,丹尼斯·里奇布萊恩·柯林漢合作出版了《C程式語言》第一版,事實上即為K&R C標準[5]。在這之後,制定標準的工作轉移到ISO和ANSI。目前最新的C語言標準是C18

語法

Hello World 程式

Brian Kernighan於1978年親筆書寫的「Hello World」程式

下面是一個在標準輸出裝置(stdout)上列印出 "Hello, world!" 字串的簡單程式。類似的程式,通常作為初學程式語言時的第一個程式:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

其中只有int,void,return為C語言的關鍵字,前置處理器會將#include <stdio.h>替換為stdio.h檔案的內容。

main函數是C語言程式的入口點

"Hello, world!\n"中的\n是一個跳脫字元,形式為\加上一個字元。所起的作用在ASCII碼中規定。

printf是聲明於stdio.h的函數,關於printf的更多細節,參見printf

關于格式化字串的更多資訊,參見格式化字串

進一步了解

C語言由函數和變數組成,C的函數就像是Fortran中的子程式和函數[4][6]

在C語言中,程式從 main 開始執行。main 函數通過呼叫和控制其他函數進行工作。例如上面的printf。程式設計師可以自己寫函數,或從庫中呼叫函數。在上面的return 0; 使得 main 返回一個值給呼叫程式的殼層,表明程式是否成功執行。

一個C語言的函數由返回值、函數名、參數列和函數體組成。函數體的語法和其它的複合的陳述式部分是一樣的。

複合陳述式

C語言中的複合陳述式(或稱陳述式塊)的格式為:

{
    語句;
    語句;
    /* ... */
}

[6] 複合陳述式可以使得幾個陳述式從文法上變成一個陳述式。

有時必須使用複合陳述式,否則會產生錯誤。例如,在運用迴圈陳述式的時候,如果迴圈體(即迴圈中執行部分)包含多個陳述式(以分號隔開),則必須使用花括號將他們合併成一個複合陳述式。如果不這麼做,系統僅把第一個分號前的內容看做迴圈體。

需要注意的是,部分C編譯器並不支援在任意位置使用複合陳述式。

條件陳述式

C語言有兩種條件陳述式形式,分別是ifswitch

If 的格式如下:

if (運算式) // 如果
    語句; 
// 有時還會有 else:
else      // 否則
    語句;

運算式的值非零表示條件為真;如果條件為假,程式將跳過if處的陳述式,直接執行if後面的陳述式。但是如果if後面有else,則當條件為假時,程式跳到else處執行。ifelse後面的陳述式可以是另個if陳述式,這種套疊式的結構,允許更複雜的邏輯控制流程得以實現。在一般情況下,else一定與最接近的if成對,因此常用括弧{}越過此限制。比較下面兩種情況:

if (邏輯表達)
    if (邏輯表達式)
        語句; 
    else
        語句;

下面是if的另一種格式if……else if……else……

if (逻辑表达式)
    语句;
else if (逻辑表达式)  //在下面可以加入多个“else if()”语句
    语句;
else
    语句;
if (邏輯表達式甲) {
    if (邏輯表達式乙)
        語句;
}
else 
    語句;

要注意這裏的縮排和換行只用於方便閱讀。編譯器並不會根據縮排層級猜測 if 和 else 的對應關係。

switch通常用於對幾種有明確值的條件進行控制。它要求的條件值通常是整數或字元。與switch搭配的條件轉移是case。使用case後面的標值,控制程式將跳到滿足條件的case處一直往下執行,直到陳述式結束或遇到break。通常可以使用default把其他例外的情況包含進去。如果switch陳述式中的條件不成立,控制程式將跳到default處執行;如果省略default子句,則直接執行下一陳述式。switch是可以巢狀的。

switch () {
  case :
  case :
    語句段1; // 甲乙都會執行
    // 更多語句…
    break;  // 跳轉到 switch 末尾處
  case :
    語句段2;
    break; 
  default:  // 無論如何都會匹配
    語句段3;
}

簡單的條件判斷也可用?:

運算式?值1:值2;
:
   a=b>c?b:c // 如果變數"b"的值大於變數"c" 把變數"b"的值賦予變數"a"

循環陳述式

C語言有三種形式的循環陳述式[6]

do 
    語句
while(判斷式);

和:

while(判斷式) 
    語句;

和:

for(起始化;判斷式;運算式)
    語句;

whilefor中,陳述式將執行到表達式的值為零時結束。在do...while陳述式中,循環將至少被執行一次。這三種循環結構可以互相轉化:

for(起始化;判斷式;運算式)
    語句;

如果陳述式中不使用continue陳述式的話,相當於

起始化;
while (判斷式) {
    語句;
    運算式;
}

當循環條件一直為真時,將產生無窮迴圈

跳轉陳述式

跳轉陳述式包括四種:goto,continue,break和return[6]

goto 標記;

goto陳述式是無條件轉移陳述式,且標記必須在當前函數中定義,使用「標記:」的格式定義。程式將跳到標記處繼續執行。由於goto(特別是向回 goto 和長距離的 goto)容易產生閱讀上的困難,所以對新手應該儘量少用。GCC 編譯器拓展支援對指標 goto和巨集內 goto,一定程度上增強了 goto 的可讀性。


continue陳述式用在迴圈陳述式中,作用是結束當前一輪的迴圈,馬上開始下一輪迴圈。

break陳述式用在迴圈陳述式或switch中,作用是結束當前迴圈,跳到循環體外繼續執行。但是使用break只能跳出一層迴圈。在要跳出多重迴圈時,可以使用goto使得程式更為簡潔。

當一個函數執行結束後要返回一個值時,使用returnreturn可以跟一個運算式或變數。如果return後面沒有值,將執行不返回值。

在C語言中的運算符號

C和C++運算子

資料類型

資料類型 (C語言)

陣列

如果一個變數名後面跟着一個有數字的中括弧,這個聲明就是陣列聲明。字串也是一種陣列,它們以ASCII的NUL作為陣列的結束。要特別注意的是,方括內的索引值是從0算起的。

例如:

int myvector [100]/* 從myvector[0]至[99]共100個元素 */
char mystring [80]
// 声明时初始化
float mymatrix [3] [2] = {2.0 , 10.0, 20.0, 123.0, 1.0, 1.0};
int notfull [3][3] = {{1},{1,2,3},{4,5}};
// 数组套数组
char lexicon [10000] [300]/* 共一萬個最大長度為300的字元陣列。*/
int a[3][4]

上面最後一個例子建立了一個陣列,但也可以把它看成是一個多維陣列。注意陣列的下標從0開始。這個陣列的結構如下:

a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]

例子中notfull建立了一個3*3的二維陣列,初始化時有些元素並未賦值。如下:

1  ?  ?
1  2  3
4  5  ?

根據C標準的規定,在存在初始化列表時,如果初始化列表中未提供對所有元素的初始化,則剩餘元素會被預設初始化,並使用與靜態變數相同的初始化規則[7]

指標

如果一個變數聲明時在前面使用 * 號,表明這是個指標型變數[6]。換句話說,該變數儲存一個地址,而 *(此處特指單目運算子 *,下同。C語言中另有雙目運算子 * 表示乘) 則是取內容運算子,意思是取這個記憶體地址里儲存的內容。把這兩點結合在一起,可將 int *a;看作是 「*a 解得的內容類型為 int」,對更複雜的聲明也如此[注 1]。指標是 C 語言區別於其他同時代高階語言的主要特徵之一。

指標不僅可以是變數的地址,還可以是陣列、陣列元素、函數的地址。通過指標作為形式參數可以在函數的呼叫過程得到一個以上的返回值(不同於return z這樣的僅能得到一個返回值。

指標是一把雙刃劍,許多操作可以通過指標自然的表達,但是不正確的或者過分的使用指標又會給程式帶來大量潛在的錯誤。

例如:

int *pi;     // 指向整型数据的指针 pi
int * api[3];// 由指向整型数据的指针构成的数组,长度为 3
char ** argv;// 指向一个字符指针的指针
struct { int member; } stinst,
  * pst = & stinst;
// pst是一个指向一个匿名结构体的指针

儲存在指標中的地址所指向的數值在程式中可以由 * 讀取。例如,在第一個例子中, *pi 是一個整型數據。這叫做參照一個指標。

另一個運算子 &,叫做取地址運算子,它將返回一個變數、陣列或函數的儲存地址。因此,下面的例子:

int i, *pi; /* int and pointer to int */
pi = &i;

i*pi 在程式中可以相互替換使用,直到 pi 被改變成指向另一個變數的地址。

當指標指向結構體時,可以使用運算子 -> 代替 *和. 的作用,如 (*p).mp->m 等效。

字串

C語言的字串其實就是char型陣列,所以使用字串並不需要參照庫。然而C標準庫確實包含了用於對字串進行操作的函數,使得它們看起來就像字串而不是陣列。使用這些函數需要參照標頭檔string.h

檔案輸入/輸出

在C語言中,輸入和輸出是經由標準函數庫中的一組函數來實現的。在ANSI/ISO C中,這些函數被定義在標頭檔stdio.h中。

標準輸入/輸出

有三個標準輸入/輸出是標準I/O庫預先定義的:

  • stdin:標準輸入
  • stdout:標準輸出
  • stderr:標準錯誤輸出

下面的這個例子顯示了一個過濾程式(filter program)是怎樣構成的。

#include <stdio.h>

int main(int argc, const char * argv[])
{
    char c;
    while ((c=getchar())!=EOF)
        putchar(c);
    perror("getchar() got EOF");
    return -1;
}

函數

C語言的基本結構單位是函數[6]。系統首先呼叫 main函數(主函數),通過函數的巢狀呼叫,再呼叫其他函數。函數可以是系統內建的函數,也可以是用戶定義的函數。C語言中,不允許函數巢狀定義。

記憶體管理

C語言的特色之一是:程式設計師必須親自處理記憶體的分配細節。

大多數C語言實現使用棧(Stack)來儲存函數返回地址/堆疊幀基址、完成函數的參數傳遞和函數局部變數的儲存。然而,在部分極特殊的平台上,使用棧並不能獲得最大效率。此時的實現由編譯器決定[6]。 如果程式需要在執行的過程中動態分配記憶體,可以利用(Heap)來實現。

基本上C程式的元素儲存在記憶體的時候有3種分配策略:

  • 靜態分配

如果一個變數聲明為全域變數或者是函數的靜態變數,這個變數的儲存將使用靜態分配方式。靜態分配的記憶體一般會被編譯器放在數據段代碼段來儲存,具體取決於實現。這樣做的前提是,在編譯時就必須確定變數的大小。 以IA32的x86平台及gcc編譯器為例,全域及靜態變數放在數據段的低階;全域及靜態常數放在代碼段的高階。

  • 自動分配

函數的自動局部變數應該隨着函數的返回會自動釋放(失效),這個要求在一般的體系中都是利用棧(Stack)來滿足的。相比於靜態分配,這時候,就不必絕對要求這個變數在編譯時就必須確定變數的大小,執行時才決定也不遲,但是C89仍然要求在編譯時就要確定,而C99放鬆了這個限制。但無論是C89還是C99,都不允許一個已經分配的自動變數執行時改變大小。

所以說C函數永遠不應該返回一個局部變數的地址

要指出的是,自動分配也屬於動態分配,甚至可以用alloca函數來像分配堆(Heap)一樣進行分配,而且釋放是自動的。

  • 動態分配

還有一種更加特殊的情況,變數的大小在執行時有可能改變,或者雖然單個變數大小不變,變數的數目卻有很大彈性,不能靜態分配或者自動分配,這時候可以使用(Heap)來滿足要求。ANSI C定義的堆操作函數是malloc、calloc、realloc和free。

使用(Heap)記憶體將帶來額外的開銷和風險。

安全問題

C語言的特色之一是:語言不負責記憶體邊界檢查,這是因為在執行時進行記憶體邊界檢查會造成效能問題,與UNIX哲學不符。此特性容易導致緩衝區溢位問題。然而,部分編譯器(如英特爾編譯器)會出於安全性的考量,提供方法以進行執行時記憶體邊界檢查[8]

C語言的標準文件要求了一個平台移植C語言的時候至少要實現的一些功能和封裝的集合,稱為「標準庫」,標準庫的聲明頭部通過前置處理器命令#include進行參照。

在C89標準中:

檔案 簡介說明
<assert.h> 斷言相關
<ctype.h> 字元類型判斷
<errno.h> 標準報錯機制
<float.h> 浮點運算
<limits.h> 各種體系結構限制
<locale.h> 本地化介面
<math.h> 數學函數
<setjmp.h> 跨函數跳轉
<signal.h> 訊號(類似UNIX訊號定義,但是差很遠)
<stdarg.h> 可變參處理
<stddef.h> 一些標準巨集定義
<stdio.h> 標準I/O庫
<stdlib.h> 標準工具庫函數
<string.h> ASCIIZ字串及任意記憶體處理常式
<time.h> 時間相關

在94年的修正版中

  • <iso646.h>
  • <wchar.h>
  • <wctype.h>

在C99中增加了六個函數庫

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>

以上是C語言的標準。各個系統各自又對C庫函數進行的各種擴充,就浩如煙海了。如POSIX CGNU C等。

工具軟件

工具軟件可以幫助程式設計者避免一些程式中潛藏或容易出現的問題,例如常會造成程式未預期動作或是執行期錯誤的程式碼。

許多語言都有自動原始碼檢查及審計工具,C語言也有類似工具,像是Lint。可以在程式剛寫好時用Lint找出可能有問題的程式,通過Lint後再用C編譯器進行編譯,許多編譯器也可以設置是否要針對一些可能有問題的程式碼提出警告。MISRA C是一套針對嵌入式系統的法則,可主要也是避免一些可能有問題的程式碼。

也有一些編譯器、程式庫或作業系統可以處理一些非標準C語言的功能,例如邊界值檢查、緩衝區溢位偵測、序列化自動垃圾回收功能。

使用像ValgrindIBM Rational Purify等軟件工具,或者連結有特別malloc函數的程式庫,有助於找出一些運行期記憶體使用的問題。

保留關鍵字

以下是C語言的保留關鍵字:[9]

char short int unsigned
long float double struct
union void enum signed
const volatile typedef auto
register static extern break
case continue default do
else for goto if
return switch while sizeof

C99新增關鍵字

_Bool _Complex _Imaginary inline restrict

C11新增關鍵字

_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local

經典錯誤

「void main()」的用法並不是任何標準制定的[10][11]。 C語言標準語法是「int main()」,任何實現都必須支援int main(void) { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[12]。 在 C++ 標準中,main的標準型態應是int,否則類型是由實現定義的。任何實現都必須支援int main() { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[13]

參見

註腳

註解

  1. C 的聲明使用這種「解方程」的形式,於是便出現了多種複雜的聲明。https://cdecl.org 是一個基於網頁的實用「翻譯工具」。

參考資料

  1. Ritchie, Dennis M. The Development of the C Language. 1993-01 [2008-01-01]. The scheme of type composition adopted by C owes considerable debt to Algol 68, although it did not, perhaps, emerge in a form that Algol's adherents would approve of. 
  2. TIOBE Programming Community Index [TIOBE編程社區指數]. 2012 [2012-11-03] (英語). 
  3. Brian W. Kernighan and Dennis M. Ritchie. The C programming Language. Prentice-Hall. 1988. ISBN 0-13-110362-8 (英語). In C, all function arguments are passed ``by value. 
  4. 4.0 4.1 4.2 4.3 4.4 4.5 Dennis M. Ritchie,Brian W. Kernighan. C程序设计语言. 北京: 機械工業出版社. 2004年1月. ISBN 9787111128069 (中文). 
  5. Stephen Prata. C Primer Plus(第5版). 北京: 人民郵電出版社. 2005年2月: 3–4. ISBN 9787115130228 (中文). 
  6. 6.0 6.1 6.2 6.3 6.4 6.5 6.6 ISO/IEC 9899:2018 (PDF). [2020-06-10]. 
  7. ISO/IEC 9899:2011. International Organization for Standardization. [2020-03-08] (英語). §6.7.9 Initialization 21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration. 
  8. check-pointers, Qcheck-pointers. Intel. [2021-06-01] (英語). 
  9. ISO/IEC 9899:2011. International Organization for Standardization. [2020-03-08] (英語). 
  10. Can I write "void main()"? The definition void main() { /* ... */ } is not and never has been C++, nor has it even been C.
  11. 用 C99 进行开放源代码的开发. [2011-01-21]. 
  12. 「The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters: int main(void) { /* ... */ } or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ } or equivalent; or in some other implementation-defined manner.」,引自ISO/IEC 9899:1999, Section 5.1.2.2.1 Program startup
  13. 「An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main: int main() { /* ... */ } and int main(int argc, char* argv[]) { /* ... */ }.」,引自 ISO/IEC 14882, 第一版(1998)、第二版(2003)與第三版(2011), section 3.6.1 Main function

參考資料

  • Brian Kernighan, Dennis Ritchie: The C Programming Language. Also known as K&R — The original book on C.
    • 1st, Prentice Hall 1978; ISBN 978-0-13-110163-0. Pre-ANSI C.
    • 2nd, Prentice Hall 1988; ISBN 978-0-13-110362-7. ANSI C.
  • ISO/IEC 9899. The official C:1999 standard, along with technical corrigenda and a rationale. As of 2005 the latest version is ISO/IEC 9899:TC2.
  • Samuel P. Harbison, Guy L. Steele: C: A Reference Manual. This book is excellent as a definitive reference manual, and for those working on C compilers. The book contains a BNF grammar for C.
    • 4th, Prentice Hall 1994; ISBN 978-0-13-326224-7.
    • 5th, Prentice Hall 2002; ISBN 978-0-13-089592-9.
  • Derek M. Jones: The New C Standard: A Cultural and Economic Commentary, Addison-Wesley, ISBN 978-0-201-70917-9, online material
  • Robert Sedgewick: Algorithms in C, Addison-Wesley, ISBN 978-0-201-31452-6 (Part 1–4) and ISBN 978-0-201-31663-6 (Part 5)
  • William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery: Numerical Recipes in C (The Art of Scientific Computing), ISBN 978-0-521-43108-8

外部連結