2018-2019-1 20189221 《从问题到程序》第 7 周学习总结

2018-2019-1 20189221 《从问题到程序》第 7 周学习总结

第 8 章 文件和输入输出

文件的一般性概念及 C 程序里的文件使用,介绍输入输出格式控制的细节。

8.1文件的概念

由于目前计算机内存器件的特性,存于其中的数据在关机后将立刻消失。由于这些原因,为了持续性地保存数据,就必须借助外存设备,如磁盘、磁带等。
在目前的计算机系统里,外存信息都通过目录和文件方式组织起来,构成操作系统管理下的外存信息结构。目录可看作是子目录和文件的集合,文件是封装起的一组数据。每个目录或文件有名字,可以通过名字被操作和使用。程序执行中与外存打交道,主要就是访问使用作为外存信息实体的文件。
C 语言本身没有专用于输入输出的语言结构。为了提供一种统一标准,ANSI C 把文件和输入输出功能作为标准库的一部分,以提高程序的可移植性。标准库将所有与输入输出有关的机制都统一到文件的概念中,定义了一些与输入输出有关的数据结构,提供了一组与输入输出有关的操作。

8.1.1流和文件指针

标准库对文件输入输出采用的概念称为流为能与这种对象交换信息,就需要建立与它们联系,流就是这种联系。这种建立联系(创建流)的动作被形象地称为打开文件,文件被打开后就可以进行操作了。当一个文件不再需要时,程序可以切断与它的联系,撤消有关的流,这称为关闭文件。

程序与文件的连接:

标准库的流分为两类:正文流(或称为字符流)和二进制流。
正文流把文件看作行的序列,每行包含 0 个或多个字符,一行的后有换行符号'\n'。正文流适合一般输出和输入,包括与人有关的输入输出。
二进制流用于把内存数据按内部形式直接存储入文件。二进制流操作保证,在写入文件后再以同样方式读回,信息的形式和内容都不改变。二进制流主要用于程序内部数据的保存和重新装入使用,其操作过程中不做信息转换,在保存或装入大批数据时有速度优势,但这种保存形式不适合人阅读。

标准库提供了一套流操作函数,包括流的创建(打开文件)、撤消(关闭文件),对流的读写(实际上是通过流对文件的读 和写),以及一些辅助函数。

C 程序启动时自动创建三个流(建立三个文件指针并指定值):标准输入流(指针名为 stdin)、标准输出流(stdout)和标准错误流(stderr)。stdin通常与操作系统的标准输入连接,stdout与操作系统的标准输出连接,stderr通常直接与显示器连接,这说明stderr不能重新定向。前面程序所用的标准输入输出操作都是对这些流进行的。

8.1.2缓冲式输入输出

标准库定义的输入输出称为缓冲式输入输出,这是一种常用的输入输出方式。由于外存(磁盘、磁带等)速度较慢,一般采用成块传递方式,一次传递一批数据。而程序里对数据的使用则往往不是这样。为了缓和两者间在数据提供和使用方面的差异,人们提出开辟一块存储区(称为数据缓冲区,简称缓冲区),作为文件与使用数据的程序之间的传递媒介。

输入流操作:

文件中的数据将以成块方式复制到缓冲区;程序需要读入数据时就由缓冲区读取,不必每次访问外存,这样可以大大提高程序的工作效率。如果程序要求读取数据时缓冲区的数据已用完,系统就会自动执行一个内部操作,从文件里取得一批数据,将缓冲区重新填满。此后程序又可以按照正常方式读取数据了。缓冲方式可以较好地弥合程序与外存在数据操作方式和速度方面的差距。输出操作的处理方式与此类似,只是方向相反:每当缓冲区装满后自动执行一次对文件的成块写操作。

8.2文件的使用

文件的打开方式:
"r" 以读方式打开文件,如找不到文件,则打开失败
"w" 以写方式打开,如文件已有则丢弃原有内容
"a" 添加方式打开或创建文件,从文件已有部分后面接着写
"r+" 读更新方式,可以对文件读或者写
"w+" 写更新方式,可以写或者读,如文件存在则先丢弃原有内容
"a+" 添加并可读方式,从文件尾接着写

