C++文件流操作方式
引言
在 C 语言中,文件操作依赖于 FILE* 和一系列函数(fopen、fread、fwrite、fprintf 等)。这种方式虽然功能齐全,但存在类型不安全、容易忘记关闭文件、错误处理繁琐等问题。
C++ 引入了流(Stream)的概念,将输入输出抽象为"数据流",通过统一的接口操作不同的设备(键盘、屏幕、文件、字符串等)。流体系基于面向对象设计,提供了类型安全、可扩展的 IO 操作方式。

第一部分:IO 流类层次体系
一、完整继承关系

二、常用头文件与类
| 头文件 | 包含的类 | 用途 |
|---|---|---|
<iostream> | cin, cout, cerr, clog | 标准控制台 IO |
<fstream> | ifstream, ofstream, fstream | 文件 IO |
<sstream> | istringstream, ostringstream, stringstream | 字符串 IO |
<iomanip> | 操纵符函数(setw, setprecision 等) | 格式控制 |
三、四个标准流对象
#include <iostream> using namespace std; // cin — istream 对象,关联标准输入(键盘) // cout — ostream 对象,关联标准输出(屏幕) // cerr — ostream 对象,关联标准错误(无缓冲) // clog — ostream 对象,关联标准日志(有缓冲)
cerr 和 clog 的区别:
cerr << "错误信息" << endl; // 立即输出(无缓冲) clog << "日志信息" << endl; // 缓冲后输出
第二部分:标准输入流详解
一、逐字符读取
#include <iostream>
using namespace std;
int main() {
char ch;
// 方式1:get() 获取一个字符(返回 istream 引用)
cin.get(ch);
// 方式2:get() 返回字符的 ASCII 值(int 类型)
int ch2 = cin.get();
cout << ch << ", " << ch2 << endl;
return 0;
}get() 的两种重载对比:
| 重载形式 | 返回值 | 用途 |
|---|---|---|
cin.get(ch) | istream& | 读取字符存入 ch |
cin.get() | int | 返回读取字符的 ASCII 值(-1 表示 EOF) |
二、逐行读取
char buf[128] = {0};
// 方式1:getline(),遇到换行符结束
cin.getline(buf, 128);
// 方式2:getline() 指定分隔符
cin.getline(buf, 128, '\n'); // 第三个参数是分隔符
// 方式3:read() 读取指定字节数
cin.read(buf, 10);getline() 与 >> 的区别:
| 方法 | 遇到空格 | 遇到换行 | 读取换行符 | 安全性 |
|---|---|---|---|---|
cin >> buf | 停止 | 停止 | 不读取(留在缓冲区) | ❌ 无长度限制 |
cin.getline(buf, n) | 继续 | 停止 | 读取并丢弃 | ✅ 有长度限制 |
三、自定义读行函数
#include <string>
// C 风格字符串读取
long readline(char* buf, int maxSize) {
char ch;
int len = 0;
while (true) {
cin.get(ch);
if (ch == '\n' || len >= maxSize) break;
buf[len++] = ch;
}
return len;
}
// C++ string 读取
long readString(string& s) {
char ch;
while (true) {
cin.get(ch);
if (ch == '\n') break;
s += ch;
}
return s.size();
}四、缓冲区管理
cin.ignore(); // 忽略一个字符(清空缓冲区) cin.ignore(100); // 忽略最多 100 个字符 cin.ignore(100, '\n'); // 忽略直到换行符(最多 100 个) cin.clear(); // 清除错误状态标志
常见场景:cin >> n 后残留的换行符需要清理
int n; cin >> n; cin.ignore(); // 清除残留的 '\n' // 然后安全读取下一行 string line; getline(cin, line);
第三部分:标准输出流详解
一、基本输出方法
// put() — 输出单个字符
cout.put('A');
cout.put('\n');
// write() — 输出指定长度的字符串
cout.write("hello", 5); // 输出5个字符
// flush() — 强制刷新缓冲区
cout.flush();
// endl — 输出换行并刷新
cout << endl; // 等价于 cout << '\n' << flush;输出缓冲区的四种刷新时机:
| 刷新模式 | 触发条件 |
|---|---|
| 满刷新 | 缓冲区满时自动刷新 |
| 行刷新 | 遇到换行符 \n 时刷新 |
| 程序退出 | exit() 或 return 时刷新 |
| 强制刷新 | 调用 flush() 或 endl |
二、格式控制 — 进制输出
#include <iomanip> int n = 100; // 使用操纵符 cout << showbase << hex << n << endl; // 0x64 (十六进制,带前缀) cout << oct << n << endl; // 0144 (八进制) cout << dec << n << endl; // 100 (十进制) // showbase — 显示进制前缀(0x 或 0) // noshowbase — 取消进制前缀
| 操纵符 | 效果 |
|---|---|
hex | 十六进制输出 |
oct | 八进制输出 |
dec | 十进制输出(默认) |
showbase | 显示进制前缀 |
noshowbase | 取消进制前缀 |
uppercase | 十六进制字母大写 |
nouppercase | 十六进制字母小写(默认) |
三、格式控制 — 使用 flags
// 获取当前标志 ios::fmtflags old_flags = cout.flags(); // 设置新标志(会覆盖旧标志) cout.flags(ios::showbase | ios::hex); cout << 90 << endl; // 0x5a // 恢复旧标志 cout.flags(old_flags);
常用格式标志:
| 标志 | 含义 |
|---|---|
ios::showbase | 显示进制前缀 |
ios::hex | 十六进制 |
ios::oct | 八进制 |
ios::dec | 十进制 |
ios::left | 左对齐 |
ios::right | 右对齐 |
ios::fixed | 固定小数位 |
ios::scientific | 科学计数法 |
四、格式控制 — 宽度和填充
cout.width(20); // 设置输出宽度为 20(只影响下一个输出)
cout.fill('*'); // 设置填充字符为 '*'
cout << left; // 左对齐
cout << "hi,disen!" << endl;
// 输出:hi,disen!***********
// 使用操纵符
cout << setw(20) << setfill('*') << left << "hi,disen!" << endl;| 方法/操纵符 | 作用 | 生效范围 |
|---|---|---|
width(n) / setw(n) | 设置输出宽度 | 只影响下一个输出 |
fill(c) / setfill(c) | 设置填充字符 | 持久生效 |
left | 左对齐 | 持久生效 |
right | 右对齐 | 持久生效 |
五、格式控制 — 浮点数精度
#include <iomanip> double d = 1.2345678; // 设置精度 cout << setprecision(3) << d << endl; // 1.23 cout << setprecision(1) << 2.459 << endl; // 2 // 固定小数位模式 cout << fixed << setprecision(2) << 1.345678 << endl; // 1.35 // 科学计数法模式 cout << scientific << 123.456 << endl; // 1.234560e+02 // 默认模式 cout << defaultfloat << 99.2389 << endl;
| 操纵符 | 精度含义 |
|---|---|
defaultfloat | 有效数字位数(默认) |
fixed | 小数点后位数 |
scientific | 小数点后位数(科学计数法) |
第四部分:文件流详解
一、文件打开模式
#include <fstream> // 定义在 ios 中 ios::in // 读模式(ifstream 默认) ios::out // 写模式(ofstream 默认) ios::app // 追加模式(写入到文件末尾) ios::ate // 打开时定位到文件末尾 ios::trunc // 打开时清空文件内容 ios::binary // 二进制模式
组合使用:
// 读写模式,二进制
fstream fs("data.dat", ios::in | ios::out | ios::binary);
// 写模式,追加
ofstream fs("log.txt", ios::out | ios::app);二、文件写入
#include <fstream>
#include <cstring>
int main() {
ofstream fs("a.txt", ios::out);
if (!fs.good()) {
cout << "打开文件失败" << endl;
return -1;
}
char line[128] = {0};
while (true) {
cin.getline(line, 128);
if (strlen(line) == 0) break;
fs.write(line, strlen(line));
fs.write("\n", 1);
}
fs.close();
return 0;
}三、文件读取
#include <fstream>
#include <string>
int main() {
ifstream fs("a.txt"); // 默认 ios::in
if (!fs.good()) return -1;
// 方式1:逐行读取
string line;
while (getline(fs, line)) {
cout << line << endl;
}
// 方式2:逐词读取
string word;
while (fs >> word) {
cout << word << endl;
}
fs.close();
return 0;
}注意:代码中 while (!fs.eof()) 存在一个常见陷阱:
// ❌ 有问题的写法
while (!fs.eof()) {
fs.getline(buf, 128);
cout << buf << endl; // 可能输出两次最后一行
}
// ✓ 正确的写法(将读取操作放在循环条件中)
while (fs.getline(buf, 128)) {
cout << buf << endl;
}原理:eof() 只在尝试读取失败后才变为 true,不是"预知"文件结束。
四、二进制文件读写
struct Person {
int pid;
char name[32];
void hi() {
cout << "pid: " << pid << ", name: " << name << endl;
}
};
int main() {
Person p1{1001, "Lucy"}, p2{1002, "Disen"};
// 二进制写入
fstream fs("b.dat", ios::out | ios::binary);
if (!fs.good()) return -1;
fs.write(reinterpret_cast<char*>(&p1), sizeof(Person));
fs.write(reinterpret_cast<char*>(&p2), sizeof(Person));
fs.close();
// 二进制读取 — 先获取文件大小
fstream ifs("b.dat", ios::in | ios::binary);
if (!ifs.good()) return -1;
ifs.seekg(0, ios::end); // 移到文件末尾
auto len = ifs.tellg(); // 获取当前位置(即文件大小)
int n = len / sizeof(Person);
cout << "Person 个数: " << n << endl;
ifs.seekg(0, ios::beg); // 移回文件开始
for (int i = 0; i < n; i++) {
Person p;
ifs.read(reinterpret_cast<char*>(&p), sizeof(Person));
p.hi();
}
ifs.close();
return 0;
}五、文件流状态检查
ifstream fs("test.txt");
// 方法1:good()
if (fs.good()) {
// 文件打开成功,且没有错误
}
// 方法2:is_open()
if (fs.is_open()) {
// 文件已成功打开
}
// 方法3:直接用作布尔值
if (!fs) {
// 文件打开失败
}| 状态函数 | 含义 |
|---|---|
good() | 流状态正常,无任何错误 |
eof() | 到达文件末尾 |
fail() | 操作失败(可恢复) |
bad() | 严重错误(不可恢复) |
is_open() | 文件是否打开 |
六、文件指针定位
ifstream fs("data.txt");
// seekg() — 移动读指针
fs.seekg(0, ios::beg); // 移到开头
fs.seekg(0, ios::end); // 移到末尾
fs.seekg(10, ios::cur); // 从当前位置后移10字节
// tellg() — 获取读指针位置
auto pos = fs.tellg();
// seekp() — 移动写指针(ofstream)
// tellp() — 获取写指针位置| 定位标志 | 含义 |
|---|---|
ios::beg | 相对于文件开头 |
ios::cur | 相对于当前位置 |
ios::end | 相对于文件末尾 |
第五部分:C 与 C++ IO 对照表
| 操作 | C 语言 | C++ 流 |
|---|---|---|
| 打开文件 | fopen("a.txt", "r") | ifstream fs("a.txt") |
| 关闭文件 | fclose(fp) | fs.close() 或析构自动 |
| 读字符 | fgetc(fp) | fs.get(ch) |
| 读一行 | fgets(buf, n, fp) | fs.getline(buf, n) |
| 写字符 | fputc(ch, fp) | fs.put(ch) |
| 写一行 | fputs(str, fp) | fs << str |
| 二进制读 | fread(buf, sz, n, fp) | fs.read(buf, n) |
| 二进制写 | fwrite(buf, sz, n, fp) | fs.write(buf, n) |
| 文件指针 | fseek(fp, 0, SEEK_END) | fs.seekg(0, ios::end) |
| 获取位置 | ftell(fp) | fs.tellg() |
| 错误检查 | 检查返回值 | fs.good() / !fs |
| 格式化输出 | fprintf(fp, "%x", n) | fs << hex << n |
总结
一、IO 流体系核心类

