【C】标准IO库函数

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系统中,无论文本文件还是二进制文件都是由一串字节组成,tb没有区分,用哪个都一样,也可以省略不写。如果省略tbrwa+四个字符有以下6种合法的组合:

“r”只读,文件必须已存在
”w“只写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写,也就是替换掉原来的文件内容
”a“只能再文件末尾追加数据,如果文件不存在则创建
”r+“允许读和写,文件必须存在
”w+“允许读和写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写
”a+“允许读和追加数据,如果文件不存在则创建

stdin/stdout/stderr

我们经常用printf打印到屏幕,也用过scanf读键盘输入,这些也属于I/O操作,但不是对文件做I/O操作而是对终端设备做I/O操作。

那为什么printfscanf不用打开就能对终端设备进行操作呢?因为在程序启动时(在main函数还没开始执行之前)会自动把终端设备打开三次,分别赋给三个FILE *指针stdinstdoutstderr,这三个文件指针是libc中定义的全局变量,在stdio.h中声明,printfstdout写,而scanfstdin读,后面我们会看到,用户程序也可以直接使用这三个文件指针。这三个文件指针的打开方式都是可读可写的,但通常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变量,打印出来的只是一个整数值,仍然看不出是什么错误。比较好的办法是用perrorstrerror函数将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并设置errnoENOENT,紧接着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);//把读写位置移到文件开头

fseekwhenceoffset参数共同决定了读写位置移动到何处,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

freadfwrite用于读写记录,这里的记录是指一串固定长度的字节,比如一个int、一个结构体或者一个定长数组。参数size指出一条记录的长度,而nmemb指出要读或写多少条记录,这些记录在ptr所指的内存空间中连续存放,共占size * nmemb个字节,fread从文件stream中读出size * nmemb个字节保存到ptr中,而fwriteptr中的size * nmemb个字节写到文件stream中。

nmemb是请求读或写的记录数,freadfwrite返回的记录数有可能小于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)打印0xffprintf(“%x”, 0xff)打印ff
-格式化后的内容居左,右边可以留空格。见下面的例子
宽度用一个整数指定格式化后的最小长度,如果格式化后的内容没有这么长,可以在左边留空格,如果前面指定了-号就在右边留空格。宽度有一种特别的形式,不指定整数值而是写成一个``号,表示取一个int型参数作为宽度。printf(“-%10s-“, “hello”)打印-␣␣␣␣␣hello-printf(“-%-s-“, 10, “hello”)打印-hello␣␣␣␣␣-
.用于分隔上一条提到的最小长度和下一条要讲的精度。见下面的例子
精度用一个整数表示精度,对于字符串来说指定了格式化后保留的最大长度,对于浮点数来说指定了格式化后小数点右边的位数,对于整数来说指定了格式化后的最小位数。精度也可以不指定整数值而是写成一个``号,表示取下一个int型参数作为精度。printf(“%.4s”, “hello”)打印hellprintf(“-%6.4d-“, 100)打印-␣␣0100-printf(“-%.*f-“, 8, 4, 3.14)打印-␣␣3.1400-
字长对于整型参数,hhhlll分别表示是charshortlonglong long型的字长,至于是有符号数还是无符号数则取决于转换字符;对于浮点型参数,L表示long double型的字长。printf(“%hhd”, 255)打印-1
转换字符描述举例
d iint型参数格式化成有符号十进制表示,如果格式化后的位数小于指定的精度,就在左边补0。printf(“%.4d”, 100)打印0100
o u x Xunsigned int型参数格式化成无符号八进制(o)、十进制(u)、十六进制(x或X)表示,x表示十六进制数字用小写abcdef,X表示十六进制数字用大写ABCDEF,如果格式化后的位数小于指定的精度,就在左边补0。printf(“%#X”, 0xdeadbeef)打印0XDEADBEEFprintf(“%hhu”, -1)打印255
cint型参数转换成unsigned char型,格式化成对应的ASCII码字符。printf(“%c”, 256+’A’)打印A
sconst char 型参数所指向的字符串格式化输出,遇到‘\0’结束,或者达到指定的最大长度(精度)结束。printf(“%.4s”, “hello”)打印hell
pvoid 型参数格式化成十六进制表示。相当于%#xprintf(“%p”, main)打印main函数的首地址0x80483c4
fdouble型参数格式化成[-]ddd.ddd这样的格式,小数点后的默认精度是6位。printf(“%f”, 3.14)打印3.140000printf(“%f”, 0.00000314)打印0.000003
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值