《C编程.中级C》5.流I/O

简介

stdio.h标题声明的执行的输入和输出到文件和设备如控制台功能的广泛的分类。它是最早出现在C库中的标题之一。它声明了比任何其他标准头更多的功能,并且由于作为功能基础的复杂机制,还需要更多解释。

多年来,独立于设备的输入和输出模型已经取得了显着的进步,并且很少得到认可。FORTRAN II在20世纪60年代被吹捧为与机器无关的语言,但如果没有一些改变,在架构之间移动FORTRAN程序基本上是不可能的。在FORTRAN II中,您在FORTRAN代码中间的FORTRAN语句中将您正在与之交谈的设备命名为。所以,你READ INPUT TAPE 5在面向磁带的IBM 7090上说过,但READ CARD要在其他机器上读取卡片图像。FORTRAN IV具有更多通用READWRITE语句,指定逻辑单元号(LUN)而不是设备名称。独立于设备的I / O时代已​​经来临。

诸如打印机之类的外围设备对于他们被要求做的事情仍然有相当强烈的概念。然后,外围交换设施被发明用于处理奇怪的设备。当阴极射线管出现时,每个控制台制造商都以独立的方式解决了控制台光标移动等问题,引起了进一步的麻烦。

正是在这种氛围中,Unix诞生了。Unix的开发人员Ken Thompson和Dennis Ritchie因为将任意数量的好主意包装到操作系统中而值得称赞。他们设备独立性的方法是最聪明的方法之一。

ANSI C <stdio.h>库基于原始的Unix文件I / O原语,但是投射更宽的网络以适应不同系统中的最小公分母。

输入和输出,无论是来自或来自物理设备(如终端和磁带驱动器),还是来自或来自结构化存储设备上支持的文件,都被映射到逻辑数据流,其属性比各种输入和输出更均匀。支持两种映射形式:文本流和二进制流。

文本流由一行或多行组成。文本流中的一行由零个或多个字符加上一个终止的换行符组成。(唯一的例外是在某些实现中,文件的最后一行不需要终止换行符。)Unix为所有文本流采用了标准内部格式。每行文本都以换行符结束。这是任何程序在读取文本时所期望的,这是任何程序在写入文本时产生的。(这是最基本的约定,如果它不能满足连接到Unix机器的面向文本的外围设备的需要,那么修复就会发生在系统的边缘。两者之间不需要改变。 )可能必须修改进入或来自文本流的字符串以符合特定约定。这导致进入文本流的数据与出现的数据之间可能存在差异。例如,在某些实现中,当空格字符位于输入中的换行符之前时,空格字符将从输出中删除。通常,当数据仅由可打印字符和诸如水平制表符和换行符之类的控制字符组成时,文本流的输入和输出是相等的。

与文本流相比,二进制流非常简单。二进制流是一个有序的字符序列,可以透明地记录内部数据。写入二进制流的数据应始终等于在同一实现下读取的数据。但是,二进制流可能会在流的末尾附加一个实现定义的空字符数。没有其他公约需要考虑。

Unix中的任何内容都不会阻止程序将任意8位二进制代码写入任何打开的文件,或者从适当的存储库中将其更改为未更改。因此,Unix消除了文本流和二进制流之间的长期区别。

标准流

当一个C程序开始执行它的程序会自动打开一个名为三个标准流 stdinstdoutstderr。这些都附在每个C程序中。

第一个标准流用于输入缓冲,另外两个用于输出。这些流是字节序列。

考虑以下程序:

 / *示例程序。* / 
 int  main ()
 { 
     int  var ; 
     scanf  (“%d” , &var );  / *使用stdin从键盘扫描整数。* / 
     printf  (“%d” , var );  / *使用stdout打印字符。* / 
     返回 0 ; 
 } 
 / *结束程序。* /

默认情况下stdin指向键盘stdoutstderr指向屏幕。它可以在Unix下使用,并且可以在其他操作系统下重定向输入或输出到文件或两者。

流指针

由于历史原因,调用表示流的C数据结构的类型FILE而不是stream

<stdio.h>首标包含用于一种类型的定义FILE(通常是通过一typedef),其能够处理通过流实行控制所需的所有信息,包括它的文件位置指示器,一个指针相关联的缓冲液(如果有的话),误差指示器的那记录是否发生了读/写错误,以及记录是否已到达文件末尾的文件结束指示符。

FILE除非程序员正在编写其实现<stdio.h>及其内容,否则直接访问内容被认为是不礼貌的行为。FILE通过以下功能提供更好的内容访问<stdio.h>。可以说该FILE类型是面向对象编程的早期示例。

打开和关闭文件

要打开和关闭文件,该<stdio.h>库有三个功能:fopenfreopen,和fclose

打开文件

 #include  <stdio.h>
 FILE  * fopen (const  char  * filename , const  char  * mode ); 
 FILE  * freopen (const  char  * filename , const  char  * mode , FILE  * stream );

fopenfreopen打开其名称位于其指向的字符串中的文件,filename并将流与其关联。两者都返回指向控制流的对象的指针,或者,如果打开操作失败,则返回空指针。清除错误和文件结束指示符,如果打开操作失败,则设置错误。freopen不同之处在于,fopen指向的文件stream在已经打开时首先关闭,并且忽略任何关闭错误。

mode 两个函数都指向以下列序列之一开头的字符串(其他字符可能跟在序列之后):

r打开一个文本文件进行阅读
w截断为零长度或创建用于写入的文本文件
附加; 打开或创建文本文件以便在文件结尾处写入
rb打开二进制文件进行读取
wb截断为零长度或创建二进制文件进行写入
ab追加; 打开或创建二进制文件,以便在文件结尾处写入
r +打开文本文件进行更新(读写)
w +截断为零长度或创建文本文件以进行更新
a +追加; 打开或创建文本文件以进行更新
r + b或rb +打开二进制文件进行更新(读写)
w + b或wb +截断为零长度或创建二进制文件以进行更新
a + b或ab +追加; 打开或创建二进制文件以进行更新

