fopen/fclose
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
返回值:成功返回文件指针,出错返回NULL并设置errno
int fclose(FILE *fp);
返回值:成功返回0,出错返回EOF并设置errno
mode
参数是一个字符串,由rwatb+
六个字符组合而成,
r
表示读,w
表示写,a
表示追加(Append),在文件末尾追加数据使文件的尺寸增大。t
表示文本文件,b
表示二进制文件,有些操作系统的文本文件和二进制文件格式不同,而在UNIX系统中,无论文本文件还是二进制文件都是由一串字节组成,t
和b
没有区分,用哪个都一样,也可以省略不写。如果省略t
和b
,rwa+
四个字符有以下6种合法的组合:
“r” | 只读,文件必须已存在 |
---|---|
”w“ | 只写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写,也就是替换掉原来的文件内容 |
”a“ | 只能再文件末尾追加数据,如果文件不存在则创建 |
”r+“ | 允许读和写,文件必须存在 |
”w+“ | 允许读和写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写 |
”a+“ | 允许读和追加数据,如果文件不存在则创建 |
stdin/stdout/stderr
我们经常用printf
打印到屏幕,也用过scanf
读键盘输入,这些也属于I/O操作,但不是对文件做I/O操作而是对终端设备做I/O操作。
那为什么printf
和scanf
不用打开就能对终端设备进行操作呢?因为在程序启动时(在main
函数还没开始执行之前)会自动把终端设备打开三次,分别赋给三个FILE *
指针stdin
、stdout
和stderr
,这三个文件指针是libc
中定义的全局变量,在stdio.h
中声明,printf
向stdout
写,而scanf
从stdin
读,后面我们会看到,用户程序也可以直接使用这三个文件指针。这三个文件指针的打开方式都是可读可写的,但通常stdin
只用于读操作,称为标准输入(Standard Input),stdout
只用于写操作,称为标准输出(Standard Output),stderr
也只用于写操作,称为标准错误输出(Standard Error),通常程序的运行结果打印到标准输出,而错误提示(例如gcc
报的警告和错误)打印到标准错误输出,所以fopen
的错误处理写成这样更符合惯例:
if ( (fp = fopen("/tmp/file1", "r")) == NULL) {
fputs("Error open file /tmp/file1\n", stderr);
exit(1);
}
errno与perror函数
很多系统函数在错误返回时将错误原因记录在libc
定义的全局变量errno
中,每种错误原因对应一个错误码,请查阅errno(3)
的Man Page了解各种错误码,errno
在头文件errno.h
中声明,是一个整型变量,所有错误码都是正整数。
如果在程序中打印错误信息时直接打印errno
变量,打印出来的只是一个整数值,仍然看不出是什么错误。比较好的办法是用perror
或strerror
函数将errno
解释成字符串再打印。
#include <stdio.h>
void perror(const char *s);
perror
函数将错误信息打印到标准错误输出,首先打印参数s
所指的字符串,然后打印:号,然后根据当前errno
的值打印错误原因。例如:
FILE *fp = fopen("abcde", "r");
if (fp == NULL)
{
perror("Open file abcde");
exit(1);
}
如果文件abcde
不存在,fopen
返回-1并设置errno
为ENOENT
,紧接着perror
函数读取errno
的值,将ENOENT
解释成字符串No such file or directory
并打印,最后打印的结果是Open file abcde: No such file or directory
。
大多数系统函数都有一个Side Effect,就是有可能改变errno
变量(当然也有少数例外,比如strcpy
),所以一个系统函数错误返回后应该马上检查errno
,在检查errno
之前不能再调用其它系统函数。
strerror
函数可以根据错误号返回错误原因字符串。
#include <string.h>
char *strerror(int errnum);
返回值:错误码errnum所对应的字符串
有些函数的错误码并不保存在errno
中,而是通过返回值返回,就不能调用perror
打印错误原因了,这时strerror
就派上了用场:
以字节为单位的I/O函数
fgetc
函数从指定的文件中读一个字节,getchar
从标准输入读一个字节,调用getchar()
相当于调用fgetc(stdin)
。
#include <stdio.h>
int fgetc(FILE *stream);
int getchar(void);
返回值:成功返回读到的字节,出错或者读到文件末尾时返回EOF
对于fgetc函数的使用有以下几点说明:
- 要用
fgetc
函数读一个文件,该文件的打开方式必须是可读的。 - 系统对于每个打开的文件都记录着当前读写位置在文件中的地址(或者说距离文件开头的字节数),也叫偏移量(Offset)。当文件打开时,读写位置是0,每调用一次
fgetc
,读写位置向后移动一个字节,因此可以连续多次调用fgetc
函数依次读取多个字节。 fgetc
成功时返回读到一个字节,本来应该是unsigned char
型的,但由于函数原型中返回值是int
型,所以这个字节要转换成int
型再返回,那为什么要规定返回值是int
型呢?因为出错或读到文件末尾时fgetc
将返回EOF
,即-1,保存在int
型的返回值中是0xffffffff,如果读到字节0xff,由unsigned char
型转换为int
型是0x000000ff,只有规定返回值是int
型才能把这两种情况区分开,如果规定返回值是unsigned char
型,那么当返回值是0xff时无法区分到底是EOF
还是字节0xff。如果需要保存fgetc
的返回值,一定要保存在int
型变量中,如果写成unsigned char c = fgetc(fp);
,那么根据c
的值又无法区分EOF
和0xff字节了。注意,fgetc
读到文件末尾时返回EOF
,只是用这个返回值表示已读到文件末尾,并不是说每个文件末尾都有一个字节是EOF
(根据上面的分析,EOF并不是一个字节)。
fputc
函数向指定的文件写一个字节,putchar
向标准输出写一个字节,调用putchar(c)
相当于调用fputc(c, stdout)
。
#include <stdio.h>
int fputc(int c, FILE *stream);
int putchar(int c);
返回值:成功返回写入的字节,出错返回EOF
对于fputc
函数的使用也要说明几点:
- 要用
fputc
函数写一个文件,该文件的打开方式必须是可写的(包括追加)。 - 每调用一次
fputc
,读写位置向后移动一个字节,因此可以连续多次调用fputc
函数依次写入多个字节。但如果文件是以追加方式打开的,每次调用fputc
时总是将读写位置移到文件末尾然后把要写入的字节追加到后面。
操作读写位置的函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
返回值:成功返回0,出错返回-1并设置errno
long ftell(FILE *stream);
返回值:成功返回当前读写位置,出错返回-1并设置errno
void rewind(FILE *stream);//把读写位置移到文件开头
fseek
的whence
和offset
参数共同决定了读写位置移动到何处,whence
参数的含义如下:
SEEK_SET
从文件开头移动offset
个字节
SEEK_CUR
从当前位置移动offset
个字节
SEEK_END
从文件末尾移动offset
个字节
offset
可正可负,负值表示向前(向文件开头的方向)移动,正值表示向后(向文件末尾的方向)移动,如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸,从原来的文件末尾到fseek
移动之后的读写位置之间的字节都是0。
以字符串为单位的I/O函数
fgets
从指定的文件中读一行字符到调用者提供的缓冲区中,gets
从标准输入读一行字符到调用者提供的缓冲区中。
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
返回值:成功时s指向哪返回的指针就指向哪,出错或者读到文件末尾时返回NULL
参数s
是缓冲区的首地址,size
是缓冲区的长度,该函数从stream
所指的文件中读取以'\n'
结尾的一行(包括'\n'
在内)存到缓冲区s
中,并且在该行末尾添加一个'\0'
组成完整的字符串。如果文件中的一行太长,fgets
从文件中读了size-1
个字符还没有读到'\n'
,就把已经读到的size-1
个字符和一个'\0'
字符存入缓冲区,文件中剩下的半行可以在下次调用fgets
时继续读。
fputs
向指定的文件写入一个字符串,puts
向标准输出写入一个字符串。
#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
返回值:成功返回一个非负整数,出错返回EOF
以记录为单位的I/O函数
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
返回值:读或写的记录数,成功时返回的记录数等于nmemb,出错或读到文件末尾时返回的记录数小于nmemb,也可能返回0
fread
和fwrite
用于读写记录,这里的记录是指一串固定长度的字节,比如一个int
、一个结构体或者一个定长数组。参数size
指出一条记录的长度,而nmemb
指出要读或写多少条记录,这些记录在ptr
所指的内存空间中连续存放,共占size * nmemb
个字节,fread
从文件stream
中读出size * nmemb
个字节保存到ptr
中,而fwrite
把ptr
中的size * nmemb
个字节写到文件stream
中。
nmemb
是请求读或写的记录数,fread
和fwrite
返回的记录数有可能小于nmemb
指定的记录数。例如当前读写位置距文件末尾只有一条记录的长度,调用fread
时指定nmemb
为2,则返回值为1。如果当前读写位置已经在文件末尾了,或者读文件时出错了,则fread
返回0。如果写文件时出错了,则fwrite
的返回值小于nmemb
指定的值。
格式化I/O函数
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
返回值:成功返回格式化输出的字节数(不包括字符串的结尾'\0'),出错返回一个负值
printf
格式化打印到标准输出,
fprintf
打印到指定的文件stream
中。
sprintf
并不打印到文件,而是打印到用户提供的缓冲区str
中并在末尾加'\0'
,由于格式化后的字符串长度很难预计,所以很可能造成缓冲区溢出,用snprintf
更好一些,参数size
指定了缓冲区长度,如果格式化后的字符串长度超过缓冲区长度,
snprintf
就把字符串截断到size-1
字节,再加上一个'\0'
写入缓冲区,也就是说snprintf
保证字符串以'\0'
结尾。snprintf
的返回值是格式化后的字符串长度(不包括结尾的'\0'
),如果字符串被截断,返回的是截断之前的长度,把它和实际缓冲区中的字符串长度相比较就可以知道是否发生了截断。
选项 | 描述 | 举例 |
---|---|---|
# | 八进制前面加0(转换字符为o ),十六进制前面加0x(转换字符为x )或0X(转换字符为X )。 | printf(“%#x”, 0xff) 打印0xff ,printf(“%x”, 0xff) 打印ff 。 |
- | 格式化后的内容居左,右边可以留空格。 | 见下面的例子 |
宽度 | 用一个整数指定格式化后的最小长度,如果格式化后的内容没有这么长,可以在左边留空格,如果前面指定了- 号就在右边留空格。宽度有一种特别的形式,不指定整数值而是写成一个``号,表示取一个int 型参数作为宽度。 | printf(“-%10s-“, “hello”) 打印-␣␣␣␣␣hello- ,printf(“-%-s-“, 10, “hello”) 打印-hello␣␣␣␣␣- 。 |
. | 用于分隔上一条提到的最小长度和下一条要讲的精度。 | 见下面的例子 |
精度 | 用一个整数表示精度,对于字符串来说指定了格式化后保留的最大长度,对于浮点数来说指定了格式化后小数点右边的位数,对于整数来说指定了格式化后的最小位数。精度也可以不指定整数值而是写成一个``号,表示取下一个int 型参数作为精度。 | printf(“%.4s”, “hello”) 打印hell ,printf(“-%6.4d-“, 100) 打印-␣␣0100- ,printf(“-%.*f-“, 8, 4, 3.14) 打印-␣␣3.1400- 。 |
字长 | 对于整型参数,hh 、h 、l 、ll 分别表示是char 、short 、long 、long long 型的字长,至于是有符号数还是无符号数则取决于转换字符;对于浮点型参数,L 表示long double 型的字长。 | printf(“%hhd”, 255) 打印-1 。 |
转换字符 | 描述 | 举例 |
---|---|---|
d i | 取int 型参数格式化成有符号十进制表示,如果格式化后的位数小于指定的精度,就在左边补0。 | printf(“%.4d”, 100) 打印0100 。 |
o u x X | 取unsigned int 型参数格式化成无符号八进制(o)、十进制(u)、十六进制(x或X)表示,x表示十六进制数字用小写abcdef,X表示十六进制数字用大写ABCDEF,如果格式化后的位数小于指定的精度,就在左边补0。 | printf(“%#X”, 0xdeadbeef) 打印0XDEADBEEF ,printf(“%hhu”, -1) 打印255 。 |
c | 取int 型参数转换成unsigned char 型,格式化成对应的ASCII码字符。 | printf(“%c”, 256+’A’) 打印A 。 |
s | 取const char 型参数所指向的字符串格式化输出,遇到‘\0’ 结束,或者达到指定的最大长度(精度)结束。 | printf(“%.4s”, “hello”) 打印hell 。 |
p | 取void 型参数格式化成十六进制表示。相当于%#x 。 | printf(“%p”, main) 打印main 函数的首地址0x80483c4 。 |
f | 取double 型参数格式化成[-]ddd.ddd 这样的格式,小数点后的默认精度是6位。 | printf(“%f”, 3.14) 打印3.140000 ,printf(“%f”, 0.00000314) 打印0.000003 。 |