scanf 函式的真實含義及其正確用法

2021-09-29 17:23:56 字數 4501 閱讀 6058

規則0: 不要使用scanf()(除非你知道你在幹什麼)

下面是乙個常見的用法:

#include int a;

scanf("%d", &a);

printf("你輸入的數字是:%d\n", a);

你在寫這段**時大概應該知道「%d」是表示把輸入內容轉換為整數,所以你或許認為這個**是沒有問題的。確實,假如你輸入42,輸出結果的確為「你輸入的數字是:42」;但假如你輸入的是「abcdefgh」呢?你會發現結果居然是「38」。

38是從tm哪來的?答案是:如果你輸入非數字的話,那麼輸出的內容可能是任意值,甚至程式可能會直接崩潰掉。因為scanf()是用於轉換乙個數字,但使用者輸入的卻不是數字,所以它實質上沒有轉換任何東西。

因此,變數'a'壓根就沒有被執行寫操作,而我們在printf中試圖讀取乙個沒有被寫入初值的變數,這種行為顯然是非法的。

現在我們嘗試來修復上述錯誤。我們知道scanf()函式的返回值是被成功轉換的物件的個數,所以我們的第乙個想法是加入乙個複審的環節來防止使用者輸入一些別的東西。

while(scanf("%d", &a) != 1)

printf("你輸入的數字是 %d", a);

執行起來試一下,輸入abc:結果是「enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a number: enter a..."

嚇得趕緊用ctrl+c停止程式。為什麼會出現這種結果呢?

請記住下面這條規則:

規則1: scanf()不是用於讀入輸入的,而是用於解析輸入的。

scanf()的第乙個引數是乙個string,用來告訴scanf()應該解析什麼樣的東西。重點在於:scanf()不會讀入任何它無法解析的東西。在我們的例子中,我們使用了「%d」來告訴scanf去解析乙個數字。顯然,abc不是數字,因此,abc壓根沒有被讀入,仍然保留在輸入緩衝中。下一次迴圈,scanf()仍然能訪問到我們的未被讀入的abc,但再一次無法解析。

fflush(stdin);
求求你,千萬別試圖採用這種做法!在c語言中,flushing乙個輸入流是未定義的行為。所以,唯一能清除乙個未讀資料的方法就是把它讀掉。當然,我們可以採用乙個讀入任意內容的scanf()來把它讀掉,這應該很簡單。

另一種常見用法:

char name[12];

printf("what is your name?");

scanf("%s", name);

printf("your name is %s\n", name);

%s用於解析字串,所以輸入任何內容應該都奏效。

但上述**的問題在於容易造成溢位。scanf()並不知道什麼時候應該停止讀入,所以只要它還能解析,就會一直讀入並寫到name中,哪怕超出了宣告的name的大小也不停下。

因此,請記住下面這條規則:

規則2: 使用scanf()解析string時,請指定字串寬度。

char name[40];

scanf("%39s", name);

%39s會告訴scanf()至多從輸入中解析39個字元。注意,還得留乙個位置來存入\0字元,它表示字串結束。當scanf()結束解析時,會在被寫入的變數尾部自動加上\0。

再試一下,輸入matin brown,結果為:「your name is matin」

