本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
C++ Primer 笔记 第八章
C++ 提供了全面的 IO 类用于读写:
概念上来讲,输入输出操作不应该因操作对象而异。C++ 通过继承(Inheritance)的方式来保证读 / 写的一致性。IO 类的继承关系如下图:
继承也包括了操作继承。比如 ifstream 继承自 istream,因此 ifstream 也能使用 stream 操作符(“»” , “«”)。
IO类的对象是不能被拷贝也不能被赋值的。因此:
IO 类提供了一系列的 flag / 函数 来判断和操作 stream 当前的状态:
一般情况下,输入的流程如下:
通常可以使用条件来判断输入是否有效:
while (cin >> word)
//ok: read op successful
Flag 的作用是什么?
stream 做为条件时,只能反馈 stream 是否有效。通过 flag 可以判断 stream 是出于什么原因无效。
一共存在着 4 种类型为 strm::iostate 的 flag,分别指代不同的 IO 状况,其中:
除此之外还有一些用于判定的成员函数。比如 fail()
函数,之前以 stream 对象作为条件的判断:
while(cin)
实际上等同于
while(!cin.fail())
所有的 flag 都支持位运算。
除了以上判定函数之外,IO 类还提供了一系列的管理函数用于检测问题的类型和改变 stream 的状态:
// remember the current state of cin
auto old_state = cin.rdstate(); // remember the current state of cin
cin.clear(); // make cin valid
process_input(cin); // use cin
cin.setstate(old_state); // now reset cin to its old state
使用位运算可以很方便的设定 flag:
// rdstate returns a bit sequence which repersents the status of all bits
// if any bit is on (1), neg it to 0, then and it with the bit sequence
// so that we can turns off failbit and badbit but all other bits unchanged, in this case
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
Buffer 的作用是存储程序的输入和输出。Buffer的由 ostream 管理,可以将好几种不同类型的输出组合成单一的系统级别操作。这样的方式可有效提升输出的执行效率。
Buffer 在以下条件下会被 flush(刷新):
上面提到了有三种显式刷新 buffer 的方法:endl, flush, ends。 他们的区别主要在于:
除此之外还有:
使用的方法:
cout << endl;
cout << flush;
cout << ends;
cout << unitbuf;
cout << nounitbuf;
注意:如果程序非正常退出(崩溃),buffer 是不会自动 flush 的。所以在 debug 的时候一定要注意考虑 buffer 里的内容:你很可能更新了程序,但得到的还是原来的结果,原因就是 buffer 在作怪。
为什么要进行绑定?
之所以这么做事因为我们通常希望在进行交互的时候给予用于一个干净的 Buffer 用于输入。绑定的结果是对某一个绑定对象进行输入时,会刷新另外一个对象的 buffer。通过绑定,我们可以确保在用于输入信息之前,buffer 区的输出信息已经刷新完毕了。
C++ 通过什么手段进行绑定?
通过 tie
成员函数。该函数分两个版本:
tie(); // return a pointer that points to a tied output stream, or a nullptr if the stream is not tied.
tie(&ostream); //return the pointer that points to the ostream and ties ostream to the caller.
常见用法:
cin.tie(&cout); //demonstration only, cin / cout has been tied by default.
ostream *old_tie = cin.tie(nullptr); // The pointer old_tie points to the ostream that cin is currently tying.
cin.tie(&cerr); //ties cin to cerr
cin.tie(old_tie); //ties cin & cout again.
有几点需要注意的是:
使用文件输出类的时候,我们需要调用 <fstream>
header。这个 header 定义了三种类型来支持文件输出输出:ifstream, ofstream, iofstream。因为这个类继承自 iostream,所以我们对 fstream 对象也可以使用 cin, cout 等对象使用的类成员。而继承类自己也定义了一些类型和函数用于特定的操作:
fstream fs; // creates an unbound file stream
fstream fs(s); // create a file stream and open the file s.s can be a string or pointer to a cstring.
fstream fs(s, mode); // create a fstream named s, with specified mode.
fs.open(s); //open the file named s.
fs.open(s, mode); // open the file name s, with specified mode.
fs.close(); // close fstream fs.
fs.isopen(); // return a bool that indicating whether the file associated with fs was opened, or closed.
对文件进行读写,首先需要两个对象:
ifstream in(ifile); //ifstream type 'in', reading from file 'ifile'.
ofstream out; //ofstream type 'out'
out.open(ifile + ".copy"); //output to file "ifile.copy"
其次,为了确保在输出正常的情况下才进行输入,需要判定输出对象的状态:
if (out)
如果输出对象不在正常状态则不会进行输入。in.close();
in.open("newfile");
main函数有两个 parameter: int argc
, char argv
。利用这两个 parameter 可以循环对多个文件进行输入:
//Since argv[0] stores the program name, the array range for the files begins at 1
for (auto p = argv + 1; p!= argv + argc; ++p) {
//create a file with specific name and open it in each iteration
ifstream ifs(*p);
//if open successful
if (ifs) {
process(ifs);
}
else
cerr << " couldn't open " + string(*p);
}
由于 ifs
对象属于循环中的局部变量,因此每次迭代完成之后都会自动解除与当前文件的绑定(关闭)。
fstream 对象被销毁时会自动调用 close 函数。
open() 函数中的第二个参数:文件模式 ( File Mode) ,决定了 fstream 如何使用文件:
一些需要注意的规则:
如果在写入的时候想保留文件中的先前内容,必须指定 app 模式:
ofstream ofs1("file", ofstream::app);
ofstream ofs2("file", ofstream::out | ofstream::app);
Stringstream 定义了三种类型用于支持 string 在内存中的读写:
这些类型被定义在 <sstream>
头文件中;除此之外,还有一些相关的操作:
sstream ss; //ss is an umbound stringstream.
sstream ss(s); //ss is an stringstream that hold a copy of the string s.
s.str(); // returns a copy of the string that ss holds.
ss.str(s); // copies the string s into ss. return void.
通常情况下,如果我们希望对某一行输入中单个的 string 进行操作,就可以使用到 istringstream 对象。istringstream 会一直读取 stream buffer 中的内容,并以空格作为分界线将之前的内容作为一个 string。该 string 可以存储到对应的变量中。来看看书上的例子:
// members are public by default; see § 7.2 (p. 268)
struct PersonInfo {
string name;
vector<string> phones;
};
while (getline(cin, line)) {
PersonInfo info; // create an object to hold this record's data
istringstream record(line); // bind record to the line we just read
record >> info.name; // read the name
while (record >> word) // read the phone numbers
info.phones.push_back(word); // and store them
people.push_back(info); // append this record to people
}
整个逻辑是:
info
装载数据record
绑定当前的一段输入,对其内部的 string 进行处理:info
对象中,此时 record
输入的是第一个 stringinfo.phone
中。info
对象存储到 PersonInfo vector 中。ostringstream 可以用于希望先构造输出,最后再打印的情况:
for (const auto &entry : people) { // for each entry in people
ostringstream formatted, badNums; // objects created on each loop
for (const auto &nums : entry.phones) { // for each number
if (!valid(nums)) {
badNums << " " << nums; // string in badNums
} else
// ''writes'' to formatted's string
formatted << " " << format(nums);
}
if (badNums.str().empty()) // there were no bad numbers
os << entry.name << " " // print the name
<< formatted.str() << endl; // and reformatted numbers
else // otherwise, print the name and bad numbers
cerr << "input error: " << entry.name
<< " invalid number(s) " << badNums.str() << endl;
}
上面例子希望进行三件事:
在这里,ostringstream 则是扮演了“暂存分类” 角色:
badNums
是否为空,输出指定的信息。