C++ primer plus 第17 章 输入、输出和文件:文件输入和输出03:文件模式:二进制文件

news2024/12/25 13:32:36

系列文章目录

17.4.5 文件模式

程序清单17.18 append.cpp

程序清单17.19 binary.cpp

文章目录

  • 系列文章目录
  • 17.4.5 文件模式
  • 程序清单17.18 append.cpp
  • 程序清单17.19 binary.cpp
  • 17.4.5 文件模式
  • 1.追加文件来看一个在文件尾追加数据的程序。
  • 程序清单17.18 append.cpp
  • 2.二进制文件
  • 二进制文件和文本文件
  • 程序清单17.19 binary.cpp


17.4.5 文件模式

文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时(无论是使用文件名初始化文件流对象,还是使用open()方法),都可以提供指定文件模式的第二个参数:

ifstream fin("banjo",model);/constructor with mode arqument
ofstream fout();
fout.open("harp",mode2);//open()with mode arguments

ios base 类定义了一个 openmode 类型,用于表示模式;与 fimtflags 和 iostate 类型一样,它也是一种bitmask 类型(以前,其类型为 int)。可以选择 ios base 类中定义的多个常量来指定模式,表 17.7列出了这些常量及其含义。C++文件 I/O作了一些改动,以便与 ANSIC 文件 I/O 兼容。
在这里插入图片描述
如果 ifstream 和 ofstream 构造函数以及 open( )方法都接受两个参数,为什么前面的例子只使用一个参数就可以调用它们呢?您可能猜到了,这些类成员函数的原型为第二个参数(文件模式参数)提供了默认值。例如,ifstream open()方法和构造函数用ios base:in(打开文件以读取)作为模式参数的默认值,而ofstream open()方法和构造函数用ios base:out|ios base:trunc(打开文件,以读取并截短文件)作为默认值。位运算符 OR(1)用于将两个位值合并成一个可用于设置两个位的值。fstream 类不提供默认的模式值,因此在创建这种类的对象时,必须显式地提供模式。
注意,ios base::trunc标记意味着打开已有的文件,以接收程序输出时将被截短;也就是说,其以前的内容将被删除。虽然这种行为极大地降低了耗尽磁盘空间的危险,但您也许能够想象到这样的情形,即不希望打开文件时将其内容删除。当然,C++提供了其他的选择。例如,如果要保留文件内容,并在文件尾添加(追加)新信息,则可以使用ios base::app 模式:

ofstream fout("bagels",ios base::outios | base::app);

上述代码也使用|运算符来合并模式,因此ios base::out|ios base::app意味着启用模式out和 app(参见图 17.6)。
在这里插入图片描述
老式C++实现之间可能有一些差异。例如,有些实现允许省略前一例子中的iosbase::out,有些则不允许。如果不使用默认模式,则最安全的方法是显式地提供所有的模式元素。有些编译器不支持表17.6中的所有选项,有些则提供了表中没有列出的其他选项。这些差异导致的后果之一是,可能必须对后面的例子作一些修改,使之能够在所用的系统中运行。好在C++标准提供了更高的统一性。
标准 C++根据 ANSIC标准 IO 定义了部分文件 JO。实现像下面这样的 C++语句时:

ifstream fin(filename, c++mode);

就像它使用了C的fopen()函数一样:

fopen(filename, cmode);

其中,c++mode 是一个 openmode 值,如 ios base::in;而 cmode 是相应的℃模式字符串,如“r”表 17.8列出了 C++模式和C模式的对应关系。注意,ios base::out本身将导致文件被截短,但与ios base::in起使用时,不会导致文件被截短。没有列出的组合,如ios base:in[vn]ios base::trunc,将禁止文件被打开。is open()方法用于检测这种故障。
在这里插入图片描述
注意,ios_base:ate 和ios_base::app 都将文件指针指向打开的文件尾。二者的区别在于,ios base::app模式只允许将数据添加到文件尾,而iosbase::ate模式将指针放到文件尾。显然,各种模式的组合很多,我们将介绍几种有代表性的组合。

1.追加文件来看一个在文件尾追加数据的程序。

该程序维护一个存储来客清单的文件。该程序首先显示文件当前的内容(如果有话)。在尝试打开文件后,它使用isopen()方法来检查该文件是否存在。接下来,程序以ios_base:app 模式打开文件,进行输出。然后,它请求用户从键盘输入,并将其添加到文件中。最后,程序显示修订后的文件内容。程序清单17.18演示了如何实现这些目标。请注意程序是如何使用isopen()方法来检测文件是否被成功打开的。
注意:在早期,文件 I/0 可能是 C++最不标准的部分,很多老式编译器都不遵守当前的标准。例如,有些编译器使用诸如 nocreate 等模式,而这些模式不是当前标准的组成部分。另外,只有一部分编译器要求在第二次打开同一个文件进行读取之前调用fin.clear()。

程序清单17.18 append.cpp

// append.cpp -- appending information to a file
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>      // (or stdlib.h) for exit()

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

// show initial contents
    ifstream fin;
    fin.open(file);

    if (fin.is_open())
    {
        cout << "Here are the current contents of the "
             << file << " file:\n";
        while (fin.get(ch))
            cout << ch;
        fin.close();
    }

// add new names
    ofstream fout(file, ios::out | ios::app);
    if (!fout.is_open())
    {
        cerr << "Can't open " << file << " file for output.\n";
        exit(EXIT_FAILURE);
    }

    cout << "Enter guest names (enter a blank line to quit):\n";
    string name;
    while (getline(cin,name) && name.size() > 0)
    {
          fout << name << endl;
    }
    fout.close();

// show revised file
    fin.clear();    // not necessary for some compilers
    fin.open(file);
    if (fin.is_open())
    {
        cout << "Here are the new contents of the "
             << file << " file:\n";
        while (fin.get(ch))
            cout << ch;
        fin.close();
   }
    cout << "Done.\n";
    // cin.get();
    return 0; 
}

在这里插入图片描述
此时,guests.txt文件还没有创建,因此程序不能预览该文件。但第二次运行该程序时,guests.txt文件已经存在,因此程序将预览该文件。另外,新数据被追加到旧文件的后面,而不是取代它们。
在这里插入图片描述
可以用任何文本编辑器来读取 guest.txt的内容,包括用来编写源代码的编辑器。

2.二进制文件

将数据存储在文件中时,可以将其存储为文本格式或二进制格式。文本格式指的是将所有内容(甚至数字)都存储为文本。例如,以文本格式存储值-2.324216e+07时,将存储该数字包含的13个字符。这需要将浮点数的计算机内部表示转换为字符格式,这正是<<插入运算符完成的工作。另一方面,二进制格式指的是存储值的计算机内部表示。也就是说,计算机不是存储字符,而是存储这个值的 64位 double 表示。对于字符来说,二进制表示与文本表示是一样的,即字符的 ASCI 码的二进制表示。对于数字来说,二进制表示与文本表示有很大的差别(参见图17.7)。
在这里插入图片描述
每种格式都有自己的优点。文本格式便于读取,可以使用编辑器或字处理器来读取和编辑文本文件,可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统。二进制格式对于数字来说比较精确,因为它存储的是值的内部表示,因此不会有转换误差或舍入误差。以二进制格式保存数据的速度更快,因为不需要转换,并可以大块地存储数据。二进制格式通常占用的空间较小,这取决于数据的特征。然而,如果另一个系统使用另一种内部表示,则可能无法将数据传输给该系统。同一系统上不同的编译器也可能使用不同的内部结构布局表示。在这种情况下,则必须编写一个将一种数据转换成另一种的程序。来看一个更具体的例子。考虑下面的结构定义和声明:

const int LIM=20;
struct planet
{
	char name[LIM];// name of planet
	double population;// its population
	double g;// its acceleration of qravity
}
planet pl;

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

ofstream fout("planets.dat",ios base::outios |base::app) ;
fout << pl.name<<""<< pl.population << " "<< pl.g << "\n"

必须使用成员运算符显式地提供每个结构成员,还必须将相邻的数据分隔开,以便区分。如果结构有30个成员,则这项工作将很乏味。
要用二进制格式存储相同的信息,可以这样做:

ofstream fout("planets.dat"
	ios base::outios base::appios|base::binary);
fout.write((char *)&pl,sizeof pl);

上述代码使用计算机的内部数据表示,将整个结构作为一个整体保存。不能将该文件作为文本读取,但与文本相比,信息的保存更为紧凑、精确。它确实更便于键入代码。这种方法做了两个修改:
使用二进制文件模式:
使用成员函数write()。
下面更详细的介绍这两项修改。
有些系统(如Windows)支持两种文件格式:文本格式和二进制格式。如果要用二进制格式保存数据应使用二进制文件格式。在C++中,可以将文件模式设置为ios base::binary 常量来完成。要知道为什么在Windows系统上需要完成这样的任务,请参见后面的旁注“二进制文件和文本文件”。

二进制文件和文本文件

使用二进制文件模式时,程序将数据从内存传输给文件(反之亦然)时,将不会发生任何隐藏的转换而默认的文本模式并非如此。例如,对于 Windows文本文件,它们使用两个字符的组合(回车和换行)表示换行符;Macintosh 文本文件使用回车来表示换行符;而 UNIX和 Linux 文件使用换行(linefeed)来表示换行符。C++是从 UNIX系统上发展而来的,因此也使用换行(linefecd)来表示换行符。为增加可移植性,Windows C++程序在写文本模式文件时,自动将C++换行符转换为回车和换行;Macintosh C++程序在写文件时,将换行符转换为回车。在读取文本文件时,这些程序将本地换行符转换为C++格式。对于二进制数据,文本格式会引起问题,因此 double 值中间的字节可能与换行符的 ASCII 码有相同的位模式。另外在文件尾的检测方式也有区别。因此以二进制格式保存数据时,应使用二进制文件模式(UNIX 系统只有一种文件模式,因此对于它来说,二进制模式和文本模式是一样的)。
要以二进制格式(而不是文本格式)存储数据,可以使用write0)成员函数。前面说过,这种方法将内存中指定数目的字节复制到文件中。本章前面用它复制过文本,但它只逐字节地复制数据,而不进行任何转换。例如,如果将一个long 变量的地址传递给它,并命令它复制4个字节,它将复制 long值中的4个字节,而不会将它转换为文本。唯一不方便的地方是,必须将地址强制转换为指向char 的指针。也可以用同样的方式来复制整个planet结构。要获得字节数,可以使用sizeof运算符:

fout.write((char *)&pl,sizeof pl);

这条语句导致程序前往 pl结构的地址,并将开始的36个字节(sizeofpl表达式的值)复制到与 fout相关联的文件中。
要使用文件恢复信息,请通过一个ifstream对象使用相应的read()方法:

ifstream fin("planets.dat",ios base::in | ios base::binary);
fin.read((char *)&pl,sizeof pl);

这将从文件中复制 sizeofpl个字节到p1结构中。同样的方法也适用于不使用虚函数的类。在这种情况下,只有数据成员被保存,而方法不会被保存。如果类有虚方法,则也将复制隐藏指针(该指针指向虚函数的指针表)。由于下一次运行程序时,虚函数表可能在不同的位置,因此将文件中的旧指针信息复制到对象中,将可能造成混乱(请参见“编程练习6”中的注意)。
提示:read()和 write()成员函数的功能是相反的。请用read()来恢复用 write()写入的数据
程序清单17.19使用这些方法来创建和读取二进制文件。从形式上看,该程序与程序清单17.18相似,但它使用的是 write()和read(),而不是插入运算符和 get()方法。另外,它还使用控制符来格式化屏幕输出。注意:虽然二进制文件概念是 ANSIC的组成部分,但一些C和C++实现并没有提供对二进制文件模式的支持。原因在于:有些系统只有一种文件类型,因此可以将二进制操作(如read()和 write())用于标准文件格式。因此,如果实现认为 ios base:binary 是非法常量,只要删除它即可。如果实现不支持 fixed和 right 控制符,则可以使用 cout.setf(ios base::fixed、ios base::foatfield)和 cout.setf(ios base:.right.ios base::adiustfield)。另外,也可能必须用ios替换ios base。其他编译器(特别是老式编译器)可能还有其他特征。

程序清单17.19 binary.cpp

// binary.cpp -- binary file I/O
#include <iostream> // not required by most systems
#include <fstream>
#include <iomanip>
#include <cstdlib>  // (or stdlib.h) for exit()

inline void eatline() { while (std::cin.get() != '\n') continue; }
struct planet
{
    char name[20];      // name of planet
    double population;  // its population
    double g;           // its acceleration of gravity
};

const char * file = "planets.dat";

int main()
{
    using namespace std;
    planet pl;
    cout << fixed << right;

// show initial contents
    ifstream fin;
    fin.open(file, ios_base::in |ios_base::binary);  // binary file
    //NOTE: some systems don't accept the ios_base::binary mode
    if (fin.is_open())
    {
    cout << "Here are the current contents of the "
        << file << " file:\n";
    while (fin.read((char *) &pl, sizeof pl))
    {
        cout << setw(20) << pl.name << ": "
              << setprecision(0) << setw(12) << pl.population
              << setprecision(2) << setw(6) << pl.g << endl;
    }    
    fin.close();
    }

// add new data
    ofstream fout(file, 
             ios_base::out | ios_base::app | ios_base::binary);
    //NOTE: some systems don't accept the ios::binary mode
    if (!fout.is_open())
    {
        cerr << "Can't open " << file << " file for output:\n";
        exit(EXIT_FAILURE);
    }

    cout << "Enter planet name (enter a blank line to quit):\n";
    cin.get(pl.name, 20);
    while (pl.name[0] != '\0')
    {
        eatline();
        cout << "Enter planetary population: ";
        cin >> pl.population;
        cout << "Enter planet's acceleration of gravity: ";
        cin >> pl.g;
        eatline();
        fout.write((char *) &pl, sizeof pl);
        cout << "Enter planet name (enter a blank line "
                "to quit):\n";
        cin.get(pl.name, 20);
    }
    fout.close();

// show revised file
    fin.clear();    // not required for some implementations, but won't hurt
    fin.open(file, ios_base::in | ios_base::binary);
    if (fin.is_open())
    {
        cout << "Here are the new contents of the "
             << file << " file:\n";
        while (fin.read((char *) &pl, sizeof pl))
        {
            cout << setw(20) << pl.name << ": "
                 << setprecision(0) << setw(12) << pl.population
                 << setprecision(2) << setw(6) << pl.g << endl;
        }
        fin.close();
    }
    cout << "Done.\n";
// keeping output window open
    // cin.clear();
    // eatline();
    // cin.get();
    return 0; 
}

在这里插入图片描述
看到该程序的主要特征后,下面再次讨论前面提到的几点。程序在读取行星的g值后,将使用下面的代码(以内嵌eatline()函数的形式):

while(std::cin.get()!=n')continue;

这将读取并丢弃输入中换行符之前的内容。考虑循环中的下一条输入语句:

cin.get(pl.name,20);

如果保留换行符,该语句将换行符作为空行读取,然后终止循环,
您可能会问,如果该程序是否可以使用string对象而不是字符数组来表示planet 结构的name 成员?答案是否定的,至少在不对设计做重大修改的情况下是否定的。问题在于,string对象本身实际上并没有包含字符串,而是包含一个指向其中存储了字符串的内存单元的指针。因此,将结构复制到文件中时,复制的将不是字符串数据,而是字符串的存储地址。当您再次运行该程序时,该地址将毫无意义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1996142.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05_ Electron 自定义菜单、主进程与渲染进程通信

Electron 自定义菜单、主进程与渲染进程通信 一、定义顶部菜单二、Electron 自定义右键菜单1、使用 electron/remote 模块实现 三、 Electron 主进程和渲染进程通信场景1&#xff1a;渲染进程给主进程发送异步消息场景2&#xff1a;渲染进程给主进程发送异步消息&#xff0c;主…

Vue3使用ECharts的自定义堆叠条形图(纵向)

先上效果图 展示区域 <div id"main" style"height: 300px; width: 100%"></div> 配置信息 每个种类的data数组是number数组&#xff0c;这里我使用的变量是从接口返回的数据&#xff0c;为了方便理解&#xff0c;年度考核的绿色种类&#x…

AT32F421驱动BLDC 配合上位机控制与调参

AT32F421驱动BLDC 配合上位机控制与调参 &#x1f527;AT32 电机控制与调参上位机软件&#xff1a;ArteryMotorMonitor&#xff1a;https://www.arterytek.com/cn/support/motor_control.jsp?index0&#x1f33f;测试电机参数&#xff1a;2204-12N14P&#xff0c;无感BLDC&…

Summer School science communication project--Laptop Selection Suggestion

目录 Introduction Audiance Usage CPU What is a central processing unit (CPU) Notable makers of CPUs GPU Graphics Card: GPU The classifications of graphics cards The brands of graphics cards Dedicated Graphics Cards GeForce MX Series: GeForc…

渠道刷量怎么办?Xinstall来帮你一键识破!

在App推广的道路上&#xff0c;数据是我们最得力的助手&#xff0c;也是我们最头疼的难题。每日下载量、安装量、注册量……这些看似简单的数字&#xff0c;背后却隐藏着无数的故事。哪个渠道在默默发力&#xff1f;哪个渠道又在浑水摸鱼&#xff1f;这一切&#xff0c;都需要我…

【生信入门linux篇】如何安装一个linux虚拟机用于学习

一.虚拟机 虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;是一种软件实现的计算机系统&#xff0c;它能够在物理计算机上模拟出多个独立的计算机环境。每个虚拟机都可以运行自己的操作系统和应用程序&#xff0c;就像在独立的物理计算机上一样。虚拟机技术…

(十)Dockerfile详解及使用教程

一、Dockerfile是什么 1、简介 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本 官网地址&#xff1a;https://docs.docker.com/engine/reference/builder/ 构建步骤 编写Dockerfile文件docker build命令构建镜像docke…

基于Java+SpringBoot+Vue前后端分离药店/药品/药品采购/药品销售管理系统设计与实现(有视频讲解)

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

数据结构总体概述

参考&#xff1a; 【王道考研】王道数据结构与算法详细笔记&#xff08;全&#xff09;_王道数据结构笔记-CSDN博客 数据结构的三要素 一、数据的逻辑结构 逻辑结构是指数据元素之间的逻辑关系&#xff0c;即从逻辑关系上描述数据。 逻辑结构包括&#xff1a; 集合结构&#xf…

【数据结构】—— 内部排序算法详解

1、前言2、常见排序算法3、排序算法实现3.1 直接插入排序3.2 希尔排序3.3 选择排序3.4 堆排序3.5 冒泡排序3.6 快速排序3.6.1 单趟排序hoare法挖坑法双指针法 3.6.2 非递归实现3.6.3 常见问题基准值的选取小区间优化 3.7 归并排序3.7.1 递归实现3.7.2 非递归实现 3.8 计数排序 …

SAM2分割模型微调指南

SAM2&#xff08;Segment Anything 2&#xff09;是 Meta 推出的一款新模型&#xff0c;旨在对图像中的任何内容进行分割&#xff0c;而不局限于特定的类别或领域。该模型的独特之处在于其训练数据规模&#xff1a;1100 万张图像和 110 亿个掩码。这种广泛的训练使 SAM2 成为训…

AI大语言模型对消防工程知多少?

在过去2年的时间里&#xff0c;大语言模型受到前所未有的关注。ChatGPT的出现更是让人工智能对话风靡一时。我们不再把搜索引擎当作求解问题的唯一途径&#xff0c;AI聊天成为了当前最受欢迎的问题求助工具。 让ChatGPT用通俗的语言解释什么是ChatGPT 什么是大语言模型&#x…

SD微调 dreambooth Lora controlNet 持续更新中

微调&#xff1a;步骤 1 选择预训练模型 如ResNet VGG 2 准备新的数据集 3 构建模型 4 冻结部分模型 5 定义损失函数和优化器 6 微调模型 7 评估模型 8 微调的策略 https://www.zhangzhenhu.com/aigc/dreamBooth.html dreambooth (fix the object, then generate the same obje…

Android Media Framework(十五)ACodec - Ⅲ

这一篇我们一起来了解ACodec的Buffer分配流程。 1、initiateStart 首先对上一篇内容做一点补充&#xff0c;configureCodec执行完成后组件的状态没有变化&#xff0c;仍处在OMX_StateLoaded。因此&#xff0c;当我们调用initiateStart时&#xff0c;发出的消息将由ACodec::Loa…

SVN使用教程 - 快速上手

参考视频&#xff1a; SVN使用教程 - 快速上手 一、SVN简介 1、SVN的功能 &#xff08;1&#xff09;SVN是一种代码版本管理工具&#xff0c;它能记住程序员每次修改的内容&#xff0c;可以查看所有的历史修改记录&#xff0c;可以将代码恢复到任何历史版本&#xff0c;可以恢…

【Mysql】第十一章 事务-重点(原子性+持久性+隔离性+一致性)

文章目录 一、概念1.查看事务支持版本-show engines2.事务提交方式-show variables like autocommit3.事务常见操作方式1.将mysql的默认隔离级别设置成读未提交&#xff0c;方便看到实验现象2.需要重启终端&#xff0c;进行查看隔离级别3.创建一个银行用户表4.演示 - 证明事务的…

【Transformer】关于RNN以及transformer的相关介绍

文章目录 RNNTransformer是干什么的&#xff1f;什么是 Word Embedding &#xff1f;什么是 Word2vec &#xff1f;CBOW(Continuous Bag-of-Words Model)Skip-gram(Continuous Skip-gram Model)Word2vec 的优缺点 Transformer整体架构注意力机制self-attention&#xff08;自注…

生成式人工智能助力6G核心技术

崔曙光 加拿大皇家科学院 加拿大工程院双院院士 主要工作&#xff1a;适配改造人工智能算法&#xff0c;来满足通信网络性能 从基础LLM到专用LLM&#xff1a;四个必须面对的问题 如何选择合适的基础LLM规模如何让基础LLM读懂专用领域信息如何避免基础LLM的幻觉现象&#xf…

第9天 xxl-job

使用xxl-job需要建表 引入依赖 添加配置 Bean public XxlJobSpringExecutor xxlJobExecutor() {logger.info(">>>>>>>>>>> xxl-job config init.");XxlJobSpringExecutor xxlJobSpringExecutor new XxlJobSpringExecutor();xxlJo…

sql注入——sqlilabs16-26

文章目录 less-163.注入 less-172.数据库名2.1 floor报错注入数据库名 3.查到数据表3.1floor 报错注入数据表 4.查取列名4.1 floor报错注入 列名 5.查取内容 less-181.添加X-Forwarded-For测试2修改User-Agent测试3.查数据表名4.查数据列5.查取数据 less-192.查数据库3.查数据表…