一,概述
1 ANSI C文件系统建立在C语言早期版本的缓冲文件系统(也成为格式化或高级文件系统)之上。
2 流与文件的差别:C的I/O系统提供的程序员和使用设备之间的一级抽象叫做流,物理设备叫做文件。C文件系统可在终端、磁盘驱动器和磁带驱动器的众多设备上工作,不管各种设备有多大差异,ANSI 文件系统都把他们转换成称之为“流”的逻辑设备,具有极大的设备无关性。在C语言中,文件这个逻辑概念是用于从磁盘文件到终端打印机的任何东西,流通过完成打开操作与某文件联系起来。文件一旦打开,里面的信息就可以在程序与该文件之间交换。并非所有的文件都有此功能,例如,磁盘文件支持随机访问,但终端却不行,这说明了C语言中I/O系统的一个重要特性:所有的流都是相同的,但文件却不同。
3 有两种类型的流,文本流和二进制流:文本流是由字符组成的序列,在文本流中,特定字符的转换是由主机环境要求的,因而,写(或读)的字符与存储在外设中的字符间并无一一对应的关系。同样,由于可能的转换,写(或读)字符的数量可能与外设存储的字符不一致。二进制流是指字节序列,它在外设中的存储是一对一的,也就是说,不存在字符转换。因此,写(或读)字节的数量与外设中存储的字节一致。
4 文件通过关闭操作与说明流挂钩。为输出而打开的流,在关闭文件时与它相关的流写到外设中,这一过程通常叫做清仓,它保证了在磁盘缓冲区中不留下任何信息。当程序结束时,所有的文件自动关闭,如果程序因故障而崩溃,则文件不关闭,也就是信息可能没有真正的写到磁盘中去。与文件相关的流都有一个类型为FILE的控制结构。该结构在stdio.h中定义,不能对它进行热河修改操作。
5 在stdio.h中定义了如下函数和类型:
fopen() fclose() putc() fputc() getc() fgetc() fseek() fprintf() fscanf() feof()—若到文件尾返回真值 ferror() rewind() remove()—清除一个文件 fflush()—清仓一个文件,freadopen()—可以重定向标准流,这个函数用于将某一现存流与一新文件联系起来。类型:size_t(类型非常大,足以容纳两个指针相减的结果,它是unsigned int的变体),fpos_t(用于在文件中描述特定位置,也是unsigned int的变体),FILE类型,另外还定义了几个宏指令:EOF(通常被定义为-1),SEEK_SET,SEEK_CUR,SEEK_END。
6 当以读/写方式操作文件时,必须注意两点:一、若写操作后在读,必须调用fflush()函数或文件定位函数,如fseek()或rewind()。第二,若读操作后写,必须是在文件末尾或在两个操作之间调用文件定位函数。
二,函数详解
1,打开关闭文件
(1)打开文件:ANSI C库中打开文件函数fopen声明如下:
FIEF *fopen(_const char *_restrict_filename,_const char *_restrict_modes);
如果执行成功,将返回打开文件的文件指针。如果执行失败,将返回NULL。
函数的第一个参数是指向欲打开的文件名称字符串的指针(例如”/etc/service”)第二个参数为打开模式。打开文件模式如下:
参数 说明
r(或rb) 以只读方式打开,该文件必须存在
r+(或rb+) 以可读写方式打开,此文件必须存在
w(或wb) 以只写方式打开。该文件存在则清空,若不存在就创建
w+(或wb+) 以可读写的方式打开,。该文件存在则清空,若不存在就创建
a(或ab) 以只写方式追加文件,若文件存在,写入的数据会追加到文件后面,若文件不存在,就创建
a+(或ab+) 以可读写方式追加文件,若该文件存在,在它的尾部读写数据,若文件不存在,就创建。
(2)关闭文件:int fclose(FILE *_stream);
如果需要关闭打开的所有流对象,使用fcloseall函数。int fcloseall(void);
更新缓冲区内容:即使缓冲区没有填满,也可以刷新缓冲区内容,即使用I/O系统调用将缓冲区内容写回到磁盘中,使用的函数是fflush 其声明如下:
int fflush(FILE *_stream);
2,读写文件流
(1):字符读写文件流
(a) 字符读操作:字符读操作是指每次标准I/O调用只读出流的一个字符。相关函数声明: int fgetc(FILE*_stream) //从流读一个字符
从标准输入设备,读一个字符到标准输出
作用和fgetc一样
(b) 字符写操作:字符写操作是指每次标准I/O调用只写一个字符到流中。相关函数如声明:fputc函数把一个字符写入到一个输出文件流中。它返回写入的值。
#include<stdio.h>
int fputc(int c,FILE *stream)
int putc(int c,FILE *stream)
int putchar(int c)
函数的作用也相当于fputs。putchar函数相当于putc(c,stdout),它把单个字符写到标准输出
(2):行读写文件流
(a) 行读出操作:行读出操作是指每次标准I/O调用只从标准流中读出一行字符。fgets函数从输入文件流stream里读取一个字符串,它把读到的字符串写到s 指向的字符串里,直到遇到换行符或文件结束标志为止,在字符串后面要加上一个表示结尾的空字节/0。
#include<stdio.h>
char *fgets(char *s ,int n,FILE *stream);
char *gets(char *s);
gets函数类似于fgets,只不过它从标准输入读取数据并丢弃遇到的换行符。它在接收字符串的尾部加上一个null字节
(b) 行写入操作:行写入操作是指每次标准I/O调用只写一行字符到标准流中。puts()将s指向的以空字符结尾的字符串(后接换行符)写入标准输出流stdout。fputs将s指向的以空字符结尾的字符串写入指定输出stream,但不追加换行符。
#include<stdio.h>
char fputs(char *s,FILE *stream);
char puts(char *s)
(3),块读写文件流
(a)块读出操作
int fread(void *buffer,int size,int count,FILE *fp);
fread()──从fp所指向文件的当前位置开始,一次读入size个字节,重复count次,并将读入的数据存放到从buffer开始的内存中; buffer是存放读入数据的起始地址(即存放何处)。
(b)快写入操作
int fwrite(void *buffer,int size,int count,FILE *fp);
fwrite()──从buffer开始,一次输出size个字节,重复count次, 并将输出的数据存放到fp所指向的文件中。buffer是要输出数据在 内存中的起始地址(即从何处开始输出)。
一般用于二进制文件的处理。
(c)举例
将一个字符串写入文件:
char *str="hello,I am a test program!";
fwrite(str,sizeof(char),strlen(str),fp)
将一个字符数组写入文件:
char str[]={'a','b','c','d','e'};
fwrite(str,sizeof(char),sizeof(str),fp)
将一个整型数组写入文件:
int a[]={12,33,23,24,12};
先计算数组元素个数nmemb,之后
fwrite(a,sizeof(int),nmemb,fp)
注:由于程序生成的文件是二进制文件而非文本文件,因此,不用机器,整数的表达不同,
所以无法直接打开生成文件。可通过fread函数检验数据是否写入文件。
3 文件流定位
(1)返回当前读写位置
ftell函数
long int ftell (FILE * stream)
如果执行成功,返回但前指针位置距离文件开始的字节数,如果失败,返回-1。
(2)修改当前读写位置
fseek用法
int fseek(FILE *stream, long offset, int fromwhere);
第一个参数file指针
第二个参数移动的偏移量
第三个参数移动到哪里
分别用3个宏
SEEK_SET 既0 文件开头
SEEK_CUR 既1 文件当前位置
SEEK_END 既2 文件结尾
但不推荐用数字 最好用宏
简言之:
fseek(fp,100L,SEEK_SET);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,SEEK_CUR);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100L,SEEK_END);把fp指针退回到离文件结尾100字节处。
此函数常用来计算流的长度:
int filesize = fseek( fp, 0, SEEK_END );
fseek( fp, 0, SEEK_SET );
(3)重置当前读写位置
当执行完一次操作,为了实现第二次操作,需要调用rewind函数将读写位置重置到文件开头
void rewind (FILE *stream)
4 文件流检测
要标识一个错误,许多的stdio库函数会返回一个越界的值,例如空指针或者是定值EOF.在这些情况下,这些错误是由外部的变量errno来标识的:
#include <errno.h>
extern int errno;
在这里我们要注意的,许多的函数会改变errno的值.只有当一个函数的调用失败时,他的值才是可用的.我们应在一个函数标识失败后立刻检测errno的值.我们应在使用他之前要将他的值拷贝到另一个变量中,因为一些打印函数,如fprintf也许会修改他的值.
我们也可以通过检测文件流的状态来决定是否发生了错误,或者是已经达到文件结尾.
判读文件是否端到文件尾
int feof(FILE * stream)
如果读到文件尾,返回1,否则返回0;
ferror判断给定的流是否出现错误
int ferror (FILE *stream)
如果没有出现错误,返回0,否则返回错误,保存在error中,
在使用以上两个函数进行文件流检测时,将设置错误标志,执行错误处理后,应该清除该错误标识位
void cleareer(FILE *stream)
clearerr函数会清除stream指针所指的文件流的文件结束或是错误标识符.这个函数并没有返回值也没有定义的错误.我们可以使用这个函数来在流上由错误条件进行恢复.这个函数应的一个例子也许就是当发生磁盘满时会将数据重新写入文件流中。
5 流与文件描述符
每一个文件流都是与底层的文件描述符相对应的.我们可以混合使用底层的输入和输出与高层的文件流操作,但是通常而言这是不明智的,因为缓冲区的影响是不可预知的.
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);
我们可以通过调用fileno函数来得知一个文件流正在使用哪一个底层的文件描述符.他会为指定的文件流返回一个文件描述符,如果失败则会返回-1.如果我们需要底层的访问一个打开的流,我们可以使用这个函数,如使用fstat.
我们可以通过调用fdopen函数来在一个已经打开的文件描述符的基础上创建一个新的文件流.实质上,这个函数会为一个已经打开的文件描述符提供一个stdio的缓冲区,这也许会是一个用来进行解释的一个较为简单的方式.
fdopen 函数与fopen的操作方式相类似,所不同的只是他所使用的为一个底层的文件描述符.如果我们需要使用open来创建一个文件,也许是为了更好的权限控 制,但是却希望使用文件流进行写操作时,这个函数就会显得尤为有用.mode参数与fopen函数的参数相同,而且必须与这个文件最初打开时所建立的文件 访问方式相兼容.fdopen会返回一个新的文件流,如果失败则会返回NULL.
6 文件与目录维护
标准库与系统调用对于文件的创建与维护提供了完全的控制.
chmod
我们可以使用chmod系统调用改变一个文件或是目录的权限.这构成了Shell编程的基本内
其语法如下:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
由path所指定的文件将会具有由mode所指定的权限.在这里所指定的mode与open系统调用中的相同,是一个所需权限的位或.除非是这个程序被指定了合适的权限,否则只有这个文件的所有者或是超级用户才可以改变他的权限.
chown
超级用户可以使用chown系统调用来改变一个文件的所有者.
其语法如下:
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
这个调用使用用户ID或是组ID的数值(可以由getuid和getgid调用得到)和一个常量来谁可以来改变文件的所有者.如果设置了合适的权限我们就可以改变一个文件的所用者和所属的组.
unlink,link,symlink
我们可以使用unlink来移除一个文件.
unlink可以为一个文件移除目录实体并减少他的连接数量.如果函数调用成功则返回0,失败则会返回-1.我们必须在所要执行命令的目录中有写和执行的权限,因为文件对于这个函数调用有他自己的目录实体.
其语法如如下:
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);
如 果连接数量达到0而没有进程打开文件,这个文件则会被删除.事实上,一个目录实体总是会被删除,但是这个文件的空间并不会被回收,直到关闭最后一个相关的 进程.rm程序使用这个调用.在通常情况下我们可以使用ln程序来为一个文件创建一个链接.我们可以使用link系统为一个文件有计划的创建链接.
link系统调用为一个已存在的文件path1创建一个新的链接.新的目录实体是由path2来指定的.我们可以用相类似的方式使用symlink来创建一个符号链接.在这里我们要注意的就是一个文件的符号链接不会像硬链接那样阻止一个文件的删除。