18、标准IO库详解及实例
标准IO库是由Dennis Ritchie于1975年左右编写的,它是Mike Lestbain写的可移植IO库的主要修改版本,2010年以后, 标准IO库几乎没有进行什么修改。标准IO库处理了很多细节,如缓冲区分配、以优化的块长度执行IO等,用户不必在担心不能正确选择块长度,这些处理方便了用户的使用。与系统调用I/O相似,也包括打开、读写、关闭这些操作,主要的函数列举如下。
◆ 打开与关闭文件:fopen,fclose。
◆ 读写文件:fread,fwrite。
◆ 读写文本行:fgets,fputs。
◆ 格式化读写:fscanf,fprintf。
◆ 标准输入输出:printf,scanf。
◆ 读写字符:fgetc,getc,getchar,fputc,putc,putchar。
◆ 其他:fflush,fseek。
1、fopen()与fclose()
fopen()、fdopen()和freopen()三个函数用于打开一个标准IO流,其函数原型如下:
#include <stdio.h> FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd,const char *mode); FILE *freopen(const char *path, const char *mode,FILE *stream);
参数mode用于定义打开文件的访问权限。下面描述了mode的取值,有如下几种方式:
- "r" 以只读方式打开文件,该文件必须存在。
- "w" 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
- "w+" 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
- "a" 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
- "a+" 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留)
- "wb" 只写打开或新建一个二进制文件,只允许写数据。
- "wb+" 读写打开或建立一个二进制文件,允许读和写。
- "ab" 追加打开一个二进制文件,并在文件末尾写数据。
- "ab+"读写打开一个二进制文件,允许读,或在文件末追加数据。
后面加b字符可以表示打开的文件为二进制,而不是纯文本文件,
函数fopen()打开由参数path指定路径名的一个文件。函数fdopen()取一个已有的文件描述符,并使一个标准的IO流与该描述符项结合。函数freopen()在一个指定的流上打开一个指定的文件,若该流已经打开,则先关闭该流;若该流已经定向,则清除改定向。
一旦打开了流,即可对其进行读写操作,读写操作可在三种不同类型的非格式化IO中进行选择:
- 每次一个字符的io。一次读写一个字符,如果流带缓存,则标准io处理所有缓存
- 每次一行的io,使用fgets和fputs一次读写一行,当调用fgets时应说明能处理的he最大行长
- fread 和 fwrite函数支持这种类型的io,每次io操作或写某种数量的对象,而每个对象有指定的长度。
函数fclose()用于关闭一个已经打开的标准IO流,其函数原型如下:
#include <stdio.h>
int fclose(FILE * fp);
函数fclose()在关闭文件流之前,会刷新缓冲区,将所有数据同步到磁盘中,函数若执行成功会返回0,否则返回非0值,同时设置errno。
注意:使用fopen()打开的文件,一定要记得使用fclose()关闭,否则会出现很多意想不到的情况,例如对文件的更改没有被记录到磁盘上,其他进程无法存取该文件等。#include <stdio.h> int main(void) { FILE *pf; pf = fopen("log.txt", "w+"); /*打开文件*/ if(pf != NULL) { printf("open file ok\n"); } else { printf("open file error\n"); } fclose(pf); return 0; }
2、fgetc()与fputc()
fgetc()、getc()和getchar()三个函数用于从标准流中一次性读取一个字符,其函数原型如下
#include<stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar();
上述三个函数用于返回当前位置的下一个字符,返回字符时会进行类型转换:将unsigned char类型转换为int类型。字符不带符号的理由是:最高位为1也不会使返回值为负。返回值类型为整型的理由是:可以返回所有可能的字符,再加上一个已发生错误或已达到文件尾端的标识符。值得一提的是,函数getchar()等同于函数getc(stdin).
在头文件<stdio.h>中常数EOF被要求为一个负值,通常是-1.
#define EOF (-1)
不管是出错还是到达文件结尾,上述三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror()和feof()。
#include <stdio.h> int feof(FILE *stream); int ferror(FILE *stream); void clearerr(FILE *stream);
大多数实现中,FILE对象为每个流维护了两个标识符:
◆出错标识。
◆文件结束标识。
函数feof()用于检测当前是否到达文件结尾,若是返回0,否则返回非0值;函数ferror()用于检测当前流是否发生错误,若是返回0,否则返回非0值;函数clearerr()可以清除这两个标识。
putc()、fputc()和putchar()三个函数用于向标准流中一次性写入一个字符,其函数原型如下:
#include<stdio.h> int fputc(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c);
与输入函数一样,函数putchatr(c)等同于函数putc(c,stdout)。
示例----对一个文件进行复制
程序源码为:
#include <stdio.h> int main(void) { char c; FILE *base_fp; FILE *copy_fp; base_fp =fopen("log.txt","r"); copy_fp =fopen("copyfile","w"); if(!base_fp)printf("error1\n"); if(!copy_fp)printf("error2\n"); while((c =fgetc(base_fp)) != EOF){ fputc(c,copy_fp); } }
程序延时了如何调用fgetc()函数,每次调用时,检查返回值是否为EOF以查看是否已经到达文件末尾。
程序编译运行结果如下:
3、fgets()与gets
fgets()和gets()函数用于从打开流中一次性读取一行字符,其函数原型如下:
#include <stdio.h> char *fgets(char *s,int size FILE *stream); char *gets(cahr *s);
参数说明:
- s为一个字符数组,用来保存读取到的字符。
- size为要读取的字符的个数。如果该行字符数大于size-1,则读到 size-1 个字符时结束,并在最后补充' \0';如果该行字符数小于等于 size-1,则读取所有字符,并在最后补充 '\0'。即,每次最多读取 size-1 个字符。
- stream为文件流指针。
【返回值】读取成功,返回读取到的字符串,即string;失败或读到文件结尾返回NULL。因此我们不能直接通过fgets()的返回值来判断函数是否是出错而终止的,应该借助feof()函数或者ferror()函数来判断。
这两个函数都指定了缓冲区地址,将读取的行送入其中,函数gets()从标准输入读取,其函数fgets()从指定的流读取。
对于函数fgets(),必须指定缓冲的长度为n,此函数一直读到下一个换行符为止,但不超过n-1个字符,读取的字符被送入缓冲区,该缓冲区以NULL字节结尾。如果改行最后一个换行符的字符数超过n-1,则函数fgets()返回一个不完整的行,但是缓冲区总是以NULL字节结尾,对函数fgets()的下一次调用会继续读该行。
函数gets()是一个不被推荐使用的函数,其问题是调用者在使用函数gets()时不能指定缓存区的长度,这样就可能造成缓冲区溢出,写到缓冲区之后的存储空间中,从而产生不可预料的错误,这种缺陷曾经造成了1988年的网络蠕虫病毒。
注意:fgets()与gets()不一样,不仅仅是因为gets()函数只有一个参数 FILE *stream,更重要的是,fgets()可以指定最大读取的字符串的个数,杜绝了gets()使用不当造成缓存溢出的问题。另外一个区别是:函数gets()不能把换行符存入缓冲区。
示例———行缓存显示文件内容
程序源码为:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { FILE *fp=NULL; char *buf; int count = 0; buf=(char*)malloc(10*sizeof(char)); memset(buf,0,10*sizeof(char)); fp=fopen("log.txt","r"); if(fp==NULL) { perror("fopen()"); exit(1); } if((fgets(buf,10,fp))==NULL) { printf("fgets():error!\n"); exit(1); } for(count=0;count<10;count++) { printf("buf[%d]:\t%c->%d\n",count,buf[count],buf[count]); } exit(0); }
编译运行结果如下
4、fputs()与puts()
fputs()和puts()函数用于向打开流中一次性写入一个字符串,其函数原型如下:
#include <stdio.h> int fputs(const char *s,FILE *stream); int puts(const char *s);
函数fputs()将一个NULL 字节结尾的字符串写到指定的流中,尾端的终止符NULL不用写出。由于字符串不需要换行符作为最后一个非NULL字符,因此函数fputs()并不一定是每次输出一行。通常在NULL字符之前是一个换行符,但要求并不总是如此。
函数puts将一个NULL字节终止的字符串写到标准输出中,但终止符不需要写出。随后,函数puts()又将一个换行符写到标准输出中。函数puts()虽不像函数gets()那样不安全,但是我们还是应该避免使用它,以免增加需要记住它在最后是否添加了一个换行符的麻烦。如果总是使用函数fgets()和fputs(),那么我们就会熟知在每行终止处必须处理换行符。
示例:使用fputs()写一个文件,然后fgets()读取文件内容并显示。
程序首先创建一个空文件,并利用fputs()函数写 入两行字符串,刷新关闭。然后重新以只读的方式打开该文件, 使用fgets()函数连续两次读取该文件,第一次读取3-1个字符,第 二次读取一行。最终结果都显示出来。
#include <stdio.h> #include<stdlib.h> #include<sys/io.h> #include<curses.h> int main(void) { char msg[] = "This is a test!\n secend line\n"; char msgget[100]; int i = 0; FILE* fstream; fstream = fopen("test.txt","w+"); /*打开文件*/ if(fstream==NULL) { printf("Open file failed!\n"); exit(1); } fputs(msg, fstream); /*写入文件*/ fflush(fstream); close(fileno(fstream)); fstream=fopen("test.txt","r"); i = 0; fgets(msgget,3,fstream) ; fputs(msgget, stdout); printf("\n"); fgets(msgget,100,fstream) ;/*从流中读取一行或者指定个数字符*/ fputs(msgget, stdout); /*送一个字符串到流中*/ return 0; }
编译运行结果如下
5、fread()与fwrite()
对于文本文件,通常以字符或行为单位进行文件读写,对于二进制文件,更倾向于一次性读写一个完整的结构。如果使用函数getc()或putc()读写一个结构,那么循环必须通过整个结构,循环每次只能处理一个字节,这样会非常麻烦,且效率低下。如果使用函数fputs(),可能实现不了完整读写结构的要求,因为函数fputs()在遇到NULL字节时就会停止,而在结构中可能含有NULL字节。类似的,如果输入数据中包含有NULL字节或换行符,则函数fgets()也不能进行完整的读写的操作。因此,提供了函数fread()和fwrite(),用以执行二进制文件,其函数原型如下:
#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_ nmemb,FILE *stream);
参数说明:
(1)ptr:是一个指针,对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址。
(2)size:要读写的字节数;
(3)nmemb:要进行读写多少个size字节的数据项;
(4)stream:文件型指针。
返回值:读或写的记录数,成功时返回的记录数等于nmemb,出错或读到文件末尾时返回的记录数小于nmemb,也可能返回0。
注意:1 完成一次写操作(fwrite())后必须关闭流(fclose());
2 完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,不关闭流继续下一次读操作则接着上次的输出继续输出;
3、读写函数fread()和fwrite()是按照数据项item来读写的,一次读或者写一个数据项,而不是按照字节读写的。数据项可以是一个int数据,char数据,字符串,结构体数据等等。
函数fread()和fwrite()通常有以下两种常见的用法:
1、读写常用类型
下面为一个写int数据到文件的一个示例:
#include <stdio.h> #include <stdlib.h> int main () { FILE * pFile; int buffer[] = {1, 2, 3, 4}; if((pFile = fopen ("myfile.txt", "wb"))==NULL)//从wb可知道,写的是一个二进制文件爱你 { printf("cant open the file"); exit(0); } //可以写多个连续的数据(这里一次写4个) fwrite (buffer , sizeof(int), 4, pFile); fclose (pFile); return 0; }
编译运行结果如下:
对应的有将一个int数据从一个文件中读取出来
#include <stdio.h> #include <stdlib.h> int main () { FILE * fp; int buffer[4]; if((fp=fopen("myfile.txt","rb"))==NULL) { printf("cant open the file"); exit(0); } if(fread(buffer,sizeof(int),4,fp)!=4) //可以一次读取 { printf("file read error\n"); exit(0); } for(int i=0;i<4;i++) printf("%d\n",buffer[i]); return 0; }
运行结果如下:
2、读写结构体数据
下面为一个示例写结构提到数据。
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct{ int age; char name[30]; }people; int main () { FILE * pFile; int i; people per[3]; per[0].age=20;strcpy(per[0].name,"li"); per[1].age=18;strcpy(per[1].name,"wang"); per[2].age=21;strcpy(per[2].name,"zhang"); if((pFile = fopen ("myfile.txt", "wb"))==NULL) { printf("cant open the file"); exit(0); } for(i=0;i<3;i++) { if(fwrite(&per[i],sizeof(people),1,pFile)!=1) printf("file write error\n"); } fclose (pFile); return 0; }
运行结果如下
最后我们通过读结构体将其读取出来
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct{ int age; char name[30]; }people; int main () { FILE * fp; people per; if((fp=fopen("myfile.txt","rb"))==NULL) { printf("cant open the file"); exit(0); } while(fread(&per,sizeof(people),1,fp)==1) //如果读到数据,就显示;否则退出 { printf("%d %s\n",per.age,per.name); } return 0; }
运行结果如下
二进制文件读写操作的基本问题是:他只能用于读在同一系统上已写的数据。以前这不是问题,但是,现在由于很多异构系统通过网络互联了起来,就会出现在一个系统上写的数据要在另一个系统上处理的文件(比如FPGA的DE10-NANO、DE1-SOC等都有这种情况),在这种环境下,这两个函数可能就无法正常工作,其原因是:
(1)在一个结构中,同一成员的偏移量可能随编译程序和系统的不同而不同,某些编译程序有一选项,可选择不同值,可以使结构中的各成员紧密包装(节省空间的同时性能有所下降),或者准确对其对齐(以便在运行时易于存取结构中的各个成员)。这意味着即使同一操作系统上,一个结构的二进制存取方式也可能因编译程序选项的不同而不同。
(2)用来存储多字节整数和浮点数的二进制格式在不同的操作系统结构间也可能不同,在不同系统之间交换二进制数据的实际解决方法也是使用互认的规范格式。
6、文件流定位
文件流定位的方法通常有三种:
(1)ftell和fseek函数。这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
(2)ftello和fseeko函数。Single UNIX Specification引入了这两个函数,可以使文件偏移量不必一定使用长整型。它们使用off_t数据类型代替了长整型。
(3)fgetpos和fsetpos函数。这两个函数是由ISO C引入的。它们使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以定义为记录一个文件位置所需的长度。需要移植到非UNIX系统上运行的应用程序应当使用fgetpos和fsetpos。
上述文件流函数定位的函数原型如下:
#include <stdio.h> long ftell( FILE *stream); long fseek(FILE *stream,long offset,int whence); off_set ftello(FILE *stream); int fseeko(FILE *stream,off_t offset,int whence); int fgetpos(FILE *stream, fpos_t *pos); int fsetpos(FILE *stream, fpos_t *pos);
第一个参数stream为文件指针。
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移。off_t类型默认是32位的long int
第三个参数whence设定文件哪里开始偏移,可能取值为:SEEK_CUR、SEEK_END或SEEK_SET。
SEEK_SET:文件开头
SEEK_CUR:当前位置
SEEK_END:文件结尾
fpos_t是代表文件访问指针位置信息的类型名,你可以把它看成跟int或long这样的类型名一样的东西。
对于一个二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为计量单位。ftell用于二进制文件时,其返回值就是这种字节位置。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种偏移量的方式。whence的值与lseek函数的相同:SEEK_SET表示从文件的起始位置开始,SEEK_CUR表示从当前文件位置开始,SEEK_END表示从文件的尾端开始。
对于文本文件,它们的文件当前位置可能不以简单的字节偏移量来度量。这主要也是在非UNIX系统中,它们可能以不同的格式存放文本文件。为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0(绕回到文件的起始位置),或是对该文件调用ftell所返回的值。使用rewind函数也可以将一个流设置到文件的起始位置。
下面通过一些实例对上述函数进行了解
1、ftell函数用于得到文件位置指针当前位置相对与文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
#include <stdio.h> int main( void ) { FILE *stream; stream = fopen( "MYFILE.TXT", "w+" ); fprintf( stream, "This is a test" ); printf( "The file pointer is at byte \ %ld\n", ftell( stream ) ); fclose( stream ); return(0); }
程序编译运行结果如下:
ftell一般用于读取文件的长度,下面补充一个例子,读取文本文件中的内容:
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; int flen; char *p; /* 以只读方式打开文件 */ if ( (fp = fopen( "log.txt", "r" ) ) == NULL ) { printf( "\nfile open error\n" ); exit( 0 ); } fseek( fp, 0L, SEEK_END ); /* 定位到文件末尾 */ flen = ftell( fp ); /* 得到文件大小 */ p = (char *) malloc( flen + 1 ); /* 根据文件大小动态分配内存空间 */ if ( p == NULL ) { fclose( fp ); return(0); } fseek( fp, 0L, SEEK_SET ); /* 定位到文件开头 */ fread( p, flen, 1, fp ); /* 一次性读取全部文件内容 */ p[flen] = '\0'; /* 字符串结束标志 */ printf( "%s", p ); fclose( fp ); free( p ); return(0); }
程序运行结果:
2、fseek函数通过定位进行文件修改
#include<stdio.h> #define N 5 typedef struct student{ long sno; char name[10]; float score[3]; }STU; void fun(char*filename,STU n) { FILE*fp; fp=fopen(filename,"rb+"); fseek(fp,-1L*sizeof(STU),SEEK_END);//定位到文件的结束位置 fwrite(&n,sizeof(STU),1,fp); fclose(fp); } int main()/*修改覆盖最后一个学生数据*/ { STU t[N]={ {10001,"MaChao",91,92,77}, {10002,"CaoKai",75,60,88}, {10003,"LiSi",85,70,78}, {10004,"FangFang",90,82,87}, {10005,"ZhangSan",95,80,88} }; STU n={10006,"ZhaoSi",55,70,68},ss[N]; int i,j;FILE*fp; fp=fopen("student.dat","wb"); fwrite(t,sizeof(STU),N,fp); fclose(fp); fp=fopen("student.dat","rb"); fread(ss,sizeof(STU),N,fp); fclose(fp); printf("\nThe original data:\n\n"); for(j=0;j<N;j++) { printf("\nNo:%ldName:%-8sScores:",ss[j].sno,ss[j].name); for(i=0;i<3;i++) printf("%6.2f",ss[j].score[i]); printf("\n"); } fun("student.dat",n); printf("\nThe data after modifing:\n\n"); fp=fopen("student.dat","rb"); fread(ss,sizeof(STU),N,fp); fclose(fp); for(j=0;j<N;j++) { printf("\nNo:%ldName:%-8sScores:",ss[j].sno,ss[j].name); for(i=0;i<3;i++) printf("%6.2f",ss[j].score[i]); printf("\n"); } return 0; }
程序编译运行结果如下:
tello函数和ftell函数相同,而fseeko函数和fseek函数相同,除了偏移量的类型是off_t而不是long
想移植到非UNIX系统上运行应用程序应该使用fgetpos和fsetpos
fgetpos示例:应用fgetpos函数取得当前文件的指针所指的位置。
/******************************************************** //首先,程序以读写方式打开一个名为test.txt的文件,并把字符串"This is a test"写入文件。 注意:字符串共14个字节,地址为0~13。用fwrite函数写入后,文件指针自动指向文件最后一个字节的下一个位置。即这时的fp的值应该是14。 //再用fseek函数重设文件指针的位置,让fp的值为3,即指向文件中第4个字节。 *******************************************************/ #include <string.h> #include <stdio.h> int main(void) { FILE *fp; char string[] = "This is a test"; fpos_t pos; /* 以读写方式打开一个名为test.txt的文件 */ fp = fopen("test.txt", "w+"); /* 将字符串写入文件 */ fwrite(string, strlen(string), 1, fp); /* 取得指针位置并存入&pos所指向的对象 */ fgetpos(fp, &pos); printf("The file pointer is at byte %ld/n", pos); /*重设文件指针的位置*/ fseek(fp,3,0); /* 再次取得指针位置并存入&pos所指向的对象 */ fgetpos(fp, &pos); printf("The file pointer is at byte %ld/n", pos); fclose(fp); return 0; }
程序编译结果:
fsetpos示例:应用fsetpos函数定位文件指针。
#include<stdio.h> main() { FILE *stream; long offset; fpos_t pos; stream = fopen ("/etc/passwd", "r"); fseek (stream, 5, SEEK_SET) ; printf ("offset = %d\n", ftell (stream) ); rewind (stream) ; fgetpos (stream , &pos) ; /* 用 fgetpos () 取得读写位置 */ printf ("offset = %d\n", pos); pos.__pos = 10 ; /*pos.__pos的下划线是两道短下划线组合在一起*/ fsetpos (stream, &pos); /*用 fgetpos () 设置读写位置 */ printf ("offset = %d\n", ftell (stream) ); fclose (stream) ; }
程序运行结果:
7、格式化输入/输出
格式化输入/输出是指按照指定的格式输入/输出内容。C语言中,函数printf()用于实现格式化输出操作,而函数scanf()用于实现格式话输入操作。
函数族scanf()的工作方式与函数族printf()类似,所不同的是从一个文件流中读取内容,并在作为参数传递的指针地址处放置变量值。二者以同样的方式使用格式字符串来控制输入数据的转换,涉及的转换控制符也是相同的。
通常,函数族scanf()并不被推荐使用,主要有三方面原因:
◆传统的原因是因为这些函数的实现存在一些漏洞。
◆它们的使用不够灵活。
◆它们会使得编写的程序难以理解。
所以,建议尽量使用其他函数完成输入功能,如fread()或fgets()。
fscanf,scanf 和 sscanf:
fscanf 可以从文件流以一定的格式读取数据,其函数原型如下:
#include <stdio.h> int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
int sscanf(const char *str, const char *format, ...);
◆ stream:要读取的文件指针。
◆ format:格式字符串。
◆ 可变参数:一般是指针,指向用于存储到的数据流量。
◆ str 参数就是被读取的字符串。
◆ 返回值:成功解析的数据项的个数(不是字节数),失败则返回 EOF。
格式字符串中的字符将与输入流中读到的字符进行匹配,具体来说有以下几种情况。
◆ 空白字符:包括空格、制表、换行等字符,将与输入流中的连续 0 个或多个空白字符相匹配,也就是说,一个空白字符可以消
耗多个空白字符。
◆ 普通字符:不想与从输入流读入的字符相同。
◆ 转换符:以符合 % 开始的多个字符,这时输入流中读入的字符将按某种格式解析为数据,存入对应的可变参数指向的变量中。
常用输入转换符:
转换符 作用
%d 以十进制格式读入整数,存在整型变量中
%i 当下一个字符是 0 时,以八进制格式读取整数;当下两个字符是 0x 或 0X 时,以十六进制格式读入整数;否则以十进制格式读入整数,存放在整型变量中。
%u 以十进制格式读入整数,存放在无符号整型变量中
%o 以八进制格式读入整数,存放在无符号整型变量中
%x 或 %X 以十六进制格式读入整数,存放在无符号整型变量中
%f,%g,%e 或 %E 读入浮点数,存放在浮点型变量中
%s 读入字符串,字符串从下一个非空白字符开始,再遇到一个空白字符或者达到指定的域宽后结束。字符串存放在对应的参数指向的缓冲区中,末尾会自动加上 \0
%c 读入域宽所指定个数的字符,默认是一个。不跳过开始的空白字符,读入的字符放在对应参数指向的字符数组中,末尾不加 \0
scanf 函数类似于 fscnaf 函数,只不过是从标准输入读取数据,还有一个 sscanf 函数可以从字符串中格式化读取数据。
Linux系统中,格式化输出是由函数族printf()进行处理的,该函数能够对各种不同类型的参数进行格式编排和输出。每个参数在输出流中的表示形式能格式参数format控制,它是一个包含需要输出的普通字符和转换控制符代码的字符串,转换控制符规定了其余的参数应该以何种方式输出到何种地方。函数族printf()的函数原型如下:
#include <stdio.h> int pirntf(const char *format, ...); int fprintf(FILE *stream,const char *format,...); int dprintf(int fd, const char *format,...); int sprintf(char *str,const char *format,...); int snpritf(char *str,size_t size,const char *format,...);
◆ stream:要写入的文件指针。
◆ format:格式字符串。
◆ 可变参数:要写入的数据。
◆ 返回值:如果写入成功,则返回格式化后字符串的长度,也就是写入数据的长度,负数表示有错误发生。
常用输出转换符:
格式符 功能
%d 或 %i 按有符号十进制格式输出整型参数
%u 按无符号十进制格式输出无符号整型参数
%o 按无符号八进制格式输出无符号整型参数
%x 按无符号十六进制格式输出无符号整型参数,使用字母 a,b,c,d,e,f
%X 按无符号十六进制方式输出无符号整型参数,使用字母 A,B,C,D,E,F
%c 将整型参数转换为无符号字符型,并输出为字符
%f 按十进制格式输出高精度浮点型参数
%e 按科学计数法格式输出高精度浮点型参数,使用字母 e
%E 按科学计数法格式输出高精度浮点型参数,使用字母 E
%g 或 %G 可理解为系统自带选择 %f 或 %e 格式输出
%p 按十六进制格式输出指针型参数
%s 将字符指针型参数视为字符串输出
因为格式字符串中的符号 % 有了特殊的含义,所以要原样输出一个 %,则需要连续写两个 %,即 %%。
常用输出格式符标志(放在 % 的后面):
字符 作用
数字 0 当输出数字时,填充 0 而不是空格
减号 - 修改为左对齐方式,空格填充在右边
空格 对应正数来说,左边预留一个空格作为符号位
加号 + 总是在正数左边加上 + 符号,在负数左边加上 - 符号
函数printf()用于将格式数据写到标准输出中;
函数fprintf()用于将格式化数据写到指定的流中,我们常用的 printf 函数实际上是对 fprintf 函数的包装,它用来向标准输出写入格式的字符串,它比 fprintf 函数少一个文件指针参数,因为这个文件指针一定是 stdout;
函数dprintf()用于将格式化数据写到制定的文件描述符中,该函数并不处理文件指针,因而不需要调用函数fdopen()将文件描述符转化为文件指针;
函数sprintf()用于将格式化数据写到数组str中,它并不是文件 I/O 操作,而是将格式化的字符串输出到一个缓冲区中,sprintf 函数会在输出字符串的末尾加上结束符 \0。使用这个函数时要注意;
str指向的缓冲区要有足够的大小来容纳生成的字符串,否则就有内存访问越界的问题。很多情况下并不能事先知道结果字符串的长度,这时可以用函数snprintf(),函数snprintf()在数组str末尾自动追加一个NULL字节,但该字节不包括在返回值中。
值得一提的是,函数sprintf()可能会造成由参数str所指向缓冲区的溢出,调用者有责任确保该缓冲区溢出问题,引入了函数snprintf()。该函数中,缓冲区长度是一个显示参数size,超过缓冲去结尾的所有字符都被丢弃。如果缓冲去足够大,函数snprintf()返回小于缓冲区长度n的整数,输出没有被截断;若发生一个编码错误,则函数snprintf()返回负数。