在上篇文章 中,我們簡單地提到過使用freopen的方式進行文件的輸入輸出,這篇文章我們再介紹一下其他幾種文件的輸入輸出方式,接著再談談如何構造數據檢測程序是否正確。
一、 文件輸入輸出
1、標準輸入輸出
我們最常接觸到的程序的輸入輸出,是直接在命令行窗口(Dos窗口)進行輸入輸出的,也可以直接地理解為,需要我們在命令行中用鍵盤輸入,同時結果也在命令行中顯示的方式。這樣的一種方式,我們稱之為標準輸入輸出。
標準輸入輸出的優缺點都很明顯,優點是簡單方便,直觀;缺點是需要手工操作,對于復雜數據比較麻煩,難以批量處理數據。在NOIP/NOI或ACM比賽中,因為涉及到的程序、輸入數據特別多,因此不可能使用標準輸入輸出進行校驗和評分,因此標準輸入輸出一般都是用于自己調試數據時使用,在比賽中都是需要使用文件輸入輸出。下面將介紹幾種常用的文件輸入輸出方式。
2、freopen文件輸入輸出
這是使用了文件的重定向方式,C++利用freopen函數將stdin和stdout重新定向到相關的文件,使原來的標準輸入輸出變成了文件輸入輸出。也就是說,如果之前我們有一段已經用標準輸入輸出的代碼,我們并不需要更改任何代碼,只需要在原來的基礎上加上freopen語句,重新定向一下就可以了,因此這是一種非常簡單、快捷的方式。
// 假設輸入文件是input.in,輸出文件是output.out,同時需要注意的是OI比賽中要求文件名不能帶目錄,即要求輸入文件應該與源代碼在同一目錄下
freopen("input.in", "r", stdin);? ? ? ? // 輸入文件
freopen("output.out", "w", stdout); // 輸出文件
freopen()函數有三個參數,前兩個是需要『雙引號』的。第一個參數是『輸入文件/輸出文件』;第二個參數『r』表示讀入,是read的縮寫,『w』表示寫入,是write的縮寫;第三個參數是『標準輸入/標準輸出』,即是stdin,stdout。
如:a+b程序
//標準輸入輸出
#include<iostream>
using namespace std;
int main(){
int a, b;
cin >> a >> b;
cout << a+b;
return 0;
}
freopen()重定向輸入輸出,只需要在以上代碼的基礎上添加兩句代碼即可,同時因為freopen函數是在cstdio這個庫中,因此在書寫時,應該引入cstdio庫。
#include <iostream>
#include <cstdio>? // 需要包含這個庫
using namespace std;
int main(){
freopen("input.in", "r", stdin);? ? ? ? // 輸入文件
freopen("output.out", "w", stdout); // 輸出文件
int a, b;
cin >> a >> b;
cout << a+b;
return 0;
}
3、文件流輸入輸入(fstream)
『流』這是一個講起來比較復雜的概念,我們暫且不需要理解和掌握,知道就可以了,隨著后面的深入學習我們會逐步理解。其實『流』在我們之前就已經接觸到了,譬如cin,cout本身也是一種流的輸入輸出,我們知道它是C++中的一種特性就可以了。
流式文件的操作一般包含兩種,一個是stream類的流文件,另一個是文件指針FILE,涉及到指針的問題又比較復雜,所以我們重點介紹一下stream類的流文件的處理。
我們同樣來看案例代碼,進行對比。(a+b問題,標準輸入輸出同上)
#include <iostream>
#include <fstream>? ? // f是file的縮寫,stream是流,合起來就是文件流
using namespace std;
ifstream fin("input.in");? ? ? // 輸入文件
ofstream fout("output.out");? //輸出文件
int main(){
int a, b;
fin >> a >> b;? ? // cin變成了fin
fout << a+b;? ? // cout 變成了fout
return 0;
}
對上述代碼的補充說明:
1、需要包含fstream庫,即#include <fstream>,f是file的縮寫,stream是流,合起來就是流文件,很好記憶。
2、ifstream,i是in的縮寫,輸入流文件;同樣的,ofstream,o是out的縮寫,輸出流文件。并且特別要注意的是,ifstream和ofstream,一般寫在函數外面,因為是全局使用的,而且建議最好就直接寫在using namespace std 之后。
3、fin/fout,僅僅是一個變量名,可以更換,但是推薦使用這個,養成良好的代碼習慣。fin/fout與cin/cout用法基本上是一致的。
4、代碼中可以不出現關閉文件代碼,因為程序結束后會自動關閉。
4、輸入輸出速度問題
由于兼容性等歷史問題,文件操作時,C++的cin/cout要保證與printf/scanf同步,因此cin/cout的效率會降低。雖然我們可以使用關閉同步功能(iso::sync_with_stdio(false);)來提高效率,但是在不同的C++編譯器中,它們效率表現不太一樣,因此我們認為它的效率是不穩定的。所以為了保障輸入輸出的效率,在題目中有大規模數據輸入輸出時,我們建議使用scanf,printf,而不用cin,cout。
cin,cout雖然效率不穩定,但是相對比較簡單,不需要我們去記各種格式,所以如果依舊不想記憶各種格式的同學,可以選擇用fstream的方式進行文件操作。scanf,printf雖然效率高,但是有時要記的格式有點多,不過其實用多了就會很熟練了。OI比賽中一般都是只有一個輸入、輸出文件,因此用freopen+scanf/printf的方式也挺好的。但是就強烈不建議,使用freopen+cin/cout的方式。
二、構造數據
1、為什么要構造數據
在OI/ACM比賽中,我們的程序都是基于『黑盒測試』的。也就是程序的正確與否,比賽并不關心,比賽也并不關心你使用了什么方法。只要你能在規定的時間內,把問題解決就可以了。那么如何評判是否是正確答案呢?一般都是出題人會用若干個輸入數據,然后運行代碼,將得出的結果與答案進行比對,絕大多數情況下,都是將你的程序的結果與答案進行逐行比對,多一個空格/換行,也不正確,因此需要特別注意輸出時的格式。
對于OI和ACM有所區別的是,OI允許非正確解(沒能通過全部測試點)得分,OI會按通過點得分,通過幾個點就得到幾個點的分數,因此在OI界有非常多的『騙分』。而在ACM比賽中,只允許正解得分(通過全部測試點),要么是滿分,要么零分(和提交次數有關,多提交會扣分)。當然啦,我們比賽中,我們合理利用所有能用的方法(作弊的拖出去槍斃),盡可能拿到更多的分,本身就是合情合理的,你在騙分,出題人在想怎么防你騙分,本身也是一種斗智斗勇,想騙也不是那么容易騙的,這也是一種能力。
那么針對『黑盒測試』,我們想要通過更多的測試點,也就是要求我們考慮問題更加全面,我們也就需要在寫完代碼之后,自己構造數據,進行測試。對于選手來說,如何測試是一樣非常重要的能力,同樣對于程序員來說也是如此,這是一項基本功,很難一下子提高,需要不斷積累和總結。
2、怎么構造數據
構造數據需要考慮幾個要點,第一,盡可能全面地考慮問題;第二,考慮邊界、臨界值;第三,考慮大數據;第四,考慮特殊值,極端數據;第五,隨機數據。
最最簡單的檢測方法,就是自己根據上述幾個要點,自己構造數據,用程序進行檢測,如果出現不通過的情況,可以結合前面的調試程序的方法去改正和調試。還有比較特殊的一個方法,就是構造隨機數,然后進行數據的『對拍』。所謂對拍,即是用一個保證正確的方法(也許時間復雜度比較大)得出的結果,與你所謂的『正解』結果進行比對。如果對拍了很多數據都能通過,基本上就是正解了,對拍也是一樣很重要的技能。關于對拍的方法和技巧,我們會在下一次課再介紹。