一.流的概念与FILE
当我们使用标准I/O库打开或创建一个文件时,便会将一个流与该文件相关联。那么什么是流呢?流这个词可以让我们联想到河道里的流水,其实这可以很形象的比喻出我们要讨论的流。标准在输入与文件之间增加了一个缓冲区,这就相当于是河道,而数据便是河道中的流水。而这个河道的宽度,长度,目的地便是通过FILE对象来维护的,FILE结构中包含:文件描述符,指向该流的缓冲区的指针,缓冲区的长度,当前缓冲区的字符数,出错标记等等。FILE中还标识了该流是宽定向的还是字节定向的,即流定向(感觉叫流定宽更符合,哈哈),所谓宽定向指的是缓冲区中存储的是宽字符(很像河道的宽度),字节定向指的是缓冲区中存储的是单字节(也就是说流水是单字节数据)。当一个流刚创建时是未定向的,决定于起初在该流上使用的是单字节I/O函数还是多字节I/O函数。我们可以使用freopen函数来清除一个流的定向,fwide函数可用来设置流的定向,但不可改变流的定向,即使用fwide之前应该先使用freopen清除流定向。且fwide函数无返回值,因此应该使用前先保存现有errno,再将errno清空,调用fwide后判断errno。
对一个进程预定义了3个流并被进程自动使用,分别为:标准输入,标准输出,标准错误。这些流引用的文件(注意不一定引用的是同一个文件对象)与文件描述符STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO相同。这三个标准IO流通过预定义的文件指针stdin,stdout,stderr进行引用。
二.标准I/O的缓冲
标准IO缓冲有3类:全缓冲(填满时进行I/O),行缓冲(遇到换行符或缓冲满时进行I/O),不带缓冲(立即进行I/O)。我们可以在一个已打开的流上调用setbuf与setvbuf(更安全,且可以设置缓冲类型)来设置用户自己的缓冲,以此代替标准I/O缓冲。当然,对于自己定义的缓冲区应该记得在关闭流后释放。
三.对标准I/O相关注意事项
- 我们可以使用fdopen将一个标准的I/O流与某个已有描述符相关联,此函数常常用于管道的文件描述符和套接字文件描述符,因为这些特殊类型的文件不能用标准I/O函数fopen打开。也可以调用fileno获取一个流的描述符。
- 内核中其实是不区分文本文件与二进制文件的,因此在打开标准I/O时的打开方式参数中的‘b’实际上并无作用。
- 当我们关闭字节流时,会先冲洗缓冲中的输出数据。缓冲区中的任何输入数据被丢弃。同样的,当一个进程正常终止时(调用exit或从main函数返回),则所有含未写缓冲数据(输出)的标准I/O流都将被冲洗(取决于exit的实现),并关闭所有打开的标准I/O流。
- 如果中间没有fflush、fseek、fsetpos、或rewind,则输出操作的后面不能直接跟随输入,标准I/O库不允许这样做,即若在写后直接跟一个读函数,将无法独处数据。必须先进行冲洗,而fsetpos等函数在移动文件指针前也会先对数据进行冲洗,测试如下:
int main()
{
const char *path = "/home/lwh/QtProject/testFile.txt";
FILE* fp = fopen(path, "r+");
char fileBuf[FILE_BUF_SIZE];
setvbuf(fp, fileBuf, _IOFBF, FILE_BUF_SIZE); // 设置标准I/O使用的缓存,使用全缓冲
char data[100];
memset(data, 'a', sizeof(data) - 1);
data[99] = '\0';
fpos_t pos;
fgetpos(fp, &pos);
fputs(data, fp); // 若在此语句之后打印fileBuf,会发现数据还存留在其中
// 若打开该注释会发现即使文件中有数据也不可读出
// 当若将该段移至fstepos之后便可读出数据
/* char rbuf[200];
size_t n = fread(rbuf, 1, 100, fp);
rbuf[n] = '\0';
std::cout<<rbuf<<std::endl;*/
fsetpos(fp, &pos);
std::cout<<"fsetpos complete";
getchar();
fclose(fp);
return 0;
}
- 如果中间没有fseek、fsetpos、或rewind,或者一个输入操作没有到达文件尾端,则在操作之后不能直接输出。
四.临时文件
可以使用函数tmpnam创建一个与现有文件名不同的有效路径名字符串,tmpfile可以创建一个临时文件,在关闭或程序结束后自动删除。mkdtemp与mkstemp有同样的功能,只是其用于创建目录,具体参考APUE p134