一、流
对于不同的I/O设备,通常具有不同的特性和操作协议,操作系统负责这些不同设备的通信细节,并向程序员提供一个更为简单和统一的I/O接口。
ANSI C进一步对I/O的概念进行了抽象。就C程序而言,所有的I/O操作只是简单地从程序移进或移出字节,这种字节流就被称为流(stream)。
流分为两种类型:文本(text)流和二进制(binary)流。
文本流与文本文件
文本流中的字节以ASCII码值的形式写入到文件或设备中,适用于文本数据。与文本流相关联的文件就是文本文件。
二进制流和二进制文件
二进制流中的字节将完全根据程序编写它们的形式写入到文件或设备中,而且完全根据它们从文件或设备读取的形式读入到程序中,适用于非文本数据和文本数据。
二、文件
文件就是保存在外存上的一种数据类型。把数据存到文件中可以实现数据的持久化。
文件分类
C程序设计中,讨论的文件主要分为程序文件和数据文件(按使用功能角度分类)
程序文件
包括源文件(后缀为.c)、目标文件(windows下为.obj)、可执行程序(windows下为.exe)。
数据文件
用于程序读取、写入数据的文件。
文件标识
文件标识由三个部分组成:文件路径+文件主干名+文件后缀
例如:c:\code\test.txt
文件名通常指文件主干名+文件后缀
三、文件的打开和关闭
文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
一般情况下,都是用指向FILE的指针来维护这个FILE结构的变量。比如下面创建的pf变量就是一个指向FILE结构的指针,我们把这种变量叫做文件指针变量。
FILE* PF;
通过文件指针变量,就能找到与之相关联的文件。
文件的打开和关闭
文件读写之前要打开文件,读写结束之后应关闭文件。
在C语言中,使用fopen函数来打开文件,使用fclose关闭文件。
fopen的函数原型如下
fopen第一个参数为用一个字符串,是所要打开文件的文件名。
需要注意的是:
- 如果该文件在源文件当前目录下,只要写文件主干名和文件后缀就可以了;
- 如果该文件不再源文件当前目录下,需要写文件的绝对路径。
fopen的第二个参数也是一个字符串,指定打开流的模式(只能在规定的把模式中选择)。决定打开的流适用于只读、只写还是既读又写,以及它是文本流还是二进制流。
下面列出常用模式
读取 | 写入 | 添加 | |
---|---|---|---|
文本 | “r” | “w” | “a” |
二进制 | “rb” | “wb” | “ab” |
需要注意的是
- 如果要打开的文件原先不存在,以读取的方式打开会打开失败,fopen返回NULL
- 如果要打开的文件原先不存在,以写入的方式打开,则会在源代码当前路径下新创建一个以第一个参数为文件名的文件。
- 如果要打开的文件原先不存在,以添加的方式打开,也会创建一个新的文件。
- 如果要打开的文件存在,以写入的方式打开,则源文件会被销毁,新创建一个同名文件。
- 如果要打开的文件存在,以添加的方式打开,则源文件会被销毁,新创建一个同名文件。
fclose的函数原型如下
fclose接收一个流作为参数,把与这个流关联的文件关闭。
注意
- fclose的参数不能是NULL,否则程序会崩溃,因此使用fopen后有必要检查返回值是否为null。
- 对于输出流,fclose在关闭文件之前会刷新缓冲区。
和动态内存分配的malloc和free函数配对使用类似,fopen和fclose也要配对使用。
下面给出程序中常见的文件使用方式
#include<stdio.h>
int main()
{
FILE* pf;
//打开文件
pf = fopen("test.txt","r");//以只读的方式打开一个文本流
if(NULL==pf)//检查文件是否打开成功,若失败则结束程序
{
perror("fail");
exit(EXIT_FAILURE);
}
//读文件,这里略
//关闭文件
fclose(pf);
pf = NULL;//避免野指针
return 0;
}
四、文件读写
文件的读写通过ANSI C中规定的I/O函数实现,下面先介绍I/O函数
I/O函数
I/O函数以3中基本形式处理数据:单个字符、文本行和二进制数据。
对于每种形式,都有一组特定的函数对它们进行处理。
下面列出用于每种形式I/O形式的函数,及函数家族。
执行字符、文本行和二进制的I/O函数
数据类型 | 输入 | 输出 | 描述 |
---|---|---|---|
字符 | getchar | putchar | 读取(写入)单个字符 |
文本行 | gets、scanf | puts、printf | 文本行未格式化的输入(输出)、格式化的输入(输出) |
二进制数据 | fread | fwrite | 读取(写入)二进制数据 |
输入输出函数家族
家族名 | 目的 | 可用于所有的流 | 只用于stdin和stdout | 用于内存中的字符串 |
---|---|---|---|---|
getchar | 字符输入 | fgetc、getc | getchar | 无 |
putchar | 字符输出 | fputc、putc | putchar | 无 |
gets | 文本行输入 | fgets | gets | 无 |
puts | 文本行输出 | fputs | puts | 无 |
scanf | 格式化输入 | fscanf | scanf | sscanf |
printf | 格式化输出 | fprintf | printf | sprintf |
下面分别介绍各个函数
字符I/O
从流中读取单个字符由getchar函数家族实现;
向流中输出单个字符由putchar函数家族实现。
fgetc
参数是需要操作的流,函数从流的当前位置读取一个字符,返回值是该字符的ASCII码。如果流中不存在更多字符,则返回常量值EOF(定义在stdio.h的一个标准I/O常量,是一个整形常量,用于标记文件末尾)
这里需要注意的是,返回值之所以是int型而不是char型,是为了处理返回值是EOF的情况。
getc
这个函数和fgetc在使用上没有区别,区别在于getc是通过#define指令定义的宏,而fgetc是真正的函数。
getchar
getchar无需参数,因为它只从标准输入流读取字符,返回该字符的ASCII码值,在标准输入流(这里指键盘),可以通过连输三次ctrl+z来模拟EOF。
getchar也是通过#define指令定义的宏。
fputc
第一个int型参数是需要输出字符的ASCII码值,第二个参数是被写入的流。
putc
putc在使用上和fputc相同,不同之处在于putc是宏,而fputc是函数
putchar
putchar只用于标准输出流,只需要一个参数,表示需要输出的字符的ASCII码值。
putchar也是宏。
未格式化的行I/O
未格式化的行I/O由fgets、gets、fputs、puts实现
fgets
功能:fgets从指定的流stream读取字符并复制到string中。
要点:
- 在读取到第一个换行符(fgets保留换行符)或读取字符数到达n-1(因为fgets会在读取的字符串末尾添加一个‘\0‘)时停止读取。
- 下一次调用时fgets将从流的下一个字符开始读取。
- 如果在读取任何字符之前就到达了文件尾,fgets返回NULL;否则fgets返回它的第一个参数。这个返回值一般只用于检测是否到达了文件尾。
gets
gets只用于标准输入流。从标准输入流读取一个字符串直到\n并且把\n替换成\0,返回值就是参数值。
需要注意的是,由于gets无法知道读取的字符串中字符个数是否会超出buffer的容量,很容易造成越界,因此gets在正经的程序中很少使用。
fputs
fputs第一个参数是以\0结尾的字符串,并将这个字符串输出到指定的流stream中。
如果成功输出,则返回一个非0值,否则返回EOF。
puts
puts只用于标准输出流,参数是需要输出的字符串。
如果成功输出,则返回一个非0值,否则返回EOF。
需要注意的是,puts会在输出的字符串中添加一个换行符\n,以和gets配合使用。
格式化的行I/O
格式化行I/O由scanf、printf、fscanf、fprintf、sscanf、sprintf实现。
我们已经用过不少次printf和scanf,这里主要对比以下这三组函数
scanf家族
相同点
- 每个原型中的省略号表示一个可变长度的指针列表。从输入转换来的值逐个存储到这些指针所指向的内存位置。
- 都从输入源读取字符并根据format字符串给出的格式代码进行转换。
- 当格式化字符串到达末尾或者读取的输入不再匹配格式字符串指定的类型时输入停止。
- 如果在任何输入被转换之