天天看點

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

點選檢視第一章 點選檢視第三章

第2章

C語言快速入門

2.1 C語言的文法特點

C語言是一門文法精簡的語言,它的關鍵字僅有32個,C語言以main函數為主函數,程式編譯運作之後,執行的就是main函數的内容,是以,縱觀C語言的很多程式,就會發現它們形成了一道有趣的風景線:頭檔案和C代碼檔案以main函數為中心構造,在main函數中調用這些檔案中編寫的代碼,引用頭檔案。C語言程式實質上就是在程式中調用 C标準庫提供的函數、其他C庫提供的函數、作業系統提供的API接口、自己定義的函數,同時應用适當的資料結構和算法來完成工作。C語言主要包含如下關鍵字。

  • auto:聲明自動變量。
  • short:聲明短整型變量或函數。
  • int: 聲明整型變量或函數。
  • long:聲明長整型變量或函數。
  • float:聲明浮點型變量或函數。
  • double:聲明雙精度變量或函數。
  • char:聲明字元型變量或函數。
  • struct:聲明結構體變量或函數。
  • union:聲明共用資料類型。
  • enum:聲明枚舉類型。
  • typedef:用以為資料類型取别名。
  • const:聲明隻讀變量。
  • unsigned:聲明無符号類型變量或函數。
  • signed:聲明有符号類型變量或函數。
  • extern:聲明變量是在其他檔案中聲明的。
  • register:聲明寄存器變量。
  • static:聲明靜态變量。
  • volatile:聲明變量在程式執行中可被隐含地改變。
  • void:聲明函數無傳回值或無參數,聲明無類型指針。
  • if:條件語句。
  • else:條件語句否定分支(與if連用)。
  • switch:用于開關語句
  • case:開關語句分支。
  • for:一種循環語句。
  • do:循環語句的循環體。
  • while:循環語句的循環條件。
  • goto:無條件跳轉語句。
  • continue:結束目前循環,開始下一輪循環。
  • break:跳出目前循環。
  • default:開關語句中的“其他”分支。
  • sizeof:計算資料類型長度。
  • return:子程式傳回語句(可以帶參數,也可以不帶參數)循環條件。

C語言雖然精簡,但功能卻很強大,其不但能夠完成比它更複雜的程式語言所做的事情,而且還能做其他語言不擅長的工作,比如,MySQL(當今世界最流行的開源關系型資料庫管理系統)、Nginx(高性能的 HTTP 和 反向代理伺服器)、SQLite(嵌入式的輕型資料庫)、GNOME桌面(通常運作在Linux/UNIX系統下的桌面系統)、OpenCV(跨平台計算機視覺庫)等都是C語言的傑作,尤其是在作業系統核心的設計與研發領域,它的“兄弟”C++也不是對手(目前還沒有出現一款流行于世的C++制作的作業系統核心)。

2.2 猜數字遊戲

本節将應用C語言制作猜數字遊戲,以幫助讀者快速複習C語言的基礎知識。猜數字遊戲的規則具體如下:輸入一個1-500以内的正整數,程式根據玩家輸入的數字,提示該數字比正确答案大,或者比正确答案小,如果等于正确答案就提示猜中了。比如,要猜的數字是85,玩家第一次輸入90,則提示比要猜的數字大,第二次輸入80,則提示比要猜的數字小,第三次輸入85,則提示猜中了。下面就來分步講解整個遊戲的制作過程。

2.2.1 編寫輸入數字的C代碼

首先,參照第1章介紹的編輯C程式的方法,編寫源代碼檔案2-1.c,代碼如程式2-1所示:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

接着,使用PuTTY等SSH用戶端登入Ubuntu後,在終端編譯2-1.c:

$ gcc 2-1.c -o myguess

最後,運作該程式進行驗證,程式接受一個數字輸入後,将輸入的數字輸出到螢幕中,代碼如下:

$ ./myguess

你好,請輸入一個數字:55

你輸入的數字是:55

縱觀程式2-1及其執行結果,可以發現,C語言使用“;”作為語句的結尾;可使用printf函數完成螢幕輸出,在輸出時可使用“n”表示換行符;使用scanf函數從鍵盤中接受指定格式的資料錄入,其中“%d”表示整數格式,scanf的第2個參數是輸入的變量的位址(即&mynum,其中“&”是取位址符)。

