2011年12月14日 星期三

C 語言入門 - 使用者輸入與變數宣告

如何接收使用者的輸入?

  在談這個話題之前,我們必須先想想,使用者輸入的資料要儲存在哪?如果不先找個地方放著,我們如何使用?所以我們先介紹如何儲存資料。要儲存東西必須要有容器,現在我們來看看如何變出一些容器,來儲存我們想存的東西。


作為容器的變數及其宣告方式

  程式在執行的時候,肯定會有各種各樣的資料需要暫時存放著。程式結束後可能就沒用了,但是在執行的過程卻是必須的。例如我們必須把使用者輸入的資料存起來,否則讀取完輸入後,也不知道到哪兒去了,如何去使用它?就像人腦,只是路上看過一塊招牌而沒多留意,一般而言是不會記下來的,之後要回想也想不起來。如果不先把運算結果存起來,像是在腦中算完就馬上忘了,輸出時又豈知該輸出的結果是什麼?

  舉個例,像是向對方要電話號碼時,如果沒有紙筆可以記錄,至少也會默唸個數十次來記住它。相同的,程式在讀取使用者輸入時,也必須將它記憶起來,才能使用。而儲存這樣子的記憶,就是作為容器的「變數」的工作。我們可以用下列的方式宣告變數,變數在經過宣告後就會成為實際可用的容器。日後會提及更多關於變數的事情,現在先來談談如何宣告。

int a, b;

  其中 int 是表示變數的類型為整數 (integer),若想宣告其它類型的變數,則將 int 置換成其它類型即可。在此先不提其它還有哪些類型。變數的類型稱為「型態」。之後是宣告 int 型態的變數 a 和 b,中間以逗號隔開,最後以分號結束這個部份,表示宣告已經告一段落。注意 C 語言是不在意換行的,所以必須要有分號,表示這個部份告一段落了。否則如果寫成這樣:

int a, b
printf("kinniku YEAH YEAH\n")

  編譯器會認為你在宣告完 a 和 b 之後因為沒有分號,表示宣告還沒結束,但是 b 和後面的 printf 之間沒有逗號,它會以為你的 printf() 是要對變數 b 的宣告做出修飾,卻又不是語法規定的修飾用字,格式也不符,但是又不能擅自猜測你的意思,所以會產生編譯錯誤,要求你按照語法寫得清楚明白,否則它無法解讀。

  現在我們知道如何宣告 int 型態的變數了,所以我們有能力讀取使用者所輸入的整數了。那麼,立刻來試試看吧。


讀取使用者的輸入並輸出

  這裡我們試著讀取使用者輸入的整數,然後把它輸出在畫面上。你可能會想這怎麼辦得到,我們又不知道使用者會輸入什麼,怎麼知道如何輸出?沒關係,如果我們成功讀取並儲存起來的話,就能夠直接拿來使用,也就知道該輸出什麼了。

int a, b;
scanf("%d%d", &a, &b);

  新函式出現了。我們可以先和他握個手,交個朋友。有了上次 printf() 的經驗,應該能夠從外觀了解到,它的名字是 scanf 而且是個函式,而我們傳遞了一些參數給它,讓它知道我們希望它幫我們什麼忙。我想這不難猜測到,我們希望它做的事情,是讀取使用者輸入的兩個整數,然後將讀取到的整數分別儲存至 a 和 b 這兩個變數中。可是我們怎麼知道到底有沒有正常運作?沒關係,先讓我們輸出看看。

#include <stdio.h>

