Brian Kernighan & Dennis Ritchie:c 程序设计语言 (第2版)
以下是從提供的資料中提取並詳盡解釋的主要論點:
C 語言的程式結構與基本概念
-
程式由函數組成: C 程式的核心是由一個或多個函數構成,其中
main函數是程式執行時的入口點。程式從main函數的第一個敘述開始執行。函數是程式碼組織的基本單位,負責完成特定任務。 -
編譯與執行流程: C 程式通常需要經過編譯器將原始程式碼(例如
.c檔案)轉換為機器碼組成的執行檔(例如在 UNIX 上是a.out)。編譯過程包括預處理、編譯、組譯和連結。連結器會將程式所需的標準庫函數(如printf)與程式碼合併。執行檔可以直接在作業系統上執行。 -
基本輸入/輸出: 程式與外部世界互動的最基本方式是透過標準輸入(鍵盤或其他輸入來源)和標準輸出(螢幕或其他輸出目的地)。
getchar()函數用於從標準輸入讀取一個字元,putchar(c)函數用於將字元c寫入標準輸出。printf()函數則提供了更強大的格式化輸出功能,可以輸出各種型別的資料,並控制輸出的格式(如寬度、精度等)。\n字元用於表示換行。 -
變數與資料型別: 變數用於儲存資料,必須先宣告後使用。C 語言提供了基本的資料型別,包括整數型別 (
int,short,long,char,以及它們的signed和unsigned版本) 和浮點數型別 (float,double,long double)。不同的型別佔用不同的記憶體空間,並有不同的數值範圍和精度。int通常反映了機器處理整數的自然大小,但其具體範圍可能因系統而異。char用於儲存字元,在記憶體中通常以小整數表示(如 ASCII 值)。 -
常數: 程式中可以直接使用的固定值,包括整數常數(如
123,0L,123U),浮點數常數(如123.4,1e-2,123.4F),字元常數(如'a','\n','\013','\x7'),字串常數(如"hello, world")。字元常數是單個字元的值,而字串常數是一個以\0結束的字元序列。列舉常數 (enum) 提供了另一種定義具名整數常數的方式,提高了程式碼的可讀性。 -
宣告與初始化: 變數可以在宣告時進行初始化,例如
int i = 0;或char esc = '\\';。外部變數和靜態變數如果沒有顯式初始化,會被自動初始化為零。自動變數如果沒有顯式初始化,其值是不確定的。const關鍵字用於宣告常數變數,其值在初始化後不能被改變。 -
基本運算子: 包括算術運算子 (
+,-,*,/,%取餘),關係運算子 (>,<,>=,<=,==等於,!=不等於),邏輯運算子 (&&邏輯且,||邏輯或,!邏輯非)。關係運算子和邏輯運算子的結果是整數,表示真(非零)或假(零)。 -
型別轉換: 在運算式中,不同型別的運算元會發生隱式型別轉換(例如,將
int轉換為float以進行浮點數運算)。C 語言有一套明確的轉換規則,稱為「常用算術轉換」,旨在保留精度。也可以使用強制轉換運算子顯式進行型別轉換,例如(double) n。 -
遞增與遞減運算子:
++和--運算子用於將變數加一或減一。它們有前置形式(++i,--i,先改變值再使用)和後置形式(i++,i--,先使用值再改變)。 -
複合賦值運算子: 形式如
op=,例如i += 2等價於i = i + 2。這種形式更簡潔,且在某些情況下可能更有效率。 -
條件運算子:
expr1 ? expr2 : expr3,如果expr1為真,則整個運算式的值是expr2,否則為expr3。這提供了編寫簡單條件表達式的緊湊方式。 -
逗號運算子:
,運算子按順序計算多個運算式,整個運算式的值是最後一個運算式的值。它常在for迴圈的初始化和迭代部分使用。 - 運算子優先順序和結合性: 不同的運算子有不同的優先順序和結合性規則,決定了複雜運算式中各部分的求值順序。可以使用括號來覆蓋這些規則。
程式控制流程
-
敘述與區塊: C 程式由敘述組成,敘述以分號
;結束。一對大括號{}及其中的內容構成一個複合敘述(或稱區塊),可以在需要單個敘述的地方使用。變數可以在任何區塊的開頭宣告,其作用域限定在該區塊內。 -
if-else敘述: 根據條件的真假執行不同的程式碼區塊。else部分是可選的。巢狀的if-else中,else與最近的未配對的if結合。可以使用else-if結構處理多個互斥的條件。 -
switch敘述: 根據一個整數表達式的值,從多個case標籤中選擇一個程式碼區塊執行。default標籤是可選的,用於處理不匹配任何case的情況。break敘述用於跳出switch敘述,避免執行後續的case程式碼。 -
while迴圈: 在條件為真時重複執行程式碼區塊。先檢查條件,後執行迴圈體。 -
for迴圈: 一個更通用的迴圈結構,包含初始化、條件檢查和迭代表達式。形式為for (initialization; condition; iteration) statement;。任何部分都可以省略。for (;;)是一個無限迴圈。 -
do-while迴圈: 先執行迴圈體一次,然後在條件為真時重複執行。至少執行一次迴圈體。 -
break與continue:break敘述用於立即跳出最內層的while,for,do-while或switch敘述。continue敘述用於跳過當前迴圈迭代的剩餘部分,並繼續下一次迭代的條件檢查(while,do-while,for)。 -
goto敘述: 允許無條件跳轉到程式中標有特定標籤的位置。雖然 C 語言支援goto,但通常建議避免使用,以免產生難以理解和維護的「麵條式程式碼」。在某些特定情況下,如從深層巢狀結構中跳出到錯誤處理程式碼,goto可能會簡化程式碼。
函數與程式結構
-
函數定義: 函數定義包括回傳型別、函數名、參數列表(包含參數型別和名稱)和函數體。
void型別用於表示函數不回傳值。 -
函數原型: 在函數使用(呼叫)之前,應提供函數原型(聲明),告知編譯器函數的名稱、回傳型別和參數型別。ANSI C 推薦使用帶參數型別的函數原型(例如
int power(int, int);),這使得編譯器可以檢查函數呼叫時參數的型別是否正確。 - 參數傳遞: C 語言使用「傳值呼叫」方式傳遞函數參數。函數接收的是參數值的副本,對參數副本的修改不會影響原始參數。要修改原始參數,必須傳遞指向原始參數的指標。
-
回傳值: 函數使用
return敘述回傳一個值(型別應與函數的回傳型別相符)並結束執行。沒有回傳值的函數(或回傳型別為void的函數)也可以使用return;結束執行。 - 作用域規則: 變數的作用域決定了它在程式中哪些地方可以被訪問。自動變數(在函數或區塊內部宣告)具有區塊作用域。函數參數具有函數作用域。外部變數(在所有函數外部宣告)和靜態變數具有檔案作用域或區塊作用域,具體取決於宣告位置。
-
連結性 (
extern和static): 外部變數和函數預設具有外部連結性,可以在程式的多個原始檔案中共享。extern關鍵字用於在其他檔案中聲明一個外部變數或函數。static關鍵字用於限制變數或函數的作用域或連結性:用於外部變數或函數時,限制其檔案作用域,使其不能被其他檔案訪問;用於函數或區塊內部的自動變數時,使其在函數呼叫結束後仍保留其值(靜態儲存期)。 -
多檔案程式: 大型程式通常分割成多個原始檔案。不同的檔案可以透過
extern聲明共享外部變數和函數。編譯時需要將所有相關的原始檔案或目標檔案連結起來。 -
標頭檔: 標頭檔(
.h檔案)用於存放函數原型、巨集定義 (#define)、型別定義 (typedef) 和外部變數聲明。程式中需要使用這些定義時,透過#include指示字將標頭檔包含進來。#include <filename>通常用於包含標準庫標頭檔,#include "filename"用於包含使用者自訂標頭檔。 -
預處理器: C 預處理器在編譯之前處理原始程式碼。主要的預處理器指示字包括:
-
#define: 定義巨集,可以定義常數或帶參數的巨集函數。巨集在預處理階段進行文本替換。 -
#undef: 取消已定義的巨集。 - 條件式編譯 (
#if,#ifdef,#ifndef,#elif,#else,#endif): 根據條件包含或排除程式碼段,常用於處理不同平台或編譯選項。 -
#include: 包含其他檔案的內容。 -
#line,#error,#pragma: 其他用於控制編譯器或產生錯誤的指示字。
-
- 遞迴: 函數可以直接或間接地呼叫自身。遞迴在處理樹狀結構或需要分解為相似子問題的問題時非常有用(如快速排序)。
陣列與指標
- 陣列: 陣列是儲存相同型別元素的連續記憶體區域。陣列元素透過索引存取(從 0 開始)。陣列名本身在大多數情況下代表陣列第一個元素的位址。
-
指標: 指標是一個變數,其值是另一個變數的位址。宣告指標使用
*(如int *ip;)。&運算子用於取一個變數的位址(如ip = &x;)。*運算子用於間接存取(解引用)指標指向的值(如y = *ip;)。 -
指標與陣列的關係: 在 C 語言中,指標和陣列有著非常緊密的關係。陣列名
a在大多數情況下等價於&a[0](陣列第一個元素的位址)。a[i]等價於*(a+i)。這意味著指標可以像陣列一樣使用索引來存取元素(例如pa[i]等價於*(pa+i)),反之,陣列名也可以用於指標運算(但在大多數情況下,陣列名是一個常數位址,不能被修改,如a++是非法的)。 -
指標算術: 指標可以進行算術運算。例如,
p+i表示指向p所指元素之後的第i個元素的位址。p++使指標指向下一個元素。兩個指向同一陣列元素的指標可以相減,結果是它們之間元素的個數(單位是元素的大小)。 -
字元指標與字串: C 語言中的字串是字元陣列,以
\0(空字元)結束。字串常數(如"hello")儲存在程式的靜態儲存區,其型別是char陣列。字元指標常被用於操作字串,例如char *p = "hello";宣告一個字元指標並指向字串常數的開始位址。標準函式庫<string.h>提供了處理字串的常用函數(如strcpy,strlen,strcmp)。 -
指標作為函數參數: 為了讓函數能夠修改呼叫者函數中的變數,可以將變數的指標傳遞給函數。例如,
swap函數需要接收兩個整數的指標才能交換它們的值。 -
指向函數的指標: 函數名在運算式中可以表示函數的位址。可以宣告指向函數的指標,並透過指標呼叫函數。這在實現泛型演算法(如
qsort函數可以接受一個指向比較函數的指標)時非常有用。 -
命令列參數:
main函數可以接受兩個參數:argc(argument count) 表示命令列參數的個數,argv(argument vector) 是一個字元指標陣列,其中argv[0]是程式名,argv[1]到argv[argc-1]是實際的參數字串。 -
複雜宣告的解讀: C 語言的宣告語法可能很複雜(如指向函式的指標陣列)。理解宣告的核心是找到被宣告的名稱,然後從內往外、按優先順序(主要看括號
()和方括號[])解讀其型別。typedef可以用於簡化複雜的宣告。
結構、聯合與位元欄位
-
結構 (
struct): 結構允許將不同型別的變數組合在一起,形成一個單一的邏輯實體。結構的成員透過.運算子存取。 -
指向結構的指標: 可以宣告指向結構的指標。透過指標存取結構成員時,使用
->運算子(例如p->member等價於(*p).member)。 - 結構的陣列: 可以宣告結構型別的陣列。
- 巢狀結構: 結構的成員可以是另一個結構。
- 遞迴結構: 結構可以包含指向同型別結構的指標,這是實現鏈結串列、樹等資料結構的基礎。
-
聯合 (
union): 聯合允許在同一塊記憶體空間中儲存不同型別的資料,但只能在任何時候儲存其中一種型別。聯合的大小由其最大成員的大小決定。 - 位元欄位 (bit-fields): 在結構中,可以指定整數成員的位元寬度,以緊湊地儲存多個旗標或小整數。
-
typedef:typedef用於為現有型別創建新的名稱(別名),包括基本型別、陣列、指標、結構、聯合和函數指標等。這可以提高程式碼的可讀性和可移植性。
標準函式庫與系統介面
-
標準 I/O 庫 (
<stdio.h>): 提供了一組用於處理檔案和標準 I/O 流的函數。- 檔案操作:
fopen開啟檔案,fclose關閉檔案,freopen重新定向流。 - 字元 I/O:
getc,putc,getchar,putchar,ungetc(將字元退回輸入流)。 - 行 I/O:
fgets從流讀取一行,fputs向流寫入一行。 - 格式化 I/O:
printf,scanf(標準流),fprintf,fscanf(指定流),sprintf,sscanf(字串)。 - 檔案狀態和錯誤:
feof(檢查是否到達檔案尾),ferror(檢查是否發生錯誤),perror(列印系統錯誤訊息)。
- 檔案操作:
-
標準函式庫的其他部分: ANSI C 標準庫提供了豐富的功能:
-
<ctype.h>: 字元分類和轉換函數 (如isalpha,isdigit,tolower,toupper)。 -
<string.h>: 字串處理函數 (如strcpy,strlen,strcmp,strstr)。 -
<stdlib.h>: 通用工具函數 (如atof,atoi,atol,rand,srand,malloc,free,exit,system,bsearch,qsort,abs,div)。 -
<math.h>: 數學函數 (如sin,cos,sqrt,pow,fabs)。 -
<time.h>: 時間和日期函數。 -
<assert.h>: 斷言巨集,用於程式調試。 -
<stdarg.h>: 支援可變參數列表的函數。
-
-
低階 I/O (UNIX 介面示例): 提供了比標準 I/O 更接近作業系統的介面,通常使用檔案描述符 (integers) 而非
FILE指標。-
read(fd, buf, n): 從檔案描述符fd讀取最多n個位元組到緩衝區buf。 -
write(fd, buf, n): 將緩衝區buf中的n個位元組寫入檔案描述符fd。 -
open(name, flags, perms): 開啟檔案並回傳檔案描述符。 -
creat(name, perms): 建立或截斷檔案並回傳檔案描述符。 -
close(fd): 關閉檔案描述符。 -
unlink(name): 刪除檔案。 -
lseek(fd, offset, origin): 在檔案中定位讀寫位置。
-
-
檔案系統資訊:
stat(name, &stbuf)和fstat(fd, &stbuf)函數用於獲取檔案的資訊(如大小、型別、權限等),資訊儲存在struct stat結構中。 -
目錄讀取: 在 UNIX 系統中,可以使用
opendir開啟目錄,readdir讀取目錄中的條目,closedir關閉目錄。 -
動態記憶體分配:
malloc(size)從堆中分配指定大小的記憶體區塊,並回傳指向該區塊的指標。calloc(nobj, size)分配一個能容納nobj個大小為size的元素的空間,並將其初始化為零。free(p)釋放malloc或calloc分配的記憶體。這些函數定義在<stdlib.h>中。提供的資料中包含了一個簡化的malloc和free實現示例,展示了如何管理可用記憶體區塊的自由列表。
C 語言特性與設計哲學
- 精簡的核心語言: C 語言本身相對小巧,不包含許多在其他語言中內建的功能(如 I/O、字串操作、記憶體管理)。這些功能透過標準函式庫提供,這使得 C 語言的核心更易於學習和實現,同時提供了很大的靈活性。
- 貼近硬體: C 語言提供了指標和位元運算等低階功能,可以直接操作記憶體和位元層級的資料,這使得它適合編寫作業系統、嵌入式系統程式和對效能要求高的應用程式。
- 注重效率: C 語言的設計考慮了生成高效的機器碼。其控制結構和資料型別都旨在與典型的電腦硬體架構緊密對應。
-
可移植性: 雖然 C 提供了低階介面,但透過標準函式庫和對不同平台特性的抽象(如
sizeof運算子),C 程式可以在不同的硬體和作業系統平台上編譯和執行,具有良好的可移植性。ANSI C 標準的出現進一步增強了 C 語言的可移植性。
這些論點共同描繪了 C 語言作為一種強大、靈活且高效的通用程式語言的特性,特別適合系統程式設計和對資源控制要求嚴格的應用場景。
comments
comments for this post are closed