怎么允许宏(excel如何允许宏)
2329
2022-05-29
GitHub: https://github.com/storagezhang
Emai: debugzhang@163.com
本文为《UNIX 环境高级编程》第 5 章学习笔记
大多数 UNIX 应用程序都使用标准 I/O 库。
本章说明了该库提供的很多函数以及某些实现细节和效率方面的考虑。
标准 I/O 库使用了缓冲技术,而它正是产生很多问题、引起许多混淆的部分。
5.2 流和 FILE 对象
对于标准 I/O 库,它们的操作是围绕流(stream)进行的。当用标准 I/O 库打开或创建一个文件时,我们已使一个流与一个文件相关联。
对于 ASCII 字符集,一个字符用一个字节表示。对于国际字符集,一个字符可用多个字节表示。标准 I/O 文件流可用于单字节或多字节(“宽”)字符集。
流的定向(stream’s orientation)决定了所读、写的字符是单字节还是多字节的。
当一个流最初被创建时,它并没有定向。
如若在未定向的流上使用一个单字节 I/O 函数(
若在未定向的流上使用一个单字节 I/O 函数,则将该流的定向设为字节定向的。
只有两个函数可改变流的定向。
freopen 函数清除一个流的定向。
fwide 函数可用于设置流的定向。
#include
参数:
mode:
若 mode 值为负,fwide 将试图使指定的流是字节定向的。
若 mode 值为正,fwide 将试图使指定的流是宽定向的。
若 mode 值为 0,fwide 将不试图设置流的定向,但返回标识该流定向的值。
返回值:
若流是宽定向的,返回正值。
若流是字节定向的,返回负值。
若流是未定向的,返回 0。
注意:
fwide 并不改变已定向流的定向。
fwide 无出错返回。
在调用前先清除 errno,从返回时检查 errno 的值。
当打开一个流时,标准 I/O 函数 fopen 返回一个指向 FILE 对象的指针。该对象通常是一个结构,它包含了标准 I/O 库为管理该流需要的所有信息,包括用于实际 I/O 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。
5.3 标准输入、标准输出和标准错误
对一个进程预定义了 3 个流,并且这 3 个流可以自动地被进程使用(
标准输入:文件指针 stdin ——文件描述符 STDIN_FILENO
标准输出:文件指针 stdout ——文件描述符 STDOUT_FILENO
标准错误:文件指针 stderr ——文件描述符 STDERR_FILENO
5.4 缓冲
标准 I/O 库提供缓冲的目的是尽可能减少使用 read 和 write 调用的次数。它也对每个 I/O 流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。
标准 I/O 流提供 3 种类型的缓冲:
全缓冲
在填满标准 I/O 缓冲区后才进行实际 I/O 操作。对于驻留在磁盘上的文件通常是由标准 I/O 库实施全缓冲的。在一个流上执行第一次 I/O 操作时,相关标准 I/O 函数通常调用 malloc 获得需使用的缓冲区。
术语冲洗说明标准 I/O 缓冲区的写操作。缓冲区可由标准 I/O 例程自动地冲洗,或者可以调用函数 fflush 冲洗一个流。
行缓冲
当在输入和输出中遇到换行符时,标准 I/O 库执行 I/O 操作。这允许我们一次输出一个字符(用标准 I/O 函数 fputc),但只有在写了一行之后才进行实际 I/O 操作。当流涉及一个终端时(如标注输入和标准输出),通常使用行缓冲。
行缓冲的限制:
因为标准 I/O 库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行 I/O 操作。
任何时候只要通过标准 I/O 库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流得到输入数据,那么就会冲洗所有行缓冲输出流。
不带缓冲
标准 I/O 库不对字符进行缓冲存储。
标准错误流 stderr 通常是不带缓冲的,这就使得出错信息可以尽快显示除了,而不管它们是否含有一个换行符。
ISO C 要求下列缓冲特征:
当且仅当标准输入和标准输出并不指向交互式设备时,它们才是全缓冲的。
标准错误绝不会是全缓冲的。
很多系统默认使用下列类型的缓冲:
标准错误是不带缓冲的。
若是指向终端设备的流,则是行缓冲的;否则是全缓冲的。
对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个更改缓冲类型:
#include
参数:
fp:被打开的文件对象的指针
buf:为了带缓冲进行 I/O,buf 必须指向一个长度为 BUFSIZ 的缓冲区(该常量定义在
如果 buf 为 NULL,关闭缓冲。
如果 buf 非 NULL,通常设定该流为全缓冲的。但是如果该流与一个设备终端相关,那么某些系统也可将其设置为行缓冲的。
setvbuf 中,如果 buf 为 NULL 而该流是带缓冲的,标准 I/O 库将自动的为该流分配适当长度的缓冲区。
mode:缓冲类型
_IOFBF:全缓冲
_IOLBF:行缓冲
_IONBF:不带缓冲
size:缓冲区的长度
返回值:
若成功,返回 0。
若出错,返回非 0。
任何时候,我们都可以强制冲洗一个流:
#include
返回值:
若成功,返回 0。
若出错,返回 EOF。
此函数使该流所有未写的数据都被传送至内核。
作为一种特殊情形,如若 fp 是 NULL,则此函数将导致所有输出流被冲洗。
冲洗是双向的:
输入流 —> 用户缓冲区。
输出流 —> 内核 —> 磁盘或者终端。
冲洗并不是立即写到磁盘文件中,冲洗只是负责数据传到内核。
5.5 打开流
下列 3 个函数打开一个标准 I/O 流。
#include
区别:
fopen 函数打开路径名为 pathname 的一个指定的文件。
freopen 函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若流已经定向,则使用 freopen 清楚该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
fdopen 函数取一个已有的文件描述符,并使一个标准的 I/O 流与该描述符相结合。此函数常用于由创建管道和网络通信信道函数返回的描述符。因为这些特殊类型的文件不能用标准 I/O 函数 fopen 打开,所以我们必须先调用设备专用函数以获得一个文件描述符,然后用 fdopen 使一个标准 I/O 流与该描述符相结合。
参数:
type:指定对该 I/O 流的、读写方式,ISO C 规定该参数可以有 15 种不同的值
使用字符 b 作为 type 的一部分,使得标准 I/O 系统可以区分文本和二进制文件。因为 UNIX 内核并不对着两种文件进行区分,所以在 UNIX 系统环境下指定字符 b 作为 type 的一部分实际上并无作用。
对于 fdopen,因为该描述符已被打开,所以 fdopen 为写而打开并不截断该文件,标准 I/O 追加写方式也不能用于创建该文件。
当用追加写类型打开一个文件后,每次写都将数据写到文件的当前尾端处。如果有多个进程用标准 I/O 追加写方式打开同一文件,那么来自每个进程的数据都将正确地写到文件中。
打开一个流的 6 中不同的的方式:
返回值:
若成功,返回文件指针。
若出错,返回 NULL。
调用 flose 关闭一个打开的流:
#include
返回值:
若成功,返回 0。
若出错,返回 EOF。
在该文件被关闭之前,冲洗缓冲中的输出数据,缓冲区中的任何输入数据被丢弃,如果标准 I/O 库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
当一个进程正常终止时(直接调用 exit 函数,或从 main 函数返回),所有带未写缓冲数据的标准 I/O 流都被冲洗,所有打开的标准 I/O 流都被关闭。
5.6 读和写流
一旦打开了流,可在 3 中不同类型的非格式化 I/O 中进行选择,对其进行读、写操作:
每次一个字符的 I/O。
一次读或写一个字符,如果流是带缓冲的,则标准 I/O 函数处理所有缓冲。
每次一行的 I/O。
fgets 和 fputs
直接 I/O。
fread 和 fwrite
每次 I/O 操作读或写某种数量的对象,而每个对象具有指定的长度。
输入函数
以下 3 个函数可用于一次读一个字符:
#include
区别:
函数 getchar 等同于 getc(stdin)。
getc 可被实现为宏,而 fgetc 不能实现为宏。
返回值:
若成功,返回下一个字符。
若已到达文件尾端或出错,返回 EOF。
为了区分出错还是到到文件尾端,必须调用 ferror 或 feof:
#include
返回值:
若条件为真,返回非 0(真);
否则,返回 0(假)。
在大多数实现中,为每个流在 FILE 对象中维护了两个标志:
出错标志;
文件结束标志。
调用 clearerr 可以清楚这两个标志。
从流中读取数据以后,可以调用 ungetc 将字符再押送回流中:
#include
返回值:
若成功,返回 c。
若出错,返回 EOF。
回送的字符,不一定是上次读到的字符。
不能回送 EOF,但是当文件已经到达尾端时,仍可以回送一个字符,下次读将返回该字符,再读则返回 EOF。能这样做的原因是,一次成功的 ungetc 调用会清除该流的文件结束标志。
用 ungetc 压送回字符时,并没有将它们写到底层文件中或设备上,只是将它们写回标准 I/O 库的流缓冲区中。
输出函数
对应于上述的每个输入函数都有一个输出函数:
#include
区别:
putchar(c) 等同于 putc(c, stdout。
putc 可被实现为宏,而 fputc 不能实现为宏。
返回值:
若成功,返回 c。
若出错,返回 EOF。
5.7 每次一行 I/O
下面 2 个函数提供每次输入一行的功能:
#include
区别:
gets 从标准输入读,而 fgets 从指定的流读。
gets 不将换行符存入缓冲区中,而 fgets 存入。
gets 不推荐使用,原因是调用者在使用 gets 时不能指定缓冲区的长度,可能造成缓冲区溢出。
返回值:
若成功,返回 buf。
若已到达文件尾端或出错,返回 EOF。
这两个函数都指定了缓冲区的地址,读入的行将送入其中。
fputs 和 puts 提供每次输出一行的功能:
#include
区别:
fputs 将一个以 null 字节终止的字符串写到指定的流,尾端的终止符 null 不写出。
这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非 null 字节。
puts 将一个以 null 字节终止的字符串写到标准输出,终止符不写出。但是 puts 随后又将一个换行符写到标准输出。
同样避免使用,以免需要记住它在最后是否添加了一个换行符。
返回值:
若成功,返回非负值。
若出错,返回 EOF。
5.8 标准 I/O 的效率
使用标准 I/O 例程的一个优点是无需考虑缓冲及最佳 I/O 长度的选择。
使用每次一行的 I/O 版本的速度大约是每次一个字符版本速度的两倍。
标准 I/O 库与直接调用 read 和 write 函数相比并不慢很多。对于大多数比较复杂的应用程序,最主要的用户 CPU 时间是由应用本身的各种处理消耗的,而不是由标准 I/O 例程消耗的。
5.9 二进制 I/O
下列两个函数执行二进制 I/O 操作:
#include
参数:
ptr:存放二进制数据对象的缓冲区地址
size:单个二进制数据对象的字节数(比如一个 struct 的大小)
nobj:二进制数据对象的数量
fp:打开的文件对象指针
返回值:读或写的对象数
对于读,如果出错或到达文件尾端,则此数字可以少于 nobj。在这种情况下,应该调用 ferror 或 feof 判断究竟是哪一种情况。
对于写,如果返回值少于所要求的 nobj,则出错。
使用二进制 I/O 的基本问题是,它只能用于读在同一系统上已写的数据。
现在,很多异构系统通过网络相互连接起来,在一个系统上写的数据,要在另一个系统上进行处理。在这种环境下,这两个函数可能就不能正常工作,其原因是:
在一个结构中,统一成员的偏移量可能随编译程序和系统的不同而不同(由于不同的对齐要求)。
用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。
5.10 定位流
有 3 种方法定位标准 I/O 流:
ftell 和 fseek 函数。
V7
假定文件的位置可以存放在一个长整型中。
ftello 和 fseeko 函数。
Single UNIX Specification
使用 off_t 数据类型代替了长整型。
fgetpos 和 fsetpos 函数。
ISO C
使用一个抽象数据类型 fpos_t 记录文件的位置,这种数据类型可以根据需要定义为一个足够大的数,用以记录文件位置。
需要移植到非 UNIX 系统上运行的应用程序应当使用 fgetpos 和 fsetpos:
#include
参数:
offset:偏移量。
whence:偏移量的解释方式
SEEK_SET:从文件的起始位置开始
SEEK_CUR:从文件的当前位置开始
SEEK_END:从文件的尾端开始
除了偏移量的类型是 off_t 而非 long 以外,ftello 函数与 ftell 相同,fseeko 函数与 fseek 相同:
#include
fgetpos 将文件位置指示器的当前值存入由 pos 指向的对象中。在以后调用 fsetpos 时,可以使用此值将流重新定位至该位置:
#include
5.11 格式化 I/O
格式化输出
格式化输出是由 5 个 printf 函数来处理的:
#include
区别:
printf 将格式化数据写到标准输出
fprintf 写至指定的流
dprintf 写至指定的文件描述符
不需要调用fopen 将文件描述符转换为文件指针
sprintf 将格式化的字符送入数组 buf 中,并在该数组的尾端自动加一个 null 字节,但该字符不包括在返回值中
sprintf 函数可能会造成由 buf 指向的缓冲区的溢出,调用者有责任确保该缓冲区足够大
snprintf 中缓冲区长度是一个显示参数,超过缓冲区尾端写的所有字符都被丢弃。与 sprintf 相同,该返回值不包括结尾的 null 字节。
参数:
format, ...:输出的格式化字符串,一个转换说明有 4 个可选择的部分:
%[flags][fldwidth][precision][lenmodifier]convtype
flags
fldwidth:说明最小字段宽度,转换后参数字符如果小于宽度,则多余字符位置用空格填充。
字段宽度是一个非负十进制数,或者是一个星号 *
precision:说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后最大字节数。
精度是一个点 .,后跟随一个可选的非负十进制数或者一个星号 *
lenmodifier:说明参数长度:
convtype 不是可选的,它控制如何解释参数:
buf:一个缓冲区的指针,格式化输出到该缓冲区中
下列 5 种 printf 族的变体类似于上面的 5 种,但是可变参数表(…)替换成了 arg:
#include
格式化输入
执行格式化输入处理的是 3 个 scanf 函数:
#include
scanf 族用于分析输入字符串,并将字符序列转换成指定类型的变量。在格式之后的各参数包含了变量的地址,用转换结构对这些变量赋值。
一个转换说明有 3 个可选择的部分:
%[*][fldwidth][m][lenmodifier]convtype:
*:用于抑制转换,按照转换说明的其余部分对输入进行转换,但转换结果并不存放在参数中,而是抛弃。
fldwidth:说明最大宽度,即最大字符数
m:赋值分配符
可以用于 %c、%s 以及 %[转换符],迫使内存缓冲区分配空间以接纳转换字符串
相关的参数必须是指针地址,分配的缓冲区地址必须复制给该指针
如果调用成功,该缓冲区不再使用时,由调用者负责通过调用 free 函数来释放该缓冲区
lenmodifier:说明要转换结果赋值的参数大小
convtype:与 printf 中相比,输入中带符号的可赋予无符号类型
与 printf 族相同,scanf 族也使用由
#include
5.12 实现细节
每个标准 I/O 流都有一个与其相关联的文件描述符,可以对一个流调用 fileno 函数以获得其描述符:
#include
fileno 不是 ISO C 标准部分,而是 POSIX.1 支持的扩展。
如果要调用 dup 或 fcntl 等函数,需要此函数。
5.13 临时文件
ISO C 标准 I/O 库提供了两个函数以帮助创建临时文件:
#include
参数:
ptr:指向存放临时文件名的缓冲区的指针
若 ptr 为 NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。后续调用 tmpnam 时,会重写该静态区。
若 ptr 不是 NULL,则认为它应该是指向长度至少是 L_tmpnam(
tmpnam 函数产生一个与现有文件名不同的一个有效路径名字符串。每次调用它时,都产生一个不同的路径名,最多调用次数是 TMP_MAX(
tmpfile 创建一个临时二进制文件(类型 wb+),在关闭该文件或程序结束时将自动删除这种文件。
tmpfile 函数经常使用的标准 UNIX 技术是先调用 tmpnam 产生一个唯一的路径名,然后用该路径名创建一个文件,并立即 unlink 它。
对一个文件解出链接并不删除其内容,关闭该文件时才删除其内容。而关闭文件可以是显式的,也可以在程序终止时自动进行。
注意:UNIX 对二进制文件不进行特殊区分。
执行这两个函数时,会出现:
warning: the use of `tmpnam' is dangerous, better use `mkstemp'
原因是 tmpnam 和 tmpfile 有一个缺点:
在返回唯一的路径名和用该名字创建文件之间存在一个时间窗口,在这个时间窗口中,另一进程可以用相同的名字创建文件。
Single UNIX Specification 为处理临时文件定义了另外两个函数,即 mkdtemp 和 mkstemp,它们是 XSI 的扩展部分:
#include
区别:
mkdtemp 函数创建了一个目录,该目录有一个唯一的名字。
创建的目录使用访问权限位集:S_IRUSR | S_IWUSR | S_IXUSR。
调用进程的文件模式创建屏蔽字可以进一步限制这些权限。
mkstemp 函数创建了一个文件,该文件有一个唯一的名字。
返回的文件描述符以读写方式打开。
使用访问权限位:S_IRUSR | S_IWUSR。
参数:
template:字符串
这个字符串是后 6 位设置为 XXXXXX 的路径名。
函数将这些占位符替换成不同的字符来构建一个唯一的路径名。
如果成功的话,这两个函数将修改 template 字符串反应临时文件的名字。
5.14 内存流
标准 I/O 库把数据缓存在内存中,因此每次一字符和每次一行的 I/O 更有效。我们也可以通过调用 setbuf 或 setvbuf 函数让 I/O 库使用我们自己的缓冲区。
内存流:
SUSv4 中支持。
一种标准 I/O 流,虽然仍使用 FILE 指针进行访问,但其实并没有底层文件。
所有的 I/O 都是通过在缓冲区与主存直接来回传送字节来完成的。
即便这些流看起来像文件流,它们的某些特征使其更适用于字符串操作。
有 3 个函数可用于内存流的创建,第一个是 fmemopen 函数:
#include
参数:
buf:指向缓冲区的开始位置
size:指定了缓冲区大小的字节数
如果 buf 参数为 NULL,函数分配 size 字节数的缓冲区,并当流关闭时释放缓冲区。
type:控制如何使用流
以追加方式打开内存流时,如果缓冲区中不存在 null 字节,则当前位置设为缓冲区结尾的后一个字节。
如果 buf 是 NULL,则打开流进行读或者写都没有任何意义。
因为此时缓冲区是通过 fmemopen 分配的,没有办法找到缓冲区的地址。
追加 null 需要满足两个条件:
增加流缓冲区中数据量。
调用 fclose、fflush、fseek、fseeko、fsetpos 。
用于创建内存流的其他两个函数分别是 open_memstream 和 open_wmemstream:
#include
区别:
open_memstream 函数创建的流是面向字节的
open_wmemstream 函数创建的流是面向宽字节的
与 fmemopen 相比:
创建的流只能写打开;
不能指定自己的缓冲区,但可以分别通过 bufp 和 sizep 参数访问缓冲区地址和大小;
缓冲区地址和长度只有在调用 fclose 或 fflush 才有效;
这些值只有在下一次流写入或调用 fclose 前才有效,因为缓冲区可以增长,可能需要重新分配,如果出现这种情况,缓冲区的内存地址在下一次调用 fclose 或 fflush 会改变;
关闭流后需要自行释放缓冲区;
对流添加字节会增加缓冲区大小。
参数:
bufp:指向缓冲区地址的指针(用于返回缓冲区地址)
sizep:指向缓冲区大小的指针(用于返回缓冲区大小)
因为避免了缓冲区溢出,内存流非常适用于创建字符串。因为内存流只访问主存,不访问磁盘上的文件,所以对于把标准 I/O 流作为参数用于临时文件的函数来说,会有很大的性能提升。
5.15 标准 I/O 的替代软件
标准 I/O 库的一个不足之处是效率不高,这与它需要复制的数据量有关:
当使用每次一行的函数 fgets 和 fputs 时,通常需要复制两次数据:
在内核和标准 I/O 缓冲区之间(当调用 read 和 write 时)
在标准 I/O 缓冲区和用户程序中的行缓冲区之间
Linux Unix
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。