文章目录
输入/输出
-
C++通过**标准库(Standard Library)**来提供输入输出(IO)机制。具体来说就是iostream库,包括istream和ostream(这两个也是iostream库的基础),分别表示输入流和输出流
-
它定义了4个IO对象
cin(标准输入) [ derived from istream ]
cout(标准输出)[ the left 3 derived from istream ]
cerr(标准错误)
clog(标准日志) -
endl是操纵符 – 换行并刷新缓冲区
-
输出操作符 << 的结果就是输出流本身(在这里意思就是cout<<xx 这个表达式会返回 cout 这个流,这也就是为什么cout可以连续地使用 << )
C++中的IO类(iostream, fstream, stringstream)小结
- 为了支持不同种类的IO处理操作,除了iostream之外,C++还定义了两个类,我们索性三个一起说了:
- iostream定义了用于读写控制窗口的基本类型
- fstream定义了读写命名文件的类型
- sstream定义了读写内存中string对象的类型
- i开头的只可以读
- o开头的只可以写
- io开头的或者没有开头前缀的既可以读也可以写
所有的i都继承istream
所有的o都继承stream
所有的io开头的或者没有前缀开头的都继承iostream
- 由于多态,对istream& 及逆行操作的函数也可以用ifstream或者istringstream调用
IO对象不能拷贝或赋值
- 意味着
- IO对象不能放在容器里面(like auto_ptr),因为容器要求赋值或者复制之后两个容器需要相同
- 不能作为形参和返回类型(传形参和作为返回类型暗含复制操作)
- 不能把它们用作形参或返回类型,一般我们就用引用/指针对象
- 读写IO对象会改变它的状态,所以它的引用不能是const的 总之,只能是通过普通引用的方式来使用它。
条件状态 P247
基本概念
- 表示状态变量的类型为strm::iostate,其中strm是一种流类型,可以是iostream、fstream等。比如,我们定义一个标准IO流状态:
iostream::iostate strm_state=iostream::goodbit;
- IO库存定义了4个iostate类型的contexpr值,表示特定的位模式。这些值用来表示特定类型的IO条件,可以与位运算一起使用来一次性检测或设置多个标志位。
- strm::badbit用来指定流已崩溃。
它表示系统级的错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了。
- strm::failbit
指出一个IO操作失败了。比如说要cin一个int,结果输入了一个string。这种问题通常可以修正,流还可以继续使用。
- strm::eofbit
流达了文件末尾时,该位置位。
到达文件结束位置,eofbit和failbit都会被置位。
- strm::goodbit
表示流未处于错误状态(eofbit\failbit\badbit都没有被置位时)。此值保证为零。
goodbit的值为0,表示流未发生错误。如果badbit、failbit和eofbit任一个置位,则检测流状态的条件会失败。
- 标准库还定义了一组函数来查询这些标志位的状态,假如s是一个流,那么:
s.eof() // 若流s的eofbit置位,则返回true
s.fail() // 若流s的failbit或badbit置位,则返回true
s.bad() // 若流s的badbit被置位,则返回true
s.good() // 若流s处于有效状态,则返回true
-
建议使用状态函数来获得状态结果,因为返回的就是true/false,而不是状态值
-
也可以使用函数: rdstate() 来获得真实的状态值,即返回类型为iostate , 可以挨个与各状态取值判断
-
在实际我们在循环中判断流的状态是否有效时,都直接使用流对象本身,比如:while(cin>>variable){cout<<variable},在实际中都转换为了while((cin>>variable).good()){cout<<variable}。
管理条件状态
IO类库提供了3个函数来管理和设置流的状态:
s.clear(); // 将流s中所有条件状态复位,将流的状态设置为有效,调用good会返回true
s.clear(flags); // 根据给定的flags标志位,将流s中对应的条件状态复位
s.setstate(flags); // 根据给定的flags标志位,将流s中对应的条件状态置位。
s.rdstate(); // 返回一个iostate值,对应流当前的状态。
我们可以这样使用上面的这些成员函数。
iostream::iostate old_state = cin.rdstate(); // 记住cin当前的状态
cin.clear(); // 使用cin有效
process_input(cin); // 使用cin
cin.setstate(old_state); // 将cin置为原有状态
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); // 下failbit和badbit复位,保持eofbit不变。
.clear( )
* clear()会清除所有错误标志位(复位)。
.clear( flag )
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
括号内为位运,这里只复位了failbit和badbit
输出缓冲区的管理 p249
缓冲分类
- 完全缓冲:缓冲区被充满时被清空(内容发送到其目的地)。这种类型的缓冲通常出现在文件输入中。
- 行缓冲:遇到一个换行字符时被清空缓冲区。键盘的输入是标准的行缓冲,因此按下回车键将清空缓冲区。
缓冲机制的存在原因
- 有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
导致缓冲刷新(就是数据真正写到输出设备或文件中)的原因
- 程序正常结束,作为main函数的return操作的一部分( main函数中的return操作包含刷新缓冲操作 ),缓冲刷新被执行
- 缓冲区满了,所以要刷新缓冲,以便后来的数据能继续写入缓冲
使用例如endl的操作符来显式刷新缓冲区 - 在每个输出操作后,我们可以用unitbuf设置流的内部状态来清空缓冲区。对于cerr来说,unitbuf是默认设置的,因此写到cerr的内容都是立即刷新的
- 一个输出流被关联到另一个流时,当读写被关联的流时,关联到的流的缓冲区自动刷新。例如,默认情况下,cin和cerr都关联到cout,因此读cin或者写cerr都会导致cout的缓冲区被刷新
操纵符 manipulator
- endl非常熟悉,在缓冲中加入换行符,并刷新;
- flush,直接刷新。
- ends ( 很少用 ),在缓冲中加入空字符(null , 即\0),刷新
unibuf操纵符
cout << unibuf ; // open
...
cout << nounibuf ; // close
- cerr默认就是使用了unibuf控制符,这样关闭了缓冲,每次写都会直接输出。
关联流
- 使用tie来将流与输出流关连起来,有重载。
cin.tie(cout) ; // 将cin与cout关连;
cin.tie(nullptr) ; // 停止cin的关连
# cin.tie( 0 )也可以
cin.tie() ; // 重载;返回目前cin关连的流。
- 每个流同时最多主动关联一个输出流(对的 参数类型是输出流 没有输入流的参数签名),但一个ostream可以同时被多个输入/输出流关联。
- 被关联的流在主动关联者做任一个输入/输出动作的时候都会刷新自己的缓冲区
- 默认cin和cout是绑定的 – 为了实现交互式程序,所以带来了cin比较慢的说法= = more details can be refered to :
http://www.hankcs.com/program/cpp/cin-tie-with-sync_with_stdio-acceleration-input-and-output.html
- 默认cin和cout是绑定的 – 为了实现交互式程序,所以带来了cin比较慢的说法= = more details can be refered to :
程序崩溃
- 程序崩溃时 就不会刷新缓冲区,所以有必要显式刷新缓冲区,防止某次崩溃丢失信息
<fstream.h>
- 特有的open & close 函数
- 绑定文件的两种方式
string infile="../input.txt";
string outfile = "../output.txt";
# method 1
ifstream in(infile); // 定义时打开文件
# method 2
ofstream out;
out.open(outfile); // 用open打开文件
failbit
- 如果调用open失败,failbit会被置位,所以调用open时进行检测通常是一个好习惯 ( 或者直接 if( stream ) 进行check )
- 如果用一个读文件流ifstream去打开一个不存在的文件,将导致读取失败,而如果用一写文件流ofstream去打开一个文件,如果文件不存在,则会创建这个文件。
- 一旦一个文件流已经被打开,它就保持与对应文件的关联。**实际上,对一个已经打开的文件流调用open会失败,并会导致failbit被置位,随后的试图使用文件流的操作都会失败。**为了将文件流关联到另外一个文件【重新捆绑】,必须首先关闭已经关联的文件( xfstream.close( ) )。
- 一般,close之后还会跟着clear( ), 否则就算关闭一个流也不能清除其错误状态。
- 所以启用一个流进行文件绑定时候,最保险的就是先不管三七二十一 close() 然后clear( )
文件模式file mode p254
-
也是fstream 特有的
-
整型常量,跟strm::iostate 一样,支持位运算
ifstream: in (默认方式)
ofstream: out(默认方式) 、app (append) 、trunc
ate ( at the end )、binary ( 二进制 ) 支持任意文件读写流,且可以与其他任何文件模式组合使用。
所有的file mode 都支持fstream -
默认情况下,即使我们没有指定trunc,以out模式打开的文件也缺省为trunc(被截断)。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
这么理解更好些: app和trunc就是为了和out模式组合一起用的 ,其中out模式默认是捆绑trunc模式的= =
#comment: 在这几条语句中,file1都被截断
#comment: 隐含以输出模式打开文件并截断文件
ofstream out("file1");
#comment: 隐含地截断文件
ofstream out2("file1", ofstream::out);
#comment: 显式截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc);
#comment: 为了保留文件内容,我们必须显式指定app
#comment:隐含为输出模式
ofstream app("file2", ofstream::app);
ofstream app("file2", ofstream::app | ofstream::out);
- 有些文件模式不能混用 详见P255 文件读写规则
<stringstream.h> p257
- 不同于fstream的参数是char* 了,这个参数就是string类型的
- 一次读取一行文本,然后将其中的单词分别取出保存在一个vector中。
- 使用istringstream + >> 做类似split的操作
string line,word;
while (getline(cin, line))
{
vector<string> wordList;
istringstream lineText(line);
#comment 遇到空白符会自动断掉,并且忽略空白符(' ','\n')
while (lineText >> word)
{
wordList.push_back(word);
}
}
- getline( stream , string )
ostream 可以实现类似itoa和ftoa的操作
int num1 = 42;
double pi = 3.1415926;
string str = "some numbers";
ostringstream formatted;
formatted << str << pi << num1;
cout << formatted.str() << endl;
- .str() 获取stringstream里存储的string的副本 是stringstream有几个特有操作之一。
string s;
stringstream strm(s);// 保存s的一个拷贝,此构造函数是explicit的。
strm.str(); // 返回strm所保存的string对象的拷贝。
strm.str(s); // 将s拷贝到strm中,返回void。
istringstream 可以实现类似atoi和atof的操作
ostringstream unformatted( formatted .str() );
unformatted >> dummy >> pi >> num1;
miscellaneous
宽字符
起初C语言使用char(也就是ASCII编码),只占有8位,后来随着需求增多,需要使用Unicode编码,Unicode编码占据的位数就增多了
- wchar_t类型 ( wide-character )
- 宽字符对应的iostream和fstream还有stringstream都是在前面加了个w
逗号操作符
- 使用逗号运算符是为了把几个表达式放在一起。
整个逗号表达式的值为系列中最后一个表达式的值。
从本质上讲,逗号的作用是将一系列运算按顺序执行。
最右边的那个表达式的值将作为整个逗号表达式的值,其他表达式的值会被丢弃。例如:
var = (count=19, incr=10, count+1);
- 常用于for循环代码中 ,like:
while(cin>>c,!cin.eof()){
//do your exception check
}
这里只有遇到输入流的末尾的时候才会停止循环,过程中忽视输入流的其他错误
由于历史原因,IO便准库里面的fstream使用C风格字符串作为参数
if(cin) 就相当于 if(cin.good())
补充知识 TL;DR
- 关于流的输出格式啊、随机访问流(tellp 、seekp等操作)
https://www.cnblogs.com/ronny/p/3717713.html