C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++文件流操作

C++文件流操作方式

作者:代码中介商

文章介绍了C++中的IO流体系,与C语言文件操作的对比,文中详细描述了流体系的层次、常用流类对象、输入输出控制、文件流操作等并,并并详细对比了C++IO流与C语言文件操作的区别,最终总结了C++IO流体系的设计优势与使用要点

引言

在 C 语言中,文件操作依赖于 FILE* 和一系列函数(fopenfreadfwritefprintf 等)。这种方式虽然功能齐全,但存在类型不安全、容易忘记关闭文件、错误处理繁琐等问题。

C++ 引入了流(Stream)的概念,将输入输出抽象为"数据流",通过统一的接口操作不同的设备(键盘、屏幕、文件、字符串等)。流体系基于面向对象设计,提供了类型安全、可扩展的 IO 操作方式。

第一部分:IO 流类层次体系

一、完整继承关系

二、常用头文件与类

头文件包含的类用途
<iostream>cincoutcerrclog标准控制台 IO
<fstream>ifstreamofstreamfstream文件 IO
<sstream>istringstreamostringstreamstringstream字符串 IO
<iomanip>操纵符函数(setwsetprecision 等)格式控制

三、四个标准流对象

#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 流体系是一个设计精巧的面向对象框架,它将控制台、文件、字符串等不同设备的输入输出统一到一个继承层次中。理解流体系的关键在于:

到此这篇关于C++文件流操作方式的文章就介绍到这了,更多相关C++文件流操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文