目录
1.2 setvbuf 函数(设置程序的FILE缓冲区函数)
fgetc/getc/getchar---------单字输入函数
前言:
众所周知文件IO可以分为系统IO和标准IO,而系统IO指的是操作系统(glibc库)提供给用户操作文件的接口(open/close/read/write/lseek...),那么标准IO指的是什么呢?
1.标准IO是什么,为什么需要标准IO
在每一个操作系统下面,对文件的管理和接口都是不一样的, Linux的系统IO、windows:CreatFile....
在linux系统中流程多为:
APP---->进程文件表项的下标(文件描述符)--->struct file *---->struct file ----->struct inode ----->硬件
Linux系统IO操作函数:open/close/read/write/lseek...
同一个文件,在不同的操作系统下面,操作文件的方法和代码可能不一样
但是C语言是几乎所有操作系统都支持的语言,使用C语言调用系统IO写的操作文件的代码地方移植性就非常差(使用了CreatFile,就不能在Linux下面运行,使用了open/read就不能运行到其他的系统中(没有glibC)),于是C语言标准委员会,就觉得它有责任,来统一文件操作的接口。
======>
标准IO:C语言标准委员会统一的一套操作文件的接口
标准IO只能操作普通文件(不能操作设备文件/目录文件)
普通文件:
普通的文本文件
没有特殊组织和格式的文件,以字符的ASCII码来顺序解析的文件
.txt .c .h .cpp .sh ....
能够使用记事本打开的
二进制文件
内部有特殊格式的文件,文件的内容需要按照特定的格式去解析
某一些字节可能会代表特殊的含义(不能使用记事本打开)
可执行文件, .bmp,.jpg, .doc .....
在标准IO中,使用结构体(FILE)来描述一个打开的文件,结构体内部记录了文件的一些信息,结构体中也创建了两个缓冲区(两段可以使用的内存区域),一个读缓冲,一个写缓冲
同时还提供了对"普通文件"操作的函数接口
fopen/fclose/fwrite/fread/fseek/gets/puts/printf/scanf
只要支持C语言就可以使用标准IO的函数
APP通过标准IO去操作文件的流程:
APP--->标准IO的库函数(fopen/fclose)----->对应平台的系统IO--->内核-->硬件
使用标准IO,每打开一个文件,就使用一个FILE类型的结构体表示,FILE内部有两个缓冲区:
*out ----> 写缓冲区
*in -----> 读缓冲区
缓存区的作用主要是为了提高操作的效率
标准IO文件是带缓冲区的IO,也叫做IO流(文件流),它的效率比系统IO高。
区别:
系统IO:
read 1字节,会把操作文件的所有流程走一遍,需要访问一次硬件
write 1字节,会把操作文件的所有流程走一遍,需要访问一次硬件
标准IO:
fread: 1字节,会把操作文件的所有流程走一遍,访问一次硬件
但是会直接从硬件上面读取一块内容,放到缓冲区
下一次还需要读的时候,就不需要访问硬件,直接从缓冲区读取
fwrite: 1字节,不会立即同步到硬件,而是把内容写入到缓冲区
等待用户刷新或者缓冲区的内容满了的时候才会去访问一次硬件
1.1 标准IO的文件有三种缓冲区类型:
行缓冲:缓冲区的内容达到了一行(默认是1024个字节,可以设置),自动同步到硬件上面去,假设你设置行缓冲的大小最多是100个字节,缓冲区的数据达到了100个字节的时候,就会自动同步到硬件
printf------>行缓冲 (stdout是行缓冲)
遇到'\n'也会把数据同步到硬件上面去
全缓冲:缓冲区的数据需要填满整个缓冲区,才会把数据同步到硬件上面去
普通的文件就是全缓冲
无缓冲:缓冲区中只要有数据就会同步到硬件上面去
perror----->无缓冲 (stderr是无缓冲)
1.2 setvbuf 函数(设置程序的FILE缓冲区函数)
也可以通过函数自己设置程序的FILE缓冲区类型和大小(setvbuf)
NAME
setbuf, setbuffer, setlinebuf, setvbuf - stream buffering operations 操作文件的缓冲区
SYNOPSIS
#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);
在系统IO中操作系统会为每一个进程打开三个文件
标准输入文件(键盘) 文件描述符 STDIN_FILENO 0
标准输出文件(终端) 文件描述符 STDOUT_FILENO 1
标准错误文件(终端) 文件描述符 STDERR_FILENO 2
在标准IO中,会为每一个进程打开三个标准IO文件流(FILE),对应系统IO打开的三个文件
标准输入文件流 FILE *stdin
stdin是声明在<stdio.h>中的一个全局变量,它指向标准输入设备(键盘)
scanf默认就是从sdtin中获取数据
标准输出文件流 FILE *stdout
stdout是声明在<stdio.h>中的一个全局变量,它指向标准输出设备(终端),有缓冲区
printf就是把数据输出到stdout中
标准出错文件流 FILE *stderr
stderr是声明在<stdio.h>中的一个全局变量,它指向标准输出设备(终端),没有缓冲区
perror就是把数据输出到stderr中
2.常见的标准IO的函数接口
2.1.打开或者关闭一个文件流(fopen/fclose)
打开
NAME
fopen, fdopen - stream open functions
SYNOPSIS
#include <stdio.h>
fopen是用来打开一个普通文件的
FILE *fopen(const char *pathname, const char *mode);
pathname:你要打开的文件的路径名(可以是相对路径/可以是绝对路径)
mode:打开方式
"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
┌─────────────┬───────────────────────────────┐
│fopen() mode │ open() flags │
├─────────────┼───────────────────────────────┤
│ r │ O_RDONLY │
├─────────────┼───────────────────────────────┤
│ w │ O_WRONLY | O_CREAT | O_TRUNC │
├─────────────┼───────────────────────────────┤
│ a │ O_WRONLY | O_CREAT | O_APPEND │
├─────────────┼───────────────────────────────┤
│ r+ │ O_RDWR │
├─────────────┼───────────────────────────────┤
│ w+ │ O_RDWR | O_CREAT | O_TRUNC │
├─────────────┼───────────────────────────────┤
│ a+ │ O_RDWR | O_CREAT | O_APPEND │
└─────────────┴───────────────────────────────┘
返回值:
成功返回打开的文件的文件指针(FILE *)
在标准IO中,FILE*表示一个打开的文件,后面的标准IO库函数操作文件都是使用这个指针
失败返回NULL,同时errno被设置
关闭
NAME
fclose - close a stream
SYNOPSIS
#include <stdio.h>
int fclose(FILE *stream);
关闭一个流,会先同步文件,并且关闭底层的文件描述符
2.2.读写一个文件流(fread/fwrite)
读写一个文件流可以使用多种函数,我们可以将这些函数分为三类。
a.每一次一个字符的读写
fgetc/getc/getchar
fputc/putc/putchar
b.每一次一行的读写
fgets/gets
fputs/puts
c.直接读写,操作的数据由用户自定义
fread/fwrite
a.每一次一个字符的读写
fgetc/getc/getchar---------单字输入函数
NAME
fgetc, getc, getchar- input of characters
SYNOPSIS
#include <stdio.h>
fgetc和getc都是从指定的文件流中读取一个字符,返回读取到的字符的ASCII码
int fgetc(FILE *stream);
int getc(FILE *stream);
返回值:
成功返回读取到的字符的ASCII码
失败返回-1,同时errno被设置
int asc = fgetc(fp);
or
int asc = fgetc(stdin);
getchar是从标准输入(stdin)中读取一个字符,返回读取到的字符的ASCII码
int getchar(void);
getchar() <====> fgetc(stdin)
fputc/putc/putchar-----单字输出函数
NAME
fputc, putc, putchar- output of characters
SYNOPSIS
#include <stdio.h>
fputc和putc是用来把指定的字符c输出到stream指定的文件流中去
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
c:你要输出的字符的ASCII码
返回值:
成功返回实际写入文件的字符的ASCII码
失败返回-1,同时errno被设置
int ret = fputc('X',fp);
int ret = fputc('X',stdout);
fputc和putc的区别:fputc是一个函数,而putc可能是一个宏
int putchar(int c);
b.每一次一行的读写
fgets/gets ------行输入函数
NAME
fgets - input of strings
SYNOPSIS
#include <stdio.h>
gets是用来从标准输入文件流中获取一行
把读取到的数据保存到s指向的空间中去
char *gets(char *s); (有bug,不要使用)
s:是一个指针,指向一块可用的空间,用来保存读取到的字符串的
返回值:
成功返回s的首地址
失败返回NULL,同时errno被设置
NOTES:没有考虑到s指向的空间的大小问题,有可能会造成内存的非法访问
========>
fgets的作用是从stream指定的文件流中获取至多size个字符
把获取到的数据保存到s指向的空间中去
char *fgets(char *s, int size, FILE *stream);
s:是一个指针,指向一块可用的空间,用来保存读取到的字符串的
size:表示你最多获取size个字符(一般是s指向的空间的可用长度)
输入结束有两种情况
a.遇到'\n'或者文件结束了(\n也会被读取)
b.已经读取了size-1个字符(最后一个字符存储'\0')
stream:
表示你要从哪一个文件中读取数据
返回值:
成功返回s的首地址
为什么很多函数都返回自己传进去的地址(gets/fgets/strcpy/strcat...)
目的是为了让函数可以写成链式表达式(让函数表达式可以作为其他函数的参数)
失败返回NULL,同时errno被设置
char buf[1024] = {0};
char *s = fgets(buf,1024,stdin);
int r = strlen(buf);
==========>
int r = strlen( fgets(buf,1024,stdin) );
fputs/puts---------行输出函数
NAME
fputs, puts - output of strings
SYNOPSIS
#include <stdio.h>
fputs是用来把s指向的字符串,输出到stream指定的文件流中去
int fputs(const char *s, FILE *stream);
s:指针,表示你要输出的字符串的首地址(从s的位置开始一个字符一个字符的输出,直到遇到\0为止)
返回值:
成功返回一个非负数
失败返回-1,同时errno被设置
puts是用来把s指向的字符串,输出到stdout
但是会把字符串最后的'\0'转换为一个'\n'
int puts(const char *s);
c.直接读写,操作的数据由用户自定义
fread/fwrite 这两个函数都是直接操作缓冲区
NAME
fread, fwrite - binary stream input/output
SYNOPSIS
#include <stdio.h>
fread是用来从stream指定的文件流中读取nmemb个对象,并且每一个对象有size个字节,读取到的内容保存到ptr指向的内存空间中去
总共读取了: size * nmemb
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:是一个指针,指向一块可用的空间,用来保存读取到的数据
size:每一个数据对象的大小
nmemb:你要读取多少个数据对象
stream:表示你要从哪一个文件中读取数据
返回值:
成功返回实际读取到的元素个数(<= nmemb)
失败返回-1,同时errno被设置
fwrite是用来把ptr指向的nmemb个元素写入到stream指定的文件中去,每一个元素的大小是size
总共写入了: size * nmemb
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
ptr:是一个指针,指向你要写入的数据
size:每一个数据对象的大小
nmemb:你要写入多少个数据对象
stream:表示你要把数据写入到哪一个文件中
返回值:
成功返回实际写入的元素个数(<= nmemb)
失败返回-1,同时errno被设置
3.冲洗/同步一个文件流(fflush)
标准IO是带缓冲区的IO,读和写的操作都是针对于缓冲区的,并不是每一次读取都会同步到硬件,有时候多线程编程就会产生不可预测的结果
可以手动把缓冲区中的内容同步到硬件
NAME
fflush - flush a stream
SYNOPSIS
#include <stdio.h>
int fflush(FILE *stream);
stream:你要同步的文件流
返回值:
成功返回0
失败返回-1.同时errno被设置
注意:
对于普通文件
输出流(out):fflush会把缓冲区的内容直接更新到文件中去
输入流(in):fflush把缓冲区的内容直接丢弃(下一次读取,会直接从文件中读取)
stream为NULL,会把当前进程打开的所有的文件都进行一个同步
fflush(NULL)
4.定义一个文件流(fseek、ftell)
上面讲到fread/fwrite,只是从指定的文件中读取/写入数据,并没有指定从文件的哪一个位置开始读写
标准IO同样会为每一个文件保存一个"文件偏移量",offset(光标)下一次读和写的起始位置,并且每一次的读写,都会改变光标位置
一般来说,读和写之前,都需要重新定位光标的位置
NAME
fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream
SYNOPSIS
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
stream:你要定位的文件流
offset:偏移量,可正可负,配合第三个参数使用
whence:定位方式,注意有三种
SEEK_SET 基于文件开头定位
新位置 = 文件开头 + offset(>=0)
SEEK_CUR 基于文件当前位置定位
新位置 = 文件当前光标位置 + offset(可正可负)
SEEK_END 基于文件结尾定位
新位置 = 文件结尾 + offset(可正可负)
返回值:
成功返回0
失败返回-1,同时errno被设置
ftell返回当前文件流光标离文件开头有多少个字节
long ftell(FILE *stream);
fseek(fp,0,SEEK_END); //把光标定位到文件末尾
long size = ftell(fp);
//把文件光标定位到文件开头
void rewind(FILE *stream);
等价于:
fseek(stream, 0, SEEK_SET);
5.文件出错/文件结束标记(feof)
EOF(end of file):宏,文件结尾标记
NAME
feof - check and reset stream status
SYNOPSIS
#include <stdio.h>
feof是用来判断一个文件是否到达末尾(结束)
int feof(FILE *stream);
返回值:
文件流结束(光标到达了最后),返回真(非0)
文件还没有到达末尾,返回假
while( !feof(fp) ) //文件没有结束,就可以进入循环
{
...
}
在读到文件末尾的时候,再一次读的时候就会自动的往缓冲区中填入一个字节
EOF:二进制 1111 1111 ------> -1
6.格式化的输入输出(scanf/printf)
a.格式化输入(按照指定的格式输入数据)
scanf/sscanf/fscanf
NAME
scanf, fscanf, sscanf - input format conversion
SYNOPSIS
#include <stdio.h>
...:在C语言里面表示可变参数
int scanf(const char *format, ...);
scanf可以带很多个参数,参数分为两类
scanf("%d%d%d",&a,&b,&c);
第一类是第一个参数:格式化字符串(format string)
规定用户的输入方式,你必须要按照格式化字符串指定的格式一模一样的输入,不然就会失败
如:
scanf("abcd%d",&x);
"abcd%d"就是格式化字符串,在输入的时候一定要输入abcd+整数(不然就会匹配失败),abcd123
格式化字符串由三类字符组成:
a.空白字符(space tab)
指示用户,输入的时候,此处需要分隔符,用户可以输入任意数量的空白符(包括0个)
b.非转义字符
在输入的时候,普通字符需要精准匹配,你必须按照格式化字符串一模一样的输入
c.转义字符(以%开头)
%d----->匹配整数
%c----->匹配一个任意的字符
%f----->匹配一个浮点数
%s----->匹配一个字符串(中间不能有空白,scanf会把空白当前分隔符)
...
其他的参数是第二类参数(地址列表):
格式化字符串中每一个转义字符都会对应一个地址,把一个转义字符匹配到的输入存储到指定的地址上去,如果转义字符的个数大于地址个数,程序的行为是未定义的
scanf从标准输入中获取数据,如何结束?
a.该输入的都输入完了
格式化字符串中所有规定的内容都匹配成功
scanf("abcd%d %dabcd",&a,&b);
输入:abcd123 456abcd
所有的字符都匹配成功
b.scanf匹配失败了
scanf("abcd%d %dabcd",&a,&b);
输入:abab123 456abcd
匹配失败,停止匹配,直接结束函数
匹配失败之后,内容依然在标准输入的缓冲区
返回值:
返回匹配成功的变量的个数
========================================================
fscanf它的功能以及返回值与scanf类似,只不过fscanf的输入来源不是标准输入(stdin),而是从通过stream指定的文件流中读取
int fscanf(FILE *stream, const char *format, ...);
sscanf它的功能以及返回值与scanf类似,只不过sscanf的输入来源不是文件,而是从通过str指定的内存中读取
int sscanf(const char *str, const char *format, ...);
如:
char *s = "1234";
int a;
sscanf(s,"%d",&a);
b.格式化输出(按照指定的格式输出数据)
printf/fprintf/sprintf/snprintf
NAME
printf, fprintf, sprintf, snprintf - formatted output conversion
SYNOPSIS
#include <stdio.h>
printf是按照格式化字符串输出内容到标准输出文件(stdout)
int printf(const char *format, ...);
printf可以带很多个参数,参数分为两类
printf("a = %d,b = %d,c = %d",a,b,c);
第一类是第一个参数:格式化字符串(format string)
规定用户的输出方式,会按照格式化字符串指定的格式一模一样的输出
如:
printf("a = %d,b = %d,c = %d",a,b,c);
"a = %d,b = %d,c = %d"就是格式化字符串,在输出的时候会一模一样输出到stdout
格式化字符串由2类字符组成:
a.非转义字符
在输出的时候,普通字符会一模一样输出到stdout,不会有任何改变
b.转义字符(以%开头)
%d----->把后面指定的数据对象,以10进制整型输出
%x----->把后面指定的数据对象,以16进制整型输出
%c----->把后面指定的数据对象,以字符形式输出
%f----->把后面指定的数据对象,以浮点数输出
%u----->把后面指定的数据对象,以无符号10进制输出
%s----->字符串(从一个地址开始,一个字符一个字符的输出,直到遇到\0)
...
其他的参数是第二类参数(变量列表):
格式化字符串中每一个转义字符都会对应一个"输出对象",要输出的对象的数量应该与转义字符的个数一样
返回值:
返回实际输出到终端的字符数量(包括转义字符转换之后的数据)
fprintf的功能返回值与printf类似,只不过fprintf是把内容输出到stream指向的文件流中,而不是stdout
int fprintf(FILE *stream, const char *format, ...);
sprintf的功能返回值与printf类似,只不过sprintf是把内容输出到str指向的内存中,而不是stdout
int sprintf(char *str, const char *format, ...);
返回值:
返回实际输出到内存的字符数量
只不过,sprintf有一个bug,不应该被使用
str只是一个内存的起始编号,并没有指定str的可用长度,有可能会造成内存越界
======>
snprintf的功能返回值与sprintf类似,只不过snprintf中多了一个参数size,size的作用是指定str表示的空间的大小
int snprintf(char *str, size_t size, const char *format, ...);
返回值:
返回理论应该输出的字符串长度,而不是实际输出的长度
实际输出的长度就是str中字符串的长度
例子:
char buf[10] = {0};
char *str = "123456789abcdefg";
int r = snprintf(buf,10,"%s",str);
r---->16 理论应该输出的字符数量是16
if(r>10)
{
输出不完整!!
}