Skip to content

18 IO文件操作

I/O 的基本概念

流与缓冲区

流(Stream) 是一种对连接的抽象。C++ 程序把输入和输出看作字节流:

  • 输入时,程序从流中抽取字节;输出时,程序将字节插入到输出流中
  • 流充当程序和流源/流目标之间的桥梁,使得程序可以以相同的方式对待键盘输入和文件输入
  • 输入流需要两个连接:文件端连接提供流的来源,程序端连接转储流出的数据

缓冲区(Buffer) 是用作中介的内存块,用于更高效地处理输入输出:

  • 磁盘等设备以块(如 512 字节)为单位传输数据,而程序通常逐字节读取
  • 缓冲区帮助匹配这两种不同的传输速率:磁盘一次性读取大量数据到缓冲区,程序再从缓冲区逐字节读取
  • 输入时,数据先进入缓冲区,按回车后才流入程序;输出时,数据先进入缓冲区,缓冲区满或手动刷新(flush)后才流出
  • 程序非正常结束时,缓冲区中未刷新的数据可能丢失

三个标准流

说明 缓冲
stdin(标准输入) 默认来自键盘 有缓冲
stdout(标准输出) 默认流向屏幕 有缓冲
stderr(标准错误) 专门输出错误信息 无缓冲,立即输出

错误流无缓冲是为了避免程序非正常结束时错误信息丢失。

C 与 C++ 的 I/O 特点

C 语言的 I/O

C 语言没有输入输出语句,所有 I/O 功能通过 ANSI C 标准库函数实现,基于 FILE 结构体:

  • fopen / fclose:打开 / 关闭文件
  • fgetc / fputc:读 / 写一个字符
  • fprintf / fscanf:格式化写 / 读
  • fread / fwrite:二进制读 / 写
  • feof / ferror:文件结束 / 错误检测
  • fseek:随机定位

C++ 的 I/O

C++ 保留了对 C 中 I/O 函数的支持,同时提供了一套基于类层次的 I/O 系统,运用了类、派生类、函数重载、模板和多重继承等特性。一切文件操作函数都是定义在类里的成员函数

C++ I/O 类层次:

说明
streambuf 为缓冲区提供内存,管理缓冲区的填充、访问、刷新
ios_base 表示流的一般特征(是否可读、文本流/二进制流等)
ios 基于 ios_base,包含指向 streambuf 对象的指针
istream 派生自 ios,提供输入方法
ostream 派生自 ios,提供输出方法
iostream 基于多继承自 istreamostream,同时具备输入输出方法

cinistream 类的实例,coutostream 类的实例,通过重载的 >><< 运算符进行输入输出。

C++ 文件 I/O 的方法

文件流类

说明 继承自
ofstream 写操作(输出)的文件类 ostream
ifstream 读操作(输入)的文件类 istream
fstream 可同时读写的文件类 iostream

用法与 cincout 完全一样,只是流的目标从键盘/屏幕变为文件。

基本文件操作

文件流对象可通过构造函数或 open() 成员函数关联文件:

#include <fstream>
using namespace std;

int main() {
    // 方式1:构造时打开
    ifstream source("source-file.txt");
    ofstream destination("dest-file.txt");

    // 方式2:先定义,再 open
    // ifstream source;
    // source.open("source-file.txt");

    int x;
    source >> x;            // 从文件读取,类似 cin
    source.close();         // 关闭文件,类似 fclose
    destination << x;       // 写入文件,类似 cout
    // destination 析构时会自动调用 close()
    return 0;
}

若未显式调用 close(),文件流对象析构时会自动关闭文件。

文本文件读写示例

读取文件 source.txt(内容为 13 3.14 9),计算前两者之积写入 result.txt

#include <fstream>
using namespace std;