2.2.2 限制輸入數字的範圍

遊戲要求輸入1-500以内的整數,但在運作程式2-1時,輸入900、-10等不符合要求的數字,仍然能夠通過。比如,在下面所示的運作結果中,900和-10均通過了輸入程式的測試:

你好,請輸入一個數字:900

你輸入的數字是:900

你好,請輸入一個數字:-10

你輸入的數字是:-10

本步驟的目标就是讓程式拒絕接受不合法的數字,并提示玩家重新輸入,是以,需要修改程式,限制玩家輸入數字的範圍,修改的代碼如程式2-2所示:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-2,從下述運作效果來看,錯誤的數字并沒有被接受,但要想重新輸入,必須再運作一次程式:

$ gcc -o myguess 2-2.c

$ ./mynum

你好,請輸入一個數字:988

數字僅限于1-500之間,請重新運作本程式!

程式2-2使用了C語言的“if...else...”條件語句,這是很多語言都有的一個機制(包括一些函數語言,比如Haskell的“if...then...else....”),“if…else…”條件語句分為兩個部分:第1個部分是if語句段,表示如果if後面所跟的條件滿足要求的話,則執行if語句段; 第2個部分是else語句段,表示如果if後的條件不滿足要求時執行的語句段。

什麼是條件滿足與不滿足呢?C語言可了解為:如果條件的傳回值非0則表示條件滿足,如果是0則表示條件不滿足。

可以将多個條件組合成一個綜合條件作為“if...else...”條件語句的條件,方式是使用“||”(表示或者)或“&&”(表示并且),比如,程式2-2的條件是“mynum>500 ||mynum<1”。

用非0與0來判斷條件的真假讓C語言的條件語句具備較強的靈活性,但是這會帶來一個困擾:在C語言條件語句中,NULL和0的值是一樣的,而NULL常用于指針和對象,0常用于int等整型數,這就意味着,如果出現了類似下面的語句塊,則是對含有指針變量的條件進行判斷。例如,在下面這種形式的代碼中,mypoint指向了其他變量的記憶體位址,如果指針變量mypoint指向的位址為NULL,則表示mypoint指向的位址是無效的,否則是有效的。示例代碼如下:

if (mypoint!=NULL){

............//指針指向内容有效時執行的語句塊

}

