目录
在 C语言中,有三个函数可以用来在显示器上输出数据,它们分别是:
- puts():只能输出字符串,并且输出结束后会自动换;
- putchar():只能输出单个字符;
- printf():可以输出各种类型的数据,在前面的很多章节中都进行了介绍。
printf() 是最灵活、最复杂、最常用的输出函数,完全可以替代 puts() 和 putchar(),大家一定要掌握。
这篇文章篇幅较长,但我向大家保证,只要你认真看完,一定能掌握 printf() 函数的全部用法。
1、printf基本用法
printf 是 print format 的缩写,意思是“格式化打印”。这里所谓的“打印”就是在屏幕上显示内容,与“输出”的含义相同,所以我们一般称 printf 是用来格式化输出的。
先来看一个简单的例子:
printf("C语言");
这个语句可以在屏幕上显示“C语言”,与puts("C语言");
的效果类似。
输出变量 abc 的值:
int abc=999; printf("%d", abc);
这里就比较有趣了。先来看%d
,d 是 decimal 的缩写,意思是十进制数,%d
表示以十进制整数的形式输出。输出什么呢?输出变量 abc 的值。%d 与 abc
是对应的,也就是说,会用 abc 的值来替换 %d。
再来看个复杂点的:
int abc=999; printf("The value of abc is %d !", abc);
会在屏幕上显示:
The value of abc is 999 !
你看,字符串 "The value of abc is %d !" 中的 %d 被替换成了 abc 的值,其他字符没有改变。这说明 %d 比较特殊,不会原样输出,会被替换成对应的变量的值。
再来看:
int a=100; int b=200; int c=300; printf("a=%d, b=%d, c=%d", a, b, c);
会在屏幕上显示:
a=100, b=200, c=300
再次证明了 %d 与后面的变量是一一对应的,第一个 %d 对应第一个变量,第二个 %d 对应第二个变量……
%d
称为格式控制符,它指明了以何种形式输出数据。格式控制符均以%
开头,后跟其他字符。%d
表示以十进制形式输出一个整数。除了 %d,printf 支持更多的格式控制,例如:
- %c:输出一个字符。c 是 character 的简写。
- %s:输出一个字符串。s 是 string 的简写。
- %f:输出一个小数。f 是 float 的简写。
我们把代码补充完整,体验一下:
#include <stdio.h> int main() { int n = 100; char c = '@'; //字符用单引号包围,字符串用双引号包围 float money = 93.96; printf("n=%d, c=%c, money=%f\n", n, c, money); return 0; }
输出结果:
n=100, c=@, money=93.959999
要点提示:
1) \n
是一个整体,组合在一起表示一个换行字符。换行符是 ASCII
编码中的一个控制字符,无法在键盘上直接输入,只能用这种特殊的方法表示,被称为转义字符。
所谓换行,就是让文本从下一行的开头输出,相当于在编辑 Word 或者 TXT 文档时按下回车键。
puts 输出完成后会自动换行,而 printf 不会,要自己添加换行符,这是 puts 和 printf 在输出字符串时的一个区别。
2)
//
后面的为注释。注释用来说明代码是什么意思,起到提示的作用,可以帮助我们理解代码。注释虽然也是代码的一部分,但是它并不会给程序带来任何影响,编译器在编译阶段会忽略注释的内容,或者说删除注释的内容。
3) money 的输出值并不是 93.96,而是一个非常接近的值,这与小数本身的存储机制有关,这种机制导致很多小数不能被精确地表示,即使像 93.96 这种简单的小数也不行。
我们也可以不用变量,将数据直接输出:
#include <stdio.h> int main() { float money = 93.96; printf("n=%d, c=%c, money=%f\n", 100, '@', money); return 0; }
输出结果与上面相同。
在以后的编程中,我们会经常使用 printf,说它是C语言中使用频率最高的一个函数一点也不为过,每个C语言程序员都应该掌握 printf 的用法,这是最基本的技能。
不过 printf 的用法比较灵活,也比较复杂,初学者知识储备不足,不能一下子掌握,目前大家只需要掌握最基本的用法,以后随着编程知识的学习,我们会逐步介绍更加高级的用法,最终让大家完全掌握 printf。
【脑筋急转弯】%ds输出什么
%d 输出整数,%s 输出字符串,那么 %ds 输出什么呢?
我们不妨先来看一个例子:
#include <stdio.h> int main() { int a=1234; printf("a=%ds\n", a); return 0; }
运行结果:
a=1234s
从输出结果可以发现,%d
被替换成了变量 a
的值,而s
没有变,原样输出了。这是因为,
%d
才是格式控制符,%ds
在一起没有意义,s
仅仅是跟在%d
后面的一个普通字符,所以会原样输出。
【拓展】如何在字符串中书写长文本
假设现在我们要输出一段比较长的文本,它的内容为:
解学武的个人门户网站,一个学习C语言和数据结构的网站,坚持用工匠的精神来打磨每一套教程。坚持做好一件事情,做到极致,让自己感动,让用户心动,这就是足以传世的作品!网站的网址是:https://xiexuewu.github.io
如果将这段文本放在一个字符串中,会显得比较臃肿,格式也不好看,就像下面这样:
超出编辑窗口宽度的文本换行
超出编辑窗口宽度的文本隐藏
当文本超出编辑窗口的宽度时,可以选择将文本换行,也可以选择将文本隐藏(可以在编辑器里面自行设置),但是不管哪种形式,在一个字符串里书写长文本总是不太美观。
当然,你可以多写几个 puts 函数,就像下面这样:
我不否认这种写法也比较美观,但是这里我要讲的是另外一种写法:
#include <stdio.h> int main() { puts( "解学武的个人门户网站,一个学习C语言和数据结构的网站,坚持用工匠的精神来打磨每一套教程。" "坚持做好一件事情,做到极致,让自己感动,让用户心动,这就是足以传世的作品!" "网站的网址是:https://xiexuewu.github.io" ); return 0; }
在 puts 函数中,可以将一个较长的字符串分割成几个较短的字符串,这样会使得长文本的格式更加整齐。
注意,这只是形式上的分割,编译器在编译阶段会将它们合并为一个字符串,它们放在一块连续的内存中。
多个字符串并不一定非得换行,也可以将它们写在一行中,例如:
#include <stdio.h> int main() { puts("C语言" "C语言和数据结构!" "https://xiexuewu.github.io"); return 0; }
本节讲到的 puts、printf,以及后面要讲到的 fprintf、fputs 等与字符串输出有关的函数,都支持这种写法。
2、printf高级用法
前面带大家学习了 printf() 的基本用法,接下来介绍 printf() 的高级用法。
首先汇总一下前面学到的格式控制符:
格式控制符 | 说明 |
---|---|
%c | 输出一个单一的字符 |
%hhd、%hd、%d、%ld、%lld | 以十进制、有符号的形式输出 char、short、int、long、long long 类型的整数 |
%hhu、%hu、%u、%lu、%llu | 以十进制、无符号(unsigned)的形式输出 char、short、int、long、long long 类型的整数 |
%hho、%ho、%o、%lo、%llo | 以八进制、不带前缀、无符号的形式输出 char、short、int、long、long long 类型的整数 |
%#hho、%#ho、%#o、%#lo、%#llo | 以八进制、带前缀、无符号的形式输出 char、short、int、long、long long 类型的整数 |
%hhx、%hx、%x、%lx、%llx %hhX、%hX、%X、%lX、%llX |
以十六进制、不带前缀、无符号的形式输出 char、short、int、long、long long 类型的整数。如果 x 小写,那么输出的十六进制数字也小写;如果 X 大写,那么输出的十六进制数字也大写。 |
%#hhx、%#hx、%#x、%#lx、%#llx %#hhX、%#hX、%#X、%#lX、%#llX |
以十六进制、带前缀、无符号的形式输出 char、short、int、long、long long 类型的整数。如果 x 小写,那么输出的十六进制数字和前缀都小写;如果 X 大写,那么输出的十六进制数字和前缀都大写。 |
%f、%lf、%Lf | 以十进制的形式输出 float、double、long double 类型的小数 |
%e、%le、%Le %E、%lE、%LE |
以指数的形式输出 float、double、long double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。 |
%g、%lg、%Lg %G、%lG、%LG |
以十进制和指数中较短的形式输出 float、double、long double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。 |
%s | 输出一个字符串 |
通过前面的学习,相信你已经熟悉了 printf() 的基本用法,但是这还不足以把它发挥到极致,printf() 可以有更加炫酷、更加个性、更加整齐的输出形式。
假如现在老师要我们输出一个 4×4 的整数矩阵,为了增强阅读性,数字要对齐,怎么办呢?我们显然可以这样做:
#include <stdio.h> int main() { int a1=20, a2=345, a3=700, a4=22; int b1=56720, b2=9999, b3=20098, b4=2; int c1=233, c2=205, c3=1, c4=6666; int d1=34, d2=0, d3=23, d4=23006783; printf("%d %d %d %d\n", a1, a2, a3, a4); printf("%d %d %d %d\n", b1, b2, b3, b4); printf("%d %d %d %d\n", c1, c2, c3, c4); printf("%d %d %d %d\n", d1, d2, d3, d4); return 0; }
运行结果:
20 345 700 22 56720 9999 20098 2 233 205 1 6666 34 0 23 23006783
矩阵一般在大学的《高等数学》中会讲到,m×n 的数字矩阵可以理解为把 m×n 个数字摆放成 m 行 n 列的样子。
看,这是多么地自虐,要敲那么多空格,还要严格控制空格数,否则输出就会错位。更加恶心的是,如果数字的位数变了,空格的数目也要跟着变。例如,当 a1 的值是 20 时,它后面要敲八个空格;当 a1 的值是 1000 时,它后面就要敲六个空格。每次修改整数的值,都要考虑修改空格的数目,逼死强迫症。
类似的需求随处可见,整齐的格式会更加美观,让人觉得生动有趣。其实,我们大可不必像上面一样,printf() 可以更好的控制输出格式。更改上面的代码:
#include <stdio.h> int main() { int a1=20, a2=345, a3=700, a4=22; int b1=56720, b2=9999, b3=20098, b4=2; int c1=233, c2=205, c3=1, c4=6666; int d1=34, d2=0, d3=23, d4=23006783; printf("%-9d %-9d %-9d %-9d\n", a1, a2, a3, a4); printf("%-9d %-9d %-9d %-9d\n", b1, b2, b3, b4); printf("%-9d %-9d %-9d %-9d\n", c1, c2, c3, c4); printf("%-9d %-9d %-9d %-9d\n", d1, d2, d3, d4); return 0; }
输出结果:
20 345 700 22 56720 9999 20098 2 233 205 1 6666 34 0 23 23006783
这样写起来更加方便,即使改变某个数字,也无需修改 printf() 语句,增加或者减少空格数目。
%-9d
中,d
表示以十进制输出,9
表示最少占9个字符的宽度,宽度不足以空格补齐,-
表示左对齐。综合起来,%-9d
表示以十进制输出,左对齐,宽度最小为9个字符。大家可以亲自试试%9d
的输出效果。
printf() 格式控制符的完整形式如下:
%[flag][width][.precision]type
[ ] 表示此处的内容可有可无,是可以省略的。
1) type 表示输出类型,比如 %d、%f、%c、%lf,type 就分别对应
d、f、c、lf;再如,%-9d
中 type 对应 d。
type 这一项必须有,这意味着输出时必须要知道是什么类型。
2) width 表示最小输出宽度,也就是至少占用几个字符的位置;例如,%-9d
中
width 对应 9,表示输出结果最少占用 9 个字符的宽度。
当输出结果的宽度不足 width 时,以空格补齐(如果没有指定对齐方式,默认会在左边补齐空格);当输出结果的宽度超过 width 时,width 不再起作用,按照数据本身的宽度来输出。
下面的代码演示了 width 的用法:
#include <stdio.h> int main(){ int n = 234; float f = 9.8; char c = '@'; char *str = "https://xiexuewu.github.io"; printf("%10d%12f%4c%8s", n, f, c, str); return 0; }
运行结果:
234 9.800000 @https://xiexuewu.github.io
对输出结果的说明:
- n 的指定输出宽度为 10,234 的宽度为 3,所以前边要补上 7 个空格。
- f 的指定输出宽度为 12,9.800000 的宽度为 8,所以前边要补上 4 个空格。
- str 的指定输出宽度为 8,"https://xiexuewu.github.io" 的宽度为 26,超过了 8,所以指定输出宽度不再起作用,而是按照 str 的实际宽度输出。
3) .precision 表示输出精度,也就是小数的位数。
- 当小数部分的位数大于 precision 时,会按照四舍五入的原则丢掉多余的数字;
- 当小数部分的位数小于 precision 时,会在后面补 0。
另外,.precision 也可以用于整数和字符串,但是功能却是相反的:
- 用于整数时,.precision 表示最小输出宽度。与 width 不同的是,整数的宽度不足时会在左边补 0,而不是补空格。
- 用于字符串时,.precision 表示最大输出宽度,或者说截取字符串。当字符串的长度大于 precision 时,会截掉多余的字符;当字符串的长度小于 precision 时,.precision 就不再起作用。
请看下面的例子:
#include <stdio.h> int main(){ int n = 123456; double f = 882.923672; char *str = "abcdefghi"; printf("n: %.9d %.4d\n", n, n); printf("f: %.2lf %.4lf %.10lf\n", f, f, f); printf("str: %.5s %.15s\n", str, str); return 0; }
运行结果:
n: 000123456 123456 f: 882.92 882.9237 882.9236720000 str: abcde abcdefghi
对输出结果的说明:
- 对于 n,.precision 表示最小输出宽度。n 本身的宽度为 6,当 precision 为 9 时,大于 6,要在 n 的前面补 3 个 0;当 precision 为 4 时,小于 6,不再起作用。
- 对于 f,.precision 表示输出精度。f 的小数部分有 6 位数字,当 precision 为 2 或者 4 时,都小于 6,要按照四舍五入的原则截断小数;当 precision 为 10 时,大于 6,要在小数的后面补四个 0。
- 对于 str,.precision 表示最大输出宽度。str 本身的宽度为 9,当 precision 为 5 时,小于 9,要截取 str 的前 5 个字符;当 precision 为 15 时,大于 9,不再起作用。
4) flag 是标志字符。例如,%#x
中 flag 对应 #,%-9d
中 flags
对应-
。下表列出了 printf() 可以用的 flag:
标志字符 | 含 义 |
---|---|
- | - 表示左对齐。如果没有,就按照默认的对齐方式,默认一般为右对齐。 |
+ | 用于整数或者小数,表示输出符号(正负号)。如果没有,那么只有负数才会输出符号。 |
空格 | 用于整数或者小数,输出值为正时冠以空格,为负时冠以负号。 |
# |
|
请看下面的例子:
#include <stdio.h> int main(){ int m = 192, n = -943; float f = 84.342; printf("m=%10d, m=%-10d\n", m, m); //演示 - 的用法 printf("m=%+d, n=%+d\n", m, n); //演示 + 的用法 printf("m=% d, n=% d\n", m, n); //演示空格的用法 printf("f=%.0f, f=%#.0f\n", f, f); //演示#的用法 return 0; }
运行结果:
m= 192, m=192 m=+192, n=-943 m= 192, n=-943 f=84, f=84.
对输出结果的说明:
- 当以
%10d
输出 m 时,是右对齐,所以在 192 前面补七个空格;当以%-10d
输出 m 时,是左对齐,所以在 192 后面补七个空格。 - m 是正数,以
%+d
输出时要带上正号;n 是负数,以%+d
输出时要带上负号。 - m 是正数,以
% d
输出时要在前面加空格;n 是负数,以% d
输出时要在前面加负号。 %.0f
表示保留 0 位小数,也就是只输出整数部分,不输出小数部分。默认情况下,这种输出形式是不带小数点的,但是如果有了#
标志,那么就要在整数的后面“硬加上”一个小数点,以和纯整数区分开。
printf() 不能立即输出的问题
printf() 有一个尴尬的问题,就是有时候不能立即输出,请看下面的代码:
#include<stdio.h> #include<unistd.h> int main() { printf("C语言"); sleep(5); //程序暂停5秒钟 printf("https://xiexuewu.github.io\n"); return 0; }
这段代码使用了两个 printf() 语句,它们之间有一个 sleep() 函数,该函数的作用是让程序暂停 5 秒,然后再继续执行。sleep() 是 Linux 和 Mac OS 下特有的函数,不能用于 Windows。当然,Windows 下也有功能相同的暂停函数,叫做 Sleep(),稍后我们会讲解。
在 Linux 或者 Mac OS 下运行该程序,会发现第一个 printf() 并没有立即输出,而是等待 5 秒以后,和第二个 printf() 一起输出了,请看下面的动图演示:
我们不妨修改一下代码,在第一个 printf() 的最后添加一个换行符,如下所示:
printf("C语言\n");
再次编译并运行程序,发现第一个 printf()
首先输出(程序运行后立即输出),等待 5 秒以后,第二个 printf()
才输出,请看下面的动图演示:
为什么一个换行符\n
就能让程序的表现有天壤之别呢?按照通常的逻辑,程序运行后第一个
printf() 应该立即输出,而不是等待 5 秒以后再和第二个 printf()
一起输出,也就是说,第二种情形才符合我们的惯性思维。然而,第一种情形该如何理解呢?
其实,这一切都是输出缓冲区(缓存)在作怪!
从本质上讲,printf()
执行结束以后数据并没有直接输出到显示器上,而是放入了缓冲区,直到遇见换行符\n
才将缓冲区中的数据输出到显示器上。更加深入的内容,我将在《进入缓冲区(缓存)的世界,破解一切与输入输出有关的疑难杂症》中详细讲解。
以上测试的是 Linux 和 Mac OS,我们不妨再测试一下 Windows,请看下面的代码:
#include<stdio.h> #include<Windows.h> int main() { printf("C语言"); Sleep(5000); //程序暂停5秒钟 printf("https://xiexuewu.github.io\n"); return 0; }
在 Windows 下,想让程序暂停可以使用 Windows.h 头文件中的 Sleep()
函数(S
要大写),它和 Linux 下的 sleep() 功能相同。不过,sleep()
要求的时间单位是秒,而 Sleep() 要求的时间单位是毫秒,1 秒等于 1000
毫秒。这段代码中,我们要求程序暂停 5000 毫秒,也即 5 秒。
编译并运行程序,会发现第一个 printf() 首先输出(程序运行后立即输出),等待 5 秒以后,第二个 printf() 才输出。
在第一个 printf() 的最后添加一个换行符,情况也是一样的,第一个 printf() 从来不会和第二个 printf() 一起输出。
你看,Windows 和 Linux、Mac OS 的情况又不一样。这是因为,Windows 和 Linux、Mac OS 的缓存机制不同。
要想破解 printf() 输出的问题,必须要了解缓存,它能使你对输入输出的认识上升到一个更高的层次,以后不管遇到什么疑难杂症,都能迎刃而解。可以说,输入输出的“命门”就在于缓存。
总结
对于初学者来说,上面讲到的 printf() 用法已经比较复杂了,基本满足了实际开发的需求,相信大家也需要一段时间才能熟悉。
吸收了本文中 printf 函数的所有用法,你就成功掌握了它。