C語言scanf函數用法完全攻略

2020-07-16 10:04:19
本節介紹輸入函數 scanf 的用法。scanf 和 printf 一樣,非常重要,而且用得非常多,所以一定要掌握。

概述

scanf 的功能用一句話來概括就是“通過鍵盤給程式中的變數賦值”。該函數的原型為:

# include <stdio.h>
int scanf(const char *format, ...);

它有兩種用法,或者說有兩種格式。

1) scanf("輸入控制符", 輸入引數);

功能:將從鍵盤輸入的字元轉化為“輸入控制符”所規定格式的資料,然後存入以輸入引數的值為地址的變數中。

下面給大家舉個例子:
#include <stdio.h>
int main(void)
{
    int i;
    i = 10;
    printf("i = %dn", i);
    return 0;
}
我們前面都是像這樣寫的,即直接給變數 i 賦一個值。但是這樣寫功能比較弱,因為這個值就變成一個“死值”了,它只能是 10,不可能是其他值,除非在程式中修改。很多時候我們希望這個值不是由程式設計師在程式中指定的,而是在程式執行的過程中由使用者從鍵盤輸入的。使用者輸入多少,變數i就是多少,這樣程式的功能就更加靈活了。

那麼如何實現在程式執行的過程中由使用者從鍵盤輸出值呢?用 scanf 即可實現:
# include <stdio.h>
int main(void)
{
    int i;
    scanf("%d", &i);  //&i 表示變數 i 的地址,&是取地址符
    printf("i = %dn", i);
    return 0;
}
“輸入控制符”和“輸出控制符”是一模一樣的。比如一個整型資料,通過 printf 輸出時用%d輸出,通過 scanf 輸入時同樣是用%d

要想將程式中的 scanf 行弄明白,首先要清楚的是:我們從鍵盤輸入的全部都是字元。比如從鍵盤輸入 123,它表示的並不是數位 123,而是字元 '1'、字元 '2' 和字元 '3'。這是為什麼呢?

作業系統核心就是這樣運作的。作業系統在接收鍵盤資料時都將它當成字元來接收的。這時就需要用“輸入控制符”將它轉化一下。%d的含義就是要將從鍵盤輸入的這些合法的字元轉化成一個十進位制數位。經過 %d 轉化完之後,字元 123 就是數位 123 了。

第二個要弄清楚的是:&是一個取地址運算子,&後面加變數名表示“該變數的地址”,所以&i就表示變數 i 的地址。&i又稱為“取地址i”,就相當於將資料存入以變數 i 的地址為地址的變數中。

那麼以變數 i 的地址為地址的變數是哪個變數呢?就是變數 i。所以程式中 scanf 的結果就把值 123 放到變數i中。

綜上所述,scanf 語句的意思就是:從鍵盤上輸入字元 123,然後%d將這三個字元轉化成十進位制數 123,最後通過“取地址 i”找到變數 i 的地址,再將數位 123 放到以變數 i 的地址為地址的變數中,即變數 i 中,所以最終的輸出結果就是i=123

注意,為什麼不直接說“放到變數i中”?而是說“放到以變數 i 的地址為地址的變數中”?因為這麼說雖然很繞口,但是能加強對 &i 的理解,這麼說更能表達 &i 的本質和內涵。很多人在學習 scanf 的時候,經常將“變數 i”和“變數 i 的地址”混淆,從而思維開始混亂,等深刻了解 &i 的含義之後就可以不那麼說了。

以上是 scanf 的最簡單用法,也是最常用、最基本、最重要的用法。這樣通過 scanf 就可以在程式執行的過程中由使用者來指定變數 i 的值,這與在程式中賦值相比較功能更強大。

2) scanf("輸入控制符非輸入控制符", 輸入引數);

這種用法幾乎是不用的,也建議你們永遠都不要用。但是經常有人問,為什麼 printf 中可以有“非輸出控制符”,而 scanf 中就不可以有“非輸入控制符”。事實上不是不可以有,而是沒有必要!下面來看一個程式:
# include <stdio.h>
int main(void)
{
    int i;
    scanf("i = %d", &i);
    printf("i = %dn", i);
    return 0;
}
在 printf 中,所有的“非輸出控制符”都要原樣輸出。同樣,在 scanf 中,所有的“非輸入控制符”都要原樣輸入。所以在輸入的時候i=必須要原樣輸入。比如要從鍵盤給變數 i 賦值 123,那麼必須要輸入i=123才正確,少一個都不行,否則就是錯誤。

所以 scanf 中%d後面也沒有必要加n,因為在 scanf 中n不起換行的作用。它不但什麼作用都沒有,你還要原樣將它輸入一遍。

