标准I/O是ANSI C建立的一个标准I/O模型,封装了缓冲区,使得在读写文件的时候减少了系统调用的次数,提高了效率。(在执行系统调用的时候,Linux必须从用户态切换到内核态,在内核中处理相应的请求,然后再返回用户态。如果频繁地执行系统调用则会增加这种开销。标准I/O为了减少这种开销,采取缓冲机制,为用户空间创建缓冲区,读写时优先操作缓冲区,在必须访问文件时(例如缓冲区满、强制刷新、文件读写结束等情况)再通过系统调用将缓冲区的数据读写实际文件中,从而避免了系统调用的次数)
1、标准I/O缓存
1.1、标准的I/O提供了三种类型的缓存。
- 全缓存:当缓冲区被填满后才进行实际的I/O操作。(如 FILE *fp)默认大小:4096
- 行缓存:当输入或输出中遇到换行符时进行实际的I/O操作。(如 stdin(标准输入流)、stdout(标准输出流))默认大小:1024
- 无缓存:直接进行实际的I/O操作。(如 stderr(标准输出错误流))大小:0
1.2、刷新缓存区的三种方式
- 程序结束(关闭流)
- 缓存区填满
- 特定命令刷新,\n(行缓存默认),fflush
1.2.1 例子:
#include<stdio.h>
#include<unistd.h> // sleep
int main() {
while (1)
{
printf("hello");
sleep(1);
}
return 0;
}
结果:
没有直接输出,输出暂存在缓冲区里了
1.2.2 例子:
#include<stdio.h>
#include<unistd.h> // sleep
int main() {
while (1)
{
printf("hello");
//printf("hello\n"); // \n 刷新缓冲区
fflush(stdout); //刷新缓冲区
sleep(1);
}
return 0;
}
结果:
直接输出了内容。
2、流的定义
标准I/O的核心对象是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件。我们把这个FILE结构体称为“流”。标准I/O函数都是基于流进行各种操作的
流就是字节流,分文本流和二进制流,每个ANSI C程序,系统至少提供三个stdin,stdout,stderr。
- 文本流:文本流是由字符文件组成的序列,每一行包含0个或多个字符并以'\n'结尾。在流处理过程中所有数据以字符形式出 现,'\n'被当做回车符CR和换行符LF两个字符处理,即'\n'ASCII码存储形式是0x0D和0x0A。当输出时,0x0D和0x0A转换 成'\n'
- 二进制流:二进制流是未经处理的字节组成的序列,在流处理过程中把数据当做二进制序列,若流中有字符则把字符当做ASCII码的二进制数表示。'\n'不进行变换。
(在Linux/Unix系统中,文本流与二进制流没有差异,但是在Windows中稍有差异,所以标准C库定义了两种流。)
默认提供三种流:stdin(标准输入),stdout(标准输出),stderr(标准错误)
3、标准i/o函数
3.1 fopen
头 文 件 :#include<stdio.h>
函数原型:FILE *fopen(const char *path, const char *mode)
功 能 :打开一个文件,成功返回流指针,失败返回 NULL
注 意 :fopen创建文件默认权限是 0666 wr-wr-wr 但linux下umask会影响访问权限,最终权限为(0666 & ~umask)
参 数 :
- path 路径名(文件名)
- mode 打开方式
符号 | 意义 |
---|---|
r | 以读的方式打开文件,如果文件不存在,报错 |
w | 以写的方式打开文件,如果文件不存在,自动创建,如果文件存在,将文件中的数据清空 |
a | 以写的方式打开文件,如果文件不存在,自动创建,如果文件存在,将数据写入到文件的末尾 |
r+ | 以读写的方式打开文件,如果文件不存在,报错 |
w+ | 以读写的方式打开文件,如果文件不存在,自动创建,如果文件存在,将文件中的数据清空 |
a+ | 以读写的方式打开文件,如果文件不存在,自动创建,如果文件存在,将数据写入到文件的末尾 |
3.2 perror
头 文 件 :#include<stdio.h>
函数原型:void perror(const char *s)
功 能 :先输出字符串s 再输出错误号对应的错误信息
3.3 fclose
头 文 件 :#include<stdio.h>
函数原型:int fclose(FILE *stream)
功 能 :关闭一个流,成功返回0,失败返回 EOF
注 意 :流关闭时会刷新缓冲区,程序终止时,所有打开的流都会被关闭
参 数 :stream 流指针
3.4 fgetc
头 文 件 :#include<stdio.h>
函数原型:int fgetc(FILE *stream)
功 能 :读取一个字符,成功返回读取的字符,失败返回 EOF
注 意 :getchar()等同于fgetc(stdin)
参 数 :stream 流指针
3.5 fputc
头 文 件 :#include<stdio.h>
函数原型:int fputc(int c, FILE *stream)
功 能 :写入一个字符,成功返回写入的字符,失败返回 EOF
注 意 :putchar(c)等同于fputc(c,stdout)
参 数 :stream 流指针
3.51 例子 用fgetc和fputc复制文件:
#include<stdio.h>
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char ch;
if (argc < 3) //判断参数个数
{
printf("Usage: %s <src_file> <dst_file> \n",argv[0]);
return -1;
}
if((fps = fopen(argv[1],"r")) == NULL) //打开要复制的文件
{
perror("fopen src_file ");
return -1;
}
if((fpd = fopen(argv[2],"w")) == NULL) //打开/创建 复制成的文件
{
perror("fopen dst_file ");
return -1;
}
while((ch = fgetc(fps)) != EOF) //读到文件末尾返回EOF
{
fputc(ch,fpd);
}
fclose(fps); //关闭流
fclose(fpd); //关闭流
return 0;
}
3.6 fgets
头 文 件 :#include<stdio.h>
函数原型:char *fgets(char *s, int size, FILE *stream)
功 能 :读取一行字符串,成功返回s,到文件末尾或出错时返回NULL
注 意 :遇到 '\n' 或已输入size-1个字符时返回,总是包含 '\0'
设缓冲区buf大小为4,输入 abcd\nef 实际存储 a b c /0 大小为6时,实际存储 a b c d \n \0
参 数 :
s 缓冲区地址
size 输入的大小
stream 流指针
3.6.1 例题: 统计一个文本文件包含多少行
#include<stdio.h>
#include<string.h> //strlen()
int main(int argc, char *argv[])
{
FILE *fp;
char buf[64]; //定义缓冲区
int line = 0;
if (argc < 2)
{
printf("Usage: %s <dst_file> \n",argv[0]);
return -1;
}
if((fp = fopen("test.txt","r")) == NULL)
{
perror("fopen");
return -1;
}
while (fgets(buf, 64, fp) != NULL)
{
if(buf[strlen(buf)-1] == '\n') line++;
}
close(fp); //注意关闭流
printf("%d\n",line);
return 0;
}
3.6 fputs
头 文 件 :#include<stdio.h>
函数原型:int fgetc(const char *s, FILE *stream)
功 能 :读取一个字符,成功返回输出的字符个数,出错返回 EOF
注 意 :puts()会自动输出\n 而fputs不会
参 数 :
s 缓冲区地址
stream 流指针
3.7 fread
头 文 件 :#include<stdio.h>
函数原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
功 能 :从文件中读取数据单元,成功返回实际读对象个数,如果小于nmemb,则可能文件结束或读取出错
注 意 :fread不区分文件尾和错误,因此调用者必须用feof和ferror才能判断发生了什么。如果size或nmemb为0,则返回0
参 数 :
ptr 存放读取数据的缓冲区
size 读取的数据单元的大小
nmemb 读取的数据单元的个数
stream 流指针
3.8 fwrite
头 文 件 :#include<stdio.h>
函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
功 能 :从文件中读取数据单元,成功返回实际写对象个数
注 意 :puts()会自动输出\n 而fputs不会
参 数 :
ptr 存放写入数据的缓冲区
size 写入的数据单元的大小
nmemb 写入的数据单元的个数
stream 流指针
3.81例题:用fread,fwrite复制文件
#include<stdio.h>
#define N 64
int main(int argc, char *argv[])
{
FILE *fps,*fpd;
int n;
char buf[N];
if (argc < 3)
{
printf("Usage: %s <src_file> <dst_file> \n",argv[0]);
return -1;
}
if(NULL == ((fps = fopen(argv[1],"r"))))
{
perror("open src_file");
return -1;
}
if(NULL == ((fpd = fopen(argv[2],"w"))))
{
perror("open dst_file");
return -1;
}
while (!feof(fps)) //feof 到文件末尾返回1
{
n = fread(buf, 1, N,fps); //一个字节大小的单元
fwrite(buf, 1, n,fpd); //写入实际读取到的字符的个数
}
fclose(fps);
fclose(fpd);
return 0;
}
3.9 fprintf、sprintf、printf
头 文 件 :#include<stdio.h>
函数原型:
int fprintf(FILE *fp, const char *fmt, ...)
int sprintf(char *s, const char *fmt, ...)
int printf(const char *fmt, ...)
功 能 :格式化输出,成功返回输出的字符个数,出错返回EOF
参 数 :
fp 流指针
s 缓冲区地址
fmt 输出的格式
3.9.1例子:每隔1秒想文件test.txt中写入当前系统时间
#include<stdio.h>
#include<string.h> //strlen
#include<unistd.h> //sleep
#include<time.h> //time / localtime
int main()
{
FILE *fp;
int line = 0;
char buf[64];
time_t t;
struct tm *tp;
if(NULL == (fp = fopen("test.txt","r+")))
{
perror("fopen");
return -1;
}
while (fgets(buf, 64, fp));
{
if(buf[strlen(buf)-1] == '\n') line++;
}
while (1)
{
time(&t);
tp = localtime(&t);
fprintf(fp,"%d: %d-%02d-%02d %02d:%02d:%02d\n",++line,tp->tm_year+1900,tp->tm_mon+1,tp->tm_mday,tp->tm_hour,tp->tm_min,tp->tm_sec);
fflush(fp); //上面默认为全缓冲 fflush强制刷新一下
sleep(1); //睡眠一秒
}
return 0;
}
4、流操作
在打开流的时候,默认偏移位置为0,随着读写的进行,当前读写位置会自动后移,每次后移的大小为实际读写的大小。可以使用fseek()函数和ftell()函数对当前流的偏移量进行定位操作。
4.1 fflush
头 文 件 :#include<stdio.h>
函数原型:int fflush(FILE *fp)
功 能 :刷新流,成功返回0,出错返回EOF
注 意 :Linux下只能刷新输出缓冲区
参 数 :fp 流指针
4.2 ftell
头 文 件 :#include<stdio.h>
函数原型:long ftell(FILE *stream)
功 能 :返回当前读写位置,成功返回当前读写位置,失败返回EOF
参 数 :stream 流指针
4.3 fseek
头 文 件 :#include<stdio.h>
函数原型:long fseek(FILE *stream, long offset, int whence)
功 能 :定位一个流,成功返回0,出错返回EOF
注 意 :whence+offset不能为负数,可以超过文件的长度
参 数 :
stream 流指针
offset 偏移量 相对于第三个参数再次发生的偏移
whence 基准点, SEEK_SET 文件开头 SEEK_CUR 当前位置 SEEK_END 文件末尾
4.31例子:获取文件长度
#include<stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if(argc < 2)
{
printf("Usage: %s <file_name> \n",argv[0]);
return -1;
}
if(NULL == (fp = fopen(argv[1],"r")))
{
perror("open");
return -1;
}
fseek(fp,0,SEEK_END);
printf("file length %ld \n",ftell(fp));
fclose(fp);
return 0;
}