C和指针_第15章_输入输出函数_学习笔记
扫描二维码
随时随地手机看文章
1.错误报告
perror函数一种简单、统一的方式报告错误。标准库函数在一个外部整型变量errno(在errno.h中定义)中保存错误代码之后把这个信息传递给用户程序,提示操作失败的准确原因。perror函数简化向用户报告这些特定错误的过程。定义于stdio.h中:
void perror( char const *message );
如果message不是NULL并且指向一个非空的字符串,perror函数将打印这个字符串,后面跟一个分号和一个空格,然后打印出一条用于解释errno当前错误代码的信息。
注意,只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误发生。反之,只有当被调用函数提示有错误发生时检查errno的值才有意义。
2.终止执行
void exit( int status );
status参数返回给操作系统,提示程序是否正常完成。预定义符号EXIT_SUCCESS和EXIT_FAILURE分别提示程序的终止是成功还是失败。经常会出现的是在调用perror函数之后再调用exit终止程序。
4.ANSI I/O概念
stdio.h包含FILE结构的声明,FILE是一个数据结构,用于访问一个流。编译器保证一个程序同时最多可以打开文件的数量至少为FOPEN_MAX。这个常量包括了三个标准流,它的值至少为8。
5.流总览
字符 getcharputchar 读取(写入)单个字符 文本行
gets
scanf
puts
printf
文本行未格式化的输入(输出)
格式化的输入(输出)
二进制数据 fread fwrite 读取(写入)二进制数据
斜体为函数家族,指一组函数中的每个都执行相同的基本任务,只是方式稍有不同。这些函数的区别在于获得输入的来源或输出写入的地方不同。这些变种用于执行下面任务:
1.只用于stdin或stdout
2.随作为参数的流使用
3.使用内存中的字符串而不是流
getchar 字符输入 fgets,getc getchar ① putchar 字符输出 fputc,putc putchar ① gets 文本行输入 fgets gets ② puts 文本行输出 fputs puts ② scanf 格式化输入 fscanf scanf sscanf printf 格式化输出 fprintf printf sprintf
①对指针使用下标引用或间接访问操作从内存中获得一个字符(或向内存写入一个字符)。
②使用strcpy函数从内存中读取文本行(或向内存中写入文本行)
6.打开流
FILE *fopen( char const *name, char const *mode );
应该始终检查fopen函数的返回值。
FILE *fptr; fptr = fopen( "data_name", "r" ); if( fptr == NULL ) { perror("data_name"); exit(EXIT_FAILURE); }
freopen函数用于打开(重新打开)一个特定的文件流。原型如下:
FILE *freopen( char const *name, char const *mode, FILE *stream );
最后一个参数就是需要打开的流。它可能是一个先前从fopen函数返回的流,也可能是标准流。这个函数首先试图关闭这个流,然后用指定的文件和模式重新打开这个流。如果打开失败,返回NULL。如果打开成功,函数返回其第3个参数。
7.关闭流
流是使用函数fclose关闭的,关闭前会刷新缓冲区。如果只选成功,返回0,否则返回EOF。函数原型如下:
int fclose( FILE *stream );
只要操作成功后和操作失败后的执行指令不一样,就应该进行错误检查。
#include#includeint main( int argc, char **argv) { int exit_status = EXIT_SUCCESS; FILE *input; //when the file not only one while(*++argv != NULL ) { //open the file input = fopent( *argv, "r" ); if( input == NULL ) { perror( *argv ); exit_status = EXIT_FAILURE; continue; } //deal with the opened file //close the file if( fclose( input ) != 0) { perror( "fclcose" ); exit( EXIT_FAILURE ); } } return EXIT_SUCCESS; }
8.字符I/O
int fgetc( FILE *stream ); int getc( FILE *stream ); int getchar( void );
getc和fgetc是从传入的操作流中读取,而getchar从标准输入流中读取。每个函数从流中读取下一个字符,并把它作为函数的返回值返回。如果流中不存在更多字符,函数返回常量值EOF。返回int值的真正原因是为了运行函数报告文件的末尾(EOF),如果返回值为char,则256个字符中一定有一个被指定为EOF,导致如果文件中出现这个字符,字符后的内容不会被读取。
int fputc( int character, FILE *stream ); int putc( int character, FILE *stream ); int putchar( int character );
fgetc和fputc是真正的函数,但getc、putc、getchar和putchar都是通过#define指令定义的宏。
8.2撤销字符I/O
int ungetc( int character, FILE *stream );
ungetc把一个先前读入的字符返回到流中,使其在以后可以被重新读入。每个流都允许至少一个字符被退回。如果一个流允许退回多个字符,那么这些字符再次被读取的顺序就以退回时的反序进行。注意把字符退回到流中和写入到流中并不相同。与一个流相关联的外部存储并不受ungetc的影响。
警告:“退回”字符和流的当前位置有关,如果用fseek、fsetpos或rewind函数改变了流的位置,所有退回的字符将被丢弃。
9.未格式化的行I/O
未格式化的行I/O简单读取或写入字符串
char *fgets( char *buffer, int buffer_size, FILE *stream ); char *gets( char *buffer );
fgets从指定的stream流中读取字符并把它们复制到buffer(至少2个字节,因为最后一个需要用来放NUL)中。当他读到一个换行符并存储到缓冲区中后不再读取。如果缓冲区内存储的字符数达到buffer_size - 1个时也停止读取。下一次调用fgets将从流的下一个字符开始读取。在任何一种情况下,一个NUL字节将被添加到缓冲区所存储的数据的末尾,使其成为一个字符串。
如果在任何字符读取前就到达了文件尾,缓冲区就未进行修改,fgets函数返回一个NULL指针。否则,fgets就返回它的第一个参数(指向缓冲区的指针)。返回值通常只用于检查是否到达了文件尾。
gets和fgets主要不同在于读取一行输入时,并不在缓冲区中存储结尾的换行符。由于gets没有缓冲区参数,所以无法判断缓冲区长度。有可能缓冲区溢出。
int fputs( char const *buffer, FILE *stream ); int puts( char const *buffer );
传递给fputs的缓冲区必须包含一个字符串,它的字符被写入到流中。这个字符串预期以NUL字节结尾,这个字符串是逐字写入。写入出现错误时,fputs返回常量值EOF,否则返回一个非负值。
puts与fputs的主要不同在于写入一个字符串时,在字符串写入之后向输出再添加一个换行符。
#include#define MAX_LINE_LENGTH 1024 void copylines( FILE *input, FILE *output ) { char buffer[MAX_LINE_LENGTH]; while( (fgets( buffer, MAX_LINE_LENGTH, input ) != EOF ) ) fputs( buffer, output ); }
10.格式化的行I/O
格式化的行I/O会执行数字和其他变量的内部和外部表示形式之间的转换。
10.1 scanf函数家族
int fscanf( FILE *stream, char const *format, ...); int scanf( char const *format, ...); int sscanf( char const *string, char const *format, ...);
原型中的省略号表示一个可变长度的指针列表。从输入源读取字符并根据format字符串给出的格式代码对其进行转换,将从输入转换而来的值逐个存储到这些指针参数所指向的内存位置。
当格式化字符串到达末尾或者读取的输入不在匹配格式字符串所指定的类型时,输入停止。被转换的参数数目作为函数的返回值返回。如果在任何输入值被转换前 文件就达到尾部,返回常量EOF。
scanf函数家族中的format字符串参数可能包含的内容有:
空白字符——与输入中的0个或多个空白字符匹配,在处理过程中被忽略。 格式代码——指定函数如何解释接下来的输入字符 其他字符——任何其他字符出现在格式字符串时,下一个输入字符必须和它匹配。匹配后将被丢弃,不匹配函数不再读取直接返回。
scanf函数家族的格式代码都以一个%开头,后面可以是:
一个可选的星号——星号将使转换后的值丢弃而不是存储。 一个可选的宽度——宽度以一个非负的整数给出,限制将被读取用于转换的输入字符的个数。如果未给出宽度,函数就连续读入字符直到遇见输入中的下一个空白字符。 一个可选的限定符 d,i,n short long o,u,x unsigned short unsigned long e,f,g double long double
4.格式代码
c char *
读取和存储单个字符。前导的空白字符并不跳过。如果给出宽度,就读取和存储这个数目
的字符。字符后面不会再添加一个NUL字节。参数必须指向一个足够大的字符数组
i
d
int *
一个可选的有符号整数被转换。d把输入解释为10进制数;i根据它的第1个字符决定值的
基数,就像整型字面值常量的表达式一样。
u
o
x
unsigned *
一个可选的有符号整数被转换,但它按照无符号数存储。如果使用u,值被解释为十进制
数,如果使用0,值被解释为八进制数;如果使用x,值被解释为十六进制数。X与x同义
e
f
g
float *
期待一个浮点值。它的形式必须像一个浮点型字面值常量,但小数点并非必需。E和G分
别与e和g同义
s char *
读取一串非空白字符。参数必须指向一个足够大的字符数组。当发现空白输入时就停止,
字符串后面会自动加上NUL终止符
[xxx] char *
根据给定组合的字符从输入中读取一串字符。参数必须指向一个足够大的字符数组。当遇
到第1个不在给定组合中出现的字符时,输入就停止。字符串后面会自动加上NUL终止符。
代码%[abc]表示字符组包括a、b和c。如果列表中以一个^字符开头,表示字符组合是所
列出的字符的补集,所以%[^abc]表示字符组为a、b、c之外的所有字符。右方括号也可以
出现在字符列表中,但它必须是列表的第1个字符。至于横杆是否用来指定某个范围的字符
(例如%[a-z]),则因编译器而异
p void *
输入预期为一串字符,诸如那些由printf函数的%p格式代码所产生的输出。它的转换方式
因编译器而异,但转换结果将和按照上面描述的进行打印所产生的字符的值是相同的
n int *
d到目前为止通过这个scanf函数的调用从输入读取的字符数被返回。%n转换的字符并不计
算在scanf函数的返回值之内。它本身并不消耗任何输入。
% 这个代码与输入中的一个%相匹配,该%符号将被丢弃。
fscanf中将换行符也当做空白字符跳过,所以在使用fscanf时,在输入中保持行边界的同步有困难。
10.3printf家族
int fprintf( FILE *stream, char const *format, ... ); int printf( char const *format, ... ); int sprintf( char *buffer, char const *format, ... );
sprintf把它的结果作为一个NUL结尾的字符串存储到指定的buffer缓冲区,注意,缓冲区的大小不是sprintf的参数,所以易出现内存块越位,可以通过对格式进行分析,看看最大可能出现的值被转换后的输出结果将由多长来防止越位。格式代码以一个%开头,后面:
零个或多个标志字符,用于修改有些转换的执行方式 一个可选的最小字段宽度 一个可选的精度 一个可选的修改符 转换类型
c int 参数被裁剪为unsigned char类型并作为字符进行打印
d
i
int 参数作为一个十进制数打印。如果给出精度而且值的位数小于精度位数,前面就用0填充
u
o
x,X
int 参数作为一个无符号值打印,u使用十进制,0使用八进制,x或X使用十六进制,两者区别在于x使用abcdef,X使用ABCDEF
e
E
double 参数根据指数形式打印。小数点后面的位数由精度字段决定,默认是6 f double 参数按照常规的浮点格式打印。小数点后面的位数由精度字段决定,默认是6
g
G
double 如果指数≥-4但小于进度字段就用%f,否则使用指数格式 s char * 打印一个字符串 p void * 指针值被转换为一串因编译器而异的可打印字符。这个代码主要和scanf中的%p代码组合使用 n int * 独特代码,不产生任何输出。相反,到目前为止函数所产生的输出字符数目将被保存到对应的参数中 % (无)
打印一个%字符
- 值在字段中左对齐。默认情况是右对齐 0
d当数值是右对齐时,默认情况下是使用空格填充值左边未使用的列,这个标志表示用0填充。它可用于
d,i,u,o,x,X,e,E,f,g和G代码。使用d,i,o,u,x和X代码时,如果给出精度,零标志就被忽略。如果格式代码
中出现负号(-),零标志也没有效果
+
当用于一个格式化某个符号值的代码时,如果值非负,正号标志就会给它加上一个正号。如果该值为负,就像
往常一样显示一个负号。默认情况下,正号并不会显示
空格
只用于转换有符号值的代码。当值非负时,这个标志把一个空格添加到它的开始位置。注意这个标志和正号标志
是相互排斥的。如果两个同时给出,空格标志将被忽略
# 选择某些代码的另一种转换形式。
11.二进制I/O
把数据写到文件效率最高的方法就是用二进制写入。
size_t fread( void *buffer, size_t size, size_t count, FILE *stream ); size_t fwrite( void *buffer,size_t size, size_t count, FILE *stream );
buffer是一个指向保存数据的内存位置的指针,size是缓冲区每个元素的字节数,count是读取或写入的元素数。函数返回值是实际写入或读取的元素数目。
12.刷新和定位函数
int fflush( FILE *stream );
fflush迫使一个输出流的缓冲区内的数据进行物理写入,不管缓冲区是否被写满。例如,为保证调试信息实际打印出来,使用fflush。
long ftell( FILE *stream ); int fseek( FILE *stream, long offset, int from);
from可以是SEEK_SET、SEEK_CUR和SEEK_END。在二进制流中,从SEEK_END进行定位可能不被支持。在文本流中,如果from是SEEK_CUR或SEEK_END,offset必须是0.如果from是SEEK_SET,offset必须是一个从同一个流中以前调用ftell返回的值。
SEEK_SET 从流的起始位置起offset个字节,offset必须是一个非负值。 SEEK_CUR 从流的当前位置起offset个字节,offset的值可正可负 SEEK_END 从流的尾部位置起offset个字节,offset的值可正可负,如果为正,将定位到文件尾的后面
之所以存在这些限制,部分原因是文本流所执行的行末字符映射。由于这个映射的存在,文本文件的字节数可能和程序写入的字节数不同。因此,一个可移植的程序不能根据实际写入字符数的计算结果定位到文本流的一个位置。
用fseek改变一个流的位置会带来三个副作用:
行末指示符被清除 如果在fseek之前使用ungetc把一个字符返回到流中,那么这个被退回的字符会被丢弃,因为在定位操作后,它不再是“下一个字符” 定位允许从写入模式切换到读取模式,或者回到打开的流以便更新。
void rewind( FILE *stream ); int fgetpos( FILE *stream, fpos_t *position ); int fsetpos( FILE *stream, fpos_t *position );
rewind函数将读/写指针设置回指定流的起始位置,同时清除流的错误提示标志。
13.改变缓冲方式
在流上执行的缓冲方式有时并不合适,下面两个函数可以用于对缓冲方式进行修改。这两个函数只有当指定的流被打开但还没有在它上面执行任何操作前才能被调用。
void setbuf( FILE *stream, char *buf ); int setvbuf( FILE *stream, char *buf, int mode, size_t size );
setbuf设置了另一个数组,用于对流进行缓冲。这个数组的字符长度必须是BUFSIZ(定义于stdio.h)。为一个流自行指定缓冲区可以防止I/O函数库为它动态分配一个缓冲区。如果用一个NULL参数调用该函数,setbuf将关闭流的所有缓冲方式,字符准确地将程序所规划的方式进行读取和写入。
setvbuf函数中,mode参数用于指定缓冲的类型。_IOFBF指定一个完全缓冲的流,_IONBF指定一个不缓冲的流,_IOLBF指定一个行缓冲的流。所谓行缓冲,就是每当一个换行符写入到缓冲区,缓冲区就进行刷新。
14.流错误函数
int feof( FILE *stream ); int ferror( FILE *stream ); void clearerr( FILE *stream );
如果流当前处于文件尾,feof函数返回真。这个状态可以通过对流执行fseek、rewind或fsetpos函数来清除。ferror函数报告流的错误状态,如果出现任何读/写错误函数就返回真。最后,clearerr函数对指定流的错误标志进行重置。
15.临时文件
FILE *tmpfile( void );
这个函数创建一个文件,当文件被关闭或程序终止时,这个文件便自动删除。这个文件以wb+模式打开,这使它可用于二进制和文本数据。临时文件的名字可以用tmpnam函数创建,它的原型如下:
char *tmpnam( char *name );
如果传递给函数的参数为NULL,那么这个函数便返回一个指向静态数组的指针,该数组包含了被创建的文件名。否则,参数便假定是一个指向长度至少为L_tmpnam的字符数组的指针。在这个情况下,文件名在这个数组中创建,返回值就是这个参数。
16.文件操纵函数
int remove( char const *filename ); int rename( char const *oldname, char const *newname );
成功返回0值,失败返回非0值。
数据类型 | 输入 | 输出 | 描述 | |
---|---|---|---|---|
家族名 | 目的 | 可用于所有的流 | 只用于stdin和stdout | 内存中的字符串 |
格式码 | h | l | L | |
代码 | 参数 | 含义 | ||
代码 | 参数 | 含义 | ||
标志 | 含义 | |||
from | 定位 |