C++ 文件流操作详解

2023-11-20

1. C++ I/O流

本文章有很多内容参考并借鉴了《C++ primer plus》 这本经典。这里先说明一下。

1.1. 数据流

数据流(data stream)是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

c++通过一种称为流(stream)的机制提供了更为精良的输入和输出方法。流是一种灵活且面向对象的I/O方法。

什么是流?
C++程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。对于面向文本的程序,每个字节代表一个字符,更通俗地说,字节可以构成字符或数值数据的二进制表示。

流充当了程序和流源或流目标之间的桥梁。这使得C++程序可以以相同的方式对待来自键盘的输入和来自文件的输入。C++程序只是检查字节流,而不需要知道字节来自何方。同理,通过使用流,C++程序处理输出的方式将独立于其去向。

根据操作对象不同分为文件流、字符串流、控制台流。


1.2. 控制台流

C++输入输出操作分别是由istream(输入流)和ostream(输出流)这两个类提供的,为了允许双向的输入/输出,由istream和ostream派生出了iostream类。

函数 功能 应用
cin 输入istream类对象 从设备读入数据
cout 输出ostream类对象 向设备输出或写入数据
cerr 标准错误的ostream类对象 屏幕设备输出数据

上面对象的 << 和 >> 操作符,是因为iostream.h头文件中,ostream类和 istream类对应每个基本数据类型都有其友元函数对流插入操作符进行了友元函数的重载。所以对于所有的流对象来说,流插入操作符都是可以被使用的。


1.3. 文件流

1.3.1. 什么是文件流?

大多数计算机程序都使用了文件。字处理程序创建文档文件;数据库程序创建和搜索信息文件;编译器读取源代码文件并生成可执行文件。

文件本身是存储在某种设备(磁带、光盘、软盘或硬盘)上的一系列字节。

文件流是将程序与文件相连、让程序读取文件内容以及让程序创建和写入文件的途径。

文件输入流需要两个连接,每端各一个。文件端部连接提供了流的来源,程序端连接将流的流出部分转储到程序中(文件端连接可以是文件,也可以是设备,如键盘)。

同样,对输出的管理包括将输出流连接到程序以及将输出目标与流关联起来。这就像将字节(而不是水)引入到水管中。

1.3.2. 缓冲区

通常,通过使用缓冲区可以更高效地处理输入和输出。

缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序传输给设备的临时存储工具。

缓冲方法则从磁盘上读取大量信息,将这些信息存储在缓冲区中,然后每次从缓冲区里读取一个字节。

因为从内存中读取单个字节的速度比从硬盘上读取快很多,所以这种方法更快,也更方便。当然,到达缓冲区尾部后,程序将从磁盘上读取另一块数据。

输出时,程序首先填满缓冲区,然后把整块数据传输给硬盘,并清空缓冲区,以备下一批输出使用。这被称为刷新缓冲区(flushing the buffer)。

键盘输入每次提供一个字符,因此在这种情况下,程序无需缓冲区来帮助匹配不同的数据传输速率。

然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正。C++程序通常在用户按下回车键时刷新输入缓冲区。

1.3.3. 文件流和控制流的关系

文件流的输入和输出类在fstream头文件中被定义,和控制流继承的关系为
如图:
在这里插入图片描述

C++ I/O类软件包处理文件输入和输出的方式与处理标准输入和输出的方式非常相似。

要写入文件,需要创建一个ofstream对象,并使用ostream方法,如<<插入运算符或write()。

要读取文件,需要创建一个ifstream对象,并使用istream方法,如>>抽取运算符或get()。

然而,与标准输入和输出相比,文件的管理更为复杂。

1.3.4. 文件处理

C++的文件处理也可以看成一个对象使用文件流的类,使用头文件istream,处理的时候有文本文件和二进制文件之分,主要的区别就是存储的形式。

C++通过以下几个类支持文件的输入输出︰

ofstream: 写操作(输出)的文件类(由ostream引申而来)

ifstream: 读操作(输入)的文件类(由istream引申而来)

fstream: 可同时读写操作的文件类(由iostream引申而来)

要在C++中进行文件处埋,必须包含头文件
iostream和fstream。

1.3.5. 简单的文件I/O

要让程序写入文件,必须这样做:

  1. 创建一个ofstream对象来管理输出流;

  2. 将该对象与特定的文件关联起来;

  3. 以使用cout的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕。

读取文件内容与写入文件相似:

  1. 创建一个ifstream对象来管理输入流;

  2. 将该对象与特定的文件关联起来;

  3. 以使用cin的方式使用该对象。

