C++ 文件读写:探索 ofstream, ifstream 和 fstream 的奥秘

简介

在计算机编程中,文件操作是一项基本且重要的技能。通过对文件的读取和写入,我们可以在程序与外部数据之间建立联系,实现数据的存储、共享和分析。C++为文件操作提供了强大且灵活的支持,使得程序员能够轻松实现各种复杂的文件操作任务。

文件流库概述:ofstream, ifstream, fstream
在这里插入图片描述
C++ 标准库中的 头文件提供了文件流类,用于处理文件输入和输出。这些类包括:

  • ofstream:文件输出流,用于创建和写入文件。
  • ifstream:文件输入流,用于读取文件内容。
  • fstream:文件输入/输出流,既可以用于读取文件,也可以用于写入文件。 这些类都是从基类 ios、istream 和 ostream

继承而来,因此它们具有相似的操作符和成员函数。在本博客中,我们将探讨如何使用这些类处理文件的读写操作。


C++文件流库

头文件

要使用 C++ 的文件流库,首先需要包含 头文件。这个头文件包含了文件操作所需的类和函数定义。在代码中包含此头文件的方法如下:
#include <fstream>


文件流类别:输入、输出和输入/输出

文件流类可分为三类,分别用于处理不同的文件操作:

  • 输入:使用 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)都有至少一个流指针:

  1. ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  2. ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  3. 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++ 程序中实现文件读写操作。这将使您能够更好地处理外部数据,并为您的应用程序提供更强大的功能。

猜你喜欢

转载自blog.csdn.net/qq_21438461/article/details/129868809