一. 文件与流
1.1 文件
⛱什么是文件?
文件是当今计算机系统不可或缺的部分。文件用于储存程序、文档、数据、书信、表格、图形、照片、视频和许多其他种类的信息。作为程序员, 必须会编写创建文件和从文件读写数据的程序。
文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。然而,对操作系统而言,文件更复杂一些。例如,大型文件会被分开储存,或者包含一些额外的数据,方便操作系统确定文件的种类。然而,这都是操作系统所关心的,程序员关心的是C程序如何处理文件(除非你正在编写操作系统)。
程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。
文件名:一个文件要有一个唯一的文件标识,以便用户识别和引用。 文件名包含3部分:文件路径+文件名主干+文件后缀例如: c:\code\test.txt为了方便起见,文件标识常被称为文件名。
⛱文本文件与二进制文件
在C语言中,<stdio.h>支持两种类型的文件∶文本文件和二进制文件。首先,要区分文本内容和二进制内容、文本文件格式和二进制文件格式,以及文件的文本模式和二进制模式。所有文件的内容都以二进制形式(0或1)储存。但是,如果文件最初使用二进制编码的字符(例如, ASCII或Unicode)表示文本(就像C字符串那样),该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代表机器语言代码或数值数据(使用相同的内部表示,假设,用于long或 double类型的值)或图片或音乐编码,该文件就是二进制文件,其中包含二进制内容。
为了规范文本文件的处理,C 提供两种访问文件的途径:二进制模式和文本模式。 在二进制模式中,程序可以访问文件的每个字节。而在文本模式中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时, 把本地环境表示的行末尾或文件结尾映射为C模式。例如,C程序在旧式 Macintosh中以文本模式读取文件时,把文件中的\r转换成\n;以文本模式写 入文件时,把\n转换成\r。或者,C文本模式程序在MS-DOS平台读取文件 时,把\r\n转换成\n;写入文件时,把\n转换成\r\n。在其他环境中编写的文本 模式程序也会做类似的转换。
除了以文本模式读写文本文件,还能以二进制模式读写文本文件。如果读写一个旧式MS-DOS文本文件,程序会看到文件中的\r 和\n 字符,不会发生映射。如果要编写旧式 Mac格式、MS-DOS格式或UNIX/Linux格式的文件模式程序,应该使用二进制模式,这样程序才能 确定实际的文件内容并执行相应的动作。
文本文件具有两种二进制文件没有的特性。
- 文本文件分为若干行。文本文件的每一行通常以一两个特殊字符结尾,特殊字符的 选择与操作系统有关。在Windows中,行末的标记是回车符(’\x0d’)与一个紧跟其后的回行符(’\x0a’)。在UNIX和Macintosh操作系统(Mac OS)的较新版本中,行末的标记是一个单独的回行符。旧版本的Mac OS使用一个单独的回车符。
- 文本文件可以包含一个特殊的"文件末尾"标记。一些操作系统允许在文本文件的末尾 使用一个特殊的字节作为标记。在Windows中,标记为’\xla’(Ctrl+Z)。Ctrl+Z不是必需的,但如果存在,它就标志着文件的结束,其后的所有字节都会被忽略。使用Ctrl+Z 的这一习惯继承自DOS,而DOS中的这一习惯又是从CP/M(早期用于个人电脑的一种操作系统)来的。大多数其他操作系统(包括UNIX)没有专门的文件末尾字符。
二进制文件不分行,也没有行末标记和文件末尾标记,所有字节都是平等对待的。
向文件写入数据时,我们需要考虑是按文本格式存储还是按二进制格式进行存储。为了搞清楚其中的差别,考虑在文件中存储数32 767的情况。一种选择是以文本的形式把该数按字符3、2、7、6、7写入。假设字符集为ASCII,那么就可以得到下列5个字节∶
另一种选择是以二进制的形式存储此数(short类型),这种方法只会占用两个字节∶
编写用来读写文件的程序时,需要考虑该文件是文本文件还是二进制文件。在屏幕上显示文件内容的程序可能要把文件视为文本文件。但是,文件复制程序就不能认为要复制的文件为文本文件。如果那样做,就不能完全复制含有文件末尾字符的二进制文件了。在无法确定文件是文本形式还是二进制形式时,安全的做法是把文件假定为二进制文件。
1.2 C语言中的流
⛱什么是流?
在C语言中,术语流(stream)表示任意输入的源或任意输出的目的地。许多小型程序都是通过一个流(通常和键盘相关)获得全部的输入,并且通过另一个流(通常和屏幕相关)写出全部的输出。 较大规模的程序可能会需要额外的流。这些流常常表示存储在不同介质(如硬盘驱动器、CD、DVD和闪存)上的文件,但也很容易和不存储文件的设备(网络端口、打印机等)相关联。这里将集中讨论文件,因为它们常见且容易理解。但是,请千万记住一点,<stdio.h>中的许多函数可以处理各种形式的流,而不仅仅可以处理表示文件的流。
程序的处理结果或计算结果会随着程序运行结束而消失。因此要将程序运行结束后仍需保存的数值和字符串等数据保存在文件(file)中。 针对文件、键盘、显示器、打印机等外部设备的数据读写操作都是通过流(stream进行的。我们可以将流想象成流淌着字符的河。) 由此可见,在前面的学习中所有用到 printf函数或 scanf函数的程序都使用了流。
⛱标准流
我们之所以能够如此简单方便地执行使用了流的输入输出操作,是因为C语言程序在启动时已经将标准(standard stream)准备好了。 标准流有以下三种。
■ stdin —— 标准输入流(standard input stream)
用于读取普通输入的流。在大多数环境中为从键盘输入。scanf与getchar等函数会从这个流中读取字符。
■stdout —— 标准输出流(standard output stream)
用于写入普通输出的流。在大多数环境中为输出至显示器界面。printf、puts 与putchar 等函数会向这个流写入字符。
■stderr —— 标准错误流(standard error stream)
用于写出错误的流。在大多数环境中为输出至显示器界面。
⛱文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统 声明的,取名FILE.
表示标准流的 stdin、stdout、stderr都是指向 FILE型的指针型。FILE 型是在<stdio.h>头文件中定义的,该数据类型用于记录控制流所需要的信息,其中包含以下数据。
文件位置指示符(file position indicator),记录当前访问地址。
错误指示符(error indicator),记录是否发生了读取错误或写入错误。
文件结束指示符(end-of-file indicator)记录是否已到达文件末尾。
通过流进行的输入输出都是根据上述信息执行操作的。而且这些信息也会随着操作结果更新。FILE 型的具体实现方法因编译器而异,一般多以结构体的形式实现。
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。 下面我们可以创建一个FILE的指针变量:
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联 的文件。比如:
二. 文件打开与关闭及其读写操作
2.1 文件的打开与关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
⛱文件操作模式
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
打开文件时可以指定以下四种模式:
- 只读模式 —— 只从文件输入。
- 只写模式 —— 只向文件输出。
- 更新模式 —— 既从文件输入,也向文件输出。
- 追加模式 —— 从文件末尾处开始向文件输出。
⛱打开文件
文件的打开是使用fopen
函数,打开后务必记得关闭!
FILE *fopen( const char *filename, const char *mode );
名称 | 详情 |
---|---|
头文件 | stdio.h |
参数 const char *filename |
文件名(或文件路径) |
参数 const char *mode |
文件模式(以字符串形式输入) |
返回值 | FILE *型的指针(返回NULL表示打开失败) |
作用 | 打开文件(文本或二进制文件) |
⛱关闭文件
int fclose( FILE *stream );
名称 | 详情 |
---|---|
头文件 | stdio.h |
参数 FILE *stream |
需要关闭的文件指针 |
返回值 | 整型(表示关闭流或文件指针总数,返回EOF表示文件关闭失败) |
作用 | 关闭文件(文本或二进制文件) |
示例
#include <stdio.h>
int main()
{
//打开文件,以w+模式为例,若没指定路径,默认跟随该.c文件
FILE* pf = fopen("abc.txt", "w+");//read and write
if (pf == NULL)
{
perror("file open");
return -1;
}
//文件操作(读文件,写文件)
//代码
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
2.2 文件的读写
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
getchar
只针对标准输入流stdin。即使对stdin重定向,getchar针对的也只是stdin。
■ stdin —— 标准输入流(standard input stream)
用于读取普通输入的流。在大多数环境中为从键盘输入。scanf与getchar等函数会从这个流中读取字符。
■stdout —— 标准输出流(standard output stream)
用于写入普通输出的流。在大多数环境中为输出至显示器界面。printf、puts 与putchar 等函数会向这个流写入字符。
■stderr —— 标准错误流(standard error stream)
用于写出错误的流。在大多数环境中为输出至显示器界面。
⛱文本文件字符输入输出(fgetc、fputc)
文本文件字符输入函数,用法与函数getchar
基本一样。
//Read a character from a stream (fgetc, fgetwc) or stdin (_fgetchar, _fgetwchar).
int fgetc( FILE *stream );
名称 | 详情 |
---|---|
fgetc | 读取一个数据,读一次后保存下一个数据的位置,下次读取从保存的位置开始。 |
头文件 | stdio.h |
参数 FILE *stream |
输入流(从流中读取数据输入,传文件指针读取文件,传stdin读取键盘等) |
返回值 | 整型(表示输入字符ASCII码,返回EOF表示输入失败) |
文本文件字符输出函数,用法与函数putchar
基本一样。