天天看点

2021-08-24 标准IO学习笔记标准IO前言一、为什么需要标准IO二、标准IO的函数接口总结

标准IO

文章目录

  • 标准IO
  • 前言
  • 一、为什么需要标准IO
          • "文件": 普通的文本文件和二进制文件
          • 文本文件: 无组织、无格式的文件,以字符的ASCII码等其他编码来解析的文件。
          • 二进制文件: 有特定格式的文件。
          • ​还提供了对"文件"操作的接口函数:
          • 使用标准IO的实现过程:
          • ​ 标准IO带缓存的IO,IO流,它的效率要比系统IO要高,why?
          • beacase
          • 标准IO库,会自动为每个进程,打开三个标准IO流(文件):
          • 标准输入流:FILE *stdin
          • 标准输出流: FILE *stdout
          • 标准出错:FILE *stderr
  • 二、标准IO的函数接口
    • (1)标准IO打开或关闭一个文件流
      • fopen
      • fclose:用来关闭指定的文件流
    • (2)读写流:一旦读写成功啦,光标会自动往后移n个位置(n就是你读写成功的字节数)
      • (2.1)每次一个字符读写
      • (2.2)每次一行读写
      • (2.3)直接读写 fread/ fwrite
        • fread
        • fwrite
      • (3)冲洗流 fflush,同步
    • (4)定位流
    • (5)文件出错/结束标记
    • (6)格式化IO
      • (6.1)格式化输入
  • 总结

前言

一、为什么需要标准IO

因为每个操作系统下面,对文件的管理和接口是不一样的。

linux:open/read/write/close…,struct file, struct inode

windows:winopen,winclose,…

同一个文件,在不同的操作系统下面,我们的操作文件的代码都不一样。

c语言标准委员会,就统一了文件操作的接口

–》标准IO库: 主要统一对文件操作的接口

“文件”: 普通的文本文件和二进制文件
文本文件: 无组织、无格式的文件,以字符的ASCII码等其他编码来解析的文件。

​ 如:.txt .c .h .s .cpp .java …

二进制文件: 有特定格式的文件。

​ 如:.jpg .bmp .gif .mp3 .mp4

在标准IO库,用结构体 FILE结构体来描述或表示一个文件,然后在这个结构体中创建了两个缓冲区(一段内存),一个读缓冲区,一个写缓冲区

​		FILE  
​		{
​			char *in; //指向读的缓冲区
​			char *out //指向写的缓冲区 
​		
​		};  
           

​还提供了对"文件"操作的接口函数:

fopen/fclose/fread/fwrite/fseek/…

puts/gets/fputs/fgets/scanf/printf…

使用标准IO的实现过程:

​ Your Code —>标准IO库(如:fopen/fclose…) -->系统IO -->内核–>Hardware

​ FILE 有两个缓冲区(标准IO库中开辟的一段内存)

​ *in —> 读的缓冲区

​ *out --> 写的缓冲区

​ 标准IO带缓存的IO,IO流,它的效率要比系统IO要高,why?
beacase

系统IO: read 1byte 从硬盘中读一个字节出来

​标准IO:

​ read 1byte,它会从硬盘上读一块(如:512byte)出来,放到 标准IO缓冲区。(内存条)

​ 标准IO:IO流,stream跟水流一样,流走了就没有了,读走了就没有了

​ 缓冲: 同步的问题。

​ 缓冲区中的数据,何时同步到外设上去呢?

​ 缓存区开多大呢? 标准IO缓冲区有三种类型:行缓冲、全缓冲、无缓冲

​ 行缓冲:缓冲区的数据达到一行,同步到外设上去。

​ 假设你设置一行顶多80个字节。遇到\n(称换行符,一行结束的标志)就会把缓冲区中的数据同步到外设上去

​ printf —> 行缓存

​			main()
​			{
​				printf("hello");
​				while(1);
​			
​			}
           

此时在屏幕上没有“hello”打印出来,因为没有hello后面没有‘\n’,hello一直在缓冲区中,并没有把缓冲区中的数据同步到外设上去。

​ 全缓冲: 缓冲区中数据要填满整个缓冲区,才同步到外设上去。

​ 无缓冲: 缓冲区中有一个字节,就同步到外设上去。

​ perror --> 无缓冲

标准IO库,会自动为每个进程,打开三个标准IO流(文件):
标准输入流:FILE *stdin