為什麼沒有讀入brown呢?我們翻一下手冊,會發現:%s其實只能解析乙個word,而不是乙個string。從輸入中解析乙個「string」的問題在於,它不知道string結束的標誌是什麼。如果使用%s的話,其實是告訴scanf()一旦遇到空白就停止解析。如果你想達到別的效果的話,請使用%[:

* %[a-z]:只要輸入位於a-z就繼續解析

*%[^.]:只要輸入中沒有.就繼續解析

所以記住下面這條:

規則3: 雖然scanf()的格式字串和printf()的很像,但常常有不同的含義(請閱讀其手冊)

所以你可以這樣寫:

scanf("%39[^\n]", name);
這樣,告訴scanf,只要輸入不是\n就繼續解析。所以只有輸入了回車才會停止解析。

但遺憾的是,上述**仍然有bug。假如你直接輸入回車鍵,會產生什麼效果呢?你會發現輸出結果是:「your name is y|n」,輸出了乙個莫名其妙的名字。

原因:手冊規定,使用了[ 指定轉換後,scanf()不會跳過前導空白,所以不會跳過我們輸入的\n符號;而它又不匹配我們指定的格式(我們的格式是:只解析非回車的字元)。所以最終的結果是,scanf()沒有解析任何東西,沒有向name中寫入任何內容。

一種解決辦法是,告訴scanf()跳過空白。

char name[40];

scanf(" %39[^\n]", name); //注意%39前的空白,它可以匹配任何空白

注:上面這段我不是很理解,我在mac上測試了一下,直接輸入回車並沒有發生錯誤。可能跟具體庫函式有關。

在c中用於讀入資料的函式有幾個,最常用的函式:fgets()

fgets()的功能很簡單,它讀入指定長度的字元,或者在遇到新行的時候停止(注意:會讀入換行符)。換言之,它讀入一行內容。

char* fgets(char* str, int n, file* stream)

這個函式有幾點好處:

1. 最大字元長度引數已經考慮了需要新增的\0,所以我們只需傳入我們的變數的長度40即可

2. 返回值是個指向str的指標,或指向null的指標(如果因某種原因沒有讀入任何東西)

現在可以重寫上面的**了:

char name[40];

if(fgets(name, 40, stdin))

但上述**還有點小問題,它在輸入!前會先換行。這其實是因為fgets()把換行符一起讀進來了。

我們可以使用string.h提供的strcspn()函式來獲取換行符在字串中的索引並用0來覆蓋。

if(fgets(name, 40, stdin))
在c中有很多函式可用於將字元轉換為數字。最常用的乙個是atoi()函式,名字的含義是anything to integer。它在轉換失敗時會返回0.

現在可以重寫之前的函式了:

int main()

//有一些輸入, 將其轉換為整數

a = atoi(buf);

} while(a == 0); //重複,直到我們得到乙個有效的數字

}

但atoi()的問題在於:

1)假如我們想輸入的就是0數字呢?我們無法區分atoi()返回的0到底是因為它就是讀入了0,還是因為它轉換失敗了返回的0.

2)假如輸入的是15xsd,那麼它會轉換得到數字15,而忽略掉其他的字元,這可能不是我們想達到的效果。

如果你想規避這種錯誤,那麼有個更好的選擇:strtol()函式:

long int strtol(const char* nptr, char** endptr, int base);

引數:1)endptr:它被設定為指向第乙個不能被轉換的字元。

2)base:用於指定讀入數字的基,多數情況下是10,但如果你想讀入二進位制資料的話,那麼應該給2

3)這個函式甚至提供了errno,所以你可以檢查一下待轉換的數字是不是太長或太短

改一下上面的**:

#include #include #include long a;

char buf[1024];

int success;

do //有一些輸入,將其轉換為整數

char* endptr;

errno = 0;

a = strtol(buf, &endptr, 10);

if(errno == erange)

else if(endptr == buf)

else if(*endptr && *endptr != '\n')

else

success = 1;

}while(!success)

printf("your entered %ld", a);

是的,你可以,這裡是最後乙個規則:

規則 4: scanf()是個非常強大的函式

下面直接給出例子: 

int main()

if(rc == eof)

else

}

int main()

mvc的真實含義

mvc是乙個設計模式,它強制性的使應用程式的輸入 處理和輸出分開。使用 mvc應用程式被分成三個核心部件 模型 m 檢視 v 控制器 c 它們各自處理自己的任務。檢視 檢視是使用者看到並與之互動的介面。對老式的web應用程式來說,檢視就是由html元素組成的介面,在新式的web應用程式中,html依...

qsort函式及其模擬實現

qsort函式 無型別排序函式 void qsort void base,size t num,size t size,int compar const void,const void 傳入引數陣列首位址,陣列字元個數,陣列型別大小,自己寫的比較函式位址 模擬實現qsort函式 include in...

Go的目錄及其含義

執行 go env 找到go的執行目錄,可以看到下邊有三個檔案 bin存放編譯後的可執行檔案 go install 自動建立 pkg存放編譯後的包檔案 go install 自動建立 src存放專案原始檔 手動建立 進入src,開啟go專案檔案,有很多目錄,讓我們看看分別是什麼含義吧!conf目錄 ...