c++中cin和scanf的區別是什麼?
如下面兩個代碼所示,這是一個十進位轉十六進位的程序,兩段代碼其中唯一的區別就是一個用了cin,另一個用scanf,結果完全不同,用scanf得出的結果是正確的。想問造成這種結果的原因是什麼?
ps:經指出輸入數據超出了unsigned int範圍,但是scanf的結果仍然是正確的。想問的是cin和scanf出現這樣結果的原因是什麼?以後如何避免?
這才恰恰說明scanf不安全,因為你的輸入已經溢出了,在該fail掉的時候fail掉,給出明顯錯誤的值才是正確的做法。而且你可以試試 cin.fail() 或者 cin.rdstate(), 你會發現明顯檢測到錯誤,等著你往外拋異常呢。就這樣一段程序,題主都沒有發現輸入已經溢出了,還是別人指出的。等到代碼長了,用scanf怎麼死的都不知道。。。
如果你要玩OJ,老老實實確定輸入大小再來寫,不然坑死你。然後常備一個bigint的模板,需要的時候抄一遍。要不換Java,自帶大整數實現。
還有你要在OJ上用iosteam且不和stdio混用的話,要加一句
std::ios::sync_with_stdio(false);
不然TLE等著你,免得到時候又罵C艹辣雞,IO慢啥得,貽笑大方
變數住在內存里,那就像人住房屋裡。所以在內存中相挨的兩個變數,自然是鄰居。那天有一個超級大胖變數要住的就是這間房。這房,兩室兩廳,合四個位元組。
我早說他住不下的。
沒人信。搬家公司是scanf。看,你們臉色都變了。暴力搬遷,硬把大胖子所有家當塞進這四位元組。胖子是沒事,他家的冰箱也沒擠壞。只是兩米八的床捅破這內存的牆。可憐隔壁家住的好像是一對母女。那母女都是字元。合起來也才2位元組。夜裡睡得好好的,做著點小市民的甜夢。就被那胖子的床一擠,一擠。
程序案件術語上,那叫溢出。你們懂。內存的世界,你們也是懂的。可憐那母女連肉體的渣都留不下。就這樣魂兒歸西。我們只是程序里的片段,又上哪喊冤。可氣製造這一慘案的兇手。那程序員,還在誇scanf好生強大!真牛,這麼大的胖子,毫髮無損塞進去。快出來到屏幕讓朕看看。這胖頭胖腦的模樣。朕喜歡呢。
來人!把早先那家cin搬遷公司上上下下。滿門操斬(主子,評論區說應該是抄字)。cin,死到臨頭,你可知罪?小民知罪。說!那胖子非要進這小屋,他橫!說就喜歡住這經濟適用房。退全款都不行。小民脾氣爆把他頭給削了。
送小民走吧。那母女必須活著,別說這是未定義。scanf在你的數組不夠大的時候,就會成為一個緩衝溢出漏洞。而使用cin的時候,人類的偷懶會使得你不會使用數組來輸入數據。
這(兩)段代碼槽點太多,不過我先說兩點:
1. 34534534534 超出了 32 位無符號整數的表示範圍。
2. A6B2D86 怎麼看都不像是正確答案。
首先scanf的答案是不對的
應該是80A6B2D86
win10 + codeblocks16.01(默認是gcc32位編譯器)
使用ida簡單反編譯一下
可以看到讀的34534534是存在 esp + 1C處的,此時在該處已經有問題 : 0A6B2D86
此後又繼續只賦值給eax,我們知道eax是32位寄存器(這裡沒有使用rax應該我是32位編譯器問題),而且並沒有藉助其他寄存器或者棧,那麼在call __Z9toSixteenj的時候,傳入參數的也就是只有4個位元組
scanf是個不安全的函數,容易造成緩衝區溢出
比如下面的代碼
#include &
int main() {
int flag = 0;
char c[4];
scanf("%s",c);
if(flag) {
printf("flag is not 0
");
} else {
printf("flag is 0
");
}
}
嘗試輸入:wwwww
會列印出"flag is not 0"
我好像沒回答到點子上!!!我要去努力搬磚去了。。。
具體實現的方法的問題,你輸入的整數在你的平台上已經超過unsigned int了,scanf這種情況下不會報錯,具體n的結果就沒定義,從你的結果看,你用的scanf應該是自然溢出,取了低32bit,而cin從測試結果看在溢出的時候用32bit全1標識錯誤,具體怎定義的沒去查標準,先保留觀點
你可以認為cin在這裡相當於:先用一個假想的可以容納很大整數的類型輸入,然後判斷是否溢出,如果溢出則返回全1。當然實際上並不是這麼搞:)
最後說一下,你scanf用%ud應該是不對的,u和d分別對應unsigned int和int,u並不是一個前綴修飾,%u就行
If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.
嘖,我以前都沒發現 scanf 有這麼不安全
正如前面所說,scanf存在溢出後發生未定義行為的問題,是不安全的。
C語言正規場合最好不要用scanf,而是使用getchar和ungetc按位元組讀取,使用strtol、strtoul、strtod這些C89函數(以及strtoll、strtoull、strtof、strtold、strtoimax、strtoumax這些C99函數)進行轉換,它們對於溢出返回邊界值(浮點數返回inf)並且設置errno為ERANGE,和cin&>&>的行為差不多。
至於OJ這種非正規場合,用scanf就行了,別小題大做,沒人會在乎你的OJ程序是否安全。
ps. 不要以為按位元組讀取很慢,stdio是帶緩衝的,所以其實getchar是很快的。
ps2. 根據http://cppreference.com,atoi、atol、atoll、atof在標準語境下也是不安全的。
想了半天區別,感覺自己不夠專業
只能說cin是c艹這個語言里一個特方便的偷懶吧。其他請輔助其他答主答案理解。題主你看,轉換成16進位的答案明明是8 0A6B 2D86,你怎麼還堅持scanf讀進去的數算出來的結果是對的呢?
強行答一波,因為我好為人師,希望檢驗一下我這種教學風格是否適合小白。
先聲明,我暫時對這裡涉及到的知識點沒有詳細考察。
開始回答問題:
首先,我們需要了解的是scanf和cin的工作流程是怎麼樣的。
step1,得到目標數據的數據類型,可能是根據scanf的格式化字元串如"%ud"之類,或者根據cin指定的數據類型,如 cin &>&> x 中的x類型推導出目標數據類型。
step2,得到目標數據的存儲地址。
step3,得到用戶的輸入,比如「13343 3434 3434回車「
step4,解析用戶輸入,得到分割後的字元串,比如13343,3434,3434
step5,依次得到分割後的字元串,根據指定的數據類型進行轉換,將轉換結果存入到目標存儲地址中去。
以上的流程中,確認學生哪些不能理解,需要再次解釋清楚。
關於step1,有一個知識點需要詳解。scanf的格式化字元串說明說的很清楚,沒有問題。
cin怎麼得到目標數據類型,這裡需要說道說道。似乎是重載操作符?我記得不太清楚了。這是一個知識點。
step2中,如何得到目標數據的存儲地址,scanf很容易理解,因為地址在參數中給出了。cin為什麼沒有給出地址卻能正確接受數據?是引用的概念在裡面嗎?這又是一個知識點。
step3,這裡就有疑惑了。scanf和cin是如何得到用戶輸入的數據的。是用戶在鍵盤上敲了一個字元1,就被它們接受到了?還是根據什麼得到的?這裡我們知道是根據回車這個鍵。
這個知識點要講清楚也不容易啊。
第一,用戶敲擊的鍵盤,實際上是實時存儲在輸入緩存中的。深入,誰在緩存?操作系統?運行時庫?
第二,scnaf實際上是讀取標準輸入文件設備的內容。類似讀取普通文件一樣,只是必須要讀到一個回車字元的時候才得到一行字元串。cin也是類似的吧。標準輸入文件,這個知識點也挺難講明白的。
好,假設現在我們記住了,用戶輸入了回車,scanf函數和cin就得到了這一行字元串。
step4,解析字元串。根據什麼來分割字元串的?scanf因為已經得到了目標字元串的類型,它是否能夠智能的根據類型,只讀取該類型最大長度的字元嗎?比如這裡是%ud,可能最大只能有13位十進位數字字元(隨便說的,我沒有計算) ,那麼scanf會最多讀取13個字元嗎?如果遇到空格了怎麼辦?如果遇到了非數字字元怎麼辦?嗯,我剛剛看了看scanf的實現代碼,頭暈。
可能scanf無法智能的根據最大個數的十進位數字來分割。遇到空格和非數字字元就算得到了一個%ud數據。那麼這裡面又有一個問題,scanf得到這個分割的一段字元串存儲在哪裡?好,假設我們知道了這個位置了。
step5,scanf如何把字元串轉換成十進位值?特別是當十進位的數字字元串非常大的時候,是怎麼轉換的?這個轉換支持這種超範圍的轉換嗎?為什麼?
cin怎麼轉換的呢?這裡的這個細節實在是太細了,我沒有備課,沒有這個知識點,說不出來。
差不多了吧,這麼些知識點要說完,我感覺我要說結巴了。似乎還是說不清楚。
只了解scanf時間短……
cin是iostream類的一個對象,&>&>符號是重載,讀入的是「流」,再將流強制轉換成你所要的數據類型。具體可看c++primer中io類一章了解。
scanf是函數,c的遺產,不太了解。
不過函數和對象是很明顯兩個概念。
建議用cin和cout。推薦閱讀:
※客戶端產品一般是用什麼編程語言寫的?
※應該把C語言學習到什麼程度?
※學習 C 語言需要數學基礎嗎?
※怎樣提高自己代碼可讀性?
※大學裡教的C語言,與知乎上說的入門編程要學的C語言是同一個東西嗎?