下面是ofstream/ifstream类的默认构造函数原型为:


ofstream/ifstream(const char*filename, int mode=ios::out|ios::trunc, int penprot=filebuf::openprot);

参数说明

如图:

参数 值来源
filename 要打开的文件名
mode 要打开的文件模式(ios::out)
prot 打开文件属性(filebuf::openprot)

mode属性表

如图:

类型 解释
ios::app 追加方式打开文件
ios::ate 文件打开后定位到文件尾
ios::binary 以二进制格式存储打开文件,默认为文本格式
ios::out 文件以输入方式打开
ios::in 文件以输出方式打开
ios::nocreate 不建立文件,文件不存在时打开失败
ios::noreplace 不覆盖文件,文件存在时打开失败

打开文件属性值

如图:

类型 解释
0 普通文件,用于访问
1 只读文件
2 隐藏文件
4 系统文件

注意:

ios_base::ate和ios_base::app都将文件指针指向打开的文件尾。

二者的区别在于,ios_base::app模式只允许将数据添加到文件尾,而ios_base::ate模式将指针放到文件尾。

程序写入文件案例:

#include<fstream>  //首先包含头文件fstream
#include<iostream>  
//对于大多数(但不是全部)实现来说
//包含该文件便自动包括iostream文件
//因此不必显示包含iostream。
//但为了代码方便移植,建议加上

int main()
{
    using namespace std;

    ofstream outf;
    //然后声明一个ofstream对象

    //必须将这个对象与特定的文件关联起来。
    //为此,可以使用构造函数
    //将这两步(创建对象和关联到文件)合并成一条语句

    //文件名后面使用函数默认的参数ios::out|ios::trunc
    ofstream outf("file.txt");

    //outf.open("file.txt");  
    //也可以使用open()方法。(后面会详讲)

    //然后,以使用cout的方式使用outf
    //假设我们要将"I love China"写入文件
    outf<<"I love China"<<endl;
 
    outf.close();  //关闭文件

    return 0;
}

注意:
由于ostream是ofstream类的基类,因此可以使用所有的ostream方法,包括各种插入运算符定义、格式化方法和控制符。

且以这种方式打开文件来进行输出时,如果没有这样的文件,将创建一个新文件;如果有这样的文件,则打开文件将清空文件,输出将进入到一个空文件中。

以默认模式打开文件进行输出将自动把文件的长度截短为零,这相当于删除已有的内容。

程序读取文件案例:

#include<iostream>
#include<fstream>

int main()
{
  using namespace std;
  
  //首先,当然要包含头文件fstream。
  //然后声明一个ifstream对象,将它与文件名关联起来。
  ifstream inf("file.txt");
  
  //现在,可以像使用cin那样使用fin或fis。
  char ch[80];

  inf>>ch;
  cout<<ch<<endl;

  string s;
  inf>>s;
  cout<<s<<endl;

  inf.close();  //关闭文件
}

注意:
输入和输出一样,也是被缓冲的,因此创建ifstream对象与fin一样,将创建一个由fin对象管理的输入缓冲区。

当输入和输出流对象过期(如程序终止)时,到文件的连接将自动关闭。

另外,一般使用close()方法来显式地关闭到文件的连接。
写程序的时候应该养成成一种好习惯,再程序终止之前关闭所有打开文件。

outf.close();
inf.close();

关闭这样的连接并不会删除流,而只是断开流到文件的连接。

1.3.6. 流状态检查和is_open()

C++文件流类从ios_base类那里继承了一个流状态成员。

该成员存储了指出流状态的信息:一切顺利、已到达文件尾、I/O操作失败等。

如果一切顺利,则流状态为零(没有消息就是好消息)。其他状态都是通过将特定位设置为1来记录的。

可以通过检查流状态来判断最后一个流操作是否成功。对于文件流,这包括检查试图打开文件时是否成功。

流状态检测案例:



fin(argv[file]);

//fali()
if(fin.fail()){
    ...
}

//!fin
//由于ifstream对象和istream对象一样
//被放在需要bool类型的地方时
//将被转换为bool值,因此也可以这样做
if(!fin){
    ...
}

//fin.good()
//fin对象被用于测试条件时
//如果文件操作正常,返回true,否则返回false
if(!fin.good()){

}

//is_open()
//然而,较新的C++实现提供了一种更好的检查文件是否被打开的方法
//这种方式之所以更好
//是因为它能够检测出其他方式不能检测出的微妙问题
if(!fin.is_open()){
    ...
}

注意:
这些测试无法检测到这样一种情形:试图以不合适的文件模式打开文件时失败。

