欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++输入/输出文件

程序员文章站 2022-03-14 15:56:08
...

输入/输出文件
C ++提供了以下类来执行文件中字符的输出和输入:

ofstream:流类以写在文件上
ifstream:流类以从文件读取
fstream:流类以读取和写入文件。

这些类直接或间接地从类istream和中派生ostream。我们已经使用的对象,其类型为这些类:cin是类的一个对象istream,并cout为类的一个对象ostream。因此,我们已经在使用与文件流相关的类。实际上,我们可以像以前使用cinand一样使用文件流cout,唯一的区别是我们必须将这些流与物理文件相关联。让我们来看一个例子:

// basic file operations
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream myfile;
  myfile.open ("example.txt");
  myfile << "Writing this to a file.\n";
  myfile.close();
  return 0;
}

这段代码创建了一个名为的文件,example.txt并以与处理相同的方式在其中插入一个句子cout,但myfile改为使用文件流。

但是,让我们一步一步走:

打开文件
通常对这些类之一的对象执行的第一个操作是将其关联到实际文件。此过程称为打开文件。一个打开的文件在程序中由流表示(即,这些类之一的对象;在前面的示例中为myfile),并且对该流对象执行的任何输入或输出操作都将应用于与之关联的物理文件。它。

为了使用流对象打开文件,我们使用它的成员函数open: 其中是一个字符串,代表要打开的文件的名称,并且是带有以下标志的组合的可选参数:

open (filename, mode);

filenamemode


ios::in	打开进行输入操作。
ios::out	打开进行输出操作。
ios::binary	以二进制模式打开。
ios::ate	将初始位置设置在文件末尾。
如果未设置此标志,则初始位置是文件的开头。
ios::app	所有输出操作都在文件末尾执行,将内容附加到文件的当前内容。
ios::trunc	如果打开该文件进行输出操作并且该文件已经存在,则将删除其先前的内容,并用新的内容替换。

可以使用按位运算符OR(|)组合所有这些标志。例如,如果我们想以example.bin二进制模式打开文件以添加数据,则可以通过以下对成员函数的调用来做到这一点open:

ofstream myfile;
myfile.open ("example.bin", ios::out | ios::app | ios::binary); 

每个open类的成员函数ofstream,ifstream并且fstream具有默认模式,如果打开文件时没有第二个参数,则使用默认模式:

类	默认模式参数
ofstream	ios :: out
ifstream	ios :: in
fstream	ios :: in | ios :: out

对于ifstream和ofstream类,ios::in以及ios::out自动和分别假定,即使不包括它们的模式被作为第二个参数传递的open成员函数(标志被组合)。

对于fstream,仅在调用函数而未为mode参数指定任何值的情况下才应用默认值。如果使用该参数中的任何值调用该函数,则默认模式将被覆盖,而不是合并。

以二进制模式打开的文件流独立于任何格式考虑因素执行输入和输出操作。非二进制文件称为文本文件,并且由于某些特殊字符(例如换行符和回车符)的格式而可能会发生某些翻译。

由于对文件流执行的第一个任务通常是打开文件,因此这三个类包括一个构造函数,该构造函数自动调用open成员函数并具有与该成员完全相同的参数。因此,我们还可以myfile通过编写以下示例来声明先前的对象并在我们之前的示例中进行相同的打开操作:

ofstream myfile (“example.bin”, ios::out | ios::app | ios::binary);

在单个语句中将对象构造和流打开结合在一起。打开文件的两种形式均有效且等效。

要检查文件流是否成功打开文件,可以通过调用member来完成is_open。如果流对象确实与打开的文件相关联,则该成员函数返回的bool值true,false否则:

if (myfile.is_open()) { /* ok, proceed with output */ }

关闭文件
当我们完成对文件的输入和输出操作时,我们将关闭它,以便通知操作系统并使其资源再次可用。为此,我们调用流的成员函数close。该成员函数将刷新关联的缓冲区并关闭文件:

myfile.close();

