前言
C 語言是一門抽象的、面向程序的語言,C 語言廣泛應用于底層開發,C 語言在計算機體系中占據著不可替代的作用,可以說 C 語言是編程的基礎,也就是說,不管你學習任何語言,都應該把 C 語言放在首先要學的位置上,
福利在文章最后!
下面這張圖更好的說明 C 語言的重要性
可以看到,C 語言是一種底層語言,是一種系統層級的語言,作業系統就是使用 C 語言來撰寫的,比如 Windows、Linux、UNIX ,如果說其他語言是光鮮亮麗的外表,那么 C 語言就是靈魂,永遠那么樸實無華,
C 語言特性
那么,既然 C 語言這么重要,它有什么值得我們去學的地方呢?我們不應該只因為它重要而去學,我們更在意的是學完我們能學會什么,能讓我們獲得什么,
? C 語言的設計
C 語言是 1972 年,由貝爾實驗室的丹尼斯·里奇(Dennis Ritch)和肯·湯普遜(Ken Thompson)在開發 UNIX 作業系統時設計了C語言,C 語言是一門流行的語言,它把計算機科學理論和工程實踐理論完美的融合在一起,使用戶能夠完成模塊化的編程和設計,
計算機科學理論:簡稱 CS、是系統性研究資訊與計算的理論基礎以及它們在計算機系統中如何實作與應用的實用技術的學科,
? C 語言具有高效性
C 語言是一門高效性語言,它被設計用來充分發揮計算機的優勢,因此 C 語言程式運行速度很快,C 語言能夠合理了使用記憶體來獲得最大的運行速度
? C 語言具有可移植性
C 語言是一門具有可移植性的語言,這就意味著,對于在一臺計算機上撰寫的 C 語言程式可以在另一臺計算機上輕松地運行,從而極大的減少了程式移植的作業量,
? C 語言特點
? C 語言是一門簡潔的語言,因為 C 語言設計更加靠近底層,因此不需要眾多 Java 、C# 等高級語言才有的特性,程式的撰寫要求不是很嚴格,
? C 語言具有結構化控制陳述句,C 語言是一門結構化的語言,它提供的控制陳述句具有結構化特征,如 for 回圈、if? else 判斷陳述句和 switch 陳述句等,
? C 語言具有豐富的資料型別,不僅包含有傳統的字符型、整型、浮點型、陣列型別等資料型別,還具有其他編程語言所不具備的資料型別,比如指標,
? C 語言能夠直接對記憶體地址進行讀寫,因此可以實作匯編語言的主要功能,并可直接操作硬體,
? C 語言速度快,生成的目標代碼執行效率高,
下面讓我們通過一個簡單的示例來說明一下 C 語言~
入門級 C 語言程式
下面我們來看一個很簡單的 C 語言程式,我是 mac 電腦,所以我使用的是xcode進行開發,我覺得工具無所謂大家用著順手就行,

