哦!這該死的 C 語(yǔ)言!
掃描二維碼
隨時(shí)隨地手機(jī)看文章
前言
C 語(yǔ)言是一門抽象的
、面向過(guò)程
的語(yǔ)言,C 語(yǔ)言廣泛應(yīng)用于底層開發(fā)
,C 語(yǔ)言在計(jì)算機(jī)體系中占據(jù)著不可替代的作用,可以說(shuō) C 語(yǔ)言是編程的基礎(chǔ),也就是說(shuō),不管你學(xué)習(xí)任何語(yǔ)言,都應(yīng)該把 C 語(yǔ)言放在首先要學(xué)
的位置上。下面這張圖更好的說(shuō)明 C 語(yǔ)言的重要性
可以看到,C 語(yǔ)言是一種底層語(yǔ)言,是一種系統(tǒng)層級(jí)的語(yǔ)言,操作系統(tǒng)就是使用 C 語(yǔ)言來(lái)編寫的,比如 Windows、Linux、UNIX 。如果說(shuō)其他語(yǔ)言是光鮮亮麗的外表,那么 C 語(yǔ)言就是靈魂,永遠(yuǎn)那么樸實(shí)無(wú)華。
C 語(yǔ)言特性
那么,既然 C 語(yǔ)言這么重要,它有什么值得我們?nèi)W(xué)的地方呢?我們不應(yīng)該只因?yàn)樗匾W(xué),我們更在意的是學(xué)完我們能學(xué)會(huì)什么,能讓我們獲得什么。
C 語(yǔ)言的設(shè)計(jì)
C 語(yǔ)言是 1972 年,由貝爾實(shí)驗(yàn)室的丹尼斯·里奇(Dennis Ritch)
和肯·湯普遜(Ken Thompson)
在開發(fā) UNIX 操作系統(tǒng)時(shí)設(shè)計(jì)了C語(yǔ)言。C 語(yǔ)言是一門流行的語(yǔ)言,它把計(jì)算機(jī)科學(xué)理論和工程實(shí)踐理論完美的融合在一起,使用戶能夠完成模塊化的編程和設(shè)計(jì)。
計(jì)算機(jī)科學(xué)理論:簡(jiǎn)稱 CS、是系統(tǒng)性研究信息與計(jì)算的理論基礎(chǔ)以及它們?cè)谟?jì)算機(jī)系統(tǒng)中如何實(shí)現(xiàn)與應(yīng)用的實(shí)用技術(shù)的學(xué)科。
C 語(yǔ)言具有高效性
C 語(yǔ)言是一門高效性語(yǔ)言,它被設(shè)計(jì)用來(lái)充分發(fā)揮計(jì)算機(jī)的優(yōu)勢(shì),因此 C 語(yǔ)言程序運(yùn)行速度很快,C 語(yǔ)言能夠合理了使用內(nèi)存來(lái)獲得最大的運(yùn)行速度
C 語(yǔ)言具有可移植性
C 語(yǔ)言是一門具有可移植性的語(yǔ)言,這就意味著,對(duì)于在一臺(tái)計(jì)算機(jī)上編寫的 C 語(yǔ)言程序可以在另一臺(tái)計(jì)算機(jī)上輕松地運(yùn)行,從而極大的減少了程序移植的工作量。
C 語(yǔ)言特點(diǎn)
-
C 語(yǔ)言是一門簡(jiǎn)潔的語(yǔ)言,因?yàn)?C 語(yǔ)言設(shè)計(jì)更加靠近底層,因此不需要眾多 Java 、C# 等高級(jí)語(yǔ)言才有的特性,程序的編寫要求不是很嚴(yán)格。 -
C 語(yǔ)言具有結(jié)構(gòu)化控制語(yǔ)句,C 語(yǔ)言是一門結(jié)構(gòu)化的語(yǔ)言,它提供的控制語(yǔ)句具有結(jié)構(gòu)化特征,如 for 循環(huán)、if? else 判斷語(yǔ)句和 switch 語(yǔ)句等。 -
C 語(yǔ)言具有豐富的數(shù)據(jù)類型,不僅包含有傳統(tǒng)的 字符型、整型、浮點(diǎn)型、數(shù)組類型等數(shù)據(jù)類型,還具有其他編程語(yǔ)言所不具備的數(shù)據(jù)類型,比如指針。 -
C 語(yǔ)言能夠直接對(duì)內(nèi)存地址進(jìn)行讀寫,因此可以實(shí)現(xiàn)匯編語(yǔ)言的主要功能,并可直接操作硬件。 -
C 語(yǔ)言速度快,生成的目標(biāo)代碼執(zhí)行效率高。
下面讓我們通過(guò)一個(gè)簡(jiǎn)單的示例來(lái)說(shuō)明一下 C 語(yǔ)言
入門級(jí) C 語(yǔ)言程序
下面我們來(lái)看一個(gè)很簡(jiǎn)單的 C 語(yǔ)言程序,我是 mac 電腦,所以我使用的是 xcode
進(jìn)行開發(fā),我覺(jué)得工具無(wú)所謂大家用著順手就行。
第一個(gè) C 語(yǔ)言程序
#include <stdio.h>
int main(int argc, const char * argv[]) {
printf("Hello, World!\n");
printf("My Name is cxuan \n");
return 0;
}
你可能不知道這段代碼是什么意思,不過(guò)別著急,我們先運(yùn)行一下看看結(jié)果。
這段程序輸出了 Hello,World!
和 My Name is cxuan
,最后一行是程序的執(zhí)行結(jié)果,表示這段程序是否有錯(cuò)誤。下面我們解釋一下各行代碼的含義。
首先,第一行的 #include <stdio.h>
, 這行代碼包含另一個(gè)文件,這一行告訴編譯器把 stdio.h
的內(nèi)容包含在當(dāng)前程序中。stdio.h
是 C 編譯器軟件包的標(biāo)準(zhǔn)部分,它能夠提供鍵盤輸入和顯示器輸出。
什么是 C 標(biāo)準(zhǔn)軟件包?C 是由 Dennis M 在1972年開發(fā)的通用,過(guò)程性,命令式計(jì)算機(jī)編程語(yǔ)言。C標(biāo)準(zhǔn)庫(kù)是一組 C 語(yǔ)言內(nèi)置函數(shù),常量和頭文件,例如<stdio.h>,<stdlib.h>,<math.h>等。此庫(kù)將用作 C 程序員的參考手冊(cè)。
我們后面會(huì)介紹 stdio.h ,現(xiàn)在你知道它是什么就好。
在 stdio.h 下面一行代碼就是 main
函數(shù)。
C 程序能夠包含一個(gè)或多個(gè)函數(shù),函數(shù)是 C 語(yǔ)言的根本,就和方法是 Java 的基本構(gòu)成一樣。main()
表示一個(gè)函數(shù)名,int
表示的是 main 函數(shù)返回一個(gè)整數(shù)。void 表明 main() 不帶任何參數(shù)。這些我們后面也會(huì)詳細(xì)說(shuō)明,只需要記住 int 和 void 是標(biāo)準(zhǔn) ANSI C
定義 main() 的一部分(如果使用 ANSI C 之前的編譯器,請(qǐng)忽略 void)。
然后是 /*一個(gè)簡(jiǎn)單的 C 語(yǔ)言程序*/
表示的是注釋,注釋使用 /**/
來(lái)表示,注釋的內(nèi)容在兩個(gè)符號(hào)之間。這些符號(hào)能夠提高程序的可讀性。
注意:注釋只是為了幫助程序員理解代碼的含義,編譯器會(huì)忽略注釋
下面就是 {
,這是左花括號(hào),它表示的是函數(shù)體的開始,而最后的右花括號(hào) }
表示函數(shù)體的結(jié)束。{ }
中間是書寫代碼的地方,也叫做代碼塊。
int number
表示的是將會(huì)使用一個(gè)名為 number 的變量,而且 number 是 int
整數(shù)類型。
number = 11
表示的是把值 11 賦值給 number 的變量。
printf(Hello,world!\n);
表示調(diào)用一個(gè)函數(shù),這個(gè)語(yǔ)句使用 printf()
函數(shù),在屏幕上顯示 Hello,world
, printf() 函數(shù)是 C 標(biāo)準(zhǔn)庫(kù)函數(shù)中的一種,它能夠把程序運(yùn)行的結(jié)果輸出到顯示器上。而代碼 \n
表示的是 換行
,也就是另起一行,把光標(biāo)移到下一行。
然后接下來(lái)的一行 printf() 和上面一行是一樣的,我們就不多說(shuō)了。最后一行 printf() 有點(diǎn)意思,你會(huì)發(fā)現(xiàn)有一個(gè) %d
的語(yǔ)法,它的意思表示的是使用整形輸出字符串。
代碼塊的最后一行是 return 0
,它可以看成是 main 函數(shù)的結(jié)束,最后一行是代碼塊 }
,它表示的是程序的結(jié)束。
好了,我們現(xiàn)在寫完了第一個(gè) C 語(yǔ)言程序,有沒(méi)有對(duì) C 有了更深的認(rèn)識(shí)呢?肯定沒(méi)有。。。這才哪到哪,繼續(xù)學(xué)習(xí)吧。
現(xiàn)在,我們可以歸納為 C 語(yǔ)言程序的幾個(gè)組成要素,如下圖所示
C 語(yǔ)言執(zhí)行流程
C 語(yǔ)言程序成為高級(jí)語(yǔ)言的原因是它能夠讀取并理解人們的思想。然而,為了能夠在系統(tǒng)中運(yùn)行 hello.c
程序,則各個(gè) C 語(yǔ)句必須由其他程序轉(zhuǎn)換為一系列低級(jí)機(jī)器語(yǔ)言指令。這些指令被打包作為可執(zhí)行對(duì)象程序
,存儲(chǔ)在二進(jìn)制磁盤文件中。目標(biāo)程序也稱為可執(zhí)行目標(biāo)文件。
在 UNIX 系統(tǒng)中,從源文件到對(duì)象文件的轉(zhuǎn)換是由編譯器
執(zhí)行完成的。
gcc -o hello hello.c
gcc 編譯器驅(qū)動(dòng)從源文件讀取 hello.c
,并把它翻譯成一個(gè)可執(zhí)行文件 hello
。這個(gè)翻譯過(guò)程可用如下圖來(lái)表示
這就是一個(gè)完整的 hello world 程序執(zhí)行過(guò)程,會(huì)涉及幾個(gè)核心組件:預(yù)處理器、編譯器、匯編器、連接器,下面我們逐個(gè)擊破。
-
預(yù)處理階段(Preprocessing phase)
,預(yù)處理器會(huì)根據(jù)開始的#
字符,修改源 C 程序。#include <stdio.h> 命令就會(huì)告訴預(yù)處理器去讀系統(tǒng)頭文件stdio.h
中的內(nèi)容,并把它插入到程序作為文本。然后就得到了另外一個(gè) C 程序hello.i
,這個(gè)程序通常是以.i
為結(jié)尾。 -
然后是
編譯階段(Compilation phase)
,編譯器會(huì)把文本文件hello.i
翻譯成文本hello.s
,它包括一段匯編語(yǔ)言程序(assembly-language program)
。 -
編譯完成之后是
匯編階段(Assembly phase)
,這一步,匯編器 as
會(huì)把 hello.s 翻譯成機(jī)器指令,把這些指令打包成可重定位的二進(jìn)制程序(relocatable object program)
放在 hello.c 文件中。它包含的 17 個(gè)字節(jié)是函數(shù) main 的指令編碼,如果我們?cè)谖谋揪庉嬈髦写蜷_ hello.o 將會(huì)看到一堆亂碼。 -
最后一個(gè)是
鏈接階段(Linking phase)
,我們的 hello 程序會(huì)調(diào)用printf
函數(shù),它是 C 編譯器提供的 C 標(biāo)準(zhǔn)庫(kù)中的一部分。printf 函數(shù)位于一個(gè)叫做printf.o
文件中,它是一個(gè)單獨(dú)的預(yù)編譯好的目標(biāo)文件,而這個(gè)文件必須要和我們的 hello.o 進(jìn)行鏈接,連接器(ld)
會(huì)處理這個(gè)合并操作。結(jié)果是,hello 文件,它是一個(gè)可執(zhí)行的目標(biāo)文件(或稱為可執(zhí)行文件),已準(zhǔn)備好加載到內(nèi)存中并由系統(tǒng)執(zhí)行。
你需要理解編譯系統(tǒng)做了什么
對(duì)于上面這種簡(jiǎn)單的 hello 程序來(lái)說(shuō),我們可以依賴編譯系統(tǒng)(compilation system)
來(lái)提供一個(gè)正確和有效的機(jī)器代碼。然而,對(duì)于我們上面講的程序員來(lái)說(shuō),編譯器有幾大特征你需要知道
-
優(yōu)化程序性能(Optimizing program performance)
,現(xiàn)代編譯器是一種高效的用來(lái)生成良好代碼的工具。對(duì)于程序員來(lái)說(shuō),你無(wú)需為了編寫高質(zhì)量的代碼而去理解編譯器內(nèi)部做了什么工作。然而,為了編寫出高效的 C 語(yǔ)言程序,我們需要了解一些基本的機(jī)器碼以及編譯器將不同的 C 語(yǔ)句轉(zhuǎn)化為機(jī)器代碼的過(guò)程。 -
理解鏈接時(shí)出現(xiàn)的錯(cuò)誤(Understanding link-time errors)
,在我們的經(jīng)驗(yàn)中,一些非常復(fù)雜的錯(cuò)誤大多是由鏈接階段引起的,特別是當(dāng)你想要構(gòu)建大型軟件項(xiàng)目時(shí)。 -
避免安全漏洞(Avoiding security holes)
,近些年來(lái),緩沖區(qū)溢出(buffer overflow vulnerabilities)
是造成網(wǎng)絡(luò)和 Internet 服務(wù)的罪魁禍?zhǔn)祝晕覀冇斜匾ヒ?guī)避這種問(wèn)題。
系統(tǒng)硬件組成
為了理解 hello 程序在運(yùn)行時(shí)發(fā)生了什么,我們需要首先對(duì)系統(tǒng)的硬件有一個(gè)認(rèn)識(shí)。下面這是一張 Intel 系統(tǒng)產(chǎn)品的模型,我們來(lái)對(duì)其進(jìn)行解釋
-
總線(Buses)
:在整個(gè)系統(tǒng)中運(yùn)行的是稱為總線的電氣管道的集合,這些總線在組件之間來(lái)回傳輸字節(jié)信息。通??偩€被設(shè)計(jì)成傳送定長(zhǎng)的字節(jié)塊,也就是字(word)
。字中的字節(jié)數(shù)(字長(zhǎng))是一個(gè)基本的系統(tǒng)參數(shù),各個(gè)系統(tǒng)中都不盡相同?,F(xiàn)在大部分的字都是 4 個(gè)字節(jié)(32 位)或者 8 個(gè)字節(jié)(64 位)。
-
I/O 設(shè)備(I/O Devices)
:Input/Output 設(shè)備是系統(tǒng)和外部世界的連接。上圖中有四類 I/O 設(shè)備:用于用戶輸入的鍵盤和鼠標(biāo),用于用戶輸出的顯示器,一個(gè)磁盤驅(qū)動(dòng)用來(lái)長(zhǎng)時(shí)間的保存數(shù)據(jù)和程序。剛開始的時(shí)候,可執(zhí)行程序就保存在磁盤上。每個(gè)I/O 設(shè)備連接 I/O 總線都被稱為
控制器(controller)
或者是適配器(Adapter)
??刂破骱瓦m配器之間的主要區(qū)別在于封裝方式??刂破魇?I/O 設(shè)備本身或者系統(tǒng)的主印制板電路(通常稱作主板)上的芯片組。而適配器則是一塊插在主板插槽上的卡。無(wú)論組織形式如何,它們的最終目的都是彼此交換信息。 -
主存(Main Memory)
,主存是一個(gè)臨時(shí)存儲(chǔ)設(shè)備
,而不是永久性存儲(chǔ),磁盤是永久性存儲(chǔ)
的設(shè)備。主存既保存程序,又保存處理器執(zhí)行流程所處理的數(shù)據(jù)。從物理組成上說(shuō),主存是由一系列DRAM(dynamic random access memory)
動(dòng)態(tài)隨機(jī)存儲(chǔ)構(gòu)成的集合。邏輯上說(shuō),內(nèi)存就是一個(gè)線性的字節(jié)數(shù)組,有它唯一的地址編號(hào),從 0 開始。一般來(lái)說(shuō),組成程序的每條機(jī)器指令都由不同數(shù)量的字節(jié)構(gòu)成,C 程序變量相對(duì)應(yīng)的數(shù)據(jù)項(xiàng)的大小根據(jù)類型進(jìn)行變化。比如,在 Linux 的 x86-64 機(jī)器上,short 類型的數(shù)據(jù)需要 2 個(gè)字節(jié),int 和 float 需要 4 個(gè)字節(jié),而 long 和 double 需要 8 個(gè)字節(jié)。 -
處理器(Processor)
,CPU(central processing unit)
或者簡(jiǎn)單的處理器,是解釋(并執(zhí)行)存儲(chǔ)在主存儲(chǔ)器中的指令的引擎。處理器的核心大小為一個(gè)字的存儲(chǔ)設(shè)備(或寄存器),稱為程序計(jì)數(shù)器(PC)
。在任何時(shí)刻,PC 都指向主存中的某條機(jī)器語(yǔ)言指令(即含有該條指令的地址)。從系統(tǒng)通電開始,直到系統(tǒng)斷電,處理器一直在不斷地執(zhí)行程序計(jì)數(shù)器指向的指令,再更新程序計(jì)數(shù)器,使其指向下一條指令。處理器根據(jù)其指令集體系結(jié)構(gòu)定義的指令模型進(jìn)行操作。在這個(gè)模型中,指令按照嚴(yán)格的順序執(zhí)行,執(zhí)行一條指令涉及執(zhí)行一系列的步驟。處理器從程序計(jì)數(shù)器指向的內(nèi)存中讀取指令,解釋指令中的位,執(zhí)行該指令指示的一些簡(jiǎn)單操作,然后更新程序計(jì)數(shù)器以指向下一條指令。指令與指令之間可能連續(xù),可能不連續(xù)(比如 jmp 指令就不會(huì)順序讀?。?/p>
下面是 CPU 可能執(zhí)行簡(jiǎn)單操作的幾個(gè)步驟
-
加載(Load)
:從主存中拷貝一個(gè)字節(jié)或者一個(gè)字到內(nèi)存中,覆蓋寄存器先前的內(nèi)容 -
存儲(chǔ)(Store)
:將寄存器中的字節(jié)或字復(fù)制到主存儲(chǔ)器中的某個(gè)位置,從而覆蓋該位置的先前內(nèi)容 -
操作(Operate)
:把兩個(gè)寄存器的內(nèi)容復(fù)制到ALU(Arithmetic logic unit)
。把兩個(gè)字進(jìn)行算術(shù)運(yùn)算,并把結(jié)果存儲(chǔ)在寄存器中,重寫寄存器先前的內(nèi)容。
算術(shù)邏輯單元(ALU)是對(duì)數(shù)字二進(jìn)制數(shù)執(zhí)行算術(shù)和按位運(yùn)算的組合數(shù)字電子電路。
-
跳轉(zhuǎn)(jump)
:從指令中抽取一個(gè)字,把這個(gè)字復(fù)制到程序計(jì)數(shù)器(PC)
中,覆蓋原來(lái)的值
剖析 hello 程序的執(zhí)行過(guò)程
前面我們簡(jiǎn)單的介紹了一下計(jì)算機(jī)的硬件的組成和操作,現(xiàn)在我們正式介紹運(yùn)行示例程序時(shí)發(fā)生了什么,我們會(huì)從宏觀的角度進(jìn)行描述,不會(huì)涉及到所有的技術(shù)細(xì)節(jié)
剛開始時(shí),shell 程序執(zhí)行它的指令,等待用戶鍵入一個(gè)命令。當(dāng)我們?cè)阪I盤上輸入了 ./hello
這幾個(gè)字符時(shí),shell 程序?qū)⒆址鹨蛔x入寄存器,再把它放到內(nèi)存中,如下圖所示
當(dāng)我們?cè)阪I盤上敲擊回車鍵
的時(shí)候,shell 程序就知道我們已經(jīng)結(jié)束了命令的輸入。然后 shell 執(zhí)行一系列指令來(lái)加載可執(zhí)行的 hello 文件,這些指令將目標(biāo)文件中的代碼和數(shù)據(jù)從磁盤復(fù)制到主存。
利用 DMA(Direct Memory Access)
技術(shù)可以直接將磁盤中的數(shù)據(jù)復(fù)制到內(nèi)存中,如下
一旦目標(biāo)文件中 hello 中的代碼和數(shù)據(jù)被加載到主存,處理器就開始執(zhí)行 hello 程序的 main 程序中的機(jī)器語(yǔ)言指令。這些指令將 hello,world\n
字符串中的字節(jié)從主存復(fù)制到寄存器文件,再?gòu)募拇嫫髦袕?fù)制到顯示設(shè)備,最終顯示在屏幕上。如下所示
高速緩存是關(guān)鍵
上面我們介紹完了一個(gè) hello 程序的執(zhí)行過(guò)程,系統(tǒng)花費(fèi)了大量時(shí)間把信息從一個(gè)地方搬運(yùn)到另外一個(gè)地方。hello 程序的機(jī)器指令最初存儲(chǔ)在磁盤
上。當(dāng)程序加載后,它們會(huì)拷貝
到主存中。當(dāng) CPU 開始運(yùn)行時(shí),指令又從內(nèi)存復(fù)制到 CPU 中。同樣的,字符串?dāng)?shù)據(jù) hello,world \n
最初也是在磁盤上,它被復(fù)制到內(nèi)存中,然后再到顯示器設(shè)備輸出。從程序員的角度來(lái)看,這種復(fù)制大部分是開銷,這減慢了程序的工作效率。因此,對(duì)于系統(tǒng)設(shè)計(jì)來(lái)說(shuō),最主要的一個(gè)工作是讓程序運(yùn)行的越來(lái)越快。
由于物理定律,較大的存儲(chǔ)設(shè)備要比較小的存儲(chǔ)設(shè)備慢。而由于寄存器和內(nèi)存的處理效率在越來(lái)越大,所以針對(duì)這種差異,系統(tǒng)設(shè)計(jì)者采用了更小更快的存儲(chǔ)設(shè)備,稱為高速緩存存儲(chǔ)器(cache memory, 簡(jiǎn)稱為 cache 高速緩存)
,作為暫時(shí)的集結(jié)區(qū)域,存放近期可能會(huì)需要的信息。如下圖所示
圖中我們標(biāo)出了高速緩存的位置,位于高速緩存中的 L1
高速緩存容量可以達(dá)到數(shù)萬(wàn)字節(jié),訪問(wèn)速度幾乎和訪問(wèn)寄存器文件一樣快。容量更大的 L2
高速緩存通過(guò)一條特殊的總線鏈接 CPU,雖然 L2 緩存比 L1 緩存慢 5 倍,但是仍比內(nèi)存要快 5 - 10 倍。L1 和 L2 是使用一種靜態(tài)隨機(jī)訪問(wèn)存儲(chǔ)器(SRAM)
的硬件技術(shù)實(shí)現(xiàn)的。最新的、處理器更強(qiáng)大的系統(tǒng)甚至有三級(jí)緩存:L1、L2 和 L3。系統(tǒng)可以獲得一個(gè)很大的存儲(chǔ)器,同時(shí)訪問(wèn)速度也更快,原因是利用了高速緩存的 局部性
原理。
Again:入門程序細(xì)節(jié)
現(xiàn)在,我們來(lái)探討一下入門級(jí)
程序的細(xì)節(jié),由淺入深的來(lái)了解一下 C 語(yǔ)言的特性。
#include<stdio.h>
我們上面說(shuō)到,#include<stdio.h>
是程序編譯之前要處理的內(nèi)容,稱為編譯預(yù)處理
命令。
預(yù)處理命令是在編譯之前進(jìn)行處理。預(yù)處理程序一般以 #
號(hào)開頭。
所有的 C 編譯器軟件包都提供 stdio.h
文件。該文件包含了給編譯器使用的輸入和輸出函數(shù),比如 println() 信息。該文件名的含義是標(biāo)準(zhǔn)輸入/輸出 頭文件。通常,在 C 程序頂部的信息集合被稱為 頭文件(header)
。
C 的第一個(gè)標(biāo)準(zhǔn)是由 ANSI 發(fā)布的。雖然這份文檔后來(lái)被國(guó)際標(biāo)準(zhǔn)化組織(ISO)采納并且 ISO 發(fā)布的修訂版也被 ANSI 采納了,但名稱 ANSI C(而不是 ISO C) 仍被廣泛使用。一些軟件開發(fā)者使用ISO C,還有一些使用 Standard C。
C 標(biāo)準(zhǔn)庫(kù)
除了 <sdtio.h> 外,C 標(biāo)準(zhǔn)庫(kù)還包括下面這些頭文件
<assert.h>
提供了一個(gè)名為 assert
的關(guān)鍵字,它用于驗(yàn)證程序作出的假設(shè),并在假設(shè)為假輸出診斷消息。
<ctype.h>
C 標(biāo)準(zhǔn)庫(kù)的 ctype.h 頭文件提供了一些函數(shù),可以用于測(cè)試和映射字符。
這些字符接受 int 作為參數(shù),它的值必須是 EOF
或者是一個(gè)無(wú)符號(hào)字符
EOF是一個(gè)計(jì)算機(jī)術(shù)語(yǔ),為 End Of File 的縮寫,在操作系統(tǒng)中表示資料源無(wú)更多的資料可讀取。資料源通常稱為檔案或串流。通常在文本的最后存在此字符表示資料結(jié)束。
C 標(biāo)準(zhǔn)庫(kù)的 errno.h 頭文件定義了整數(shù)變量 errno,它是通過(guò)系統(tǒng)調(diào)用設(shè)置的,這些庫(kù)函數(shù)表明了什么發(fā)生了錯(cuò)誤。
C 標(biāo)準(zhǔn)庫(kù)的 float.h 頭文件包含了一組與浮點(diǎn)值相關(guān)的依賴于平臺(tái)的常量。
limits.h 頭文件決定了各種變量類型的各種屬性。定義在該頭文件中的宏限制了各種變量類型(比如 char、int 和 long)的值。
locale.h 頭文件定義了特定地域的設(shè)置,比如日期格式和貨幣符號(hào)
math.h 頭文件定義了各種數(shù)學(xué)函數(shù)和一個(gè)宏。在這個(gè)庫(kù)中所有可用的功能都帶有一個(gè) double 類型的參數(shù),且都返回 double 類型的結(jié)果。
setjmp.h 頭文件定義了宏 setjmp()、函數(shù) longjmp() 和變量類型 jmp_buf,該變量類型會(huì)繞過(guò)正常的函數(shù)調(diào)用和返回規(guī)則。
signal.h 頭文件定義了一個(gè)變量類型 sig_atomic_t、兩個(gè)函數(shù)調(diào)用和一些宏來(lái)處理程序執(zhí)行期間報(bào)告的不同信號(hào)。
stdarg.h 頭文件定義了一個(gè)變量類型 va_list 和三個(gè)宏,這三個(gè)宏可用于在參數(shù)個(gè)數(shù)未知(即參數(shù)個(gè)數(shù)可變)時(shí)獲取函數(shù)中的參數(shù)。
stddef .h 頭文件定義了各種變量類型和宏。這些定義中的大部分也出現(xiàn)在其它頭文件中。
stdlib .h 頭文件定義了四個(gè)變量類型、一些宏和各種通用工具函數(shù)。
string .h 頭文件定義了一個(gè)變量類型、一個(gè)宏和各種操作字符數(shù)組的函數(shù)。
time.h 頭文件定義了四個(gè)變量類型、兩個(gè)宏和各種操作日期和時(shí)間的函數(shù)。
main() 函數(shù)
main 函數(shù)聽起來(lái)像是調(diào)皮搗蛋的孩子故意給方法名起一個(gè) 主要的
方法,來(lái)告訴他人他才是這個(gè)世界的中心。但事實(shí)卻不是這樣,而 main()
方法確實(shí)是世界的中心。
C 語(yǔ)言程序一定從 main() 函數(shù)開始執(zhí)行,除了 main() 函數(shù)外,你可以隨意命名其他函數(shù)。通常,main 后面的 ()
中表示一些傳入信息,我們上面的那個(gè)例子中沒(méi)有傳遞信息,因?yàn)閳A括號(hào)中的輸入是 void 。
除了上面那種寫法外,還有兩種 main 方法的表示方式,一種是 void main(){}
,一種是 int main(int argc, char* argv[]) {}
-
void main() 聲明了一個(gè)帶有不確定參數(shù)的構(gòu)造方法 -
int main(int argc, char* argv[]) {} 其中的 argc 是一個(gè)非負(fù)值,表示從運(yùn)行程序的環(huán)境傳遞到程序的參數(shù)數(shù)量。它是指向 argc + 1 指針數(shù)組的第一個(gè)元素的指針,其中最后一個(gè)為null,而前一個(gè)(如果有的話)指向表示從主機(jī)環(huán)境傳遞給程序的參數(shù)的字符串。如果argv [0]不是空指針(或者等效地,如果argc> 0),則指向表示程序名稱的字符串,如果在主機(jī)環(huán)境中無(wú)法使用程序名稱,則該字符串為空。
注釋
在程序中,使用 /**/ 的表示注釋,注釋對(duì)于程序來(lái)說(shuō)沒(méi)有什么實(shí)際用處,但是對(duì)程序員來(lái)說(shuō)卻非常有用,它能夠幫助我們理解程序,也能夠讓他人看懂你寫的程序,我們?cè)陂_發(fā)工作中,都非常反感不寫注釋的人,由此可見注釋非常重要。
C 語(yǔ)言注釋的好處是,它可以放在任意地方,甚至代碼在同一行也沒(méi)關(guān)系。較長(zhǎng)的注釋可以多行表示,我們使用 /**/ 表示多行注釋,而 // 只表示的是單行注釋。下面是幾種注釋的表示形式
// 這是一個(gè)單行注釋
/* 多行注釋用一行表示 */
/*
多行注釋用多行表示
多行注釋用多行表示
多行注釋用多行表示
多行注釋用多行表示
*/
函數(shù)體
在頭文件、main 方法后面的就是函數(shù)體(注釋一般不算),函數(shù)體就是函數(shù)的執(zhí)行體,是你編寫大量代碼的地方。
變量聲明
在我們?nèi)腴T級(jí)的代碼中,我們聲明了一個(gè)名為 number
的變量,它的類型是 int,這行代碼叫做 聲明
,聲明是 C 語(yǔ)言最重要的特性之一。這個(gè)聲明完成了兩件事情:定義了一個(gè)名為 number 的變量,定義 number 的具體類型。
int 是 C 語(yǔ)言的一個(gè) 關(guān)鍵字(keyword)
,表示一種基本的 C 語(yǔ)言數(shù)據(jù)類型。關(guān)鍵字是用于語(yǔ)言定義的。不能使用關(guān)鍵字作為變量進(jìn)行定義。
示例中的 number
是一個(gè) 標(biāo)識(shí)符(identifier)
,也就是一個(gè)變量、函數(shù)或者其他實(shí)體的名稱。
變量賦值
在入門例子程序中,我們聲明了一個(gè) number 變量,并為其賦值為 11,賦值是 C 語(yǔ)言的基本操作之一。這行代碼的意思就是把值 1 賦給變量 number。在執(zhí)行 int number 時(shí),編譯器會(huì)在計(jì)算機(jī)內(nèi)存中為變量 number 預(yù)留空間,然后在執(zhí)行這行賦值表達(dá)式語(yǔ)句時(shí),把值存儲(chǔ)在之前預(yù)留的位置??梢越o number 賦不同的值,這就是 number 之所以被稱為 變量(variable)
的原因。
printf 函數(shù)
在入門例子程序中,有三行 printf(),這是 C 語(yǔ)言的標(biāo)準(zhǔn)函數(shù)。圓括號(hào)中的內(nèi)容是從 main 函數(shù)傳遞給 printf 函數(shù)的。參數(shù)分為兩種:實(shí)際參數(shù)(actual argument)
和 形式參數(shù)(formal parameters)
。我們上面提到的 printf 函數(shù)括號(hào)中的內(nèi)容,都是實(shí)參。
return 語(yǔ)句
在入門例子程序中,return 語(yǔ)句是最后一條語(yǔ)句。int main(void)
中的 int 表明 main() 函數(shù)應(yīng)返回一個(gè)整數(shù)。有返回值的 C 函數(shù)要有 return 語(yǔ)句,沒(méi)有返回值的程序也建議大家保留 return 關(guān)鍵字,這是一種好的習(xí)慣或者說(shuō)統(tǒng)一的編碼風(fēng)格。
分號(hào)
在 C 語(yǔ)言中,每一行的結(jié)尾都要用 ;
進(jìn)行結(jié)束,它表示一個(gè)語(yǔ)句的結(jié)束,如果忘記或者忽略分號(hào)會(huì)被編譯器提示錯(cuò)誤。
關(guān)鍵字
下面是 C 語(yǔ)言中的關(guān)鍵字,C 語(yǔ)言的關(guān)鍵字一共有 32
個(gè),根據(jù)其作用不同進(jìn)行劃分
數(shù)據(jù)類型關(guān)鍵字
數(shù)據(jù)類型的關(guān)鍵字主要有 12 個(gè),分別是
-
char
: 聲明字符型變量或函數(shù) -
double
: 聲明雙精度變量或函數(shù) -
float
: 聲明浮點(diǎn)型變量或函數(shù) -
int
: 聲明整型變量或函數(shù) -
long
: 聲明長(zhǎng)整型變量或函數(shù) -
short
: 聲明短整型變量或函數(shù) -
signed
: 聲明有符號(hào)類型變量或函數(shù) -
_Bool
: 聲明布爾類型 -
_Complex
:聲明復(fù)數(shù) -
_Imaginary
: 聲明虛數(shù) -
unsigned
: 聲明無(wú)符號(hào)類型變量或函數(shù) -
void
: 聲明函數(shù)無(wú)返回值或無(wú)參數(shù),聲明無(wú)類型指針
控制語(yǔ)句關(guān)鍵字
控制語(yǔ)句循環(huán)的關(guān)鍵字也有 12 個(gè),分別是
循環(huán)語(yǔ)句
-
for
: for 循環(huán),使用的最多 -
do
:循環(huán)語(yǔ)句的前提條件循環(huán)體 -
while
:循環(huán)語(yǔ)句的循環(huán)條件 -
break
: 跳出當(dāng)前循環(huán) -
continue
:結(jié)束當(dāng)前循環(huán),開始下一輪循環(huán)
條件語(yǔ)句
-
if
:條件語(yǔ)句的判斷條件 -
else
: 條件語(yǔ)句的否定分支,與 if 連用 -
goto
: 無(wú)條件跳轉(zhuǎn)語(yǔ)句
開關(guān)語(yǔ)句
-
switch
: 用于開關(guān)語(yǔ)句 -
case
:開關(guān)語(yǔ)句的另外一種分支 -
default
: 開關(guān)語(yǔ)句中的其他分支
返回語(yǔ)句
retur
:子程序返回語(yǔ)句(可以帶參數(shù),也看不帶參數(shù))
存儲(chǔ)類型關(guān)鍵字
-
auto
: 聲明自動(dòng)變量 一般不使用 -
extern
: 聲明變量是在其他文件正聲明(也可以看做是引用變量) -
register
: 聲明寄存器變量 -
static
: 聲明靜態(tài)變量
其他關(guān)鍵字
-
const
: 聲明只讀變量 -
sizeof
: 計(jì)算數(shù)據(jù)類型長(zhǎng)度 -
typedef
: 用以給數(shù)據(jù)類型取別名 -
volatile
: 說(shuō)明變量在程序執(zhí)行中可被隱含地改變
后記
這篇文章我們先介紹了 C 語(yǔ)言的特性,C 語(yǔ)言為什么這么火,C 語(yǔ)言的重要性,之后我們以一道 C 語(yǔ)言的入門程序講起,我們講了 C 語(yǔ)言的基本構(gòu)成要素,C 語(yǔ)言在硬件上是如何運(yùn)行的,C 語(yǔ)言的編譯過(guò)程和執(zhí)行過(guò)程等,在這之后我們又加深講解了一下入門例子程序的組成特征。
如果你覺(jué)得這篇文章不錯(cuò)的的話,歡迎小伙伴們四連走起:點(diǎn)贊、在看、留言、分享。你的四連是我更文的動(dòng)力。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!