1)当文件打开操作不能正常完成时,函数fopen返回空指针值。由于文件打开操作是与程序外部打交道,操作能否完成依赖于程序运行的环境。所以,在文件打开操作之后必须检查函数的返回值,以确保后续操作的有效性。显然,对空的文件指针操作不会有任何意义。
2)上面例子中的写法都是以正文文件方式打开文件。若需要以二进制方式打开文件,就需要在模式串中加字符b说明。例如,"rb"、"wb+"、"a+b"分别表示以二进制读方式、二进制写更新方式、二进制添加并可读方式打开文件。
3)对于以读写方式打开的文件,在读操作和写操作之间切换时,必须做文件重新定位,并需要调用函数fflush 刷新流的缓冲区。

程序cat:

直接输入输出函数

标准库提供了二进制流和直接输入输出函数。原型分别是:

size_t fread(void *pointer, size_t size,size_t num, FILE *stream); 
size_t fwrite(const void *pointer, size_t size,size_t num, FILE *stream); 

8.3标准流输入输出与格式控制

8.3.1 行式输入和输出

对于标准输入和标准输出流也有一对行式输入和输出函数。它们的原型分别是:

char *gets(char *s); 
int puts(const char *s); 

函数gets的一个问题无法防止输入数组的可能越界问题。与前面一般性的文件行式输入函数相比,gets缺少长度控制参数,因此使用中就无法控制对字符数组的写入长度。于输入来自程序外部,我们根本无法预料外部提供的一行到底有多长,因此有安全问题。

如果需要从标准输入流按行输入,更安全的是用:

fgets(s, 256, stdin); 

8.3.2 输入格式控制

函数scanf的原型,其原型如下:

int scanf(const char *format, ...); 

空白字符 (包括空格、制表符、换行符):它们将被忽略,但也会导致scanf抛弃掉读入中遇到的所有空白字符,直至遇到非空白字符。
普通字符 (遇到除字符 % 外的非空白字符):scanf 将它与输入流中的下一非空白字符匹配,字符相同则匹配成功。这里有可能出现匹配失败的情况。
转换描述(由字符 % 开始的若干个字符组成):% 字符之后可以有:一个星号*,表示只进行匹配和转换,不向参数赋值;一个字段长度描述,表示这个转换应处理的输入字符个数;一个对赋值目标的长度指示字符(字母h、l或L);后是转换字符本身。

转换字符 要求的输入数据形式 要求的参数
d 十进制形式的整数 int*
i 整数,可以是十进制表示(起始数字非 0),八进制表示(由数字
0开始),或者十六进制表示(由0x或0X开始) int*
o 八进制表示的整数,可以有或者没有先导的数字字符0 int*
u 无符号十进制整数 unsigned
x 十六进制表示的整数,可以有或者没有先导的0x或0X int

c 字符。若指定输入宽度,这个转换可以将多个字符输入到字符数组里。读字符过程中不跳过空白字符,读入多个字符时不加'\0' char*
s 读入一个非空白字符序列,可以有长度限制。读入后在字符数组的后加空字符'\0'(做成字符串)。作为参数的字符数组应当足够存放读入的所有字符和结尾的'\0' char*
e,f,g 符合 C 语言规定形式的浮点数 float*
p 指针值,其形式与用printf("%p", ...)输出形式的一样。这使人可以把通过printf输出的指针值重新读回程序里。 void*
n 向对应参数中写入本次函数调用执行到此已经读的字符个数。处理这一“转换描述”时不读入字符,也不计入转换的项数 int*
[...] 与输入流里由方括号中列出的字符形成的 长字符序列匹配,将这些字符写入由参数确定的字符数组里,并附加一个'\0'。
可用[]...]的形式表示被匹配字符串里也包含“]” char*
[^...] 与输入流里不包括方括号中列出的任何字符的 长字符序列匹配,将这些字符写入由参数确定的字符数组里并附加一个'\0'。 可用[^]...]的形式表示被匹配字符串里不能包括“]” char*
% 与输入流中的字符%匹配,没有赋值操作 --

8.3.3 输出格式控制

