A Gentle Introduction to C++ IO Streams
Manasij Mukherjee
https://www.cprogramming.com/tutorial/c++-iostreams.html
C++ 的一大优势就是其IO系统,IO流。
以下特性使得IO流可以用于各种种类的数据管理场景:
- 流(stream)的本质是一个字符序列。字符可能是普通的字符(char)或者宽字符(wchat_t)。流为各种类型的存储介质(比如文件)提供了一个统一的基于字符的接口,用户不需要了解向存储介质读写的具体细节。任何可以被作为一种流来进行写操作的对象都可以被写入任何类型的流中。换句话说,只要对象具有流式表示,那么任何的存储介质就可以接收这种表示。
- 流可以用于内置数据类型,并且用户可以通过重载(overloading)插入运算符(<<)和提取运算符(>>)来将用户自己定义的数据类型写入或者写出流。
- 库函数中统一的访问方式使得流易于使用。
What do input and output really mean?
首先,将信息理解为一个字符流。之所以这样理解,是因为无论我们通过键盘敲入什么内容,都只能被理解为许多个字符。假设用户敲入数字7479。。。等等,实际上,程序员并不能真正知道用户敲键盘敲入的到底是数字7479还是说四个字符'7','4','7','9'。怎么理解它们完全取决于程序员,而这些字符是否可以被正确理解为程序员想要的类型,完全取决于输入流中的字符是否可以以目标数据类型的表示方式来理解。
输入的字符需要以一种有序的组织方式来存放,这样它们之后才能被作为各种数据类型来使用,除非只是将他们用作字符数组。
IO 流不仅仅定义了字符流和标注数据类型之间的关系,还允许用户去定义字符流和自定义类的关系。
How do streams work?
流是用于存储、缓存文件、以及其他存储介质的序列化接口。不同存储介质之间的区别隐藏在接口之下。
对于它们的接口来说,流的序列化本质非常重要。用户无法在流中直接进行随机读或者写。尽管可以通过seek
等方式来指定读写的位置。
使用序列化的表示形式给了所有设备一种统一的接口。许多设备具有同时生产和消费数据的能力;如果数据被持续的生产,那么最简单的理解读取数据的方式就是理解为获取流中下一个字符。
与实际介质中紧密结合的底层接口是字符缓冲区(character buffer),它可以被认为是流的核心。作为一个缓冲区,如果数据过多,那么它并不会保存流的所有内容,因此不能将其用于随机访问。
基本流操作的重点在于:
- 首先,流需要按照适当的数据类型(比如使用
std::string
来初始化stringstream
,使用filename
初始化fstream
)、适当的模式(比如使用ios::in
初始化input)来进行初始化 - 在这之后,可以通过
get
或者put
指针来明确IO操作发生的位置。取决于打开流的方式,位置可能已经被设置好了(比如,如果使用ios::app
来打开文件,那么get pointer
就指向流的末尾) - 一旦位于了流中的合适的位置,输入和输出就是通过
<<
和>>
操作符来完成的。
Error handling with IO streams
An example of creating a stream-enable object
#include <ctime>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace std;
std::string timestamp();
class LogStatement;
ostream& operator<<(ostream& ost, const LogStatement& ls);
class LogStatement {
public:
LogStatement(std::string s) : data(s), time_string(timestamp()) {}
friend ostream& operator<<(ostream& ost, const LogStatement&);
private:
std::string data;
std::string time_string;
};
ostream& operator<<(ostream& ost, const LogStatement& ls) {
ost << "~|" << ls.time_string << '|' << ls.data << "|~";
return ost;
}
std::string timestamp() {
ostringstream stream;
time_t rawtime;
tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
stream << (timeinfo->tm_year) + 1900 << " " << timeinfo->tm_mon << " "
<< timeinfo->tm_mday << " " << timeinfo->tm_hour << " "
<< timeinfo->tm_min << " " << timeinfo->tm_sec;
return stream.str();
}
int main(int argc, char** argv) {
if (argc < 2) {
return -1;
}
ostringstream log_data;
for (int i = 1; i < argc; i++) {
log_data << argv[i] << ' ';
}
LogStatement log_entry(log_data.str());
clog << log_entry << endl;
ofstream logfile("logfile", ios::app);
if (!logfile) {
return -1;
}
logfile << log_entry << endl;
logfile.close();
return 0;
}
ostream
是所有进行写操作的类的基类,比如ofstream
和ostringstream
。标准流对象:std::cout
,std::cerr
,std::clog
以及它们的宽版本都是ostream
类的对象。- 使用
stringstream
来简化字符串的处理
Parts of the IO stream library
Standard Stream Objects for Console IO:(cout, cin, clog, etc.)
这些对象都声明在<iostream>
头文件中,并且为控制IO提供了一致的接口。
File IO
文件IO是通过手动声明ifstream, ofstream or fstream
类的对象并且通过使用流的open
方法来将文件和流绑定起来。
#include <iostream>
#include <fstream>
using namespace std;
int main(){
ofstream ofs("a.txt", ios::app);
if(ofs.good())
{
ofs << "Hello a.txt, I'm appending this on you.";
}
return 0;
}
String Streams
Strings are streams and streams are strings. 它们都是字符数组,但是它们具有完全不同的接口(random access strings vs serial stringstreams)。
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
stringstream my_stream(ios::in|ios::out);
std::string dat("Hey, I have a double : 74.79 .");
my_stream.str(dat);
my_stream.seekg(-7,ios::end);
double val;
my_stream>>val;
val= val*val;
my_stream.seekp(-7,ios::end);
my_stream<<val;
std::string new_val = my_stream.str();
cout<<new_val;
return 0;
}
The lower level, where streams meet buffers
his is the most interesting and confusing part of this library, letting you manipulate streams at their lowest levels, bytes and bits. 这是通过抽象基类streambuf
来实现的。stringbuf
和filebuf
都是继承自streambuf
。而每一个stream
对象都有它们中的一个作为它们的backbone。这些对象都具有函数rdbuf()
,该函数返回一个指针指向 underlying stream buffer。
#include <iostream>
#include <fstream>
using namespace std;
int main(){
ifstream ifs("a.txt");
ofstream ofs("a.txt.copy", ios::trunc);
if(ifs && ofs){
ofs<<ofs.rdbuf();
}
return 0;
}