一、C++ IO库的架构
C++标准库中的IO系统基于流(Stream)的概念,分为三层结构:
- 流对象(如
cin
,cout
,fstream
) - 流缓冲区(streambuf,负责底层数据处理)
- 数据源/目的地(如内存、文件、控制台)
主要头文件:
<ios>
:基础IO类定义<istream>
/<ostream>
:输入输出流<iostream>
:控制台输入输出<fstream>
:文件流<sstream>
:字符串流
二、C++ IO类的层次结构
1. 基础类
- ios_base:这是所有 IO 类的基类,它定义了一些通用的状态标志、格式标志和事件处理机制。它不涉及具体的输入输出操作,主要用于管理和维护 IO 流的状态和格式。
- ios:继承自
ios_base
,它包含了流的缓冲区指针和一些状态信息,为后续的输入输出操作提供了基础。
2. 输入输出类
- istream:用于输入操作的类,继承自
ios
。它定义了一系列用于从流中读取数据的成员函数,如operator>>
、get
、getline
等。 - ostream:用于输出操作的类,同样继承自
ios
。它定义了一系列用于向流中写入数据的成员函数,如operator<<
、put
等。 - iostream:继承自
istream
和ostream
,它既支持输入操作,也支持输出操作。
3. 文件 IO 类
- ifstream:继承自
istream
,用于从文件中读取数据。 - ofstream:继承自
ostream
,用于向文件中写入数据。 - fstream:继承自
iostream
,支持对文件的读写操作。
4. 字符串 IO 类
- istringstream:继承自
istream
,用于从字符串中读取数据,就像从输入流中读取一样。 - ostringstream:继承自
ostream
,用于将数据写入字符串中。 - stringstream:继承自
iostream
,支持对字符串的读写操作。
层次结构示意图
三、核心基类详解
1. ios_base
(位于<ios>
)
- 功能:定义所有流类的公共属性和状态
- 关键成员:
// 格式化标志 fmtflags setf(fmtflags flags); // 设置格式 streamsize precision(streamsize prec); // 浮点数精度 // 流状态 iostate rdstate() const; // 获取当前状态 bool good() const; // 流是否正常 bool eof() const; // 是否到达流末尾
2. ios
(继承自ios_base
,位于<ios>
)
- 功能:管理流缓冲区和错误状态
- 关键成员:
class ios : public ios_base {
public:
// 流状态管理
bool good() const; // 状态正常
bool eof() const; // 到达文件尾
bool fail() const; // 可恢复错误
bool bad() const; // 严重错误
void clear(iostate state = goodbit);
iostate rdstate() const;
// 流缓冲区操作
streambuf* rdbuf() const;
streambuf* rdbuf(streambuf* sb);
};
职责:
- 维护流状态(goodbit、eofbit、failbit、badbit)
- 管理关联的
streambuf
对象
3、输入输出流基类
1. istream
(输入流,位于<istream>
)
- 功能:处理输入操作
- 关键成员:
// 基础输入 istream& operator>>(T& val); // 重载的提取操作符 int get(); // 读取单个字符 istream& getline(char* s, streamsize n); // 读取一行 // 高级操作 streamsize readsome(char* s, streamsize n); // 读取可用数据 //简易完整版 class istream : virtual public ios { public: // 基础输入 istream& operator>>(int& val); istream& operator>>(string& str); // 非格式化输入 int get(); istream& get(char& c); istream& getline(char* s, streamsize n); // 高级操作 streamsize readsome(char* s, streamsize n); istream& ignore(streamsize n = 1, int delim = EOF); };
关键方法:
>>
运算符重载(格式输入)get()
读取单个字符
2. ostream
(输出流,位于<ostream>
)
- 功能:处理输出操作
- 关键成员:
// 基础输出 ostream& operator<<(T val); // 重载的插入操作符 ostream& put(char c); // 输出单个字符 // 刷新缓冲区 ostream& flush(); // 强制刷新缓冲区 class ostream : virtual public ios { public: // 基础输出 ostream& operator<<(int val); ostream& operator<<(const char* str); // 非格式化输出 ostream& put(char c); ostream& write(const char* s, streamsize n); // 控制输出位置 pos_type tellp(); ostream& seekp(pos_type pos); };
关键方法:
<<
运算符重载(格式输出)put()
输出单个字符write()
原始字节输出
3. iostream
(继承自istream
和ostream
,位于<istream>
)
- 功能:同时支持输入输出的流(如标准控制台流)
class iostream : public istream, public ostream {
public:
// 多继承类,支持双向操作
};
典型应用:
- 标准控制台流
cin
/cout
/cerr
的实现
四、<< 、>>运算符
1. <<
运算符(输出运算符)(像水波扩撒)
基本用途
<<
运算符一般用于向输出流里写入数据,最常见的输出流便是 std::cout
,它代表标准输出(通常是控制台)。此运算符把数据发送到输出流,然后输出流会把数据显示在对应的设备上。
语法
output_stream << data;//data->output_stream
这里的 output_stream
是输出流对象,像 std::cout
、std::ofstream
(用于文件输出)等;data
是要输出的数据,可以是基本数据类型(例如 int
、double
、char
等),也可以是自定义类型。
示例代码
#include <iostream>
int main() {
int num = 10;
double pi = 3.14;
std::string str = "Hello, World!";
// 输出整数
std::cout << "整数: " << num << std::endl;
// 输出浮点数
std::cout << "浮点数: " << pi << std::endl;
// 输出字符串
std::cout << "字符串: " << str << std::endl;
return 0;
}
std::cout << "整数: "
:把字符串"整数: "
发送到标准输出流。<< num
:接着把变量num
的值发送到标准输出流。<< std::endl
:向标准输出流发送换行符,同时刷新输出缓冲区。
链式调用
<<
运算符支持链式调用,也就是可以连续使用该运算符来输出多个数据。
std::cout << "整数: " << num << ", 浮点数: " << pi << std::endl;
2. >>
运算符(输入运算符)(像水波扩散)
基本用途
>>
运算符通常用于从输入流读取数据,最常见的输入流是 std::cin
,它代表标准输入(通常是键盘)。该运算符从输入流中提取数据,并将其存储到指定的变量中。
语法
input_stream >> variable;//input_stream->variable
这里的 input_stream
是输入流对象,如 std::cin
、std::ifstream
(用于文件输入)等;variable
是要存储数据的变量。
示例代码
#include <iostream>
int main() {
int num;
double pi;
std::string str;
// 输入整数
std::cout << "请输入一个整数: ";
std::cin >> num;
// 输入浮点数
std::cout << "请输入一个浮点数: ";
std::cin >> pi;
// 输入字符串
std::cout << "请输入一个字符串: ";
std::cin >> str;
// 输出输入的数据
std::cout << "你输入的整数是: " << num << std::endl;
std::cout << "你输入的浮点数是: " << pi << std::endl;
std::cout << "你输入的字符串是: " << str << std::endl;
return 0;
}
std::cin >> num
:从标准输入流中读取一个整数,并将其存储到变量num
中。std::cin >> pi
:从标准输入流中读取一个浮点数,并将其存储到变量pi
中。std::cin >> str
:从标准输入流中读取一个字符串,并将其存储到变量str
中。- 注意,
>>
运算符在读取字符串时会忽略空白字符(如空格、制表符、换行符等),直到遇到下一个空白字符为止。
链式调用
>>
运算符也支持链式调用,可以连续使用该运算符来读取多个数据。
std::cin >> num >> pi;
3. 自定义类型的 >>
和 <<
运算符重载
对于自定义类型,也可以重载 >>
和 <<
运算符,以便支持自定义类型的输入输出操作。
示例代码
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
// 重载 << 运算符
friend std::ostream& operator<<(std::ostream& os, const Person& p) {
os << "姓名: " << p.name << ", 年龄: " << p.age;
return os;
}
// 重载 >> 运算符
friend std::istream& operator>>(std::istream& is, Person& p) {
std::cout << "请输入姓名: ";
is >> p.name;
std::cout << "请输入年龄: ";
is >> p.age;
return is;
}
};
int main() {
Person p;
std::cin >> p;
std::cout << p << std::endl;
return 0;
}
operator<<
:这是一个友元函数,用于将Person
对象的信息输出到输出流中。operator>>
:这也是一个友元函数,用于从输入流中读取数据,并将其存储到Person
对象中。
五、文件流类(<fstream>
)
1. ifstream
(输入文件流)
class ifstream : public istream {
public:
// 文件操作
void open(const char* filename, ios_base::openmode mode = ios_base::in);
void close();
bool is_open() const;
};
- 典型用法:
ifstream fin("data.txt"); if (fin) { // 检查是否成功打开 int value; fin >> value; }
2. ofstream
(输出文件流)
class ofstream : public ostream {
public:
// 支持与ifstream类似的文件操作
};
3. fstream
(双向文件流)
- 继承自
iostream
,支持读写操作:fstream file("data.txt", ios::in | ios::out);
4.示例:文件复制
ifstream in("source.txt", ios::binary);
ofstream out("dest.txt", ios::binary);
if (in && out) {
out << in.rdbuf(); // 利用流缓冲区直接复制
}
六、字符串流类(<sstream>
)
1. istringstream
(输入字符串流)
class istringstream : public istream {
public:
string str() const; // 获取底层字符串
void str(const string& s); // 设置底层字符串
};
2. ostringstream
(输出字符串流)
class ostringstream : public ostream {
public:
string str() const;
};
3. 典型应用:类型转换
#include <iostream>
#include <sstream>
using namespace std;
int main() {
// 使用istringstream和ostringstream进行字符串与数值的转换
string input = "42 3.14";
istringstream iss(input);
int a; double b;
iss >> a >> b; // a=42, b=3.14
ostringstream oss;
oss << "Result: " << a*b;
string output = oss.str(); // "Result: 133.56"
}
七、流缓冲区类(<streambuf>
)
1.基本概念
流缓冲区类为 I/O 流提供了一个数据缓冲区,起到了数据的临时存储和传输的作用。当程序进行输入操作时,数据会先从输入设备(如文件、键盘)读取到流缓冲区中,然后程序再从缓冲区中获取数据;当进行输出操作时,数据会先被写入到流缓冲区,之后再由缓冲区将数据发送到输出设备(如文件、屏幕)。
2.层次结构
<streambuf>
头文件中定义的流缓冲区类形成了一个层次结构,主要的基类是 std::streambuf
,它是所有流缓冲区类的基类,提供了通用的缓冲区管理接口。基于 std::streambuf
派生出了不同类型的流缓冲区类,以适应不同的 I/O 设备和操作:
std::filebuf
:用于文件 I/O 的流缓冲区类,管理文件的读写操作。std::stringbuf
:用于字符串 I/O 的流缓冲区类,管理字符串的读写操作。std::wstreambuf
:宽字符版本的std::streambuf
,用于处理宽字符的 I/O 操作。std::wfilebuf
:宽字符版本的std::filebuf
,用于宽字符文件的 I/O 操作。std::wstringbuf
:宽字符版本的std::stringbuf
,用于宽字符字符串的 I/O 操作。
3.主要成员函数
std::streambuf
类提供了许多成员函数,用于管理缓冲区和进行数据的读写操作:
- 缓冲区指针操作:
eback()
:返回输入缓冲区的起始指针。gptr()
:返回输入缓冲区的当前读取位置指针。egptr()
:返回输入缓冲区的结束指针。pbase()
:返回输出缓冲区的起始指针。pptr()
:返回输出缓冲区的当前写入位置指针。epptr()
:返回输出缓冲区的结束指针。
- 缓冲区填充和刷新操作:
underflow()
:当输入缓冲区为空时,该函数负责从输入设备读取数据填充缓冲区。overflow(int c = traits_type::eof())
:当输出缓冲区已满时,该函数负责将缓冲区中的数据写入到输出设备,并刷新缓冲区。sync()
:刷新缓冲区,将缓冲区中的数据写入到输出设备。
- 数据读写操作:
sgetc()
:返回当前输入缓冲区的字符,但不移动读取指针。sbumpc()
:返回当前输入缓冲区的字符,并将读取指针向后移动一位。sputc(int c)
:将字符c
写入到输出缓冲区,并将写入指针向后移动一位。sputn(const char_type* s, std::streamsize n)
:将字符串s
中的n
个字符写入到输出缓冲区。
4.自定义缓冲区(高级用法)
class MyBuffer : public streambuf {
protected:
int_type overflow(int_type c) override {
// 自定义输出处理
return c;
}
};
// 使用自定义缓冲区
MyBuffer buf;
ostream custom_stream(&buf);
custom_stream << "Hello custom buffer!";
5.使用示例
以下是一个使用 std::filebuf
进行文件读写操作的示例:
#include <iostream>
#include <fstream>
#include <streambuf>
int main() {
// 打开文件并关联到 filebuf 对象
std::filebuf fb;
if (fb.open("test.txt", std::ios::out | std::ios::binary)) {
// 写入数据到缓冲区
const char* data = "Hello, World!";
std::streamsize size = std::char_traits<char>::length(data);
fb.sputn(data, size);
// 刷新缓冲区并关闭文件
fb.pubsync();
fb.close();
}
// 打开文件并关联到 filebuf 对象进行读取操作
if (fb.open("test.txt", std::ios::in | std::ios::binary)) {
// 获取缓冲区的大小
std::streamsize size = fb.pubseekoff(0, std::ios::end, std::ios::in);
fb.pubseekpos(0, std::ios::in);
// 读取数据到缓冲区
char* buffer = new char[size];
fb.sgetn(buffer, size);
// 输出读取的数据
std::cout << "读取的数据: " << buffer << std::endl;
// 释放缓冲区内存并关闭文件
delete[] buffer;
fb.close();
}
return 0;
}
八、IO对象
C++标准库中IO对象不允许拷贝或赋值
1、基础现象验证
首先通过代码实验观察现象:
#include <fstream>
using namespace std;
int main() {
ifstream in1("test.txt");
ifstream in2 = in1; // 编译错误:拷贝构造被禁用
ofstream out;
out = ofstream("data.txt"); // 编译错误:赋值操作被禁用
}
所有IO类型(如ifstream
/ostringstream
)均无法进行拷贝构造或赋值操作。
2、底层机制分析
1. 类定义实现(以C++11标准为例)
标准库通过删除拷贝控制成员显式禁用拷贝:
namespace std {
class basic_ios : public ios_base {
// ...
basic_ios(const basic_ios&) = delete; // 拷贝构造删除
basic_ios& operator=(const basic_ios&) = delete; // 赋值操作删除
};
template<typename _CharT, typename _Traits>
class basic_ostream : virtual public basic_ios<_CharT, _Traits> {
// 同样禁用拷贝构造和赋值
};
}
3、禁止拷贝的核心原因
1. 资源唯一性原则
IO对象(如文件流)管理不可复制资源:
- 文件句柄(File Descriptor)
- 流缓冲区(streambuf)指针
- 流状态信息(goodbit/eofbit等)
如果允许拷贝,会导致多个对象操作同一个底层资源:
// 假设允许拷贝的假想场景
ifstream in1("data.txt");
ifstream in2 = in1;
in1 >> x; // 读取部分数据
in2 >> y; // 无法确定读取位置,导致竞争状态
2. 状态一致性维护
每个IO对象维护独立状态:
- 当前文件读写位置(
tellg()
/tellp()
) - 错误状态标记(
rdstate()
) - 格式控制标志(如精度、进制)
拷贝会导致状态信息的分裂,难以保证一致性。
3. 避免资源双重释放
如果允许拷贝,析构时会多次关闭同一个文件句柄:
// 假想允许拷贝的析构场景
{
ifstream in1("data.txt");
ifstream in2 = in1;
} // in2析构时关闭文件句柄 → in1析构时再次关闭已关闭的句柄 → 未定义行为
4、替代方案与正确用法
1. 使用引用传递
void process_input(istream& is) { // 通过引用操作流
int val;
is >> val;
}
ifstream in("data.txt");
process_input(in); // 合法:传递引用
process_input(cin); // 同样适用标准输入流
2. 使用指针传递
void save_data(ostream* os) {
*os << "Data: " << 42;
}
ofstream out("data.txt");
save_data(&out); // 显式传递指针
3. 移动语义(C++11+)
虽然标准流对象不可移动,但可自定义包装类:
class StreamWrapper {
unique_ptr<ostream> os;
public:
StreamWrapper(ostream&& os)
: os(os ? new ostream(os.rdbuf()) : nullptr) {}
// 实现移动构造/赋值
};
StreamWrapper sw1(ofstream("data.txt"));
StreamWrapper sw2 = std::move(sw1); // 合法移动
4. 共享流缓冲区
多个对象共享同一个流缓冲区(非拷贝对象):
ifstream in1("data.txt");
ifstream in2(in1.rdbuf()); // 构造新流但共享缓冲区
in1.seekg(100); // 影响in2的读取位置
char c;
in2 >> c; // 从位置100开始读取
5、设计模式对比
类型 | 拷贝语义 | 移动语义(C++11) | 资源管理方式 |
---|---|---|---|
std::fstream | ❌ 禁止 | ❌ 禁止 | 独占资源 |
std::string | ✅ 深拷贝 | ✅ 移动 | 值语义(COW优化) |
std::shared_ptr | ✅ 引用计数 | ✅ 移动 | 共享所有权 |
IO对象采用独占所有权模型,与unique_ptr
类似,禁止拷贝以强制明确资源所有权。
6、陷阱
1. 函数返回值误区
// 错误示例:返回临时流对象
ifstream open_file(const string& name) {
ifstream f(name);
return f; // 尝试拷贝 → 编译错误
}
// 正确做法:返回包装类或使用引用参数
void open_file(const string& name, ifstream& out) {
out.open(name);
}
2. 容器存储问题
vector<ofstream> streams; // 编译错误:元素需要可拷贝
// 正确做法:存储指针或包装类
vector<unique_ptr<ofstream>> streams;
streams.emplace_back(make_unique<ofstream>("log1.txt"));
九、条件状态
1、流状态的核心作用
IO流对象(如ifstream
/cin
)通过状态标志位实时反映操作结果,开发者可通过这些状态:
- 检测输入/输出是否成功
- 区分错误类型(可恢复错误 vs 致命错误)
- 控制程序流(重试/终止)
- 实现异常驱动式错误处理
2、状态标志位详解
在ios_base
类中定义的4个核心状态标志(通过位掩码实现):
标志位 | 值 | 触发场景 |
---|---|---|
goodbit | 0x00 | 流处于正常状态(无错误) |
eofbit | 0x01 | 到达文件末尾(End-of-File)或输入流结束(如Ctrl+D) |
failbit | 0x02 | 逻辑错误(如类型不匹配),可恢复错误 |
badbit | 0x04 | 物理错误(如磁盘损坏、无效文件指针),流已不可用 |
组合状态示例:
goodbit | eofbit
→ 无效(实际状态由位运算决定)failbit | badbit
→ 严重错误
3、状态检测方法
1. 成员函数检测
ifstream in("data.txt");
// 直接检测函数
if (in.good()) { /* 流正常 */ }
if (in.eof()) { /* 到达文件尾 */ }
if (in.fail()) { /* 可恢复错误 */ }
if (in.bad()) { /* 物理损坏 */ }
// 综合检测(等价于 !fail() && !bad() )
if (in) { /* 流可用 */ } // operator bool()重载
2. 位掩码操作
ios_base::iostate state = in.rdstate();
if (state & ios::eofbit) {
cout << "到达文件末尾" << endl;
}
if (state & (ios::failbit | ios::badbit)) {
cout << "发生错误" << endl;
}
4、状态管理操作
1. 状态清除(clear)
int value;
cin >> value;
if (cin.fail()) { // 输入非数字时触发
cin.clear(); // 清除错误状态(恢复到goodbit)
cin.ignore(1000, '\n'); // 跳过无效输入
// 重试逻辑...
}
注意:clear()
默认参数为goodbit
,可指定具体状态:
in.clear(ios::goodbit | ios::eofbit); // 强制设置特定状态
2. 设置异常掩码(exceptions)
ifstream in;
in.exceptions(ios::failbit | ios::badbit); // 设置触发异常的标志位
try {
in.open("nonexist.txt"); // 打开失败触发ios::failure异常
int val;
in >> val; // 读取失败同样触发异常
}
catch (const ios::failure& e) {
cerr << "IO异常: " << e.what() << endl;
}
5、典型应用场景
1. 安全读取循环
vector<int> values;
int tmp;
while (cin >> tmp) { // 自动检测operator>>的返回流状态
values.push_back(tmp);
}
if (cin.eof()) {
cout << "正常结束(到达EOF)" << endl;
}
else if (cin.fail()) {
cout << "输入格式错误" << endl;
cin.clear();
}
2. 文件读取容错
ifstream in("data.bin", ios::binary);
char buffer[1024];
while (in.read(buffer, sizeof(buffer))) {
// 正常处理数据块
}
if (in.eof()) {
// 处理最后不完整的数据块
streamsize count = in.gcount();
process(buffer, count);
}
else if (in.fail()) {
cerr << "文件读取错误" << endl;
}
6、状态转换机制
1. 状态转换图(简易版)
2. 操作对状态的影响
操作 | 可能设置的状态位 |
---|---|
打开文件失败 | failbit |
读取到文件末尾 | eofbit |
类型不匹配 | failbit |
流缓冲区损坏 | badbit |
seekg 越界 | failbit |
7、调试
1. 输出流状态
void debug_stream_state(const istream& is) {
ios::iostate state = is.rdstate();
cout << bitset<4>(state) << " : ";
if (state & ios::badbit) cout << "bad ";
if (state & ios::failbit) cout << "fail ";
if (state & ios::eofbit) cout << "eof ";
if (state == ios::goodbit) cout << "good";
cout << endl;
}
2. 结合gdb调试
(gdb) p cout.rdstate()
$1 = std::_Ios_Iostate = 0
(gdb) p cerr.rdstate()
$2 = std::_Ios_Iostate = 4
8、实践
- 输入后必检状态:所有
operator>>
操作后检查流状态 - 优先使用
getline
:避免>>
在行末遗留\n
导致状态混乱 - 异常慎用原则:文件打开等关键操作使用异常,常规错误使用状态检测
- RAII管理状态:利用作用域自动清除临时状态
{ ios::fmtflags old_flags = cout.flags(); // 保存原状态 cout << hex << 42; // 临时修改 // 自动作用域结束恢复 }
关键要点:始终假设IO操作可能失败,并在代码中显式处理所有可能的错误路径。
十、文件的输入和输出
1、基础
1. 包含头文件
首先,需要包含 <fstream>
头文件,该头文件定义了文件流类:
#include <fstream>
#include <string> // 用于字符串操作
#include <limits> // 用于numeric_limits
using namespace std;
2. 文件流类
-
ofstream
:输出文件流,用于写入文件。 -
ifstream
:输入文件流,用于读取文件。 -
fstream
:多功能文件流,支持读写操作。
文件流对象生命周期
1. 对象创建与文件绑定
// 方式1:构造时打开文件 ofstream out("data.txt", ios::app); // 追加模式 // 方式2:默认构造+后续打开 ifstream in; in.open("config.ini", ios::in | ios::binary);
2. 对象销毁
{ fstream tmp("temp.data"); // 进入作用域时打开文件 // 操作文件... } // 离开作用域时自动调用close()
2、写入文件(输出操作)
文件操作
方法
功能描述
open(filename, mode)
打开文件
is_open()
检查文件是否成功打开
close()
关闭文件并刷新缓冲区
步骤示例:
// 创建输出文件流对象并打开文件
ofstream outFile("example.txt", ios::out); // ios::out 可省略(默认模式)
// 检查是否成功打开
if (!outFile.is_open()) {
cerr << "无法打开文件!" << endl;
return 1;
}
// 写入内容
outFile << "第一行内容" << endl;
outFile << "第二行内容\n";
outFile << 42 << " " << 3.14 << endl;
// 显式关闭文件
outFile.close();
打开模式(ios
标志位):
模式 | 描述 |
---|---|
ios::out | 写入模式(默认,覆盖已有内容) |
ios::app | 追加模式(在文件末尾添加内容) |
ios::binary | 二进制模式(非文本文件操作) |
ios::trunc | 清空文件内容(默认与ios::out 共用) |
3、读取文件(输入操作)
步骤示例:
// 创建输入文件流对象并打开文件
ifstream inFile("example.txt");
// 检查是否成功打开
if (!inFile) { // 等价于 inFile.fail()
cerr << "无法打开文件!" << endl;
return 1;
}
// 逐行读取内容
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
// 重置错误状态并关闭文件
inFile.clear(); // 清除可能的EOF或错误标志
inFile.close();
格式化读取示例:
int num;
double pi;
if (inFile >> num >> pi) {
cout << "读取到: " << num << ", " << pi << endl;
} else {
cerr << "格式错误!" << endl;
inFile.clear();
inFile.ignore(numeric_limits<streamsize>::max(), '\n'); // 跳过错误行
}
4、二进制文件操作
写入二进制数据:
struct Data {
int id;
char name[20];
};
Data data = {1, "Alice"};
ofstream binOut("data.bin", ios::binary);
binOut.write(reinterpret_cast<char*>(&data), sizeof(Data));
binOut.close();
读取二进制数据:
Data data;
ifstream binIn("data.bin", ios::binary);
binIn.read(reinterpret_cast<char*>(&data), sizeof(Data));
cout << "ID: " << data.id << ", Name: " << data.name << endl;
binIn.close();
5、文件指针操作
随机访问文件:
fstream file("data.txt", ios::in | ios::out);
// 获取当前读取指针位置
streampos pos = file.tellg();
// 移动读取指针到第100字节
file.seekg(100, ios::beg);
// 移动写入指针到文件末尾
file.seekp(0, ios::end);
// 在文件末尾追加内容
file << "\n追加的内容";
file.close();
指针定位常量:
常量 | 描述 |
---|---|
ios::beg | 文件开头(默认) |
ios::end | 文件末尾 |
ios::cur | 当前位置 |
6、错误处理与状态管理
检测流状态:
ifstream file("data.txt");
if (file.rdstate() == ios::eofbit) {
cout << "到达文件末尾" << endl;
} else if (file.rdstate() & ios::failbit) {
cerr << "逻辑错误(如类型不匹配)" << endl;
} else if (file.rdstate() & ios::badbit) {
cerr << "物理错误(如文件损坏)" << endl;
}
file.close();
启用异常处理:
ifstream file;
file.exceptions(ios::failbit | ios::badbit); // 设置触发异常的标志
try {
file.open("data.txt");
// 文件操作...
} catch (const ios::failure& e) {
cerr << "IO异常: " << e.what() << endl;
}
7、实践总结
-
RAII原则:利用对象生命周期自动管理资源。
{ // 自动关闭文件 ofstream tmpFile("temp.txt"); tmpFile << "临时数据"; }
-
路径处理:使用原始字符串或正斜杠避免转义
ofstream log(R"(C:\Users\Logs\app.log)"); // Windows路径 ofstream config("/etc/app/config.conf"); // Linux路径
-
性能优化:
// 减少缓冲区刷新次数 ofstream log("debug.log"); log << unitbuf; // 自动刷新(慎用)
-
跨平台换行符:
ofstream out("data.txt"); out << "Line1\r\nLine2"; // Windows换行 out << "Line3\n"; // Unix/Linux换行
十一、string流
1. 头文件包含
#include <sstream> // 包含字符串流相关类
#include <string>
2. 三类字符串流
-
istringstream:从字符串读取数据(输入流)
-
ostringstream:向字符串写入数据(输出流)
-
stringstream:同时支持读写操作(输入输出流)
3. istringstream 使用步骤
场景:将字符串解析为其他类型数据
// 创建输入字符串流并绑定字符串
std::istringstream iss("42 3.14 hello");
int num;
double pi;
std::string text;
// 像使用cin一样提取数据
iss >> num >> pi >> text;
// 验证结果
std::cout << num << " " << pi << " " << text; // 输出:42 3.14 hello
4. ostringstream 使用步骤
场景:将多种类型数据拼接成字符串
std::ostringstream oss;
int age = 25;
double score = 95.5;
// 像使用cout一样插入数据
oss << "Age: " << age << ", Score: " << score;
// 获取最终字符串
std::string result = oss.str();
std::cout << result; // 输出:Age: 25, Score: 95.5
5. stringstream 综合使用
场景:多次读写字符串内容
std::stringstream ss;
// 写入数据
ss << "PI: " << 3.14159;
// 读取数据
std::string prefix;
double value;
ss >> prefix >> value;
// 清空流(重要!)
ss.str(""); // 清空内容
ss.clear(); // 清除错误状态
// 重新使用
ss << "New data: " << 100;
std::cout << ss.str(); // 输出:New data: 100
6. 关键方法
方法 | 作用 |
---|---|
str() | 获取/设置底层字符串 |
str("content") | 重置流内容 |
clear() | 清除错误状态(重要!) |
7. 典型应用场景
-
数据格式化:将多个变量组合成格式化的字符串
-
字符串解析:拆分包含混合数据的字符串(如CSV解析)
-
类型转换:实现字符串与其他类型的相互转换
// 字符串转数字
std::istringstream("123") >> number;
// 数字转字符串
std::ostringstream() << 456).str();
安全类型转换
template <typename T>
T string_to(const string& s) {
istringstream iss(s);
T value;
if (!(iss >> value >> ws).eof()) {
throw invalid_argument("无效转换");
}
return value;
}
auto num = string_to<int>("123"); // 成功返回123
auto d = string_to<double>("abc"); // 抛出异常
复杂字符串解析
void parse_csv(const string& line) {
istringstream iss(line);
string token;
while (getline(iss, token, ',')) {
// 处理每个CSV字段
process_field(token);
}
}
动态字符串构建
string build_sql(const string& table, int id) {
ostringstream oss;
oss << "SELECT * FROM " << quoted(table)
<< " WHERE id=" << id << ";";
return oss.str(); // 自动处理SQL注入问题
}
重复使用字符串流
ostringstream oss;
oss << "第一部分";
cout << oss.str(); // 输出"第一部分"
oss.str(""); // 清空内容
oss << "新内容"; // 重新使用
处理带引号的字符串
string input = "\"Hello, World!\" 42";
istringstream iss(input);
string text;
int num;
iss >> quoted(text) >> num; // text="Hello, World!", num=42
二进制数据转换
vector<uint8_t> bytes = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
ostringstream oss;
oss.write(reinterpret_cast<char*>(bytes.data()), bytes.size());
string binary_str = oss.str();
8. 注意事项
-
状态管理:多次使用同一个流时需要调用
clear()
和str("")
-
错误处理:检查流状态是否有效
if (!(iss >> value)) {
std::cerr << "格式错误!";
}
-
性能:频繁创建流对象会影响性能,可复用对象
9.性能优化
1. 预分配缓冲区
ostringstream oss;
oss.rdbuf()->pubsetbuf(nullptr, 1024); // 预分配1KB缓冲区
2. 减少临时对象
// 低效方式
string s1 = oss1.str() + oss2.str();
// 高效方式
ostringstream final;
final << oss1.rdbuf() << oss2.rdbuf();
string result = final.str();
十二、扩展与自定义
1. 重载操作符实现自定义类型IO
struct Vec3 {
float x, y, z;
friend ostream& operator<<(ostream& os, const Vec3& v) {
return os << "[" << v.x << "," << v.y << "," << v.z << "]";
}
friend istream& operator>>(istream& is, Vec3& v) {
char dummy;
return is >> dummy >> v.x >> dummy >> v.y >> dummy >> v.z >> dummy;
}
};
// 使用示例
Vec3 v;
cin >> v; // 输入格式示例:[1,2,3]
cout << v; // 输出:[1,2,3]
2. 创建自定义流
class TeeStream : public ostream {
ostream& stream1;
ostream& stream2;
TeeStream(ostream& s1, ostream& s2)
: ostream(s1.rdbuf()), stream1(s1), stream2(s2) {}
TeeStream& operator<<(ostream& (*manip)(ostream&)) {
stream1 << manip;
stream2 << manip;
return *this;
}
// 需要重载其他<<操作符...
};
// 同时输出到控制台和文件
ofstream logfile("log.txt");
TeeStream mycout(cout, logfile);
mycout << "同时输出到两个目标" << endl;