在使用C/C++進行編程的過程中,經常會遇到輸入輸出的問題。
對於C語言中,
1. 格式化輸入輸出。
在C語言中,最常用的格式化輸入輸出是scanf和printf函數。
和這兩個函數對應的更安全的函數是fscanf和fprintf:指定文件指針
對於字符串的處理還有sscanf和sprintf:指定字符串
聲明如下:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
2. 字符輸入輸出
出了這些格式化輸入輸出函數之外,還有很多針對字符輸入輸出的函數:
包括getc,getchar,gets,fgetc,fgets,putc,putchar,puts,fputc,fputs。
首先單個字符輸入/輸出函數對應的聲明如下:
int fgetc(FILE *stream); int getc(FILE *stream);
從文件中讀入一個字符,返回值就是讀入的字符,如果錯誤返回EOF。
這兩個函數具有相同的效果,但是getc是宏,而fgetc是函數。程序員常用這個宏,因為它比調用函數更快。
int fputc(int c,FILE*stream); int putc(int c,FILE*stream);
輸出字符到文件。返回輸出的字符(轉化為int型),如果錯誤返回EOF。
putc也是宏實現的。
int getchar(void),int putchar(int c)
這兩個也是宏實現,主要是從標准輸入輸出讀取或輸出字符。等同於getc(stdin)和putc(c,stdout)
其次是多個字符/字符串的輸入/輸出函數對應的聲明:
char *fgets(char *s, int size, FILE *stream);char *gets(char *s);
讀入字符串,fgets適合取代gets,因為gets無法限制讀取字符的個數。gets是直接從標准輸入讀取。
fgets從流中讀入最多n-1個字符,最后加一個空字符作為字符串結尾標記。如果在讀到最大個數的字符之前遇到了一個換行字符或者文件結尾,那么只有目前所讀入的字符會被放入到緩沖區中,如果讀到換行符'\n',那么此字符也會被放入到緩沖區中。
返回值為讀入的字符串,如果出錯,返回值為NULL
int fputs(const char *s,FILE*stream); int puts(const char*s);
返回輸出的字符的個數,如果出錯,返回EOF。
需要注意的是fgets保留換行符'\n',而gets是從stdin輸入,在讀取字符串時會刪除結尾的換行符'\n';
同樣,fputs寫入時不包括換行符,而puts在寫入字符串時會在末尾添加一個換行符。
3. 二進制輸入輸出
對於二進制的輸入輸出,主要采用fread和fwrite函數,這個比較單一。
聲明如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
其中ptr是緩沖區指針,size是讀取的元素的個數,nmemb是元素的大小(sizeof)
返回值是正確讀寫的字節數,如果出錯或達到文件末尾eof,返回一個小的值或0
必須自己判斷文件結尾還是出錯,分別使用feof函數和ferror函數
對於C++語言,
采用的是輸入輸出流來進行的。由於C++是面向對象語言,所以C++中采用的是流類。
下圖是C++的一個類繼承方式:

