第三章 格式化输入/输出《C语言程序设计现代方法(第2版)》读书笔记

简介: 第三章 格式化输入/输出《C语言程序设计现代方法(第2版)》读书笔记

第三章 格式化输入/输出 🚀


scanf函数和printf函数是C语言编程中使用最频繁的两个函数,它们用来格式化输入和输

出。



3.1 printf 函数🚀

  printf函数被设计用来显示 格式串 ( format string )的内容,并且在该串中的指定位置插

入可能的值。调用 printf 函数时必须提供格式串,格式串后面的参数是需要在显示时插入到

该串中的值:

       printf(格式串 , 表达式 1, 表达式 2, ...);

显示的值可以是常量、变量或者更加复杂的表达式。调用 printf 函数一次可以打印的值的个数

没有限制。

       格式串包含普通字符和转换说明 ( conversion specification ),其中转换说明以字符 % 开头。转换说明是用来表示打印过程中待填充的值的占位符。跟随在字符% 后边的信息指定了把数值从内部形式(二进制)转换 成打印形式(字符)的方法,这也就是“转换说明”这一术语的由来。例如,转换说明%d 指定 printf 函数把 int 型值从二进制形式转换成十进制数字组成的字符串,转换说明%f 对 float 型值也进行类似的转换。


注意:

C语言编译器不会检测格式串中转换说明的数量是否和输出项的数量相匹配。 下

面这个 printf 函数调用所拥有的转换说明的数量就多于要显示的值的数量:

       printf("%d %d\ n", i); /*** WRONG ***/

printf函数将正确显示变量i的值,接着显示另一个(无意义的)整数值。 函数调用

带有太少的转换说明也会出现类似的问题:

       printf("%d\n", i, j); /*** WRONG ***/

在这种情况下,printf函数会显示变量i的值,但是不显示变量j的值。

此外, C 语言编译器也不检测转换说明是否适合要显示项的数据类型。如果程序

员使用不正确的转换说明,程序将会简单地产生无意义的输出。思考下面的 printf

函数调用, 其中int型变量i和float型变量x的顺序放置错误:

       printf("%f %d\n", i, x); /*** WRONG ***/

因为 printf 函数必须服从于格式串,所以它将如实地显示出一个 float 型值,接着是

一个 int 型值。可惜这两个值都将是无意义的。


3.1.1 转换说明🚀


转换说明可以用%m.pX格式或%-m.pX格式


最小栏宽 ( minimum field width ) m 指定了要显示的最少字符数量。如果要显示的数值所

需的字符数少于 m ,那么值在字段内是右对齐的。(换句话说,在值前面放置额外的空格。)例如,转换说明%4d 将以 · 123 的形式显示数 123 (本章用符号 · 表示空格字符)。如果要显示的值所需的字符数多于m ,那么字段宽度会自动扩展为所需的尺寸。因此,转换说明 %4d 将以 12345的形式显示数12345 ,而不会丢失数字。在 m 前放上一个负号会导致左对齐;转换说明 %-4d 将以123· 的形式显示 123 。

精度 ( precision ) p 的含义很难描述,因为它依赖于 转换说明符 ( conversion specifier ) X 的选择。X 表明在显示数值前需要对其进行哪种转换。对数值来说最常用的转换说明符有以下几个。


image.png


编写程序时无法预知数的大小或者数值变化范围很大的情况下,说明符 g 对于数的显示是特

别有用的。在用于显示大小适中的数时,说明符 g 采用定点十进制形式。但是,在显示非常大或非常小的数时,说明符g 会转换成指数形式以便减少所需的字符数。


3.1.2 转义序列🚀