如果文件不存在或无法读取,则打开具有读取模式的文件(’ r‘作为mode参数中的第一个字符)将失败。

打开带有追加模式的文件(’ a‘作为mode参数中的第一个字符)会导致对文件的所有后续写入强制转换为当前的文件结束,而不管对fseek函数的干预调用。在一些实现中,由于空字符填充,打开具有追加模式的二进制文件(’ b‘作为上述mode参数列表中的第二或第三字符)可以最初将流的文件位置指示符定位在最后写入的数据之外。

当使用更新模式打开文件时(’ +‘作为上述mode参数值列表中的第二个或第三个字符),可以在关联的流上执行输入和输出。然而,输出可以不直接依次输入而没有插入调用fflush功能或文件定位功能(fseekfsetpos,或rewind),并且输入可以不直接接着输出而没有中间呼叫到一个文件中的定位功能,除非输入操作遇到文件结尾。在某些实现中,打开(或创建)具有更新模式的文本文件可以改为打开(或创建)二进制流。

当打开时,当且仅当可以确定不参考交互设备时,流被完全缓冲。

关闭文件

 #include  <stdio.h>
 int  fclose (FILE  * stream );

fclose函数使得指向的流stream被刷新,并关闭相关的文件。流的任何未写入的缓冲数据都被传送到主机环境以写入文件; 任何未读缓冲的数据都将被丢弃。该流与文件解除关联。如果自动分配了关联的缓冲区,则将其解除分配。如果成功关闭流或EOF检测到任何错误,则该函数返回零。

流缓冲功能

fflush函数

 #include  <stdio.h>
 int  fflush (FILE  * stream );

如果stream指向输入流或未输入最近操作的更新流,则该fflush函数会将该流的任何未写入数据推迟到主机环境以写入文件。fflush的行为未定义输入流。

如果stream是空指针,则该fflush函数对上面定义了行为的所有流执行此刷新操作。

fflush函数返回EOF如果发生写入错误,否则为零。

具有fflush函数的原因是因为C中的流可以具有缓冲的输入/输出; 也就是说,写入文件的函数实际上写入FILE结构内的缓冲区。如果缓冲区已满容量,则write函数将调用fflush实际“写入”缓冲区中的数据到文件。因为fflush每隔一段时间只调用一次,所以最小化了对操作系统执行原始写入的调用。

setbuf函数

 #include  <stdio.h>
 void  setbuf (FILE  * stream , char  * buf );

不同之处在于它没有返回值,所述setbuf函数相当于setvbuf与值调用的功能_IOFBFmodeBUFSIZsize,或(如果buf是一个空指针)与值_IONBFmode

setvbuf函数

 #include  <stdio.h>
 int  setvbuf (FILE  * stream , char  * buf , int  mode , size_t  size );

setvbuf只有在指向的流stream与打开的文件关联之后并且在对流执行任何其他操作之前,才可以使用该函数。该参数mode确定如何缓冲流,如下所示:_IOFBF使输入/输出完全缓冲; _IOLBF导致输入/输出被线缓冲; _IONBF导致输入/输出无缓冲。如果buf不是空指针,则可以使用它指向的数组而不是setvbuf函数关联的缓冲区。(缓冲区的生命周期必须至少与开放流一样长,因此应该在块退出时释放具有自动存储持续时间的缓冲区之前关闭流。)size指定数组的大小。任何时候数组的内容都是不确定的。

setvbuf函数在成功时返回零,或者如果给出无效值mode或者如果请求无法满足,则返回非零值。

修改文件位置指示符的功能

stdio.h库具有影响文件指针位置,除了那些确实读取或写入五大功能:fgetposfseekfsetposftell,和rewind

fseekftell功能比旧的fgetposfsetpos

fgetposfsetpos功能

 #include  <stdio.h>
 int  fgetpos (FILE  * stream , fpos_t  * pos ); 
 int  fsetpos (FILE  * stream , const  fpos_t  * pos );

fgetpos函数存储指向stream的对象所指向的流的文件位置指示符的当前值pos。存储的值包含fsetpos函数可用的未指定信息,用于在调用函数时将流重新定位到其位置fgetpos

如果成功,则fgetpos函数返回零; 如果失败,该fgetpos函数返回非零并存储实现定义的正值errno

fsetpos函数stream根据指向的对象的值设置指向的流的文件位置指示符,该值pos应该是从fgetpos对同一流上的函数的较早调用获得的值。

成功调用该fsetpos函数会清除流的文件结束指示符,并撤消该ungetc函数对同一流的任何影响。在fsetpos调用之后,更新流上的下一个操作可以是输入或输出。

如果成功,则fsetpos函数返回零; 如果失败,该fsetpos函数返回非零并存储实现定义的正值errno

fseekftell功能

 #include  <stdio.h>
 int  fseek (FILE  * stream , long  int  offset , int  whence ); 
 long  int  ftell (FILE  * stream );

fseek函数设置指向的流的文件位置指示符stream

对于二进制流,通过添加offset到由指定的位置获得从文件开头以字符测量的新位置whence。在三个宏stdio.hSEEK_SETSEEK_CUR以及SEEK_END扩大到唯一值。如果指定的位置whenceSEEK_SET,则指定的位置是文件的开头; 如果whenceSEEK_END,则指定的位置是文件的结尾; 如果whenceSEEK_CUR,则指定的位置是当前文件位置。二进制流不需要有意义地支持值为的fseek调用。 whenceSEEK_END

对于文本流,要么offset必须为零,要么offset是先前ftell对同一流上的函数调用返回的值,并且whence应该是SEEK_SET

fseek函数仅对无法满足的请求返回非零值。