方法is_open()能够检测到这种错误且包括good()能够检测到的错误。但老式C++实现没有is_open()。

1.3.7. 打开文件和open()

在从文件读取信息或者向文件写入信息之前,必须先打开文件。

ofstream和fstream对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用ifstream对象。

下面是open()函数的标准语法,open()函数是fstream、ifstream和ofstream对象的一个成员。


void open(const char*filename, ios::openmode mode);

open()成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。

如图:

模式标志 描述
ios::app 追加模式,所有写入都追加到文件末尾
ios::ate 文件打开后定位到文件尾
ios::in 打开文件用于读取
ios::out 打开文件用于写入
ios::trunc 如果文件已经存在,打开文件前文件内部的内容将被清空,文件宽度设为0

可以把以上两种或两种以上的模式结合使用。例如,如果想要以写入模式打开文件,并希望截断文件,以防文件已存在,那么可以使用下面的语法:

fstream outf;
outf.open("file.txt",ios::out|ios::trunc);

如果想要打开一个文件用于读写,可以使用下面的语法:


fstream file;
file.open("file.txt",ios::out|ios::in);

当然:
ofstream,ifstream和fstream所有这些类的成员函数open都包含了一个默认打开文件的方式。
如图:

默认方式
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out

案例:


#include <iostream>
#include <fstream>
#include <string>

int main()
{
    using namespace std;

    ofstream outf;

    if(outf.is_open()){
        cout<<"文件打开失败"<<endl;
        return 0;
    }

    outf.open("file.txt", ios::out|ios::trunc);

    outf << "千山鸟飞绝,万径人踪灭。" << endl;
    outf << "孤舟蓑笠翁,独钧寒江雪。" << endl;

    outf.close();

    ifstream inf;

    if(inf.is_open()){
        cout<<"文件打开失败"<<endl;
        return 0;
    }

    inf.open("file.txt", ios::in);

    string s;

    // 逐行读取文件内容
    while ( getline(inf, s) )
    {
        cout << s << endl;
    }

    //逐行读取文件内容
    //while(inf>>s){
    //    cout<<s<<endl;
    //}

    //逐字符读取文件内容
    //char ch;
    //while ( inf.get(ch) )
    //{
    //    cout << ch;
    //}

    //逐行读取和逐字符读取两者效果一样,区别在于读取方式。

    inf.close();

    return 0;
}

1.3.8. 打开多个文件和clear()

程序可能需要打开多个文件。打开多个文件的策略取决于它们将被如何使用。

如果需要同时打开两个文件,则必须为每个文件创建一个流。

例如,将两个排序后的文件拼接成第三个文件的程序,需要为两个输入文件创建两个ifstream对象,并为输出文件创建一个ofstream对象。可以同时打开的文件数取决于操作系统。

然而,可能要依次处理一组文件。例如,可能要计算某个名称在10个文件中出现的次数。在这种情况下,可以打开一个流,并将它依次关联到各个文件。这在节省计算机资源方面,比为每个文件打开一个流的效率高。

案例1:


#include<iostream>
#include<fstream>
#include<string>

//默认file已经存在
const char* file="file.txt";

const char* file1="file1.txt";
int main(){
    using namespace std;

    fstream f
    f.open(file1,ios::out|ios::trunc);
    if(!f.is_open()){
        cout<<"open file1 fail"<<endl;
        return 0;
    }
    
    string contect;
    cout<<"intput"<<endl;

    cin>>contect;
    f<<contect;

    //断开f与当前流的连接
    f.close();
    f.clear();
    
    //重新连接另一个流
    f.open(file,ios::in);
    
    if(f.is_open()){
        while(getline(f,contect)){
            cout<<contect<<endl;
        }
    }
    return 0;
}

案例2:


#include<iostream>
#include<fstream>
#include<string>
#include<cstdlib>

const char* file="guests.txt"

int main(){

    using namespace std;
    char ch;
    ifstream fin;
    fin.open(file);

//输出文件内刚开始的内容
    if(fin.is_open()){
        cout<<"文件内容:"<<endl;
        while(fin.get(ch)){
            cout<<ch;
        }
        fin.close();
    }

//添加新内容
    ofstream fout(fin,ios::out|ios::app);
    if(!fout.is_open()){
        cerr<<"fail"<<endl;
        return 0;
    }

    cout<<"输入:"<<endl;
    string name;
    while(getline(cin,name) &&name.size()){
        fout<<name<<endl;
    }
    fout.close();

//输出文件更新后的内容

    //重置流状态
    //防止打不开文件
    fin.clear();
    fin.open("file");
    if(fin.is_open()){
        cout<<"文件更新后的内容:"<<endl;
    }
    while(getline(fin,name)){
        cout<<name<<endl;
    }
    fin.close();

    cout<<"Done.\n";
    return 0;

}