else{

............ .//指針指向内容無效時執行的語句塊

也可以在條件中使用“!”(表示邏輯非),進一步簡化對mypoint是否有效的判斷,示例代碼如下:

if (!mypoint){

2.2.3 引入循環機制,允許重新輸入

循環是計算機科學運算領域的用語,也是一種常見的控制流程,循環是指一段代碼在程式中隻出現一次,但可能會連續運作多次的語句。通過C語言的循環語句,程式2-3實作了在玩家輸入錯誤數字的情況下,可再次輸入,而不是直接退出程式的功能:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-3,當輸入的數字不合法時(分别輸入了1234、-12進行測試),程式沒有接受玩家輸入,而是提示重新輸入數字,運作結果如下:

$ gcc 2-3.c -o myguess

你好,請輸入一個數字:1234

數字僅限于1-500之間

你好,請輸入一個數字:-12

你好,請輸入一個數字:88

你輸入的數字是:88

程式2-3中使用了C語言的while循環來實作玩家反複輸入數字,直到輸入的數字在1-500之内才退出循環的功能。

while循環的文法格式如下:

while(條件){

語句塊

此外,程式2-3在while的條件“while (!ispass)”中使用了邏輯“!”,簡化了程式。

C語言還有另一種循環形式“do...while...”循環,它的文法格式如下(不要忘記在最後一行的條件後加上“;”):

do{

}while (條件);

用“do…while...”循環對程式2-3進行修改,修改後的代碼如程式2-4所示:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-4,下面是運作結果,當輸入8889時,程式會提示數字不合法,并讓玩家重新輸入:

$gcc 2-4.c -o myguess

你好,請輸入一個數字:8889

你好,請輸入一個數字:12

你輸入的數字是:12

程式2-3、程式2-4使用了變量ispass作為是否退出循環的依據,如果ispasss不為真,則表示玩家輸入的數字不合法,需要繼續輸入,否則退出循環。

除了使用ispass這類變量控制C語言循環之外,還可以直接通過break語句退出循環(注意,隻能退出break語句本身所在的那層循環)。通過break語句的使用,程式2-5完成了與程式2-3、程式2-4同樣的功能,程式代碼如下:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯運作程式2-5,從下面的運作結果可以看出,程式2-5與程式2-3以及程式2-4的功能一樣,程式在玩家輸入錯誤的情況下,提示玩家重新輸入,具體如下:

$gcc 2-5.c -o myguess

你好,請輸入一個數字:8788

你好,請輸入一個數字:66

你輸入的數字是:66

2.2.4 産生1~500以内的随機整數

為增加遊戲的趣味性,編寫代碼産生1~500以内的随機整數,并将這個整數作為被猜數字,這樣玩家每次運作遊戲,需要猜的都是不同的數字。為保證需要猜的整數在1~500之間,需要按如下方式對随機整數進行加工(“%”為取餘操作符):

1~500以内的被猜數字=随機整數%499+1

借助stdlib.h中定義的srand函數來生成公式右邊所需要的随機數,該函數需要一個數值作為産生随機數的種子(也就是這個函數的唯一參數),通常使用目前時間值作為參數,目前時間值可以通過time函數(以0作為參數調用,該函數定義于time.h中)生成。程式2-6通過srand函數生成随機數,代碼如下:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-6,運作結果表示,程式産生了1~500以内的2個随機整數429與44:

$gcc a.c -o mytest

$./mytest

第一個随機數:429 第二個随機數:44

可将程式2-6中的代碼稍做修改,與程式2-5結合,将程式2-6中産生随機數的代碼定義為函數getnumber,以供main函數調用,最終代碼如程式2-7所示:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-7,觀察以下運作結果,玩家猜測數字為55,最後一行輸出了被猜的數字為109。

$ gcc guessnum.c -o myguess

number:109

為了驗證随機數效果,程式2-7中最後一個printf語句擷取到要猜的随機整數,并輸出到螢幕,但遊戲中不能把結果告訴玩家,是以,接下來需要繼續完善程式2-7,加入更多的功能。

2.2.5 反複接收玩家輸入,直到猜中數字為止

C程式通過“if…else if…else…”語句塊來實作條件語句的組合,該組合中包含有多個不同的條件,可用于定義滿足各個條件時執行的代碼塊。語句塊格式如下:

if (條件1){

.........//條件1滿足時執行的代碼塊

else if(條件2){

.......... //條件2滿足時執行的代碼塊

..........

else if(條件n){

......... //條件n滿足時執行的代碼塊

......... //以上所有條件均不滿足時執行的代碼塊

在程式2-7中增加條件語句組合,改進猜數字遊戲,實作玩家輸入的數字與被猜數字的比較,并根據比較結果為玩家提示數字大了或小了的資訊,當玩家輸入的數字與被猜數字相同時,提示玩家猜中了,修改後的代碼如程式2-8所示:

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯并運作程式2-8,玩家輸入數字之後,程式提示輸入的數字與被猜的數字相比是大了還是小了,進行幾次嘗試之後,玩家成功猜中數字,程式提示“祝賀您,您猜中了!”,運作結果如下所示:

數字小了!

你好,請輸入一個數字:280

你輸入的數字是:280

你好,請輸入一個數字:350

你輸入的數字是:350

你好,請輸入一個數字:400

你輸入的數字是:400

你好,請輸入一個數字:488

你輸入的數字是:488

數字大了!

你好,請輸入一個數字:420

你輸入的數字是:420

你好,請輸入一個數字:450

你輸入的數字是:450

你好,請輸入一個數字:440

你輸入的數字是:440

你好,請輸入一個數字:430

你輸入的數字是:430

祝賀您,您猜中了!

2.2.6 自動猜數算法

能不能讓電腦程式擁有智能,讓程式來猜數字呢?肯定可以,隻需要編寫C程式實作某種算法即可。在國内,算法最早出現在《周髀算經》 《九章算術》之中;在國外,古希臘數學家歐幾裡得(Euclid,約公元前325年—公元前265年)出版了《幾何原本》,聞名于世。人類曆史上第一次将算法編寫為程式的是Ada Byron,其于1842年為巴貝奇分析機編寫求解伯努利微分方程的程式,Ada Byronl也是以被大多數人認為是世界上第一位程式員。

算法的核心是建立問題抽象的模型和明确求解目标,之後可以根據具體的問題選擇不同的模式和方法完成算法的設計。為了能讓程式實作自動猜數,必須假設一個前提:程式不知道要猜的數字,也就是說這個算法中隻能與要猜的數字進行比較,而不能直接“知道”要猜的數字值。分析算法目标,可使用類似于折半查找法的算法,折半查找法又稱二分查找法,是一種在有序數組中查找某一特定元素的搜尋算法。查找過程從數組的中間元素開始,如果中間元素正好是要查找的元素,則搜素過程結束;如果某一特定元素大于或者小于中間元素,則在數組大于或小于中間元素的那一半中查找,而且跟開始一樣從新的中間元素開始進行比較。如果在某一步驟中數組為空,則代表找不到。這種搜尋算法每進行一次比較都會使搜尋範圍縮小一半。

例如,在一個升序排列的數字清單2、6、7、34、76、123、234、567、677、986中查找數字123所處的位置,算法過程具體如下:首先,将first(下限)指向最小數字2的位置,last(上限)指向最大的數字986的位置,得到first的值為1,而last的值為10,計算位于它們的mid(中間位置)為6,數字為76;然後将76與123進行比較,發現123比76大,于是将first設為mid之後的位置(即6),last不變,按與上一步同樣的方法,計算first與last的mid為8,将last設為中間位置的前一位置;最後,再次計算新的mid,直到mid處的值等于123為止,這樣就能成功找到123的位置,處于數字清單的第6個位置。整個過程如圖2-1所示。

折半查找算法查找的範圍每次縮小一半,是以查找效率較高,我們可以借鑒這個思想,設計自動猜數算法:當輸入一個數字時,會得到一個回報,輸入的數字相對被猜數字是大了還是小了,将被猜數字作為查找目标,将1到輸入數字的範圍作為查找範圍,實作自動猜數。如果輸入數字大了,就将輸入數字作為查找範圍的上限,如果輸入數字小了,就将輸入數字作為查找範圍的下限,每輸入一次數字,就縮小了查找範圍的一半,這樣很快就能猜中,赢得遊戲的勝利,算法過程具體如下。

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

1)設數字範圍R為1~500。

2)取範圍R以内的中間值A,把A作為程式模仿人類猜測出的數字。

3)将猜測的數字A與被猜的結果B進行比較

a)如果A>B,則将R的上限設為A,回到第2步。

b)如果A<B,則将R的下限設為A,回到第2步。

c)如果A=B,則退出程式,提示猜中數字,進入第4步。

