再會吧,這寶貴的片刻和短暫的時機限制了我在情義上的真摯表示,也不能容我們暢敘衷曲,這本來是親友重逢所應有的機緣;願上帝賜給我們美好的未來,好讓我們開懷暢談!再一次告別;勇敢作戰吧,祝你勝利!——威廉•莎士比亞《查理三世》
0 說在前面
此文主要面向正在學習 C 語言、數據結構的大學生。
當你看到這篇文章時,不妨回想一下你當初第一次用 C 語言“Hello World!”時是什么樣的心情。那是你第一次成功使用神秘代碼完成了和計算機的交流。盡管展示信息的黑框框讓你可能不大習慣這樣一種溝通方式,但這難道不也有點電影里黑客那感覺了~?
不知不覺半年過去了,你為了數據結構作業絞盡腦汁,敲下最后一個分號,鼠標輕點”編譯運行“。黑色高級框框跳出來,尷尬而不失禮貌地對你說:
--------------------------------
Process exited after 4.511 seconds with return value 3221225477
請按任意鍵繼續. . .
你質問:
”你怎么了,為什么要這樣對我……嗚嗚~“
是啊,你認識了這個框框那么久,它早已熟悉你寫 bug 的習慣,而你卻摸不清它的性情。你是時候應該了解一下它了。
1 標准輸入輸出
”你好,我叫終端,也叫控制台,英文名是 Terminal,也叫 Console,很高興成為你的朋友。“
”你不記得我啦?我就是你每次運行程序的時候跳出來跟你聊天的那位。請看——“

“其實我並不是你的程序本體,你的程序躲在電腦里面,是它派我來跟你說話的,”
“當你在鍵盤上敲敲的時候,我會幫你把你輸入的字符顯示出來,這樣你就知道你輸入的對不對了,”
“然后你一行輸完,按下回車,我幫你把整行字符串都傳給你的程序,你的程序就會對一行字符串進行解析,如果有 scanf 函數的話還會逐個解析出里面的數字、字符等等,”
“當你的程序算完之后,會把輸出的信息告訴我,我來顯示到屏幕上。”
所謂 I/O,就是 Input/Output,即輸入輸出。通過終端讀入和顯示的就是“標准輸入輸出”,由於終端也是從鍵盤獲取信息,並把信息顯示在屏幕上。所以:
- 標准輸入也叫鍵盤輸入
- 標准輸出也叫屏幕輸出
標准輸入輸出的英文是 Standard Input and Output,縮寫就是“stdio”,覺不覺得眼熟hhh~
——“你用的 scanf、gets、getchar 函數都是解析標准輸入的,printf、puts、putchar 都是標准輸出。現在知道我是干什么的了吧”
——“哦,原來是這樣。但是你好丑。”
——“???那我走”
2 輸出輸出重定向
終端走了——你萬念俱灰,把你的代碼提交給希冀的評測姬。她說:
得分0.00 最后一次提交時間:2022-03-25 19:29:50
共有測試數據:5
平均占用內存:1.401K 平均CPU時間:0.00578S 平均牆鍾時間:0.00576S
測試數據 評判結果
測試數據1 運行錯誤
測試數據2 運行錯誤
測試數據3 運行錯誤
測試數據4 運行錯誤
測試數據5 運行錯誤
——“求求你在本地測好再交給我 OK?我每天判那么多代碼很累了啦!”
——“emm……我好奇你怎么知道我們的代碼對不對的,也是用終端嗎?”
——“終端?那不是低級的 PC 才會用的東西?我們服務器不需要這個。I/O 重定向一下就行了”
現在你可以試試這樣一個操作,寫好一份 C 語言代碼,里面有標准輸入輸出函數,然后添加兩行這樣的語句:
#include <stdio.h>
// 一些額外的頭文件和宏定義
int main() {
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout); // 額外添加這兩句 :)
...
return 0;
}
然后在你的 C 程序的同一文件夾下新建文本文檔,命名為 “a.in”(注意這一步之前要確保你的電腦顯示了文件后綴[1])。然后在 “a.in” 里面寫上你要輸入的數據,Ctrl+S 保存。
編譯運行你的代碼,你會發現程序直接結束,黑框框沒有其它輸出了。
然后你在代碼所在的文件夾里發現了一個名為 “a.out” 的文本文檔,里面正是你要的答案。
當然 “a.in” 和 “a.out” 可以改成你喜歡的任何名字,文本文檔對后綴不敏感,跟 “.txt” 是一樣的。
當然你可以像我一樣玩(用編輯器打開輸入文件分屏出去,調試的時候不用每次在控制台輸入,多是一件美事):

