詳解scanf原理
- 后撤步輸入格式
- 后撤步輸入標程
- 緩沖區
- %d忽略前導空白符
- 遇到非法輸入,整句scanf停止運作
- (重點)格式串中的空白符與任意數量的任意空白符匹配
- 重新分析后撤步
- 樣例輸入
- 標程
昨天的C2上機中出現了一道考驗scanf原理的題《后撤步》,輸入格式如下:
后撤步輸入格式
第一行一個數 ,
第二行是棋子的初始坐標
(
x
0
,
y
0
)
(x_0,y_0)
(x0?,y0?),
第三行三個數,分別是圓心坐標
p
,
q
p,q
p,q和半徑
R
R
R,
接下來
n
n
n行,每行都是一個向量
(
x
i
,
y
i
)
(x_i,y_i)
(xi?,yi?),
后撤步輸入標程
scanf("%d\n(%lld,%lld)\n%lld%lld%lld\n",&n,&x,&y,&p,&q,&r);
其中\n和(輸入不當都會出錯,習慣了傻瓜輸入的我手足無措,瘋狂百度,
接下來分幾點詳解scanf部分原理,全篇萌新友好,
緩沖區
鍵盤緩沖區是在記憶體的一塊區域,可以以為記憶體中存在一塊槽,鍵盤輸入的字符從這一端按順序進入緩沖區,用戶按下回車或者緩沖區滿后,scanf從另一端按順序處理緩沖區中的字符,

如有這么一行陳述句:
scanf("%d%d",&a,&b);
用戶為了將123賦值給a,將45賦值給b,用鍵盤輸入
1 ,2 ,3, 空格,4, 5,回車,
其中,當用戶沒有按下回車之前,scanf無動于衷,不開始從緩沖區讀取資料,用戶按下回車以后,scanf開始從1開始逐個處理資料,
%d忽略前導空白符
對于“輸入一行兩個用空格分隔的數字”,除了上述寫法外,部分同學會使用如下代碼:
scanf("%d %d",&a,&b);
同學認為,題目要求我用空格分隔,我便在scanf的格式串中寫入空格,符合標準,其實大部分情況下沒有必要,格式串中的空白符容易引發奇怪的錯誤,會在下面解釋,
%d有個特性便是忽略前導空白符,所以使用
scanf("%d%d",&a,&b);
便可,分析如下,
鍵盤輸入1 2 3 空格 4 5 \n,scanf的格式串%d%d便開始從緩沖區吃資料,
第一個%d吃掉1 2 3,遇到空格,這個空格對于%d是非法字符,只能吃掉數字的%d不可能接受這個空格,第一個%d的讀入便到此為止,此時,a擁有了值123,緩沖區中剩余空格 4 5 回車,
第二個%d開始開始讀取,它首先遇到空格,由于%d擁有忽略前導空白符的性質,他會忽略所有連續的空白符,直到遇到它想要的數字字符為止,所以第二個%d忽略了前導(緩沖區最前面的)空格,此時第二個%d還沒有讀到任何東西,緩沖區剩余為 4 5 回車,
第二個%d吃掉4 5 ,其后是回車,這個回車對于%d是非法字符,只能吃掉數字的%d不可能接受這個回車,第二個%d的讀入便到此為止,a=123,b=45,緩沖區剩余回車,格式串中所有的字符都從緩沖區找到了東西與自己對應,這句scanf運行完成,
注意,并不是什么都能忽略前導空白符,%c是不會忽略前導空白符的,
遇到非法輸入,整句scanf停止運作

有代碼
int a=99,b=99,c=99,d=99;
d=scanf("%d%d%d",&a,&b,&c);
printf("a=%d,b=%d,c=%d,d=%d",a,b,c,d);
鍵盤輸入1 空格 a 空格 1 \n,試圖把1賦值給a,'a’賦值給b,1賦值給c,d是scanf的回傳值,代表這句scanf成功賦值的個數,
顯然,'a’不可能賦值給int型別的b,現在的問題便是,為b賦值失敗,接下來的c能不能接受最后的1,還是說整句scanf因為b遇到了非法輸入而就此罷工(提前回傳),
看圖,答案很簡單,是后者,
也就是說,當scanf的格式串的任何一部分遇到了非法輸入,整句scanf都會停止運作(排除%d等可以忽略前導空白符的情況),
這里的任何一部分包括
①格式說明(如%d,%f等)遇到了他們不能接受的型別,
如上圖試圖把字母’a’賦值給int型別的%d,
②格式串中的字符常量遇到了不完全相同的字符,
如下:

格式串是(%d,%d),第一次我們向緩沖區輸入< 1 , 2 > \n,按下回車后,格式串里的每一個字符(( , ))或者格式說明(%d)都會依次從緩沖區試圖吃掉“屬于自己的字符”,
首先,格式串最開始的(想要在緩沖區中找到一個和自己一模一樣的字符,但是緩沖區開頭是<,無法對應,整句scanf都罷工,現在的緩沖區里是< 1 , 2 > \n,什么都沒有被吃掉,
第二次我們嚴格按照格式輸入( 1 , 2 ) \n,格式串里最初的(在緩沖區開頭找到了一模一樣的(,緩沖區剩下1 , 2 ) \n,
接下來輪到了第一個%d讀取字符,它讀掉了1,緩沖區剩下, 2 ) \n,
…以此類推…
當整個格式串格式串(%d,%d)都從緩沖區讀取完畢,這句scanf執行完成,緩沖區剩下\n,
接下來放一個綜合練習,可以自行體會,

(重點)格式串中的空白符與任意數量的任意空白符匹配
看如下代碼,

格式串是date:%d,%d,%d,但是我們向緩沖區輸入的時候,在date前面加了一個空格,導致格式串最初的date中的d無法和緩沖區前端的空格配對,導致整句罷工(注意這里的d不會忽略前導空白符),所有字符都留在緩沖區中,沒有任何一個%d讀到有效資訊,這些運用的都是剛剛講過的知識,
那么如果換一下格式串,換成空格date:%d,%d,%d,僅僅加了一個小小的空格,就會發生巨大的變化,

如上,我們可以不向緩沖區輸入第一個空格就可以正常運行,

如上,我們可以向緩沖區輸入八百個空格,也可以正常運行,

如上,我們可以一開始向緩沖區輸入Tab*n,回車*n,空格*n,也不影響程式輸出正確結果,
為什么呢,因為格式串中的空白符與任意數量的任意空白符匹配,
其中,任意數量包括0個(對應第一張),任意種類包括那些\t \n 空格等,對應第三張,
這樣,我們便可以解釋一個問題,也是初學者遇到最多的問題之一,

當格式串最后有一個\n時,按理說,應該輸入兩個\n就可以讓scanf結束運行了,為什么按再多次\n也沒有效果,只有隨便輸入一個非空白符以后才能使scanf運行結束,并且緩沖區中根本沒有剩下剛剛輸入的那么多\n,
答案就是,我們向緩沖區輸入再多個\n,都會被格式串%d %d\n中最后的那個\n吃掉,因為格式串中的空白符與任意數量的任意空白符匹配,
只有最后輸入一個非空白字符,格式串最后的\n才發現,自己在緩沖區的最前端遇到了一個吃不掉(無法匹配)的東西!,這句scanf運行才能就此結束,
重新分析后撤步
相信如果你聽懂了我剛剛所說的,就可以知道后撤步的輸入應該怎么寫了,
第一行一個數 ,
第二行是棋子的初始坐標
(
x
0
,
y
0
)
(x_0,y_0)
(x0?,y0?),
第三行三個數,分別是圓心坐標
p
,
q
p,q
p,q和半徑
R
R
R,
接下來
n
n
n行,每行都是一個向量
(
x
i
,
y
i
)
(x_i,y_i)
(xi?,yi?),
樣例輸入
5
(2,3)
0 0 8
(6,2)
(-2,3)
(-3,-7)
(4,-2)
(-9,6)
標程
#include <stdio.h>
int n;
long long p, q, r, x, y;
int main()
{
scanf("%d\n(%lld,%lld)\n%lld%lld%lld\n", &n, &x, &y, &p, &q, &r);
for (int i = 1; i <= n; i++)
{
long long u, v;
scanf("(%lld,%lld)\n", &u, &v);
x -= u;
y -= v;
}
if ((p - x) * (p - x) + (q - y) * (q - y) <= r * r)
printf("No way!\n");
else
printf("(%lld,%lld)\n", x, y);
return 0;
}
讓我們從頭開始一一分析,
scanf("%d\n(%lld,%lld)\n%lld%lld%lld\n",&n,&x,&y,&p,&q,&r);
第一行我們向緩沖區輸入5 \n,5被粗體的%d吃掉,\n被粗體的\n吃掉,緩沖區里什么都沒有,
scanf("%d\n (%lld,%lld)\n%lld%lld%lld\n",&n,&x,&y,&p,&q,&r);
第二行我們輸入( 2 , 3 ) \n,看到這里便知道了第一行的格式串要用%d\n的理由:
因為如果沒有\n,緩沖區里就會剩下我們輸入5以后帶的\n,而這個剩在緩沖區開頭的\n沒法和粗體的(配對,導致整句scanf罷工,同理適用于for回圈里面的scanf("(%lld,%lld)\n", &u, &v);,
但是這份代碼有一個問題:
明明以前已經訂好了n=5,我們明知道for回圈中輸入5個坐標就應該結束了,為什么輸入最后的第五個坐標以后,按下回車沒有反應,并且按很多次回車也沒反應呢?
對了,這就是剛剛所講的格式串中的空白符與任意數量的任意空白符匹配,縱使多少個\n都會被scanf("(%lld,%lld)\n", &u, &v);最后的那個\n吃掉,
但是最后的那個\n又不好刪掉,否則會導致下一次輸入失敗(上一次輸入完以后緩沖區里剩的\n和下一次輸入的格式串開頭的(無法對應),
有沒有什么折中的辦法呢,
#include <stdio.h>
int n;
long long p, q, r, x, y;
int main()
{
scanf("%d\n(%lld,%lld)\n%lld%lld%lld\n", &n, &x, &y, &p, &q, &r);
for (int i = 1; i <= n; i++)
{
long long u, v;
if(i<n)
{
scanf("(%lld,%lld)\n", &u, &v);
}
else
{
scanf("(%lld,%lld)", &u, &v);
}
x -= u;
y -= v;
}
if ((p - x) * (p - x) + (q - y) * (q - y) <= r * r)
printf("No way!\n");
else
printf("(%lld,%lld)\n", x, y);
return 0;
}
哦!原來是這樣的道理!
P.S.為了讓更多人聽懂,文章里保留了一些細節,最重要的細節是,除了鍵盤緩沖區,還存在一個stdin緩沖區,鍵盤輸入的字符都實時放在了鍵盤緩沖區里,按下回車或者鍵盤緩沖區滿了,里面的內容全移到stdin緩沖區里,scanf時刻讀取stdin緩沖區里面的字符,如果stdin緩沖區為空,就等待鍵盤緩沖區提供新的字符給stdin緩沖區,如果stdin里面有資料,就不等待用戶輸入,直接用現成的,本文簡化成了只有一個鍵盤緩沖區,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/303099.html
標籤:其他
上一篇:C語言指標淺談