注意:
在早期,文件I/O可能是C++最不标准的部分,很多老式编译器都不遵守当前的标准。

例如,有些编译器使用诸如nocreate等模式,而这些模式不是当前标准的组成部分。另外,只有一部分编译器要求在第二次打开同一个文件进行读取之前调用clear()

有些C++实现要求在重用的流前,需使用clear()来重置流状态,有些则不要求,这取决于将文件与对象关联起来时,是否自动重置流状态。使用clear()是无害的,即使在不必使用它的时候也使用。

1.3.9. 二进制文件

将数据存储在文件中时,可以将其存储为文本格式或二进制格式。

文本格式指的是将所有内容(甚至数字)都存储为文本。

二进制格式指的是存储值的计算机内部表示。

也就是说,计算机不是存储字符,而是存储这个值的二进制表示。对于字符来说,二进制表示与文本表示是一样的,即字符的ASCII码的二进制表示。对于数字来说,二进制表示与文本表示有很大的差别。

如图:
在这里插入图片描述

每种格式都有自己的优点。

文本格式便于读取,可以使用编辑器或字处理器来读取和编辑文本文件,可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统。

二进制格式对于数字来说比较精确,因为它存储的是值的内部表示,因此不会有转换误差或舍入误差。以二进制格式保存数据的速度更快,因为不需要转换,并可以大块地存储数据。二进制格式通常占用的空间较小,这取决于数据的特征。

然而,如果另一个系统使用另一种内部表示,则可能无法将数据传输给该系统。同一系统上不同的编译器也可能使用不同的内部结构布局表示。

我们来看个例子:


struct people{
  char name[20];
  int age;
  long secret;
}p;

要将结构p的内容以文本格式保存,可以这样做:


ofstream outf("people.txt",ios::out|ios::app);

outf<<p.name<<" "<<p.age<<" "<<p.secret<<endl;

必须使用成员运算符显式地提供每个结构成员,还必须将相邻的数据分隔开,以便区分。如果结构有30个成员,则这项工作将很乏味。

要用二进制格式存储相同的信息,可以这样做:


ofstream outf("people.txt",ios::out|ios::app|ios::binary);

outf.write((char*)&p,sizeof(p));

上述代码使用计算机的内部数据表示,将整个结构作为一个整体保存。不能将该文件作为文本读取,但与文本相比,信息的保存更为紧凑、精确。

它确实更便于键入代码。这种方法做了两个修改:

使用二进制文件模式;
使用成员函数write()。

使用二进制文件模式时,程序将数据从内存传输给文件(反之亦然)时,将不会发生任何隐藏的转换,而默认的文本模式并非如此。

各操作系统之间的文本文件换行符并不相同:

windows(回车加换行)代表换行。
Mac(回车)代表换行。
Unix和Linux(换行)代表换行。

C++为了增加可移植性,在读写文件时会自动发生对应的转换。
但对于二进制数据,文本格式可能会产生冲突,且在文件尾检测方式也有所区别。因此,C++存储二进制数据应该使用二进制文件模式。

要以二进制格式(而不是文本格式)存储数据,可以使用write( )成员函数。

这种方法将内存中指定数目的字节复制到文件中。它只逐字节地复制数据,而不进行任何转换。唯一不方便的地方是,必须将地址强制转换为指向char的指针。

要使用文件恢复信息,请通过一个ifstream对象使用相应的read( )方法:


ifstream inf("people.txt",ios::in|ios::binary);

inf.read((char*)&p,sizeof(p));

这将从文件中复制sizeof p个字节到p结构中。同样的方法也适用于不使用虚函数的类。在这种情况下,只有数据成员被保存,而方法不会被保存。

如果类有虚方法,则也将复制隐藏指针(该指针指向虚函数的指针表)。由于下一次运行程序时,虚函数表可能在不同的位置,因此将文件中的旧指针信息复制到对象中,将可能造成混乱。

read()和write()成员函数的功能是相反的。请用read()来恢复用write()写入的数据。

注意:
虽然二进制文件概念是ANSII C的组成部分,但一些C和C++实现并没有提供对二进制文件模式的支持。

原因在于:有些系统只有一种文件类型,因此可以将二进制操作(如read()和write())用于标准文件格式。

1.3.10. 随机存取