ftell函数获取指向的流的文件位置指示符的当前值stream。对于二进制流,该值是文件开头的字符数; 对于文本流,其文件位置指示符包含未指定的信息,该fseek函数可用于将流的文件位置指示符返回到其在ftell呼叫时的位置; 两个这样的返回值之间的差异不一定是写入或读取的字符数的有意义的度量。

如果成功,该ftell函数将返回流的文件位置指示符的当前值。失败时,ftell函数返回-1L并存储实现定义的正值errno

rewind函数

 #include  <stdio.h>
 void  rewind (FILE  * stream );

rewind函数将指向的流的文件位置指示符设置为文件stream的开头。它相当于

(void)fseek(stream,0L,SEEK_SET)

除了流的错误指示符也被清除。

错误处理函数

clearerr函数

 #include  <stdio.h>
 void  clearerr (FILE  * stream );

clearerr函数清除指向的流的文件结束和错误指示符stream

feof函数

 #include  <stdio.h>
 int  feof (FILE  * stream );

feof函数测试指向的流的文件结束指示符,stream当且仅当文件结束指示符设置为时stream,才返回非零值,否则返回零。

ferror函数

 #include  <stdio.h>
 int  ferror (FILE  * stream );

ferror函数测试指向的流的错误指示符,stream当且仅当错误指示符设置stream为时,返回非零值,否则返回零。

perror函数

 #include  <stdio.h>
 void  perror (const  char  * s );

perror函数将整数表达式中的错误号映射errno到错误消息。它写字符的标准错误流的序列这样的:首先,如果s是不是一个空指针和字符指向s不空字符,字符串指向s后面跟着冒号(:)和一个空间; 然后是一个适当的错误消息字符串,后跟一个换行符。错误消息的内容strerror与具有参数的函数返回的内容相同errno,它们是实现定义的。

文件的其他操作

stdio.h库具有多种功能,除了读写之外,还对文件进行一些操作。

remove函数

 #include  <stdio.h>
 int  remove (const  char  * filename );

remove函数导致该名称filename不再可访问其名称为指向的字符串的文件。使用该名称打开该文件的后续尝试将失败,除非它是重新创建的。如果文件是打开的,则remove函数的行为是实现定义的。

remove如果操作成功,则该函数返回零,如果失败则返回非零值。

rename函数

 #include  <stdio.h>
 int  rename (const  char  * old_filename , const  char  * new_filename );

rename函数使得名称为指向的字符串的文件old_filename由此指定的字符串给出的名称new_filename。该名称old_filename不再可以访问该文件。如果new_filename在调用rename函数之前存在由指向的字符串命名的文件,则该行为是实现定义的。

rename如果操作成功,则该函数返回零,如果失败则返回非零值,在这种情况下,如果该文件先前存在,则其原始名称仍然是已知的。

tmpfile函数

 #include  <stdio.h>
 FILE  * tmpfile (void );

tmpfile函数创建一个临时二进制文件,该文件在关闭或程序终止时将自动删除。如果程序异常终止,则是否删除了打开的临时文件是实现定义的。打开文件以使用"wb+"模式进行更新。

tmpfile函数返回指向它创建的文件流的指针。如果无法创建文件,则该tmpfile函数返回空指针。

tmpnam函数

 #include  <stdio.h>
 char  * tmpnam (char  * s );

tmpnam函数生成一个字符串,该字符串是有效的文件名,而不是现有文件的名称。

tmpnam函数每次调用时都会生成一个不同的字符串,最多可以生成TMP_MAX一次。(TMP_MAX是一个定义的宏stdio.h。)如果TMP_MAX多次调用它,则行为是实现定义的。

该实现应该表现得好像没有库函数调用该tmpnam函数。

如果参数是空指针,则tmpnam函数将其结果保留在内部静态对象中,并返回指向该对象的指针。对tmpnam函数的后续调用可能会修改同一对象。如果参数不是空指针,则假定它指向至少包含L_tmpnam字符的数组(L_tmpnam是另一个宏stdio.h); 该tmpnam函数将其结果写入该数组,并将参数作为其值返回。

宏的值TMP_MAX必须至少为25。

从文件中读取

字符输入函数

fgetc函数

 #include  <stdio.h>
 int  fgetc (FILE  * stream );

fgetc函数从指向的输入流获得下一个字符(如果存在)作为unsigned char转换为a ,并前进该流的相关文件位置指示符(如果已定义)。 intstream

fgetc函数返回指向的输入流中的下一个字符stream。如果流位于文件结尾,则设置流的文件结束指示符并fgetc返回EOF(通常EOF是定义的负值)。如果发生读取错误,则设置流的错误指示符并返回。 <stdio.h>(-1)fgetcEOF

fgets函数

#include <stdio.h>
char * fgets(char * s,int n,FILE * stream);

fgets函数最多读取一个小于n指向的数据流指定的字符数所指向stream的数组s。在换行符(保留)或文件结束后不会读取其他字符。在读入数组的最后一个字符后立即写入空字符。

如果成功,该fgets函数返回s。如果遇到文件结尾且没有字符读入数组,则数组的内容保持不变,并返回空指针。如果在操作期间发生读取错误,则数组内容是不确定的,并返回空指针。

警告:不同的操作系统可能使用不同的字符序列来表示行尾序列。例如,某些文件系统\r\n在文本文件中使用终结符; fgets可能会读取这些行,删除\n但保留\r为最后一个字符s。在将字符串s用于任何内容之前,应该在字符串中删除此无效字符(除非程序员不关心它)。Unix通常使用\n其作为行尾序列,MS-DOS和Windows使用\r\n,以及\r在OS X之前使用的Mac OS。

 / *从stdin读取并写入stdout * / 
 #include  <stdio.h> 的示例程序

 #define BUFFER_SIZE 100

 int  main (void )
 { 
     char  buffer [ BUFFER_SIZE ];  / *读取缓冲区* / 
     while ( fgets  (缓冲区, BUFFER_SIZE , stdin ) !=  NULL )
     { 
          printf (“%s” ,buffer ); 
     } 
     return  0 ; 
 } 
 / *结束程序。* /