4)在螢幕上輸出A和B,并提示猜中數字。

帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章
帶你讀《C指針原理揭秘:基于底層實作機制》之二:C語言快速入門第2章

編譯後程式的運作結果如下,僅僅7次,程式就猜出了數字:

$gcc guessnum.c -o myguess

程式猜的數字為250,數字大了!

程式猜的數字為125,數字小了!

程式猜的數字為187,數字大了!

程式猜的數字為156,數字小了!

程式猜的數字為171,數字大了!

程式猜的數字為163,數字小了!

程式猜的數字為167,被猜的數字為167,猜中了!

程式2-9中使用了C語言的數組概念,數組的定義如下所示:

類型 數組名[數組長度]={用逗号分隔的數組初始值}

程式2-9中将myrange變量(表示猜數範圍)定義為一個包含2個元素(第1個元素1表示下限,500表示上限)的數組,如下面代碼所示:

int myrange[2]={1,500};

數組元素引用的方式是數組名[數組索引],其中數組索引從0開始。例如,程式2-10計算猜數時,通過數組索引形式取得myrange數組的上限與下限後,計算它們之間的平均值取得猜數範圍内的中間值,如下面代碼所示:

mynum=(myrange[0]+myrange[1])/2;

2.3 小結

C語言是一門文法精簡且功能強大的語言,其關鍵字僅有32個,它以main函數為中心,不但可以完成普通程式語言能做的工作,而且還可以做其他語言不擅長的工作,比如Linux核心、SQLite嵌入式資料庫,等等。本章以猜數字遊戲制作為例,依次介紹了基本輸入輸出、條件語句、循環語句、随機數生成等基本文法,最後以二分查找算法為基礎,講解了自動猜數算法,以幫助讀者快速學習C語言知識以及算法基礎。

繼續閱讀