随机存取指的是直接移动(不是依次移动)到文件的任何位置。随机存取常被用于数据库文件,程序维护一个独立的索引文件,该文件指出数据在主数据文件中的位置。

这样,程序便可以直接跳到这个位置,读取(还可能修改)其中的数据。

我们来看个案例:
该案例将完成以下工作:

  1. 显示文件当前的内容;
  2. 询问要修改哪条记录;
  3. 修改该记录;
  4. 显示修改后的文件。

为读取文件,需要使用ios_base::in模式。为执行二进制I/O,需要使用ios_base::binary模式。

为写入文件,需要ios_base::out或ios_base::app模式。

然而,追加模式只允许程序将数据添加到文件尾,文件的其他部分是只读的;也就是说,可以读取原始数据,但不能修改它;要修改数据,必须使用ios_base::out。


finout.open(file,ios::out|ios::in|ios::binary);

接下来,需要一种在文件中移动的方式。

fstream类为此继承了两个方法:seekg()和seekp(),前者将输入指针移到指定的文件位置,后者将输出指针移到指定的文件位置。

实际上,由于fstream类使用缓冲区来存储中间数据,因此指针指向的是缓冲区中的位置,而不是实际的文件。

也可以将seekg()用于ifstream对象,将seekp()用于oftream对象。下面是这两个成员函数的原型:


//seekg
istream& seekg(streamoff offset, seekdir origin);

istream& seekg(streamoff offset);

//seekp
ostream& seekp(streamoff offset, seekdir origin);

ostream& seekp(streamoff offset);

第一个原型定位到离第二个参数指定的文件位置特定距离(单位为字节)的位置;第二个原型定位到离文件开头特定距离(单位为字节)的位置。

offset表示偏移量,origin表示移动的基准位置,取值如下∶
ios::beg 文件开头

ios::cur 文件当前位置

ios::end 文件结尾

示例∶


inFile.seekg(2,ios::beg);  //把文件从开始位置向后移动2个字节
inFile.seekg(2,ios::cur);  //把文件从当前位置尚后移动2个字节

如果要检查文件指针的当前位置,则对于输入流,可以使用tellg()方法。
对于输出流,可以使用tellp()方法。
它们都返回一个表示当前位置的streampos值(以字节为单位,从文件开始处算起)。
示例:


auto fpos=inFile.tellg(); //返回当前文件输出指针的位置
auto fpos=inFile.tellp(); //返回当前文件输入指针的位置

注意:
如果读者还对输入流和输出流这两个概念感到有些混淆,可以这样想,输入流和输出流不是相对文件,而是相对程序本身而言的,从文件中读取信息到程序,对程序而言就是输入流,从程序输出信息到文件,对程序而言就是输出流。

案例1:


#include<iostream>
#include<fstream>
#include<string>

struct people {
	char name[20];
	int age;
	int weight;
}p;

const char* file = "fileName.txt";  

int main()
{
	using namespace std;

	fstream f;
	//打开二进制文件用于读写
	f.open(file, ios_base::binary | ios_base::in | ios_base::out);

	//判断文件是否正常打开
	if ( f.is_open() )
	{
		//如果正常打开,输出文件内已有的信息
		while (f.read((char*)&p,sizeof(p)))
		{
			cout << "名字:"<<p.name << "  ";
			cout << "年龄:"<<p.age << "  ";
			cout << "体重:"<<p.weight << endl;
		}

		//重置流状态
		f.clear();
	}
	else
	{
		//如果文件未存在

		//重置流状态
		f.clear();
		//以写入文件的方式打开文件,即创建该文件
		f.open(file, ios_base::binary | ios_base::out |ios_base::trunc);
	}

	//设置输出文件指针的位置
	f.seekp(0, ios_base::end);
	cout << "输入名字:" << endl;
	cin.get(p.name,20);

	//循环输入信息,直到输入的名字为换行
	while (p.name[0]!='\0')
	{
		//输入信息,并在输入每条信息后将多余的输入丢弃掉
		while ( getchar() != '\n' );
		cout << "请输入年龄:" << endl;
		cin >> p.age;
		while ( getchar() != '\n' );
		cout << "请输入体重:" << endl;
		cin >> p.weight;
		while ( getchar() != '\n' );

		//将信息写输入文件
		f.write((char*) &p, sizeof(p));

		cout << "输入名字:" << endl;
		cin.get(p.name, 20);
	}

	//关闭文件
	f.close();
	//重置流状态
	f.clear();
	
	//以读文件的方式打开文件
	f.open(file, ios_base::binary | ios_base::in);

	if ( f.is_open() )
	{
		//读取文件内容
		while ( f.read((char*) &p, sizeof(p)) )
		{
			cout << "名字:" << p.name << "  ";
			cout << "年龄:" << p.age << "  ";
			cout << "体重:" << p.weight << endl;
		}

		//关闭文件
		f.close();
		f.clear();
	}

	return 0;
}