可以看到cin,cout,ifstream,ofstream,istringstream,ostringstream是對應的輸入輸出類。而fstream和stringstream類是可以同時進行輸入和輸出。
1. cin,ifstream和istringstream都是從istream繼承而來,所以,我們可以分析一下istream類的成員函數:
對於istream,可以分為格式化輸入和非格式化輸入兩種形式:
(1)格式化輸入
采用的時候對操作符>>的重載。並且操作符>>在對待輸入上遇到空格就停止。
(2)非格式化輸入
分為對於字符/字符串的輸入和對於字節的輸入,包括的函數主要有get,getline,read,readsome,peek等。
其中get是獲取一個字符,getline是獲取一行字符。read和readsome是讀入字節。
get函數的聲明如下:
int get();
istream& get ( char& c );
istream& get ( char* s, streamsize n );
istream& get ( char* s, streamsize n, char delim );
istream& get ( streambuf& sb);
istream& get ( streambuf& sb, char delim );
可以從輸入設備獲得一個字符,也可以讀取字符串。默認采用'\n'作為分隔符。
使用get函數時,get函數與getline接受的參數相同,解釋參數的方式也相同,並且都讀取到行尾,但是get不再讀取並丟棄換行符,而是將其留在輸入隊列中。所以經常出問題。由於第一次調用后,換行符留在隊列中,因此第二次調用時看到第一個字符邊是換行符,因此get認為已經到達隊尾,而沒有發現任何可讀取的內容。如果不借助於幫助,get將不能跨過該換行符。可以采用get的另一種重載的形式來讀取這個換行符,然后為下一行的輸入做好准備。
但是對於空行get經常出問題。當get讀取空行后將設置失效位(failbit),這意味着接下來的輸入將被阻斷,但可以用下面的命令來恢復輸入。
cin.clear();
getline函數的聲明如下:
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
從輸入讀入字符串到s中,n是緩沖區最大容量。默認采用'\n'作為分隔符。
read和readsome函數的聲明如下:
istream& read ( char* s, streamsize n );
streamsize readsome ( char* s, streamsize n );
這兩個函數不是很熟悉。之前很少遇到過。
read()方法從緩沖區或設備讀取指定長度的字節數,返回對自身的引用.
而readsome()方法只能從緩沖區中讀取指定長度字節數,並返回實際已讀取的字節數.
比如:
const int LEN = 20;
char chars[ LEN + 1 ] = {0};
ifstream in( fileName );
in.read( chars, LEN );//將文件從設備載入緩沖區,並讀取LEN長度.
cout << chars << endl;
in.readsome( chars, LEN );//就可以從緩沖區中讀取. 在緩沖區中沒有數據時,用readsome()得不到任何數據.
cout << chars << endl;
而有時候想要從設備讀取指定長度的數據,但又要知道實際讀取的長度,這時候就要用另一個方法: gcount()
它返回自上次讀取以來所讀取的字節數,因此可以這樣得到實際讀取的長度.
int count = 0;
in.read( chars, LEN );
count = in.gcount();
cout << "Read in " << count << " chars : " << chars << endl;
實際上,readsome()也是調用read()和gcount()來實現的.
C++為了采用string類而引入了一個全局的輸入函數getline,其參數是string類型的:
istream& getline ( istream& is, string& str, char delim );
istream& getline ( istream& is, string& str );
這個函數還是非常有用的。為什么會出現這個getline函數呢?原因:cin是istream的一個對象,cin.getline()中的getline()也就是istream的一個成員函數,在string被加入到c++很久以前,istream就已經存在了,所以,參數針對的是數組類型的存儲,而沒有string。
2. cout,ofstream和ostringstream都是從ostream繼承而來,所以,我們可以分析一下ostream類的成員函數:
ostream也是分為格式化和非格式化輸出,與istream對應的。
(1)格式化輸出
采用對操作符<<重載的方式。
(2)非格式化輸出
主要包括put函數和write函數兩個。put函數輸出一個字符,write函數輸出字節。
相比輸入istream,輸出ostream中的相關函數少了很多,其實主要是因為輸出相比輸入更簡單,輸出操作符<<可以解決到大部分情況。
put函數
ostream& put ( char c );
write函數
ostream& write ( const char* s , streamsize n );
3. 在使用這些函數的時候,一個主要的問題是如何判斷輸入是否完成,即循環的控制條件該怎么寫?
C++中是通過判斷輸入流的狀態來得到輸入是否完成或出錯的。
下面的就是幾個相關的函數:good,eof,fail,bad
(1) good函數
bool good ( ) const;
檢查輸入流是否良好,可否繼續進行輸入。主要檢查流的3個狀態標志:eofbit, failbit 和badbit
(2) eof函數
bool eof ( ) const;
檢查eofbit標志。用於測試是否達到文件末尾(EOF)
(3) fail函數
bool fail ( ) const;
檢查failbit和badbit。測試是否出現輸入錯誤。
(4) bad函數
bool bad ( ) const;
檢查badbit。檢查是否出錯。
所以在具體的使用中,可以使用good函數來作為循環的判斷條件。
所以,我們總結一下在C/C++中讀取一個字符或字符串可以采用的方式。
1. 首先,讀取一個字符:
C語言方式:
(1).采用getchar從stdin輸入
while((c=getchar())!=EOF)
putchar(c);
(2). 采用fgetc/getc輸入
while((c=fgetc(stdin))!=EOF)
fputc(c,stdout);
或
while((c=getc(stdin))!=EOF)
putc(c,stdout);
C++語言方式:
(1).采用重載操作符>>
while(cin>>c)
cout<<c<<endl;
這種形式會跳過所有的空白符,包括空格,換行,制表符
(2). 采用get函數
char c;
while(cin.good())
{
c=cin.get();
if(cin.good())//這兒就是判斷是否讀入了有效的字符
cout<<c;
}
這兒可以讀入任何字符。
或者
char c;
while((c=cin.get())!=-1)
cout<<c;
這兒就是直接判斷讀到的字符,類似C中的思想。
2. 其次, 讀取一行文本:
C語言方式:
(1). 采用gets函數,從stdin輸入
char str[256];
while(gets(str)!=NULL)
puts(str);
(2). 使用比較安全的fgets函數
char str[256];
while(fgets(str,256,stdin)!=NULL)
fputs(str,stdout);
(3). gcc中擴展的函數
int read;
int len=0;
char *line=NULL;
while((read=getline(&line,&len,stdin))!=-1)
printf("%s\n",line);
free(line);
C++語言方式:
(1).采用操作符重載>>
string s;
while(cin>>s)
cout<<s<<endl;
這種方式會以空白(空格,換行,制表)為分隔符,不斷的讀入字符串。
(2). 采用istream類的成員函數getline
char str[256];
while(cin.good()){
cin.getline(str,256);
cout<<str<<endl;
}
或
char str[256];
while(cin.getline(str,256)){
cout<<str<<endl;
}
(3). 采用全局函數getline
string str;
while(getline(cin,str)){
cout<<str<<endl;
}
(4). 采用get函數
char str[265];
while(cin.good()){
cin.get(str,256);
cin.get();
cout<<str<<endl;
}
總結來看,如果要讀取一行的話,
對於C語言,可以采用fgets函數,或者如果使用linux平台的話,可以采用擴展的getline函數。
注意,這兩個函數都是要讀入最后的換行符的。
對於C++語言,如果使用C字符串的話,就采用cin.getline()函數,如果采用string型字符串的話,就采用全局函數getline(cin,n);
注意,這兩個函數都不讀入最后的換行符。
這兒有一個關於getline函數的簡單的總結:http://www.cnblogs.com/xkfz007/archive/2012/02/27/2363810.html
這兒有一個關於cin中的getline和get函數的比較:http://www.cnblogs.com/xkfz007/archive/2012/04/06/2435251.html
這兒是對C中輸入輸出函數的一個簡單總結:http://www.cnblogs.com/xkfz007/archive/2012/02/27/2363810.html
