C语言中的格式化输出

概述

与其他语言相比,C语言提供的字符串处理的方式比简陋,格式化输出方式也相当简单,但进行基本的字符串处理已经足够了。
我们常用的print函数是一个格式化处理经典的实现,这一类函数包括:

printf
fprintf
sprintf
snprintf
vprintf
vfprintf
vsprintf
vsnprintf

上述函数共同的思路是根据format提供的格式要求将相应的变量转化成易读或者易解析的字符串,进而输出到文件、终端或者内存中。
在一些情况下,为了易读或者易解析,我们需要提供一些相对整齐的格式化。如ls命令的打印结果:

[user@localhost:log]$ ls -l
total 34424
-rw-r--r-- 1 root      root                   1 Jan 28 11:55 alternatives.log
drwxr-xr-x 1 root      root                 512 Dec 24 12:57 apt
-rw-rw---- 1 root      utmp            34715520 Jan 19 19:41 btmp
drwxr-xr-x 1 root      root                 512 Jan 24  2020 dist-upgrade
-rw-r--r-- 1 root      root              140199 Dec 24 12:57 dpkg.log
drwxr-sr-x 1 root      systemd-journal      512 Mar  4  2020 journal
drwxr-xr-x 1 landscape landscape            512 Apr 20  2020 landscape
-rw-rw-r-- 1 root      utmp              292292 Jan 28 17:06 lastlog
drwxr-xr-x 1 root      root                 512 Nov 24  2018 lxd
-rw------- 1 root      root               64064 Apr 20  2020 tallylog
drwxr-x--- 1 root      adm                  512 Feb 17  2020 unattended-upgrades
-rw-rw-r-- 1 root      utmp               20736 Jan 28 17:06 wtmp

基本分析

从上述输出中,我们可以看出以上几个特点:

  • 字段的格式多样化,即有字符串,又有数字
  • 格式具体一定的对齐性,一列中相同字段是对齐
  • 为了实现格式的规则性,对于变长字段需要有空行

我们通过根据上述要求,总结一下printf规则输出的一般的使用。
为了简化示例,我们把需要格式化的数据存放到一个结构为file_desc_t的静态数组中。其中,结构体定义如下:

typedef struct file_desc {
	  char permission[10];
	  int sub_dir_count;
	  char group[256];
	  char user[256];
	
	  int size;
	
	  struct {
	    char month[4];
	    int day;
	    char time[5];
	  } date_time;
	
	  char path[256];
	} file_desc_t;

并实现一个迭代函数:

/**
 * Call back for eache iterator
 */
typedef void (*iterator_callback)(int idx,file_desc_t *file_desc);

/**
 * iterator_files - Iterate through an array of @file_desc_t and call @cb
 * 
 * @cb - callback functions used to parse and print file attribute
 */
void iterator_files( iterator_callback cb )
{
    int idx = 0;
    for( idx = 0;idx < ARRAY_SIZE(files_in_dir);idx ++){
        if(cb){
            cb(idx,&files_in_dir[idx]);
        }
    }
}

不进行格式化的输出

在不考虑对齐和空白的问题,自然的输出,由于打字的字符串长度不一,看起来很不规则,如,我们实现一个打印:

static void print_user_and_group_unformated(int idx,file_desc_t *file_desc)
{
    printf("%02d: %s %s\n",idx,file_desc->user,file_desc->group);
}

其打印的格式如下:

00: syslog root
01: root root
02: root root
03: root root
04: utmp root
05: utmp root
06: utmp root
07: systemd-journal root
08: landscape landscape
09: utmp root
10: root root
11: root root
12: adm root
13: utmp root

可以看到00、07、08三行打印比较杂乱,其原因是,我们并没有考虑字符串变长的情况,仅仅在两个字符串间留了一个空格。

长度预留

为了解决上述的问题我们可以采用长度预留的试。其format格式为:

“%{width}{d|s|f}”

其中width指定该变量需要在打印字符串中占的字符数的大小,如果变量的实际长度小于width时,将用空格补充,将指定的width占用起来,具体在左侧还是右侧填充空格,与指定的对齐方式有关。如果变量的长度大小于width时,width指定的值将失效, 不做任何处理。

指定长度满足实际占用

指定长度小于实际占用时,将使用空格进行补充,使其占用为width,如函数:

static void print_user_and_group_sufficent_width(int idx,file_desc_t *file_desc)
{
    printf("%02d: %16s %-5s\n",idx,file_desc->user,file_desc->group);
}