案例2:


#include<iostream>
#include<fstream>

//默认文件已存在
const char* file = "fileName.txt";

struct people {
	char name[20];
	int age;
	int weight;
}p;

void eatLine()
{
	while ( getchar() != '\n' );
}

int main()
{

	using namespace std;

	//以读写文件方式打开文件
	fstream f(file, ios_base::in | ios_base::out | ios_base::binary);

	int count = 0;  //显示的序号
	int ret = 0;    //要修改的序号
	int replace=0;  //修改的位置


	//如果文件打开正常
	if ( f.is_open() )
	{

		//输出文件内已有的内容
		while ( f.read((char*) &p, sizeof(p)) )
		{
			cout << "个人信息:" << endl;
			cout << count++ << " ";
			cout << p.name << " "; 
			cout << p.age << " ";
			cout << p.weight << endl;
		}
		//如果到文件尾,重置流状态
		if(f.eof())
		f.clear();
		else
		{
			//如果未完全读取,结束程序
			cout << "未完全读取" << endl;
			return 0;
		}


		cout << "input---->>>" << endl;
		cout << "请输入你要修改的序号    " << count << endl;
		//重置标准输入流状态
		cin.clear();

		//输入修改的序号
		cin >> ret;

		//当输入值非法时
		while( ret < 0 || ret >= count )
		{
			cout << "输入值非法,重新输入" << endl;
			cin >> ret;
		}

		eatLine();  //丢弃多余的部分

		//计算要修改的值在文件内对应的位置
		replace = ret * sizeof(p);

		//输出指针定位
		f.seekp(replace);

		if ( f.fail() )
		{
			cout << "定位失败" << endl;
			return 0;
		}

		cout << "请输入要修改的内容:" << endl;
		cin >> p.name;
		eatLine();
		cin >> p.age;
		eatLine();
		cin >> p.weight;
		eatLine();

		f.write((char*) &p, sizeof(p));


		if ( f.fail() )
		{
			cout << "写入失败" << endl;
			return 0;
		}
		
		count = 0;
		//定位输入指针
		f.seekg(0);
		//重置流状态
		f.clear();

		//输出文件更新后的内容
		while ( f.read((char*) &p, sizeof(p)) )
		{
			cout << "个人信息:" << endl;
			cout << count++ << " ";
			cout << p.name << " ";
			cout << p.age << " ";
			cout << p.weight << endl;
		}

		if ( f.eof() )
			f.clear();
	}

	//关闭文件
	f.close();


	return 0;
}

注意;
如果该程序是否可以使用string对象而不是字符数组来表示people结构的name成员?

答案是否定的,至少在不对设计做重大修改的情况下是否定的。

问题在于,string对象本身实际上并没有包含字符串,而是包含一个指向其中存储了字符串的内存单元的指针。

因此,将结构复制到文件中时,复制的将不是字符串数据,而是字符串的存储地址。当您再次运行该程序时,该地址将毫无意义。


1.4. 字符串流

字符串流就是能够控制字符串类型对象进行输入输出的类,C++不光支持C++风格的字符串流控制,还支持C风格的字符串流控制。

继承关系如下图:
在这里插入图片描述

istrstream   //C风格的串流的输入操作,字符串数组作为输入

ostrstream  //C风格的串流输出操作,字符串数组作为输出

strstream   //支持C风格的串流的输入输出操作

iostream族(family)支持程序与终端之间的I/O,而fstream族使用相同的接口提供程序和文件之间的I/O。

C++库还提供了sstream族,它们使用相同的接口提供程序和string对象之间的I/O。

读取string对象中的格式化信息或将格式化信息写入string对象中被称为内核格式化(incore formatting)。

头文件sstream定义了从ostream类派生而来的ostringstream类和istringstream类。

如果创建了一个ostringstream对象,则可以将信息写入其中,它将存储这些信息。可以将可用于cout的方法用于ostringstream对象。

例如:


ostringstream outstr;
double price =380.0;

char*p="for a copy of the ISO/ETC C++ standard!";

outstr<<"pay only CHF"<<price<<ps<<endl;

ostringstream类有一个名为str()的成员函数,该函数返回一个被初始化为缓冲区内容的字符串对象。

使用str()方法可以“冻结”该对象,这样便不能将信息写入该对象中。