二、文件操作通用流程
1. 创建流对象
ifstream fs("filename", ios::in);2. 检查是否打开成功
if (!fs.good()) { /* 错误处理 */ }3. 读取/写入操作
fs >> data; 或 fs.read(buf, size);4. 关闭文件
fs.close();
// 或者依赖析构函数自动关闭
三、关键记忆点
| 要点 | 说明 |
|---|---|
cin.ignore() | 清除缓冲区残留 |
cout.width(n) | 只影响下一个输出 |
cout.fill(c) | 持久生效 |
while (getline(fs, line)) | 正确的逐行读取方式 |
reinterpret_cast<char*>(&obj) | 二进制读写结构体时的类型转换 |
fs.seekg(0, ios::end) | 计算文件大小 |
| RAII 特性 | 流对象析构时自动关闭文件 |
C++ IO 流体系是一个设计精巧的面向对象框架,它将控制台、文件、字符串等不同设备的输入输出统一到一个继承层次中。理解流体系的关键在于:
- 继承层次:
istream/ostream是所有输入输出流的基类 - 格式控制:通过操纵符(manipulator)控制输出格式
- RAII 机制:流对象析构时自动关闭文件,避免资源泄漏
- 状态检查:始终检查
good()或直接使用流对象的布尔值判断
到此这篇关于C++文件流操作方式的文章就介绍到这了,更多相关C++文件流操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
c++ dynamic_cast与static_cast使用方法示例
本文用示例讲解了dynamic_cast、static_cast子类与基类之间转换功能的使用方法2013-11-11


最新评论