​ stdin 是定义在<stdio.h>中的一个全局变量,它指向标准输入设备(一般为终端或键盘)

​ scanf() <-----stdin

标准输出流: FILE *stdout

​ stdout是定义在<stdio.h>中的一个全局变量,它指向标准输出设备(控制台console,终端或屏幕)

​ printf() <----stdout

标准出错:FILE *stderr

​ stderr是定义在<stdio.h>中的一个全局变量,它指向标准出错设备(一般为终端)

​ perror() <---- stdout

二、标准IO的函数接口

(1)标准IO打开或关闭一个文件流

fopen

头文件

​ #include <stdio.h>

函数功能

​ 用来打开一个普通文件(文本文件/二进制文件)

函数原型

​ FILE *fopen(const char *pathname, const char *mode);

函数参数

​ const char *pathname //要打开的那个文件的文件名

​ const char *mode //打开文件的方式,有如下集中:

“r”: 只读打开。文件不存在,则报错; 打开后,光标在开头。

​"r+": 读写打开。文件不存在,则报错;打开后,光标在开头。

​"w": 只写打开。文件不存在,则创建; 打开后,文件内容截短(文件内容被清掉)。

​"w+": 读写打开。文件不存在,则创建; 打开后,文件内容截短(文件内容被清掉)

“a”: append 追加打开,文件不存在,则创建; 打开后,光标在末尾。文件内容不会被截短。

“a+”:读写打开。文件不存在,则创建。 原始读的位置在开头,原始写的位置在末尾

返回值

​ 成功:返回打开文件指针。 FILE *

​ 在标准IO中,FILE *代表一个打开的文件,后续标准IO库的函数都需要要到它。

​ 失败:返回NULL,同时errno被设置

fclose:用来关闭指定的文件流

头文件

​ #include <stdio.h>

函数功能

​ 用来关闭stream指定的文件流

函数原型

​ int fclose(FILE *stream);

函数参数

​ FILE *stream //要关闭的文件的FILE *

返回值

​ 成功: 返回0

​ 失败: 返回-1,同时errno被设置

(2)读写流:一旦读写成功啦,光标会自动往后移n个位置(n就是你读写成功的字节数)

​ a:每次读一个字符读写

​ fgetc/getc/getchar

​ fput/putc/putchar

​ b:每次一行读写

​ fgets/gets

​ fputs/puts

​ c:直接读写,你想要读多少个对象都可以

​ fread

​ rwrite

(2.1)每次一个字符读写

​ fgetc/getc/getchar

​ fput/putc/putchar

头文件

​ #include <stdio.h>

函数功能

​ 用来从stream指定的文件流中,读取一个字符。

函数原型

​ int fgetc(FILE *stream);

函数参数

​ FILE *stream //指定要从哪个文件流中读取字符

返回值

​ 成功: 返回读到的那个字符的ASCII码

​ 失败:返回-1,同时errno被设置。

​ get和fget一样,也是用来从stream指定的文件流中,读取一个字符。getc和fget的区别在哪里?

​ fgetc是一个函数;

​ getc可能是用用来实现的

函数原型

​ int getc(FILE *stream);

​ getcha是用来从标准输入流stdin中获取一个字符,并把读取到的字符的ASCII码返回。

函数原型

​ int getchar(void); 《==》 fgetc(stdin);

头文件

​ #include <stdio.h>

函数功能

​ 用来把c指定的字符,输出到stream指定的文件流中去,

函数原型

​ int fputc(int c, FILE *stream);

函数参数

​ int c //要输出的字符的ASCII码

​ FILE *stream //文件流,表示输出到哪个文件中去。

返回值

​ 成功:返回实际写入到文件流中的字符的ASCII码(c值)

​ 失败:返回-1,同时errno被设置。

​ putc与fputc功能与返回值 一样,只不过putc的实现可能是由宏来实现的

函数原型

​ int putc(int c, FILE *stream);

​ putchar用来把c指定的字符,输出到"标准输出流文件中"

函数原型

​ int putchar(int c); 《==》 fputc(c,stdout);

(2.2)每次一行读写

​ fgets/gets

​ fputs/puts

头文件

​ #include <stdio.h>

函数功能

​ 用来从标准输入流(stdin)获取一行字符,存储到s指向的内存空间中去。

函数原型

​ char *gets(char *s);