string s=outstr.str();
cout<<s<<endl;

istringstream类允许使用istream方法族读取istringstream对象中的数据,istringstream对象可以使用string对象进行初始化。
例如:


string s("I am Chinese");
istringstream instr(s);

这样,便可以使用istream方法读取instr中的数据。


string word;
while(instr>>word)
cout<<word<<endl;

总之,istringstream和ostringstream类使得能够使用istream和ostream类的方法来管理存储在字符串中的字符数据。当然,使用时应记得包含头文件sstream。

案例:


#include<iostream>
#include<sstream>
#include<string>

int main()
{
	using namespace std;

	ostringstream outstr;

	string s;
	getline(cin, s);

	float f;
	cin >> f;

	const char* ch = "谢谢各位帅哥美女的支持!!!";

	outstr << "感谢:" << ch << s << f << endl;

	string ret = outstr.str();
	cout << ret << endl;


	string s = "Life is like a sea, only the strong will of people, to reach the other shore。";
	istringstream p(s);
	
	string word;
	while ( p >> word )
	{
		cout << word << endl;
	}

	return 0;
}

1.5. 总结

fstream文件提供了将iostream方法扩展到文件I/O的类定义。

ifstream类是从istream类派生而来的。通过将ifstream对象与文件关联起来,可以使用所有的istream方法来读取文件。

同样,通过将ofstream对象与文件关联起来,可以使用ostream方法来写文件;通过将fstream对象与文件关联起来,可以将输入和输出方法用于文件。

要将文件与流关联起来,可以在初始化文件流对象时提供文件名,也可以先创建一个文件流对象,然后用open()方法将这个流与文件关联起来。

close()方法终止流与文件之间的连接。

类构造函数和open()方法接受可选的第二个参数,该参数提供文件模式。

文件模式决定文件是否被读和/或写、打开文件以便写入时是否截短文件、试图打开不存在的文件时是否会导致错误、是使用二进制模式还是文本模式等。

文本文件以字符格式存储所有的信息,例如,数字值将被转换为字符表示。常规的<<和>>运算符以及get(),put()和getline()都支持这种模式。

二进制文件使用计算机内部使用的二进制表示来存储信息。与文本文件相比,二进制文件存储数据(尤其是浮点值)更为精确、简洁,但可移植性较差。read()和write()方法都支持二进制输入和输出。

seekg()和seekp()函数提供对文件的随机存取。这些类方法使得能够将文件指针放置到相对于文件开头、文件尾和当前位置的某个位置。

tellg()和tellp()方法报告当前的文件位置。

sstream头文件定义了istringstream和ostringstream类,这些类使得能够使用istream和ostream方法来抽取字符串中的信息,并对要放入到字符串中的信息进行格式化。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 文件流操作详解 的相关文章