int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d %d\n", a, b);
    while(1);
    return 0;
}

  試著執行看看,然後輸入兩個整數,中間以 ENTER 或是空白鍵隔開。輸入完按下 ENTER 看看結果如何。請最多輸入 9 位數就好。沒有意外的話,應該會正確地顯示出你剛剛輸入的兩個數字,並且以空白隔開才對。是不是很神奇啊?你的程式真的知道你輸入了什麼。你可能還不了解這些程式碼的意思,不過至少它會動了。接下來只要慢慢地來了解它們就可以了。

  通常我們會先有個目的,再去想辦法把程式寫出來,達成我們的目的。我們希望可以讓使用者輸入兩個整數,然後為了確認結果,輸出讀取到的兩個整數。這是我們所期待的功能。所以我們思考,要怎麼樣利用我們所學會的程式碼,達成這樣的目的。首先必須思考解決這個問題的流程,再來思考各項細節。記住程式碼是依序執行的,所以它的運作流程,也就是先後順序相當重要。

  我們希望讀入兩個整數,所以必須有 scanf()。讀入的數必須有地方存起來,所以必須先宣告變數。我們希望知道是否有正確運作,也就是將讀到的整數輸出,讓我們可以確認,所以需要 printf() 輸出。同時我們希望看到結果而不要過早關閉程式,所以要用 while(1) 讓它卡住。那麼我們怎麼安排它們的順序?

  scanf() 在使用時需要先有變數儲存讀取到的結果,所以在 scanf() 之前必須宣告變數。printf() 要輸出的是我們讀取到的整數,因為它的目的,只是確認我們是否正確讀到。因此它一定是放在讀取完輸入之後,將讀取的整數輸出。如果放在讀取輸入之前,就失去它的存在意義了。而 while(1) 則必須在其它步驟全部執行完之後才能執行,因為這些都是必要的動作,但 while(1) 會卡死整個程式的執行。它只是用來讓程式停下來,以免在做完動作後自動關掉,沒時間讓我們看輸出的結果。為了避免在執行完所有動作之前,程式執行就被卡死,我們得把它擺最後面。

  因此得到的唯一合理順序,就如上面所述那樣了。我們可以試著交換任意兩行的先後次序試試,會發現光是流程上就相當不合邏輯,雖然這可能讓解讀錯誤的程式碼意外地有趣,也就是變成了不錯的笑話。這同時也告訴我們,除了正確地分析目的並找到合適的程式碼以外,正確的執行次序也是相當重要的。

  接下來則是來看看我們的宣告和 scanf()、printf() 要怎麼用。首先我們目的是兩個整數,那麼最少要宣告兩個 int 型態的變數,先暫時命名為 a 和 b,到這裡沒什麼問題。

  scanf() 則要先傳遞第一個參數,告訴它我們要讀取兩個整數。我們必須告訴它一個字串,讓它知道我們預期讀取到什麼樣的輸入。因為是字串所以用雙引號 "" 括起來。我們希望兩個整數,而且並非預期特定的兩個整數,所以使用 %d 來代表整數,這是約定。因為我們必須告訴它,預期讀取的是非特定的兩個整數,這不好表達,所以必須約定一個雙方都看得懂的暗語。因為要兩個所以寫兩個 %d 上去。

  現在我們傳遞了第一個參數,scanf() 已經知道我們想要讀取什麼了。但是它還不知道我們希望讀取到的整數被存放在哪兒,所以會希望我們附上。否則它到時會感到不知所措,既希望完成任務,又不敢隨便亂動。程式當掉而自動關閉時,通常是有非做不可的事,又不知道該怎麼做,只好以死明志。無法完成任務就自殺謝罪。為了避免這樣的慘劇,我們把 &a 和 &b 作為錦囊傳進去,並且以逗號「,」隔開,以免混淆。等到它遇到第一個疑問時就會打開第一個錦囊,看到 &a,然後把第一個整數放進去。遇到第二個時會打開第二個,所以先輸入的會放在 a,後輸入的放在 b。

  接下來的 printf() 也和 scanf() 非常相似。我們也不希望輸出特定的字串,而是會因應情況變動的整數。幸運的地 printf() 和 scanf() 非常要好,他們使用同樣的約定與暗語,所以我們也用 "%d %d\n" 來輸出,中間的空白是怕兩個整數黏在一起輸出,就變成一個整數了。理所當然地 printf() 不是我們,所以不會知道我們心中想著誰,自然不會知道我們想輸出什麼樣的整數,只知道是整數而已。為了避免它因為無法完成任務而尋死,我們必須告訴它是哪兩個。於是我們把 a 和 b 也作為錦囊傳進去,等到 printf() 輸出到第一個 %d 而開始不知所措時,打開第一個救命錦囊就會知道該怎麼做了。同樣地按照順序會先輸出 a 然後輸出空白,再來輸出 b 然後按照你的要求幫你換行。

  你可能會覺得很奇怪,為什麼 scanf() 要用 &a 和 &b 而 printf() 卻是用 a 和 b 呢?因為它們預期的錦囊內容不一樣,解讀方式也不一樣。在變數前面加上 & 表示我們要的不是變數自身的值,而是變數的家。變數是儲存用的容器,但容器也會有放置的地點。讀入時並不會去關心變數裡有著什麼,而是關心這個變數它到底在哪裡,好找到這個容器並置換存放的東西。所以我們要用 &a 將 a 的位置傳遞過去,而不是用 a 將 a 的值給傳遞過去。

  光是知道值並不會知道確切的容器位置,因為可能有其它容器存放著相同的東西。何況它預期你告訴它的是「位置」而不是「值」,所以就算告訴它「值」它也會將其作為「位置」解讀,然後產生誤會。如果解讀後是不存在的地址,或是該地址是去放東西會被守衛砍死的地方,程式就只好死給你看了。

  相對的 printf() 的 %d 預期是被告知一個整數,所以告知它位置是不對的。它也會將「位置」作為「值」來解讀,雖然仍然能解讀出來,但是很難是我們所預期的結果。對於 C 語言來說「位址」和「值」是沒有差別的,所以也無法做出它是「位址」或是「值」的判斷。這點在日後關於記憶體或指標的文章中會再次提及。

  現在你應該對 scanf() 和 printf() 有更多的了解了,想必也比較了解如何與它們溝通,以及溝通時的暗語了。你可以多試著和它們玩玩,指派任務給它們,然後看看是否有正確傳達並執行。例如輸入兩個整數,然後按照輸入先後順序,反過來輸出,或是試著讀入三個整數等等。多嘗試一些突發奇想,有時可以幫助你更了解它們,也能累積更多經驗。

1 則留言: