是的,
今天我要来说一个特别“简单”的标准库函数——printf

为什么说他简单呢?
毕竟这个函数真的没有太多东西,
我们早在第一堂课输出 Hello World 时就已经接触了,
而且几乎无时无刻不会用到这个函数,
以至于我们对它实在太熟悉了!

那我们还有什么好说的呢?
其实不然,我今天要说的东西与其说是 printf 的骚操作,
还不如说是 C 语言本身的骚操作,
也就是 C 的灵魂——指针的骚操作

printf 和指针在一起就能有骚操作?
是的,我想看完这篇文章后你应该会对 C 语言有个更加深刻的理解。

下面就不卖关子了,直接进入正题。

一、首先看个题目!

图1.1 知乎提问

看似很简单,
但是实现起来总是蹑手蹑脚的,对吧?

二、怎么解?

如果你是怎么写?

像这样先单独输出第一个吗?

1
2
3
4
5
6
7
int a[] = {1, 2, 3, 4, 5, 6}, i;

printf("%d", a[0]);
for (i = 1; i < 6; i++)
{
printf(",%d", a[i]);
}

好像实现了,但是 printf("%d", a[0]) 总显得很难受!
好像不是最优解!

那或者用 if/switch 判断语句?类似于这样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a[] = {1, 2, 3, 4, 5, 6}, i;

for (i = 0; i < 6; i++)
{
switch (i)
{
case 0:
printf("%d", a[0]);
break;
default:
printf(",%d", a[i]);
break;
}
}

嗯!好像结构很漂亮了,但是用 if/switch 判断语句处理这么个小问题总感觉有些杀鸡用牛刀了!

那怎么做?这里我直接给出代码:

最漂亮的写法
1
2
3
4
5
6
int a[] = {1, 2, 3, 4, 5, 6}, i;

for (i = 0; i < 6; i++)
{
printf(",%d" + !i, a[i]);
}

三、怎么做到的?

答案很简单,我认为这是一种指针偏移的合理运用。非常得简洁和优雅!

我初次看到这串代码,真的是佩服得五体投地。但细细琢磨一下,好像又没什么东西。

或许很多事都差不多吧,看着很绚丽,实际上说清楚了也就没什么了!

下面简单讲解一下:

首先,我们知道在 C 语言中,非零为真,零为假

那么,对于 !i 这个式子来说,

i = 0 时,得到的是 1
i = 1,2,3... 时,得到的是 0

所以,对于 printf 的第一个参数 ",%d" + !i 来说,
i = 0 时,它就变成了 ",%d" + 1
这里
首先做了从字符串到指针的转换,然后再做了指针的 +1 运算

就相当于指针后移了一位,即指向 "%d"

类似的,
i = 1,2,3... 时,
它就变成了 **",%d" + 0**,
即得到了 ",%d"

四、补充新解法

上面说的写法固然很妙,也很简洁。但是有一个可能的缺点,那就是可读性。

对于已经理解的人来说,一看就懂。但是并不是所有人都能一眼看懂那段代码的!

所以,这里提供一个最新的增加了可读性的写法:

增加可读性的写法
1
2
3
4
5
6
int a[] = {1, 2, 3, 4, 5, 6}, i;

for (i = 0; i < 6; i++)
{
printf((i) ? (',%d') : ("%d"), a[i]);
}