C++ I/O

讲C++,有点枯燥(不好意思啊)

今天我想分享一下 C++ 11 的输入/输出(简称I/O)。

简介

管理流和缓冲区的工作有点复杂,但iostream(以前为iostream.h)文件中包含一些专门设计用来实现、管理流和缓冲区的类。streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;ios_base类(ios是io-stream,和Apple的操作系统iOS没关系)表示了流的一般特征,如是否可读取、是二进制流还是文本、管理流等;;ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员;ostream类是从ios类派生而来的,提供了输出方法;istream类也是从ios类派生而来的,提供了输入方法;iostream类是基于istreamostream类的,因此继承了输入方法和输出方法。 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语句将输出到文件。但是clogcerr所关联的标准错误流不会被重定向(至少在大部分系统上。在Unix中,使用>2可以重定向标准错误流。),而是被显示到显示屏上。 另外,文件重定向不仅可以重定向标准输出流,也可以重定向标准输入流。假设a.exe所在文件夹(桌面)有一个文件叫i1.txt,使用a.exe <i1.txt >o1.txt,就会从i1.txt获取输入,并且输出到o1.txt(但是clogcerr依然会把提示信息发送到屏幕,尽管没有从键盘获取输入)。 另外,大家应该注意到了,clog<<"a=b!";clog<<endl;被合并成了clog<<"a=b!"<<endl;大家应该都知道这种拼接(同样适用于cincoutcerr以及他们的成员函数,再举个例子,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,编译器会报错的。 还有就是,可以像使用coutcin一样使用它们。