智能程序设计(C语言)


格式化输入输出

计算机学院    杨已彪

yangyibiao@nju.edu.cn

提纲

  • 输出: printf与转换说明

  • 转义序列

  • 输入: scanf

  • getchar与putchar

  • 综合案例: 分数相加


printf函数

printf 函数必须提供格式字符串, 后跟打印期间要插入到串中的任何值:

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

格式字符串可以包含普通字符和转换说明, 以 % 字符开头

转换说明 是一个占位符, 表示要在打印期间填充的值

  • %d 用于 int 值

  • %f 用于 float / double 值 (printf 中 float 自动提升为 double)

  • %c 用于 char 值

  • %s 用于 char [] 值


printf函数

格式字符串:

  • 普通字符 按出现在字符串中的方式打印

  • 转换说明 则需用待显示的值来替换填充

int i, j;
float x, y;
 
i = 10;
j = 20;
x = 43.2892f;
y = 5527.0f;

printf("i = %d, j = %d, x = %f, y = %f\n", i, j, x, y);
输出: 
i = 10, j = 20, x = 43.289200, y = 5527.000000

printf函数

  • 编译器不检查格式串与输出项是否匹配:

printf("%d %d\n", i);    /*** WRONG: 转换说明过多 ***/
printf("%d\n", i, j);    /*** WRONG: 转换说明太少 ***/
  • 也不检查转换说明是否合适; 用错了 → 无意义的输出

int i;
float x;
printf("%f %d\n", i, x); /*** WRONG ***/
  • %d 配 float/double 时, 读到的不是这个数的比特: 整数和浮点数调用时走不同的通道, %d 在整数通道里拿到的只是残留的无关值 (第 3 周细讲)


转换说明

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

  • m 和 p 是整数常量

  • X 是一个字符

  • m 和 p 都是可选的

  • 如果省略 p, 则分隔 m 和 p 的句点也应删除

在转换规约 %10.2f 中, m 为 10 , p 为 2, X 为 f 在转换规约 %10f 中, m 为 10 且缺少 p(连同句点) 在转换规约 %.2f 中, p 为 2 且缺少 m


转换说明

最小字段宽度 m 指定要打印的最小字符数

  • 如果要打印的值需要少于 m 个字符, 则在字段内右对齐

转换说明 %4d 会将数字 123 显示为 •123(• 表示空格字符)

  • 如果要打印的值需要超过 m 个字符, 则字段宽度会自动扩展为所需的大小, 若 m 前面加上减号会导致左对齐

转换说明 %-4d 会将数字 123 显示为 123•


转换说明

精度 p 的含义取决于转换说明符 X 的选择

  • 结合 %d 说明符使用, %m.pX 中的 p 表示要显示的最小位数(如需要, 将在数字的开头添加额外的零)

  • 如果 p 省略, 则默认它的值为 1

浮点数的转换说明:

  • %e 指数格式: p 表示小数点后位数 (默认 6; p 为 0 不显示小数点)

  • %f 固定十进制格式: p 含义与 %e 相同

  • %g 按数字大小自动选指数或定点: p 表示最大有效位数, 不显示尾随零


程序: 使用printf格式化数字

/* Prints int and float values in various formats */ 
#include <stdio.h>
 
int main(void)
{
  int i;
  float x;
 
  i = 40;
  x = 839.21f;
 
  printf("|%d|%5d|%-5d|%5.3d|\n", i, i, i, i);
  printf("|%10.3f|%10.3e|%-10g|\n", x, x, x);
 
  return 0;
}

tprintf.c程序使用 printf 以各种格式显示整数和浮点数

输出:
|40|•••40|40•••|••040|
|•••839.210|•8.392e+02|839.21••••|

转义序列

形如 \n 的代码称为 转义序列

