天天看點

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

華章程式員書庫 點選檢視第二章 點選檢視第三章

C程式設計技巧:117個問題解決方案示例

C Recipes: A Problem-Solution Approach

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

希裡什·查萬(Shirish Chavan)

盧濤 譯

第1章

歡迎學習C語言

C是一門過程式程式設計語言。C的早期曆史與UNIX非常接近。這是因為C是專門為編寫UNIX作業系統而開發的,UNIX作業系統由貝爾實驗室于1969年推出,用來取代PDP-7計算機的Multics作業系統。UNIX的原始版本是用彙編語言編寫的,但用彙編語言編寫的程式比用進階語言編寫的程式可移植性差。是以,AT&T的人們決定用進階語言重寫此作業系統。做出這個決定之後,他們開始尋找合适的語言,但是當時沒有合适的允許位級程式設計的進階語言。

在同一時期(1970年),Kenneth Thompson開發了一種系統程式設計語言,按照其母語言BCPL(由Martin Richards于1967年開發)命名為B語言。1972年,C語言作為B語言的改進版本首次亮相。C語言由Dennis Ritchie開發,其名字來自B(即字母表中,字母C跟着字母B,并且在BCPL的名字中,字母C也跟着字母B)。

Ritchie和貝爾實驗室的一組研究人員一起為C語言建立了一個編譯器。與B語言不同,C語言配備了大量标準類型。1973年,新版本的UNIX釋出了,其中90%以上的UNIX源代碼都是用C語言重寫的,這增強了它的可移植性。随着這個新版本UNIX的到來,計算社群意識到了C語言的強大功能。随着Brian Kernighan和Dennis Ritchie在1978年的《C程式設計語言》一書的出版,C語言一舉成名。

1983年,美國國家标準協會(ANSI)成立了一個名為X3J11的委員會,以建立C語言的标準規格說明。1989年,該标準被準許為ANSI X3.159—1989“Programming Language C”。這個版本的C語言通常稱為ANSI C、标準C或C89。1990年,國際标準化組織(ISO)采納ANSI C标準(稍作修改),把它作為ISO/IEC 8999:1990釋出。這個版本通常稱為C90。1995年,X3J11委員會修改了C89,并增加了一個國際字元集。1999年,它被進一步修改并釋出為ISO 9899:1999。該标準通常稱為C99。2000年,它被采納為ANSI标準。

1.1 程式、軟體和作業系統

在繼續之前,先來解釋計算機程式一詞的含義(以下簡稱程式)。程式隻不過是要送到計算機上的一組指令,這樣計算機就可以完成一些人們需要它完成的工作。程式和軟體之間的關系可以表示如下:

程式+可移植性+文檔+維護=軟體

可移植性是指程式在不同平台(例如Windows平台、UNIX平台等)上運作的能力。文檔表示使用者手冊和插入程式中的注釋。維護意味着根據使用者的請求調試和修改程式。

Microsoft Windows是一種作業系統。它包含一個圖形使用者界面(GUI)。圖形意味着圖像,界面意味着中間人,是以GUI是使用者和幫助使用者的計算機的内部機器(意味着計算機使用者)之間的圖像中間人。在酒店,服務員接受你的訂單,走進廚房,收集你點的菜肴,并為你服務。同樣,作業系統接受你的指令,接近計算機的内部機器,然後為你服務。

1.2 機器語言和彙編語言

微處理器可以恰當地描述為個人計算機的大腦。微處理器隻不過是一個晶片。有各種微處理器可供選擇。微處理器和中央處理單元(CPU)是同義詞。微處理器包含一個稱為算術邏輯單元(ALU)的重要元件,它執行所有計算。ALU的一個顯著特征是它隻能了解機器語言,而機器語言又隻包含兩個字母,即0和1(相比之下英語由26個字母組成)。這是典型的機器語言指令:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

幾十年前,程式員确實使用機器語言來編寫程式。鍵盤隻包含兩個鍵,标着0和1。編寫一個機器語言程式,然後在計算機中鍵入它是一項費力而乏味的工作。之後出現了彙編語言,它減輕了程式員的負擔。彙編語言是低級語言。以下是典型的彙編語言語句(執行兩個數字的乘法運算),這肯定比前面給出的機器語言指令更具可讀性:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