getc函数

#include <stdio.h>
int getc(FILE * stream);

getc函数等效于fgetc,但它可以实现为宏。如果它是作为宏实现的,则stream参数可能会被多次计算,因此参数永远不应该是具有副作用的表达式(即具有赋值,递增或递减运算符,或者是函数调用)。

getc函数返回指向的输入流中的下一个字符stream。如果流位于文件结尾,则设置流的文件结束指示符并getc返回EOF(通常EOF是定义的负值)。如果发生读取错误,则设置流的错误指示符并返回。 <stdio.h>(-1)getcEOF

getchar函数

#include <stdio.h>
int getchar(void);

getchar函数getc与参数等效stdin

getchar函数返回指向的输入流中的下一个字符stdin。如果stdin位于文件结尾,stdin则设置文件结尾指示符并getchar返回EOF(通常EOF是定义的负值)。如果发生读取错误,则设置错误指示器并返回。 <stdio.h>(-1)stdingetcharEOF

gets函数

#include <stdio.h>
char * gets(char * s);

gets函数将指向的输入流中的字符读入指向stdin的数组,s直到遇到文件结尾或读取换行符。任何新行字符都将被丢弃,并且在读入数组的最后一个字符后立即写入空字符。

如果成功,该gets函数返回s。如果遇到文件结尾并且没有字符被读入数组,则数组的内容保持不变并返回空指针。如果在操作期间发生读取错误,则数组内容是不确定的,并返回空指针。

此功能和说明仅包含在此处以保证完整性。现在大多数C程序员都不愿意使用gets,因为函数无法知道程序员想要读取的缓冲区有多大。诫#5 亨利·斯宾塞的十诫为C程序员(标版)写道:‘你要检查所有的字符串(事实上,所有的数组)的数组边界,为肯定,你在何处typest 的人总有一天要键入supercalifragilisticexpialidocious。’ 它gets在注释中提到:“正如大蠕虫的行为所证明的那样,这条诫命的结果是强大的生产软件永远不应该使用gets()因为它确实是魔鬼的工具。你的界面应该总是告诉你的仆人你的阵列的界限,摒弃这些建议的仆人或者悄悄地不遵守它的仆人应该立即被送到林地,在那里他们不会对你造成进一步的伤害。“

ungetc函数

#include <stdio.h>
int ungetc(int c,FILE * stream);

ungetc函数将由c(转换为unsigned char)指定的字符推回到stream指向的输入流。后推的字符将按照与其推送相反的顺序在该流上的后续读取返回。一个成功的中间呼叫(与该流指向stream)到一个文件定位功能(fseekfsetpos,或rewind)丢弃的流的任何推回字符。对应于流的外部存储器不变。

保证了一个回击的特征。如果在ungetc同一个流上调用该函数太多次而没有对该流进行插入读取或文件定位操作,则操作可能会失败。

如果值c等于宏的值EOF,则操作失败并且输入流不变。

成功调用该ungetc函数会清除流的文件结束指示符。读取或丢弃所有推回字符后,流的文件位置指示符的值应与字符被推回之前的值相同。对于文本流,在成功调用ungetc函数之后,其文件位置指示符的值未指定,直到读取或丢弃所有推回的字符。对于二进制流,其文件位置指示符通过每次成功调用该ungetc函数而递减; 如果在呼叫之前它的值为零,则在呼叫之后它是不确定的。

ungetc函数返回转换后推回的字符,或者EOF操作失败。

EOF陷阱

比较之前使用fgetc,, getcgetchar将结果分配给类型变量时出错。以下代码片段显示此错误,然后显示正确的方法(使用int类型): char EOF

错误更正
char c ; while ((c = getchar ()) != EOF ) putchar (c ); int c ; while ((c = getchar ()) != EOF ) putchar (c );

考虑一种类型char为8位宽的系统,表示256个不同的值。getchar可以返回256个可能的字符中的任何一个,并且它也可以返回EOF以指示文件结束,总共257个不同的可能返回值。

当将getchar结果分配给a时char,只能表示256个不同的值,必然会有一些信息丢失 – 当将257个项目打包到256个插槽中时,必然会发生冲突。EOF转换char为时,该值与256个字符中的任何一个共享其数值无法区分。如果在文件中找到该字符,则上述示例可能会将其误认为是文件结束指示符; 或者,同样糟糕,如果type char是无符号的,那么因为EOF是负数,它永远不会等于任何unsigned char,所以上面的例子不会在文件结尾处终止。它将永远循环,重复打印转换EOF为的字符char

但是,如果char定义已签名,则不会发生此循环失败模式(C使得默认char类型的签名性依赖于实现), 假设常用EOF值为-1。但是,基本问题仍然是如果EOF值被定义在char类型范围之外,则当分配给char该值时,该值被切片并且将不再匹配EOF退出循环所需的完整值。另一方面,如果EOF在范围内char,则保证EOF了char值和char值之间的冲突。因此,无论如何定义系统类型,char在测试时都不要使用类型EOF

在哪里系统intchar具有相同的尺寸(即系统与最小的POSIX和C99标准不兼容),即使是“好”的例子将遭受的不可分辨EOF和一些人物的价值。处理这种情况的正确方法是检查feof和退货ferror后。如果指示尚未到达文件结尾,并指示没有发生错误,则可以假定返回的by 表示实际字符。这些额外的检查很少进行,因为大多数程序员都认为他们的代码永远不需要在这些“大”系统中运行。另一种方法是使用编译时断言来确保getcharEOFfeofferrorEOFgetcharcharUINT_MAX > UCHAR_MAX,至少可以防止具有这种假设的程序在这样的系统中编译。

直接输入功能:fread功能

#include <stdio.h>
size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream);

fread函数从指向的流中读取指向的数组ptr,直到nmemb指定大小的元素。流的文件位置指示符(如果已定义)按成功读取的字符数提前。如果发生错误,则流的文件位置指示符的结果值是不确定的。如果读取了部分元素,则其值是不确定的。 sizestream

