C++ 文件和流
在前面的章节中,我们使用过 C++ 标准库中的 cin 和 cout,分别从键盘读取数据和向屏幕输出内容。
除了标准输入输出,C++ 还支持文件操作,可以将数据保存到文件中,也可以从文件中读取已有的数据。
C++ 通过 <fstream> 头文件提供文件输入输出功能,主要包含以下三个文件流类:
| 文件流类 | 描述 |
|---|---|
ofstream |
输出文件流,用于向文件写入数据,可以创建文件。 |
ifstream |
输入文件流,用于从文件读取数据。 |
fstream |
双向文件流,可以读取和写入文件。 |
使用文件流时,需要包含头文件:
#include <fstream>
如果程序还需要使用 cin、cout 等标准输入输出对象,则需要包含:
#include <iostream>
例如,创建一个用于写入文件的对象:
std::ofstream outfile("example.txt");
以上代码尝试打开 example.txt 文件。如果文件不存在,通常会创建新文件;如果文件已经存在,默认会清空原有内容。
打开文件
在读取或写入文件之前,首先需要打开文件。
C++ 可以通过文件流对象的构造函数直接打开文件,也可以先创建对象,再使用 open() 成员函数打开。
方式一:创建对象时打开文件
std::ofstream outfile("example.txt");
方式二:使用 open() 方法
std::ofstream outfile;
outfile.open("example.txt");
这两种方式都可以用于打开文件,第一种写法更简洁。
open() 的一种常见形式如下:
void open(
const char* filename,
std::ios::openmode mode
);
其中:
filename:要打开的文件名称或路径。mode:文件打开模式,用于指定读取、写入、追加等操作。
上述是简化后的函数声明,不同文件流类具有不同的默认打开模式。现代 C++ 也支持使用 std::string 指定文件名,C++17 起还支持 std::filesystem::path。
文件打开模式
C++ 提供了多种文件打开模式,可以根据需要进行选择。
| 模式标志 | 描述 |
|---|---|
ios::in |
以读取模式打开文件。 |
ios::out |
以写入模式打开文件。 |
ios::app |
追加模式,每次写入都定位到文件末尾。 |
ios::ate |
打开文件后将文件位置定位到末尾,但后续可以重新定位。 |
ios::trunc |
打开文件时清空已有内容。 |
ios::binary |
以二进制模式打开文件,避免文本模式下的换行符等转换。 |
多个模式可以使用按位或运算符 | 组合。
例如,以写入模式打开文件,并清空原有内容:
std::ofstream outfile;
outfile.open("file.dat", std::ios::out | std::ios::trunc);
如果希望保留文件原有内容,并将新内容追加到末尾,可以使用:
std::ofstream outfile;
outfile.open("file.dat", std::ios::app);
如果需要同时读取和写入文件,可以使用 fstream:
std::fstream file;
file.open("file.dat", std::ios::in | std::ios::out);
注意:上述双向读写模式通常要求文件已经存在。如果需要创建新文件,可以根据实际需求组合写入模式,但应注意 trunc 可能清空原有内容。
默认情况下,三个文件流类通常使用以下打开模式:
| 文件流类 | 默认模式 |
|---|---|
ifstream |
ios::in |
ofstream |
ios::out |
fstream |
ios::in | ios::out |
检查文件是否打开成功
文件打开操作并不一定成功。例如,文件不存在、路径错误或者没有访问权限,都可能导致打开失败。
可以使用 is_open() 方法检查文件是否成功打开。
实例
如果当前工作目录中不存在 example.txt 文件,程序将输出:
文件打开失败!
除了 is_open(),还可以使用 fail()、bad() 等成员函数检查流的状态。
关闭文件
文件使用完毕后,可以通过 close() 方法关闭文件。
其基本调用方式如下:
outfile.close();
关闭文件时,文件流会尝试完成必要的缓冲区刷新,并解除与文件的关联。
C++ 文件流对象还具有自动资源管理机制。当局部文件流对象离开作用域并被销毁时,关联的文件也会自动关闭。
例如:
{
std::ofstream file("example.txt");
file << "Hello C++";
} // file 离开作用域,自动关闭文件
因此,并不是每次都必须显式调用 close()。不过,如果需要提前关闭文件、重新打开其他文件,或者检查关闭时的错误,可以主动调用 close()。
写入文件
在 C++ 中,可以使用输出文件流 ofstream,通过流插入运算符 << 向文件写入数据。
使用方法与通过 cout 向屏幕输出内容类似。
实例
程序正常执行后,屏幕输出:
文件写入成功!
同时,在当前工作目录下生成 example.txt 文件,内容如下:
Hello C++ 欢迎学习 C++ 文件操作 数字:100
注意:如果 example.txt 已经存在,默认使用 ofstream 打开文件时会清空原有内容。
追加写入文件
如果不希望清空原有内容,而是想在文件末尾增加数据,可以使用 ios::app 模式。
实例
程序每执行一次,就会尝试在文件末尾追加一行内容,而不会主动清空已有数据。
读取文件
在 C++ 中,可以使用输入文件流 ifstream 读取文件内容。
常见的读取方式包括:
>>:按格式读取数据,通常以空白字符作为分隔。getline():读取一整行文本,可以保留行内空格。read():读取指定数量的字符数据,常用于二进制文件。
使用 >> 读取数据
下面演示如何读取文件中的两个整数。
实例
执行结果:
a = 100 b = 200
流提取运算符 >> 会根据目标数据类型读取内容,并在默认情况下跳过前导空白字符。
如果读取字符串,例如 infile >> name,遇到空格、制表符或换行符通常就会停止,因此不适合读取包含空格的完整文本行。
使用 getline() 逐行读取
如果需要读取包含空格的文本,可以使用 getline()。
实例
如果文件中包含以下内容:
Hello C++ Welcome to C++ programming This is a text file.
程序正常运行时,会逐行输出对应的文本。
getline() 默认读取到换行符为止,并将读取到的内容保存到 string 对象中,但不会将行末分隔符保存进去。
注意:当使用 cin >> 读取数字后,再使用 getline(cin, ...) 读取整行时,可能需要先处理输入缓冲区中剩余的换行符。这是因为格式化输入通常不会提取末尾的换行符。
读取 & 写入实例
下面通过一个完整实例,演示如何将用户输入的信息写入文件,然后重新打开文件并读取其中的内容。
程序会要求用户输入姓名和年龄,将数据保存到 afile.dat 文件中,再读取并显示到屏幕上。
实例
当程序被编译和执行时,假设输入姓名 Zara,年龄为 9,运行结果如下:
Enter your name: Zara Enter your age: 9 Reading from the file Name: Zara Age: 9
程序执行成功后,afile.dat 文件的内容为:
Zara 9
程序说明:
getline(cin, name):读取用户输入的整行姓名,支持姓名中包含空格。cin >> age:读取用户输入的整数年龄,并检查输入是否成功。ofstream:打开文件并写入姓名和年龄。close():关闭文件,检查写入及关闭操作是否成功。ifstream:重新打开文件,读取保存的数据。getline(infile, readName):从文件中读取完整的姓名。infile >> readAge:从文件中读取整数年龄。
这个实例采用先写入、再关闭、最后重新打开读取的方式,便于初学者理解文件输入输出过程。
如果需要在同一个文件流对象中进行读写,可以使用 fstream,但需要正确管理读取和写入位置,以及流的状态。
文件位置指针
文件流会维护与读写位置有关的状态,用于确定下一次从哪里读取数据,或者向哪里写入数据。
常用的文件位置操作函数包括:
| 成员函数 | 作用 |
|---|---|
seekg() |
设置输入流的读取位置。 |
seekp() |
设置输出流的写入位置。 |
tellg() |
获取当前输入流的位置。 |
tellp() |
获取当前输出流的位置。 |
其中,字母 g 来自 get,表示读取;字母 p 来自 put,表示写入。
seekg() 和 seekp()
seekg() 和 seekp() 可以使用绝对位置,也可以使用相对于某个基准位置的偏移量。
常见的定位基准包括:
| 定位基准 | 说明 |
|---|---|
ios::beg |
从文件开头开始计算偏移。 |
ios::cur |
从当前位置开始计算偏移。 |
ios::end |
从文件末尾开始计算偏移。 |
以下代码展示了常见的定位方式:
实例
上述代码用于说明函数的调用方式,其中 n 为适当的偏移量。
注意:在二进制文件中,常见实现允许按字节偏移进行定位;文本模式下可能存在换行符转换或编码相关问题,不应简单地将所有文件位置都理解为可任意计算的字节索引。
文件位置操作实例
下面创建一个二进制文件,写入字符串 ABCDEFGHIJ,然后使用 seekg() 从指定位置读取字符。
实例
执行结果:
读取的字符:E 第一个字符:A
因为文件位置从 0 开始计算,所以偏移量为 4 的位置对应第 5 个字符 E。
在使用 seekg() 时,还需要注意流的状态。如果之前读取已经到达文件末尾,流可能设置了错误状态,必要时应通过 clear() 清除状态后再重新定位。
获取文件大小
对于普通二进制文件,可以结合 seekg() 和 tellg() 获取文件长度。
实例
如果 position.dat 是前面示例生成的文件,包含 10 个 ASCII 字符,通常输出:
文件大小:10 字节
在常见平台和普通文件上,二进制流末尾位置可以用于获得文件的字节长度。对于现代 C++ 项目,也可以使用 C++17 提供的 std::filesystem::file_size() 获取普通文件大小。
文件操作注意事项
在实际开发中,文件操作还需要注意以下几个问题:
- 检查打开结果:文件路径不存在、权限不足等原因都可能导致打开失败。
- 注意覆盖问题:默认使用
ofstream打开已有文件通常会清空原有内容,追加数据应使用ios::app。 - 区分文本与二进制模式:文本文件可能涉及换行符转换,处理二进制数据应使用
ios::binary。 - 检查读写结果:文件打开成功不代表后续读取或写入一定成功。
- 合理处理文件路径:相对路径通常以程序的当前工作目录为基准,而不一定是源代码所在目录。
- 注意资源管理:文件流对象离开作用域时会自动关闭文件,必要时仍可主动调用
close()。
对于普通文本文件,推荐使用 std::string 和 std::getline() 读取内容,避免使用固定长度字符数组导致输入长度受限等问题。
对于需要随机访问的二进制文件,可以结合 read()、write()、seekg() 和 seekp() 实现更灵活的文件操作。
xiaoke
lkj***9@163.com
读写&复制实例
下面的 C++ 程序以读写模式打开一个文件。
file_wr() 在向文件 test.txt 写入用户输入的信息之后,程序从文件读取信息,并将其输出到屏幕上;
file_copy()将文件test.txt里的数据读取出来后,再写入test_1.txt中。
#include "iostream" #include "fstream" using namespace std; //向文件内部写入数据,并将数据读出 void file_wr(void) { char data[100]; //向文件写入数据 ofstream outfile; outfile.open("test.txt"); cout << "Write to the file" << endl; cout << "Enter your name:" << endl; cin.getline(data, 100); outfile << data << endl; cout << "Enter your age:" << endl; cin >> data; cin.ignore(); outfile << data << endl; outfile.close(); //从文件读取数据 ifstream infile; infile.open("test.txt"); cout << "Read from the file" << endl; infile >> data; cout << data << endl; infile >> data; cout << data << endl; infile.close(); } //将数据从一文件复制到另一文件中 void file_copy(void) { char data[100]; ifstream infile; ofstream outfile; infile.open("test.txt"); outfile.open("test_1.txt"); cout << "copy from test.txt to test_1.txt" << endl; while (!infile.eof()) { infile >> data; cout << data << endl; outfile << data << endl; } infile.close(); outfile.close(); } //测试上述读写文件,与文件数据复制 int _tmain(int argc, _TCHAR* argv[]) { file_wr(); file_copy(); return 0; }当上面的代码被编译和执行时,它会产生下列输入和输出:
xiaoke
lkj***9@163.com
wupa
100***5122@qq.com
参考地址
关于 cin.ignore() ,完整版本是 cin.ignore(int n, char a), 从输入流 (cin) 中提取字符,提取的字符被忽略 (ignore),不被使用。每抛弃一个字符,它都要计数和比较字符:如果计数值达到 n 或者被抛弃的字符是 a,则 cin.ignore()函数执行终止;否则,它继续等待。它的一个常用功能就是用来清除以回车结束的输入缓冲区的内容,消除上一次输入对下一次输入的影响。比如可以这么用:cin.ignore(1024,'\n'),通常把第一个参数设置得足够大,这样实际上总是只有第二个参数 \n 起作用,所以这一句就是把回车(包括回车)之前的所以字符从输入缓冲(流)中清除出去。
#include <iostream> using namespace std; void main() { int a,b,c; cout<<"input a:"; cin>>a; cin.ignore(1024, '\n'); cout<<"input b:"; cin>>b; cin.ignore(1024, '\n'); cout<< "input c:"; cin>> c; cout<< a << "\t" << b << "\t" << c << endl; }如果cin.ignore()不给参数,则默认参数为cin.ignore(1,EOF) 其中 EOF是end of file的缩写,表示"文字流"(stream)的结尾。
#include<iostream> using namespace std; int main() { char str1[30],str2[30],str3[30]; cout << "请输入你的姓名:"; cin>>str1; cout<<"请输入你的住址:"; cin.ignore(); cin.getline(str2,30,'a'); cout << "请输入你的籍贯:"; cin.ignore(); cin.getline(str3,30); cout<<str3; }如果在地址那里输入 bcdabcd 那么此时流里面剩的是 bcd\n,此时 cin.ignore(); 吃掉的就是b了,这是流里还剩下 cd\n 直接交给 cin.getline(str3,30); 应为有个 \n 所以这里 getline 就直接返回。
wupa
100***5122@qq.com
参考地址
wupa
100***5122@qq.com
对于 cin 的操作 使用 getline(cin,str)往往可以实现更加简单以及安全的字符串操作,不同于 cin.getline(char*, int a),前者可以直接对字符串进行操作。
#include<iostream> #include<string> #include<fstream> using namespace std; int main() { fstream iofile; iofile.open("D:\\t.txt", ios::out | ios::in | ios::trunc); string bookname; string bookwriter; cout << "input the bookname:" << endl; getline(cin, bookname); iofile << bookname << endl; cout << "input the bookwriter:" << endl; getline(cin, bookwriter); iofile << bookwriter << endl; iofile.close(); cout << "read the input file:" << endl; iofile.open("D:\\t.txt"); while (getline(iofile, bookname)) { cout << bookname << endl; } system("pause"); return 0; }程序输入输出结果:
wupa
100***5122@qq.com
飞羽
187***3734@qq.com
简单文件输入输出
#include <fstream> #include <string> #include <iostream> #include <streambuf> using namespace std; int WriteFile(string filepath,const string& Init) { //定义文件输出流 ofstream outfile; //根据参数路径创建要输出的文件 outfile.open(filepath, ios::out | ios::trunc); if(!outfile) return 1; outfile << Init << endl; outfile.close(); return 0; } string Read_Str(string filepath) { ifstream infile; infile.open(filepath); //打开失败,路径不正确 if(!infile) cout << "Open File Fail!" << endl; //读取文本内容到字符串 string readStr((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>()); return readStr; } int main() { //执行完成在mian.cpp同一目录下生产C.txt文件 WriteFile("./C.txt","Hello , world!"); cout << Read_Str("./C.txt") << endl; getchar(); return 0; }在main.cpp同一目录下生成一个C.txt文件。屏幕显示输出结果
飞羽
187***3734@qq.com
WillRious
wil***ous@sina.com
关于 笔记1 中的复制操作。由于 eof 指示是在读取文件到结尾的时候,才会改变有效的状态。但是,再下一次没有读到数据的时候,eof 才会改变;
但是如果此时还是用 eof 标志位来判断文件是否读到了 end(下图while循环),就会导致此时的 infile>>data 语句还会流入数据(文件的最后一行数据),导致复制的文件中,会多出一行。
while (!infile.eof()) { infile >> data; cout << data << endl; outfile << data << endl; }按照 笔记1 中的输入,最后文件 copy 的结果应该是:
为消除多与的读取、复制,我们只需要在还能读取文件数据的时候,才将数据复制到新文件中即可,即代码可以改成:
while (infile >> data) { cout << data << endl; outfile << data << endl; }WillRious
wil***ous@sina.com
Smallink
282***0464@qq.com
关于 vs2010 头文件用 ifstream 和 ofstream 解决方法:
#include <fstream> #include <iostream> using namespace std; int main(int argc, _TCHAR* argv[]) { char data[50]; fstream file; file.open("file.txt",ios::ate|ios::app);//打开文件进行写入操作 定位到文件末尾 cout<<"请输入你的姓名:"<<endl; cin.getline(data,50); file<<data<<endl;//往file.txt里写入 cout<<"请输入你的性别:"<<endl; cin>>data;//重新定义数据 cin.ignore(); file<<data<<endl; file.close();//关闭文件 释放资源 file.open("file.txt",ios::in);//打开文件进行读取操作 file>>data;//从开始处读取到空格(多个空格两个以上)或换行结束 cout<<data<<endl; file.close(); system("pause"); }Smallink
282***0464@qq.com
昕昕昶
zha***inchang2018@163.com
其实文件操作也可以用 freopen 函数完成。
以下就是简单的例子(从 test_1.txt 读入数据,找到最大数再保存到 test_2.txt 中):
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; int main(){ freopen("test_1.txt","r",stdin); freopen("test_2.txt","w",stdout);//定义输入输出流 int a[10]; for(int i=0;i<10;i++)cin>>a[i]; cout<<*max_element(a,a+10); return 0; }昕昕昶
zha***inchang2018@163.com
vv7
130***6390@qq.com
关于一楼的文件复制粘贴,在使用 cin.eof() 方法时由于被复制文件最后有空行,最后一个字符或字符串会被复制两次。
更好的解决方法:使用流对象作为条件来做,while (infile>>data) 可完美复制粘贴。
vv7
130***6390@qq.com
3rd
hel***rd@outlook.com
参考地址
一个违反C语言直觉的事,在C++中,最好让资源做到自动释放以减少心智负担。
在初学的时候强调记得close是为了提醒你在没有GC的语言中切记管理好资源的申请和释放,但是实际上C++的文件流会在析构时自动关闭打开的文件。
除非你需要在关闭后判断是否成功关闭,仅此时你需要持有文件流对象。
这里引用stackoverflow的一个贴子:https://stackoverflow.com/questions/4802494/do-i-need-to-close-a-stdfstream
> fstream 是一个正确的 RAII 对象,它会在作用域结束时自动关闭,并且当在作用域结束时关闭就足够了时,绝对不需要手动调用 close 。特别是,这不是“最佳实践”,也没有必要刷新输出。
3rd
hel***rd@outlook.com
参考地址