C++ 文件访问与输入输出流详解
参考侵删:
c语言基础——对文件的输入和输出(详细版)
C++ 工程实践(7):iostream 的用途与局限
C语言中文网
小数在内存中是如何存储的,揭秘诺贝尔奖级别的设计(长篇神文)
前置
什么是文件
文件是程序设计中一个重要的概念,所谓“文件”一般指存储在外部介质上数据的集合。文件有不同的类型,在c程序设计中,主要用到两种文件程序文件,数据文件。下面介绍一些有关文件的基本概念:
-
程序文件
包括源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为exe)等。这种文件的内容是程序代码。
-
数据文件
数据文件的内容不是程序,而是供程序运行时读写的数据。其根据数据的组织形式可分为文本文件和二进制文件。
-
文件缓冲区
由于CPU 与 I/O 设备间速度不匹配。为了缓和 CPU 与 I/O 设备之间速度不匹配矛盾。文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。使用文件缓冲区可减少读取硬盘的次数。
ANSI C标准采用缓冲文件系统处理数据文件。系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区,在向文件输出数据时,它就作为输出缓冲区,数据“装满”缓冲区后一起送到磁盘。在从文件输入数据时,它就作为输入缓冲区,从磁盘读出一批数据,送到缓冲区,最后送到程序。
-
文本类型指针
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名为FILE。
例如有一种c编译环境提供的stdio.h头文件中有以下文件类型声明:
typedef struct
{
short level; //缓冲区“满”或“空”的程度
unsinged flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如缓冲区无内容不读取字符
short bsize; //缓冲区大小
unsigned char *buffer; //数据缓冲区的位置
unsiged char *curp; //文件位置标记指针当前的指向
unsigned istemp; //临时文件指示器
short token; //用于有效性检查
}FILE;通过文件指针变量能够找到与它关联的文件,指向文件的指针变量指向内存中的文件信息区的开头
那么在C语言中“打开文件与关闭文件”的操作,实际上就可以理解为使系统为文件开辟一个相应的文件信息区和文件缓冲区,方便对该文件进行各种操作。
字符与存储数据的转换
举个例子,假设我们以文本形式将浮点数 19.625 写入文件,则该文件会直接将 “19.625” 这个字符串存储起来。当我们双击打开此文件,也可以看到 19.625。值得一提的是,由非字符串数据(比如这里的浮点数 19.625)转换为对应字符串(转化为 “19.625”)的过程,C++ 标准库已经实现好了,不需要我们操心。
但如果以二进制形式将浮点数 19.625 写入文件,则该文件存储的不再是 “19.625” 这个字符串,而是 19.625 浮点数对应的二进制数据。以 float 类型的 19.625 来说,文件最终存储的数据如下所示:
0100 0001 1001 1101 0000 0000 0000 0000
至于如何得出 float 类型的 19.625 对应的二进制,感兴趣的读者可阅读小数在内存中是如何存储的,揭秘诺贝尔奖级别的设计(长篇神文)
知道了什么是文件和C语言对文件的底层操作之后,接下来我们来看看什么是流?
什么是流
根据上述所讲,做好文件数据传输的准备后,接下来数据输入和输出的过程也是数据传输的过程,数据像水一样从一个地方流动到另一个地方,因此,在 C++ 中将此过程称为 “流(stream)”。
因为C兼容C的特性,所有C中有像C一样的I/O(输入与输出)解决方案,如:
printf、scanf |
但是C++还有另外自己的使用新的I/O解决方案,如:
cin、cout //也叫标准I/O |
标准I/O可以理解为一个函数库,他的操作都是围绕着流来进行的。当用用标准I/O打开或创建一个文件时,就已经使一个流与文件相结合。
但是为什么C++需要花费功夫建立一个自己的I/O解决方案呢?我找到了下面的描述,可一窥真相:
C++ iostream 的主要作用是让初学者有一个方便的命令行输入输出试验环境,在真实的项目中很少用到 iostream,因此不必把精力花在深究 iostream 的格式化与 manipulator。iostream 的设计初衷是提供一个可扩展的类型安全的 IO 机制,但是后来莫名其妙地加入了 locale 和 facet 等累赘。其整个设计复杂不堪,多重+虚拟继承的结构也很巴洛克,性能方面几无亮点。iostream 在实际项目中的用处非常有限,为此投入过多学习精力实在不值。
但是这位仁兄的回答并不是绝对的,还有更多的解答。
我们都知道C++中最重要的就是类和对象,那么自然就需要一个流类来管理“流”这个过程。
什么是类? 举个例子:
猫有很多特性(品种、胖瘦、颜色、会爬树、猫科等),可以列举无数多个,也可以你自己判断。但猫这个词所代表的含义就是对满足这些特性的动物的抽象,所以这些动物可以叫类猫,而猫是这个类的名字,所以类就是一类有相同特性的事物的抽象。
什么是对象?
对象就是具体的白猫、花猫、你的猫等,在程序中,需要先告诉计算机这个猫类是什么样子的,然后再用猫类具象为你自己的猫所有的特性的值。
流类
在 C++ 的标准类库中,将用于进行数据输入输出的类统称为“流类”。cin 是流类 istream 的对象,cout 是流类 ostream 的对象。要使用流类,需要在程序中包含 iostream 头文件。
C++ 中常用的几个流类及其相互关系如图1所示:
图1中的箭头代表派生关系。例如,ios 是抽象的基类,它派生出 istream 和 ostream。 istream 和 ostream 又共同派生了 iostream 类。
各个流类的作用:
- istream 是用于输入的流类,cin 就是该类的对象。
- ostream 是用于输出的流类,cout 就是该类的对象。
- ifstream 是用于从文件读取数据的类。
- ofstream 是用于向文件写入数据的类。
- iostream 是既能用于输入,又能用于输出的类。
- fstream 是既能从文件读取数据,又能向文件写入数据的类。
有了流类,对应的就会有流对象。
标准流对象
C++中操作数据进行输入输出操作的对象,叫做流对象。
iostream 头文件中定义了四个标准流对象,它们是 cin、cout、cerr 和 clog。
-
cin
对应于标准输入流,用于从键盘读取数据,也可以被重定向为从文件中读取数据。
-
cout
对应于标准输出流,用于向屏幕输出数据,也可以被重定向为向文件写入数据。
-
cerr
对应于标准错误输出流,用于向屏幕输出出错信息,不能被重定向。
-
clog
对应于标准错误输出流,用于向屏幕输出出错信息,不能被重定向。
cerr 和 clog的区别在于:cerr 不使用缓冲区,直接向显示器输出信息;而输出到 clog 中的信息会先被存放到缓冲区,缓冲区满或者刷新时才输出到屏幕。
重定向:就是将输入的源或输出的目的地改变。例如,cout 本来是输出到屏幕上的,但是经过重定向,本该输出到屏幕上的东西就可以被输出到文件中。
cout 是 ostream 类的对象。在 Visual Studio 2010 安装文件夹中有vc\crt\src\cout.cpp
文件,该文件中 cout 的定义如下:
_PURE_APPDOMAIN_GLOBAL static filebuf fout(_cpp_stdout); |
简单地看,就是:
ostream cout(&fout); |
ostream 类的无参构造函数和复制构造函数都是私有的,因此在程序中一般无法定义 ostream 类的对象,唯一能用的 ostream 类的对象就是 cout。
cout 可以被重定向,而 cerr 不能。所谓重定向,就是将输入的源或输出的目的地改变。例如,cout 本来是输出到屏幕上的,但是经过重定向,本该输出到屏幕上的东西就可以被输出到文件中。
我们来看一个例子
例子
|
第 7 行的 freopen 是一个标准库函数,第二个参数 w 代表写模式,第三个参数代表标准输出。该语句的作用是将标准输出重定向为 test.txt 文件。
重定向之后,所有对 cout 的输出都不再出现在屏幕上,而是出现在 test.txt 文件中。
test.txt 文件会和本程序的可执行文件出现在同一个文件夹中。重定向仅对本程序有效,不影响其他程序。
-
运行本程序,输入
6 2↙
程序没有输出,但是打开 test.txt文件,可以看到文件中有
3 -
如果输入
4 0↙
则程序在屏幕上输出
error.
说明 cerr 不会被重定向。
cin 也是可以被重定向的。如果在程序中加入
freopen("input.dat", "r", stdin); |
第二个参数 r 代表读入方式,第三个参数 stdin 代表标准输入。执行此语句后,cin 就不再从键盘读入数据,而是从 input.dat 文件中读人数据,input.dat 文件中有什么,就相当于从键盘输入了什么。