💎流和FILE对象
之前的IO函数都是围绕文件描述符的,并且是系统调用。标准IO库中封装了IO函数,这些函数是围绕流进行的。当用标准IO库打开一个文件或者创建一个文件时,我们已经使一个流和文件关联。
流的定向
对于ASCII字符集,一个字符一个字节,对于国际字符集,一个字符多个字节。
标准IO文件流可以用于单字节或者多字节的字符集,流的定向决定读写时是用单字节还是多字节的,在流被创建时,没有定向。当使用多字节IO函数,流被定向为宽定向的,反之则是字节定向的。
而流的定向可以被函数设置
#include<stdio.h>
#include<wchar.h>
int fwide(FILE* fp,int mode);
//若流是宽定向,返回正值,字节定向返回负值,为定向返回0
- mode参数的正负表示fwide试图将流改变成相应的定向
-
预定义的流
进程一般预定义了三个流:
标准输入:引用的文件和与
STDIN_FILENO
一样- 标准输出:
STDOUT_FILENO
- 标准错误:
STDERR_FILENO
缓冲
标准IO库提供缓冲的目的是尽可能减少使用read和write调用的次数。此外也会对每个流自动的进行缓冲管理。
:::info
why需要尽可能减少read和write的调用次数?
read和write函数实际上是在执行系统调用,系统调用通过中断进入内核态,这期间会发生用户栈和内核栈的切换,耗费较多时间。
:::
标准IO提供了以下3种类型的缓冲:
- 全缓冲:在填满标准IO缓冲区后才进行实际的IO操作
- 行缓冲:在输入和输出中遇到换行符的时候执行IO操作
- 不带缓冲,例如对于字符
当标准输入输出连接到终端是,他们是行缓冲的,行缓冲长度是1024,(输入输出的长度并不限制再1024),如果超出行缓冲的长度,会多次调用系统调用write 当将流定向到普通文件时,是全缓冲的。
修改缓冲
对于任意一个给定的流,如果不喜欢系统默认的缓冲,可以修改缓冲类型
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
buf:缓冲区,大小为BUFSIZ,此后该流应该是全缓冲的。
setvbuf可以指定缓冲区及其长度,通过mode参数实现:
- _IOFBF:全缓冲
- _IOLBF:行缓冲
-
打开流
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
fopen函数打开路径名为pathname的一个指定文件
- freopen再指定流上打开一个文件,如果流已经被定向了,那么会清除该定向
- fdopen取一个已有的文件描述符,并让标准IO流和该文件描述符结合。
mode参数可以选择对该流的读写方式
关闭流:
int fclose(FILE* fp);
读写流
一旦打开了流可以从三种不同类型的非格式化IO重疾险你选择:
- 一次一个字符的IO
- 一次一行的IO
- 直接IO:每次IO操作读写某种数量的对象,每个对象具有指定长度
:::info
实际上这种还是从标准IO自带的缓冲中读写?
:::
输入函数
一次读一个字符
若成功返回下一个字符, 若到达文件末尾返回EOF#include <stdio.h>
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
从流中读取数据后,可以调用ungetc将字符再压会流中.每次一行IO
```cppinclude
int fgetc(FILE *stream);
char fgets(char s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
<a name="zhSEa"></a>
## 输出函数
```cpp
#include <stdio.h>
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
int puts(const char *s);
读取末尾和error
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
int fileno(FILE *stream);
二进制IO
如果进行二进制IO操作,我们更愿意一次读或者写一个完整的结构。
- 如果用getc和putc需要循环整个结构
- 如果用fputs或者fgets那么遇到null字节会终止
因此需要二进制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为想要操作的元素的个数
float data[10];
if(fwrite(&data[2],sizeof(float),4,fp)!=4)
err_sys("fwrite error!");
//--------------------------------------
struct{
int a;
long b;
char c;
}item;
fwrite(&item,sizeof(item),1,fp);
返回值是读写对象的个数,如果到了文件末尾返回值可能小于期望值,需要ferror和feof判断哪种情况。
格式化IO
格式化输出
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
格式化输入
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
#include <stdarg.h>
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);