printf根据格式描述串format完成输出转换,把生成的输出字符序列送到标准输出流。操作中出错时函数返回负值;没出错时返回本调用执行中输出的总字符数。
format应是一个字符串。按函数printf的看法,该串的内容分为两类:一类是普通字符,printf遇到普通字符时将它们直接送到输出流。另一类是由%开头的转换描述,这种描述由连续的若干个字符组成,它们并不输出,而是作为处理函数实参的指示。printf 根据格式串中的转换描述顺序处理函数的其他实参。处理一个参数得到的字符序列称为一个输出字段,各输出字段插入对应转换描述在格式串里的位置,形成整个输出序列。

格式串里的转换描述总以%开始,到一个转换字符为止,两者之间顺序地可以有下面几种成分(几个字符,也可以没有):

  1. 标志字符。下面几个字符可以按任意顺序出现,可以有一个或者多个:

'-' 将转换结果在字段范围内由 左端开始输出(居左输出)
'+' 在数值的前面总输出一个正号或负号
空格 如果转换后产生的第一个字符不是正负号,就首先输出一个空格
0 用于数值输出。如果输出不能填满整个字段,那么在有效输出之前填满0
'#' 指定另一种规定形式。对转换字符o,数值之前总加0;对转换字符x 和X,非0 结果之前总加0x或0X;对于转换字符e、E、f、g、G,输出中总包含小数点;对于g和G,不去掉 后的那些0

  1. 一个十进制整数。表示本输出字段的 小宽度,要求转换结果至少占这么多个字符的宽度,如果需要可以更宽。如果得到的输出序列不够宽,在其左边(或者右边,如果要求左对齐的话)填满空格。对于数值输出,当有0标志时在数字序列的左边填满0。

  2. 一个圆点及另一个表示精度的十进制整数。对于字符串参数,这个数表示应输出的 大字符个数;对e、E、f表示小数点之后的数字位数;对g、G转换表示有效数字位数;对于整数表示要求输出的 小数字个数,如果数字个数不够就在左边添0。

  3. 目标长度修饰字符h、l或者L。字符h和l用于整型参数,h说明相应的参数是short 或者unsigned short类型;l说明对应参数是long或者unsigned long类型。字符L 用于说明对应参数为长双精度类型。对于上述这些情况,转换描述中都必须用长度修饰字符指明对应参数的表示长度(类型特征)。

各转换字符的详细说明:

d,i 带符号的十进制形式整数 int
o 无符号八进制表示的整数,没有先导的0 int
x,X 无符号十六进制整数,没有先导的0x或0X。在用转换字符x时,十以上数字用abcdef表示;用X时这些数字用ABCDEF表示 int
u 无符号十进制整数 int
c 输出一个字符,将参数转换为unsigned char输出 int
s 输出一个字符序列,从参数所指位置开始直到遇到字符'\0',或者达到字段的指定宽度为止 char*
f 一般实数形式,形式为[-]mmm.ddd,其中小数点后面数字位数由精度描述确定,默认值是6。精度为0时不输出小数点 double
e,E 科学计数形式,形式为[-]m.ddde±xx或[-]m.dddE±xx,其中小数点后面的位数由精度描述确定,默认为6位。精度为0时不输出小数点 double
g,G 灵活形式。当指数小于-4或大于等于精度描述时用%e或%E的形式输出,否则用%f的形式输出。末尾的0或小数点不输出 double
p 输出指针的值,采用某种由具体实现确定的形式 void*
n 把这次函数执行到这里已经输出的字符个数写到参数中。处理这个
“转换描述”时不产生输出 int*
% 输出字符%,不做任何转换 --

8.3.4 以字符串作为格式化输入输出对象

C 语言标准库确实提供了两个以字符串为对象的格式化输入输出函数。其中输入函数 sscanf的功能是把一个字符串作为读入对象,从中读入、分解、完成指定转换,并将转换结果赋给指定变量。字符串输出函数sprintf实现另一方向的转换,将生成的输出字符序列存入指定的字符数组,并在有效字符序列 后放入表示字符串结束的'\0',做成字符串的形式。这两个函数的原型与scanf和printf类似,只是多了一个字符指针参数,用于表示特定的字符串:

8.3.5 标准错误流

标准错误流(stderr):
要改造程序cat,只需修改关于错误信息输出的一个语句,把它改为:
fprintf(stderr, "%s, can't open in file: %s\n", name, *argv);

猜你喜欢

转载自www.cnblogs.com/gdman/p/10029553.html