目录
IO类框架
ios
:定义了基本的IO框架,比如IO的错误标志,其中io_base
是所有IO类的基类iostream
:包含了标准输入输出,最常用的头文件fstream
:用于文件输入输出sstream
:用于操作字符串,可以非常方便的进行字符串格式化与解析
IO流机制
在C++中,额外增加了许多IO的特殊机制,这些机制都在ios
头文件中。
C/C++缓冲区同步
C++是在C语言基础上改进的语言,要兼容C语言的绝大部分内容,自然包括C语言本身的IO机制。对于标准输入而言,C++使用cin
、cout
,而C语言使用printf
、scanf
。它们的功能相同,挑选一个使用即可,但是免不了会有用户两个版本交替使用,此时就可能带来同步问题。
因此C++中cout
没有自己的缓冲区,而是与printf
共用缓冲区,这样就算混用两者,也不会造成输出错误。
但是正因为兼容了printf
,cout
就要去做很多额外的工作,那么cout
的效率就会下降,为此,C++也提供了接口sync_with_stdio
用于处理C/C++之间的同步问题。
默认sync = true
,也就是C/C++之间要进行同步。此时可以通过该函数手动调整为false
,接触同步关系:
sync_with_stdio(false);
此时C/C++就不兼容了,如果再混用cout
和printf
,就可能导致输出顺序错误。但是相应的,cout
、cin
的效率会提高。
错误状态标志 iostate
每一个IO对象,内部都存储了字节的错误状态标志,其为四个比特位,分别是goodbit
、eofbit
、failbit
、badbit
,四者含义如下:
iostate | 描述 | good | eof | fail | bad |
---|---|---|---|---|---|
goodbit | 没有错误 | 1 | 0 | 0 | 0 |
eopbit | 文件读取结束 | 0 | 1 | 0 | 0 |
failbit | 逻辑错误 | 0 | 0 | 1 | 0 |
badbit | 读写错误 | 0 | 0 | 1 | 1 |
执行如下代码:
int i;
cout << "请输入数字:";
cin >> i;
cout << "good = " << cin.good() << endl;
cout << "eof = " << cin.eof() << endl;
cout << "fail = " << cin.fail() << endl;
cout << "bad = " << cin.bad() << endl;
次数输入一个不是数字的内容,比如字符x
:
请输入数字:s
good = 0
eof = 0
fail = 1
bad = 0
一旦出现错误,这个cin
对象就无法再使用了,必须通过clear
来重置错误状态标志。
当IO对象内部的错误状态标志不正确,那么再次进行IO时就无法正常进行。clear
用于将错误状态标志重置为goodbit = 1
,其余位变为0
,这样IO对象就可以再次使用了。
int i;
cout << "请输入数字:";
cin >> i;
cout << "good = " << cin.good() << endl;
cout << "eof = " << cin.eof() << endl;
cout << "fail = " << cin.fail() << endl;
cout << "bad = " << cin.bad() << endl;
cin.clear();
cin.get();
cout << "请输入数字:";
cin >> i;
cout << "good = " << cin.good() << endl;
cout << "eof = " << cin.eof() << endl;
cout << "fail = " << cin.fail() << endl;
cout << "bad = " << cin.bad() << endl;
这一次第一次输入字母x
,在clear
之后再输入数字1
。
在clear
之后紧跟了一个get
,这是因为之前输入的字符x
没有被读取走,发生了错误后,字符依然留在缓冲区中,如果clear
后直接cin >>
,那么cin
还会读取到留在缓冲区中的x
,导致一样的错误!所以要用get
先把缓冲区中的字符读走,再让用户输入。
请输入数字:x
good = 0
eof = 0
fail = 1
bad = 0
请输入数字:1
good = 1
eof = 0
fail = 0
bad = 0
tie
tie
是一种特殊的绑定机制,其将一个istream
对象绑定到另一个ostream
对象上。当istream
收到数据时,会刷新对应的ostream
的缓冲区。
默认情况下cin
会绑定到cout
上,而很多用户用完cin
接收数据后,很有可能会用cout
把处理好的数据输出出去。而cout
的缓冲区中可能存有之前没有刷新的数据,为了保证这次输出的数据不受影响,在cin
读取数据时,就把cout
的缓冲区提前刷新一次。通过tid进行绑定的机制,其实就是将未来有可能要使用的缓冲区提前刷新,为后续数据预留空间的机制。
通过函数tie
可以发现,其有两个重载,对于无参的tie
,其返回该对象绑定到的ostream
对象的指针;对于有参的tie
,传入一个ostream*
指针,将该对象重新绑定到其它ostream
对象上,如果传入nullptr
,那么相当于解绑,不绑定任何一个ostream
对象。
*cin.tie() << "hello world!" << endl;
无参的cin.tie()
返回其绑定的对象指针,也就是默认的cout
,在对指针解引用,就相当于cout <<
。此处想证明cin
默认绑定到cout
。
解除cin
的绑定:
cin.tie(nullptr);
由于cin
自动绑定到cout
,那么每次cin
读取数据都会刷新cout
的缓冲区,这会影响到cin
的效率,所以竞赛中会进行一个这样的解绑操作,让cin
读取时不再刷新cout
缓冲区,提高IO效率。
输入输出
标准流对象
C++的标准输入输出定义在头文件<iostream>
中,其中包含istream
和ostream
这两个类,分别用于输入输出。其中istream
定义了基本的cin
对象,用于读取stdin
流,ostream
定义了cout
、cerr
、clog
对象,其中cout
输出到stdout
流,cerr
与clog
输出到stderr
流。
使用上,我们常用<<
和>>
这样的操作符来操作标准流对象,这是基于操作符重载,以ostream
重载<<
为例:
ostream
重载了<<
,常见的内置类型都已经完成了重载,完成对应类型的输出。由于函数重载后会自动推导类型,所以不用像printf
一样考虑变量的类型,再使用%s
,%d
这样的占位符
此处operator
的返回值ostream&
,是为了作用于连续输出:
cout << "hello" << " world!";
这个代码本质是operator<<(operator<<(cout, "hello"), " world!")
,其中operator<<(cout, "hello")
返回了cout
,所以输出完hello
后代码变成operator<<(cout, " world!")
,所以cout
可以这样进行连续输出。
如果想要输出自定义类型,也可以自己重载对应的操作符:
struct date
{
int _year;
int _month;
int _day;
date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
};
ostream& operator<<(ostream& out, const date& d)
{
cout << d._year << "-" << d._month << "-" << d._day;
return out;
}
以上就是给date
类重载了ostream
的输出,注意函数最后要返回ostream
对象的引用。
常见输入策略
在写题时,常需要进行循环输入,一般来说会有以下常用格式:
- 单变量循环输入:
while (cin >> str)
{
// ...
}
- 多变量循环输入:
while (cin >> str1 >> str2 >> str3)
{
// ...
}
while
内部需要一个bool
类型的值判断真假,此处不论输入几个变量都返回istream
对象,为什么可以把这个输入语句直接放到while
内部做判断条件?
其实ios
类重载了operator bool
,也就是到bool
的隐式类型转化,因此istream
可以隐式转化为bool
类型,满足while
的判断要求。
文件IO
在C++中,读写一个文件的基本套路如下:
- 定义一个文件流对象
- 打开指定文件
- 进行IO
- 关闭文件
而流文件对象有ifstream
和ofstream
,分别作用于输入输出。需要头文件<fstream>
。
ifstream
ifstream
的构造函数如上,最常用的是第二个初始化的构造:
explicit ifstream (const char* filename, ios_base::openmode mode = ios_base::in);
filename
:文件名mode
:打开模式
方式 | 功能 |
---|---|
in | 读取文件内容 |
out | 输出内容到文件 |
binary | 以二进制形式打开 |
ate | 打开后,文件指针初始指向文件末尾 |
app | 以追加的形式打开文件 |
trunc | 打开时清空文件内容 |
ate
:只是初始的时候文件指针在末尾,后续可以随意移动文件指针,自由读写。app
:每次进行写入操作,文件指针都会强制跳转到文件末尾,也就是说只能操作文件的末尾。
以上模式在ios_base
类域中,可以混用,每个模式占一个位,传参时以按位或的形式传入:
ifstream ifs ("test.cpp", ios_base::in | ios_base::binary);
#include <iostream>
#include <fstream>
#include <windows.h>
using namespace std;
int main()
{
ifstream ifs("test.cpp", ios_base::in);
string buf;
while (ifs >> buf)
{
cout << buf;
}
ifs.close();
return 0;
}
重载operator>>
在实现时,会自动忽略空格与换行。对于cin
、cout
来说,这可以很方便的读取到变量,而对于文件来说,就不怎么适合了,所以一般不使用这个重载读取文件。
get
为了控制文件读取,一般会使用istream::get
方法:
#include <iostream>
#include <fstream>
#include <windows.h>
using namespace std;
int main()
{
ifstream ifs("test.cpp", ios_base::in);
char ch;
while (ifs.get(ch))
{
cout << ch;
}
ifs.close();
return 0;
}
此时按照字符读取内容,空格与换行也会被读取,最后输出的内容也就是格式的内容了。
read
对于二进制文件来说,一般会使用istream::read
读取:
ofstream
在打开方式上,ofstream
与ifstream
差不多,都是传入一个文件名以及一个打开模式。
write
对于二进制文件写入,则使用write
多:
字符流转换
在字符串处理方面,C++ 提供了两个非常有用的流类:istringstream
和 ostringstream
。这两个类分别用于字符串到数据的解析,以及将数据转换为字符串。需要头文件<sstream>
。
istringstream
istringstream
是 C++ 标准库中的一个类,属于 std::istringstream
。它继承自 std::istream
,可以将一个字符串视为输入流,从中提取各种数据类型。
string input = "123 45.67 Hello";
istringstream iss(input);
int integer;
double floating;
string word;
// 从字符串中提取数据
iss >> integer >> floating >> word;
在这段代码中,首先创建了一个 std::istringstream
对象 iss
,并将 input
作为输入流。通过重载的 >>
操作符,我们可以依次从流中提取整数、浮点数和字符串,并将它们存储到相应的变量中。
这种使用方式非常简洁,适合从格式化的字符串中提取数据。
ostringstream
ostringstream
是 C++ 标准库中的另一个重要流类,属于 std::ostringstream
。它继承自 std::ostream
,主要用于将各种数据类型格式化为字符串。
int integer = 123;
double floating = 45.67;
string word = "Hello";
ostringstream oss;
oss << "Integer: " << integer << ", Double: " << floating << ", String: " << word;
string output = oss.str();
cout << output << endl;
Integer: 123, Double: 45.67, String: Hello
ostringstream
可以很方便的生成格式化字符串。