如果機器語言程式包含50個語句,那麼相應的彙編語言程式也将包含大約50個語句。由于ALU僅了解機器語言,是以人們開發了專用軟體(稱為彙編器),以将彙編語言程式轉換為機器語言程式。

1.3 過程式語言

典型的過程式語言比彙編語言更接近英語。例如,下面是過程式語言Pascal中的語句:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

這個語句的含義非常明顯:如果rollNumber的值為147,則在螢幕上顯示消息“Entry denied.”。為了将過程式語言程式翻譯成機器語言程式,人們使用稱為編譯器的軟體。過程式語言是進階語言。

程式員将過程式語言與結構化程式設計技術結合使用。什麼是結構化程式設計?從廣義上講,結構化程式設計這一術語指的是将程式設計藝術轉化為理性科學的運動。這一切都始于Edsger Dijkstra在1968年3月出版的《Communications of the ACM》期刊上發表的“Go To Statement Considered Harmful”(Go To語句是有害的)。結構化程式設計依賴于以下基石:

□子產品化:不是編寫一個大程式,而是将程式拆分為多個子程式或子產品。

□資訊隐藏:子產品的接口應僅顯示盡可能少的資訊。例如,考慮一個計算數字平方根的子產品。該子產品的接口将接受一個數字并傳回此數字的平方根。此子產品的詳細資訊将對此子產品的使用者隐藏。

□抽象:抽象是隐藏細節的過程,以便于了解複雜的系統。在某種程度上,抽象與資訊隐藏有關。

然而,随着程式越來越大,很明顯結構化程式設計技術雖然是必要的,但還不夠。是以,計算機科學家轉向面向對象程式設計,以便管理更複雜的項目。

1.4 面向對象的語言

我們使用計算機程式來解決現實問題。結構化範式的問題在于,無法使用它友善地在計算機上模拟實際問題。在結構化範式中,使用資料結構來模拟現實生活中的對象,但這些資料結構在模拟真實對象方面遠遠不夠。汽車、房屋、狗和樹是現實生活中對象的例子,我們期望程式設計語言能夠模拟這些對象以解決現實生活中的問題。面向對象的範式簡單地通過提供軟體對象來模拟現實生活中的對象,從根本上解決了這個問題。面向對象範式提供的對象是類的執行個體,并擁有像現實生活中的對象那樣的身份、屬性和行為。例如,如果Bird(鳥)是一個類,那麼parrot、peacock、 sparrow 和 eagle(鹦鹉、孔雀、麻雀和鷹)就是對象或Bird類的執行個體。此外,如果Mammal(哺乳動物)是一個類,那麼cat、dog、lion和 tiger(貓、狗、獅子和老虎)是對象或Mammal類的執行個體。與結構化範式相比,面向對象範式更能夠重用現有代碼。代碼是指程式或其中的一部分。

面向對象的範式與結構化範式一樣古老。結構化範式的運動開始于1968年Dijkstra的著名的文章“Go To Statement Considered Harmful”,而面向對象範式自己的程式設計語言SIMULA 67出現于1967年,然而,SIMULA 67的面向對象能力不是很強。第一個真正面向對象的語言是Smalltalk。事實上,面向對象這個術語正是通過Smalltalk文獻創造的。C不是面向對象的語言,它隻是一種過程式語言。1983年,Bjarne Stroustrup為C語言添加了面向對象的功能,并将這種新語言命名為C++,這是計算機行業廣泛使用和重視的第一種面向對象語言。今天,最流行的面向對象語言是Java。面向對象語言是進階語言。

1.5 計算機術語

