C复习-输入输出函数+流
参考: 里科《C和指针》
perror
定义在stdio.h中。当一个库函数失败时,库函数会在一个外部整型变量errno(在errno.h中定义)中保存错误代码,然后传递给用户程序,此时使用perror,会在打印msg后再打印出一条用于解释errno当前错误代码的信息。如果库函数成功运行,errno的值不会被修改,所以只有当运行失败时,检查errno的值才有意义。
// 如果msg不是NULL且指向一个非空字符串,perror会打印msg,后跟一个分号和一个空格
// 再跟errno对应的信息
void perror(char const *msg);
exit
原型在stdlib.h。status返回给操作系统,预定义符号EXIT_SUCCESS和EXIT_FAILURE分别提示成功和失败。通常在调用perrno后调用exit终止。
void exit( int status );
标准I/O函数库
fflush迫使缓冲区的数据立即写入,不管它是否已满。
流分为文本流和二进制流。文本流在不同系统中可能有不同的最大长度和文本行结束方式(比如MS-DOS是\r\n,Unix是\n)。二进制流适合非文本数据,或者不希望I/O函数修改文本文件的行末字符时。
stdio.h包含FILE结构的声明,它用来访问一个流。如果同时激活了几个流,每个流都有一个相应的FILE与之关联。为了在流上执行操作,必须调用合适的函数,并传递一个FILE参数。
对于每个ANSI C程序,运行时,系统必须提供至少三个流:标准输入(stdin),标准输出(stdout)和标准错误(stderr),他们都是一个指向FILE结构的指针,标准输入是缺省的输入来源,比如键盘;标准输出是缺省的输出设置,比如终端。可以进行重定向,比如下面是执行program时,输入使用data,输出保存到answer
$program < data > answer
FOPEN_MAX:指一个程序最多同时打开几个文件。与编译器有关,> = 8
FILENAME_MAX:指文件名最大长度
输入/输出函数家族
家族名 | 目的 | 可用于所有的流 | 只用于stdin和stdout | 内存中的字符串 |
---|---|---|---|---|
getchar | 字符输入 | fgetc getc | getchar | ① |
putchar | 字符输出 | fputc putc | putchar | ① |
gets | 文本行输入 | fgets | gets | ② |
puts | 文本行输出 | fputs | puts | ② |
scanf | 格式化输入 | fscanf | scanf | sscanf |
printf | 格式化输出 | fprintf | printf | sprintf |
①对指针使用下标引用或者间接访问操作从内存中获得一个字符或写出一个字符
②使用strcpy从内存读取文本行或向内存写出文本行
打开流
写入和添加的区别在于,如果原本存在文件,那么写入会先清空内容,添加则不会。数据只能从文件的尾部写入。
fopen执行成功,返回一个指向FILE结构的指针;如果失败,返回NULL,errno会解释原因。
需要始终检查fopen的返回值。
FILE *fopen( char const *name, char const *mode );
FILE *input;
input = fopen("data3", "r");
if (input == NULL) {
perror("data3");
exit(EXIT_FAILURE); // data3: No such file or directory
}
读取(要求原先已经存在) | 写入(如果原来不存在创建;如果原来存在,删除内容) | 添加 | |
---|---|---|---|
文本 | r | w | a |
二进制 | rb | wb | ab |
mode中a+表示改文件打开用于更新,允许读和写,但如果已经读入了一些数据,那么在写数据前,必须先定位;当已经写了一些数据,又想读时,需要调用fflush或者某个文件定位函数。
freopen
stream可以是一个先前从fopen返回的流,也可以是stdin/stdout/stderr。freopen首先试图关闭stream,然后用指定的文件和模式重新打开这个流,如果失败,返回NULL,成功则返回stream。
FILE *freopen( char const *filename, char const *mode, FILE *stream );
关闭流
fclose在文件关闭前会刷新缓冲区,成功的话返回0,否则返回EOF。
fclose也是可能失败的,如果操作成功和操作失败需要执行不同的行为,那么需要检查fclose的结果。
int fclose( FILE *f );
字符I/O
字符输入是由getchar函数家族执行的,如果读到了就返回,否则返回EOF。(返回int是因为如果返回字符的话,256个字符中需要有一个指定为EOF,那如果这个字符出现在文字内部,读取就会中断)
int fgetc( FILE *stream ); // 只能作为函数使用
int getc( FILE *stream ); // 是宏,所以stream不能放表达式,因为可能有副作用
int getchar( void ); // 始终从标准输入读取
字符输出是由putchar函数家族执行的,如果character放的是一个’abc’这样的字符串,那么使用哪个字符是不一定的,由编译器决定。如果函数失败返回EOF(比如stream关闭了)。
int fputc( int character, FILE *stream ); // 是真正的函数
int putc( int character, FILE *stream );
int putchar( int character ); // 始终放到标准输出
fgetc和fputc是真正的函数,getc、putc、getchar、putchar都是#define定义的宏,不过实际操作中,程序长度和执行速度都差别不大。
使用ungetc可以把一个先前读入的字符返回到流中,后续可以被重新读入。每个流都允许至少一个字符被退回,如果允许退回多个,再读取时的顺序是退回时的反序。与一个流相关的外部存储不受ungetc的影响。
退回和流当前的位置有关,如果用了fseek、fsetpos或rewind改变了流的位置,所有退回的字符会被丢弃。
int ungetc( int character, FILE *stream );
比如从标准输入读取一段字符(如78xxs),只读取数字并转化为整数
#include <stdio.h>
#include <ctype.h>
int
read_int() {
int value;
int ch;
value = 0;
// 遇到数字停下
while( ( ch = getchar() ) != EOF && isdigit( ch ) ) {
value *= 10;
value += ch - '0';
}
// 不是数字就退回
ungetc( ch, stdin );
return value;
}
未格式化的行I/O
只是简单读取或写入字符串,不做形式转换。gets和puts家族用于操作字符串。
char *fgets( char *buffer, int buffer_szie, FILE *stream );
char *gets( char *buffer ); // 允许兼容;不会在buffer中存储结尾换行符
// gets不知道缓冲区长度,所以长行放入短缓冲区,会破坏内存,但是声明巨大缓冲区也可能有
// 更长的行,所以最好不用
int fputs( char const *buffer, FILE *stream );
int puts( char const *buffer ); // 允许兼容,会向输出添加换行符
fgets从指定的流读取字符并复制到buffer中,当读到一个换行符并存储到buffer后就停止读取。如果buffer内存储的字符数达到buffer_size-1个时,也停止读取,当下一次调用fgets时,会从流的下一个字符开始读取。在任何情况下,一个NUL字节会被添加到buffer所存储数据的末尾,使之成为一个字符串。如果在任何字符读取前就到达了文件尾,缓冲区就不需要做更改,所以fgets返回NULL;否则返回指向buffer的指针。
buffer_size是程序员要检查的,因为缓冲区溢出不会报错,如下例。
char myStr[3];
char* ptr = myStr;
// 这里虽然不会报错,但是编译器会有warning缓冲区溢出
ptr= fgets(myStr, 4, stdin);
printf("string is:\n");
while (*ptr != '\0') {
printf("%c", *ptr);
ptr++;
}
// 实际上myStr只能存1个非\n的字符,因为先存一个字符,然后存换行符,此时就停止读取了
传递给fputs的buffer必须包含一个字符串(以NUL结尾),写入是逐字写入,换行符也会被写入。如果写入时出现错误,返回EOF,否则返回一个非负值。
// 其实这个定义主要影响行的计数,因为不论大小都会被正常写出到另一个文件
// 但是如果缓冲区比较小,fputs的次数就比较多,那只能通过每行中的\n数目计算
#define MAX_LINE_LENGTH 1024
/*
** 从一个文件向另一个文件复制行
*/
void
copylines( FILE *input, FILE *output)
{
char buffer[MAX_LINE_LENGTH];
while ( fgets(buffer, MAX_LINE_LENGTH, input) != NULL )
fputs( buffer, output );
}
格式化的I/O
scanf家族
…表示可变长度的指针列表,从输入转换来的值,逐个存储到这些指针参数所指向的内存位置(所以scanf的参数前要加&,因为传的是指针)。
当格式化字符串到达末尾或者读取的输入不匹配格式时,输入停止,此时返回输入值的数目。如果在任何输入值被转换前,文件已经到达尾部,则返回EOF。
函数不会检查格式和指针参数是否匹配。
int fscanf(FILE *stream, char const *format, ... );
int scanf( char const *format, ... ); // 从stdin读取
int sscanf( char const *string, char const *format, ... ); // 从string中读取
format格式
%[*][width][modifiers]格式字符
1)*表示不存储转换后的值,直接丢弃(用来跳过不需要的输入字符)
2)width指定当前读取的最大字符数,如果不给出,则连续读入直到遇到下一个空白字符
3)modifiers:如果不是int,要用
格式字符 | h | l | L |
---|---|---|---|
d,i,n | short | long | |
o,u,x | unsigned short | unsigned long | |
e,f,g | double | long double |
scanf的格式字符
代码 | 参数 | 含义 | 举例 |
---|---|---|---|
c | char* | 读取和存储单个字符,前导空白字符不会跳过。字符后面不会添加一个NUL,参数必须指向一个足够大的字符数组 | |
i d | int * | d是解释为十进制,i可以读入8、10、16进制 | scanf(“%i”, &i); // 输入0xA |
printf(“%d”, i); // 输出10 | |||
u o x X | unsigned * | 按无符号存储,uox分别解释为10、8、16进制,X和x同义 | |
e f g E F G | float * | 小数点非必需,可以有前置+/-,可以有后置e/E。 | |
s | char * | 遇到空白时输入即停止,字符串后面自动加上NUL | |
[xxx] | char * | 匹配[]内的字符,如果不匹配即停止,字符串后自动加NUL | %[abc]表示接受输入abc中任何一个字符;%[^abc]指非abc,]也可以用,但必须放第一位,如果是需要某个范围,如%[a-z]因编译器而定 |
p | void * | 内存地址 | |
n | int * | %n转换的字符不计算在scanf的返回值内 | |
% | 与输入中的一个%匹配,该%将被丢弃 |
printf家族
返回值是实际打印或存储的字符数。
int fprintf( FILE *stream, char const *format, ... ); // 输出到stream
int printf( char const *format, ... );
// 不知道buffer的长度,但可以通过限制生成字符串的宽度来使buffer够大
int sprintf( char *buffer, char const *format, ... ); // 转换后存到buffer中
format格式
%[flags][width][.precision][length]specifier
flags
标志 | 含义 |
---|---|
- | 值左对齐,默认是右对齐 |
0 | 值右对齐时,缺省情况下使用空格填充左边,0表示用0填充;如果diuox给出了精度,0会被忽略 |
+ | 如果某个有符号值是非负的,会加一个+。缺省,不显示+ |
空格 | 只用于转换有符号值的代码,当值是非负,前面会加一个空格。空格与+是互斥的,如果同时给出,应用+ |
# | 选择某些代码的另一种转换形式 |
width是十进制整数,用于指定将出现在结果中的最小字符数。对于diuoxX,如果经过精度转换后,值的长度还是小于宽度,会在前面插入0,如果值是0,精度也是0,转换后是空的,没有数字
int i = 0;
printf("*%.0d*", i); // 输出**
如果用于表示字段宽度和(或)精度的十进制整数由*代替,那么printf的下一个参数必须是整数,且能提供宽度和(或)精度。
未指定精度时,默认为1,如果指定时不带值,则假定为0
printf的格式字符
代码 | 参数 | 含义 | 举例 |
---|---|---|---|
c | int | 参数被裁剪为unsigned char并作为字符打印 | |
d,i | int | d:参数作为十进制整数打印,如果给出了精度但值的位数少于精度位数,前面用0填充(-不占长度);i是8和16进制 | |
u,o,x,X | unsigned int | 跟scanf的一样含义,X是十六进制,用A~F | |
e,E | double | 打印指数形式。小数点后面的位数由精度决定,缺省是6 | 6.023000e23是用e,大写E则打印大写的 |
f | double | 打印浮点形式,精度决定小数点后位数,缺省6 | |
g,G | double | 参数以%f或%e(G是%E)形式打印。当指数大于等于-4但小于精度字段用%f,否则用指数格式 | 这个不太对,比如double i = 1,按%g输出是1,不同于%f和%e。参考后面的例子,更像是以更短的形式输出,如果是整数就输出整数。 |
s | char * | 打印一个字符串。如果字符串超过宽度,完全打印 | 精度指定最多字符数 |
p | void * | 打印指针地址 | |
n | int * | 不产生输出 | |
% | 打印一个% |
例子
int i = 10;
printf("%.3d", i); // 010
%.4d // -12 -> -0012
%6.4d // 1 -> **0001, *表示空格
%6.4d // -12 -> *-0012
printf格式代码修改符
修改符号 | 用于 | 表示参数是 |
---|---|---|
h | d,i,u,o,x,X | 一个short型整数 |
h | n | 一个指向short型整数的指针 |
l | d,i,u,o,x,X | long型整数 |
l | n | 指向long型整数的指针 |
L | e,E,f,gG | long double |
#的转换
用于 | 含义 | 举例 |
---|---|---|
o | 保证产生的值以0开头 | |
x,X | 在非0值前加0x前缀,%X则是0X | |
e,E,f | 确保结果始终包含一个小数点,即使它后面没有数字 | |
g,G | 确保有小数点。后缀0不会去除 | double i = 0.230e5;printf(“%#g”, i); // 23000.0 |
实例
其中*表示空格
格式代码 | 1 | .01 | .00012345 | 12345.6789 | 6.023e23 |
---|---|---|---|---|---|
%f | 1.000000 | 0.010000 | 0.00123 | 12345.678900 | 602299999999999975882752.000000 |
%10.2f | ******1.00 | ******0.01 | ******0.00 | **12345.68 | 602299999999999975882752.00 |
%e | 1.000000e+00 | 1.000000e-02 | 1.234500e-04 | 1.234568e+04 | 6.023000e+23 |
%.4e | 1.0000e+00 | 1.0000e-02 | 1.2345e-04 | 1.2346e+04 | 6.0230e+23 |
%g | 1 | 0.01 | 0.00012345 | 12345.7 | 6.023e+23 |
二进制I/O
buffer是一个指向保存数据的内存位置的指针,size是buffer中每个元素的字节数,count是读取或写入的元素数,stream是读取或写入的流。返回值是实际读取或写入的元素数目。
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 );
刷新和定位函数
需要立刻把输出缓冲区的数据进行物理写入时,用fflush。例如,调用fflush保证调试信息实际打印出来,而不是保存在buffer中等候打印。
int fflush( FILE *stream );
ftell返回流的当前位置,在二进制流中,表示的是当前位置距离文件起始位置之间的字节数;在文本流中,不一定表示字符数(因为有的os会转换行末字符),但是ftell的返回值总可以用在fseek中。
long ftell( FILE *stream );
int fseek( FILE *stream, long offset, int from );
fseek的from参数说明
from | 含义 | 说明 |
---|---|---|
SEEK_SET | 从流的起始位置起offset个字节,offset≥0 | 需要提前对同一个流调用ftell,用那个值做offset |
SEEK_CUR | 从流的当前位置起offset个字节,offset可正可负 | 文本流中from是cur或者end,offset必须是0 |
SEEK_END | 从流的尾部位置起offset个字节,offset可正可负 | 二进制流中可能不被支持,应避免 |
用fseek改变一个流的位置可能有副作用:
- 行末只是字符被清除
- 如果在fseek前使用ungetc把一个字符返回到流中,那么这个被退回的字符会被丢弃
- 定位允许从写入模式切换到读取模式,或者回到打开的流以便更新
void rewind( FILE *stream ); // 将读/写指针设置回指定流的起始位置,并清除流的错误提示标志
// fpos_t表示文件位置,非标准定义,安全的用法是fgetpos获取值,然后传递给fsetpos
int fgetpos( FILE *stream, fpos_t *position ); // 替代ftell
int fsetpos( FILE *stream, fpos_t const *position ); // 替代fseek
从一个文件读取一个特定的记录:
#include <stdio.h>
#include "student_info.h"
int
read_random_record( FILE *f, size_t rec_number, StudentInfo *buffer)
{
fseek(f, (long)rec_number * sizeof( StudentInfo ), SEEK_SET );
return fread( buffer, sizeof( StudentInfo ), 1, f );
}
改变缓冲方式
setbuf设置了另一个数组buf对流进行缓冲,这个数组的长度必须为BUFSIZ(stdio.h中定义)。为一个流自行制定缓冲区可以防止I/O函数库为它动态分配一个缓冲区。如果buf==NULL,setbuf回关闭流的所有缓冲方式
void setbuf( FILE *stream, char *buf );
// mode指定缓冲类型:_IOFBF 完全缓冲的流,_IONBF 不缓冲的流,_IOLBF 行缓冲流
// 行缓冲指当换行符写入缓冲区时,缓冲区刷新
// if buf==NULL, size必须是0. 最好用长度为BUFSIZ的字符数组作缓冲区,或者是其整数倍
int setvbuf( FILE *stream, char *buf, int mode, size_t size ); // 更通用
流错误函数
如果流处于文件尾,feof返回真,这个状态可以通过对流执行fseek、rewind或fsetpos来清除。ferror是报告流的错误状态,如果出现任何读/写错误函数就返回真。clearerr是重置指定流的错误标志。
int feof( FILE *stream );
int ferror( FILE *stream );
void clearerr( FILE *stream );
临时文件
tmpfile可以创建一个文件,当文件被关闭或者程序终止时这个文件回自动删除,打开方式是wb+,因此可以用于二进制和文本数据。如果需要以其他模式打开或者由其他程序读取,不能用tmpfile,只能用fopen+remove。
临时文件的名字可以用tmpnam创建,如果name==NULL,则返回值是一个指向静态数组的指针,该数组包含了被创建的文件名;否则就是name。只要调用次数不超过TMP_MAX次,tmpnam每次调用时都能产生一个新名字
FILE *tmpfile( void );
char *tmpnam( char *name );
文件的删除和重命名
如果remove被调用时文件处于打开状态,其结果取决于编译器。rename如果新名字跟其他文件重名,其结果也取决于编译器。
int remove( char const *filename );
int rename( char const *oldname, char const *newname );