你可能不知道這段代碼是什么意思,不過別著急,我們先運行一下看看結果,
這段程式輸出了Hello,World!和My Name is cxuan,最后一行是程式的執行結果,表示這段程式是否有錯誤,下面我們解釋一下各行代碼的含義,
首先,第一行的#include <stdio.h>, 這行代碼包含另一個檔案,這一行告訴編譯器把stdio.h的內容包含在當前程式中,stdio.h是 C 編譯器軟體包的標準部分,它能夠提供鍵盤輸入和顯示幕輸出,
什么是 C 標準軟體包?C 是由 Dennis M 在1972年開發的通用,程序性,命令式計算機編程語言,
C標準庫是一組 C 語言內置函式,常量和頭檔案,例如<stdio.h>,<stdlib.h>,<math.h>等,此庫將用作 C 程式員的參考手冊,
我們后面會介紹 stdio.h ,現在你知道它是什么就好,
在 stdio.h 下面一行代碼就是main函式,
C 程式能夠包含一個或多個函式,函式是 C 語言的根本,就和方法是 Java 的基本構成一樣,
main()表示一個函式名,int表示的是 main 函式回傳一個整數,
void 表明 main() 不帶任何引數,
這些我們后面也會詳細說明,只需要記住 int 和 void 是標準ANSI C定義 main() 的一部分(如果使用 ANSI C 之前的編譯器,請忽略 void),
然后是/*一個簡單的 C 語言程式*/表示的是注釋,注釋使用/**/來表示,注釋的內容在兩個符號之間,這些符號能夠提高程式的可讀性,
注意:注釋只是為了幫助程式員理解代碼的含義,編譯器會忽略注釋
下面就是{,這是左花括號,它表示的是函式體的開始,而最后的右花括號}表示函式體的結束,{ }中間是書寫代碼的地方,也叫做代碼塊,
int number表示的是將會使用一個名為 number 的變數,而且 number 是int整數型別,
number = 11表示的是把值 11 賦值給 number 的變數,
printf(Hello,world!\n);表示呼叫一個函式,這個陳述句使用printf()函式,在螢屏上顯示Hello,world, printf() 函式是 C 標準庫函式中的一種,它能夠把程式運行的結果輸出到顯示幕上,而代碼\n表示的是換行,也就是另起一行,把游標移到下一行,
然后接下來的一行 printf() 和上面一行是一樣的,我們就不多說了,最后一行 printf() 有點意思,你會發現有一個%d的語法,它的意思表示的是使用整形輸出字串,
代碼塊的最后一行是return 0,它可以看成是 main 函式的結束,最后一行是代碼塊},它表示的是程式的結束,
好了,我們現在寫完了第一個 C 語言程式,有沒有對 C 有了更深的認識呢?肯定沒有,,,這才哪到哪,繼續學習吧,
現在,我們可以歸納為 C 語言程式的幾個組成要素,如下圖所示
C 語言執行流程
C 語言程式成為高級語言的原因是它能夠讀取并理解人們的思想,然而,為了能夠在系統中運行hello.c程式,則各個 C 陳述句必須由其他程式轉換為一系列低級機器語言指令,這些指令被打包作為可執行物件程式,存盤在二進制磁盤檔案中,目標程式也稱為可執行目標檔案,
在 UNIX 系統中,從源檔案到物件檔案的轉換是由編譯器執行完成的,

