5.1 引言
第三章中的I/O函数都是围绕文件描述符,而标准I/O库是针对流(stream)进行的,当使用标准I/Ok库打开或创建一个文件时,我们以使一个流与一个文件关联。
当打开一个流时,标准I/O函数fopen
返回一个指向FILE
对象的指针。该对象通常是一个结构体,包含了用于实际IO的文件描述符、指向用于流缓冲区的指针、缓冲区的长度、当前在缓冲区的字符数以及出错标志等。
为了引用一个流,需将FILE指针
作为参数传递给每个标准IO函数。
5.4 缓冲
标准IO库函数提供缓冲的目的是减少read和write的次数,且使得我们不需要考虑缓冲区的管理。
标准IO提供了3种类型的缓冲。
- 全缓冲。在填满标准IO缓冲区后才进行实际IO操作。在一个流上执行一次IO操作时,相关标准IO函数通常调用
malloc
获得所需要的缓冲区。 - 行缓冲。当在输入和输出中遇到换行符时,标准
I/O
库执行I/O
操作。 - 对于行缓冲有两个原则。第一,当一行填满了缓冲区,即使没有换行符也会进行I/O操作。第二,从一个不带缓冲的流得到输入数据时,就会先冲洗所有行缓冲输出流,为了保证缓冲区中只有这个数据,而不能有其他数据。
- 不带缓冲。标准IO库通常不对字符进行缓冲。例如使用fputs写字符时就会立即输出,而不需要等到填满缓冲区或者换行符。
系统默认标准错误
是不带缓冲的。而指向终端设备的流是行缓冲的,否则是全缓冲的。但是可以使用下列函数对默认缓冲进行更改:
#include <stdio.h>
void setbuf(FILE *resetrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
//成功返回0,失败返回非0
5.5 打开流
#include <stdio.h>
FILE* fopen(const char* restrict pathname, const char* restrict type);
FILE* freopen(const char *restrict pathname, const char* restrict type, FILE* restrict fp);
FILE* fdopen(int fd, const char* type);
//若成功返回文件指针,若失败返回NULL
这三个函数的区别是:
fopen
打开指定路径名为pathname
的文件。freopen
在一个指定的流上打开文件,若该流已经打开则先关闭该流。若该流已经重定向,则使用freopen
清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。fdopen
函数取一个已有的文件描述符,并使标准的IO
流与该描述符相结合。通常用于创建管道通信或网络通信,因为这些类型的文件不能用标准IO
函数fopen
打开,所以我们必须先获得文件描述符,然后再用fdopen
与该文件描述符结合。
type
参数指定对I/O
流的读写方式,如下图所示:
但上面各种参数都会有一些限制,比如r
只能读已经存在的文件,且流可以读。
更多限制如下:
系统默认情况下:
流被打开时是全缓冲的;
若流引用终端设备,则该流是行缓冲的;
调用fclose
关闭一个打开的流:
入参:
文件指针, open时获取
返回值:
成功返回0, 失败返回EOF
#include <stdio.h>
int fclose(FILE *fp);
在文件被关闭前,冲洗缓冲区的输出数据,输入数据被丢弃,并释放缓冲区。
5.6 读和写流
打开了流之后,就要进行读写操作。
(1)每次读写一个字符:fgetc
和fputc
(2)每次读写一行字符,以换行符终止fgets
和fputs
(3)直接IO,每次读写某种数量的对象
5.6.1 每次一个字符
5.6.1.1 读
功能:
一次读取一个字符
返回值:
成功:返回下一个字符
若到达文件末尾则返回EOF
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void); //没有指定文件指针,从键盘stdin读字符
返回int类型,而不是返回unsigned char
类型,主要是为了和EOF
进行区分。
不管是出错还是到达文件尾端,函数的返回值是一样的,所以必须区分这两种情况,调用ferror
和feof
.
#include <stdio.h>
//条件为真返回非0,否则返回0.
int ferror (FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp); //调用clearerr可以清楚这两个标志
5.6.1.2 写
#include <stdio.h>
//若成功返回c, 若失败返回EOF
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar
putchar(c)
等同于putc(c, stdout),
无法指定FILE对象指针
.
putc
可以实现为宏,但fputc
不能实现为宏.
5.6.2 每次一行IO
- 从流中读数据:
#include <stdio.h>
//buf指定缓冲区地址,读入的行将送入其中
char *fgets(char* restrict buf, int n, FILE *restrict fp);
char *gets(char* buf);
//若成功则返回buf, 若到达文件末端或出错则返回NULL.
对于fgets
必须指定缓冲区长度,一直读到下一个换行符为止,但是不能超过n-1,因为缓冲区也是以null结尾,
严重不推荐使用gets函数,因为他读取的一行(遇到换行符结束)很可能超过缓冲区的长度。
另一区别:
gets()
并不把换行符存入缓冲区.
- 向流写数据:
#include <stdio.h>
int fputs(const char* str, FILE* fp);
int puts(const char* str);
//成功返回非负值,失败返回EOF.
fputs
将一个字符串(字符串都是以null结束)写入流,但结束符null不会写入。所以他并不是按行写,而是按字符串的结束符来写。
puts
也是以字符串结束符写道标准输出,同样不写结束符,但随后会补充一个换行符
到标准输出.
5.9 二进制IO
当读写一个结构时,使用getc和putc效率太低,而使用fputs
遇到null就停止,而机构中可能存在null,所以无法实现写结构的要求。而fgets
遇到换行符也会停止,无法实现读一个完整结构。
#include <stdio.h>
/* 入参:
ptr:指针
size:对象大小
objnum:对象个数
fp:FILE对象指针 */
size_t fread(void *ptr, size_t size, size_t objnum, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t objnum, FILE *fp);
//两个函数的返回值:读或写的对象数
若出错或到达尾端返回值可能会<读或写的对象数,
应调用ferror或者feof以判断是哪一种情况.
实例:
//----------------写一个结构体----------------//
struct{
short num;
char name[MAX_NAME_SIZE];
long total;
}item;
if(fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwrite error");//这是作者写的库函数
//-------将浮点数数组的第2-5个元素写至一个文件--------//
float userCost[10];
if(fwrite(&userCost[2], sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
但二进制I/O读写对象可能会有问题:
- 一个结构体中成员变量偏移量在各系统不同
- 用于存储多字节整数和浮点值的二进制格式在不同系统结构间也不同。
5.10 定位流
1、ftell
和fseek
#include <stdio.h>
//查询当前文件位置指示
long ftell(FILE *fp); //成功则返回当前位置,出错则返回-1L
//设置文件位置
long fseek(FILE *fp, long offset, int whence);//若成功返回0,失败返回-1
void rewind(FILE *fp);
whence
参数:
SEEK_SET
,则将文件偏移量设置为文件起始位置+offset
SEEK_CUR
,则将文件偏移量设置为当前偏移+offset,offset可正可负
SEEK_END
,将文件偏移量设置为文件尾端+offset,offset可正可负
2、ftello
和fseeko
除了偏移量的类型是off_t
而非long
以外,ftello
函数和ftell
函数相同,fseeko
和fseek
相同。
#include <stdio.h>
//查询当前文件位置指示
off_t ftello(FILE *fp);//成功返回当前文件位置,失败返回(off_t)-1
//设置文件位置
int fseeko(FILE *fp, off_t offset, int whence);//若成功返回0,失败返回-1
off_t
是一种专门的偏移数据类型.
3、fgetpos
和fsetpos
int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos)
fgetpos
可以将文件位置指示存入pos,在以后调用fsetpos
时,可以使用此值将流重新定位至该位置。
5.11 格式化I/O
#include <stdio.h>
int printf(const char * format, ...);
int fprintf(FILE *fp, const char *format, ...);
int dprintf(int fd, const char *format, ...);
//3个函数若成功返回输出字符串,出错返回负值
int sprintf(char *buf, const char *format, ...);
//若成功返回存入的字符数,编码出错返回负值
int snprintf(char *buf, size_t n, const char *format, ...);
//若缓冲区足够大,返回要存入的字符数,若编码出错返回负值
- printf 将格式化数据写道格式化输出
- fprintf 写至指定的流(用
fopen
打开获得文件描述符) - dprintf 写至指定的文件描述符(用
open
打开获得文件描述符) - sprintf 将格式化的字符送入数组buf中,并在数组尾端加一个null,返回值不包括null
- snprintf 为了解决sprintf的缓冲区溢出问题,缓冲区长度是一个显示参数,超过的所有字符会被丢弃,sprintf返回值同样不包括结尾的null值.
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);
- sscanf以字符串作为输入源
- scanf以标准输入作为输入源