在幾乎所有科學中,術語都源自希臘語或拉丁語等語言。為什麼?如果你從英語中導出術語,則存在技術含義與該術語的目前用法之間出現混淆的風險。然而,計算機科學中的術語源自英語,這會使初學者混淆。諸如樹(tree)、記憶體(memory)、核心(core)、根(root)、檔案夾(folder)、檔案(file)、目錄(directory)、病毒(virus)、蠕蟲(worm)、垃圾(garbage)等英語單詞被用作計算機領域中的技術術語。你可能不知道除了目前的非技術含義之外,特定術語還附帶了一些技術含義。為避免混淆,請始終在桌面上備一本好的計算機詞典。無論何時有疑問,都請查詞典。

1.6 編譯和解釋語言

當計算機科學家設計新的程式設計語言時,主要問題是在各種平台上實作該語言。實作語言有兩種基本方法:

□編譯:進階語言的代碼被翻譯成低級語言。建立一個檔案來存儲編譯或翻譯後的代碼。然後,你需要通過提供适當的指令來執行已編譯的代碼。

□解釋:代碼中的指令由虛拟機(或解釋器)逐條解釋(執行)。不建立檔案。

現在詳細讨論這兩種方法。

編譯

在編譯方法中,進階語言的源代碼被翻譯成實際機器的機器語言。FORTRAN、Pascal、Ada、PL/1、COBOL、C和C++都是編譯語言。例如,考慮一個在螢幕上顯示文本“Hello”的C程式。假設hello.c是包含此程式源代碼的檔案(C源代碼檔案的擴充名為.c)。C編譯器編譯(或翻譯)源代碼并生成可執行檔案hello.exe。檔案hello.exe包含實際機器的機器語言指令。你現在需要通過提供适當的指令來執行檔案hello.exe,并且執行檔案hello.exe不是編譯過程的一部分。在Windows平台上準備的可執行檔案hello.exe隻能在Windows平台上執行,根本無法在UNIX平台或Linux平台上執行此檔案。但是,可以使用适用于所有平台的C編譯器。是以,可以在UNIX或Linux平台上加載适當的C編譯器編譯檔案hello.c,以生成可執行檔案hello.exe,然後在該平台上執行它。

編譯語言的主要好處是編譯程式的執行速度很快。編譯語言的主要缺點是程式的可執行版本依賴于平台。

解釋

在解釋方法中,通過添加期望數量的軟體層來建立虛拟機,使得進階語言的源代碼是該虛拟機的“機器語言代碼”。例如,BASIC語言是一種解釋語言。考慮一個在螢幕上顯示文本“Hello”的BASIC程式。假設此程式的源代碼存儲在hello.bas檔案中。hello.bas中的源代碼被送到BASIC虛拟機,并且BASIC虛拟機逐條解釋(執行)hello.bas中的指令。另請注意,hello.bas中的程式設計語句是BASIC虛拟機的機器語言指令。在解釋過程中不會建立新檔案。

解釋語言的主要好處是程式與平台無關。解釋語言的主要缺點是程式的解釋(執行)很慢。BASIC、LISP、SNOBOL4、APL和Java都是解釋語言。

在實踐中,很少使用純粹的解釋,如BASIC的情況。在幾乎所有解釋語言(例如Java)中,都使用編譯和解釋的組合。首先,使用編譯器将進階語言中的源代碼轉換為中間級代碼。其次,建立虛拟機,使得上述中間級代碼是該虛拟機的機器語言代碼。然後将中間級代碼送到虛拟機以進行解釋(執行)。最後,請注意所有腳本語言(例如Perl、JavaScript、VBScript、AppleScript等)都是純粹的解釋語言。

1.7 第一個C程式

作為一種傳統,典型的C程式設計書中的第一個程式通常是“Hello, world”程式。我們遵循這一傳統,建立并運作(執行)第一個程式。此程式将在螢幕上顯示“Hello, world”文本。在C檔案中鍵入以下文本(程式)并将其儲存在檔案夾C:Code中名為hello.c的檔案中:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

編譯并執行此程式,螢幕上會顯示以下文本:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

如果語言的編譯器或解釋器區分大寫和小寫字母,則稱該語言為區分大小寫的。Pascal和BASIC不是區分大小寫的語言。C和C++是區分大小寫的語言。