所以在 scanf 的使用中一定要記住:雙引號內永遠都不要加“非輸入控制符”。除了“輸入控制符”之外,什麼都不要加,否則就是自找麻煩。而且對於使用者而言,肯定是輸入越簡單越好。

一次給多個變數賦值:
# include <stdio.h>
int main(void)
{
    int i, j;
    scanf("%d%d", &i, &j);
    printf("i = %d, j = %dn", i, j);
    return 0;
}
首先,scanf 中雙引號內除了“輸入控制符”之外不要加任何“非輸入控制符”。通過鍵盤給多個變數賦值與給一個變數賦值其實是一樣的。比如給兩個變數賦值就寫兩個 %d,然後“輸入引數”中對應寫上兩個“取地址變數”;給三個變數賦值就寫三個 %d,然後“輸入引數”中對應寫上三個“取地址變數”……

但是需要注意的是,雖然 scanf 中沒有加任何“非輸入控制符”,但是從鍵盤輸入資料時,給多個變數賦的值之間一定要用空格、回車或者 Tab 鍵隔開,用以區分是給不同變數賦的值。而且空格、回車或 Tab 鍵的數量不限,只要有就行。一般都使用一個空格。

此外強調一點:當用 scanf 從鍵盤給多個變數賦值時,scanf 中雙引號內多個“輸入控制符”之間千萬不要加逗號,

有些人覺得在輸入的時候可以用逗號分隔,所以就在“輸入控制符”之間用逗號隔開。這樣做從程式的角度確實是可以的,但是建議大家不要這樣做。在實際程式設計中這種寫法是絕對不允許的,原因有兩個:
  • 首先逗號要原樣輸入的,有幾個就要輸入幾個,少一個或多一個都不行;
  • 其次,也是最主要的原因就是輸入法的問題,在 scanf 中是在英文輸入法下寫的逗號,那麼輸入的時候如果是中文輸入法下的逗號那也是錯的。所以用逗號很容易出錯。

最後再次強調:scanf“輸入引數”的取地址符&千萬不要忘了。這是初學者經常犯的錯誤。而 printf 中的“輸出引數”是不帶取地址符的,不要混淆了。

使用scanf的注意事項

1) 引數的個數一定要對應


在前面介紹 printf 時說過,“輸出控制符”和“輸出引數”無論在“順序上”還是在“個數上”一定要一一對應。這句話同樣對 scanf 有效,即“輸入控制符”和“輸入引數”無論在“順序上”還是在“個數上”一定要一一對應。比如:
# include <stdio.h>
int main(void)
{
    char ch;
    int i;
    scanf("%c%d", &ch);
    printf("ch = %c, i = %dn", ch, i);
    return 0;
}
在 VC++ 6.0 中的輸出結果是:
a 6
ch = a, i = -858993460

這種錯誤是初學者經常犯的,由於粗心大意,少寫一個引數。更嚴重的是,這種錯誤在編譯的時候不會報錯。printf 也是一樣,即使“輸出引數”少寫了也不會報錯,但從程式的功能上講這麼寫就是錯的。所以在程式設計的時候一定要避免這種錯誤的發生。

程式中為什麼 i=–858993460?這個在《為什麼要初始化變數》中講過,當變數沒有初始化的時候就會輸出這個值。

在後面會講到 scanf 是緩衝輸入的,也就是說從鍵盤輸入的資料都會先存放在記憶體中的一個緩衝區。只有按確認鍵後 scanf 才會進入這個緩衝區和取資料,所取資料的個數取決於 scanf 中“輸入引數”的個數。所以上述程式中 scanf 只有一個輸入引數,因此按確認鍵後 scanf 只會取一個資料。所以變數 ch 有資料,而變數 i 沒有資料,沒有資料就是沒有初始化,輸出就是 –858993460。

2) 輸入的資料型別一定要與所需要的資料型別一致

在 printf 中,“輸出控制符”的型別可以與資料的型別不一致,如:
# include <stdio.h>
int main(void)
{
    int i = 97;
    printf("i = %cn", i);
    return 0;
}
在 VC++ 6.0 中的輸出結果是:
i = a

但是在 scanf 中,對於從鍵盤輸入的資料的型別、scanf 中“輸入控制符”的型別、變數所定義的型別,這三個型別一定要一致,否則就是錯的。雖然編譯的時候不會報錯,但從程式功能的角度講就是錯的,則無法實現我們需要的功能。比如:
# include <stdio.h>
int main(void)
{
    int i;
    scanf("%d", &i);
    printf("i = %dn", i);
    return 0;
}
在 VC++ 6.0 中的輸出結果是:
a
i = -858993460

輸出 –858993460 表示變數未初始化。為什麼輸入 a,變數 i 卻顯示未初始化呢?