一旦调用了该成员函数,就可以重新使用流对象来打开另一个文件,并且该文件可再次用于其他进程打开。

如果某个对象仍然与打开的文件关联时被销毁,则析构函数会自动调用member function close。

文本文件
文本文件流是那些ios::binary在其打开模式下不包含标志的流。这些文件旨在存储文本,因此从/向它们输入或输出的所有值都可能会经历一些格式转换,这些转换不一定与它们的文字二进制值相对应。

对文本文件的写入操作与我们使用的操作相同cout:

// writing on a text file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream myfile ("example.txt");
  if (myfile.is_open())
  {
    myfile << "This is a line.\n";
    myfile << "This is another line.\n";
    myfile.close();
  }
  else cout << "Unable to open file";
  return 0;
}

读取文件的操作也可以与处理文件相同cin:

// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
  string line;
  ifstream myfile ("example.txt");
  if (myfile.is_open())
  {
    while ( getline (myfile,line) )
    {
      cout << line << '\n';
    }
    myfile.close();
  }

  else cout << "Unable to open file"; 

  return 0;
}

最后一个示例读取文本文件并将其内容打印在屏幕上。我们创建了一个while循环,该循环使用以下命令逐行读取文件热线电话。传回的值热线电话是对流对象本身的引用,当将其作为布尔表达式评估时(如在while循环中)true,该流是是否准备好进行更多操作,false或者是否已到达文件末尾或是否存在其他错误发生了。

检查状态标志
存在以下成员函数来检查流的特定状态(它们全部返回一个bool值):

bad()
true如果读取或写入操作失败,则返回。例如,如果我们尝试写入未打开以进行写入的文件,或者尝试写入的设备没有剩余空间。
fail()
true在与相同的情况下返回,在bad()发生格式错误的情况下也返回,例如,当我们尝试读取整数时提取字母字符时。
eof()
返回true打开的可读文件是否已结束。
good()
它是最通用的状态标志:false在调用前面任何一个函数都会返回的情况下,它都会返回true。请注意,good和bad并非完全相反(立即good检查更多状态标志)。

成员函数clear()可用于重置状态标志。

获取和放置流定位
所有I / O流对象在内部至少保留一个内部位置:

ifstream,与一样istream,在一个下一个输入操作中保持内部获取位置以及要读取的元素的位置。

ofstream与一样ostream,保持内部放置位置以及必须写入下一个元素的位置。

最后,fstream保持获取和放置位置,如iostream。

这些内部流位置指向流中执行下一次读取或写入操作的位置。可以使用以下成员函数来观察和修改这些位置:

tellg()和tellp()
这两个没有参数的成员函数返回一个成员类型的值,该类型streampos是表示当前获取位置(在的情况下tellg)或放置位置(在的情况下tellp)的类型。

seekg()和seekp()
这些功能允许改变的位置获取和放的位置。这两个函数都带有两个不同的原型。第一种形式是: 使用该原型,将流指针更改为绝对位置(从文件开头开始计数)。此参数的类型为,与函数和所返回的类型相同。 这些函数的另一种形式是: 使用该原型,将get或put位置设置为相对于参数确定的某个特定点的偏移值。是类型。并且是类型,即

seekg ( position );
seekp ( position );

positionstreampostellgtellp



seekg ( offset, direction );
seekp ( offset, direction );

directionoffsetstreamoffdirectionseekdir枚举类型,用于确定从其开始计算偏移量的点,并且可以采用以下任何值:

ios::beg 从流的开头算起的偏移量
ios::cur 从当前位置算起的偏移量
ios::end 从流的末尾算起的偏移量

以下示例使用我们刚刚看到的成员函数来获取文件的大小:

// obtaining file size
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos begin,end;
  ifstream myfile ("example.bin", ios::binary);
  begin = myfile.tellg();
  myfile.seekg (0, ios::end);
  end = myfile.tellg();
  myfile.close();
  cout << "size is: " << (end-begin) << " bytes.\n";
  return 0;
}