函数参数

​ char *s //指向的空间,用来存储从输入缓冲区获取到的多个字符

返回值

​ 成功:非空 NULL

​ 失败:返回NULL,同时errno被设置

​ 注意:

​ gets有一巨大的bug,你懂的!!!

​ gets没有考虑到s指向的空间的大小问题,存在越界的可能。 所以从今天开始,gets你们就不要用啦。

​ fgets修正了gets的这个bug

函数功能

​ 从stream所指向的文件中读取size个字节大小的数据到s指向的空间中

函数原型

​ char *fgets(char *s, int size, FILE *stream);

函数参数

​ char *s //指向的空间用来保存从文件流中读取的数据

​ int size //表示你要最多获取size个字节,size一般为s指向的

​ 空间的可用长度。

​ fgets输入结束有两种情况:

​ (1)遇到\n或文件结束

​ (2)已经读取到size-1个字节啦(后面留一个\0的位置)

​ FILE *stream //FILE *指针,表示从哪个文件流中读取数据

返回值

​ 成功:返回s的首地址

​ 失败:返回NULL,同时errno被设置

头文件

​ #include <stdio.h>

函数功能

​ fputs用来把s指向的字符串,输出到stream指定的文件流中去

函数原型

​ int fputs(const char *s, FILE *stream);

函数参数

​ const char *s //指向要输出的字符串的首地址

​ FILE *stream //表示要输出到哪个文件流中去

返回值

​ 成功:返回一个非负数

​ 失败:返回-1,同时errno被设置。

(2.3)直接读写 fread/ fwrite

fread

头文件

​ #include <stdio.h>

函数功能

​ 从stream指定的文件流中,读取nmemb个对象且每个对象size字节,读取到ptr指向的内存空间中去。

函数原型

​ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

函数参数

​ void *ptr //指向的内存空间,用来保存从文件流读取到的"数组"

​ size_t size //每个元素占的字节大小

​ size_t nmemb //要读取的n个元素

​ FILE *stream //表示要从哪个文件流中读取数据

返回值

​ 成功:返回实际读取到的元素个数 <= nmemb

​ 失败:返回-1,同时errno被设置。

fwrite

头文件

​ #include <stdio.h>

函数功能

​ 用来把ptr指向的nmemb个元素(每个元素占size字节)写入

​ 到stream指向的文件流中去。

函数原型

​ size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

函数参数

​ const void *ptr //要写入的数组的首地址

​ size_t size //每个元素所占的大小

​ size_t nmemb //元素的个数

​ FILE *stream //表示要写到哪个文件流中去

返回值

​ 成功:返回实际写入到文件流中去的元素个数, <= nmemb

​ 失败:返回-1,同时errno被设置。

(3)冲洗流 fflush,同步

头文件

​ #include <stdio.h>

函数功能

​ 将缓冲区数据冲洗到对应的设备上或者丢弃(同步到硬件设备)

​ 对输出流,fflush把写缓冲区的内容写/更新到文件中去;

​ 对输入流,fflush把读缓冲区的内容直接discards(丢弃);

​ strea为NULL,fflush把该进程所有打开的输出流文件同步

函数原型

​ int fflush(FILE *stream);

函数参数

​ FILE *stream //要同步的文件流

返回值

​ 成功:返回0

​ 失败:返回-1,同时errno被设置。

​ 练习:

​ 请分析如下程序的输出结果?

​			main()
​			{
​				printf("hello"); //把hello写到文件 stdout的 输出缓冲区
​								 //中去了,但并没有写入到外设文件中去
​				sleep(10);
​				
​				fflush(NULL); //fflsh(stdout);  //if stream 输出流,把输出缓冲区的内容写到
​												//外设文件中去 
​												//if stream 输入流,把输入缓冲区的内容丢弃,这样的话
​												//你下次读,就可以重新从外设文件中读啦
​									
​				while(1);				
​			
​			}
           

(4)定位流

​ 上面讲到的,fread/fwrite …只是说要从哪个文件,读/写多少个字节的数据并没有指定从文件流的哪个位置开始读写,标志IO库会为每个开打的文件流保存一个“文件偏移量”

​ “文件偏移量”:offset,“光标”。直接确定下一个读/写的起始位置。

​ 一般来说,每次读或写之前,先要定位流

​ 头文件

​ #include <stdio.h>

​ 函数功能

