C++ 文件 I/O(File Input/Output)详细指南

1. 功能说明

本指南基于 file_io.cpp 文件,详细介绍了 C++ 中文件输入/输出(I/O)的各种操作和技术,包括:

  • 文本文件操作:写入、读取、追加文本文件
  • 二进制文件操作:写入、读取二进制文件
  • 文件信息获取:获取文件大小、检查文件状态
  • 结构化数据操作:读写结构体等复杂数据
  • 字符串流操作:使用 stringstream 进行字符串处理
  • 异常处理:文件操作中的异常捕获和处理
  • 文件系统操作:使用 C++17 文件系统库进行文件管理

文件 I/O 是程序与外部存储设备交互的重要方式,掌握这些操作对于开发各种应用程序都非常重要。

2. 代码解析

2.1 写入文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "=== 写入文本文件 ===" << std::endl;
// 创建输出文件流,打开 example.txt 文件
std::ofstream outFile("example.txt");

if (outFile.is_open()) {
// 写入各种类型的数据
outFile << "Hello, File I/O!" << std::endl;
outFile << "这是第2行。" << std::endl;
outFile << "这是第3行。" << std::endl;
outFile << "数字: " << 42 << std::endl;
outFile << "浮点数: " << 3.14 << std::endl;
// 关闭文件
outFile.close();
std::cout << "文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行写入。" << std::endl;
return 1;
}

解析

  • std::ofstream:输出文件流类,用于写入文件
  • is_open():检查文件是否成功打开
  • 使用流操作符 << 写入各种类型的数据
  • close():关闭文件,释放资源
  • 错误处理:如果文件无法打开,输出错误信息并退出程序

2.2 逐行读取文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== 逐行读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile("example.txt");