转义序列: 使字符串包含非打印(控制)字符和具有特殊含义的字符(如 ")

转义序列的部分列表:

警报(铃声) \a
回退符 \b
换行符 \n
水平制表符 \t
  • 一个字符串可以包含任意数量的转义序列:

  printf("Item\tUnit\tPurchase\n\tPrice\tDate\n");

转义序列

另一常见的转义序列是 \", 代表 " 字符:

printf("\"Hello!\""); /* prints "Hello!" */

要打印单个 \ 字符, 在字符串中放置两个 \:

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

scanf函数

scanf 根据指定格式读取输入; 格式字符串同样含普通字符与转换说明, 与 printf 基本相同

在多数情况下, scanf 格式字符串只包含转换说明:

int i, j;
float x, y;

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

样本输入:

1 -20 .3 -4.0e3

scanf 将变量 i、j、x和y赋值为 1、–20、0.3 和 –4000.0


scanf函数

使用 scanf 时, 程序员须自己检查:

  • 转换说明 的数量与 输入变量 的数量是否相同

  • 确保各个转换说明 适用于 相应变量

& 符号通常需要用在 scanf 调用中的每个变量之前

& 通常(但不总是)是必需的, 在读取输入至变量时需记住使用它


scanf工作原理

  • scanf 尝试将输入字符组与格式字符串中的转换说明进行匹配

  • 对每个转换说明, scanf 尝试在输入数据中寻找适当类型数据, 必要时跳过空格

  • 当被要求读取整数时, 首先搜索数字或正负号, 然后继续读取数字, 直到遇到非数字

  • 遇到不能成为当前变量一部分的字符时, 会 放回 该字符留待下次读取

  • 如果数据读取不成功, scanf 立即返回

  • 输入流中没有可用的数据时, scanf 会等待用户输入——交互式程序"卡住"往往就是在等你敲键盘


scanf工作原理

  • scanf 搜索数字时忽略空白 (空格/制表/换行); 读取四个数字的调用:

scanf("%d%d%f%f", &i, &j, &x, &y);
  • 输入的数字可以在一行上, 也可以分布在多行上:

••1
-20•••.3
•••-4.0e3

样本输入 1-20.3-4.0e3¤ 的处理步骤:

  • %d 把 1 存进 i、把 -20 存进 j (分别放回 - 和 .)

  • %f 把 0.3 存进 x、把 -4.0×10³ 存进 y (放回 - 和换行符)


scanf 与输入缓冲区

scanf 每次从上次读到的位置接着读, 程序里的多次调用共享同一条输入字符流:

int a, b;
printf("请输入两个整数: ");
scanf("%d", &a);          /* 读走 10, 剩 " 20↵" */
printf("a = %d\n", a);    /* 马上打印已读到的 */
scanf("%d", &b);          /* 跳过空格, 接着读 20 */
printf("b = %d\n", b);

用户只输入一行 10 20↵:

请输入两个整数: 10 20↵
a = 10
b = 20
  • 输入先全部进入缓冲区, 两条 scanf 依次"消费": 第一个只拿走 10, 第二个接着拿走 20

  • 夹在中间的 printf 打印的是"已经读到的"——输入与输出互不干扰

  • scanf 读走的数据不会再次被读到(不回看、不重复)

试一试: 如果第一次只输入 10↵ 就回车, 会发生什么? 程序会卡住等待继续输入——第二个 scanf 在缓冲区里找不到整数


scanf 读字符与"遗留的换行"

  • %d、%f 会自动跳过空格和换行; %c 不会——它读到的可能就是上一次输入留下的换行符

int n; char ch;
scanf("%d", &n);
scanf("%c", &ch);       /* 读到的是 '\n' 或空格, 不是用户新输入的字符! */
  • 解决: 在 %c 前加一个空格表示"跳过空白": scanf(" %c", &ch);

  • scanf 与 getchar 混用时的经典坑: scanf 结束后换行符还留在输入流里, 会被后面的 getchar() 当"第一个字符"吃掉

scanf("%d", &n);
getchar();              /* 先把遗留的换行符吃掉, 再读下面的字符 */

scanf 读字符串

char name[100];
scanf("%s", name);      /* 输入 Yang Yibiao↵ */
printf("hi, %s\n", name);  /* hi, Yang */
char line[100];
getchar();              /* 先吃掉上一行遗留的 \n */
scanf("%[^\n]", line);  /* 整行读入, 空格不停 */
printf("[%s]\n", line); /* [Hello World] */
  • 字符串 = char 数组: char s[100]; 开得足够大, 结尾是 '\0'

  • scanf("%s", s): 不加 &; 遇空白(空格/Tab/换行)就停 → 只读一个"单词"

  • 想读带空格的一整行: scanf("%[^\n]", s) —— 一直读到换行才停

  • 坑: 上一行 scanf 读完后缓冲区遗留的换行会让 %[^\n] 立刻空手而归 → 先 getchar() 吃掉

  • 输出: printf("%s", s); 单个字符用 %c

输入比数组长会溢出——为什么危险, 第 10 周讲指针时再说


格式字符串中的普通字符

  • 当在格式字符串中遇到一个或多个空白字符时, scanf 从输入中读取空白字符, 直到它遇到一个非空白字符(即*放回*)

  • 当它在格式字符串中遇到非空白字符时, scanf 将其与下一个输入字符进行比较

  • 如果匹配, 则 scanf 丢弃输入字符并继续处理格式字符串

  • 若不匹配, 则 scanf 会将有问题的字符放回输入中, 然后中止

例子:

  • 如果格式字符串是 "%d/%d" 并且输入是 •5/•96, scanf 成功

  • 如果输入 •5•/•96, 则 scanf 失败, 因为格式字符串中的 / 与输入中的空格不匹配

要在第一个数字后允许空格, 需改用格式字符串 "%d /%d"


printf与scanf混淆

scanf 和 printf 的调用看起来相似, 但两者之间存在显著差异

  • 一个常见的错误是在printf调用中将&放在变量前面:

printf("%d %d\n", &i, &j);  /*** WRONG ***/
  • 不要错误地假设 scanf 格式字符串应该类似于 printf:

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

若输入中逗号的位置实际是空格, 则scanf将终止而不读取j的值

  • 多数情况下, 一定不要在 scanf 格式化字符串的末尾放置换行符, 否则会导致交互式程序 挂起 直到用户输入下一个非空格字符


getchar与putchar

getchar: 读取单个字符

int ch;              /* 注意: 返回值是 int, 不是 char! */
ch = getchar();      /* 从键盘读取一个字符, 存入 ch */

为什么用 int? 因为 getchar 还可能返回 EOF, char 装不下

putchar: 输出单个字符

putchar(ch);      /* 在屏幕上显示字符 ch */
putchar('\n');    /* 输出换行符 */

getchar/putchar 读写字符的效率高于 scanf/printf, 在字符处理类程序(如统计输入字符个数)中常用


程序: 分数相加


#include <stdio.h>
 
int main(void)
{
  int num1, denom1, num2, denom2, result_num, result_denom;

  printf("Enter first fraction: ");
  scanf("%d/%d", &num1, &denom1);
 
  printf("Enter second fraction: ");
  scanf("%d/%d", &num2, &denom2);
 
  result_num = num1 * denom2 + num2 *denom1;
  result_denom = denom1 * denom2;
  printf("The sum is %d/%d\n",result_num, result_denom);

  return 0;
}

示例程序输出:

Enter first fraction: 5/6 Enter second fraction: 3/4 The sum is 38/24


课堂小测

  1. printf("%-8.3f", x) 中 -、8、.3 分别表示什么?
  2. 要打印出字符 " 和 \,转义序列分别怎么写?
  3. scanf("%d/%d", &a, &b) 要求用户怎么输入?
  4. getchar() 返回值的类型是什么?为什么它适合读取字符?