回過頭來我們看看這兩句是什么意思:
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
/*
* freopen: f 表示 “file (文件)”
* re 表示 “重新”
* open 表示 “打開”
*
* "a.in" / "a.out" 表示重定向的文件名
* "r" / "w" 表示文件的打開模式:"r" 意味着“讀”,"w" 意味着寫
* stdin / stdout 表示被替換的 I/O 方式
* 分別是標准輸入(standard input)和標准輸出(standart output)
*/
翻譯成人話就是:
- 我要用“只讀方式”打開文件 “a.in”,並用其替換標准輸入;
- 我要以“只寫方式”打開文件 “a.out”,並用其替換標准輸出。
- 順便說一句,只寫模式 “w” 下,如果找不到文件,程序會幫你創建一個~
評測姬說:
“現在你懂了?在我拿到你的程序時,會自動幫你加上 freopen 將標准 I/O 重定向為文件 I/O,再在我的 CPU 里跑程序,跑完再對比一下你的輸出和標准答案一不一樣就行了。”
3 文件 I/O
此時晏老師:“多出點文件 I/O 的題,難死這幫小崽子~”
理論上來說,你會 I/O 重定向之后就可以做所有文件 I/O 的題了,大不了都用 scanf 和 gets 唄。
但是有時候讓你既從標准輸入讀入又從文件讀入~文件 I/O 也不能不會是吧。
廢話了這么多,終於可以講講你們不大清楚的 I/O 函數的用法了:
3.1 文件指針
要熟練使用文件 I/O,要過的第一關就是文件指針,它相當於給你的文件貼一個標簽,讓后當你需要調用函數的時候要把文件指針作為輸入變量傳進去,這樣才能對你的文件進行操作:
FILE * file_in = fopen("in.txt", "r");
FILE * file_out = fopen("out.txt", "w");
/*
* FILE * 是一個變量類型,代表文件的指針
*
* 后面的 file_in 和 file_out 是你自己起的變量名
*
* fopen("...", "r/w"); 是打開文件的函數,前面的文件名,后面是打開模式讀或寫
* 表示將一個文件以某種方式打開,返回該文件的指針
*
* 以后你就可以把 file_in 或 file_out 傳進其它函數里了
*/
3.2 I/O 函數
3.2.1 輸入函數:
scanf("...", ...);
fscanf(file, "...", ...); // file 是前面用 "r" 模式打開的文件指針
在以下兩個條件下,這兩個函數是我最推薦大家使用的。
- 需要從輸入中獲取數字(直接 %d 或 %lf)
- 需要逐詞對字符串處理(不含空格)
如果題不是要求類似於“讀入若干行,行內有空格,對每行輸出一個balabala……”這種,真心不建議用 gets 和fgets。因為 gets 很可能會產生莫名其妙的 bug(我曾解釋過),fgets 不好記也不好用。
所以比如“單詞統計”等等這類題,只要不怕空格,還請選擇 scanf/fscanf
printf("...", ...);
fprintf(file, "...", ...);
這倆大家應該挺熟了,后面那個 fprintf 就是把輸出目標換成 “w” 模式的文件指針就行了。
介紹兩個新朋友:
len = fread(str, sizeof(str[0]), MAX, file);
/*
* 這個函數的作用是從 "r" 模式的 file 文件里把整個文件一股腦讀到 str 里
*
* str 是要接受的字符串,盡量開大點,一定要初始化為全 0,這個函數不保證在字符串末尾補 '\0'
* sizeof(str[0]) 實際上就是一個字符的大小,表示讀的單位大小
* MAX 讀的最大長度,盡量跟 str[] 的容量一樣大,要大於所給數據范圍
* 如果讀到文件末尾還不到 MAX 則返回 str 的長度
* 如果讀到 MAX 則返回 MAX
* file 文件指針
*/
fwrite(str, sizeof(str[0]), len, file);
/*
* 這個函數的作用是把 str 一股腦寫進 "w" 模式的 file 里
*
* str 是要寫的字符串
* sizeof(str[0]) 解釋同上
* len 是想寫的長度,也就是 str 的長度
* file 想寫的文件指針
*/
文件加密一題中,需要讀取一整段文本,這種情況下,用這兩個函數是最好的選擇。
剩下的不太常用我大概說一下。
gets(str); // 讀到換行就停止,讀進來的字符串不含換行,可能引起神秘 bug
fgets(str, MAX, file); /* 讀到換行 / 文件末尾 / 超過 MAX - 1 時停止讀入
* 特性:str 中保留讀到的換行並自動在末尾添加 '\0' */
上面這倆函數如果想用的話還是建議好好研究一下特性小心一點使用,挺容易出 bug 的。
ch = getchar(); // 從標准輸入讀入單個字符,毒瘤,別用
// 建議想用的時候用 scanf("%s", str); 讀字符串來避免 bug
ch = fgetc(file) // 從文件中讀單個字符,注意的一點是:
// 請把 ch 定義成 int 類型,因為它讀到文件末尾會返回 EOF
// 而 char 類型不能儲存 -1 導致無法識別文件末尾
putchar(ch); // 向標准輸出寫一個字符,等同於 printf("%c", ch);
fputc(ch, file); // 向文件寫一個 ch,等同於 fprintf(file, "%c", ch);
打開“此電腦”,在上面一欄找到“查看”按鈕,點進去,找到“文件后綴名”,看是否打勾,如果沒有請務必打上。 ↩︎