size is::40个字节。

注意我们用于变量begin和的类型end:

streampos size;

streampos是用于缓冲区和文件定位的特定类型,并且是由返回的类型file.tellg()。可以从相同类型的其他值中安全减去此类型的值,也可以将其转换为足够大的整数类型以容纳文件的大小。

这些流定位功能使用两种特殊类型:streampos和streamoff。这些类型也定义为流类的成员类型:

类型	会员类型	描述
streampos	ios::pos_type	定义为fpos<mbstate_t>。
可以将它们转换为streamoff这些类型,也可以将这些类型的值相加或相减。
streamoff	ios::off_type	它是基本整数类型之一(例如int或long long)的别名。

上面的每种成员类型都是其非成员等效项的别名(它们是完全相同的类型)。使用哪一个都不重要。成员类型更通用,因为在所有流对象上(甚至在使用特殊字符类型的流上)它们都是相同的,但是由于历史原因,非成员类型在现有代码中得到了广泛使用。

二进制文件
对于二进制文件,使用提取和插入运算符(<<和>>)和类似的函数读取和写入数据getline效率不高,因为我们不需要格式化任何数据,并且数据很可能没有行格式。

文件流包括两个成员函数,这些成员函数专门设计用于按顺序读取和写入二进制数据:write和read。第一个(write)是ostream(继承自ofstream)的成员函数。和read是一个成员函数istream(通过继承ifstream)。类的对象fstream同时具有。他们的原型是:

write(memory_block,size);
read(memory_block,size);

类型是
哪里(指向memory_blockchar*char),并代表存储读取数据元素或从中获取要写入数据元素的字节数组的地址。该size参数是一个整数值,该整数值指定要从内存块读取或向内存块写入的字符数。

// reading an entire binary file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos size;
  char * memblock;

  ifstream file ("example.bin", ios::in|ios::binary|ios::ate);
  if (file.is_open())
  {
    size = file.tellg();
    memblock = new char [size];
    file.seekg (0, ios::beg);
    file.read (memblock, size);
    file.close();

    cout << "the entire file content is in memory";

    delete[] memblock;
  }
  else cout << "Unable to open file";
  return 0;
}

在此示例中,将读取整个文件并将其存储在存储块中。让我们检查一下如何完成此操作:

首先,使用ios::ate标志打开文件,这意味着get指针将位于文件的末尾。这样,当我们调用member时tellg(),我们将直接获得文件的大小。

一旦获得文件的大小,我们就请求分配一个足以容纳整个文件的内存块:

 
memblock = new char[size];

之后,我们继续在文件的开头设置获取位置(请记住,我们在此文件的末尾打开了文件),然后读取了整个文件,最后将其关闭:

file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();

此时,我们可以处理从文件中获取的数据。但是我们的程序只是宣布文件的内容在内存中,然后完成。

缓冲区和同步
当我们处理文件流时,这些文件流与类型为的内部缓冲区对象相关联streambuf。该缓冲对象可以表示充当流和物理文件之间的中介的存储块。例如,使用时ofstream,每次调用成员函数put(写入单个字符)时,可将字符插入此中间缓冲区中,而不是直接写入与该流相关联的物理文件中。

操作系统还可以定义用于读取和写入文件的其他缓冲层。

刷新缓冲区后,其中包含的所有数据都会写入物理介质(如果它是输出流)。此过程称为同步 并在以下情况下发生:

关闭文件时:关闭文件之前,所有尚未刷新的缓冲区都将进行同步,并将所有未决数据写入或读取到物理介质。
缓冲区已满时:缓冲区具有一定大小。当缓冲区已满时,它将自动同步。
明确地,使用操纵器:在流上使用某些操纵器时,将发生显式同步。这些操纵器是:flush和endl。
明确地,使用成员函数sync():调用流的成员函数sync()会导致立即同步。如果流没有关联的缓冲区或发生故障,则此函数返回int等于-1的值。否则(如果流缓冲区已成功同步),则返回0。