APUE笔记—C语言标准IO库之文件IO

APUE笔记—C语言标准IO库之文件IO

注:本文参考APUE第五章

1.标准IO库与系统调用文件IO区别

  • 标准IO库:面向应用开发,为程序员提供一套可移植的API,并且标准IO库处理了很多细节,如缓冲区分配,以优化的方式执行IO库等。
  • 系统调用:面向底层的硬件,在Linux中,标准IO库的函数一把都要调用系统调用函数,系统调用时与操作系统相关的。

2.流和FILE对象

  • 标准IO库:是围绕流进行的,当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针,而FILE是一个结构体,包含着管理该流需要的所有信息,其中就包含文件描述符,指向缓冲区的指针,缓冲区的长度,出错标志等。
  • 系统调用:是围绕文件描述符,当打开一个文件,即返回一个文件描述符,然后该文件描述符就用于后续的操作。

3.标准输入、标准输出、标准错误

一个进程默认会定义3个流,并且这3个流会自动被进程使用,分别是:标准输入,标准输出,标准错误。
系统调用时的文件描述符分别对应:

#define STDIN_FILENO  0    //标准输入
#define STDOUT_FILENO 1    //标准输出
#define STDERR_FILENO 2    //标准错误

标准IO库中分别对应3个文件指针,struct _IO_FILE 就是FILE结构体


<stdio.h>    //标准输入输出文件

struct _IO_FILE *stdin;     //标准输入流
struct _IO_FILE *stdout;    //标准输出流
struct _IO_FILE *stderr;    //标准错误流

4.缓冲

4.1 缓冲的目的

标准IO库提供缓冲的,目的是尽可能减少使用read和write的调用次数。它也对每个IO流自动的进行缓冲管理,避免了应用程序需要考虑缓冲这一点所带来的麻烦。

4.2 缓冲的类型

  1. 全缓冲:在标准IO缓冲区填满时或者调用fflush函数才会执行实际IO操作。一般是malloc分配来的空间。
  2. 行缓冲:在输入和输出遇到换行符时标准IO库才会执行IO操作。
  3. 不带缓冲:标准IO库不对字符进行缓冲储存。例如标准错误stderr通常就是不带缓冲的,这使得错误信息立即显示出来。

4.3 缓冲的特征

  1. 当且仅当标准输入和标准输出并不指向交互式设备时,他们才是全缓冲。
  2. 标准错是不带缓冲的。
  3. 若指向终端设备的流,则是行缓冲,否则是全缓冲。

4.4 更改缓冲类型的函数

#include<stdio.h>
void setbuf(FILE *stream, char *buf);//打开或者关闭缓冲机制
//BUFSIZE的缓冲区(定义在<stdio.h>)
int setvbuf(FILE *stream, char *buf, int mode, size_t size);//设置缓冲的类型
//返回值:成功为0.出错非0
函数modebuf缓冲区及长度缓冲类型
setbuf非空长度为BUFSIZE的用户缓冲区buf全缓冲或行缓冲
NULL(无缓冲区)不带缓冲
setvbuf_IOFBF非空长度为size的用户缓冲区buf全缓冲
NULL合适长度的系统缓冲区buf全缓冲
setvbuf_IOLBF非空长度为size的用户缓冲区buf行缓冲
NULL合适长度的系统缓冲区buf行缓冲
setvbuf_IONBF(忽略)(无缓冲区)不带缓冲

任何时候,都可以强制冲洗一个流。

#include<stdio.h>
int fflush(FILE *stream);//使所有未写的数据都被传送至内核,若fp为NULL,则所有流被冲洗。
//返回值:成功为0,出错EOF

5. 打开流

下列三个函数打开一个标准的IO流

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
//打开路径名为path的一个指定文件
FILE *fdopen(int fd, const char *mode);
//取一个已有的文件描述符,使其与标准的IO流结合。常用于创建管道和网络通信通道函数返回的描述符。
FILE *freopen(const char *path, const char *mode, FILE *stream);
//在指定流上打开一个文件,若该流打开则先关闭,若流已定向,则清清除该定向。
//用于将一个指定文件打开为预定义的流:标准输入,标准输出,标准错误

//返回值:若成功,返回文件指针,若出错,返回NULL

type参数指定该IO流的读写方式。ISO C规定了15中不同的值

type说明
r或rb为读而打开
w或wb把文件截断至0长,或为写而创建
a或ab追加;为在文件尾写而打开,或为写而创建
r+或r+b或rb+为读和写而打开
w+或w+b或wb+把文件截断至0长,或为读和写而打开
a+或a+b或ab+在文件尾读和写而打开或创建

当读和写类型打开一个文件时,具有下列限制
- 如果中间没有fflush、fseek、fsetpos或rewind,则咋输出后面不能直接跟输入
- 如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有达到文件尾端,则在输入操作之后不能直接跟随输出

