C/C++中的输入与输出及如何读取一行文本
在使用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