fread函数返回成功读取的元素数,这可能小于nmemb遇到读取错误或文件结束时的数量。如果为sizenmemb为零,则fread返回零,并且数组的内容和流的状态保持不变。

格式化输入函数:函数scanf

#include <stdio.h>
int fscanf(FILE * stream,const char * format,...);
int scanf(const char * format,...);
int sscanf(const char * s,const char * format,...);

fscanf函数在指向stream的字符串的控制下从指向的流中读取输入,该字符串format指定允许的序列以及如何将它们转换为赋值,使用后续参数作为指向对象的指针以接收转换的输入。如果格式的参数不足,则行为未定义。如果参数保留时格式已用尽,则会评估多余的参数(一如既往),否则将被忽略。

格式应为多字节字符序列,以其初始移位状态开始和结束。格式由零个或多个指令组成:一个或多个空格字符; 普通的多字节字符(既不是%或空白字符); 或转换规范。每个转换规范由字符%引入。在%之后,以下顺序出现:

  • 可选的赋值抑制字符*。
  • 可选的非零十进制整数,指定最大字段宽度。
  • 可选的h,l(ell)或L表示接收对象的大小。转换说明符d,i和n的前面应该是h,如果相应的参数是指向short int而不是指针的指针int,或者如果它是指针则指向llong int。类似地,转换说明ø,ü,和X应由前面ħ如果对应的参数是一个指向unsigned short int,而不是unsigned int,或者通过升,如果它是一个指向unsigned long int。最后,如果相应的参数是指向而不是指针的指针,则转换说明符e,f和g应以l开头,如果指针double指向float,则由L开头long double。如果h,l或L与任何其他格式说明符一起出现,则行为未定义。
  • 一个字符,指定要应用的转换类型。有效的转换说明符如下所述。

fscanf函数依次执行该格式的每个指令。如果指令失败,如下所述,fscanf函数返回。故障被描述为输入故障(由于输入字符不可用)或匹配故障(由于输入不当)。

由白色空格字符组成的指令是通过读取第一个非空白字符(仍然未读取)或直到不再有字符未读的输入来执行的。

通过读取流的下一个字符来执行作为普通多字节字符的指令。如果其中一个字符与包含该指令的字符不同,则该指令失败,并且不同和后续字符仍未读取。

作为转换规范的指令定义了一组匹配的输入序列,如下面针对每个说明符所述。转换规范按以下步骤执行:

isspace除非规范包含[,c或n说明符,否则将跳过输入的空白​​字符(由函数指定)。(空白字符不计入指定的字段宽度。)

除非规范包含n指定符,否则从流中读取输入项。输入项被定义为输入字符的最长匹配序列,除非它超过指定的字段宽度,在这种情况下,它是序列中该长度的初始子序列。输入项目之后的第一个字符(如果有)仍未读取。如果输入项的长度为零,则指令的执行失败; 此条件是匹配失败,除非错误阻止了流的输入,在这种情况下它是输入失败。

除了%说明符之外,输入项(或者,在%n指令的情况下,输入字符的数量)将转换为适合转换说明符的类型。如果输入项不是匹配的序列,则指令的执行失败; 这种情况是匹配失败。除非通过*指示赋值抑制,否则转换的结果将放置在format尚未收到转换结果的参数后面的第一个参数指向的对象中。如果此对象没有适当的类型,或者无法在提供的空间中表示转换结果,则行为未定义。

以下转换说明符有效:d 匹配可选带符号的十进制整数,其格式与strtol函数的主题序列的预期相同,base参数值为10 。相应的参数应该是指向整数的指针。一世 匹配可选的有符号整数,其格式与strtol函数的主题序列的预期相同,base参数值为0 。相应的参数应该是指向整数的指针。Ø 匹配可选带符号的八进制整数,其格式与strtoul函数的主题序列的预期相同,base参数值为8 。相应的参数应该是指向无符号整数的指针。ü 匹配可选带符号的十进制整数,其格式与strtoul函数的主题序列的预期相同,base参数值为10 。相应的参数应该是指向无符号整数的指针。X 匹配可选的带符号十六进制整数,其格式与strtoul函数的主题序列的预期相同,base参数值为16 。相应的参数应该是指向无符号整数的指针。e,f,g 匹配可选的带符号浮点数,其格式与strtod函数主题字符串的预期格式相同。相应的参数将是一个浮动指针。小号 匹配一系列非空白字符。(对多字节字符没有特殊规定。)相应的参数应该是指向足以接受序列的数组的初始字符的指针和一个将自动添加的终止空字符。[ 匹配来自一组预期字符(扫描集)的非空字符序列(对多字节字符没有特殊规定)。相应的参数应该是一个指向数组初始字符的指针,该数组足以接受序列,并且终止空字符将自动添加。转换说明符包括format字符串中的所有后续字符,包括匹配的右括号(])。括号(扫描列表)之间的字符构成扫描集,除非左括号后面的字符是旋转(^),在这种情况下,扫描集包含旋转和右支架之间的扫描列表中未出现的所有字符。如果转换说明符以[]或[^]开头,则右括号字符位于扫描列表中,右下方括号字符是结束规范的匹配右括号; 否则,第一个右括号字符是结束规范的字符。如果-字符在扫描列表中并且不是第一个字符,也不是第一个字符是^的第二个字符,也不是最后一个字符,则行为是实现定义的。C 匹配由字段宽度指定的数字的字符序列(对多字节字符没有特殊规定)(如果指令中不存在字段宽度,则为1)。相应的参数应该是指向足以接受序列的数组的初始字符的指针。没有添加空字符。p 匹配实现定义的序列集,该序列集应该与函数的%p转换可能产生的序列集相同fprintf。相应的参数应该是指向void。然后,对输入的解释是实现定义的。如果输入项是在同一程序执行期间先前转换的值,则结果的指针应该等于该值; 否则%p转换的行为是未定义的。ñ 没有输入消耗。相应的参数应该是一个指向整数的指针,到目前为止,通过对fscanf函数的调用,将写入从输入流中读取的字符数。执行%n指令不会增加在执行fscanf函数时返回的赋值计数。% 匹配单个% ; 没有转换或分配。完整的转换规范应为%%。

如果转换规范无效,则行为未定义。

转换说明符E,G和X也有效,其行为分别与e,g和x相同。

如果在输入期间遇到文件结尾,则终止转换。如果在读取了与当前指令匹配的任何字符之前发生了文件结束(除了前导空格,除非允许),当前指令的执行在输入失败时终止; 否则,除非以匹配失败终止当前指令的执行,否则在输入失败时终止执行以下指令(如果有)。

如果转换在冲突的输入字符上终止,则输入流中的违规输入字符将保持未读状态。除非与指令匹配,否则尾部空白区域(包括换行符)将保持未读状态。除了通过%n指令之外,文字匹配和抑制赋值的成功不能直接确定。

如果在任何转换之前发生输入故障fscanf,则该函数返回宏的值EOF。否则,该fscanf函数返回分配的输入项的数量,如果早期匹配失败,则可以少于提供的数量,甚至为零。

scanf函数等同于fscanf在参数stdin之前插入的参数scanf。它的返回值类似于fscanf

sscanf函数等效于fscanf,除了参数s指定要从中获取输入的字符串,而不是从流中。到达字符串的末尾等同于遇到fscanf函数的文件结尾。如果在重叠的对象之间进行复制,则行为未定义。

写入文件

字符输出功能

fputc函数

#include <stdio.h>
int fputc(int c,FILE * stream);

fputc函数将由c(转换为a unsigned char)指定的字符写入stream由关联文件位置指示符(如果已定义)指示的位置指向的流,并适当地前进指示符。如果文件不支持定位请求,或者使用追加模式打开流,则该字符将附加到输出流。除非发生写入错误,否则该函数返回写入的字符,在这种情况下,将设置流的错误指示符并fputc返回EOF

fputs函数

#include <stdio.h>
int fputs(const char * s,FILE * stream);

fputs函数将指向的字符串写入指向s的流stream。不写入终止空字符。EOF如果发生写入错误,则返回该函数,否则返回非负值。

putc函数

#include <stdio.h>
int putc(int c,FILE * stream);

putc函数等效于fputc,除非它实现为宏,它可能会stream多次计算,因此参数永远不应该是带有副作用的表达式。该函数返回写入的字符,除非发生写入错误,在这种情况下,将设置流的错误指示符并返回该函数EOF

putchar函数

#include <stdio.h>
int putchar(int c);

putchar函数等效于putc第二个参数stdout。除非发生写入错误,否则它将返回写入的字符,在这种情况下,stdout将设置错误指示符并返回该函数EOF

puts函数

#include <stdio.h>
int puts(const char * s);

puts函数将指向的字符串写入指向s的流stdout,并将新行字符附加到输出。不写入终止空字符。EOF如果发生写入错误,则返回该函数; 否则,它返回一个非负值。

直接输出功能:fwrite功能

#include <stdio.h>
size_t fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream);

