Linux标准IO(三)-格式化I/O输出
在前面编写的测试代码中,会经常使用到库函数 printf()用于输出程序中的打印信息,printf()函数可将格式化数据写入到标准输出,所以通常称为格式化输出。除了 printf()之外,格式化输出还包括:fprintf()、dprintf()、sprintf()、snprintf()这 4 个库函数。
除了格式化输出之外,自然也有格式化输入,从标准输入中获取格式化数据,格式化输入包括:scanf()、fscanf()、sscanf()这三个库函数,那么本小节将向大家介绍 C 语言库函数的格式化 I/O。
格式化输出
C 库函数提供了 5 个格式化输出函数,包括:printf()、fprintf()、dprintf()、sprintf()、snprintf(),其函数定义如下所示:
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *buf, const char *format, ...);
int snprintf(char *buf, size_t size, const char *format, ...);
可以看到,这 5 个函数都是可变参函数,它们都有一个共同的参数 format,这是一个字符串,称为格式控制字符串,用于指定后续的参数如何进行格式转换,所以才把这些函数称为格式化输出,因为它们可以以调用者指定的格式进行转换输出;学习这些函数的重点就是掌握这个格式控制字符串 format 的书写格式以及它们所代表的意义,稍后介绍 format 参数的格式。
- 每个函数除了固定参数之外,还可携带 0 个或多个可变参数。
- printf()函数用于将格式化数据写入到标准输出;
- dprintf()和 fprintf()函数用于将格式化数据写入到指定的文件中,两者不同之处在于,fprintf()使用 FILE 指针指定对应的文件、而 dprintf()则使用文件描述符 fd 指定对应的文件;
- sprintf()、snprintf()函数可将格式化的数据存储在用户指定的缓冲区 buf 中。
printf()函数
前面章节内容编写的示例代码中多次使用了该函数,用于将程序中的字符串信息输出显示到终端(也就是标准输出),相信各位读者学习 C 语言时肯定用过该函数,它是一个可变参函数,除了一个固定参数 format外,后面还可携带 0 个或多个参数。
函数调用成功返回打印输出的字符数;失败将返回一个负值!
打印“Hello World”:
printf("Hello World!\n");
打印数字 5:
printf("%d\n", 5);
fprintf()函数
fprintf()可将格式化数据写入到由 FILE 指针指定的文件中,譬如将字符串“Hello World”写入到标准错误:
fprintf(stderr, "Hello World!\n");
向标准错误写入数字 5:
fprintf(stderr, "%d\n", 5);
函数调用成功返回写入到文件中的字符数;失败将返回一个负值!
dprintf()函数
dprintf()可将格式化数据写入到由文件描述符 fd 指定的文件中,譬如将字符串“Hello World”写入到标准错误:
dprintf(STDERR_FILENO, "Hello World!\n");
向标准错误写入数字 5:
dprintf(STDERR_FILENO, "%d\n", 5);
函数调用成功返回写入到文件中的字符数;失败将返回一个负值!
sprintf()函数
sprintf()函数将格式化数据存储在由参数 buf 所指定的缓冲区中,譬如将字符串“Hello World”存放在缓冲区中:
char buf[100];
sprintf(buf, "Hello World!\n");
当然这种用法并没有意义,事实上,我们一般会使用这个函数进行格式化转换,并将转换后的字符串存放在缓冲区中,譬如将数字 100 转换为字符串"100",将转换后得到的字符串存放在 buf 中:
char buf[20] = {0};
sprintf(buf, "%d", 100);
sprintf()函数会在字符串尾端自动加上一个字符串终止字符'\0'。需要注意的是,sprintf()函数可能会造成由参数 buf 指定的缓冲区溢出,调用者有责任确保该缓冲区足够大,因为缓冲区溢出会造成程序不稳定甚至安全隐患!
函数调用成功返回写入到 buf 中的字节数;失败将返回一个负值!
snprintf()函数
sprintf()函数可能会发生缓冲区溢出的问题,存在安全隐患,为了解决这个问题,引入了 snprintf()函数;
在该函数中,使用参数 size 显式的指定缓冲区的大小,如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃!如果缓冲区空间足够大,snprintf()函数就会返回写入到缓冲区的字符数,与sprintf()函数相同,也会在字符串末尾自动添加终止字符'\0'。
若发生错误,snprintf()将返回一个负值!
格式控制字符串 format
接下来重点学习以上 5 个函数中的 format 参数应该怎么写,把这个参数称为格式控制字符串,顾名思义,首先它是一个字符串的形式,其次它能够控制后续变参的格式转换。
格式控制字符串由两部分组成:普通字符(非%字符)和转换说明。普通字符会进行原样输出,每个转换说明都会对应后续的一个参数,通常有几个转换说明就需要提供几个参数(除固定参数之外的参数),使之一一对应,用于控制对应的参数如何进行转换。如下所示:
printf("转换说明 1 转换说明 2 转换说明 3", arg1, arg2, arg3);
这里只是以 printf()函数举个例子,实际上并不这样用。三个转换说明与参数进行一一对应,按照顺序方式一一对应。
每个转换说明都是以%字符开头,其格式如下所示(使用[ ]括起来的部分是可选的):
%[flags][width][.precision][length]type
flags:标志,可包含 0 个或多个标志;
width:输出最小宽度,表示转换后输出字符串的最小宽度;
precision:精度,前面有一个点号" . ";
length:长度修饰符;
type:转换类型,指定待转换数据的类型。
可以看到,只有%和 type 字段是必须的,其余都是可选的。下面分别对这些字段进行介绍。
㈠、type 类型
首先说明 type(类型),因为类型是格式控制字符串的重中之重,是必不可少的组成部分,其它的字段
都是可选的,type 用于指定输出数据的类型,type 字段使用一个字符(字母字符)来表示,可取值如下:
转换说明中的 type 字段介绍
㈡、flags
转换说明中的 flags 字段介绍
㈢、width
最小的输出宽度,用十进制数来表示输出的最小位数,若实际的输出位数大于指定的输出的最小位数,则以实际的位数进行输出,若实际的位数小于指定输出的最小位数,则可按照指定的 flags 标志补 0 或补空格。
width 的可能取值如下:
转换说明中的 precision 字段介绍
㈤、length 长度修饰符
长度修饰符指明待转换数据的长度,因为 type 字段指定的的类型只有 int、unsigned int 以及 double 等几种数据类型,但是 C 语言内置的数据类型不止这几种,譬如有 16bit 的 short、unsigned short,8bit 的 char、unsigned char,也有 64bit 的 long long 等,为了能够区别不同长度的数据类型,于是乎,长度修饰符(length)应运而生,成为转换说明的一部分。
length 长度修饰符也是使用字符(字母字符)来表示,结合 type 字段以确定不同长度的数据类型,如下所示:
length 长度修饰符说明
譬如:
printf("%hd\n", 12345); //将数据以 short int 类型进行转换
printf("%ld\n", 12345); //将数据以 long int 类型进行转换
printf("%lld\n", 12345); //将数据以 long long int 类型进行转换
关于格式控制字符串 format 就给大家介绍完了,这种东西不用去记,需要时查询即可!需要说明的是,转换说明的描述信息需要和与之相对应的参数对应的数据类型要进行匹配,如果不匹配通常会编译报错或者警告!
示例代码
前面为了说明格式控制字符串 format 的输出效果,我们使用了 printf()函数进行演示,其它格式化输出函数也是一样,接下来我们编写一个简单的测试程序,对上面学习的内容进行练习。
//示例代码 4.8.1 格式化输出函数使用练习
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void) {
char buf[50] = {0};
printf("%d (%s) %d (%s)\n", 520, "我爱你", 1314, "一生一世");
fprintf(stdout, "%d (%s) %d (%s)\n", 520, "我爱你", 1314, "一生一世");
dprintf(STDOUT_FILENO, "%d (%s) %d (%s)\n", 520, "我爱你", 1314, "一生一世");
sprintf(buf, "%d (%s) %d (%s)\n", 520, "我爱你", 1314, "一生一世");
printf("%s", buf);
memset(buf, 0x00, sizeof(buf));
snprintf(buf, sizeof(buf), "%d (%s) %d (%s)\n", 520, "我爱你", 1314, "一生一世");
printf("%s", buf);
exit(0);
}
运行结果:
关于格式化输出这几个函数其实用法上比较简单,主要是需要掌握格式控制字符串 format 参数的写法,对后续参数列表中不同类型的数据搭配不同的格式控制字符,以实现转换输出、并且控制输出样式。本小节所学内容,大家可以多多练习!