打印的效果为:

00:           syslog root 
01:             root root 
02:             root root 
03:             root root 
04:             utmp root 
05:             utmp root 
06:             utmp root 
07:  systemd-journal root 
08:        landscape landscape
09:             utmp root 
10:             root root 
11:             root root 
12:              adm root 
13:             utmp root 

可以看到打印的用户名root虽然只有4个字符,这个字段实际上占用的字符数也是程序里指定的16,多出部分在左侧通过空格补充起来,这样用户名和用户数组分别是对齐的。

指定长度不满足实际占用

当指定长度不满足实际占用时,字符串并不会被截断,而是忽略指定的长度。如将打印函数修改成:

static void print_user_and_group_specify_wide_insufficent(int idx,file_desc_t *file_desc)
{
    printf("%02d: %10s %s\n",idx,file_desc->user,file_desc->group);
}

其输出为:

00:     syslog root
01:       root root
02:       root root
03:       root root
04:       utmp root
05:       utmp root
06:       utmp root
07: systemd-journal root
08:  landscape landscape
09:       utmp root
10:       root root
11:       root root
12:        adm root
13:       utmp root

可以看到07行打印的用户名已经不受指定长度限制。

字段对齐问题

返回去查看 ls -l的实现,我们不仅需要考虑的长度补空的问题,还需要考虑对齐的问题。查看ls -l的显示,显而易见第4列(文件大小)中右对齐的,而其他列是左对齐的。
print格式实现上,默认是右对齐,若需要左对齐可以在%和{s|d|f}之间加一个“-”。
实现一个左对齐函数:

static void print_user_and_group_left_justify(int idx,file_desc_t *file_desc)
{
    printf("%02d: %-16s %-5s\n",idx,file_desc->user,file_desc->group);
}

其输出为:

00: syslog           root 
01: root             root 
02: root             root 
03: root             root 
04: utmp             root 
05: utmp             root 
06: utmp             root 
07: systemd-journal  root 
08: landscape        landscape
09: utmp             root 
10: root             root 
11: root             root 
12: adm              root 
13: utmp             root 

最终打印效果

结合上述分析,我们可以使用补空和对齐特性, 实现相对比较规则打印。

static void print_file_list(int idx,file_desc_t *file_desc)
{
    printf("%02d: %10s %d %-16s %-16s %10d %s %-3d %5s %s \n",
        idx,
        file_desc->permission,
        file_desc->sub_dir_count,
        file_desc->user,
        file_desc->group,
        file_desc->size,
        file_desc->date_time.month,
        file_desc->date_time.day,
        file_desc->date_time.time,
        file_desc->path);
}

最终打印的效果为:

00: drwxrwxr-x 1 syslog           root                    512 Apr 20   2020 ./ 
01: drwxr-xr-x 1 root             root                    512 Mar 5    2020 ../ 
02: -rw-r--r-- 1 root             root                      1 Jan 28  11:55 alternatives.log 
03: drwxr-xr-x 1 root             root                    512 Dec 24  12:57 apt/ 
04: -rw-rw---- 1 utmp             root               34715520 Jan 19  19:41 btmp 
05: drwxr-xr-x 1 utmp             root                    512 Jan 24   2020 dist-upgrade/ 
06: -rw-r--r-- 1 utmp             root                 140199 Dec 24  12:57 dpkg.log 
07: drwxr-sr-x 1 systemd-journal  root                    512 Mar 4    2020 journal/ 
08: drwxr-xr-x 1 landscape        landscape               512 Apr 20   2020 landscape/ 
09: -rw-rw-r-- 1 utmp             root                 292292 Jan 28  17:06 lastlog 
10: drwxr-xr-x 1 root             root                    512 Nov 24   2018 lxd/ 
11: -rw------- 1 root             root                  64064 Apr 20   2020 tallylog 
12: drwxr-x--- 1 adm              root                    512 Feb 17   2020 unattended-upgrades/ 
13: -rw-rw-r-- 1 utmp             root                  20736 Jan 28  17:06 wtmp 

后记

本文所有的实际主要是为了展现用简单的方式实现规则化的打印,并非说ls -l就是这样实现。其实现方式远比上述的描述更加具有技巧性。比如,使用ls不加任何参数时,可以实现对文件名分不同数量的行列进行打印,且行列之前都是对齐的,同时,在打印过程中,其实现还考虑了终端宽度,不同的宽度将会显示不同的列数。

感兴趣的可以自行阅读相关代码。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值