​ 定位一个文件的偏移量

​ 函数原型

​ int fseek(FILE *stream, long offset, int whence);

​ 函数参数

​ FILE *stream //文件流指针,表示你要定位哪个文件

​ long offse //偏移量,可正可负;要结合第三个参数

​ int whence //从哪里开始定位 定位的方式,有如下三种:

​ SEEK_SET:基于文件开头定位

​ 新光标位置 = 文件开头 + offset(>=0)

​ SEEK_CUR:current基于当前光标位置定位

​ 新光标位置=当前位置 + offset(可正可负)

​ SEEK_END:基于文件末尾定位

​ 新光标位置=文件末尾 + offset(可正可负)

​ 返回值

​ 成功:返回0

​ 失败:返回-1,同时errno被设置。

​ ftell返回当前光标位置离文件开头有多少字节

​ 函数原型

​ long ftell(FILE *stream);

​ rewind把文件光标,定位在文件开头

​ 函数原型

​ long ftell(FILE *stream);

​ rewind(stream) => fseek(stream, 0, SEEK_SET);

(5)文件出错/结束标记

​ 头文件

​ #include <stdio.h>

​ 函数功能

​ 判断stream指定的文件流是否结束

​ 函数原型

​ int feof(FILE *stream);

​ 函数参数

​ FILE *stream //填要判断的文件流

​ 返回值

​ 如果返回真(非0) 文件到达末尾啦

​ 如果返回假(0) 该文件还没有到达末尾。

​ 注意:

​ 标准IO库,在读到文件末尾时,会往缓冲区中填入一个EOF(二进制 11111111)

(6)格式化IO

(6.1)格式化输入

​ scanf/sscanf/fscanf

​ “格式化输入”?:按照我指定的格式来输入。

​ 头文件

​ #include <stdio.h>

​ 函数功能

​ 按照我指定的格式来输入

​ 函数原型

​ int scanf(const char *format, …);

​ 函数参数

​ const char *format,…

​ scanf可以带很多个参数,scanf的参数可以分为两类:

​ 第一个参数为第一类,格式化字符串,format string:

​ "格式化字符串"就是告诉用户怎么输入的,意思是你必须​按照它指定的格式去输入。

​ 在“格式化字符串”中有三类字符:

​ a.空白符(space tab)

​ 指示用户 你可以输入任意数量的空白符(包含0个)scanf把\n当作是输入结束

​ b.非转义字符(普通字符,除空白符和%以外的字符)精准匹配,你得原样输入

​ c.转义字符(以%开头的字符),有特殊含义:

​ %d -> [0-9]+

​ %c -> 匹配一个字符(可以输入的字符)

​ %f -> 浮点数

​ %s -> 字符串(不带空白符,scanf把空白符当作是一个分割符号)

​ 其他参数为第二类,地址列表:

​ 格式化字符串中一个转义字符会对应一个地址,把一个转移字符的输入存储到指定的地址中去,如果转移字符的个数 多余 地址个数,程序行为将是 undefined(未定义的)

​ 返回值

​ 成功匹配的变量个数!!!

​ scanf获取输入时,如何结束?scanf从stdin的读缓冲去中获取输入

​ a.该输入的都输入完了

​ sacnf(“abcd%d %cabcd”,&a,&c);

​ 用户输入:

​ abcd1234Aabcd -> 该输入的都输入啦,scanf结束

​ b.失败啦

​ scanf(“abcd%d %cabcd”,&a,&c);

​ 用户输入:

​ ABCD-》scanf停止匹配啦

​ 例子:

​ int r;

​ int a;

​ char c;

​ r = scanf(“abcd%d %c”,&a,&c);

​ 假设用户入:

​ ABCD123 A

​ a=?

​ c=?

​ r=?

不匹配所以a与c不是123与A ,r=0;

​ r = scanf("%d %c",&a,&c);

​ 加入用户输入:

​ 123B

​ a=123

​ c=B

​ r=2

​sscanf它的功能与返回值 ,和scanf一样的,只不过sscanf的输入来源不是stdin,而str指向的字符串。

sscanf的参数有三类:

​ 1.str是输入来源字符串

​ 2.format是格式化字符串

​ 3.其他参数为地址列表

​函数原型

​ int sscanf(const char *str, const char *format, …);

​ 例子:

​ (1)char *str = “123456sdadwa”

​ int r;