□C是區分大小寫的語言,是以不應混淆大寫和小寫字母。例如,如果鍵入Main而不是main,則會導緻錯誤。

□不要混淆檔案名和程式名。這裡,hello.c是包含程式源代碼的檔案的名稱,而hello是程式名稱。

為了解釋這個程式(或者任何其他程式)是如何工作的,需要引用這個程式中的代碼行(LOC),是以,需要對代碼行進行編号。我重寫了程式hello,其中添加了行号作為注釋(這些是多行注釋),如下所示。此程式産生與程式hello相同的輸出。

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

C中有兩種類型的注釋:多行注釋(也稱為塊注釋)和單行注釋(也稱為行注釋)。單行注釋來自C++,自C99起正式引入C語言。

現在注意用插入單行注釋重寫的程式hello,如下所示。此程式産生與程式hello相同的輸出。

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

傳統上,C語言教科書僅使用多行注釋并避免單行注釋。我将在本書中遵循這一慣例。

1.8 C的突出特點

C是一種流行語言。它大受歡迎歸功于以下功能:

□C是一種小型語言。它隻有32個關鍵字。是以,可以很快學會它。

□它具有強大的内置函數庫。

□它是一種可移植的語言。為一種平台(例如,Windows)編寫的C程式可以移植到另一種具有微小變化的平台(例如,Solaris)。

□C程式執行速度快。是以,C程式用在效率很重要的地方。

□結構化程式設計所需的所有構造都可以在C中獲得。

□C語言中提供了低級程式設計所需的大量構造,是以C可用于系統程式設計。

□C語言中提供指針,這增強了它的功能。

□在C中遞歸功能可用于解決棘手的問題。

□C具有擴充自身的能力。程式員可以将自己編寫的函數添加到函數庫中。

□C幾乎是一種強類型語言。

1.9 隐式類型轉換

在指派語句中,右側顯示的量稱為右值(r-value),左側顯示的量稱為左值(l-value)。在每個指派語句中,都要確定左值的資料類型與右值的資料類型相同。有關示例請參閱此處給出的指派語句(假設intN為int變量):

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

這裡,L1表示LOC 1,為了節省空間,我使用字母L來表示代碼中的LOC。在LOC 1中,左值為intN,右值為350,它們的資料類型是相同的:int。當編譯器編譯這樣的語句時,它從不會忘記檢查指派語句兩邊的類型。編譯器的這個任務稱為類型檢查(typechecking)。如果雙方的類型不一樣會怎樣?會發生類型轉換!在類型轉換中,右側的值的類型在指派之前被更改為左側的值的類型。類型轉換可以分為兩類。

□隐式或自動類型轉換

□顯式類型轉換

注意這裡給出的LOC(假設dblN是double變量):

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在此LOC中,dblN的類型為double,數值常量35的類型為int。這裡,編譯器将資料類型35從int(源類型)提升為double(目标類型),然後将double類型常量35.000000賦給dblN。這稱為隐式類型轉換或自動類型轉換。在隐式(或自動)類型轉換中,類型轉換是自動發生的。

在類型轉換中,右值的類型稱為源類型,左值的類型稱為目标類型。如果目标類型的範圍寬于源類型的範圍,則此類型的轉換稱為擴大類型轉換。如果目标類型的範圍窄于源類型的範圍,則此類型的轉換稱為縮小類型轉換。LOC 2中的類型轉換是擴大類型轉換,因為double(目标類型)的範圍比int(源類型)的範圍寬。

這是隐式類型轉換的另一個例子(假設intN是int變量):

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在此LOC中,數字常量14.85的類型是double,intN的類型是int。這裡,編譯器将14.85的資料類型從double降級為int,它截斷并丢棄其小數部分,然後将整數部分14指派給intN。LOC 3中的類型轉換是縮小類型轉換。

這是隐式類型轉換的另一個例子:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在此LOC中,右值是一個表達式,該表達式又由數值常量2除以數值常量4.0組成。但是數值常量2的類型是int,而數值常量4.0的類型是double。這裡,編譯器将數值常量2的類型從int提升為double,然後執行浮點數2.0 / 4.0的除法。結果0.5被指派給dblN。

