張春玲
摘 要:scanf函數(shù)是C語言中最基本的輸入函數(shù),本文通過分析scanf函數(shù)調(diào)用過程中的實(shí)現(xiàn)機(jī)制,以幫助正確使用scanf函數(shù)。
關(guān)鍵詞:C;scanf函數(shù)調(diào)用;實(shí)現(xiàn)機(jī)制
1 scanf函數(shù)
scanf的功能是從標(biāo)準(zhǔn)輸入設(shè)備讀取輸入的任何固有類型的數(shù)據(jù)自動轉(zhuǎn)換成機(jī)內(nèi)格式并把數(shù)據(jù)輸入到指定的變量之中,返回正確讀入數(shù)值的個數(shù)。函數(shù)原型主要代碼如下:
int_cdecl scanf(const char *format,...)
{va_list arg;
va_start(arg,format);
return vscanf(_input_l,format,NULL,arg);}
2 scanf函數(shù)調(diào)用
2.1 調(diào)用格式
scanf(格式控制,地址表列);“格式控制”為格式字符串,將用戶輸入的數(shù)據(jù)轉(zhuǎn)換為指定格式;“地址表列”由若干個地址組成的參數(shù)表列,可以是變量的地址或字符串首地址。原型中_cdecl是c中的默認(rèn)函數(shù)調(diào)用方式,調(diào)用函數(shù)參數(shù)自右向左入棧,因此scanf函數(shù)左邊的第一個參數(shù)format被放于棧頂。
2.2 參數(shù)入棧
C調(diào)用協(xié)議下,為遵循對齊原則,要求每個變量地址都是sizeof(int)的倍數(shù),因此參數(shù)入棧都是整數(shù)字節(jié)。同時調(diào)用不帶原型聲明的函數(shù)時,調(diào)用者會對每個參數(shù)執(zhí)行“默認(rèn)實(shí)際參數(shù)提升”。從scanf函數(shù)調(diào)用格式中,可變參數(shù)是若干個地址列表(指針),C中為每個指針變量統(tǒng)一分配4個字節(jié),即可變參數(shù)入棧時都占用4個字節(jié),滿足了對齊原則,與指針指向的變量類型沒有關(guān)系。這與printf函數(shù)調(diào)用時參數(shù)入棧不一樣。
2.3 格式字符串與標(biāo)準(zhǔn)輸入流的匹配
參數(shù)入棧后,編譯器先獲取格式字符串,對照字符串中的各項(xiàng),從內(nèi)存緩沖區(qū)中取數(shù)據(jù),若沒有數(shù)據(jù),則等待用戶輸入。用戶通過鍵盤輸入數(shù)據(jù),數(shù)據(jù)回顯于顯示器上,同時數(shù)據(jù)被存入內(nèi)存緩沖區(qū)(不是鍵盤緩沖區(qū))中。為什么?scanf源碼中,函數(shù)功能的實(shí)現(xiàn)依靠vscanf函數(shù)調(diào)用_input_l,而_input_l的函數(shù)功能是把鍵盤輸入數(shù)據(jù)寫入stdin(標(biāo)準(zhǔn)輸入流)來創(chuàng)建一個臨時交換文件的緩沖區(qū),只有當(dāng)用戶輸入回車后,scanf函數(shù)開始從內(nèi)存緩沖區(qū)取數(shù)據(jù)。在接收數(shù)據(jù)時,對照字符串的各項(xiàng),并按匹配規(guī)則,逐一取數(shù):內(nèi)存緩沖區(qū)中,讀取時順序讀取寫入的數(shù)據(jù),即先讀先寫入的數(shù)據(jù)再讀后寫入的數(shù)據(jù),這與對字符串中從左到右匹配順序一致,匹配規(guī)則如下:C99中,格式字符串有如下三種類型字符:
⑴格式說明符:遇到格式說明符去讀取緩沖區(qū)時,匹配分兩步,首先是格式字符與緩沖區(qū)數(shù)據(jù)類型匹配,然后格式字符與對應(yīng)參數(shù)指向的變量類型匹配。匹配時先將緩沖區(qū)中一個或多個連續(xù)的空白字符(格式字符為字符型除外)移出并去掉,再將格式符與緩沖數(shù)據(jù)進(jìn)行匹配(見(四)),若類型匹配,則讀取緩沖區(qū)中直到遇到非法字符(與指定類型不匹配的字符)或者達(dá)到輸出寬度要求前的數(shù)據(jù),再將數(shù)據(jù)送到與格式字符類型匹配的變量中去(借助三個宏va_start;va_arg;va_end訪問后面每個參數(shù))。若格式字符類型與緩沖區(qū)數(shù)據(jù)不匹配,stdin流被阻塞,scanf函數(shù)不在讀取后面的部分。若格式字符與后面的參數(shù)類型不一致,則丟棄數(shù)據(jù)。
因此匹配格式字符時,輸入流中開始讀取到的空白字符(尤其對字符串中多個數(shù)值型格式字符緊挨著時,需要輸入的一個或多個空白字符)對匹配沒有任何影響,自動略去。也就是匹配格式字符時,輸入流中可以輸入任意多個空白字符。如scanf(“%d”,&a);輸入時可以直接輸入整數(shù),也可以輸入一個或者多個空白字符再輸入整數(shù),結(jié)果一樣。
⑵空白符:可以是空格、制表符和新行符。字符串中的空白字符的作用是使scanf函數(shù)在讀操作中略去輸入流中的一個或多個空白字符。因此,空白符是使scanf在輸入流中讀,但不保存結(jié)果,直到發(fā)現(xiàn)非空白字符為止,同時將棧中字符串位置指向下一個非空白字符。
因此匹配空白字符時,入棧的空白字符與輸入流中不一定匹配,因?yàn)檫@時候字符串中的空白字符對字符串來說就是一個分隔符,輸入數(shù)據(jù)時用戶即可以輸入相同個或更多個空白字符也可以不輸入(當(dāng)多個數(shù)值型格式字符由空白字符隔開時,在輸入時必須輸入空白字符),對讀取數(shù)據(jù)沒有任何影響,因?yàn)樽址锌瞻鬃址梢月匀ポ斎肓髦械亩鄠€連續(xù)空白字符。
⑶非空白符:使scanf()根據(jù)棧中字符在流中讀匹配的字符并放棄。如“a=%d”,使scanf讀取流中a=并放棄,如未發(fā)現(xiàn)匹配,scanf()返回。若字符串中非空白字符前面沒有空白字符,在輸入數(shù)據(jù)時,一定不要輸入空白字符。例:scanf(“a=%d”,&a);輸入時第一字符只能是a,若是空格則結(jié)果不對。棧中的a與輸入流中的空格不匹配,因?yàn)榭瞻鬃址粫詣勇匀ァ?/p>
2.4 格式字符匹配輸入流中的數(shù)據(jù)
如f,則告訴scanf接收數(shù)據(jù)的變量是float類型,需從輸入流中讀取一個單精度數(shù),再放入float類型變量中;如果將 L/l放在前面,則告訴scanf接收數(shù)據(jù)的變量為double??梢钥闯鲚斎霑r區(qū)別%f,%lf的,這與printf函數(shù)不同。使用printf時,f遵循提升規(guī)則表示double類型。而scanf匹配時首先是根據(jù)格式字符類型讀取數(shù)據(jù),再將數(shù)據(jù)輸入對應(yīng)變量。假如scanf中f代表double,表示從輸入流讀取雙精度數(shù),若%f對應(yīng)的變量是單精度,那不能保證將一個double型的數(shù)放到一個float類型中,原因是定義變量時float,double分別分配4個字節(jié),8個字節(jié),向變量輸入數(shù)據(jù)時是受字節(jié)限制的。因此輸入數(shù)據(jù)時要區(qū)分float,double,%f代表兩種類型不行。
3 總結(jié)
scanf函數(shù)在C中應(yīng)用比較頻繁,本文詳細(xì)分析了scanf函數(shù)調(diào)用過程,重點(diǎn)分析了格式字符串中三種類型的字符與內(nèi)存緩沖區(qū)匹配的過程,希望對使用scanf函數(shù)有正確指導(dǎo)意義。
[參考文獻(xiàn)]
[1]譚浩強(qiáng).C語言程序設(shè)計(jì)[M].北京.清華大學(xué)出版社.2011.
[2]徐娟.可變參函數(shù)scanf的執(zhí)行過程分析[J].信息與電腦.2013.5.