Note
本章节仅介绍部分printf和scanf的用法, 详细内容见"Chapter22-输入和输出"
3.1 printf函数
printf()函数用来显示格式串(format string)的内容, 并且在转换说明(conversion specification)处插入参数值.
特性 | 说明 | 代码 |
---|---|---|
printf不检查参数数量 | 参数缺失的情况: 未定义行为, 可能随机打印一个数据, 可能打印栈中数据 参数过多的情况: 未定义行为, 可能忽略多余参数, 可能崩溃等 | printf("%d %d\n", i); printf("%d\n", i, j); |
printf不检查参数类型 | printf会继续根据参数变量的二进制数据进行解析, 打印没有意义的数据 | printf("%f\n", i); |
其中转换说明包括:
- 常量
- 变量
- 表达式
printf(格式串, 表达式1, 表达式2, ...);
实例如下:
#include <stdio.h>
int main(void) {
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);
return 0;
}
输出如下:
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/" && gcc test.c -o test && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/"test
i = 10, j = 20, x = 43.289200, y = 5527.000000
3.1.1 转换说明表
仅介绍常用的转换说明信息
数据类型 | 格式说明符 | 描述 | 变形 |
---|---|---|---|
整数类型 | 1. 对齐长度: %5d , 长度不够左侧补空格2. 对齐精度: %5.3d , 长度不够左侧补零3. 右侧对齐: %-5d , 长度不够右侧补空格 | ||
int | %d | 十进制有符号整数 | |
%i | 十进制有符号整数(与 %d 类似)用于 printf : 与%d 没有区别用于 scanf : 可以识别八进制(前缀为0 ), 十六进制(前缀为0x 或者0X ) | ||
unsigned int | %u | 十进制无符号整数 | |
short | %hd | 十进制有符号短整数 | |
unsigned short | %hu | 十进制无符号短整数 | |
long | %ld | 十进制有符号长整数 | |
unsigned long | %lu | 十进制无符号长整数 | |
long long | %lld | 十进制有符号长长整数 | |
unsigned long long | %llu | 十进制无符号长长整数 | |
int 或 unsigned int | %o | 八进制整数 | |
%x | 十六进制无符号整数(小写字母) | ||
%X | 十六进制无符号整数(大写字母) | ||
%p | 指针地址 | ||
浮点类型 | 1. 对齐长度: 转换说明前添加数字 2. 精度: 小数点 + 数字 = %.2f | ||
float 或 double | %f | 小数点形式的浮点数 | |
%e | 科学计数法(小写 e) (注意: 不能使用%ef ) | ||
%E | 科学计数法(大写 E) | ||
%g | 根据数值自动选择 %f 或 %e | ||
%G | 根据数值自动选择 %f 或 %E | ||
long double | %Lf | 小数点形式的长双精度浮点数 | |
%Le | 科学计数法的长双精度浮点数 | ||
%Lg | 根据数值自动选择 %Lf 或 %Le | ||
字符类型 | |||
char | %c | 单个字符 | |
char[] | %s | 字符串,遇到空字符 \0 结束 | |
特殊类型 | |||
void* | %p | 指针地址 | |
size_t | %zu | 无符号整数(通常为对象大小) | |
ptrdiff_t | %td | 有符号整数(通常为指针差值) | |
intmax_t | %jd | 最大的有符号整数 | |
uintmax_t | %ju | 最大的无符号整数 |
测试代码
#include <stdio.h>
int main(void) {
int i;
float x;
i = 40;
x = 839.21f;
printf("|%d|%5d|%-5d|%5.3d|%-5.3d|\n", i, i, i, i, i);
printf("|%10.3f|%10.3e|%-10g|\n", x, x, x);
return 0;
}
输出:
|40| 40|40 | 040|040 |
| 839.210| 8.392e+02|839.21 |
3.1.2 转义序列
转义序列 | 描述 | Unicode/ASCII 值 |
---|---|---|
%% | % | |
\\ | 反斜杠符号 | U+005C |
\' | 单引号 | U+0027 |
\" | 双引号 | U+0022 |
\? | 问号 | U+003F |
\a | 警报(响铃) | ASCII 7 |
\b | 退格 | ASCII 8 |
\f | 换页 | ASCII 12 |
\n | 换行 | ASCII 10 |
\r | 回车 | ASCII 13 |
\t | 水平制表符 | ASCII 9 |
\v | 垂直制表符 | ASCII 11 |
\ooo | 八进制数表示的字符 | 例如:\101 表示 A |
\xhh | 十六进制数表示的字符 | 例如:\x41 表示 A |
\0 | 空字符(NUL) | ASCII 0 |
3.2 scanf函数
类似于printf()
, scanf()
格式串也包含: 普通字符和转换说明两个部分
特性 | 说明 | 代码 |
---|---|---|
传入变量地址而非变量本身内容 | scanf向内存写入数据, 需要的是变量地址而非变量本身 | |
紧密压缩的格式串 | 常用的格式串, 但是输入的时候需要加上空格 | %d%d%f%f |
scanf错误处理 | 处理顺序: 从左到右 错误处理: 1. 如果前半部分的输入正确, scanf会保留并写入变量, 后续从第一个非法字符开始将保留至缓冲区 2. 如果遇到错误输入, 立即返回, 不对后续参数处理 3. scanf返回值传回正确接收的参数个数 | |
scanf忽略空白符 | 输入时中间存在空白仅用于分割输入, 多余的空白scanf会忽略 | |
scanf解析过程 | 1. 整数解析: a. 开头解析 + 或者- (数字后不能出现)b. 解析数字 2. 浮点数解析: a. 开头解析 + 或者- (数字后不能出现)b. 解析数字 c. 解析 . 小数点d. 解析小数 | |
scanf不读取最后的换行符 | 该换行符将是下一次scanf读取的第一个字符 影响情况: 下一次scanf是 %c 时会读取空白符解决方法: 使用 getchar(); 手动清空换行符. | |
格式串的普通字符 | 1. 空白字符: 空白字符的数量无关紧要, scanf会忽略多余的空白符 2. 其他字符: 遇到非空白符时, 会将其输入进行比较 若匹配: scanf放弃输入字符, 并继续处理格式串 不匹配: scanf保留字符在缓冲区中, 然后异常退出 | 格式字符串: %d/%d 输入一: • 5/ • 96 , 跳过空白, 5 匹配, / 匹配, 后续空白跳过输入二: • 5 • / • 96 , 跳过空白, 5 匹配, 格式字符串强制要求匹配/ , 此时匹配的是空白符, 异常退出 |
scanf格式串末尾不要带\n | \n 的行为: 等价于一个空白符, 它会使scanf忽略空白符, 同时忽略了\r 回车符, 导致scanf会继续等待输入 | scanf("%d\n", &num); |
实例-分数相加
代码
#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 fracion: ");
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 fracion: 3/4
The sum is 38/24
练习题
答案链接:
- http://knking.com/books/c2/answers/c3.html
- https://github.com/williamgherman/c-solutions/tree/master/03
记号说明:
- 红色字体: 表明错误的答案或者遗漏的知识点
Practice1
- 下面的printf 函数调用产生的输出分别是什么?
(a) printf("%6d,%4d", 86, 1040);
(b) printf("%12.5e", 30.253);
(c) printf("%.4f", 83.162);
(d) printf("%-6.2g", .0000009979);
回答:
注意: •表示空格
••••86,1040 (正确)
••••30.25300 (错误)
•3.02530e+01 (答案)
83.1620 (正确)
1e-06• (正确)
Practice2
编写printf 函数调用,以下列格式显示float 型变量x:
- 指数表示形式,字段宽度8,左对齐,小数点后保留1位数字。
- 指数表示形式,字段宽度10,右对齐,小数点后保留6位数字。
- 定点十进制表示形式,字段宽度8,左对齐,小数点后保留3位数字。
- 定点十进制表示形式,字段宽度6,右对齐,小数点后无数字。
回答:
%-8.1e
%10.6e
%-8.3f
%6.0f
Practice3
说明下列每对scanf 格式串是否等价?如果不等价,请指出它们的差异。
- "%d" 与" %d" 。
- "%d-%d-%d" 与"%d -%d -%d" 。
- "%f" 与"%f " 。
- "%f,%f" 与"%f, %f" 。
回答:
- 等价: 开头的空白符会被scanf忽略
- 等价: "转换说明"后续的空白符会被scanf忽略
- 不等价: 后续多出的空格符会导致
\r
回车符忽略, 导致程序即使读取了一个浮点数, 仍会卡在scanf - 等价: ,可以正确匹配, 空白符忽略
Practice4 *
假设scanf函数调用的格式如下:
scanf("%d%f%d", &i, &x, &j);
如果用户录入
10.3 5 6
调用执行后,变量i、x和j的值分别是多少?(假设变量i和变量j都是int型,而变量x是float型。)
回答:
- i = 10
- x = 未定义值
- 订正: x = 0.3 (见书中关于scanf解析浮点数的过程)
- j = 未定义值
- 订正: j = 5 (继续读取后续的输入)
Practice5 *
假设scanf 函数调用的格式如下:
scanf("%f%d%f", &x, &i, &y);
如果用户录入
12.3 45.6 789
调用执行后,变量x、i和y的值分别是多少?(假设变量x和变量y都是float型,而变量i是int型。)
回答:
- x = 12.3
- i = 45
- y = 未定义值
- 订正: y = 0.6
Practice6
指出如何修改3.2节中的addfrac.c 程序,使用户可以输入在字符/的前后都有空格的分数。
#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 fracion: ");
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;
}
代码题
code1-time
编写一个程序,以月/日/年(即mm/dd/yyyy
)的格式接受用户录入的日期信息,并以年月日(即yyyymmdd
)的格式将其显示出来:
Enter a date (mm/dd/yyyy): 2/17/2011
You entered the date 20110217
代码如下
#include <stdio.h>
int main(void) {
int y, m, d;
printf("Enter a date(mm/dd/yy): ");
scanf("%d/%d/%d", &m, &d, &y);
printf("You entered the date %d%2.2d%2.2d\n", y, m, d);
return 0;
}
编译并运行
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter3/practice6-addfrac
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code1-time/" && gcc time.c -o time && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code1-time/"time
Enter a date(mm/dd/yy): 2/17/2011
You entered the date 20110217
code2-product
编写一个程序,对用户录入的产品信息进行格式化。程序会话应类似下面这样: 其中,产品编号和日期项采用左对齐方式,单位价格采用右对齐方式,允许最大取值为9999.99的美元。 提示:各个列使用制表符控制。
Enter item number: 583
Enter unit price: 13.5
Enter purchase date (mm/dd/yyyy): 10/24/2010
Item Unit Purchase
Price Date
583 $ 13.50 10/24/2010
代码如下
#include <stdio.h>
int main(void) {
int number, y, m, d;
float price;
printf("Enter item number: ");
scanf("%d", &number);
printf("Enter unit price: ");
scanf("%f", &price);
printf("Enter purchase date (mm/dd/yyyy): ");
scanf("%d/%d/%d", &m, &d, &y);
printf("Item\t\t\tUnit\t\t\tPurchase\n");
printf(" \t\t\tPrice\t\t\tDate\n");
printf("%-4d\t\t\t$%6.2f\t\t\t%2d/%2d/%4d\n", number, price, m, d, y);
}
编译并运行
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter3/code2-product
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code2-product/" && gcc product.c -o product && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code2
-product/"product
Enter item number: 583
Enter unit price: 13.5
Enter purchase date (mm/dd/yyyy): 10/10/2020
Item Unit Purchase
Price Date
583 $ 13.50 10/10/2020
code3-book
图书用国际标准书号(ISBN)进行标识。2007年1月1日之后分配的ISBN包含13位数字(旧的ISBN使用10位数字),分为5组,如978-0-393-97950-3。
- 第一组(GS1前缀)目前为978或979。
- 第二组(组标识)指明语言或者原出版国(如0和1用于讲英语的国家)。
- 第三组(出版商编号)表示出版商(393是W. W. Norton出版社的编号)。
- 第四组(产品编号)是由出版商分配的用于识别具体哪一本书的(97950)。
- ISBN的末尾是一个校验数字,用于验证前面数字的准确性。 编写一个程序来分解用户录入的ISBN信息:
注意:每组中数字的个数是可变的,不能认为每组的长度都与示例一样。用实际的ISBN值(通常放在书的封底和版权页上)测试你编写的程序。
Enter ISBN: 978-0-393-97950-3
GS1 prefix: 978
Group identifier: 0
Publisher code: 393
Item number: 97950
Check digit: 3
代码如下
#include <stdio.h>
int main(void) {
int prefix, ident, code, num, check;
printf("Enter ISBN: ");
scanf("%d-%d-%d-%d-%d", &prefix, &ident, &code, &num, &check);
printf("GS1 prefix: %d\n", prefix);
printf("Group identifier: %d\n", ident);
printf("Publisher code: %d\n", code);
printf("Item number: %d\n", num);
printf("Check digit: %d\n", check);
return 0;
}
编译并运行
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter3/code3-book
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code3-book/" && gcc book.c -o book && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter3/code3-book/"bo
ok
Enter ISBN: 978-0-393-97950-3
GS1 prefix: 978
Group identifier: 0
Publisher code: 393
Item number: 97950
Check digit: 3
code4-phone
编写一个程序,提示用户以(xxx) xxx-xxxx的格式输入电话号码,并以xxx.xxx.xxxx的格式显示该号码:
Enter phone number [(xxx) xxx-xxxx]: (404) 817-6900
You entered 404.817.6900
代码如下
#include <stdio.h>
int main(void) {
int area, pre, num;
printf("Enter phone number [(xxx) xxx-xxxx]: ");
scanf("(%d) %d-%d", &area, &pre, &num);
printf("You entered %d.%d.%d\n", area, pre, num);
return 0;
}
编译并运行
Enter phone number [(xxx) xxx-xxxx]: (404) 817-6900
You entered 404.817.6900
code5-matrix
编写一个程序,要求用户(按任意次序)输入从1到16的所有整数,然后用4×4矩阵的形式将它们显示出来,再计算出每行、每列和每条对角线上的和:
如果行、列和对角线上的和都一样(如本例所示),则称这些数组成一个幻方 (magic square)。这里给出的幻方出现于艺术家和数学家Albrecht Dürer在1514年的一幅画中。(注意,矩阵的最后一行中间的两个数给出了该画的创作年代。)
Enter the numbers from 1 to 16 in any order:
16 3 2 13 5 10 11 8 9 6 7 12 4 15 14 1
16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1
Row sums: 34 34 34 34
Column sums: 34 34 34 34
Diagonal sums: 34 34
代码如下
#include <stdio.h>
int main(void) {
int matrix[16];
printf("Enter the numbers from 1 to 16 in any order:\n");
for (int i = 0; i < 16; i++) {
scanf("%d", &matrix[i]);
}
printf("\n");
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
printf("%d\t", matrix[i * 4 + j]);
}
printf("\n");
}
printf("\n");
int row[4];
int column[4];
int diagonal[2];
for (int i = 0; i < 4; i++) {
row[i] = matrix[i * 4 + 0] + matrix[i * 4 + 1] + matrix[i * 4 + 2] + matrix[i * 4 + 3];
column[i] = matrix[i] + matrix[i + 4] + matrix[i + 8] + matrix[i + 12];
}
diagonal[0] = matrix[0] + matrix[5] + matrix[10] + matrix[15];
diagonal[1] = matrix[3] + matrix[6] + matrix[9] + matrix[12];
printf("Row sums: %d %d %d %d\n", row[0], row[1], row[2], row[3]);
printf("Column sums: %d %d %d %d\n", column[0], column[1], column[2], column[3]);
printf("Diagonal sums: %d %d\n", diagonal[0], diagonal[1]);
return 0;
}
编译并运行
Enter the numbers from 1 to 16 in any order:
16 3 2 13 5 10 11 8 9 6 7 12 4 15 14 1
16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1
Row sums: 34 34 34 34
Column sums: 34 34 34 34
Diagonal sums: 34 34
code6-addfrac2
修改3.2节的addfrac.c 程序,使用户可以同时输入两个分数,中间用加号隔开:
Enter two fractions separated by a plus sign: 5/6+3/4
The sum is 38/24
代码如下
#include <stdio.h>
int main(void) {
int m1, s1, m2, s2, rm, rs;
printf("Enter two fractions separated by a plus sign: ");
scanf("%d/%d+%d/%d", &s1, &m1, &s2, &m2);
rs = s1 * m2 + s2 * m1;
rm = m1 * m2;
printf("\nThe sum is %d/%d\n", rs, rm);
return 0;
}
编译并运行
Enter two fractions separated by a plus sign: 5/6+3/4
The sum is 38/24
TODO
- 为什么float和double的格式化输出相同
- 因为在printf中float类型的参数会被自动提升为double (称为默认参数提升), 这是C标准规定的