gcc 編譯器驅動從源檔案讀取hello.c,并把它翻譯成一個可執行檔案hello,這個翻譯程序可用如下圖來表示
這就是一個完整的 hello world 程式執行程序,會涉及幾個核心組件:【前處理器、編譯器、匯編器、連接器】,下面我們逐個擊破,
? 預處理階段(Preprocessing phase)
前處理器會根據開始的#字符,修改源 C 程式,#include <stdio.h>命令就會告訴前處理器去讀系統頭檔案stdio.h中的內容,并把它插入到程式作為文本,
然后就得到了另外一個 C 程式hello.i,這個程式通常是以.i為結尾,
? 然后是編譯階段(Compilation phase)
編譯器會把文本檔案hello.i翻譯成文本hello.s,它包括一段匯編語言程式(assembly-language program),
? 編譯完成之后是匯編階段(Assembly phase)
這一步,匯編器 as會把 hello.s 翻譯成機器指令,把這些指令打包成可重定位的二進制程式(relocatable object program)放在 hello.c 檔案中,
它包含的 17 個位元組是函式 main 的指令編碼,如果我們在文本編輯器中打開 hello.o 將會看到一堆亂碼,
? 最后一個是鏈接階段(Linking phase)
我們的 hello 程式會呼叫printf函式,它是 C 編譯器提供的 C 標準庫中的一部分,
printf 函式位于一個叫做printf.o檔案中,它是一個單獨的預編譯好的目標檔案,而這個檔案必須要和我們的 hello.o 進行鏈接,連接器(ld)會處理這個合并操作,
結果是,hello 檔案,它是一個可執行的目標檔案(或稱為可執行檔案),已準備好加載到記憶體中并由系統執行,
? 你需要理解編譯系統做了什么
對于上面這種簡單的 hello 程式來說,我們可以依賴編譯系統(compilation system)來提供一個正確和有效的機器代碼,然而,對于我們上面講的程式員來說,編譯器有幾大特征你需要知道
? 優化程式性能(Optimizing program performance)
現代編譯器是一種高效的用來生成良好代碼的工具,對于程式員來說,你無需為了撰寫高質量的代碼而去理解編譯器內部做了什么作業,
然而,為了撰寫出高效的 C 語言程式,我們需要了解一些基本的機器碼以及編譯器將不同的 C 陳述句轉化為機器代碼的程序,
? 理解鏈接時出現的錯誤(Understanding link-time errors)
在我們的經驗中,一些非常復雜的錯誤大多是由鏈接階段引起的,特別是當你想要構建大型軟體專案時,
? 避免安全漏洞(Avoiding security holes)
近些年來,緩沖區溢位(buffer overflow vulnerabilities)是造成網路和 Internet 服務的罪魁禍首,所以我們有必要去規避這種問題,
? 系統硬體組成
為了理解 hello 程式在運行時發生了什么,我們需要首先對系統的硬體有一個認識,
下面這是一張 Intel 系統產品的模型,我們來對其進行解釋:
? 總線(Buses):
在整個系統中運行的是稱為總線的電氣管道的集合,這些總線在組件之間來回傳輸位元組資訊,
通常總線被設計成傳送定長的位元組塊,也就是字(word),
字中的位元組數(字長)是一個基本的系統引數,各個系統中都不盡相同,
現在大部分的字都是 4 個位元組(32 位)或者 8 個位元組(64 位),
? I/O 設備(I/O Devices)
Input/Output 設備是系統和外部世界的連接,
上圖中有四類 I/O 設備:用于用戶輸入的鍵盤和滑鼠,用于用戶輸出的顯示幕,一個磁盤驅動用來長時間的保存資料和程式,
剛開始的時候,可執行程式就保存在磁盤上,
每個I/O 設備連接 I/O 總線都被稱為控制器(controller)或者是配接器(Adapter),
控制器和配接器之間的主要區別在于封裝方式,
控制器是 I/O 設備本身或者系統的主印制板電路(通常稱作主板)上的芯片組,
而配接器則是一塊插在主板插槽上的卡,無論組織形式如何,它們的最終目的都是彼此交換資訊,
? 主存(Main Memory)
主存是一個臨時存盤設備,而不是永久性存盤,磁盤是永久性存盤的設備,
主存既保存程式,又保存處理器執行流程所處理的資料,從物理組成上說,主存是由一系列DRAM(dynamic random access memory)動態隨機存盤構成的集合,
邏輯上說,記憶體就是一個線性的位元組陣列,有它唯一的地址編號,從 0 開始,
一般來說,組成程式的每潭訓器指令都由不同數量的位元組構成,C 程式變數相對應的資料項的大小根據型別進行變化,
比如,在 Linux 的 x86-64 機器上,short 型別的資料需要 2 個位元組,int 和 float 需要 4 個位元組,而 long 和 double 需要 8 個位元組,
? 處理器(Processor)
CPU(central processing unit)或者簡單的處理器,是解釋(并執行)存盤在主存盤器中的指令的引擎,
處理器的核心大小為一個字的存盤設備(或暫存器),稱為程式計數器(PC),
在任何時刻,PC 都指向主存中的某潭訓器語言指令(即含有該條指令的地址),
從系統通電開始,直到系統斷電,處理器一直在不斷地執行程式計數器指向的指令,再更新程式計數器,使其指向下一條指令,
處理器根據其指令集體系結構定義的指令模型進行操作,在這個模型中,指令按照嚴格的順序執行,執行一條指令涉及執行一系列的步驟,
處理器從程式計數器指向的記憶體中讀取指令,解釋指令中的位,執行該指令指示的一些簡單操作,然后更新程式計數器以指向下一條指令,
指令與指令之間可能連續,可能不連續(比如 jmp 指令就不會順序讀取)
下面是 CPU 可能執行簡單操作的幾個步驟:
? 加載(Load):從主存中拷貝一個位元組或者一個字到記憶體中,覆寫暫存器先前的內容
? 存盤(Store):將暫存器中的位元組或字復制到主存盤器中的某個位置,從而覆寫該位置的先前內容
? 操作(Operate):把兩個暫存器的內容復制到ALU(Arithmetic logic unit),把兩個字進行算術運算,并把結果存盤在暫存器中,重寫暫存器先前的內容,
算術邏輯單元(ALU)是對數字二進制數執行算術和按位運算的組合數字電子電路,
? 跳轉(jump):從指令中抽取一個字,把這個字復制到程式計數器(PC)中,覆寫原來的值
? 剖析 hello 程式的執行程序
前面我們簡單的介紹了一下計算機的硬體的組成和操作,現在我們正式介紹運行示例程式時發生了什么,我們會從宏觀的角度進行描述,不會涉及到所有的技術細節~
剛開始時,shell 程式執行它的指令,等待用戶鍵入一個命令,
當我們在鍵盤上輸入了./hello這幾個字符時,shell 程式將字符逐一讀入暫存器,再把它放到記憶體中,如下圖所示:
當我們在鍵盤上敲擊回車鍵的時候,shell 程式就知道我們已經結束了命令的輸入,
然后 shell 執行一系列指令來加載可執行的 hello 檔案,這些指令將目標檔案中的代碼和資料從磁盤復制到主存,
利用DMA(Direct Memory Access)技術可以直接將磁盤中的資料復制到記憶體中,如下
一旦目標檔案中 hello 中的代碼和資料被加載到主存,處理器就開始執行 hello 程式的 main 程式中的機器語言指令,
這些指令將 hello,world\n 字串中的位元組從主存復制到暫存器檔案,再從暫存器中復制到顯示設備,最終顯示在螢屏上,如下所示:
? 高速快取是關鍵
上面我們介紹完了一個 hello 程式的執行程序,系統花費了大量時間把資訊從一個地方搬運到另外一個地方,
hello 程式的機器指令最初存盤在磁盤上,當程式加載后,它們會拷貝到主存中,
當 CPU 開始運行時,指令又從記憶體復制到 CPU 中,同樣的,字串資料hello,world \n最初也是在磁盤上,它被復制到記憶體中,然后再到顯示幕設備輸出,
從程式員的角度來看,這種復制大部分是開銷,這減慢了程式的作業效率,因此,對于系統設計來說,最主要的一個作業是讓程式運行的越來越快,
由于物理定律,較大的存盤設備要比較小的存盤設備慢,
而由于暫存器和記憶體的處理效率在越來越大,所以針對這種差異,系統設計者采用了更小更快的存盤設備,稱為高速快取存盤器(cache memory, 簡稱為 cache 高速快取),作為暫時的集結區域,存放近期可能會需要的資訊,
如下圖所示:
圖中我們標出了高速快取的位置,位于高速快取中的L1高速快取容量可以達到數萬位元組,訪問速度幾乎和訪問暫存器檔案一樣快,
容量更大的L2高速快取通過一條特殊的總線鏈接 CPU,雖然 L2 快取比 L1 快取慢 5 倍,但是仍比記憶體要快 5 - 10 倍,
L1 和 L2 是使用一種靜態隨機訪問存盤器(SRAM)的硬體技術實作的,
最新的、處理器更強大的系統甚至有三級快取:L1、L2 和 L3,系統可以獲得一個很大的存盤器,同時訪問速度也更快,原因是利用了高速快取的區域性原理,
Again:入門程式細節
現在,我們來探討一下入門級程式的細節,由淺入深的來了解一下 C 語言的特性,
? #include<stdio.h>
我們上面說到,#include<stdio.h>是程式編譯之前要處理的內容,稱為編譯預處理命令,
預處理命令是在編譯之前進行處理,預處理程式一般以 # 號開頭,
所有的 C 編譯器軟體包都提供stdio.h檔案,該檔案包含了給編譯器使用的輸入和輸出函式,比如 println() 資訊,
該檔案名的含義是標準輸入/輸出頭檔案,通常,在 C 程式頂部的資訊集合被稱為頭檔案(header),
C 的第一個標準是由 ANSI 發布的,雖然這份檔案后來被國際標準化組織(ISO)采納并且 ISO 發布的修訂版也被 ANSI 采納了,但名稱 ANSI C(而不是 ISO C) 仍被廣泛使用,一些軟體開發者使用ISO C,還有一些使用Standard C,
? C 標準庫
除了 <sdtio.h> 外,C 標準庫還包括下面這些頭檔案
? <assert.h>
提供了一個名為assert的關鍵字,它用于驗證程式作出的假設,并在假設為假輸出診斷訊息,
? <ctype.h>
C 標準庫的 ctype.h 頭檔案提供了一些函式,可以用于測驗和映射字符,
這些字符接受 int 作為引數,它的值必須是EOF或者是一個無符號字符
EOF是一個計算機術語,為 End Of File 的縮寫,在作業系統中表示資料源無更多的資料可讀取,資料源通常稱為檔案或串流,通常在文本的最后存在此字符表示資料結束,
C 標準庫的errno.h頭檔案定義了整數變數errno,它是通過系統呼叫設定的,這些庫函式表明了什么發生了錯誤,
C 標準庫的float.h頭檔案包含了一組與浮點值相關的依賴于平臺的常量,
? limits.h頭檔案決定了各種變數型別的各種屬性,定義在該頭檔案中的宏限制了各種變數型別(比如 char、int 和 long)的值,
? locale.h頭檔案定義了特定地域的設定,比如日期格式和貨幣符號
? math.h頭檔案定義了各種數學函式和一個宏,在這個庫中所有可用的功能都帶有一個double型別的引數,且都回傳double型別的結果,
? setjmp.h頭檔案定義了宏setjmp()、函式longjmp()和變數型別jmp_buf,該變數型別會繞過正常的函式呼叫和回傳規則,
? signal.h頭檔案定義了一個變數型別sig_atomic_t、兩個函式呼叫和一些宏來處理程式執行期間報告的不同信號,
? stdarg.h頭檔案定義了一個變數型別va_list和三個宏,這三個宏可用于在引數個數未知(即引數個數可變)時獲取函式中的引數,
? stddef .h頭檔案定義了各種變數型別和宏,這些定義中的大部分也出現在其它頭檔案中,
? stdlib .h頭檔案定義了四個變數型別、一些宏和各種通用工具函式,
? string .h頭檔案定義了一個變數型別、一個宏和各種操作字符陣列的函式,
? time.h頭檔案定義了四個變數型別、兩個宏和各種操作日期和時間的函式,
? main() 函式
main 函式聽起來像是調皮搗蛋的孩子故意給方法名起一個主要的方法,來告訴他人他才是這個世界的中心,但事實卻不是這樣,而main()方法確實是世界的中心,
C 語言程式一定從 main() 函式開始執行,除了 main() 函式外,你可以隨意命名其他函式,
通常,main 后面的()中表示一些傳入資訊,我們上面的那個例子中沒有傳遞資訊,因為圓括號中的輸入是 void ,
除了上面那種寫法外,還有兩種 main 方法的表示方式,一種是void main(){},一種是int main(int argc, char* argv[]) {}
? void main() 宣告了一個帶有不確定引數的構造方法
? int main(int argc, char* argv[]) {} 其中的 argc 是一個非負值,表示從運行程式的環境傳遞到程式的引數數量,
它是指向 argc + 1 指標陣列的第一個元素的指標,其中最后一個為null,而前一個(如果有的話)指向表示從主機環境傳遞給程式的引數的字串,
如果argv [0]不是空指標(或者等效地,如果argc> 0),則指向表示程式名稱的字串,如果在主機環境中無法使用程式名稱,則該字串為空,
? 注釋
在程式中,使用 /**/ 的表示注釋,注釋對于程式來說沒有什么實際用處;
但是對程式員來說卻非常有用,它能夠幫助我們理解程式,也能夠讓他人看懂你寫的程式,我們在開發作業中,都非常反感不寫注釋的人,由此可見注釋非常重要,
C 語言注釋的好處是,它可以放在任意地方,甚至代碼在同一行也沒關系,
較長的注釋可以多行表示,我們使用 /**/ 表示多行注釋,而 // 只表示的是單行注釋,下面是幾種注釋的表示形式

