C语言之文件操作
文件的操作正确流程为:打开文件 -> 读写文件 -> 关闭文件 。文件在进行读写操作之前要先打开,使用后要关闭。打开文件就是获取文件的有关信息,这些信息会保存到一个FILE类型的结构体变量中。关闭文件就是断开与文件之间的联系,释放结构体变量,禁止对文件进行操作。
打开文件使用<stdio.h>
文件中的**fopen()
**函数即可打开文件。open()
函数原型为:FILE *fopen(char *filename, char *mode);
,其中mode为打开方式,fopen()
的返回值为FILE类型的结构体变量的指针。
关闭文件使用**fclose()
**函数,释放资源。fclose()
函数原型为:int fclose(FILE *fp);
,文件正常关闭时返回值为0,如果错误的话返回值为非0。
用一个指针变量指向一个文件,这个指针称为文件指针。FILE是<stdio.h>
中定义的一个结构体,该结构体中含有文件名、文件状态和文件当前位置等信息。
不同编译器在stdio.h
文件中定义的FILE略有差异,标准C中定义为:
typedef struct _iobuf {
int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
// 其他成员
} FILE;
获取文件大小,**ftell()
**函数用来获取文件内部指针距离文件开头的字节数。ftell()
函数原型为:long int ftell ( FILE * fp );
,同时文件需要以二进制打开。
dup()
/dup2()
在linux中,通过open()
打开文件以后,会返回一个文件描述符,文件描述符会指向一个文件表(包含文件状态、文件偏移指针、V节点指针),文件表中的节点指针会指向节点表(包含V节点信息、i节点信息、文件长度)。
dup()
和dup2()
两个函数都可以用来复制打开的文件描述符,复制成功后和复制源共享同一个文件表。dup()
和dup2()
用来复制一个文件描述符,通常用来重定向进程的标准输入输出等。
dup(int fd)
:dup返回的新文件描述符一定是当前可以用描述符中最小值。
dup2(int fd, int fd2)
:和dup()
函数一样,只是返回的文件描述符可以通过第二个参数指定,如果fd2是打开的状态,则会被关闭(断开fd2和现有的文件表项之间的映射关系),如果fd和fd2一样,则不会被关闭。关闭的意思是fd2(可用的文件描述符)和之前文件表项之间的映射关系,然后再让fd2和fd指向同一个文件表项。
文件描述符(fd): 文件表
+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 标识 | 文件指针 | | 文件状态标志 |
+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 当前文件偏移量 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| refcnt = 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| V节点指针 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
v节点表
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| V节点信息 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| i节点信息 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
select()
在linux中内核使用文件描述符来访问文件,文件描述符是非负数。打开或者新建一个文件是,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定文件。
结构体fd_set
是一个集合,这个集合中存放了文件描述符,用一位来表示一个文件描述符。这个集合fd_set结构体可以通过操作来控制。
FD_ZERO(fd_set *)
:用来清空fd_set集合,即让fd_set集合不在包含任何文件句柄,在文件描述符集合进行设置前,必须对其进行初始化。
FD_SET(int, fd_set *)
:用来将一个指定的文件描述符加入集合中。
FD_CLR(int, fd_set *)
:用来将一个给定的文件描述符从集合中删除。
FD_ISSET(int, fd_set *)
:用来检测fd在fd_set集合中的状态是否发生变化,当检测到fd状态发生变化的时候返回真,否则返回假(也可以认为是集合中的指定的文件描述符是否可以读写)。
select()
函数原型:int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
。
select()
函数用来监视需要监视的文件描述符的状态变化,并通过返回值告知。当一组套接字或者一个套接字有信号时,就通知。
maxfdp
:集合中所有文件描述符的范围,为所有文件描述符的最大值加1。readfds
:要进行检测的读文件集。writefds
:要进行检测的写文件集。errorfds
:要进行检测的异常数据。timeout
:select的超时时间,可以使select处于三种状态。如果监视的文件有变化就会返回一个大于0的值,如果timeout就返回0,如果发生错误就返回负值。- 将NULL作为参数传入的话,就是将select设置为阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止。
- 将时间设置为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有发生变化,都立刻返回继续执行,文件无变化返回0,有变化返回正值。
- timeout的值大于0,就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回,否则在超时时间后不管怎样一定返回。
返回值:错误返回-1,超时返回0,监视的文件描述符由变化就返回发生变化的文件描述符数量。
工作原理:select()
函数传入要监听的文件描述符集合(可读,可写,异常)开始监听,select处于阻塞状态,当有事件发生或者设置的等待时间timeout到了就会返回,返回之前自动去除集合中无事件发生的文件描述符,返回时传出有事件发生的文件描述符集合。
监视范围:select()
函数要求通过第一个参数传递监视对象文件描述符的数量,因此需要得到注册在fd_set变量中的文件描述符数。每次新建文件描述符时,这个值都会加1,所以只需要将最大的文件描述符值加1传入就行,加1是因为文件描述符的值是从0开始的。
超时时间:timeval结构体定义,定义在include/uapi/linux/time.h
中:
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
select()
函数只有在监视文件描述符发生变化时才返回,未发生变化会进入阻塞状态。将timeval结构体作为参数传入,即使监视的文件描述符没有发生变化,只要超过的timeval结构体中的时间,也可以从函数返回。
调用select函数后,结构体中的timeval
的成员变量tv_sec
和tv_usec
的值替换为超时前剩余时间,所以如果需要每次超时都设置一样的时间,需要将timeval重新赋值。
fopen()
函数原型:FILE *fp = fopen(const char *filename, const char *mode)
参数说明:const char *filename
:表示要打开的文件名称,const char *mode
:打开文件的方式。
文本文件打开方式:r - 只读,w - 只写,a - 追加
二进制文件打开方式: rb - 只读,wb - 只写, ab - 追加
- r:只读方式打开,文件必须存在。
- w:只写方式打开。
- a:追加方式打开。
- r+:读写方式打开,文件必须存在。
- w+:写入/更新方式打开文件。
- a+:追加/更新方式打开文件。
- t:文本文件,不写默认是t。
- b:二进制文件。
open()
open()函数有两个函数原型,如下所示:
函数原型:int open(const char *pathname, int flags)
参数说明:pathname
是要打开的文件名,flags
是特殊常量,用于指定怎么打开文件,O_RDONLY
– 只读、O_WRONLY
– 只写、O_RDWR
– 读写,这三个必须且只能使用一个,O_CREAT
– 若文件不存在则创建,需要使用mode
来设置权限,O_APPEWND
– 追加写,如果文件已经有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
返回值:成功打开返回打开文件的文件描述符,是int
类型,失败返回-1。
函数原型:int open(const char *pathname, int flags, mode_t mode)
参数说明:前两个删除和上面的相同,第三个参数是设定该文件的权限,S_IRUSR
– 文件所有者有读权限、S_IWUSR
– 文件所有者有写权限、S_IRGRP
– 文件所有组有读权限、S_IWGRP
– 文件所有组有写权限、S_IROTH
– 文件所属other有读权限、S_IWOTH
– 文件所属other有写权限。
fopen()
和open()
的区别
open()
是linux的系统调用,返回的是文件句柄,文件句柄是文件在文件描述符表里面的索引,文件描述符是linux的概念。
fopen()
是c语言标准库函数,返回的是一个指向文件结构的指针,是文件流。
fopen()
可移植,open()
不可移植,一般用fopen()
来打开普通文件,用open()
来打开设备文件。
fopen()
在用户态下就有了缓存,在进行read和write的时候减少了用户态和内核态的切换,而open()
则每次都需要进行内核态和用户态的切换。如果顺序访问文件,fopen系列函数要比直接调用open系列函数快。如果随机访问文件,open要比fopen快。
open()
和fopen()
,前者属于低级IO,后者属于高级IO。前者返回一个文件描述符(用户程序区的)int
,后者返回一个文件指针FILE*
。前者无缓冲,后者有缓冲。前者与write、read等配合使用,后者与fread、fwrite等配合使用。后者是在前者的基础上扩充而来的,在大所述情况下,用后者。
fclose()
函数原型:int fclose(FILE *stream)
参数说明:stream
表示文件流。
功能说明:关闭给定的文件流,任何未写入的缓冲数据将刷新到操作系统。任何未读取的缓冲数据都将被丢弃。
返回值:成功0,失败非0.
fgets()
函数原型:char *fgets(char *string, int n, FILE *stream)
参数说明:string
为目标字符串数组的地址,n
为要读取的字符个数,stream
为文件指针,函数返回后文件指针会指向后n-1
个字符。
功能说明:文本输入函数,读取指针后面的n-1个字符到目标字符数组去(要放一个“\0”到字符数组中去)。
fgetc()
函数原型:int fgetc(FILE *steram)
功能说明:字符输入函数,返回文件指针指向的字符,需要用一个变量来接收,返回字符后文件指针会指向下一个字符。
fputs()
函数原型:int fputs(const char *string, FILE *stream)
参数说明:string
为输出的字符串,stream
为文件指针。
功能说明:字符输出函数,将字符串写到文件流中。
fputc()
函数原型:int fputc(int c, FILE *stream)
参数说明:c
为输出的字符,stream
为文件指针。
功能说明:将一个字符输出到一个文件流中或者标准输出流中。
fscanf()
函数原型:int fscanf(FILE *stream, const char *format[,argument]...)
参数说明:stream
为文件指针,format
为数据格式,argument
为参数
注意事项:
- 若文件中的第一个数据与给定的格式的第一个数据格式不正确,就不会接着读取数据了。
- 全部读完,在读取字符串时会把连续的字符串不论数组大小全部读到指定数组。
功能说明:格式化输入函数,把文件指针指定的文件中的数据以提供的格式输入到指定地址。
fprintf()
函数原型:int fprintf(FILE *steram, const char *format[,argument]...)
参数说明:stream
为文件指针,format
为数据格式,argument
为参数
功能说明:格式化输出函数,把数据转化为指定格式输出到文件中去。
fread()
函数原型:size_t fread(void *buffer, size_t size, size_t count, FILE *stream)
参数说明:buffer
为读取的数据的目标存储位置,size
为字节大小,count
为读取数据的个数。
功能说明:读取二进制函数。
fwrite()
函数原型:size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream)
参数说明:buffer
为要写入的数据的原始位置,size
为要写入的数据的单位大小,count
为要写入的数据的个数。
功能说明:将数据转化为二进制的形式输入到文件中。
fseek()
函数原型:int fseek(FILE *stream, long offset, int origin)
参数说明:stream
文件指针,offset
为偏移位置,origin
总览表
函数名 | 适用于 |
---|---|
fread、fwrite | 文件 |
fgetc、fputc、fgets、fputs、fscanf、fprintf | 所有输出/输入流 |
所有适用于所有输出/输入流的函数都可以使用stdout/stdin来输出和输入。
fflush
函数原型:int fflush(FILE *stream)
参数说明:stream
为流指针,可以理解为一个文件指针,fflush()
会将缓冲区中的内容写到stream所指的文件中去,若stream为NULL,则会将所有打开的文件进行数据更新。键盘称为标准输入文件(stdin),显示器称为标准输出文件(stdout)。可以使用fflush来清空缓冲区中的数据。
fflush(stdin):刷新缓冲区,将缓冲区中的数据清空并丢弃。
fflush(stdout):刷新缓冲区,将缓冲区内的数据输出到设备。
feof()
函数原型:int feof(FILE *stream)
参数说明:stream
表示文件流
功能说明:检查是否达到文件流的末尾。该函数仅报告最近I/O操作报告的流状态,但不检查关联的数据源。
返回值:如果已达到文件流的末尾,则返回非零值,否则为0;
ferror()
函数原型:int ferror(FILE *stream)
参数说明:stream
表示文件流。
功能说明:检查给定的流是否有错误。
返回值:如果文件流发生错误就返回非零值,否则返回0.
注:输入流处理会在出现任何错误时停止,ferror
和feof
随后用于不同的错误条件之间进行区分。
clearerr()
函数原型:void clearerr(FILE *stream)
参数说明:stream
表示文件流。
功能说明:重置EOF给定文件流的错误标志和指示符。
fgetpos()
函数原型:
int fgetpos(FILE *stream, fpos_t *pos);
int fgetpos(FILE *restrict stream, fpos_t * restrict pos);
参数说明:stream
表示文件流,pos
表示指向文件位置的指针。
功能说明:获取文件流的文件位置指示器和当前解析状态,stream并将他们存储在指向的对象中pos,存储的值只对输入有意义。
getc()
函数原型:int getc(FILE *stream)/int fgetc(FILE *stream)
功能说明:从给定的输入流中读取下一个字符,getc可能被实现给一个宏。