格式串中常用的代码\n被称为转义序列escape sequence)。转义序列( 7.3节)使字符串包含一些特殊字符而不会使编译器引发问题,这些字符包括非打印的(控制)字符和对编译器有 特殊含义的字符(如 " 


image.png


当这些转义序列出现在 printf 函数的格式串中时,它们表示在显示中执行的操作。在大多

数机器上,输出 \a 会产生一声鸣响,输出 \b 会使光标从当前位置回退一个位置,输出 \n 会使光

标跳到下一行的起始位置,输出 \t 会把光标移动到下一个制表符的位置。

附带提一下,不能在字符串中只放置单独一个字符 \ ,编译器将认为它是一个转义序列的开

始。为了显示单独一个字符 \ ,需要在字符串中放置两个 \ 字符:

printf("\\"); /* prints one \ character */


3.2 scanf 函数🚀

scanf函数转换说明的用法和printf函数转换说明的用法本质上是一样的。

scanf 函数调用中像 "%d%d%f%f" 这样“紧密压缩”的格式串是很普遍的,而printf 函数的格式串很少有这样紧挨着的转换说明。


image.png


3.2.1 scanf 函数的工作方法🚀

实际上scanf函数可以做的事情远远多于目前为止已经提到的这些。scanf 函数本质上是一

种“模式匹配”函数,试图把输入的字符组与转换说明相匹配。

像printf函数一样,scanf函数是由格式串控制的。 调用时,scanf函数从左边开始处理字符串中的信息。对于格式串中的每一个转换说明,scanf函数从输入的数据中定位适当类型的项,并在必要时跳过空格。然后,scanf函数读入数据项,并且在遇到不可能属于此项的字符时停止。如果读入数据项成功,那么scanf函数会继续处理格式串的剩余部分;如果某一项不能成功读入,那么scanf函数将不再查看格式串的剩余部分(或者余下的输入数据)而立即返回。

在寻找数的起始位置时,scanf函数会忽略空白字符 ( white-space character ,包括空格符、

水平和垂直制表符、换页符和换行符)。因此,我们可以把数字放在同一行或者分散在几行内输入。考虑下面的scanf 函数调用:

scanf("%d%d%f%f", &i, &j, &x, &y);

假设用户录入 3 行输入:

 1

-20      .3

    -4.0e3

scanf 函数会把它们看成是一个连续的字符流:

·· 1¤-20 ··· .3¤ ··· -4.0e3¤

(这里使用符号 · 表示空格符,用符号 ¤ 表示换行符。)因为 scanf 函数在寻找每个数的起始位置时会跳过空白字符,所以它可以成功读取这些数。在接下来的图中,字符下方的s 表示此项被跳过,而字符下面的r 表示此项被读取为输入项的一部分:


image.png


scanf函数“忽略”了最后的换行符,实际上没有读取它。这个换行符将是下一次scanf函数调

用的第一个字符。

scanf 函数遵循什么规则来识别整数或浮点数呢?在要求读入整数时, scanf 函数首先寻找正号或负号,然后读取数字直到读到一个非数字时才停止。当要求读入浮点数时,scanf函数

会寻找一个正号或负号(可选),随后是一串数字(可能含有小数点),再后是一个指数(可选)。指数由字母e (或者字母 E )、可选的符号和一个或多个数字构成。在用于 scanf 函数时,转换说明%e 、 %f 和 %g 是可以互换的,这 3 种转换说明在识别浮点数方面都遵循相同的规则。

当scanf函数遇到一个不可能属于当前项的字符时,它会把此字符“放回原处”,以便在扫描下一个输入项或者下一次调用scanf函数时再次读入。 思考下面(公认有问题的) 4 个数的排列:


image.png

image.png


3.2.2 格式串中的普通字符🚀

通过编写含有普通字符和转换说明的格式串能更进一步地理解模式匹配的概念。处理格式

串中的普通字符时,scanf函数采取的动作依赖于这个字符是否为空白字符。

       空白字符。 当在格式串中遇到一个或多个连续的空白字符时,scanf函数从输入中重复

读空白字符直到遇到一个非空白字符(把该字符“放回原处”)为止。 格式串中空白字

符的数量无关紧要,格式串中的一个空白字符可以与输入中任意数量的空白字符相匹

配。(附带提一下,在格式串中包含空白字符并不意味着输入中必须包含空白字符。格

式串中的一个空白字符可以与输入中 任意 数量的空白字符相匹配,包括零个。)

       其他字符。 当在格式串中遇到非空白字符时,scanf函数将把它与下一个输入字符进行

比较。 如果两个字符相匹配,那么 scanf 函数会放弃输入字符而继续处理格式串。如果

两个字符不匹配,那么 scanf 函数会把不匹配的字符放回输入中,然后异常退出,而不

进一步处理格式串或者从输入中读取字符。

例如,假设格式串是"%d/%d"。如果输入是:


· 5/ · 96

在寻找整数时, scanf 函数会跳过第一个空格,把 %d 与 5 相匹配,把 / 与 / 相匹配,在寻找下一个整数时跳过一个空格,并且把%d 与 96 相匹配。另一方面,如果输入是:

· 5 · / · 96

scanf 函数会跳过一个空格,把 %d 与 5 相匹配,然后试图把格式串中的 / 与输入中的空格相匹配。但是二者不匹配,所以scanf 函数把空格放回原处,把字符· / · 96 留给下一次 scanf 函数调用来读取。为了允许第一个数后边有空格,应使用格式串"%d /%d" 。


3.2.3 易混淆的 printf 函数和 scanf 函数🚀


image.png


问与答(很重要)🚀

*问:转换说明%i也可以用于读写整数。%i%d之间有什么区别?p.27

* 答:在 printf 格式串中使用时,二者没有区别。但是,在 scanf 格式串中 %d 只能与十进制(基数为 10 )形式的整数相匹配,而%i 则可以匹配用八进制(基数为 8 )、十进制或十六进制(基数为 16 )表示的整数。如果输入的数有前缀0 (如 056 ),那么 %i 会把它作为八进制数( 7.1 节)来处理;如果输入的数有前缀0x 或 0X (如 0x56 ),那么 %i 会把它作为十六进制数( 7.1 节)来处理。如果用户意外地将0 放在数的开始处,那么用 %i 代替 %d 读取数可能有意想不到的结果。由于这是一个陷阱,所以建议坚持采用%d 。


问:如果printf函数将%作为转换说明的开始,那么如何显示字符%呢?(选读)


* 答:如果 printf 函数在格式串中遇到两个连续的字符 % ,那么它将显示出一个字符 % 。例如,语句

printf("Net profit: %d%%\n", profit);

可以显示出

Net profit: 10%


* 问:转义序列 \t 会使 printf 函数跳到下一个水平制表符处。如何知道水平制表符到底跳多远呢? ( p.29 )

* 答:不可能知道。打印 \t 的效果不是由 C 语言定义的,而是依赖于所使用的操作系统。水平制表符之间的距离通常是8 个字符宽度,但 C 语言本身无法保证这一点。


问:如果要求读入一个数,而用户却录入了非数值的输入,那么scanf函数会如何处理?


* 答:请看下面的例子:

printf("Enter a number: ");

scanf("%d", &i);

假设用户录入了一个有效数,后边跟着一些非数值的字符:

Enter a number: 23foo

这种情况下,scanf函数读取2和3,并且将23存储在变量i中,而剩下的字符(foo)则留给下一次scanf函数调用(或者某些其他的输入函数)来读取。 另一方面,假设输入从开始就是无效的:

Enter a number: foo

这种情况下,没有值会被存储到变量i中,字符foo会留给下一次scanf函数调用。

如何处理这种糟糕的情况呢?后面将看到检测 scanf 函数调用是否成功( 22.3 节)的方法。如

果调用失败,可以终止或者尝试恢复程序,可能的方法是丢掉有问题的输入并要求用户重新输入。(在第22 章结尾的“问与答”部分会讨论有关丢弃错误输入的方法。)



问:我不能理解scanf函数如何把字符“放回原处”并在以后再次读取。(p.31)


答:我们知道,用户从键盘输入时,程序并没有读取输入,而是把用户的输入放在一个隐藏的缓冲区中,由scanf 函数来读取。 scanf 函数把字符放回到缓冲区中供后续读取是非常容易的。第 22 章将会更详细地讨论输入缓冲。



问:如果用户在两个数之间加入了标点符号(如逗号),scanf函数将如何处理?


答:先来看一个简单的例子。假设我们想用 scanf 函数读取一对整数:

printf("Enter two numbers: ");

scanf("%d%d", &i, &j);

如果用户录入

4,28

scanf 函数将读取 4 并且把它存储在变量 i 中。在寻找第二个数的起始位置时, scanf 函数遇到了逗号。因为数不能以逗号开头,所以scanf 函数立刻返回,而把逗号和第二个数留给下一次scanf 函数调用。

当然,如果能确定数与数之间始终用逗号进行分割,我们可以很容易地解决这个问题,只要在格式串中添加逗号即可:

printf("Enter two numbers, separated by a comma: ");

scanf("%d,%d", &i, &j);

相关文章
|
1月前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
22 3
|
2月前
|
C语言
C语言程序设计核心详解 第四章&&第五章 选择结构程序设计&&循环结构程序设计
本章节介绍了C语言中的选择结构,包括关系表达式、逻辑表达式及其运算符的优先级,并通过示例详细解释了 `if` 语句的不同形式和 `switch` 语句的使用方法。此外,还概述了循环结构,包括 `while`、`do-while` 和 `for` 循环,并解释了 `break` 和 `continue` 控制语句的功能。最后,提供了两道例题以加深理解。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
2月前
|
C语言
C语言程序设计核心详解 第三章:顺序结构,printf(),scanf()详解
本章介绍顺序结构的基本框架及C语言的标准输入输出。程序从`main()`开始依次执行,框架包括输入、计算和输出三部分。重点讲解了`printf()`与`scanf()`函数:`printf()`用于格式化输出,支持多种占位符;`scanf()`用于格式化输入,需注意普通字符与占位符的区别。此外还介绍了`putchar()`和`getchar()`函数,分别用于输出和接收单个字符。
|
2月前
|
存储 算法 C语言
C语言程序设计核心详解 第一章:数制及转换与ASCII码
本专栏旨在夯实C语言基础,涵盖基础知识与进阶内容,助力解决自命题考试和考研问题,为数据结构与算法设计奠定坚实基础。内容包括数制及其转换、ASCII码、内存管理、机器码等,重点讲解二进制、八进制、十六进制的概念与转换方法,并介绍C语言的结构、数据类型和标识符规范。
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第六章 数组_一维数组_二维数组_字符数组详解
本章介绍了C语言中的数组概念及应用。数组是一种存储同一类型数据的线性结构,通过下标访问元素。一维数组定义需指定长度,如`int a[10]`,并遵循命名规则。数组元素初始化可使用 `{}`,多余初值补0,少则随机。二维数组扩展了维度,定义形式为`int a[3][4]`,按行优先顺序存储。字符数组用于存储字符串,初始化时需添加结束符`\0`。此外,介绍了字符串处理函数,如`strcat()`、`strcpy()`、`strcmp()` 和 `strlen()`,用于拼接、复制、比较和计算字符串长度。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
2月前
|
C语言
C语言程序设计核心详解 第二章:数据与数据类型 4种常量详解 常见表达式详解
本文详细介绍了C语言中的数据与数据类型,包括常量、变量、表达式和函数等内容。常量分为整型、实型、字符型和字符串常量,其中整型常量有十进制、八进制和十六进制三种形式;实型常量包括小数和指数形式;字符型常量涵盖常规字符、转义字符及八进制、十六进制形式;字符串常量由双引号括起。变量遵循先定义后使用的规则,并需遵守命名规范。函数分为标准函数和自定义函数,如`sqrt()`和`abs()`。表达式涉及算术、赋值、自增自减和逗号运算符等,需注意运算符的优先级和结合性。文章还介绍了强制类型转换及隐式转换的概念。