简介
在计算机编程中,文件操作是一项基本且重要的技能。通过对文件的读取和写入,我们可以在程序与外部数据之间建立联系,实现数据的存储、共享和分析。C++为文件操作提供了强大且灵活的支持,使得程序员能够轻松实现各种复杂的文件操作任务。
文件流库概述:ofstream, ifstream, fstream
C++ 标准库中的 头文件提供了文件流类,用于处理文件输入和输出。这些类包括:
ofstream
:文件输出流,用于创建和写入文件。ifstream
:文件输入流,用于读取文件内容。fstream
:文件输入/输出流,既可以用于读取文件,也可以用于写入文件。
这些类都是从基类 ios、istream 和 ostream继承而来,因此它们具有相似的操作符和成员函数。在本博客中,我们将探讨如何使用这些类处理文件的读写操作。
C++文件流库
头文件
要使用 C++ 的文件流库,首先需要包含 头文件。这个头文件包含了文件操作所需的类和函数定义。在代码中包含此头文件的方法如下:
#include
文件流类别:输入、输出和输入/输出
文件流类可分为三类,分别用于处理不同的文件操作:
- 输入:使用 ifstream 类,主要用于从文件中读取数据。
- 输出:使用 ofstream 类,主要用于向文件中写入数据。
- 输入/输出:使用 fstream 类,可以同时进行文件的读写操作。
文件打开模式
在处理文件时,可以选择不同的文件打开模式,以便控制文件操作的行为。以下是一些常见的文件打开模式:
- ios::in:打开文件进行读取操作。
- ios::out:打开文件进行写入操作。如果文件不存在,则创建新文件;如果文件存在,则清空原有内容。
- ios::app:打开文件进行写入操作,但数据将追加到文件末尾,而不是覆盖原有内容。
- ios::binary:以二进制模式打开文件。
- ios::ate:打开文件并直接定位到文件末尾,可用于读写操作。
- ios::trunc:如果文件存在并且已成功打开,则清空文件内容。
这些文件打开模式可以组合使用,例如 ios::in | ios::out,表示打开文件进行读写操作。
ofstream:文件输出流
创建 ofstream 对象
要使用 ofstream 类创建文件并写入数据,首先需要创建一个 ofstream 对象。例如:
std::ofstream outFile;
打开文件
使用 ofstream 对象的 open() 成员函数来打开文件。在此过程中,可以指定文件打开模式。例如:
outFile.open("example.txt", std::ios::out);
如果省略文件打开模式,ofstream 默认使用 std::ios::out 模式。
写入文件
有两种方法可以向文件中写入数据:
a. 使用 << 操作符
这是一种简单的方法,可以像使用 std::cout 一样向文件写入数据:
outFile << "Hello, World!" << std::endl;
b. 使用 write() 函数
write() 函数允许更底层的文件操作,例如向文件写入二进制数据:
const char *data = "Hello, World!"; outFile.write(data, strlen(data));
关闭文件
完成文件操作后,使用 close() 成员函数关闭文件:
outFile.close();
实例:使用 ofstream 创建和写入文件
下面是一个完整的示例,展示如何使用 ofstream 创建并写入文件:
#include <iostream> #include <fstream> int main() { std::ofstream outFile("example.txt", std::ios::out); if (!outFile) { std::cerr << "Error opening file." << std::endl; return 1; } outFile << "Hello, World!" << std::endl; outFile.close(); std::cout << "File has been created and written successfully." << std::endl; return 0; }
在这个示例中,我们创建了一个名为 example.txt 的文件,并向其中写入了一行文本。运行程序后,会在当前目录下生成 example.txt 文件。
ifstream:文件输入流
创建 ifstream 对象
要使用 ifstream 类读取文件内容,首先需要创建一个 ifstream 对象。例如:
std::ifstream inFile;
打开文件
使用 ifstream 对象的 open() 成员函数打开文件。在此过程中,可以指定文件打开模式。例如:
inFile.open("example.txt", std::ios::in);
如果省略文件打开模式,ifstream 默认使用 std::ios::in 模式。
读取文件
有多种方法可以从文件中读取数据:
a. 使用 >> 操作符
这是一种简单的方法,可以像使用 std::cin 一样从文件读取数据:
std::string word; inFile >> word; ### b. 使用 getline() 函数 getline() 函数用于从文件中读取一整行文本: ```cpp std::string line; std::getline(inFile, line);
c. 使用 read() 函数
read() 函数允许更底层的文件操作,例如从文件读取二进制数据:
char buffer[256]; inFile.read(buffer, sizeof(buffer));
关闭文件
完成文件操作后,使用 close() 成员函数关闭文件:
inFile.close();
实例:使用 ifstream 读取和显示文件内容
下面是一个完整的示例,展示如何使用 ifstream 读取并显示文件内容:
#include <iostream> #include <fstream> #include <string> int main() { std::ifstream inFile("example.txt", std::ios::in); if (!inFile) { std::cerr << "Error opening file." << std::endl; return 1; } std::string line; while (std::getline(inFile, line)) { std::cout << line << std::endl; } inFile.close(); std::cout << "File content has been read and displayed successfully." << std::endl; return 0; }
在这个示例中,我们读取了名为 example.txt 的文件,并将其内容显示在屏幕上。程序会逐行读取文件内容,直到到达文件结尾。
fstream:文件输入/输出流
创建 fstream 对象
要使用 fstream 类同时进行文件的读写操作,首先需要创建一个 fstream 对象。例如:
std::fstream file;
打开文件
使用 fstream 对象的 open() 成员函数打开文件。在此过程中,可以指定文件打开模式。例如:
file.open("example.txt", std::ios::in | std::ios::out);
这里我们指定了 std::ios::in 和 std::ios::out 模式,表示打开文件进行读写操作。
读写文件
使用 fstream 类,可以同时对文件进行读取和写入操作:
a. 使用 << 和 >> 操作符
这是一种简单的方法,可以像使用 std::cin 和 std::cout 一样进行文件的读写操作:
std::string word; file >> word; // 读取 file << "Hello, World!" << std::endl; // 写入
b. 使用 getline() 和 write() 函数
getline() 函数用于从文件中读取一整行文本,而 write() 函数用于向文件中写入数据:
std::string line; std::getline(file, line); // 读取一行文本 const char *data = "Hello, World!"; file.write(data, strlen(data)); // 写入数据
关闭文件
完成文件操作后,使用 close() 成员函数关闭文件:
file.close();
实例:使用 fstream 对文件进行读写操作
下面是一个完整的示例,展示如何使用 fstream 对文件进行读写操作:
#include <iostream> #include <fstream> #include <string> int main() { std::fstream file("example.txt", std::ios::in | std::ios::out); if (!file) { std::cerr << "Error opening file." << std::endl; return 1; } std::string line; while (std::getline(file, line)) { std::cout << "Old content: " << line << std::endl; } file.clear(); // 清除 EOF 标志 file.seekp(0, std::ios::end); // 将文件指针移到文件末尾 file << "\nNew content: Hello, World!" << std::endl; file.close(); std::cout << "File has been updated successfully." << std::endl; return 0; }
在这个示例中,我们首先读取了名为 example.txt 的文件,并将其内容显示在屏幕上。接着,我们将文件指针移至文件末尾,并向文件中添加新内容。运行程序后,example.txt 文件将被更新。
文件操作错误处理
在处理文件操作时,可能会遇到各种错误和异常。为了确保程序的健壮性和稳定性,我们需要了解如何处理这些错误。
检查文件是否成功打开
在尝试读取或写入文件之前,应检查文件是否已成功打开。可以使用文件流对象的隐式转换为 bool 类型来实现这一点:
std::ifstream inFile("example.txt", std::ios::in); if (!inFile) { std::cerr << "Error opening file." << std::endl; return 1; }
检查文件是否到达结尾
在读取文件时,可能需要检查是否已到达文件结尾。可以使用 eof() 成员函数来实现这一点:
std::ifstream inFile("example.txt", std::ios::in); while (!inFile.eof()) { // 读取文件内容 }
然而,在某些情况下,使用 eof() 并不是最佳实践。通常,更推荐使用如下方法检查文件结尾:
std::string line; while (std::getline(inFile, line)) { // 处理文件内容 }
处理文件读写异常
在执行文件操作时,可能会抛出异常。为了确保程序能够正常运行,可以使用 C++ 异常处理机制(try-catch 语句)来捕获和处理这些异常:
#include <fstream> #include <iostream> int main() { try { std::ifstream inFile("nonexistent.txt", std::ios::in); if (!inFile) { throw std::runtime_error("Error opening file."); } // 文件操作 } catch (const std::runtime_error &e) { std::cerr << e.what() << std::endl; return 1; } return 0; }
通过以上方法,可以确保在遇到文件操作错误时,程序能够以适当的方式处理异常并向用户提供有关错误的信息。
使用场景
文本文件读写
open(打开文件)
在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作.
原型:
void open ( const char * filename, ios_base::openmode mode = ios_base::in | ios_base::out ); void open (const string& filename,ios_base::openmode mode = ios_base::in | ios_base::out); void open(const wchar_t *_Filename, ios_base::openmode mode= ios_base::in | ios_base::out, int prot = ios_base::_Openprot);
参数: filename 操作文件名
mode
打开文件的方式
prot
打开文件的属性 //基本很少用到打开文件的方式在ios类(所以流式I/O的基类)中定义,常见有如下几种方式:
- ios::nocreate: 不建立文件,所以文件不存在时打开失败
- ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
- ios::binary ifstream ofstream fstream 以二进制方式打开文件。若不指定此模式,则以文本模式打开。
- ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
- ios::in | ios::out ofstream
- ios::in | ios::out | ios::trunc fstream 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。
模式标记 | 适用对象 | 作用 |
ios::in | ifstream fstream | 文件以输入(读)方式打开(文件数据输入到内存)。如果文件不存在,则打开出错。 |
ios::out | ofstream fstream | 文件以输出(写)方式打开(内存数据输出到文件)。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。 |
ios::app | ofstream fstream | 打开文件,用于在其尾部添加数据(追加)。如果文件不存在,则新建该文件。 |
ios::ate | ifstream | 打开一个已有的文件,并将文件读指针指向文件末尾。如果文件不存在,则打开出错。ios:app就包含有此属性 |
ios::binary | ifstream ofstream fstream | 以二进制方式打开文件,缺省的方式是文本方式. |
ios:: trunc | ofstream | 如果文件存在,打开时会清空内部存储的所有数据,把文件长度设为0,单独使用时与 ios::out 相同。 |
ios::in | ios::out | fstream |
ios::in | ios::out | ofstream |
ios::in | ios::out | ios::trunc |
这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如
ofstream out; out.open("Hello.txt", ios::in|ios::out|ios::binary) //根据自己需要进行适当的选取
打开文件的属性同样在ios类中也有定义:
0 普通文件,打开操作 1 只读文件 2 隐含文件 4 系统文件
close (关闭文件)
file.close()
当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。
成员函数close(),它负责将缓存中的数据排放出来并关闭文件。
这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。
为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close。
>>和<<读写文本文件
fstream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;
同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。**
- 当 fstream 或者 ifstream 类对象打开文件(通常以 ios::in 作为打开模式)之后,就可以直接借助 >> 输入流运算符,读取文件中存储的字符(或字符串);
- 当 fstream 或者 ofstream 类对象打开文件(通常以 ios::out 作为打开模式)后,可以直接借助 << 输出流运算符向文件中写入字符(或字符串)。
Demo
fstream file1; file1.open("c:\\config.sys",ios::binary|ios::in,0); file1.open("c:\\config.sys"); file1.open("c:\\config.sys",ios::in|ios::out,0); //无参时的默认模式 fstream file1("c:\\config.sys"); ifstream file2("c:\\pdos.def");//以输入方式打开文件 ofstream file3("c:\\x.123");//以输出方式打开文件 //以下为默认方式 ofstream out("...", ios::out); ifstream in("...", ios::in); fstream foi("...", ios::in|ios::out);
fstream如果以输入方式(ios::in)打开文件,则默认不创建文件;如果以输出方式(ios::out)打开文件,则创建文件。
一句话,输入方式打开文件时不自动创建文件,输出方式打开文件时自动创建文件。
在实际应用中,根据需要的不同,选择不同的类来定义:
- 如果想以输入方式打开,就用ifstream来定义,默认不创建文件;
- 如果想以输出方式打开,就用ofstream来定义,默认文件不存在时自动创建文件;
- fstream如果以输入方式(ios::in)打开文件,则默认不创建文件;
- 如果以输出方式(ios::out)打开文件,则创建文件。
一句话,输入方式打开文件时不自动创建文件,输出方式打开文件时自动创建文件。
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。
所以fstream 的对象可以使用其父类的成员来访问数据。
一般来说,使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。
从文件中读取一行字符串
istream & getline(char* buf, int bufSize); istream & getline(char* buf, int bufSize, char delim);
第一种语法格式用于从文件输入流缓冲区中读取 bufSize-1 个字符到 buf,或遇到 \n 为止(哪个条件先满足就按哪个执行),
该方法会自动在 buf 中读入数据的结尾添加 ‘\0’。
第二种语法格式和第一种的区别在于,第一个版本是读到 \n 为止,
第二个版本是读到 delim 字符为止。\n 或 delim 都不会被读入 buf,但会被从文件输入流
状态标志符的验证(Verification of state flags)**
- bad()
如果在读写过程中出错,返回 true 。
例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
- fail()
除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,
例如当想要读入一个整数,而获得了一个字母的时候。
- eof()
如果读文件到达文件末尾,返回true。
- good()
这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
- 获得和设置流指针(get and put stream pointers)
所有输入/输出流对象(i/o streams objects)都有至少一个流指针:
- ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
- ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
- fstream, 类似 iostream, 同时继承了get 和 put
- 使用流类的构造函数打开文件
//以 ifstream 类为例 ifstream::ifstream (const char* szFileName, int mode = ios::in, int); //第一个参数是指向文件名的指针;第二个参数是打开文件的模式标记,默认值为ios::in; 第三个参数是整型的,也有默认值,一般极少使用。
二进制文件
- write 和 read
文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。
ostream & write(char* buffer, int count); istream & read(char* buffer, int count); ostream& put (char c); int get(); istream& get (char& c); // 从buffer中读取size个字符,写到文件中。 write ( char \* buffer, streamsize size ); //ostream 的一个成员函数,都是被ofstream所继承。** //从文件中读取size个字符到buffer中。 --- read ( char \* buffer, streamsize size ); //istream 的一个成员函数,被ifstream 所继承。 //类 fstream 的对象同时拥有这两个函数。
- put()
ofstream &put(char ch),使用:*file1.put(’c’) //就是向流写一个字符 ‘c’。**
- get()
- (1).ifstream &get(char ch),使用:
file1.get(x)//从文件中读取一个字符存储在x中
- (2).ifstream &get(char *buf,int num,char delim=’\n’),从文件中读取长度为num的字符串到buf中,或者读取到delim为止,使用:
file2.get(str1,127,’A’); //从文件中读取字符到字符串str1,当遇到字符’A’或读取了127个字符时终止。**
读出或配置流指针
tellg() 和 tellp()
//ifstream 类和 fstream 类有 tellg 成员函数,能够返回文件读指针的位置; int tellg(); //ofstream 类和 fstream 类有 tellp 成员函数,能够返回文件写指针的位置。 int tellp();
这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,
就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).
seekg() 和seekp()
ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:
seekg ( pos_type position ); seekp ( pos_type position );
使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。
要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
seekg ( off_type offset, seekdir direction ); //设置读位置 seekp ( off_type offset, seekdir direction );//设置写位置
使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。
它可以是:
ios::beg 从流开始位置计算的位移 ios::cur 从流指针当前位置开始计算的位移 ios::end 从流末尾处开始计算的位移
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,****因为文本模式的文件中某些特殊字符可能被修改。
由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
缓存和同步(Buffers and Synchronization)
当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。
这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。
例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。
当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。
这个过程称为同步(synchronization),它会在以下任一情况下发生:
- 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
- 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
- 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败.
Demo
// writing on a text file #include <fiostream.h> int main (void) { ofstream out("out.txt"); if (out.is_open()) { out << "This is a line.\n"; out << "This is another line.\n"; out.close(); } return 0; }
// reading a text file #include <iostream.h> #include <fstream.h> #include <stdlib.h> int main () { char buffer[256]; ifstream in("test.txt"); if (! in.is_open()) { cout << "Error opening file"; exit (1); } while (!in.eof() ) { in.getline (buffer,100); cout << buffer << endl; } return 0; }
// obtaining file size #include <iostream.h> #include <fstream.h> const char * filename = "test.txt"; int main () { long l,m; ifstream in(filename, ios::in|ios::binary); l = in.tellg(); in.seekg (0, ios::end); m = in.tellg(); in.close(); cout << "size of " << filename; cout << " is " << (m-l) << " bytes.\n"; return 0; }
// reading binary file #include <iostream> #include <fstream.h> const char * filename = "test.txt"; int main () { char * buffer; long size; ifstream in (filename, ios::in|ios::binary|ios::ate); size = in.tellg(); in.seekg (0, ios::beg); buffer = new char [size]; in.read (buffer, size); in.close(); cout << "the complete file is in a buffer"; delete[] buffer; return 0; }
//<< and >> read and write file #include <iostream> #include <fstream> using namespace std; int main() { int x,sum=0; ifstream srcFile("in.txt", ios::in); //以文本模式打开in.txt备读 if (!srcFile) { //打开失败 cout << "error opening source file." << endl; return 0; } ofstream destFile("out.txt", ios::out); //以文本模式打开out.txt备写 if (!destFile) { srcFile.close(); //程序结束前不能忘记关闭以前打开过的文件 cout << "error opening destination file." << endl; return 0; } //可以像用cin那样用ifstream对象 while (srcFile >> x) { sum += x; //可以像 cout 那样使用 ofstream 对象 destFile << x << " "; } cout << "sum:" << sum << endl; destFile.close(); srcFile.close(); return 0; }
小结
本博客文章介绍了 C++ 文件读写操作的基本概念和技巧。我们学习了如何使用 ofstream、ifstream 和 fstream 类进行文件操作,并详细讨论了如何打开、读取、写入和关闭文件。此外,我们还探讨了如何处理文件操作中可能遇到的错误和异常。
以下是本文的主要内容回顾:
简介:C++ 文件操作的重要性以及文件流库的概述。
C++ 文件流库:介绍了 头文件、文件流类别和文件打开模式。
- ofstream:文件输出流,包括创建对象、打开文件、写入文件和关闭文件的方法。
- ifstream:文件输入流,包括创建对象、打开文件、读取文件和关闭文件的方法。
- fstream:文件输入/输出流,包括创建对象、打开文件、读写文件和关闭文件的方法。
文件操作错误处理:介绍了如何检查文件是否成功打开、检查文件是否到达结尾以及处理文件读写异常的方法。
通过掌握这些基本概念和技巧,您将能够轻松地在 C++ 程序中实现文件读写操作。这将使您能够更好地处理外部数据,并为您的应用程序提供更强大的功能。