暗无天日

=============>DarkSun的个人博客

标准IO库

1 流与FILE对象

1.1 流定向

标准IO文件流可用于单字符或多字符字符集. 流的定向(orientation)决定了所读,写的字符是单字符还是多字符的.

当一个流被最初创建时,它并没有定向.

如果在未定向的流上使用一个多字节IO函数(<wchar.h>),则将该流的定向设置为宽定向的.

如果在未定向的流上使用一个单字节IO函数,则流的定向设置为单字节定向.

1.1.1 fwide函数:设置流的定向

#include <stdio.h>
#include <wchar.h>

/* 若mode参数值为负,fwide尝试将流设置为单字节定向 */
/* 若mode参数值为正,fwide尝试将流设置为多字节定向 */
/* 若mode参数值为0,fwide不设置定向,只是返回该流的定向 */
int fwide(FILE* fp, int mode);  /*  返回值表示流的定向,正为宽定向,负为但定向,0为未定向*/

注意:

  • fwide并不改变已定向流的定向
  • fwide无出错返回. 只能根据errno的值来判断流是否无效

1.1.2 freopen函数:清除一个流的定向

1.2 打开流

#include <stdio.h>

FILE* fopen(const char* path,const char* type);
FILE* freopen(const char* path,const char* type,FILE* fp);
FILE* fdopen(int filedes,const char* type);
  • fopen打开一个指定的文件,返回新的流
  • freopen在一个指定的流上打开一个指定的文件.

    若该流已被打开,则关闭该流.

    若该流以及定向,则清除该定向

    该函数一般将一个指定的文件打开为一个预定义的流:stdin,stdout,stderr

  • fdopen将一个标准IO流与文件描述符相结合.

    该函数一般用于将PIPE/SOCKET返回的描述符与标准IO流结合,因为这些特殊类型的文件不能用fopen函数打开.

  • 当以读写类型打开文件时,具有如下限制:
    • 如果中间没有fflush,fseek,fsetops或rewind,则输出的后面不能直接跟随输入
    • 如果中间没有fseek,fsetpos或rewind或一个输入操作没有到达文件尾部,则再输入操作之后不能直接跟随输出.

1.3 读写流

1.3.1 一次一个字符的IO

#include <stdio.h>

int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar();
int ungetc(int c,FILE* fp);

int putc(int c,FILE* fp);
int fputc(int c,FILE* fp);
int putchar(int c);

其中getc和fgetc的区别为,getc可能为宏,而fgetc一定是函数. 即

  • getc的参数不应当是具有副作用的表达式
  • fgetc一定可以得到其地址,从而作为参数传递给另一个参数.
  • fgetc调用时间可能会长于getc,因为调用函数所需的时间通常长于调用宏.
  • ungetc压回的字符, 可以不是刚读出的字符

putc和fputc的区别类似于getc和fgetc的区别.

1.3.2 一次一行的IO

#include <stdio.h>

char* fgets(char* buf,int n,FILE* fp);
char* gets(char* buf);
int fputs(const char* str,FILE* fp);
int puts(const char* str);

注意:

  • gets不能指定缓冲区的长度, 且gets并不将换行符存入缓冲区内.
  • fgets读取时会保持换行符
  • fputs输出时,指数出str的内容,而不会添加换行符
  • puts输出时,会添加换行符.

1.3.3 二进制直接IO

#include <stdio.h>

size_t fread(void* ptr,size_t size,size_t ntimes,FILE* fp);
size_t fwrite(const void* ptr,size_t size,size_t ntimes,FILE* fp);
  • 这两个函数,常用于从二进制文件中读/写一个结构

1.4 定位流

#include <stdio.h>

long ftell(FILE* fp);
int fseek(FILE* fp,long offset,int whence);
void rewind(FILE* fp);

off_t ftello(FILE* fp);
int fseeko(FILE* fp,off_t offset,int whence);

int fgetpos(FILE* fp,fpos_t* pos);
int fsetpos(FILE* fp,const fpos_t* pos);

1.5 错误判断

读取出错或到达文件结尾,读取函数的返回值都是同一个值. 为了区分这两种不同的情况,必须调用ferror或feof

#include <stdio.h>

int ferror(FILE* fp);
int feof(FILE* fp);