随机推荐

  • Linux创建文件的5种方式

    Linux创建文件的5种方式 1 touch 1 1 创建一个文件 touch yyTest ini 1 2 同时创建两个文件 touch test1 txt test2 txt 1 3 批量创建文件 如创建2000个文件 touch te
  • 自定义modal转场动画,滑动手势控制 dismiss 过程

    效果 假设有 1 两个视图控制器 presentingVC presentedVC 2 一个继承于UIPercentDrivenInteractiveTransition 并遵守协议UIViewControllerAnimatedTrans
  • java上传Base64编码格式的图片

    工具类 public class ImageBase64Converter 本地文件 图片 excel等 转换成Base64字符串 param imgPath public static String convertFileToBase64
  • LWIP学习笔记(2)---IP协议

    IP首部 最高位在左边记为 bit 最低位在右边 记为31 bit 传输顺序 先0 7bit 在8 15bit 然后16 13 最后24 31bit 这种方式称为 big endian 也叫网络字节序 版本 ipv4 或 6 ipv6 首部
  • Scanner类: next() 与 nextLine()与nextInt()

    next nextLine nextInt 是scanner内置的方法 next 1 一定要读取到有效字符后才可以结束输入 2 对输入有效字符之前遇到的空白 next 方法会自动将其去掉 3 只有输入有效字符后才将其后面输入的空白作为分隔符
  • 从零开始入门 K8s

    作者 至天 阿里巴巴高级研发工程师 一 基本知识 存储快照产生背景 在使用存储时 为了提高数据操作的容错性 我们通常有需要对线上数据进行 snapshot 以及能快速 restore 的能力 另外 当需要对线上数据进行快速的复制以及迁移等动
  • 解决cmd命令行运行java程序,编译通过,执行时却找不到主类的问题

    命令行中使用javac命令编译Train通过 使用java命令运行却找不到主类Trian 原因 有package的存在 编译成功后 需要返回上一层文件目录使用java命令执行train Trian 即 java命令后跟 包名称调用主类名称
  • Ubuntu和Windows使用Mmdetection训练Swin-Transformer+Mask-RCNN

    最近想用各种SOTA的Swin Transformer来试试实例分割效果 于是找了一下教程实验了一下 主要分为以下步骤 1 安装Mmdetection 这部分的教程很多 网上搜一下就行了 但是这里出错最多 2 下载Swin Transfor
  • java 获取list中的两两组合,给定一个数组,获取排列组合

    如题 获取一个数组中两两组合 示例 给定一个List lt 1 2 3 4 gt 输出 1 2 1 3 1 4 2 3 2 4 3 4 demo public static void main String args List
  • 几率大的网络安全面试题(含答案)

    其他面试题类型汇总 Java校招极大几率出的面试题 含答案 汇总 几率大的网络安全面试题 含答案 几率大的多线程面试题 含答案 几率大的源码底层原理 杂食面试题 含答案 几率大的Redis面试题 含答案 几率大的linux命令面试题 含答案
  • 【Linux】 Linux使用timedatectl命令修改时间报错

    Linux中可以使用timedatectl命令来修改系统时间信息 具体命令中常见的参数格式及作用如下 参数 作用 status 显示状态信息 list timezones 列出已知时区 set time 设置系统时间 set timezon
  • STM32学习笔记——USB鼠标

    最近搞了好久的STM32模拟USB鼠标 功能就是简单的利用三个按键实现滚轮和鼠标左右键的功能 USB功能其实已经集成好一个库了 我们只是对其中几个函数进行配置而已 其实很多配置还不是太懂 整个USB程序的过程大概就是1 中断配置 2 USB
  • 【win10】 设置应用开机自启动

    步骤如下 1 按Win r键 输入 shell startup 2 确定后会出现一个文件夹 把要开机启动的应用快捷方式放到里面 3 在任务管理器的启动里面进行设置 可以在状态字段选择启用或者禁用 放在文件夹里只是让它可以在任务管理器的启动里
  • jdk与jre的区别

    jdk与jre的区别 很多程序员已经干了一段时间java了依然不明白jdk与jre的区别 JDK就是Java Development Kit 简单的说JDK是面向开发人员使用的SDK 它提供了Java的开发环境和运行环境 SDK是Softw
  • 解决Ubuntu 20.04 node-v 和nodejs --version显示不同版本

    Ubuntu 20 04 node v 和nodejs version显示不同版本 1 删除原来的node js版本以及之前的软链接 我这里是输入node v显示4 0 0pre 首先要删除 卸载这个版本对应的node js文件 此时如果在
  • Match Points【Codeforces 1156C】【二分答案】

    题目链接 题意有点像上海EC某年的一道铜牌题 具体是哪年记不得了 我们要去N个的关系 使得最多的匹配对达到他们的差值 Z 这样的情况 有这样的一组数据可以很好的反映这道题为什么有人会WA了 4 3 1 4 5 7 但是 同时也证明了 我们取
  • 《机器学习实战》4.朴素贝叶斯

    目录 1 基于贝叶斯决策理论的分类方法 2 利用朴素贝叶斯进行文档分类 一般过程 3 使用python进行文本分类 3 1准备数据 从文本中构建词向量 3 2 训练算法 从词向量计算概率 3 3测试算法 根据现实情况修改分类器 3 4 准备
  • 【SDR】OpenBTS 介绍及安装

    前言 今天是元旦假期的第一天 还有两天 就要跨入2018年了 无心工作 写两篇博客吧 纪念一下自己的2017 也为2018开个好头 希望2018 我的三个愿望 能够圆满实现 好了 开始正题 前一段时间搭建了OpenBTS的环境 用于测试其相
  • 图形界面编程:使用C语言开发GUI应用

    使用C语言开发图形界面 GUI 应用通常涉及使用特定的库或框架 下面将为您提供一个详细的教程 以介绍使用C语言开发GUI应用的一般步骤和常用库 1 选择GUI库 C语言本身不提供直接的GUI支持 因此您需要选择一个适合的GUI库或框架 以下
  • C++ 文件流操作详解

    1 C I O流 本文章有很多内容参考并借鉴了 C primer plus 这本经典 这里先说明一下 1 C I O流 1 1 数据流 1 2 控制台流 1 3 文件流 1 3 1 什么是文件流 1 3 2 缓冲区 1 3 3 文件流和控制