fwrite函数从指向的数组写入ptr最大nmemb为指定大小的元素size到指向的流stream。流的文件位置指示符(如果已定义)按成功写入的字符数提前。如果发生错误,则流的文件位置指示符的结果值是不确定的。该函数返回成功写入的元素数,这将少于nmemb仅在遇到写入错误时。

格式化输出函数:函数printf

#include <stdarg.h>
#include <stdio.h>
int fprintf(FILE * stream,const char * format,...);
int printf(const char * format,...);
int sprintf(char * s,const char * format,...);
int vfprintf(FILE * stream,const char * format,va_list arg);
int vprintf(const char * format,va_list arg);
int vsprintf(char * s,const char * format,va_list arg);

注意:某些长度说明符和格式说明符是C99中的新增内容。这些可能不适用于符合C89 / C90标准的旧版编译器和stdio库版本。只要有可能,新的将标记为(C99)。

fprintf函数将输出写入由指向stream的字符串控制下指向的流,该字符串format指定后续参数如何转换为输出。如果格式的参数不足,则行为未定义。如果参数保留时格式已用尽,则会评估多余的参数(一如既往),否则将被忽略。fprintf 遇到格式字符串的结尾时,该函数返回。

格式应为多字节字符序列,以其初始移位状态开始和结束。格式由零个或多个指令组成:普通的多字节字符(不是%),它们不加改变地复制到输出流中; 和转换规范,每个转换规范导致获取零个或多个后续参数,根据相应的转换说明符转换它们(如果适用),然后将结果写入输出流。

每个转换规范由字符%引入。在%之后,以下顺序出现:

  • 零个或多个标志(以任何顺序),用于修改转换规范的含义。
  • 可选的最小字段宽度。如果转换后的值的字符数少于字段宽度,则在字段宽度上用左侧(或右侧,如果左侧调整标志,稍后描述)填充空格(默认情况下)。字段宽度采用星号*(稍后描述)或十进制整数的形式。(注意,0被视为标志,而不是字段宽度的开头。)
  • 一个可选的精度给出的最小位数显示为d,我,ö,ü,X,和X的转化,为小数点字符之后出现的位数一个,甲,ê,ê,˚F和F转换,g和G转换的最大有效位数,或s转换中从字符串写入的最大字符数。精度采用句号的形式(。)后跟星号*(稍后描述)或可选的十进制整数; 如果仅指定了句点,则精度为零。如果精度与任何其他转换说明符一起出现,则行为未定义。浮点数被舍入以适应精度; 即printf(“%1.1f \ n”,1.19); 产生1.2。
  • 一个可选的长度修饰符,用于指定参数的大小。
  • 转换说明符字符,指定要应用的转换类型。

