Standard I/O library
1 byte oriented or wide(multibyte) oriented
2 buffering
3 opening a stream
4 reading and writing a stream
5 binary I/O
7 memory stream
1 byte oriented or wide(multibyte) oriented
标准IO文件流可以是单字节或者是多字节字符集,流定向决定了字符串读写时是单字节还是多字节。当流建立的时候,没有定向。当一个多字节IO函数使用在流上面的时候,流被设置为宽(multibyte)定向;当一个字节IO函数使用在流上面的时候,流被设置为字节(single-byte)定向,这边所提到的流是尚未定义定向的流。
#include <wchar.h>
int fwide(FILE *stream, int mode);
int fwide(FILE *stream, int mode);
如果mode为负数,则尝试设置流为字节定向。
如果mode为正数,则尝试设置流为宽定向。
如果mode为0,则不尝试设置流定向,当时仍会返回流的定向。
fwide不能改变已经定向的流定向,而且返回值并不是和以往一样,0表示成功,-1表示失败。在调用fwide的时候可以清楚errno值,并在调用结束之后检查errno,借此来检查是否发生了错误。
2 buffering
标准库中提供了三种类型的缓冲:全缓冲,行缓冲,不缓冲。
以下函数可以用于更改缓冲类型:
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setbuf: 用于开关缓冲,当将buf设置为nullptr时,关闭缓冲,或者buf指向一个长度为BUFSIZ(stdio.h)的缓冲区。
setvbuf: 设置所需的缓冲类型
mode可以选择的值如下:
_IOFBF: 全缓冲
_IOLBF: 行缓冲
_IONBF: 不缓冲
如果指定一个不缓冲的流,则可以忽略buf和size参数。
如果指定全缓冲或者行缓冲,则可以指定一个缓冲区和长度,如果流的buf也设置为nullptr, 则标准IO库自动分配一个BUFSIZE长度的缓冲区。
#include <stdio.h>
int fflush(FILE* stream);
fflush: 强制刷新一个流,该流所有未写的数据都被传送到内核中,当stream为nullptr时,所有打开的输出流都被刷新。
3 opening a stream
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
fopen: 打开一个文件,并返回一个与之相关联的流
其中,mode可以选择以下字符串:
"r" : 打开一个读文件(可以读的文件,下同), 流从文件头开始。
"r+": 打开一个读写文件,流从文件头开始。
"w" : 打开一个写文件,并且文件长度截短为0,流从文件头开始。
"w+": 打开一个读写文件,如果文件不存在,则创建,如果存在,则文件长度截短为0,流从文件头开始。
"a" : 打开一个写文件,如果文件不存在,则创建,如果存在,流从文件尾开始。
"a+": 打开一个读写文件,如果文件不存在,则创建,如果存在,则读从文件头开始,写从文件尾开始。
经常可以看见添加符号'b'的,不过POSIX系统一般都忽略,包括Linux。
fdopen: 通过文件描述符返回一个与之关联的流。
其中,mode可选的字符串与fopen相同,但不会有"文件不存在,则创建"之类效果,也不会截短,因为有效的fd保证了文件已 经存在了。
当fdopen创建的流被关闭的时候,文件描述符fd也会被关闭。这边会有一些共享内存对象的问题。
另外,mode与获取fd时选择的mode要兼容,假设使用open获取fd时,设置为readonly,则此时设置"r+"会冲突,具体看示例 test5.1, 对应如下:
r : O_RDONLY
r+ : O_RDWR
w : O_WRONLY | O_CREAT | O_TRUNC
w+ : O_RDWR | O_CREAT | O_TRUNC
a : O_WRONLY | O_CREAT | O_APPEND
a+ : O_RDWR | O_CREAT | O_APPEND
并不一定是完全对应的,应该根据实际情况来定,比如下边的示例:
r+ : O_RDWR | O_CREAT
程序用例:
// test5.1.cc
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
int fd = open("test5.1.log", O_RDWR | O_CREAT);
if(fd == -1)
{
printf("fd open filed, error[%d].\n", errno);
return 1;
}
printf("fd is %d.\n", fd);
FILE* file = fdopen(fd, "r+");
if(!file)
{
printf("fdopen failed, error[%d].\n", errno);
return 1;
}
printf("fdopen success\n");
return 0;
}
可以尝试将open时候的O_RDWR改为O_RDONLY或者修改fdopen中的r+为r。会报错为无效参数
#include <stdio.h>
int fileno(FILE* stream);
fileno: 返回它的文件描述符。
4 reading and writing a stream
当我们打开一个流之后,可以选择三种unformatted I/O类型进行读写。
-1:每次一个字符的I/O,每次可以读或者写一个字符,如果流是带缓冲的,I/O标准库会处理所有的缓冲。
-2:每次一行的I/O,可以使用fgets/fputs来读/写一行,每行以一个换行符终止。
-3:直接I/O,可以使用fread/fwrite支持这种I/O。
-1:character-at-a-time I/O
reading:
#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar();
return: next character if OK, EOF on end of file or error.
getchar()相当于getc(stdin)
getc: getc被ISO C实现为宏,因此,不能将其作为函数指针传递(或许有编译器将其实现为函数),书上称其不能成为有副作用的表达式(稍后解释)。
fgetc:被实现为一个函数。
副作用的表达式:
#define SQRT(x) (x)*(x)
int sqrt(int x) { return x * x; }
如果x的值为++5,我们想取得的值为 6 * 6 = 36.
那么宏SQRT不能正确表达我们的想法,称为有副作用的表达式。
需要注意的是当文件到达末尾或者发生错误时,都是返回EOF,为了分辨使用以下函数:
#include <stdio.h>
int feof(FILE* stream);
int ferror(FILE* stream);
如果为真,则返回非零整数表示true,返回零表示false
在大部分的实现中,流内部维持着一个状态,通过这个状态来判断是那种错误。
可以通过以下函数来清除这种状态:
#include <stdio.h>
void clearerr(FILE* stream);
还有一种函数时用来将字符塞回到缓冲区的,相当于push back,从后头塞进去。
假设缓冲中有"abcd", 当我们塞入f的时候,它出现在末尾"abcdf"。
一次只能塞入一个字符,且不能塞入EOF,这个字符并不是真正的写到底层文件或者设备中,而是流的缓冲区中。
#include <stdio.h>
int ungetc(int c, FILE* stream);
writing:
#include <stdio.h>
int putc(int c, FILE* stream);
int fputc(int c, FILE* stream);
int putchar(int c);
return c if OK, EOF on error.
putc通常被实现成宏,而fputc实现为函数。
-2: line-at-a-time I/O
reading:
#include <stdio.h>
char* gets(char* s);
char* fgets(char* s, int size, FILE* stream);
return buf if OK, null on end of file or error.
fgets指定读取的长度,如果一行超过这个长度,则只读取部分,然后接着读取这一行。
一行的结束以null字符做为标志。
gets是不建议使用的,因为没有指定长度,很容易overflow。
writing:
#include <stdio.h>
int fputs(const char* s, FILE* stream);
int puts(const char* s);
return non-negative value if OK, EOF on error
puts到没有像gets()那样强调不要使用,但是,还是最好使用fputs()吧。
程序用例:
#include <stdio.h>
#include <errno.h>
int main(int argc, char* argv[])
{
int c;
while((c == getc(stdin)) != EOF)
{
if(putc(c, stdout) == EOF)
{
printf("putc error[%d].\n", errno);
return 1;
}
}
const int MAXLINE = 4096;
char buf[MAXLINE];
while(fgets(buf, MAXLINE, stdin) != NULL)
{
if(fputs(buf, stdout) == EOF)
{
printf("putc error[%d].\n", errno);
return 1;
}
}
printf("All success.\n");
return 0;
}
fgetc和fgets效率,依赖于其实现。
5 binary I/O
#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);
fread: ptr指向欲存放读取进来的数据空间,读取字符数以参数size * nmemb决定。
fwrite: ptr指向欲下乳的数据地址,写入的字符书以参数size * nmemb决定。
程序用例:
#include <stdio.h>
#include <errno.h>
#include <string.h>
const int _name_size = 32;
struct Data
{
int id;
int money;
int imageId;
char name[_name_size];
};
void FillData(Data* dataArray)
{
Data* data1 = &dataArray[0];
data1->id = 100;
data1->money = 1000;
data1->imageId = 1000010;
memcpy(data1->name, "AlexZ", _name_size);
Data* data2 = &dataArray[1];
data2->id = 101;
data2->money = 2000;
data2->imageId = 1000011;
memcpy(data2->name, "Hey", _name_size);
}
void PrintfData(const Data* read)
{
if(!read)
return;
printf("id: %d\t money: %d\t imageId: %d\t name: %s\n",
read->id, read->money, read->imageId, read->name);
}
int main(int argc, char* argv[])
{
// test fwrite
FILE* fp = fopen("test5.2.log", "w+");
if(!fp)
{
printf("fp fopen failed, error[%d].\n", errno);
return 1;
}
struct Data dataArray[2];
memset(dataArray, 0, sizeof(Data) * 2);
FillData(dataArray);
int ret = fwrite(dataArray, sizeof(Data), 2, fp);
if(ret != 2)
{
printf("fwrite falied. ret[%d]\t error[%d].\n", ret, errno);
return 1;
}
fclose(fp);
printf("fwrite success.\n");
// test fread
FILE* readFp = fopen("test5.2.log", "r");
if(!readFp)
{
printf("readFp fopen failed, error[%d].\n", errno);
return 1;
}
struct Data readArray[2];
ret = fread(readArray, sizeof(Data), 2, readFp);
if(ret != 2)
{
printf("fread failed. ret[%d]\t error[%d].\n", ret, errno);
return 1;
}
// printf
printf("print whatever read:\n");
PrintfData(&readArray[0]);
PrintfData(&readArray[1]);
printf("test Over.\n");
return 0;
}
执行结果:
fwrite success.
print whatever read:
id: 100 money: 1000 imageId: 1000010 name: AlexZ
id: 101 money: 2000 imageId: 1000011 name: Hey
test Over.
fread和fwrite涉及到一个读写不在相同系统上的问题,在一中类型的系统写,在另一种系统读。
这种情况暂时未碰见,不过后边socket倒是也会遇到这个问题。一般都是改变结构体对齐方式来解决。或者是自行填充一个缓冲区。
6 formatted I/O
格式化输出:
#include <stdio.h>
int dprintf(int fd, const char* format, ...);
int vdprintf(int fd, const char* format, va_list ap);
int printf(const char* format, ...);
int fprintf(FILE* stream, 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 vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);
转换类型:
d, i: 有符号十进制
o : 无符号八进制
u : 无符号十进制
x, X: 无符号十六进制
f, F: double精度浮点数
e, E: 指数格式的double精度浮点数
g, G: 解释为f, F, e, E,取决于被转换的值
a, A: 十六进制指数格式的double精度浮点数
c : 字符(若带有长度修饰符l,则为宽字符)
s : 字符串(若带有长度修饰符l,则为宽字符)
p : 指向void的指针
n : 将到目前为止,把缩写的字符数写入到指针所指向的无符号整型中
C : 相当于 lc
S : 相当于 ls
格式化输出:
#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);
转换类型:
d : 有符号十进制,基数为10
i : 有符号十进制,技术由输入格式决定
o : 无符号八进制(输入可选的有符号)
u : 无符号十进制,技术为10(输入可选的有符号)
x : 无符号十六进制(输入可选的有符号)
a, A, e, E, f, F, g, G: 浮点数
c : 字符(若带有长度修饰符l,则为宽字符)
s : 字符串(若带有长度修饰符l,则为宽字符)
[ : 匹配列出的字符序列,以]终止
[^: 匹配除列出的字符序列以外的序列,以]终止
p : 指向void的指针
n : 将到目前为止读取的字符数写入到指针所指向的无符号整型中
C : 宽字符,相当于 lc
S : 宽字符串,相当于 ls
7 memory stream
#include <stdio.h>
FILE* fmemopen(void*buf, size_t size, const char* mode);
FILE* open_memstream(char**ptr, size_t* sizeloc);
#include <wchar.h>
FILE* open_wmemstream(wchar_t** ptr, size_t* sizeloc);
相当于把一段内存映射成一个文件来操作。
fmemopen: mode与fopen相同
#include <stdlib.h>
#include <string.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
static char buffer[] = "Hello world";
int main(int argc, char* argv[])
{
FILE* in = fmemopen(buffer, strlen(buffer), "r");
if(!in)
handle_error("fmemopen failed.");
int ch;
while((ch = fgetc(in)) != EOF)
printf("%c ", ch);
fclose(in);
printf("\ntest Over\n");
return 0;
}
/////////////////////
open_memstream:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char* argv[])
{
char* buf;
size_t len;
FILE* out = open_memstream(&buf, &len);
if(!out)
handle_error("open_memstream is failed.");
printf("len: %d\n", len);
fprintf(out, "Hello World\n");
fflush(out);
printf("out: %s len: %d\n", buf, len);
fclose(out);
free(buf); // 需要主动释放
return 0;
}