if (inFile.is_open()) {
std::string line;
int lineNumber = 1;
// 逐行读取文件内容
while (std::getline(inFile, line)) {
std::cout << "第 " << lineNumber << " 行: " << line << std::endl;
lineNumber++;
}
// 关闭文件
inFile.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

解析

  • std::ifstream:输入文件流类,用于读取文件
  • std::getline():逐行读取文件内容,将结果存储在字符串中
  • 循环读取直到文件结束
  • 关闭文件,释放资源

2.3 逐词读取文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 逐词读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile2("example.txt");

if (inFile2.is_open()) {
std::string word;
// 逐词读取文件内容(默认以空格分隔)
while (inFile2 >> word) {
std::cout << word << std::endl;
}
// 关闭文件
inFile2.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

解析

  • 使用流操作符 >> 逐词读取文件内容,默认以空格分隔
  • 循环读取直到文件结束
  • 关闭文件,释放资源

2.4 写入二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 写入二进制文件 ===" << std::endl;
// 创建二进制输出文件流,打开 data.bin 文件
std::ofstream binOutFile("data.bin", std::ios::binary);

if (binOutFile.is_open()) {
// 准备要写入的数组
int numbers[] = {10, 20, 30, 40, 50};
// 写入二进制数据
binOutFile.write(reinterpret_cast<char*>(numbers), sizeof(numbers));
// 关闭文件
binOutFile.close();
std::cout << "二进制文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开二进制文件进行写入。" << std::endl;
return 1;
}

解析

  • std::ios::binary:以二进制模式打开文件
  • write():写入二进制数据
  • reinterpret_cast<char*>:将整数数组指针转换为字符指针
  • sizeof(numbers):计算要写入的数据大小(字节)

2.5 读取二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
std::cout << "\n=== 读取二进制文件 ===" << std::endl;
// 创建二进制输入文件流,打开 data.bin 文件
std::ifstream binInFile("data.bin", std::ios::binary);

if (binInFile.is_open()) {
// 准备存储读取数据的数组
int readNumbers[5];
// 读取二进制数据
binInFile.read(reinterpret_cast<char*>(readNumbers), sizeof(readNumbers));

std::cout << "读取的数字: ";
for (int i = 0; i < 5; ++i) {
std::cout << readNumbers[i] << " ";
}
std::cout << std::endl;
// 关闭文件
binInFile.close();
} else {
std::cerr << "无法打开二进制文件进行读取。" << std::endl;
return 1;
}

解析

  • std::ios::binary:以二进制模式打开文件
  • read():读取二进制数据
  • reinterpret_cast<char*>:将整数数组指针转换为字符指针
  • sizeof(readNumbers):计算要读取的数据大小(字节)

2.6 追加文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::cout << "\n=== 追加文件内容 ===" << std::endl;
// 创建输出文件流,以追加模式打开 example.txt 文件
std::ofstream appendFile("example.txt", std::ios::app);

if (appendFile.is_open()) {
// 追加内容
appendFile << "这行是追加的。" << std::endl;
appendFile << "另一行追加的内容。" << std::endl;
// 关闭文件
appendFile.close();
std::cout << "内容追加成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行追加。" << std::endl;
return 1;
}

解析

  • std::ios::app:以追加模式打开文件,新写入的数据会添加到文件末尾
  • 使用流操作符 << 追加内容
  • 关闭文件,释放资源

2.7 带错误检查的文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::cout << "\n=== 带错误检查的文件读取 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream checkFile("example.txt");

if (checkFile.is_open()) {
std::string line;
// 检查文件是否可读
while (checkFile.good()) {
std::getline(checkFile, line);
if (!line.empty()) {
std::cout << line << std::endl;
}
}
// 检查是否到达文件末尾
if (checkFile.eof()) {
std::cout << "已到达文件末尾。" << std::endl;
} else if (checkFile.fail()) {
std::cout << "文件读取失败。" << std::endl;
}
// 关闭文件
checkFile.close();
}

解析

  • good():检查流是否处于良好状态(可读写且无错误)
  • eof():检查是否到达文件末尾
  • fail():检查是否发生非致命错误(如类型不匹配)
  • 错误处理:根据不同的错误状态输出相应的信息

2.8 获取文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout << "\n=== 获取文件信息 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream infoFile("example.txt");

if (infoFile.is_open()) {
// 定位到文件末尾
infoFile.seekg(0, std::ios::end);
// 获取当前位置(即文件大小)
std::streampos fileSize = infoFile.tellg();
std::cout << "文件大小: " << fileSize << " 字节" << std::endl;
// 关闭文件
infoFile.close();
}

解析

  • seekg():设置输入流的位置指针
  • std::ios::end:文件末尾位置
  • tellg():获取输入流的当前位置指针
  • 文件大小等于文件末尾的位置值

2.9 读写结构化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
std::cout << "\n=== 读写结构化数据 ===" << std::endl;

// 定义结构体
struct Person {
std::string name;
int age;
double salary;
};

// 创建 Person 结构体向量
std::vector<Person> people = {
{"Alice", 30, 50000.0},
{"Bob", 25, 45000.0},
{"Charlie", 35, 60000.0}
};

// 写入结构化数据到文件
std::ofstream peopleOut("people.txt");
if (peopleOut.is_open()) {
for (const auto& person : people) {
peopleOut << person.name << " " << person.age << " " << person.salary << std::endl;
}
peopleOut.close();
std::cout << "人员数据写入文件成功。" << std::endl;
}

// 从文件读取结构化数据
std::vector<Person> readPeople;
std::ifstream peopleIn("people.txt");
if (peopleIn.is_open()) {
std::string name;
int age;
double salary;
while (peopleIn >> name >> age >> salary) {
readPeople.push_back({name, age, salary});
}
peopleIn.close();

std::cout << "读取的人员数据:" << std::endl;
for (const auto& person : readPeople) {
std::cout << "姓名: " << person.name
<< ", 年龄: " << person.age
<< ", 工资: " << person.salary << std::endl;
}
}

解析

  • 定义 Person 结构体,包含姓名、年龄和工资字段
  • 创建 Person 结构体向量并初始化
  • 写入结构化数据:将每个结构体的字段按顺序写入文件
  • 读取结构化数据:按相同的顺序从文件读取字段,创建新的结构体并添加到向量中

2.10 使用 stringstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 使用 stringstream ===" << std::endl;
#include <sstream>
// 创建输出 stringstream
std::stringstream ss;
// 向 stringstream 写入数据
ss << "Hello " << 42 << " " << 3.14;
// 获取 stringstream 中的字符串
std::string str = ss.str();
std::cout << "String stream 内容: " << str << std::endl;

// 创建输入 stringstream
std::stringstream ss2("10 20 30");
int a, b, c;
// 从 stringstream 读取数据
ss2 >> a >> b >> c;
std::cout << "解析的值: " << a << ", " << b << ", " << c << std::endl;

解析

  • std::stringstream:字符串流类,用于在内存中进行字符串的输入/输出操作
  • 输出操作:使用流操作符 << 向 stringstream 写入数据
  • str():获取 stringstream 中的字符串
  • 输入操作:使用流操作符 >> 从 stringstream 读取数据,自动进行类型转换

2.11 文件操作异常处理

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== 文件操作异常处理 ===" << std::endl;
try {
// 尝试打开一个不存在的文件进行读取
std::ifstream nonExistentFile("non_existent_file.txt");
if (!nonExistentFile.is_open()) {
throw std::runtime_error("无法打开文件:non_existent_file.txt");
}
nonExistentFile.close();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}

解析

  • 使用 try-catch 块捕获文件操作中的异常
  • 当无法打开文件时,抛出 std::runtime_error 异常
  • 在 catch 块中捕获并处理异常,输出错误信息

2.12 使用文件系统库(C++17)

1
2
3
4
5
6
7
8
9
std::cout << "\n=== 使用文件系统库(C++17) ===" << std::endl;
// 检查文件是否存在
if (std::filesystem::exists("example.txt")) {
std::cout << "文件 example.txt 存在。" << std::endl;
// 获取文件大小
std::cout << "文件大小: " << std::filesystem::file_size("example.txt") << " 字节" << std::endl;
} else {
std::cout << "文件 example.txt 不存在。" << std::endl;
}

解析

  • std::filesystem:C++17 引入的文件系统库
  • exists():检查文件是否存在
  • file_size():获取文件大小(字节)
  • 注意:需要包含 <filesystem> 头文件,并且编译器需要支持 C++17 标准

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 file_io.cpp 文件:

1
g++ -std=c++17 -fexec-charset=GBK -o file_io file_io.cpp

参数说明:

  • -std=c++17:使用 C++17 标准(因为使用了 std::filesystem 库)
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o file_io:指定输出可执行文件名为 file_io

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./file_io  # Linux/macOS
.\file_io.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
=== 写入文本文件 ===
文件写入成功。

=== 逐行读取文本文件 ===
第 1 行: Hello, File I/O!
第 2 行: 这是第2行。
第 3 行: 这是第3行。
第 4 行: 数字: 42
第 5 行: 浮点数: 3.14

=== 逐词读取文本文件 ===
Hello,
File
I/O!
这是第2行。
这是第3行。
数字:
42
浮点数:
3.14

=== 写入二进制文件 ===
二进制文件写入成功。

=== 读取二进制文件 ===
读取的数字: 10 20 30 40 50

=== 追加文件内容 ===
内容追加成功。

=== 带错误检查的文件读取 ===
Hello, File I/O!
这是第2行。
这是第3行。
数字: 42
浮点数: 3.14
这行是追加的。
另一行追加的内容。
已到达文件末尾。

=== 获取文件信息 ===
文件大小: 104 字节
=== 读写结构化数据 ===
人员数据写入文件成功。
读取的人员数据:
姓名: Alice, 年龄: 30, 工资: 50000
姓名: Bob, 年龄: 25, 工资: 45000
姓名: Charlie, 年龄: 35, 工资: 60000

=== 使用 stringstream ===
String stream 内容: Hello 42 3.14
解析的值: 10, 20, 30

=== 文件操作异常处理 ===
异常: 无法打开文件:non_existent_file.txt

=== 使用文件系统库(C++17) ===
文件 example.txt 存在。
文件大小: 104 字节

4. 技术要点

4.1 文件流类

类名 描述 头文件
std::ifstream 输入文件流,用于读取文件 <fstream>
std::ofstream 输出文件流,用于写入文件 <fstream>
std::fstream 双向文件流,用于读写文件 <fstream>
std::stringstream 字符串流,用于内存中的字符串操作 <sstream>

4.2 文件打开模式

模式标志 描述
std::ios::in 以输入模式打开文件(默认用于 ifstream
std::ios::out 以输出模式打开文件(默认用于 ofstream
std::ios::app 以追加模式打开文件,新数据添加到文件末尾
std::ios::ate 打开文件并定位到文件末尾
std::ios::trunc 打开文件并截断文件长度为 0(默认用于 ofstream
std::ios::binary 以二进制模式打开文件
std::ios::nocreate 仅当文件存在时才打开(已弃用,建议使用文件系统库)
std::ios::noreplace 仅当文件不存在时才打开(已弃用,建议使用文件系统库)

4.3 流状态标志

状态标志 描述 检查函数
goodbit 流状态良好,无错误 good()
eofbit 已到达文件末尾 eof()
failbit 发生非致命错误(如类型不匹配) fail()
badbit 发生致命错误(如文件损坏) bad()
rdstate() 获取当前流状态 rdstate()
clear() 清除流状态标志 clear()

4.4 文件定位函数

函数 描述
seekg() 设置输入流的位置指针
seekp() 设置输出流的位置指针
tellg() 获取输入流的当前位置
tellp() 获取输出流的当前位置

4.5 二进制文件操作

  • 二进制模式:使用 std::ios::binary 打开文件
  • 写入操作:使用 write() 函数,需要指定数据指针和大小
  • 读取操作:使用 read() 函数,需要指定缓冲区指针和大小
  • 类型转换:使用 reinterpret_cast<char*> 进行指针类型转换

4.6 字符串流操作

  • 创建std::stringstream ss;
  • 写入ss << "data";
  • 读取ss >> variable;
  • 获取字符串std::string str = ss.str();
  • 设置字符串ss.str("new data");

4.7 文件系统库(C++17)

函数 描述
std::filesystem::exists() 检查文件或目录是否存在
std::filesystem::file_size() 获取文件大小
std::filesystem::is_regular_file() 检查是否为普通文件
std::filesystem::is_directory() 检查是否为目录
std::filesystem::create_directory() 创建目录
std::filesystem::remove() 删除文件或目录
std::filesystem::rename() 重命名文件或目录
std::filesystem::copy() 复制文件或目录

5. 常见问题解答

5.1 如何处理文件路径?

解答

  • 相对路径:相对于当前工作目录的路径,如 "example.txt"
  • 绝对路径:从文件系统根目录开始的完整路径,如 "C:\\Users\\example.txt"(Windows)或 "/home/user/example.txt"(Linux)
  • 路径分隔符:Windows 使用 \,Linux/macOS 使用 /,建议使用 std::filesystem::path 来处理跨平台路径

5.2 如何确保文件正确关闭?

解答

  • 显式关闭:使用 close() 函数关闭文件
  • RAII 原则:利用文件流对象的析构函数自动关闭文件,当对象超出作用域时会自动调用析构函数关闭文件
  • 推荐做法:使用局部作用域的文件流对象,或使用智能指针管理文件流对象

5.3 如何处理大文件?

解答

  • 分块读取:对于大文件,使用分块读取的方式,避免一次性加载整个文件到内存
  • 二进制模式:对于二进制大文件,使用二进制模式进行读写,提高效率
  • 内存映射:对于需要随机访问的大文件,考虑使用内存映射(memory-mapped files)技术

5.4 如何处理文件编码?

解答

  • ASCII 编码:适用于纯英文文本,是最基本的编码方式
  • UTF-8 编码:适用于包含多种语言的文本,是互联网上最常用的编码方式
  • GBK 编码:适用于中文文本,在 Windows 系统中较为常用
  • 编码转换:使用专门的库(如 ICU)进行不同编码之间的转换

5.5 如何提高文件 I/O 性能?

解答

  • 使用二进制模式:对于非文本数据,使用二进制模式可以提高读写速度
  • 缓冲区大小:调整文件流的缓冲区大小,如 stream.rdbuf()->pubsetbuf(buffer, size);
  • 批量操作:减少读写操作的次数,尽量进行批量读写
  • 异步 I/O:对于需要同时处理多个文件的场景,考虑使用异步 I/O 操作
  • 内存映射:对于需要随机访问的大文件,使用内存映射技术

5.6 如何处理文件锁?

解答

  • 标准库:C++ 标准库没有直接提供文件锁机制
  • 平台特定
    • Windows:使用 LockFileUnlockFile 等函数
    • Linux/macOS:使用 flockfcntl 等函数
  • 第三方库:使用 Boost.Interprocess 等跨平台库提供的文件锁机制

5.7 如何使用异常处理文件操作?

解答

  • 启用异常:通过 stream.exceptions(std::ios::failbit | std::ios::badbit); 启用异常
  • 捕获异常:使用 try-catch 块捕获文件操作中的异常
  • 自定义异常:对于特定的文件操作错误,可以定义自定义异常类

6. 代码优化建议

6.1 文件流管理

  • 使用 RAII 原则:利用文件流对象的析构函数自动关闭文件

    1
    2
    3
    4
    5
    // 推荐做法
    {
    std::ifstream file("example.txt");
    // 使用文件
    } // 文件自动关闭
  • 避免重复打开关闭:尽量减少文件的打开和关闭次数,特别是在循环中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 不推荐
    for (int i = 0; i < 10; ++i) {
    std::ofstream file("example.txt", std::ios::app);
    file << i << std::endl;
    file.close();
    }

    // 推荐
    std::ofstream file("example.txt", std::ios::app);
    for (int i = 0; i < 10; ++i) {
    file << i << std::endl;
    }
    // 文件自动关闭

6.2 错误处理

  • 全面的错误检查:检查文件操作的每一步,不仅仅是文件打开

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    std::ifstream file("example.txt");
    if (!file) {
    std::cerr << "无法打开文件" << std::endl;
    return 1;
    }

    std::string line;
    if (!std::getline(file, line)) {
    std::cerr << "读取文件失败" << std::endl;
    return 1;
    }
  • 使用异常处理:对于关键的文件操作,使用异常处理机制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try {
    std::ifstream file("example.txt");
    if (!file) {
    throw std::runtime_error("无法打开文件");
    }
    // 其他操作
    } catch (const std::exception& e) {
    std::cerr << "错误: " << e.what() << std::endl;
    }

6.3 性能优化

  • 使用二进制模式:对于非文本数据,使用二进制模式可以提高读写速度

    1
    2
    // 对于二进制数据
    std::ofstream file("data.bin", std::ios::binary);
  • 调整缓冲区大小:根据文件大小和操作类型,调整文件流的缓冲区大小

    1
    2
    3
    char buffer[4096];
    std::ifstream file("example.txt");
    file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
  • 批量读写:对于大块数据,使用 read()write() 函数进行批量读写

    1
    2
    3
    4
    5
    6
    // 批量读取
    char buffer[4096];
    std::ifstream file("example.txt", std::ios::binary);
    while (file.read(buffer, sizeof(buffer))) {
    // 处理数据
    }

6.4 代码可读性

  • 使用命名空间别名:对于频繁使用的命名空间,使用别名简化代码

    1
    2
    3
    4
    namespace fs = std::filesystem;
    if (fs::exists("example.txt")) {
    // 操作
    }
  • 使用结构化绑定:C++17 中可以使用结构化绑定简化代码

    1
    2
    3
    4
    5
    6
    7
    8
    // 读取多个值
    std::string name;
    int age;
    double salary;
    file >> name >> age >> salary;

    // C++17 结构化绑定
    auto [name, age, salary] = readPerson(file);
  • 使用辅助函数:将重复的文件操作封装为辅助函数

    1
    2
    3
    4
    5
    6
    bool readFile(const std::string& filename, std::string& content) {
    std::ifstream file(filename);
    if (!file) return false;
    content.assign(std::istreambuf_iterator<char>(file), {});
    return true;
    }

7. 代码优化示例

7.1 使用 RAII 原则管理文件流

优化前

1
2
3
4
5
6
7
8
std::ofstream file;
file.open("example.txt");
if (file.is_open()) {
file << "Hello World" << std::endl;
file.close();
} else {
std::cerr << "无法打开文件" << std::endl;
}

优化后

1
2
3
4
5
6
7
8
9
{
std::ofstream file("example.txt");
if (file) {
file << "Hello World" << std::endl;
} else {
std::cerr << "无法打开文件" << std::endl;
}
// 文件自动关闭
}

7.2 使用文件系统库检查文件存在性

优化前

1
2
3
4
5
6
7
std::ifstream file("example.txt");
if (file.is_open()) {
std::cout << "文件存在" << std::endl;
file.close();
} else {
std::cout << "文件不存在" << std::endl;
}

优化后

1
2
3
4
5
if (std::filesystem::exists("example.txt")) {
std::cout << "文件存在" << std::endl;
} else {
std::cout << "文件不存在" << std::endl;
}

7.3 使用 stringstream 进行字符串格式化

优化前

1
2
3
std::string name = "Alice";
int age = 30;
std::string message = "姓名: " + name + ", 年龄: " + std::to_string(age);

优化后

1
2
3
4
5
std::string name = "Alice";
int age = 30;
std::stringstream ss;
ss << "姓名: " << name << ", 年龄: " << age;
std::string message = ss.str();

7.4 批量读取文件内容

优化前

1
2
3
4
5
6
std::ifstream file("example.txt");
std::string line;
std::vector<std::string> lines;
while (std::getline(file, line)) {
lines.push_back(line);
}

优化后

1
2
3
4
5
6
7
std::ifstream file("example.txt");
std::vector<std::string> lines;
std::string line;
lines.reserve(1000); // 预分配空间
while (std::getline(file, line)) {
lines.push_back(std::move(line)); // 使用移动语义
}

7.5 使用异常处理文件操作

优化前

1
2
3
4
5
6
7
8
9
10
11
std::ifstream file("example.txt");
if (!file) {
std::cerr << "无法打开文件" << std::endl;
return 1;
}

std::string line;
if (!std::getline(file, line)) {
std::cerr << "读取文件失败" << std::endl;
return 1;
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
std::ifstream file("example.txt");
if (!file) {
throw std::runtime_error("无法打开文件");
}

std::string line;
if (!std::getline(file, line)) {
throw std::runtime_error("读取文件失败");
}
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}

8. 总结

C++ 文件 I/O 是程序与外部存储设备交互的重要方式,掌握各种文件操作技术对于开发各种应用程序都非常重要。本指南详细介绍了 C++ 中文件 I/O 的各种操作和技术,包括文本文件操作、二进制文件操作、文件信息获取、结构化数据操作、字符串流操作、异常处理和文件系统操作等。

8.1 核心要点

  • 文件流类ifstreamofstreamfstreamstringstream 是处理文件 I/O 的核心类
  • 文件打开模式:根据操作类型选择合适的文件打开模式
  • 流状态管理:检查和处理流状态,确保文件操作的正确性
  • 错误处理:使用条件判断或异常处理机制处理文件操作中的错误
  • 性能优化:根据文件类型和操作需求,选择合适的操作方式和优化策略
  • 现代 C++ 特性:利用 C++17 中的文件系统库等现代特性,简化文件操作代码

8.2 最佳实践

  • 使用 RAII 原则:利用对象的生命周期管理文件资源
  • 错误处理:全面检查文件操作的每一步,及时处理错误
  • 性能优化:根据实际需求选择合适的文件操作方式和优化策略
  • 代码可读性:使用命名空间别名、结构化绑定和辅助函数等提高代码可读性
  • 跨平台兼容:使用 std::filesystem 等跨平台库,确保代码在不同平台上的兼容性

8.3 应用场景

  • 配置文件读写:读取和写入程序配置信息
  • 数据持久化:将程序运行数据保存到文件,或从文件加载数据
  • 日志记录:将程序运行日志写入文件
  • 文件格式转换:在不同文件格式之间进行转换
  • 大文件处理:处理大型数据文件,如视频、音频、图像等
  • 网络文件传输:通过网络发送和接收文件数据

通过掌握本指南介绍的文件 I/O 技术,您可以更加高效、可靠地处理各种文件操作需求,为开发高质量的 C++ 应用程序打下坚实的基础。

file_io.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <filesystem>

int main() {
std::cout << "=== 写入文本文件 ===" << std::endl;
// 创建输出文件流,打开 example.txt 文件
std::ofstream outFile("example.txt");

if (outFile.is_open()) {
// 写入各种类型的数据
outFile << "Hello, File I/O!" << std::endl;
outFile << "这是第2行。" << std::endl;
outFile << "这是第3行。" << std::endl;
outFile << "数字: " << 42 << std::endl;
outFile << "浮点数: " << 3.14 << std::endl;
// 关闭文件
outFile.close();
std::cout << "文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行写入。" << std::endl;
return 1;
}

std::cout << "\n=== 逐行读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile("example.txt");

if (inFile.is_open()) {
std::string line;
int lineNumber = 1;
// 逐行读取文件内容
while (std::getline(inFile, line)) {
std::cout << "第 " << lineNumber << " 行: " << line << std::endl;
lineNumber++;
}
// 关闭文件
inFile.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 逐词读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile2("example.txt");

if (inFile2.is_open()) {
std::string word;
// 逐词读取文件内容(默认以空格分隔)
while (inFile2 >> word) {
std::cout << word << std::endl;
}
// 关闭文件
inFile2.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 写入二进制文件 ===" << std::endl;
// 创建二进制输出文件流,打开 data.bin 文件
std::ofstream binOutFile("data.bin", std::ios::binary);

if (binOutFile.is_open()) {
// 准备要写入的数组
int numbers[] = {10, 20, 30, 40, 50};
// 写入二进制数据
binOutFile.write(reinterpret_cast<char*>(numbers), sizeof(numbers));
// 关闭文件
binOutFile.close();
std::cout << "二进制文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开二进制文件进行写入。" << std::endl;
return 1;
}

std::cout << "\n=== 读取二进制文件 ===" << std::endl;
// 创建二进制输入文件流,打开 data.bin 文件
std::ifstream binInFile("data.bin", std::ios::binary);

if (binInFile.is_open()) {
// 准备存储读取数据的数组
int readNumbers[5];
// 读取二进制数据
binInFile.read(reinterpret_cast<char*>(readNumbers), sizeof(readNumbers));

std::cout << "读取的数字: ";
for (int i = 0; i < 5; ++i) {
std::cout << readNumbers[i] << " ";
}
std::cout << std::endl;
// 关闭文件
binInFile.close();
} else {
std::cerr << "无法打开二进制文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 追加文件内容 ===" << std::endl;
// 创建输出文件流,以追加模式打开 example.txt 文件
std::ofstream appendFile("example.txt", std::ios::app);

if (appendFile.is_open()) {
// 追加内容
appendFile << "这行是追加的。" << std::endl;
appendFile << "另一行追加的内容。" << std::endl;
// 关闭文件
appendFile.close();
std::cout << "内容追加成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行追加。" << std::endl;
return 1;
}

std::cout << "\n=== 带错误检查的文件读取 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream checkFile("example.txt");

if (checkFile.is_open()) {
std::string line;
// 检查文件是否可读
while (checkFile.good()) {
std::getline(checkFile, line);
if (!line.empty()) {
std::cout << line << std::endl;
}
}
// 检查是否到达文件末尾
if (checkFile.eof()) {
std::cout << "已到达文件末尾。" << std::endl;
} else if (checkFile.fail()) {
std::cout << "文件读取失败。" << std::endl;
}
// 关闭文件
checkFile.close();
}

std::cout << "\n=== 获取文件信息 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream infoFile("example.txt");

if (infoFile.is_open()) {
// 定位到文件末尾
infoFile.seekg(0, std::ios::end);
// 获取当前位置(即文件大小)
std::streampos fileSize = infoFile.tellg();
std::cout << "文件大小: " << fileSize << " 字节" << std::endl;
// 关闭文件
infoFile.close();
}

std::cout << "\n=== 读写结构化数据 ===" << std::endl;

// 定义结构体
struct Person {
std::string name;
int age;
double salary;
};

// 创建 Person 结构体向量
std::vector<Person> people = {
{"Alice", 30, 50000.0},
{"Bob", 25, 45000.0},
{"Charlie", 35, 60000.0}
};

// 写入结构化数据到文件
std::ofstream peopleOut("people.txt");
if (peopleOut.is_open()) {
for (const auto& person : people) {
peopleOut << person.name << " " << person.age << " " << person.salary << std::endl;
}
peopleOut.close();
std::cout << "人员数据写入文件成功。" << std::endl;
}

// 从文件读取结构化数据
std::vector<Person> readPeople;
std::ifstream peopleIn("people.txt");
if (peopleIn.is_open()) {
std::string name;
int age;
double salary;
while (peopleIn >> name >> age >> salary) {
readPeople.push_back({name, age, salary});
}
peopleIn.close();

std::cout << "读取的人员数据:" << std::endl;
for (const auto& person : readPeople) {
std::cout << "姓名: " << person.name
<< ", 年龄: " << person.age
<< ", 工资: " << person.salary << std::endl;
}
}

std::cout << "\n=== 使用 stringstream ===" << std::endl;
#include <sstream>
// 创建输出 stringstream
std::stringstream ss;
// 向 stringstream 写入数据
ss << "Hello " << 42 << " " << 3.14;
// 获取 stringstream 中的字符串
std::string str = ss.str();
std::cout << "String stream 内容: " << str << std::endl;

// 创建输入 stringstream
std::stringstream ss2("10 20 30");
int a, b, c;
// 从 stringstream 读取数据
ss2 >> a >> b >> c;
std::cout << "解析的值: " << a << ", " << b << ", " << c << std::endl;

std::cout << "\n=== 文件操作异常处理 ===" << std::endl;
try {
// 尝试打开一个不存在的文件进行读取
std::ifstream nonExistentFile("non_existent_file.txt");
if (!nonExistentFile.is_open()) {
throw std::runtime_error("无法打开文件:non_existent_file.txt");
}
nonExistentFile.close();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}

std::cout << "\n=== 使用文件系统库(C++17) ===" << std::endl;
// 检查文件是否存在
if (std::filesystem::exists("example.txt")) {
std::cout << "文件 example.txt 存在。" << std::endl;
// 获取文件大小
std::cout << "文件大小: " << std::filesystem::file_size("example.txt") << " 字节" << std::endl;
} else {
std::cout << "文件 example.txt 不存在。" << std::endl;
}

return 0;
}