如上所述,字段宽度或精度或两者可以用星号表示。在这种情况下,int参数提供字段宽度或精度。指定字段宽度或精度或两者的参数应在要转换的参数(如果有)之前(按此顺序)出现。负字段宽度参数被视为-标志,后跟正字段宽度。如果省略精度,则采用负精度参数。

旗帜字符及其含义是:-  转换的结果在字段内左对齐。(如果未指定此标志,则右对齐。)+ 签名转换的结果始终以加号或减号开头。(如果未指定此标志,则仅在转换负值时以符号开头。负零的所有浮动转换结果以及舍入为零的负值的结果包括减号。)空间 如果签名转换的第一个字符不是符号,或者签名转换不会导致字符,则会在结果前面添加一个空格。如果出现空格和+标志,则忽略空格标志。# 结果转换为“替代形式”。对于o转换,它会增加精度,当且仅在必要时,强制结果的第一个数字为零(如果值和精度都为0,则打印单个0)。对于x(或X)转换,非零结果的前缀为0x(或0X)。对于a,A,e,E,f,F,g和G.转换时,结果始终包含小数点字符,即使后面没有数字。(通常,只有在跟随数字的情况下,这些转换的结果中才会出现小数点字符。)对于g和G转换,不会从结果中删除尾随零。对于其他转换,行为未定义。0 对于d,i,o,u,x,X,a,A,e,E,f,F,g和G转换,前导零(在符号或基数的任何指示之后)用于填充到字段宽度; 没有执行空格填充。如果出现0和-标志,则忽略0标志。对于d,i,o,u,x和X转换,如果指定了精度,则忽略0标志。对于其他转换,行为未定义。

长度修饰符及其含义是:HH (C99)指定以下d,i,o,u,x或X转换说明符适用于signed charunsigned char参数(该参数将根据整数提升进行提升,但其值应转换为打印前signed charunsigned char打印前) ; 或者后面的n转换说明符适用于指向signed char参数的指针。H 指定以下d,i,o,u,x或X转换说明符适用于short intunsigned short int参数(该参数将根据整数提升进行提升,但其值应转换为打印前short intunsigned short int打印前); 或者后面的n转换说明符适用于指向short int参数的指针。l(ell) 指定以下d,i,o,u,x或X转换说明符适用于long intunsigned long int参数; 后面的n转换说明符适用于指向long int参数的指针; (C99)以下c转换说明符适用于wint_t参数; (C99)以下的s转换说明符适用于指向wchar_t参数的指针; 或对后续的a,A,e,E,f,F,g没有影响或G转换说明符。ll(ell-ell) (C99)指定以下d,i,o,u,x或X转换说明符适用于long long intunsigned long long int参数; 或者后面的n转换说明符适用于指向long long int参数的指针。Ĵ (C99)指定以下d,i,o,u,x或X转换说明符适用于intmax_tuintmax_t参数; 或者后面的n转换说明符适用于指向intmax_t参数的指针。ž (C99)指定以下d,i,o,u,x或X转换说明符适用于size_t或对应的有符号整数类型参数; 或者后面的n转换说明符适用于指向与size_t参数对应的有符号整数类型的指针。Ť (C99)指定以下d,i,o,u,x或X转换说明符适用于ptrdiff_t或相应的无符号整数类型参数; 或者后面的n转换说明符适用于指向ptrdiff_t参数的指针。大号 指定跟随a,A,e,E,f,F,g或G转换说明符适用于long double参数。

如果长度修饰符出现时带有除上述指定之外的任何转换说明符,则行为未定义。

