C语言Printf输出格式美化
在项目开发中,测试功能时使用Printf()调试代码应该是最为简单实用的方法之一了,但关于对输出格式美化方面的介绍内容较少,在此本人结合实际开发中使用的方法和网上查阅的资料做一点整理,希望能帮助到有需要的人.
Printf函数简介
printf()
函数,相信每个人都不陌生,很多人的C语言开发之路都是从一句Hello word
开始的.printf()
的函数原型如下:
int printf ( const char * format, ... );
printf
将 format 指向的 C 字符串写入标准输出 (stdout)。如果 format 包含格式说明符(以 % 开头的子序列),则 format 后面的附加参数将被格式化并插入到结果字符串中,替换其各自的说明符。
格式说明符遵循此原型:%[flags][width][.precision][length]specifier
,其中末尾的说明符是最重要的部分,因为它定义了其对应参数的类型和解释,并且不可省略,而剩余的子说明符部分可以做省略,关于格式说明符和其子说明符的关系请参考此链接C Printf说明学习,下面简略介绍一下子说明符。
[flags]
:
- 若为
+
则在输出中强制加上+
.如"+3". - 若为
-
,控制输出左对齐,默认为右对齐。
[with]:
- 若为数值,则表示要打印的最小字符数。如果要打印的值短于此数字,则结果将用空格填充。即使结果较大,也不会截断该值。
- 若为
*
,则表示宽度未在格式字符串中指定,而是作为必须格式化的参数前面的附加整数值参数指定。
[.precision]
:
- 对于整数说明符(d、i、o、u、x、X):精度指定要写入的最小位数。如果要写入的值短于此数字,则结果将用前导零填充。即使结果较长,也不会截断该值。精度为 0 表示不会为值 0 写入任何字符。
- 对于 a、A、e、E、f 和 F 说明符:这是小数点后要打印的位数(默认情况下为 6)。
- 对于 g 和 G 说明符:这是要打印的最大有效位数。
- 对于 s:这是要打印的最大字符数。默认情况下,将打印所有字符,直到遇到结尾的空字符。
[length]
:
- 长度子说明符修改数据类型的长度。
输出格式控制
在实际开发中,我们可能需要对字符串,浮点数或者整型变量做输出格式化控制,结合上文中提到格式说明符与子说明符的关系,下面简单演示下如何使用子说明符控制复杂格式输出:
//test_printf.c
#include<stdio.h>
int main(void)
{
//1. 字符串长度控制
char *string = "abcdefghijk";
printf("String: %3s %30.30s %-30.30s %-3.3s\n" ,
string ,string , string ,string );
//2. 整数长度控制
int a = 123456789 ;
printf("Int: %5d %20.20d %-20.20d %-2.2d\n" , a , a , a , a );
//3. 浮点数长度控制
double b = 1.23456789;
printf("Double %3f %30.30f %-30.30f %3.3f \n" , b , b , b ,b);
return 0;
}
编译运行结果如下:
可以看到,和上文中描述的子说明符作用一致:
- 对整数而言,由于实际长度(10)小于[precision]值,前面被补零,实际长度(10)没有被[width]项限制
- 对浮点数而言,精度被指定为[precision]值,[precision]值为30时,补全精度为30,[precision]值为3时,截断输出精度为3
- 对字符串而言,分别展示了字符串的左右对齐,及[precision]值指定下的字符串输出,小于[precision]值时全部输出,大于[precision]值时截断为[precision]值。
制表符作用原理
\t
制表符在printf
中使用的较少,可能大家都比较陌生,它主要的作用是控制输出字符,使输出字符%8 = 0,能用于实现列补全.简单展示下用法如下:
//test_print_t.c
#include<stdio.h>
int main(void)
{
char *stringa = "12" ;
char *stringb = "123" ;
char *stringc = "12345" ;
char *stringd = "1" ;
printf("%s\t%s\t%s\t%s\t\n",stringa ,stringb ,stringc ,stringd);
}
编译运行结果如下可以看到,每个字符串的输出都被补全到8的倍数.
可以认为它补全空格的计算公式如下:
t = 8 - string_len % 8
输出格式美化
结合上文的制表符和格式说明符相关的知识,就不难做到本文提到的输出格式美化了.大致分为以下四个步骤:
- 根据数据内容,定义数据最大长度
- 根据数据形式,定义对应模板
- 使用子说明符及制表符实现模板
- 填充数据到输出模板
下面假设我们需要提供一个输出格式为:
****************
** XX : XX **
****************
下面分别举例说明多行输出和单行输出的做法。
单行输出美化的例子:
由于只需要对单行输出进行美化,就可以取巧一下,利用[width]参数中的*
(宽度未在格式字符串中指定,而是作为必须格式化的参数前面的附加整数值参数指定),只对核心的输出格式做限制,将其余的整行的长度等等,完全交由代码自动生成.代码如下:
//test_format_printf.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int get_line_length(char * string1 , char * string2)
{
int string1_len = strlen(string1);
int string2_len = strlen(string2);
int line_len = 8 + string1_len + (8 - (string1_len % 8)) + 8 + string2_len + (8 - (string2_len % 8)) + 2;
printf("Get string1_len %d ,string2_len %d , line_len %d\n" ,
string1_len , string2_len , line_len);
return line_len;
}
char *build_line(int line_length , char format)
{
int i = 0;
line_length = line_length + 1;//'\0'
char *buffer_line = (char *)malloc(line_length);
snprintf(buffer_line , line_length , "%0*d" , line_length , 0);
while(buffer_line[i] == '0')
{
buffer_line[i++] = format;
}
printf("Build Buffer_line \n%s \n" , buffer_line);
return buffer_line;
}
int main(int argc , char *argv[])
{
if(argc != 3)
{
printf("Usage: %s <string1> <string2>",argv[0]);
exit(-1);
}
int line_length = get_line_length(argv[1] , argv[2]);
char *buffer_line = build_line(line_length , '*');
printf("%s \n" , buffer_line);
printf("**\t%*.*s\t:\t%*.*s\t**\n", (int)strlen(argv[1]) , (int)strlen(argv[1]) , argv[1] ,
(int)strlen(argv[2]) , (int)strlen(argv[2]) , argv[2] );
printf("%s \n" , buffer_line);
free(buffer_line);
return 0;
}
编码后可以看到运行结果如上图,对于任意输入字符串,都能很好的达成目标格式.
多行输出美化的例子
对多行输出进行美化时,就不能取巧了,需要事先确认数据输入中的最大值,然后定义好最长行宽,对数据做截断,代码如下:
//test_format_printf.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char *build_line(int line_length , char format)
{
int i = 0;
line_length = line_length + 1;//'\0'
char *buffer_line = (char *)malloc(line_length);
snprintf(buffer_line , line_length , "%0*d" , line_length , 0);
while(buffer_line[i] == '0')
{
buffer_line[i++] = format;
}
printf("Build Buffer_line \n%s \n" , buffer_line);
return buffer_line;
}
int main(int argc , char *argv[])
{
if(argc < 3)
{
printf("Usage: %s <string1> <string2> ... ",argv[0]);
exit(-1);
}
printf("Get argc %d \n" , argc);
//int line_length = get_line_length(argv[1] , argv[2]);
char *buffer_line = build_line(50 , '*');
printf("%s \n", buffer_line);
for(int i=1 ; i < argc ; i+=2)
{
int j = i+1;
printf("%s \n", buffer_line);
printf("**\t%12.12s\t:\t%12.12s\t**\n", argv[i] , argv[j]);
//8 + 12 + 4 + 8 +12 +4 + 2 = 50
}
printf("%s \n", buffer_line);
free(buffer_line);
return 0;
}
编译运行结果如下;
代码很简单没什么多讲的,唯一需要注意的是正确计算含制表符在内的输出长度( 此处输出的右对齐的,如果想变成左对齐,修改**\t%12.12s\t:\t%12.12s\t**
为**\t%-12.12s\t:\t%-12.12s\t**
即可。