? 函式體
在頭檔案、main 方法后面的就是函式體(注釋一般不算),函式體就是函式的執行體,是你撰寫大量代碼的地方,
? 變數宣告
在我們入門級的代碼中,我們宣告了一個名為number的變數,它的型別是 int,這行代碼叫做宣告,宣告是 C 語言最重要的特性之一,這個宣告完成了兩件事情:定義了一個名為 number 的變數,定義 number 的具體型別,
int 是 C 語言的一個關鍵字(keyword),表示一種基本的 C 語言資料型別,關鍵字是用于語言定義的,不能使用關鍵字作為變數進行定義,
示例中的number是一個識別符號(identifier),也就是一個變數、函式或者其他物體的名稱,
? 變數賦值
在入門例子程式中,我們宣告了一個 number 變數,并為其賦值為 11,賦值是 C 語言的基本操作之一,
這行代碼的意思就是把值 1 賦給變數 number,
在執行 int number 時,編譯器會在計算機記憶體中為變數 number 預留空間,然后在執行這行賦值運算式陳述句時,把值存盤在之前預留的位置,
可以給 number 賦不同的值,這就是 number 之所以被稱為變數(variable)的原因,
? printf 函式
在入門例子程式中,有三行 printf(),這是 C 語言的標準函式,圓括號中的內容是從 main 函式傳遞給 printf 函式的,引數分為兩種:實際引數(actual argument)和形式引數(formal parameters),我們上面提到的 printf 函式括號中的內容,都是實參,
? return 陳述句
在入門例子程式中,return 陳述句是最后一條陳述句,int main(void)中的 int 表明 main() 函式應回傳一個整數,有回傳值的 C 函式要有 return 陳述句,沒有回傳值的程式也建議大家保留 return 關鍵字,這是一種好的習慣或者說統一的編碼風格,
? 分號
在 C 語言中,每一行的結尾都要用;進行結束,它表示一個陳述句的結束,如果忘記或者忽略分號會被編譯器提示錯誤,
? 關鍵字
下面是 C 語言中的關鍵字,C 語言的關鍵字一共有32個,根據其作用不同進行劃分:
? 資料型別關鍵字
資料型別的關鍵字主要有 12 個,分別是
? char: 宣告字符型變數或函式
? double: 宣告雙精度變數或函式
? float: 宣告浮點型變數或函式
? int: 宣告整型變數或函式
? long: 宣告長整型變數或函式
? short: 宣告短整型變數或函式
? signed: 宣告有符號型別變數或函式
? _Bool: 宣告布爾型別
? _Complex:宣告復數
? _Imaginary: 宣告虛數
? unsigned: 宣告無符號型別變數或函式
? void: 宣告函式無回傳值或無引數,宣告無型別指標
——————
? 控制陳述句關鍵字
控制陳述句回圈的關鍵字也有 12 個,分別是
回圈陳述句
? for: for 回圈,使用的最多
? do:回圈陳述句的前提條件回圈體
? while:回圈陳述句的回圈條件
? break: 跳出當前回圈
? continue:結束當前回圈,開始下一輪回圈
條件陳述句
? if:條件陳述句的判斷條件
? else: 條件陳述句的否定分支,與 if 連用
? goto: 無條件跳轉陳述句
開關陳述句
? switch: 用于開關陳述句
? case:開關陳述句的另外一種分支
? default: 開關陳述句中的其他分支
回傳陳述句
? retur:子程式回傳陳述句(可以帶引數,也看不帶引數)
存盤型別關鍵字
? auto: 宣告自動變數 一般不使用
? extern: 宣告變數是在其他檔案正宣告(也可以看做是參考變數)
? register: 宣告暫存器變數
? static: 宣告靜態變數
——————
? 其他關鍵字
? const: 宣告只讀變數
? sizeof: 計算資料型別長度
? typedef: 用以給資料型別取別名
? volatile: 說明變數在程式執行中可被隱含地改變
后記
這篇文章我們先介紹了 C 語言的特性,C 語言為什么這么火,C 語言的重要性;
之后我們以一道 C 語言的入門程式講起,我們講了 C 語言的基本構成要素,C 語言在硬體上是如何運行的,C 語言的編譯程序和執行程序等;
在這之后我們又加深講解了一下入門例子程式的組成特征,

如果你想更深入的了解——編程俱樂部【下圖進入】!
涉及到:C語言、C++、windows編程、網路編程、QT界面開發、Linux編程、游戲編程、黑客等等......

程式員編程入門資料:

程式員?推薦學習書籍:

帶你一個活躍、高逼格、高層次的程式員編程學習殿堂;編程入門只是順帶,思維的提高才有價值!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/201277.html
標籤:C
