我们已经了解了如何使用printf()打印信息,以及如何使用scanf()接受用户输入,但我忽略了这两个函数的许多细节。现在让我们来看看其中的一些细节。
printf()函数是C语言的主要输出函数。我们已经用它来打印简单的字符串,如"Hello,world/n"。在2.2.1节中,我们还尝试过用它来打印变量。它可以打印所有变量类型,只需提供正确的格式字符串即可。
当我们调用printf()时,首先提供的通常是一个字符串字面量。第一个参数被称为格式字符串。你可以将简单的字符串“按原样”回传到终端,也可以打印(并格式化)变量的值。使用格式字符串可以让printf()知道将要打印的内容。我们在讨论声明和赋值时,创建了一些变量,让我们来打印这些变量。请看 ch02/hello3.c :
这就是结果:
将输出结果与源代码进行比较,你可以看到,我们大部分时间都是按原样打印格式字符串中的字符。但是,当我们遇到格式指定符时,我们会用格式字符串后面的一个参数值来代替。仔细观察我们对printf()的第一次调用。在格式字符串中有两个格式指定符。在格式字符串之后,我们提供了两个变量。这两个变量从左到右依次填入格式说明符。如果你检查输出结果,就会发现第一行输出确实首先包含了count的值,然后是total的值。真不错,我们还得到了字符和字符串变量的输出结果。
如果你注意到每种类型都使用了不同的说明符,那么恭喜你!你找到了这些语句中的重要区别。(如果看起来还有点像胡言乱语,也不要放弃。随着你阅读和练习的深入,模式和不符合模式的东西都会逐渐显现出来。)事实上,printf()有很多格式指定符,如表2-5所示。有些是显而易见的,而且明显与特定类型相关。其他的就比较深奥了,但这就是书的作用。你会记住最常用的几个指定符,需要时还可以查找不常用的指定符。
表2-5:printf()的常用格式指定符类型
此外还有其他格式,但这些格式将留待以后需要打印奇特或特殊数据时使用。这些格式将涵盖日常所需的绝大部分内容。附录B将对本书中使用的所有格式进行更详细的讨论。
但是,如何格式化这些值呢?毕竟,C语言使用了“格式字符串”和“格式说明符”这两个短语。为实现这一目标,你需要向格式说明符添加信息。最常见的例子之一就是打印浮点数,如银行账户余额或模拟传感器读数。让我们给出一些有趣的小数,并尝试打印出来。
我们声明了3个变量,一个浮点类型、两个双类型。我们在printf()语句中使用%f格式指定符。好极了!下面是编译并运行程序后的结果:
它们都有6位小数,尽管我们并没有指定要多少位小数,而且我们的变量中没有一个正好有6位小数。为了获得恰到好处的信息量,你可以给格式指定器一些额外的细节。所有格式指定器都可以接受宽度和精度参数。这两个参数都是可选的,你可以提供其中一个或两个参数。如图2-2所示,额外参数看起来像一个小数: width.precision ,这些参数位于百分号和类型字符之间。
图2-2:隐式转换层级结构
对于浮点数来说,使用这两个选项都很有意义。我们现在可以要求更多或更少的位数。试着这样修改 ch02/floats.c 中的3个printf()调用:
我在扩展格式指定符前后添加了竖条或管道字符(|),这样你就能看到宽度元素对输出的影响。看看新的结果吧:
❶我们的数值0.5将以小数点后两位的精度显示,字段总宽度为5个字符。因为我们不需要全部5个位置,所以在开头添加了一个空格字符。
❷一个较长的小数位数被打印在12个位置内。请注意,我们得到的小数点后6位数与未指定任何宽度或精度时相同。
❸一个更长的小数显示在12个位置内,但包括10位精度。注意这里的12是总宽度,包括小数点后的数字所占的位置。
对于printf(),如果给定了宽度,则你要求的精度和实际打印值优先于宽度。你经常会看到“%0.2f”或“%.1f”这样的浮点格式,它们能在所需的精确位数内提供正确的小数位数。例如,将这两种格式应用于π时,结果将分别是3.14和3.1。
对于字符串或int等其他类型,宽度选项相当简单。例如,如 ch02/tabular.c 所示,无论打印的是什么值,都使用相同的宽度,就可以非常方便地打印表格数据:
列对齐效果极佳:
非常棒。请注意我是如何处理列标签的。我使用了格式说明符和字符串字面量,而不是手动间隔标签的单一字符串。我这样做是为了强调输出宽度的使用,尽管手动操作并不困难。事实上,在这几列中手动将标签居中会更容易。如果你想做个小练习,打开 tabular.c 文件并尝试调整第一个printf()来看看能否让标签居中。
虽然宽度选项对于所有类型都很直观,但对于非浮点格式,添加精度选项的效果可能就不那么直观了。对于字符串,指定精度会导致截断文本以适应给定的字段宽度。(对于int和char类型,通常没有影响,但编译器可能会警告你不要依赖这种“典型”行为。)
输出的另一面是输入。在2.2.1节中,我们已经介绍了如何使用scanf()函数来实现这一功能。现在,你可能已经认识到我们在那个简单程序中使用的%s作为格式指定符。这种熟悉感还不止于此:你可以使用表2-5中列出的所有格式指定符和scanf(),从用户输入中获取这些类型的值。
关于使用scanf()的变量,有一点我需要说明。在第一个例子中,我们幸运地扫描到了一个字符串。如果你还记得,C语言中的字符串实际上就是char类型的数组。我们将在第4章和第6章中看到更多关于这个主题的内容,但在这里,我只想指出数组是指针在C语言中的一种特殊情况。指针是指内存中事物地址(位置)的特殊值。函数scanf()使用的是变量的地址,而不是变量的值。事实上,scanf()函数的作用就是将一个值放入变量中。由于数组实际上是指针,因此可以直接使用字符数组变量。但是,要使用scanf()来使用数字变量和单个字符变量,必须在变量名上使用一个特殊的前缀&。
我将在第6章详细介绍&前缀。它告诉编译器使用变量的地址——非常适合scanf()。看看这个小代码段:
请注意在scanf()行中使用name变量与使用&age变量的不同之处。这完全是因为name是一个数组,而age是一个简单的整数。遗憾的是,这一点很容易被遗忘。不必担心,这很容易解决,如果你忘记了,编译器会提醒你:
当看到这个“expects type”错误时,请记住,int、float、char和类似的非数组变量在与scanf()一起使用时总是需要&前缀。