学习笔记之文件操作

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中内核使用文件描述符来访问文件,文件描述符是非负数。打开或者新建一个文件是,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定文件。

扫描二维码关注公众号,回复: 16332275 查看本文章

结构体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如果发生错误就返回负值
    1. 将NULL作为参数传入的话,就是将select设置为阻塞状态一定等到监视文件描述符集合中某个文件描述符发生变化为止
    2. 将时间设置为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有发生变化,都立刻返回继续执行,文件无变化返回0,有变化返回正值。
    3. 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_sectv_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为参数

注意事项

  1. 若文件中的第一个数据与给定的格式的第一个数据格式不正确,就不会接着读取数据了。
  2. 全部读完,在读取字符串时会把连续的字符串不论数组大小全部读到指定数组。

功能说明格式化输入函数把文件指针指定的文件中的数据以提供的格式输入到指定地址

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.

注:输入流处理会在出现任何错误时停止,ferrorfeof随后用于不同的错误条件之间进行区分。

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可能被实现给一个宏。

猜你喜欢

转载自blog.csdn.net/qq_41323475/article/details/127893660