■注意 在表達式或指派語句中混合使用不同類型時,編譯器會在計算表達式或指派時執行自動類型轉換。在執行類型轉換時,編譯器會盡力防止資訊丢失。但有時候資訊丢失是不可避免的。

例如,在LOC 3中,存在資訊丢失(double類型數值常量14.85轉換為int類型數值常量14)。在擴大類型轉換中沒有資訊丢失,但在縮小類型轉換時存在一些資訊丢失。編譯器總是允許擴大類型轉換。編譯器也允許縮小類型轉換,但有時編譯器會顯示警告。不允許無意義的轉換。某些類型轉換在編譯期間允許,但在運作期間會報告錯誤。例如,請注意這裡給出的代碼段:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

編譯器成功編譯了這段代碼,沒有任何警告。但是,當你執行這段代碼時,螢幕上會顯示以下文本行而不是預期的輸出:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

程式在執行LOC M期間“崩潰”,這行代碼嘗試縮小類型轉換。當一個程式在運作時突然終止時,我們用程式員的語言說程式崩潰了。

不同的語言允許将類型混合到不同的程度。允許不受限制地混合不同類型的語言稱為弱類型的(weakly typed)語言或具有弱類型的語言。不允許混合不同類型的語言稱為強類型的(strongly typed)語言或具有強類型的語言。

■注意 C幾乎是一種強類型語言。

C的強類型檢查在函數調用中很明顯。如果函數需要一個int類型參數,并且将一個字元串作為參數(而不是int類型參數)傳遞給該函數,那麼編譯器會報告錯誤并停止編譯程式,這證明了C是一種強類型語言。

請注意,前面使用了幾乎這個術語,因為在某種程度上,C語言中允許隐式類型轉換,這使得C成為“幾乎”強類型的語言,而不是完全強類型的語言。

1.10 顯式類型轉換

你可以顯式執行類型轉換,而不是讓類型轉換完全依賴編譯器。此操作稱為顯式類型轉換、強制轉換或強制。強制轉換中使用的運算符稱為強制轉換運算符。注意這裡給出的LOC(假設intN是一個int變量):

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在此LOC中,對數值常量14.85執行強制轉換操作。強制轉換運算符是“(int)”。在此操作中,14.85的類型從double被更改為int,其小數部分被截斷并丢棄,整數部分14作為int類型的數值常量傳回,而int又被指派給intN。以下是強制轉換操作或顯式類型轉換的通用文法:

(desiredType) 表達式

這裡,desiredType是任何有效的類型,如char、short int、int、long int、float、double等。在這種文法中,強制轉換運算符是“(desiredType)”。請注意,括号是必需的,并且是轉換運算符的一部分。此轉換操作的效果是将表達式的類型更改為desiredType。

在LOC 1中,強制轉換操作對數值常量執行,但是它也可以對變量執行。注意這裡給出的代碼片段:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

執行後,這段代碼在螢幕上顯示以下文本行:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在這段代碼中,對變量dblN執行了兩次強制轉換操作,第一次在LOC 4中執行,第二次在LOC 7中執行。請注意,在dblN上執行轉換操作後,dblN的值不受影響。實際上,強制轉換操作不在dblN上執行,存儲在dblN中的值被擷取,然後對該擷取的值(即在數值常量3.7上)執行強制轉換操作。也是以,在LOC 4中使用運算符“(int)”對dblN執行強制轉換操作後,變量dblN在執行LOC 6後仍然不受影響。LOC 6的執行将dblN的值顯示為3.7。在LOC 7中,printf()函數的參數不是變量而是表達式,如下所示:

帶你讀《C程式設計技巧:117個問題解決方案示例》之一:歡迎學習C語言第1章

在這一章裡,我們讨論了與C語言相關的各種問題。在本書的其餘章節中,你将看到所有的C技巧。本書的目的是為你提供現成的解決方案,在本書中,你還可以找到滿足各種水準讀者需求的現成解決方案。

繼續閱讀