转换说明符及其含义是:d,我 该int参数被转换为符号十进制的风格 ] DDDD。精度指定要显示的最小位数; 如果转换的值可以用更少的数字表示,则使用前导零进行扩展。默认精度为1.转换零值且精度为零的结果不是字符。o,u,x,X 该unsigned int参数被转换为无符号八进制(ø),无符号十进制(Û),或无符号十六进制表示法(X或X的样式)DDDD ; 字母abcdef用于x转换,字母ABCDEF用于X转换。精度指定要显示的最小位数; 如果转换的值可以用更少的数字表示,则使用前导零进行扩展。默认精度为1.转换零值且精度为零的结果不是字符。f,F 甲double表示(有限)浮点数参数被转换为十进制表示在样式– ] DDD ddd,其中小数点字符后面的位数等于精度规格。如果缺少精度,则取6; 如果精度是零,并且#未指定标志,则没有出现小数点字符。如果出现小数点字符,则在其前面至少出现一个数字。该值四舍五入到适当的位数。
(C99)double表示无穷大的参数以– inf或– ]之一转换无限 – 哪种风格是实现定义的。表示NaN的双参数在样式– nan或– nan(n-char-sequence )之一中转换 – 哪种样式以及任何n-char序列的含义是实现定义的。所述˚F转换说明产生INF,INFINITY,或NAN代替INF,无穷大,或楠分别。(当应用于无限和NaN值时,-,+和空格旗帜有其通常的含义; 在#和0标志没有任何效果。)e,E 甲double表示(有限)浮点数参数被转换在样式– ] d ddd e± dd,其中有一位数(如果参数非零,则为非零)在小数点字符之前且其后的位数等于精度; 如果精度丢失,则取6; 如果精度是零,并且#未指定标志,则没有出现小数点字符。该值四舍五入到适当的位数。所述ë转换说明产生许多与Ë代替ë介绍指数。指数始终包含至少两个数字,并且只包含表示指数所需的更多数字。如果该值为零,则指数为零。
(C99)double表示无穷大或NaN的参数以f或F转换说明符的样式转换。g,G 甲double表示(有限)浮点数参数被转换风格˚F或È(或风格˚F或Ë在的情况下ģ转换说明),其精度指定的显著位数。如果精度为零,则将其视为1.使用的样式取决于转换的值; 只有当这种转换产生的指数小于-4或大于或等于精度时,才使用样式e(或E)。尾随零将从结果的小数部分删除,除非#被指定的标志; 只有后跟数字后才会出现小数点字符。
(C99)double表示无穷大或NaN的参数以f或F转换说明符的样式转换。a,A 表示(有限)浮点数(C99)的双参数被转换在样式– 0X ħ hhhh p± d,其中有一个十六进制数字(如果参数是规范化的浮点数,则为非零,否则未指定)在小数点字符之前(二进制实现可以选择小数点左边的十六进制数字)点字符,以便后续数字对齐半字节[4位]边界。)和后面的十六进制数字等于精度; 如果精度缺失并且FLT_RADIX是2的幂,则精度足以精确表示该值; 如果精度丢失了FLT_RADIX不是2的幂,那么精度足以区分(精度p足以区分源类型的值,如果16 p -1 > n,其中bFLT_RADIXnb中的base- b数字的数量根据用于确定小数点字符左边的数字的实现方案,较小的p可能就足够了。)类型的值double,除了尾部零可以省略; 如果精度是零,并且#未指定标志,则没有出现小数点字符。字母abcdef被用于一个转换和字母ABCDEF为甲转换。的甲转换说明产生具有号码X和P,而不是X和p。指数始终包含至少一个数字,并且只包含表示小数指数为2所需的更多位数。如果该值为零,则指数为零。
double表示无穷大或NaN被转换在风格参数˚F或˚F转换说明。C 如果不存在l length修饰符,则将int参数转换为a unsigned char,并写入生成的字符。
(C99)如果存在l长度修饰符,则wint_t参数的转换就好像是没有精度的ls转换规范和指向两元素数组的初始元素wchar_twint_t参数,第一个元素包含参数lc转换规范,第二个是null宽字符。小号 如果不存在l length修饰符,则参数应该是指向字符类型数组的初始元素的指针。(对多字节字符没有特殊规定。)数组中的字符被写入(但不包括)终止空字符。如果指定了精度,则不会写入多个字符。如果未指定精度或大于数组的大小,则数组应包含空字符。
(C99)如果存在l长度修饰符,则参数应该是指向wchar_t类型数组的初始元素的指针。数组中的宽字符将转换为多字节字符(每个字符就像通过调用wcrtomb函数一样,转换状态由mbstate_t对象在转换第一个宽字符之前初始化为零)直到并包括终止空宽字符。生成的多字节字符被写入(但不包括)终止空字符(字节)。如果未指定精度,则数组应包含空宽字符。如果指定了精度,则不会写入多个字符(字节)(包括移位序列,如果有的话),并且如果等于由精度给出的多字节字符序列长度,则数组应包含空宽字符,函数需要访问一个超过数组末尾的宽字符。在任何情况下都不会写入部分多字节字符。(如果多字节字符具有依赖于状态的编码,则可能导致冗余移位序列。)p 参数应该是指针void。指针的值以实现定义的方式转换为可打印字符序列。ñ 参数应该是一个指向有符号整数的指针,其中写入了此调用到目前为止写入输出流的字符数fprintf。没有参数被转换,但是消耗了一个参数。如果转换规范包括任何标志,字段宽度或精度,则行为未定义。% 一%字符写入。没有参数被转换。完整的转换规范应为%%。

如果转换规范无效,则行为未定义。如果任何参数不是相应的转换规范的正确类型,则行为未定义。

在任何情况下,不存在或小的字段宽度都不会导致字段截断; 如果转换结果比字段宽度宽,则扩展该字段以包含转换结果。

对于a和A转换,如果FLT_RADIX是2的幂,则将值正确舍入为具有给定精度的十六进制浮点数。

建议惯例如果FLT_RADIX不是2的幂,则结果应该是具有给定精度的十六进制浮动样式中的两个相邻数字之一,并且额外规定错误应该具有当前舍入方向的正确符号。

对于e,E,f,F,g和G转换,建议的做法是,如果有效小数位数最多DECIMAL_DIG,则应正确舍入结果。(对于二进制到十进制的转换,结果格式的值是用给定格式说明符表示的数字。有效位数由格式说明符确定,在源值固定点转换的情况下也是如此。 。)如果有效十进制数的数量大于DECIMAL_DIG但是源值可以准确表示DECIMAL_DIG数字,那么结果应该是具有尾随零的精确表示。否则,源值由两个相邻的十进制字符串L <U限制,两者都具有DECIMAL_DIG有效数字; 结果十进制字符串D的值应满足L≤D≤U,并额外规定误差应具有当前舍入方向的正确符号。

fprintf函数返回传输的字符数,如果发生输出或编码错误,则返回负值。

printf函数等同于fprintf在参数stdout之前插入的参数printf。它返回发送的字符数,如果发生输出错误则返回负值。

sprintf函数等效于fprintf,除了参数s指定要将生成的输入写入的数组,而不是流。在写入的字符末尾写入空字符; 它不算作退还金额的一部分。如果在重叠的对象之间进行复制,则行为未定义。该函数返回数组中写入的字符数,不包括终止空字符。

vfprintf函数等效于fprintf,变量参数列表被替换arg,其应该由va_start宏(以及可能的后续va_arg调用)初始化。该vfprintf函数不会调用va_end宏。该函数返回发送的字符数,如果发生输出错误则返回负值。

vprintf函数等效于printf,变量参数列表被替换arg,其应该由va_start宏(以及可能的后续va_arg调用)初始化。该vprintf函数不会调用va_end宏。该函数返回发送的字符数,如果发生输出错误则返回负值。

vsprintf函数等效于sprintf,变量参数列表被替换arg,其应该由va_start宏(以及可能的后续va_arg调用)初始化。该vsprintf函数不会调用va_end宏。如果在重叠的对象之间进行复制,则行为未定义。该函数返回写入数组的字符数,不包括终止空字符。

猜你想读:《C编程.中级C》6.字符串操作

THE END
分享