C++ 文件输入输出深入解析
2022-03-082.7k 阅读
C++ 文件输入输出基础
流的概念
在 C++ 中,文件输入输出是通过流(stream)来实现的。流是一种抽象,它表示字节的序列。在文件输入输出的场景下,流连接了程序与外部文件。输入流(input stream)用于从文件读取数据到程序,而输出流(output stream)则用于将程序中的数据写入文件。
头文件
要进行文件输入输出操作,需要包含 <fstream>
头文件。这个头文件定义了三个主要的类:std::ifstream
用于输入文件流,std::ofstream
用于输出文件流,以及 std::fstream
用于既可以读又可以写的文件流。
打开和关闭文件
- 使用构造函数打开文件
- 对于
std::ofstream
,可以在构造对象时指定文件名来打开文件用于写入。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile << "This is some text to write to the file." << std::endl; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 在上述代码中,
std::ofstream outFile("example.txt");
使用构造函数尝试打开名为example.txt
的文件。如果文件成功打开,is_open()
函数返回true
,然后可以向文件中写入数据,最后通过close()
函数关闭文件。 - 对于
std::ifstream
,同样可以在构造时指定文件名来打开文件用于读取。例如:
#include <iostream> #include <fstream> #include <string> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { std::string line; while (std::getline(inFile, line)) { std::cout << line << std::endl; } inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 对于
- 使用 open() 函数打开文件
- 也可以先创建流对象,然后使用
open()
函数来打开文件。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile; outFile.open("another_example.txt"); if (outFile.is_open()) { outFile << "Writing to another file." << std::endl; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 这种方式在需要动态决定文件名等情况下更为灵活。
- 也可以先创建流对象,然后使用
文件模式
- 输出模式
std::ios::out
:默认的输出模式,用于写入文件。如果文件不存在,会创建文件;如果文件已存在,会截断文件(即清空原有内容)。例如:
std::ofstream outFile("test.txt", std::ios::out);
std::ios::app
:追加模式。文件打开后,写入操作会在文件末尾进行,不会截断原有内容。例如:
std::ofstream outFile("test.txt", std::ios::app); outFile << "This is appended text." << std::endl;
std::ios::trunc
:截断模式。确保文件被截断,即原有内容被清空,这与默认的std::ios::out
效果类似,只是显式指定了截断操作。例如:
std::ofstream outFile("test.txt", std::ios::trunc);
- 输入模式
std::ios::in
:用于打开文件进行读取。例如:
std::ifstream inFile("test.txt", std::ios::in);
- 读写模式
std::ios::in | std::ios::out
:用于打开文件既可以读又可以写。例如:
std::fstream ioFile("test.txt", std::ios::in | std::ios::out);
std::ios::ate
:打开文件后定位到文件末尾。这在需要先读取文件末尾信息,然后进行读写操作时很有用。例如:
std::fstream ioFile("test.txt", std::ios::in | std::ios::out | std::ios::ate);
基本文件输入操作
读取字符
- 使用 get() 函数
std::ifstream
类提供了get()
函数来读取单个字符。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { char ch; while (inFile.get(ch)) { std::cout << ch; } inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 在上述代码中,
inFile.get(ch)
每次读取一个字符并存储到ch
中。当到达文件末尾时,get()
函数返回false
,从而退出循环。
- 使用 getline() 函数读取一行
std::getline()
函数可以从输入流中读取一行文本。它有两种形式,一种是针对std::string
的,另一种是针对字符数组的。- 针对
std::string
的形式:
#include <iostream> #include <fstream> #include <string> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { std::string line; while (std::getline(inFile, line)) { std::cout << line << std::endl; } inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 这里
std::getline(inFile, line)
从inFile
中读取一行到line
中,每次读取都会忽略换行符。 - 针对字符数组的形式:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { char buffer[100]; while (inFile.getline(buffer, 100)) { std::cout << buffer << std::endl; } inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
inFile.getline(buffer, 100)
从inFile
中读取最多 99 个字符(为 '\0' 预留一个位置)到buffer
中。
格式化输入
- 使用提取运算符 >>
- 可以像从
std::cin
读取数据一样,使用>>
运算符从文件输入流中读取格式化数据。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("data.txt"); if (inFile.is_open()) { int num; double dbl; inFile >> num >> dbl; std::cout << "Read integer: " << num << ", double: " << dbl << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 假设
data.txt
文件内容为10 3.14
,上述代码会从文件中读取一个整数和一个双精度浮点数。
- 可以像从
基本文件输出操作
写入字符和字符串
- 使用 put() 函数写入单个字符
std::ofstream
类的put()
函数可以将单个字符写入文件。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile.put('H'); outFile.put('e'); outFile.put('l'); outFile.put('l'); outFile.put('o'); outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 上述代码会在
example.txt
文件中写入字符串Hello
。
- 使用插入运算符 << 写入字符串和其他数据类型
- 可以使用
<<
运算符将字符串、数字等数据类型写入文件。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile << "This is a string." << std::endl; int num = 42; outFile << "The number is: " << num << std::endl; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 这里先写入一个字符串,然后写入一个整数。
- 可以使用
格式化输出
- 控制输出格式
- 可以使用 C++ 的流操纵符来控制输出格式。例如,设置浮点数的精度:
#include <iostream> #include <fstream> #include <iomanip> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { double pi = 3.14159265358979323846; outFile << std::fixed << std::setprecision(2) << pi << std::endl; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
std::fixed
表示以固定小数位数形式输出,std::setprecision(2)
设置小数位数为 2。所以上述代码会在文件中写入3.14
。
文件定位
输入流定位
- tellg() 函数
tellg()
函数用于获取输入流中当前的读取位置。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { char ch; inFile.get(ch); std::streampos pos = inFile.tellg(); std::cout << "Current position after reading one character: " << pos << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
std::streampos
是一个表示流位置的类型。上述代码读取一个字符后,获取当前的读取位置并输出。
- seekg() 函数
seekg()
函数用于移动输入流中的读取位置。它有两种重载形式。- 第一种形式接受一个偏移量和一个参照位置:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { inFile.seekg(5, std::ios::beg); // 从文件开头移动 5 个字节 char ch; inFile.get(ch); std::cout << "Character at position 5: " << ch << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 这里
std::ios::beg
表示文件开头,5
是偏移量,即从文件开头移动 5 个字节后读取一个字符。 - 第二种形式接受一个绝对位置:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); if (inFile.is_open()) { std::streampos pos = 10; inFile.seekg(pos); char ch; inFile.get(ch); std::cout << "Character at position 10: " << ch << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 这里直接指定了绝对位置
10
。
输出流定位
- tellp() 函数
tellp()
函数用于获取输出流中当前的写入位置。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile << "Hello"; std::streampos pos = outFile.tellp(); std::cout << "Current position after writing 'Hello': " << pos << std::endl; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 上述代码写入字符串
Hello
后,获取当前的写入位置并输出。
- seekp() 函数
seekp()
函数用于移动输出流中的写入位置,用法与seekg()
类似。例如:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile << "Hello World"; outFile.seekp(6); // 移动到第 6 个位置 outFile << "C++"; outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 这里先写入
Hello World
,然后移动到第 6 个位置,再写入C++
,最终文件内容为Hello C++ld
。
文件操作中的错误处理
检测流状态
- 使用 fail() 函数
fail()
函数用于检测流操作是否失败。例如,在读取文件时,如果格式不正确导致读取失败,fail()
会返回true
。
#include <iostream> #include <fstream> int main() { std::ifstream inFile("data.txt"); int num; inFile >> num; if (inFile.fail()) { std::cerr << "Failed to read integer from file." << std::endl; } inFile.close(); return 0; }
- 假设
data.txt
文件内容为abc
,上述代码读取整数时会失败,fail()
函数返回true
。
- 使用 bad() 函数
bad()
函数用于检测流是否发生了严重错误,比如硬件故障等。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("nonexistent_file.txt"); if (inFile.bad()) { std::cerr << "A serious error occurred while opening the file." << std::endl; } inFile.close(); return 0; }
- 如果尝试打开一个不存在的文件,并且系统底层发生了更严重的错误(不仅仅是文件不存在这种常规错误),
bad()
函数可能返回true
。
- 使用 eof() 函数
eof()
函数用于检测是否到达文件末尾。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("example.txt"); char ch; while (!inFile.eof()) { inFile.get(ch); std::cout << ch; } inFile.close(); return 0; }
- 上述代码在未到达文件末尾时,持续读取并输出字符。不过,这种使用
eof()
的方式不太推荐,更好的方式是在读取操作的返回值上进行判断,如之前get()
函数示例中的方式。
清除错误状态
- 使用 clear() 函数
- 当流发生错误后,可以使用
clear()
函数清除错误状态,以便后续操作。例如:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("data.txt"); int num; inFile >> num; if (inFile.fail()) { std::cerr << "Failed to read integer. Clearing error state." << std::endl; inFile.clear(); inFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); inFile >> num; if (inFile.fail()) { std::cerr << "Still failed to read integer after clearing error." << std::endl; } else { std::cout << "Successfully read integer: " << num << std::endl; } } inFile.close(); return 0; }
- 这里先尝试读取整数,如果失败,清除错误状态,并忽略当前行剩余的字符,然后再次尝试读取整数。
- 当流发生错误后,可以使用
二进制文件操作
写入二进制数据
- 使用 write() 函数
std::ofstream
的write()
函数用于将二进制数据写入文件。例如,将一个整数以二进制形式写入文件:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("binary_data.bin", std::ios::binary); if (outFile.is_open()) { int num = 42; outFile.write(reinterpret_cast<const char*>(&num), sizeof(int)); outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
reinterpret_cast<const char*>(&num)
将整数num
的地址转换为const char*
类型,sizeof(int)
表示要写入的字节数。注意要以std::ios::binary
模式打开文件,这样才能正确写入二进制数据。
- 写入结构体
- 同样可以将结构体以二进制形式写入文件。例如:
#include <iostream> #include <fstream> struct Point { int x; int y; }; int main() { std::ofstream outFile("point_data.bin", std::ios::binary); if (outFile.is_open()) { Point p = {10, 20}; outFile.write(reinterpret_cast<const char*>(&p), sizeof(Point)); outFile.close(); } else { std::cerr << "Unable to open file for writing." << std::endl; } return 0; }
- 这里将
Point
结构体以二进制形式写入文件。
读取二进制数据
- 使用 read() 函数
std::ifstream
的read()
函数用于从二进制文件中读取数据。例如,读取之前写入的整数:
#include <iostream> #include <fstream> int main() { std::ifstream inFile("binary_data.bin", std::ios::binary); if (inFile.is_open()) { int num; inFile.read(reinterpret_cast<char*>(&num), sizeof(int)); std::cout << "Read integer: " << num << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
reinterpret_cast<char*>(&num)
将整数num
的地址转换为char*
类型,read()
函数从文件中读取sizeof(int)
字节的数据到num
中。
- 读取结构体
- 读取之前写入的
Point
结构体:
#include <iostream> #include <fstream> struct Point { int x; int y; }; int main() { std::ifstream inFile("point_data.bin", std::ios::binary); if (inFile.is_open()) { Point p; inFile.read(reinterpret_cast<char*>(&p), sizeof(Point)); std::cout << "Read point: (" << p.x << ", " << p.y << ")" << std::endl; inFile.close(); } else { std::cerr << "Unable to open file for reading." << std::endl; } return 0; }
- 这里从二进制文件中读取
Point
结构体的数据。
- 读取之前写入的
通过以上对 C++ 文件输入输出的深入解析,包括基础操作、文件定位、错误处理以及二进制文件操作等方面,希望读者能够全面掌握 C++ 文件输入输出的相关知识,并在实际编程中灵活运用。