调用fclose关闭一个打开的流

#include <stdio.h>
int fclose(FILE *fp);//关闭文件之前,冲洗缓冲中的输出数据
//返回值:成功为0,出错EOF

6. 读和写流

一旦打开了流,则可在3种不同类型的非格式化IO中进行选择,对其进行读写操作。
1. 每次一个字符的IO。如果流时带缓冲的,标准IO函数处理所有缓冲
2. 每次一行的IO。使用fgets和fputs,每行以一个换行符终止。当调用fgets时,应说明能处理的最大长度
3. 直接IO。fread和fwrite函数支持这类型的IO,每次操作读写某些数量的对象,每个对象具有指定的长度。

6.1 输入函数

下面3个函数一次读一个字符。

#include <stdio.h>
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
//返回值:成功返回下一个字符,若已到达文件尾端或错误,返回EOF
//注意,不管出错还是到达文件尾端,这三个函数都返回同样的值,为了区分就必须使用ferror或feof。

int feof(FILE *stream); //检测流上的文件结束符
int ferror(FILE *stream);//检测流上的错误标志
void clearerr(FILE *stream);//可以清除上面两个函数检测到的标志:出错标志和文件结束标志  

函数getchar等同于getc(stdin)。
getc和fgetc的区别是:getc可被实现为宏,而fgetc不能实现为宏。意味着:
1. getc的参数不应当是具有副作用的表达式(表达式执行后改变表达式的值),因为它可能被计算很多次。
2. 因为fgetc一定是函数,所以可以得到地址。可将其地址作为函数的参数。
3. 调用fgetc所需时间可能长于getc,因为调用函数所需时间长于调用宏。

6.2 输出函数

对应上面所述的输入函数都有一各输出函数

#include <stdio.h>
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
//返回值:若成功,返回c,若出错,返回EOF

与输入函数一样,putchar(c)等同于putc(c, stdout),putc可被实现为宏,而fputc不能实现为宏。

7. 每次一行IO

下面给两个函数提供每次输入一行的功能

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
//返回值:若成功,返回buf,若达到文件尾端或出错,返回NULL

这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgetc从指定的流读。
- 对于fgets,必须指定缓冲的长度n。此函数一直读到下一个换行符为止,但不超过n-1个字符,读入的字符被送入缓冲区,该缓冲区以NULL结尾,如若改行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是缓冲区总是以NULL字节结尾。对fgetc的下一次调用会继续读改行。
- 对于gets是一个不推荐使用的函数。可能会造成缓冲区溢出,产生不可预料的后果。

fputs和puts提供一次输出一行的功能。

#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
//返回值:若成功,返回非负值,若出错,返回EOF
  • 函数fputs将一个以NULL字节终止的字符串写到指定的流,尾端的终止符NULL不写出。每次不一定输出换行符
  • 函数puts将一个以NULL字节终止的字符串写到标准输出,终止符不写出。但是,puts会将一个换行符写到标准输出。

8. 二进制IO

上述的getc和putc是一次处理一个字节,如果要操作一个结构必须通过循环整个结构,而fputs和fgets一次处理一行,遇到NULL会停止,而在结构中就可能会包含NULL字符。因此,通过二进制IO,就可以实现一次读或者写一个完整的结构。下面的两个函数可以执行二进制IO操作。

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
//size指定长度,nmemb为欲写的元素个数。
//返回值:读或写的对象数。
//对于读,如果出错或者到达文件尾端,返回数少于obj,这种情况应用ferror或feof判断是哪一种情况。
//对于写,如果返回数少于要求的nobj,则出错。

这些函数常见的有两种用法:
1. 读或写一个二进制数组
2. 读或写一个结构体。

9. 定位流

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);   
//返回值:若成功,返回0,若出错,返回-1
long ftell(FILE *stream);   
//返回值:若成功,返回当前文件位置指示,若出错,返回-1
void rewind(FILE *stream);//将一个流设置到文件的起始位置
  • 对于二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位。为了定位一个二进制文件,必须制定一个offset,以及解释这种偏移量的方式,whence的值有三种:SEEK_SET(文件起始位置)、SEEK_CUR(文件当前位置)、SEEK_END(文件尾端)。
  • 对于文本文件,它们的文件位置不能以简单的字节偏移量来度量。这主要在非Linux系统中,它们可能以不同的格式存放文本文件。

如果需要移植到非Linux系统上运行应用程序应当使用下面的函数。

int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
//返回值:若成功,返回0,若出错,返回非0

fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后的调用fsetpos时,可以使用此值将流重新定位至该位置。

10. 获取文件描述符

如果调用dup或fcntl等函数,则需要该函数。

#include<stdio.h>
int fileno(FILE *fp);
//返回值:与该流相关联的文件描述符
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值