int main() {
    int someInt;
    float someFloat;
    char someChar;

    ifstream inFile;
    ofstream outFile;
    inFile.open("Source.txt");
    outFile.open("Result.txt");

    inFile >> someInt >> someFloat >> someChar;   // 13, 3.14, '9'
    outFile << "The answer is: " << someInt * someFloat << endl;

    inFile.close();
    outFile.close();
    return 0;
}
// Result.txt 内容:The answer is: 40.82

字符读取:>> vs get()

使用 >> 读取字符会跳过空白字符(空格、换行、制表符),导致文件复制时丢失空格。应使用 get() 成员函数逐字符读取(包含空白字符):

// 错误:>> 会跳过空白字符
inFile >> c;
while (inFile) {
    outFile << c;
    inFile >> c;
}

// 正确:get() 读取所有字符
inFile.get(c);
while (inFile) {
    outFile << c;
    inFile.get(c);
}

文件打开方式

open() 的第二个参数指定打开方式,对应 C 语言 fopen 的模式字符串:

打开方式 含义
ios::in 以输入(读)方式打开
ios::out 以输出(写)方式打开
ios::ate 初始位置为文件尾
ios::app 所有输出附加在文件末尾(追加)
ios::trunc 如果文件已存在,先删除该文件
ios::binary 以二进制方式打开

打开方式可用按位或 | 组合(实际是位标志,如 0x10):

ofstream file;
file.open("example.bin", ios::out | ios::app | ios::binary);
// 等价于:
ofstream file("example.bin", ios::out | ios::app | ios::binary);

各文件流类的默认打开方式

默认方式
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out

可通过成员函数 is_open() 检查文件是否已成功打开,返回 bool 值。

二进制读写

二进制形式将数据在内存中的原样存储到文件中,不进行 ASCII 转换。使用 read()write() 成员函数:

// 函数原型
istream& read(char* buffer, streamsize size);
ostream& write(const char* buffer, streamsize size);
int a[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
int b[10];

// 写入二进制文件
ofstream outFile("1.dat", ios::binary);
for (int i = 0; i < 10; i++) {
    outFile.write((char*)&a[i], sizeof(a[i]));
}
outFile.close();

// 读取二进制文件
ifstream inFile("1.dat", ios::binary);
for (int i = 0; i < 10; i++) {
    inFile.read((char*)&b[i], sizeof(b[i]));
}
inFile.close();

read / write 的第一个参数必须是 char*,因此需要用 (char*) 强制转换其他类型指针。

文件定位

C++ I/O 系统管理两个与文件关联的指针:

  • 读指针:说明输入操作在文件中的位置
  • 写指针:说明下次写操作的位置
成员函数 说明
seekg(offset, origin) 设置指针位置
seekp(offset, origin) 设置指针位置
tellg() 返回当前指针位置
tellp() 返回当前指针位置

origin(基准位置)取值:

含义
ios::beg 文件开头
ios::cur 当前位置
ios::end 文件结尾
file1.seekg(1234, ios::cur);  // 读指针从当前位置向后移 1234 字节
file2.seekp(1234, ios::beg);  // 写指针从文件开头向后移 1234 字节

seekg 中的 g 代表 "get"(读取),seekp 中的 p 代表 "put"(写入)。

动态文件名

可通过 string 接收用户输入的文件名,再用 c_str() 转换为 C 风格字符串传给 open()

string FileName;
cout << "Please enter the output file name";
cin >> FileName;
outFile.open(FileName.c_str(), ios::binary);

C++20 新特性:std::format

C 语言中可用 printf 格式化输出,C++ 中用 cout 拼接较为繁琐:

// C
printf("[ %lf, %lf, %lf ]\n", a[0], a[1], a[2]);

// C++ 传统方式
std::cout << "[ " << a[0] << ", " << a[1] << ", " << a[2] << " ]";

// C++20 std::format(类似 Python 的格式化)
std::cout << std::format("[ {}, {}, {} ]\n", a[0], a[1], a[2]);

std::formatcout 更易读,比 printf 更易写,返回 std::string 类型。