IO#

组成部分#
C++定义了ios这个基类来定义输入输出的最基本的操作,C++io库所有的类都继承自这个类即可
istream、ostream这两个类直接继承自ios类ostream类定义了从内存到输出设备(例如显示器)的功能,我们最常用的cout就是ostream类对象istream类定义了输入设备(例如键盘)到内存的功能,我们最常用的cin就是istreasm类对象- iostream文件定义了
ostream、istream类对象,就是cin和cout
ifstream、ofstream类分别继承自istream类和ostreasm类ifstream定义了磁盘到内存的功能,因为istream重载了">>“运算符,所以ifstream对象可以使用”>>“运算符来将文件数据写入内存,除了”=“的所用重载运算符都是可以被继承的ofstream定义了内存到磁盘的功能,与ifstream同理,也可以用”<<“操作数据流fstream文件引入了ifstreasm与ofstream,所以我们只要引入ifstream这个头文件就可以操作文件功能了
istringstream、ostringstream分别继承自istream类和ostream类istringstream定义了从指定字符串到特定内存的功能,与ifstream同理,可以用”>>“运算符操作数据ostringstream定义了从特定内存到指定字符串的功能,也可以用”<<“运算符操作数据sstream定义了istringstream和ostringstreasm
所以我们使用io库主要就三个头文件
sstream iostream fstream
注意事项#
io对象无法使用拷贝构造函数和赋值运算符#
#include <iostream>
#include <fstream>
#include <sstream>
int main() {
std::istream myCin(std::cin); //BUG: 无法使用拷贝构造
return 0;
}所以我们使用流对象无法使用值传递,一般使用引用进行传递
io对象的状态#
- io操作是非常容易出现错误的操作,一些错误是可以修复的,另一部分则发生在系统更深处,已经超出了应用程序可以修正的范围
- 比如我们使用
cin向一个int类型的数中输入一个字符串,会使cin这个对象出现错误所以我们在使用io对象都应该判断io对象的状态
- 比如我们使用
while(cin >> val) or if(cin >> val);不要只用这两个进行控制,最好搭配iostate来使用
#include <ios>
#include <iostream>
#include <limits>
#include <stdexcept>
int main() {
int i = 10;
while(std::cin >> i , !std::cin.eof()) {
if(std::cin.bad()) { //NOTE: 系统级错误
throw std::runtime_error("cin is corrupted\n");
}
if(std::cin.fail()) { //NOTE: 格式不对
std::cin.clear();//NOTE: 先清空状态栏
std::cin.ignore(std::numeric_limits<std::streamsize>::max() , '\n');//NOTE:再忽略缓存区
std::cout << "data format error, please try again" << std::endl;
continue;
}
std::cout << i << std::endl;
}
std::cout << "process completed" << std::endl;
return 0;
}- 我们需要知道流对象错误的原因,因为不同的错误需要不同的处理方法。
- io库定义了
iostate类型,可以完整的表示io对象当前的状态。在不同的平台中,iostate实现方法略有区别,在vs中直接用int来代表iostate类型,将不同的位置1以表示不同的状态。可以与位操作符一起使用来一次检测或设置多个标志位。 - 可以用
rdstate函数来获得io对象当前用iostate类型表示的状态
- io库定义了
#include <iostream>
#include <fstream>
#include <string>
void checkStreamState(const std::ios& stream) {
// 1. 使用 rdstate() 获取当前的 iostate (本质上是一个 int 整数)
std::ios::iostate state = stream.rdstate();
std::cout << "当前状态数值 (rdstate): " << state << std::endl;
// 2. 使用位操作符 & 来检测特定的标志位是否被置为 1
// std::ios::badbit: 系统级故障(如读写硬件错误)
// std::ios::failbit: 逻辑错误(如期望读数字却读到了字母)
// std::ios::eofbit: 到达文件末尾
// std::ios::goodbit: 值为0,表示没有任何错误位被置位
if (state & std::ios::badbit) std::cout << "[位检测] 发现 badbit: 发生严重系统错误!" << std::endl;
if (state & std::ios::failbit) std::cout << "[位检测] 发现 failbit: 数据格式不匹配或操作失败。" << std::endl;
if (state & std::ios::eofbit) std::cout << "[位检测] 发现 eofbit: 已触及流的末尾。" << std::endl;
if (state == std::ios::goodbit) {
std::cout << "[位检测] 状态正常 (goodbit)" << std::endl;
}
std::cout << "-----------------------------------" << std::endl;
}
int main() {
int number;
// 情况 A: 正常输入
std::cout << "请输入一个数字: ";
if (std::cin >> number) {
checkStreamState(std::cin);
} else {
// 情况 B: 故意触发错误 (比如输入一个字母 'abc')
std::cout << "\n[错误触发] 输入无效!" << std::endl;
// 此时 iostate 的 failbit 位会被置为 1
checkStreamState(std::cin);
// 3. 使用 clear() 配合位运算可以重置特定状态
// 比如:只清除 failbit,但保留其他可能的错误(虽然通常直接用 cin.clear())
std::cout << "尝试清除错误状态..." << std::endl;
std::cin.clear();
// 清除状态后需要同步清空缓冲区,否则错误的输入还在缓存里导致死循环
std::string dummy;
std::cin >> dummy;
checkStreamState(std::cin);
}
return 0;
}- iostate类型有以下状态
- badbit状态,系统级错误,一旦表示badbit的位置被置为1,流对象就再也无法使用了。
- failbit状态,代表可恢复错误,比如想读取一个数字却读取了一个字符,这种错误就是可以恢复的。当badbit位被置为1时,failbit也会被置1。
- eofbit状态,当到达文件结束位置时,eofbit和failbit位都会被置为1
- goodbit状态,表示流对象没有任何错误。
只要badbit、falbit、eofbit有一位置被置为1,则检测流状态的条件就会失败。
标准库还定义了一组成员函数来查询这些标志位的状态。
- good()函数在所有错误均为置1的情况下返回
true - bad() , fail() , eof()函数在对应位置被置1的情况下返回
true。因为badbit位被置1或eofbit位被置1时,failbit位也会被置1。所以用fail()函数可以准确判断出流对象是否出现错误。 - 实际上,我们将流对象当作条件使用的代码就等价于
!fail()
- good()函数在所有错误均为置1的情况下返回
流对象的管理
- rdstate函数,返回一个iostate值,对应当前流状态
- setstate(flag)函数,将流对象设置为想要的状态
- clear函数,是一个重载函数
- clear()将所有位置0,也就是goodbit状态
- clear(flag)将对应条件状态标志位复位
- ignore函数
- 作用:提取输入字符并丢弃他们
- 函数原型:
istream& ignore(streamsize n = 1, int delim = EOF读取前n个字符或在读这n个字符进程中遇到delim字符就停止,把读取的这些东西丢弃
内存与磁盘的交互#
fstream相对于iostream多了很多独有操作
- io库默认没有给ifstream和ofstream提供对象,需要自己创建
- fstream对象创建方式有3种
#include <iostream>
#include <string>
#include <fstream>
int main() {
// NOTE: 1. 默认构造函数进行定义
std::fstream fs;
std::ifstream ifs;
std::ofstream ofs;
// NOTE: 2. 在创建流对象时打开想要的文件,例如ifstream,fstream(s),s可以是字符串,也可以是c风格的字符串指针,文件的mod依赖于流对象的类型
std::fstream fs("data.txt", std::ios::in | std::ios::out); // 也可以在打开文件时指定文件的打开类型
std::ifstream ifs("data.txt");
std::ofstream ofs("data.txt");
// NOTE: 3. fstream.open(s)函数,将文件与fstream绑定,s可以是一个string,也可以是一个c风格字符串指针
std::fstream fs;
fs.open("data.txt");
// NOTE: 4. fstream.close()函数,关闭文件, 一定要写,可以用RAII封装
fs.close();
// NOTE: 5. fstream.is_open()函数,返回一个bool值,指出与fstream关联的文件是否成功打开且尚未关闭
return 0;
}例子:让客户输入文件名称,如果文件不存在,就让客户重新输入文件名称,如果文件存在,就将文件全部输出
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <fstream>
int main() {
std::string fileName;
std::string fileContent;
while ( std::cin >> fileName, !std::cin.eof() ) {
if ( std::cin.bad() ) {
throw std::runtime_error( "cin is corrupeted\n" );
}
std::ifstream ifs( fileName );
if ( ifs.is_open() ) {
while ( std::getline( ifs, fileContent ) ) {
std::cout << fileContent << std::endl;
}
if ( ifs.bad() ) {
throw std::runtime_error( "ifs is corrupted\n" );
}
ifs.close();
} else {
ifs.clear();
ifs.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
std::cout << "file not exist , please try again\n";
continue;
}
}
std::cout << "process complete\n";
return 0;
}文件模式#
| mode | description |
|---|---|
| ios::in | read only |
| ios::out | write only(会覆盖原文件) |
| ios::app | append(每次都从文件尾开始写入) |
| ios::ate | 打开后立即定位到文件尾(At the end) |
| ios::trunc | 截断模式,如果文件存在则清空其内容 |
| ios::binary | 以二进制模式打开 |
#include <fstream>
#include <iostream>
void basicModes() {
// 1. 只读模式 (ios::in)
// 注意:如果文件不存在,打开会失败
std::ifstream input("test.txt", std::ios::in);
// 2. 只写模式 (ios::out)
// 注意:默认会丢失原文件内容(隐含了 trunc)
std::ofstream output("test.txt", std::ios::out);
// 3. 追加模式 (ios::app)
// 写入的数据永远添加在文件末尾,不会覆盖之前的内容
std::ofstream appendFile("log.txt", std::ios::app);
appendFile << "新日志条目\n";
}#include <fstream>
#include <iostream>
void combinedModes() {
// 1. 读写模式且不销毁原内容 (in | out)
// 这种模式下你可以定位(seek)到文件任意位置修改内容
std::fstream fs("data.bin", std::ios::in | std::ios::out | std::ios::binary);
// 2. 创建一个既能读又能写的空文件 (in | out | trunc)
// trunc 会确保如果文件存在,先清空它
std::fstream newFile("config.txt", std::ios::in | std::ios::out | std::ios::trunc);
// 3. 以二进制追加方式写入 (out | app | binary)
// 常用于记录二进制日志或保存图片/音频片段
std::ofstream binLog("trace.dat", std::ios::out | std::ios::app | std::ios::binary);
}字符串流#
string流可以向string对象写入数据,也可以从string对象读取数据,与文件操作类似,只不过数据交互变成了从内存到内存。
string流对象继承自 iostream 对象,除了继承得来的操作,string流对象还有自己的成员来管理流相关的string
- 对于string流,io库是没有像cin cout这样的自定义流对象的。流对象需要我们自己去定义
- sstream ss sstream代表一个string流对象的类型
- sstream ss(string) 表示一个绑定了string的拷贝的string流对象
- ss.str(): 返回ss所保存的string拷贝
- ss.str(string): 将绑定的string拷贝到新的string对象中
种类#
- istringstream: 从string对象读取数据
- ostringstream: 向string对象写入数据
- stringstream: 既可以从string对象读取数据,也可以向string对象写入数据
作用#
- 对数据类型进行转化,也就是string和其他类型的转化,这是string流对象最重要的功能。
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <string>
int main() {
// NOTE: string转int
std::string str( "nb" );
std::stringstream ssin( str );
int i = 0;
ssin >> i;
if ( ssin.bad() ) {
throw std::runtime_error( "cin is corrupted\n" );
} else if ( ssin.fail() ) {
ssin.clear();
ssin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
std::cout << "bad string format\n";
} else {
std::cout << i << '\n';
}
return 0;
}#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <string>
int main() {
// NOTE: int转string类型
int strcI = 100;
std::stringstream ssin;
std::string str;
ssin << strcI << std::endl;
if ( ssin.bad() ) {
throw std::runtime_error( "ssin is corrupted\n" );
} else {
std::cout << ssin.str();
}
return 0;
}- 用于对空格分隔的字符串切分
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <string>
int main() {
std::string src( "hello world ni hao" );
std::string dest;
std::stringstream ssin( src );
while ( ssin >> dest ) {
std::cout << dest << '\n';
}
if ( ssin.bad() ) {
throw std::runtime_error( "cin is corrupted\n" );
}
return 0;
}
