今天我想分享一下 C++ 11 的输入/输出(简称I/O)。
简介
管理流和缓冲区的工作有点复杂,但
iostream
(以前为iostream.h
)文件中包含一些专门设计用来实现、管理流和缓冲区的类。streambuf
类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;ios_base
类(ios是io-stream,和Apple的操作系统iOS没关系)表示了流的一般特征,如是否可读取、是二进制流还是文本、管理流等;;ios
类基于ios_base
,其中包括了一个指向streambuf
对象的指针成员;ostream
类是从ios
类派生而来的,提供了输出方法;istream
类也是从ios类派生而来的,提供了输入方法;iostream
类是基于istream
和ostream
类的,因此继承了输入方法和输出方法。 C++的iostream
类库管理了很多细节。例如,在程序中包含iostream
文将会自动创建8个流对象(4个用于窄字符流,4个用于宽字符流)(cin,cout,cerr,clog)。 C++在头文件fstream
(以前为fstream.h
)中定义了多个新类,其中包括用于文件输入的ifstream
和用于文件输出的ofstream
。C++还定义了一个fstream
类,用于同步文件I/O。这些类都是从头文件iostream
中的类派生而来的,因此这些新类的对象可以使用前面介绍过的方法。 (引用自C++ Primer Plus 第六版 中文版 第十七章)
使用iostream
cin
:标准输入流cout
:标准输出流cerr
:标准错误流(不缓冲)clog
:标准错误流(缓冲) 在他们前面加上"w"(即wcin
等)即可处理wchar_t
类型。
代码示例:
//io1.cpp
#include<iostream>
int main()
{
using namespace std;
int a,b;
char str[20];
cerr<<"Input 2 integers:";
cin>>a>>b;
if(a==b)
clog<<"a=b!"<<endl;
cerr<<"Input a string:";
cin>>str;
cout<<"a:"<<a<<endl;
cout<<"b:"<<b<<endl<<"str:";
cout<<str;
cout<<endl<<"Bye!";
return 0;
}
编译并三次运行这个程序:
C:\Users\BobLiu\Desktop>g++ io1.cpp C:\Users\BobLiu\Desktop>a.exe Input 2 integers:1 2 Input a string:A string a:1 b:2 str:A Bye! C:\Users\BobLiu\Desktop>a.exe Input 2 integers:55 55 a=b! Input a string:!$^&$ a:55 b:55 str:!$^&$ Bye! C:\Users\BobLiu\Desktop>a.exe >io1.txt Input 2 integers:25 5 Input a string:25=5*5
此时a.exe所在文件夹(桌面)上生成文件io1.txt,打开查看,内容如下:
a:25 b:5 str:25=5*5 Bye!
示例程序说明
第一次运行程序的字符串输入为“A string”,但是在稍后的回显中只显示了“A”,原因是:cin<<str;
语句中直接使用了cin
进行输入,而cin
是遇到空白字符(也就是isspace(ch)
(在cctype(老版本是ctype.h)中定义)返回true的字符)时,会停止获取输入并丢弃空白字符。也就是说,此时输入缓冲区中剩余string\n
,在cin<<str;
后如果还有一个cin
读取一个字符串,就会读取“string”,并且丢弃‘\n’。如果要读取一整行,那怎么办呢?答案是,使用cin.getline()
。把cin<<str;
替换成cin.getline(str,20);
后,可以读取一整行,同样丢弃‘\n’。
第三次运行程序却出现了一个有趣的现象。这里没有使用a.exe
直接调用,而是使用了文件重定向,将输出重定向到io1.cpp(即a.exe >io1.cpp
命令),相当于把显示移动到了文件里,所以代码中的cout
语句将输出到文件。但是clog
和cerr
所关联的标准错误流不会被重定向(至少在大部分系统上。在Unix中,使用>2
可以重定向标准错误流。),而是被显示到显示屏上。
另外,文件重定向不仅可以重定向标准输出流,也可以重定向标准输入流。假设a.exe所在文件夹(桌面)有一个文件叫i1.txt,使用a.exe <i1.txt >o1.txt
,就会从i1.txt获取输入,并且输出到o1.txt(但是clog
和cerr
依然会把提示信息发送到屏幕,尽管没有从键盘获取输入)。
另外,大家应该注意到了,clog<<"a=b!";clog<<endl;
被合并成了clog<<"a=b!"<<endl;
大家应该都知道这种拼接(同样适用于cin
、cout
、cerr
以及他们的成员函数,再举个例子,cin.get().get();
和cin.get();cin.get();
是等价的。)这是因为clog<<"a=b!"
的返回值为clog
,导致原语句变成了(clog<<"a=b!")<<endl;
,执行完输出后,就变成了clog<<endl;
同理,cin.get()
的返回值也是cin
,所以cin.get().get();
在执行完一次cin.get()
后变成了cin.get();
使用fstream
头文件fstream
不像iostream
,已经声明了负责输入/输出的对象(这是有道理的。不然,你只能同时打开2个文件,一个用于输入,一个用于输出。),而你需要自己声明他们。
代码示例
//io2.cpp
#include<fstream>
/*在大部分实现中,包含fstream意味着同时包含iostream,所以不用自行包含iostream。
如果你的编译器出现“error:'cerr' was not declared in this scope”,那还是加上吧*/
#include<string>
using namespace std;
int main()
{
ofstream fout;//如果不使用using namespace std;则需要使用std::ofstream或者using std::ofstream
ifstream fin;//同上
fstream file;//同上
fin.open("fin.txt",ios_base::in);//NOTE:确保文件存在!!!
fout.open("fout.txt",ios_base::out|ios_base::trunc);
/*NOTE:ios_base::trunc将删除原本的文件内容!*/
file.open("file.txt",ios_base::in|ios_base::out);
if(!fin.is_open()||!fout.is_open()||!file.is_open())
/*or if(!(fin.is_open()&&fout.is_open()&&file.is_open()))*/
{
cerr<<"Open file failed.";
return 1;
}
string str;
getline(fin,str);//get a line
//NOTE: for string, do not use fin.getline(str,XXX)
fout<<str;
file<<str;
fin>>str;
fout<<str;
file<<str;
file>>str;
fout<<str;
fin.close();
fout.close();
file.close();
return 0;
}
fin.txt内容如下:
Hello, there! Hi! Bye.
file.txt内容如下:
I love C++!
编译并运行程序:
C:\Users\BobLiu\Desktop>g++ io2.cpp C:\Users\BobLiu\Desktop>a.exe
文件fin.txt内容不变。 文件fout.txt(新生成)内容如下:
Hello, there!Hi!Hi!
文件file.txt内容如下:
Hello, there!Hi!
示例程序说明
使用ofstream
声明的对象可以用于输出,打开文件格式如下:ofstream fout; fout.open("file name (c-style string)",open_mode)
当然,也可以调用构造函数:ofstream fout("(c-style string)",open_mode)
。其中,open_mode是一个常亮,在ios_base
中定义,可以用“|”连接。如果你的文件名是C++的string
类型,比如string str;
,请使用str.c_str()
。
同理,使用ifstream
声明的对象可以用于输入,格式和ifstream
相同,但是要确保文件存在。
**使用fstream
声明的对象既可以用于输入,也可以用于输出。
fstream
的open_mode如下表(表格引用自C++ Primer Plus 第六版 中文版 第十七章):
常量 | 含义 |
---|---|
ios_base::in | 打开文件,以便读取 |
ios_base::out | 打开文件,以便写入 |
ios_base::ate | 打开文件,并移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_basse::trunc | 如果文件存在,则截断文件(删除内容) |
ios_base::binary | 以二进制模式打开 |
但是请不要使用奇奇怪怪的组合,比如ios_base::in|ios_base::trunc
,编译器会报错的。
还有就是,可以像使用cout
和cin
一样使用它们。