​ int a;

​ char c;

​ r = sscanf(str, “%d %c”, &a,&c);

​ a=?

​ c=?

​ r=?

​ (2)

​ char *s = “123”;

​ int a;

​ sscanf(s,"%d",&a);

​ fscanf它的功能和返回值,与scanf一样,只不过,fscanf它的输入

​ 来源不是stdin,而stream指定的文件流,所以fscanf的参数,分为三类:

​ 1.第一个参数FILE *,指定输入来源

​ 2.第二个参数format,格式化字符串,与scanf是一样

​ 3.其他参数为 地址列表

​ 函数原型

​ int fscanf(FILE *stream, const char *format, …);

​ scanf(format,…) <==> fscanf(stdin,format,…)

​ (6.2)格式化输出

​ printf/sprinft/snprintf/fprintf

​ “格式化输出”?:按照我指定的格式去输出。

​ 头文件

​ #include <stdio.h>

​ 函数功能

​ 按照指定的格式去输出

​ 函数原型

​ int printf(const char *format, …);

​ 函数参数

​ const char *format, …

​ printf可以带多个参数 ,这么多参数,可以分为两类:

​ 第一个参数为第一类 格式化字符串,就是告诉你怎么输出的

​ 格式化输出字符串有两类:

​ a.转义字符:以%开头的字符

​ %d -> 按十进制有符号整数输出

​ %u

​ %ld

​ %lu

​ %f

​ %c ->输出字符的形状

​ %s ->把后面的地址,字符串去输出 直到遇到\0

​ …

​ b.非转义字符

​ 你得原样输出

​ 其他参数为第二类,要输出得变量或对象列表

​ 要输出得变量或对象个数 应该与转义字符的个数一致

​ 返回值

​ 实际打印的字符个数

​ 例子:

​ int a = 123;

​ char c = ‘A’;

​ int r;

​ r = printf(“a = %d c = %c”,a,c);

​ r =?

​ fprintf它的功能和printf一样的,只不过fprintf

​ 输出不是输出到stdout,而是输出到stream指定的

​ 文件中。所以fprintf它的参数,可以分为三部分:

​ 第一个参数为 FILE *,指定要输出到哪个文件中去

​ 第二个参数 format,格式化字符串

​ 其他参数,为要输出的变量或对象列表

​ 函数原型

​ int fprintf(FILE *stream, const char *format, …);

​ 返回值 :

​ 为实际输出到文件中的字符个数

​ printf(format,…) <==> fprintf(stdout,format,…);

​ sprintf它的功能和printf一样,只不过,sprintf

​ 输出不是输出到stdout,而是输出到str指定的内存中去。

​ 所以sprintf参数,可以分为三部分:

​ 第一个参数为char *,内存地址,指定输出字符串的输出位置

​ 第二个参数为format,格式化字符串

​ 其他参数,为实际输出的变量或对象列表

​ 函数原型

​ int sprintf(char *str, const char *format, …);

​ 返回值 :

​ 为实际输出到内存中去的字符个数。

​ 例子:

​ char filename[512];

​ char *file =“xxx.mp3”;

​ char *path ="/home/china";

​ sprintf(filename,"%s/%s",path,file);

​注意:但是,sprintf有一个bug

​ str只是指定了一个内存的起始地址,并没有限定它的内存范围,if输出字符串的长度 > str指向的内存的范围,有越界的风险。

​ =》内存的非法访问

​ so,为了解决sprintf的这个bug,才有了 下面的snprintf ,snprintf中size指定str指向的那段内存空间的最大长度,也就是说,格式化的输出顶多输出size-1个字符到str 指向的空间中去。

​ 函数原型

​ int snprintf(char *str, size_t size, const char *format, …);

​ 例子:

​ char s[8];

​ char *str = “1234567890”;

​ int r;

​ r = snprintf(s, 8, “%s”,str);

​ r =

​ printf("%s",s);

​ 注意:

​ snprintf它的返回值 是应该输出的字符串长度,而不是实际输出到内存中的长度。

总结

​1.标准IO库是什么东西? 为什么需要它?它的实现原理是什么?

2. 标准IO与系统IO之间的关系?标准IO中用什么东西来表示一个文件?

​3. 为什么把标准IO称为流?为什么说标准IO的效率要系统IO高?

4.标准IO只能操作 “普通文件”!!!!

继续阅读