void clearerr(FILE* fp);

1.6 fileno:获取流对应的文件描述符

#include <stdio.h>

int fileno(FILE* fp);

2 缓冲

标准IO提供了2种类型的缓冲

2.1 全缓冲

填满IO缓冲区才进行实际的IO操作

对于驻留在磁盘上的文件,通常是由标准IO库实施全缓冲.

2.2 行缓冲

当输入和输出中遇到换行符时,标准IO库执行IO操作

当流涉及一个终端时,通常使用行缓冲.

2.3 缓冲的惯例

一般标准IO缓冲的惯例为:标准出错不带缓冲,打开至终端设备的流是行缓冲,其他流是全缓冲.

2.4 更改缓冲类型

#include <stdio.h>

void setbuf(FILE* fp,char* buf);
int setvbuf(FILE* fp,char* buf,int mode,size_t size);

2.4.1 setbuf函数:打开/关闭缓冲机制

当为打开缓冲机制时,这里参数buf必须为一个长度为BUFSIZ的缓冲区(BUFSIZ定义在stdio.h中)

若要关闭缓冲机制,参数buf为NULL

2.4.2 setvbuf函数:精确指定缓冲类型

  • 参数mode可以为:

    _IOFBF 全缓冲
    _IOLBF 行缓冲
    _IONBF 无缓冲
  • 参数buf无长度限制,因为有参数size指定缓冲大小.

    若流设置为带缓冲的,而buf为NULL,则表示IO自动为该流分配适当长度的缓冲区(一般为BUFSIZ大小)

    一般而言,应由系统选择缓冲区的长度,并自动分配缓冲区.

2.4.3 fflush函数:强制将缓冲区内容写入磁盘

#include <stdio.h>

int fflush(FILE* fp);

3 标准输入,标准输出和标准出错

<stdio.h>中预定义了三个文件指针:stdin,stdout和stderr 一般来说,stderr不带缓冲,stdin和stdout为行缓冲

4 产生临时文件

#include <stdio.h>

char* tmpnam(char* ptr);
char* tempnam(const char* directory,const char* prefix);

FILE* tmpfile();
int mkstemp(char* template);
  • tmpnam函数产生一个临时文件的名字字符串,最多能调用TMP_MAX次
  • 参数ptr可以为NULL,则产生的路径名存放在一个静态区域,下一次调用时被覆盖.
  • 若ptr不为NULL,则数组长度应该不少于L_tmpnam
  • tmpfile创建一个临时的二进制文件. 关闭该文件或程序结束后会 自动删除该文件.
  • tempnam与tmpnam的不同在于它允许调用者为所产生的临时文件路径指定目录和前缀. 其按如下顺序选择目录
    1. 环境变量TMPDIR (此时忽略参数directory的值!)
    2. 参数directory
    3. <stdio.h>中的字符串P_tmpdir(通常为/tmp)
    4. 本地目录
  • tempnam中的prefix参数最多只能包含5个字符.
  • mkstemp的参数template必须为一个路径名,且 最后6个字符设置为XXXXXX
  • mkstemp产生的 临时文件不会自动被删除

5 线程安全的方式管理FILE对象

PSOXI.1提供了以线程安全的方式管理FILE文件的方法. 可以使用`flockfile'和`ftrylockfile'获取与FILE对象关联的锁,且这个锁为递归锁.

所有操作FILE对象的标准IO函数,都需要表现的好像内部调用了flockfile和funlockfile一样,即不能出现多个线程同时写入同一FILE对象时出现乱序的情况.

#include <stdio.h>

int ftrylockfile(FILE* fp);

void flockfile(FILE* fp);

void funlockfile(FILE* fp);

通过这些函数,我们可以把多个标准IO函数的调用组合成一个原子序列.

若标准IO函数每次操作都获取自己的锁再释放,那么在进行单字符的IO操作时,会消耗大量的性能到加解锁中,因此就有了 不加锁的基于字符的标准IO函数

#include <stdio.h>

int getchar_unlocked();
int getc_unlocked(FILE* fp);

int putchar_unlocked(int c);
int putc_unlocked(int c,FILE* fp);

请保证这四个函数调用时包含在flockfile/ftrylockfile和funlockfile中