在 scanf 中,從鍵盤輸入的一切資料,不管是數位、字母,還是空格、迴車、Tab 等字元,都會被當作資料存入緩衝區。儲存的順序是先輸入的排前面,後輸入的依次往後排。按確認鍵的時候 scanf 開始進入緩衝區取資料,從前往後依次取。

但 scanf 中 %d 只識別“十進位制整數”。對 %d 而言,空格、迴車、Tab 鍵都是區分資料與資料的分隔符。當 scanf 進入緩衝區中取資料的時候,如果 %d 遇到空格、迴車、Tab 鍵,那麼它並不取用,而是跳過繼續往後取後面的資料,直到取到“十進位制整數”為止。對於被跳過和取出的資料,系統會將它從緩衝區中釋放掉。未被跳過或取出的資料,系統會將它一直放在緩衝區中,直到下一個 scanf 來獲取。

但是如果 %d 遇到字母,那麼它不會跳過也不會取用,而是直接從緩衝區跳出。所以上面這個程式,雖然 scanf 進入緩衝區了,但使用者輸入的是字母 a,所以它什麼都沒取到就出來了,而變數 i 沒有值,即未初始化,所以輸出就是 –858993460。

但如果將 %d 換成 %c,那麼任何資料都會被當作一個字元,不管是數位還是空格、迴車、Tab 鍵它都會取回。

不但如此,前面講過,你從鍵盤輸入 123,這個不是數位 123,而是字元 '1'、字元 '2' 和字元 '3',它們依次排列在緩衝區中。因為每個字元變數 char 只能放一個字元。所以輸入“123”之後按回車,scanf 開始進入緩衝區,按照次序,先取字元 '1',如果還要取就再取字元 '2',以此類推。

如果都取完了還有 scanf 要取資料,那麼使用者就需要再輸入。先寫一個程式看一下:
# include <stdio.h>
int main(void)
{
    char i, j, k;
    scanf("%c%c%c", &i, &j, &k);
    printf("i = %c, j = %c, k = %cn", i, j, k);
    return 0;
}
在 VC++ 6.0 中的輸出結果是:
123
i = 1, j = 2, k = 3

從這個程式中我們看出,就單純地輸入 123,不加任何空格,按確認鍵之後就同我們所講的一樣,分別將字元 '1'、字元 '2' 和字元 '3' 賦給字元變數 i、j 和 k。

但是需要提醒大家注意的是,在之前程式中,因為 scanf 是 %d,所以 a 沒有被取出來,還在緩衝區中。當遇到下一個 scanf 是 %c 時它就會被取出來。但是如果一直沒有出現 %c,那麼這時就會出現一個問題:scanf怎麼取十進位制整數?即使使用 %d,但是由於字元 a “擋”在最前面,scanf 進去先碰到的總是 a,也就無法取到它後面的整數,所以必須先將 a“弄走”。這就牽涉到“清空輸入緩衝區”的概念,這個稍後再講。

3) 在使用 scanf 之前使用 printf 提示輸入

大家想一想,前面寫的 scanf 程式有沒有不足的地方?

程式寫好之後,編譯、連結、執行,然後彈出黑視窗,出現一個游標在那不停地閃。對於編寫程式的人來說他知道要輸入什麼,但是對於使用者而言,使用者怎麼知道是什麼意思呢?所以之前的程式都缺少提示資訊!因此在使用scanf之前,最好先用printf提示使用者以什麼樣的方式輸入,這樣可以大大提高程式碼的品質。看看下面這個程式:
# include <stdio.h>
int main(void)
{
    int i, j;
    printf("請輸入兩個值,中間以空格分隔:");
    scanf("%d%d", &i, &j);
    printf("i = %d, j = %dn", i, j);
    return 0;
}
這樣在執行的時候,使用者一看就知道是要輸入兩個值,然後中間用空格隔開。所以這樣寫就更人性化、智慧化了。

小結

scanf 的使用看似細節繁雜,但使用起來非常簡單。就目前而言,只要掌握以下五點:
  1. 在 scanf 的“輸入引數”中,變數前面的取地址符&不要忘記。
  2. scanf 中雙引號內,除了“輸入控制符”外什麼都不要寫。
  3. “輸出控制符”和“輸出引數”無論在“順序上”還是在“個數上”一定要一一對應。
  4. “輸入控制符”的型別和變數所定義的型別一定要一致。對於從鍵盤輸入的資料的型別,資料是使用者輸入的,程式設計師是無法決定的,所以在寫程式時要考慮容錯處理,這個稍後再講。
  5. 使用 scanf 之前先用 printf 提示輸入。

只要掌握了以上五點,scanf 的使用基本上就沒什麼問題了。至於其他注意點,到後面講陣列和指標的時候再介紹。