复习题
1.再次运行程序清单 4.1
但是在要求输入名时,请输入名和姓(根据英文书写习惯,名和姓中间有一个空格),看看会发生什么情况?为什么?
//程序清单4.1 talkback.c -- 演示与用户交互
#include <stdio.h>
#include <string.h> // 提供strlen()函数的原型
#define DENSITY 62.4 // 人体密度(单位:磅/立方英尺)
int main()
{
float weight, volume;
int size, letters;
char name[40]; // name是一个可容纳40个字符的数组
printf("Hi! What's your first name?\n");
scanf("%s", name);
printf("%s, what's your weight in pounds?\n", name);
scanf("%f", &weight);
size = sizeof name;
letters = strlen(name);
volume = weight / DENSITY;
printf("Well, %s, your volume is %2.2f cubic feet.\n", name, volume);
printf("Also, your first name has %d letters,\n", letters);
printf("and we have %d bytes to store it.\n", size);
return 0;
}
答:
程序虽然能运行,但是结果是错误的。第1个 scanf()语句只读取用户输入的名,而用户输入的姓仍留在输入缓冲区中(缓冲区是用于存储输入的临时存储区)。下一条scanf()语句在输入缓冲区査找重量时,从上次读入结束的地方开始读取。这样就把留在缓冲区的姓作为体重来读取,导致scanf()读取失败(在centos7 gcc编译器,能读取到数值,但是不正确)。另一方面,如果在要求输入姓名时输Lasha144, 那么程序会把144作为用户的体重(虽然用户是在程序提示输入体重之前输入了144)。
运行结果:
Hi! What's your first name?
xiao zheng
xiao, what's your weight in pounds?
Well, xiao, your volume is -35287870734336.00 cubic feet.//转换为很奇怪的值
Also, your first name has 4 letters,
and we have 40 bytes to store it.
2.假设下列示例都是完整程序中的一部分,它们打印的结果分别是什么?
a.printf(“He sold the painting for $%2.2f.\n”, 2.345e2);
b.printf(“%c%c%c\n”, ‘H’, 105, ‘\41’);
c.#define Q “His Hamlet was funny without being vulgar.”
printf(“%s\nhas %dcharacters.\n”, Q, strlen(Q));
d.printf(“Is %2.2e the same as %2.2f?\n”, 1201.0, 1201.0);
答:主要考查printf函数的转换说明
a.He sold the painting for $234.50.
b.Hi!(转义序列\数字,即使没有前缀0也会被识别为八进制,\41和\041是一样的)
c.His Hamlet was funny without being vulgar.
has 42 characters.
d.Is 1.20e+03 the same as 1201.00?
3.在第2题的c中,要输出包含双引号的字符串Q,应如何修改?
答:在这条语句中使用\”:printf(“\”%s\”\nhas %dcharacters.\n”, Q, strlen(Q));
4.找出下面程序中的错误。
define B booboo
define X 10
main(int)
{
int age;
char name;
printf("Please enter your first name.");
scanf("%s", name);
printf("All right, %c, what's your age?\n", name);
scanf("%f", age);
xp = age + X;
printf("That's a %s! You must be at least %d.\n", B, xp);
rerun 0;
}
答:正确的代码如下:
#include <stdio.h>
#define B "booboo"
#define X 10
int main(void)
{
int age;
char name[40];
int xp;
printf("Please enter your first name.\n");
scanf("%s", name);
printf("All right, %s, what's your age?\n", name);
scanf("%d", &age);
xp = age + X;
printf("That's a %s! You must be at least %d.\n", B, xp);
return 0;
}
5.假设一个程序的开头是这样:
#define BOOK "War and Peace"
int main(void)
{
float cost =12.99;
float percent = 80.0;
请构造一个使用BOOK、cost和percent的printf()语句,打印以下内容:
This copy of "War and Peace" sells for $12.99.
That is 80% of list.
答:程序如下:
#include <stdio.h>
#define BOOK "War and Peace"
int main(void)
{
float cost =12.99;
float percent = 80.0;
printf("This copy of \"%s\" sells for $%.2f.\n", BOOK, cost);
printf("That is %.0f%% of list.\n", percent);
return 0;
}
6.打印下列各项内容要分别使用什么转换说明?
a.一个字段宽度与位数相同的十进制整数
b.一个形如8A、字段宽度为4的十六进制整数
c.一个形如232.346、字段宽度为10的浮点数
d.一个形如2.33e+002、字段宽度为12的浮点数
e.一个字段宽度为30、左对齐的字符串
答:
a.%d
b.%4X
c.%10.3f
d.%12.2e
e.%-30s
7.打印下面各项内容要分别使用什么转换说明?
a.字段宽度为15的unsigned long类型的整数
b.一个形如0x8a、字段宽度为4的十六进制整数
c.一个形如2.33E+02、字段宽度为12、左对齐的浮点数
d.一个形如+232.346、字段宽度为10的浮点数
e.一个字段宽度为8的字符串的前8个字符
答:
a.%15lu
b.%#4x
c.%-12.2E
d.%+10.3f
e.%8.8s
8.打印下面各项内容要分别使用什么转换说明?
a.一个字段宽度为6、最少有4位数字的十进制整数
b.一个在参数列表中给定字段宽度的八进制整数
c.一个字段宽度为2的字符
d.一个形如+3.13、字段宽度等于数字中字符数的浮点数
e.一个字段宽度为7、左对齐字符串中的前5个字符
答:
a.%6.4d
b.%*o
c.%2c
d.%+0.2f
e.%-7.5s
9.分别写出读取下列各输入行的scanf()语句,并声明语句中用到变量和数组。
a.101
b.22.32 8.34E−09
c.linguini
d.catch 22
e.catch 22 (但是跳过catch)
答:源码如下
#include <stdio.h>
int main(void)
{
//a
int num;
scanf("%d", &num);
//b (注意:对于本题的输入,可以使用转换字符e、f和g。另外,除了%之外,在%和转换字符之间加空格不会影响最终的结果)
float kgs, share;
scanf("%f%f", &kgs, &share);
//c
char action[20];
scanf("%s", action);
//d
char action1[20];
int value;
scanf("%s %d", action1, value);
//e
int value1;
scanf("%*s%d", &value1);
return 0;
}
10.什么是空白?
答:空白包括空格、制表符和换行符。C语言使用空白分隔记号。scanf()使用空白分隔连续的输入项
11.下面的语句有什么问题?如何修正?
printf("The double type is %z bytes..\n", sizeof(double));
答:%z中的z是修饰符,不是转换字符,所以要在修饰符后面加修饰的转换字符。可以使用%zd打印十进制数,或用不同的说明符打印不同进制的数,例如,号%zx打印十六进制的数。printf("The double type is %zd bytes..\n", sizeof(double));
12.假设要在程序中用圆括号代替花括号,以下方法是否可行?
#define ( {
#define ) }
答:可以分别把 ( 和 ) 替换成 { 和 } 。但是预处理器无法区分哪些圆括号应替换成花括号,哪些圆括号不能替换成花括号。因此
#define ( {
#define ) }
int main(void)
(
printf("Hello, O Great One!\n");
)
//将变成:
int main{void}
{
printf{"Hello, O Great One!\n"};
}
编程练习
1.编写一个程序,提示用户输入名和姓,然后以“名,姓”的格式打印出来。
源码如下:
#include <stdio.h>
int main(void)
{
char fname[40];
char lname[40];
printf("Enter your first name: ");
scanf("%s", fname);
printf("Enter your last name: ");
scanf("%s", lname);
printf("%s, %s\n", lname, fname);
return 0;
}
2.编写一个程序,提示用户输入名和姓,并执行一下操作:
a.打印名和姓,包括双引号;
b.在宽度为20的字段右端打印名和姓,包括双引号;
c.在宽度为20的字段左端打印名和姓,包括双引号;
d.在比姓名宽度宽3的字段中打印名和姓。
#include <stdio.h>
#include <string.h>
int main(void)
{
char first_name[40];
char second_name[40];
printf("请输入您的姓:\n");
scanf("%s", first_name);
printf("请输入您的名:\n");
scanf("%s", second_name);
//a.打印名和姓,包括双引号;
printf("您的姓名是:\"%s%s\"\n", first_name, second_name);
//b.在宽度为20的字段右端打印名和姓,包括双引号;
printf("您的姓名是:\"%20s%20s\"\n", first_name, second_name);
//c.在宽度为20的字段左端打印名和姓,包括双引号;
printf("您的姓名是:\"%-20s%-20s\"\n", first_name, second_name);
//d.在比姓名宽度宽3的字段中打印名和姓。
printf("您的姓名是:\"%*s%*s\"\n", strlen(first_name) + 3, first_name, strlen(second_name) + 3, second_name);
return 0;
}
运行结果如下:
请输入您的姓:
胡
请输入您的名:
歌
您的姓名是:"胡歌"
您的姓名是:" 胡 歌"
您的姓名是:"胡 歌 "
您的姓名是:" 胡 歌"
3.读取浮点数并以不同形式打印该浮点数
编写一个程序,读取一个浮点数,首先以小数点记数法打印,然后以指数记数法打印。用下面的格式进行输出(系统不同,指数记数法显示的位数可能不同):
a.输入21.3或2.1e+001;
b.输入+21.290或2.129E+001;
源码如下
#include <stdio.h>
int main(void)
{
float f1;
float f2;
printf("请输入第一个浮点数:\n");
scanf("%f", &f1);
printf("以小数点记数法打印:%0.1f\n", f1);
printf("以指数记数法打印:%0.1e\n");
printf("请输入第二个浮点数:\n");
scanf("%f", &f2);
printf("以小数点记数法打印:%+.3f\n", f2);
printf("以指数记数法打印:%+.3E\n");
return 0;
}
运行结果如下:
请输入第一个浮点数:
21.3
以小数点记数法打印:21.3
以指数记数法打印:2.1e+01
请输入第二个浮点数:
21.29
以小数点记数法打印:+21.290
以指数记数法打印:+2.129E+01
4.关于身高英寸和英尺之间的换算的程序
编写一个程序,提示用户输入身高(单位:英寸)和姓名,然后以下面的格式显示用户刚输入的信息:
Dabney, you are 6.208 feet tall
使用float类型,并用/作为除号。如果你愿意,可以要求用户以厘米为单位输入身高,并以米为单位显示出来。
源码如下:
#include <stdio.h>
int main(void)
{
float height;
char name[40];
printf("Enter your height in inches: ");
scanf("%f", &height);
printf("Enter your name: ");
scanf("%s", name);
printf("%s, you are %.3f feet tall\n", name, height / 12.0);
return 0;
}
(1)C语言中,只能对相同数据类型的数据进行运算,类型如果不同会自动转换
(2)运算的结果与操作数的数据类型一致,int / int = int,也就是说,1 / 2 = 0
5.编写一个程序计算下载时间
提示用户输入以兆位每秒(Mb/s)为单位的下载速度和以兆字节(MB)为单位的文件大小。程序中应计算文件的下载时间。注意,这里1字节等于8位。使用float类型,并用/作为除号。该程序要以下面的格式打印 3 个变量的值(下载速度、文件大小和下载时间),显示小数点后面两位数字:
At 18.12 megabits per second, a file of 2.20 megabytes
downloads in 0.97 seconds.
源码如下:
#include <stdio.h>
int main(void)
{
//下载速度
float download_speed;
//文件大小
float file_size;
//下载时间
float download_time;
printf("请您输入您网络的下载速度(Mb/s):\n");
scanf("%f", &download_speed);
printf("请您输入您要下载的文件的大小(MB):\n");
scanf("%f", &file_size);
download_time = file_size * 8 / download_speed;
printf("At %.2f megabits per second, a file of %.2f megabytes downloads in %.2f seconds.\n",\
download_speed, file_size, download_time);
printf("以每秒%.2fMb的下载速度,下载%.2fMB大小的文件,需要%.2f秒\n", \
download_speed, file_size, download_time);
return 0;
}
运行结果如下:
请您输入您网络的下载速度(Mb/s):
18.12
请您输入您要下载的文件的大小(MB):
2.2
At 18.12 megabits per second, a file of 2.20 megabytes downloads in 0.97 seconds.
以每秒18.12Mb的下载速度,下载2.20MB大小的文件,需要0.97秒
6.按照格式打印姓名及姓名的字母数
编写一个程序,先提示用户输入名,然后提示用户输入姓。在一行打印用户输入的名和姓,下一行分别打印名和姓的字母数。字母数要与相应名和姓的结尾对齐,如下所示:
Melissa Honeybee
7 8
接下来,再打印相同的信息,但是字母个数与相应名和姓的开头对齐,如下所示:
Melissa Honeybee
7 8
源代码如下:
#include <string.h>
#include <stdio.h>
int main(void)
{
char first_name[40];
int first_name_length = 0;
char second_name[40];
int second_name_length = 0;
printf("请输入您的姓:\n");
scanf("%s", first_name);
printf("请输入您的名:\n");
scanf("%s", second_name);
first_name_length = strlen(first_name);
second_name_length = strlen(second_name);
printf("%s %s\n", first_name, second_name);
printf("%*d %*d\n", first_name_length, first_name_length, second_name_length, second_name_length);
printf("%s %s\n", first_name, second_name);
printf("%-*d %-*d\n", first_name_length, first_name_length, second_name_length, second_name_length);
return 0;
}
运行结果如下:
请输入您的姓:
Melissa
请输入您的名:
Honeybee
Melissa Honeybee
7 8
Melissa Honeybee
7 8
7.很奇怪的一个练习题,奇怪在没有明白到底想考察啥?
编写一个程序,将一个double类型的变量设置为1.0/3.0,一个float类型的变量设置为1.0/3.0。分别显示两次计算的结果各3次:一次显示小数点后面6位数字;一次显示小数点后面12位数字;一次显示小数点后面16位数字。程序中要包含float.h头文件,并显示FLT_DIG和DBL_DIG的值。1.0/3.0的值与这些值一致吗?
源代码如下:
/* Programming Exercise 4-7 */
#include <stdio.h>
#include <float.h>
int main(void)
{
float ot_f = 1.0 / 3.0;
double ot_d = 1.0 / 3.0;
printf(" float values: ");
printf("%.4f %.12f %.16f\n", ot_f, ot_f, ot_f);
printf("double values: ");
printf("%.4f %.12f %.16f\n", ot_d, ot_d, ot_d);
printf("FLT_DIG: %d\n", FLT_DIG);
printf("DBL_DIG: %d\n", DBL_DIG);
return 0;
}
运行结果如下:
float values: 0.3333 0.333333343267 0.3333333432674408
double values: 0.3333 0.333333333333 0.3333333333333333
FLT_DIG: 6
DBL_DIG: 15
8.计算汽车的油耗
编写一个程序,提示用户输入旅行的里程和消耗的汽油量。然后计算并显示消耗每加仑汽油行驶的英里数,显示小数点后面一位数字。接下来,使用1加仑大约3.785升,1英里大约为1.609千米,把单位是英里/加仑的值转换为升/100公里(欧洲通用的燃料消耗表示法),并显示结果,显示小数点后面 1 位数字。注意,美国采用的方案测量消耗单位燃料的行程(值越大越好),而欧洲则采用单位距离消耗的燃料测量方案(值越低越好)。使用#define 创建符号常量或使用 const 限定符创建变量来表示两个转换系数。
源代码如下:
#include <stdio.h>
#define L_PER_GALLON 3.785
#define KM_PER_MILE 1.609
int main(void)
{
//需要旅行的距离
float need_mile;
//所消耗的油量
float oil;
//每加仑汽油行驶的英里数
float mile_per_gallon;
printf("请输入您的旅行距离(英里):\n");
scanf("%f", &need_mile);
printf("请输入您所耗的汽油量(加仑):\n");
scanf("%f", &oil);
mile_per_gallon = need_mile / oil;
printf("您的汽车,每加仑汽油能够行驶%.1f英里。\n", mile_per_gallon);
printf("您的汽车,百公里油耗是:%.1f。\n", (oil * L_PER_GALLON) / (need_mile * KM_PER_MILE) * 100);
return 0;
}
运行结果如下:
请输入您的旅行距离(英里):
100
请输入您所耗的汽油量(加仑):
2
您的汽车,每加仑汽油能够行驶50.0英里。
您的汽车,百公里油耗是:4.7。
总结
1.转换说明及其打印的输出结果
这些转换说明应用在很多地方,最常用的是printf()和scanf(两个函数)
转换说明 | 输出 |
---|---|
%a | 浮点数、十六进制数和p记数法(C99/C11) |
%A | 浮点数、十六进制数和p记数法(C99/C11,大写) |
%c | 单个字符 |
%d | 有符号的十进制整数 |
%e | 浮点数,e记数法 |
%E | 浮点数,e记数法(大写) |
%f | 浮点数,十进制记数法 |
%g | 根椐值的不同,自动选择%f或%e,%e格式用于指数小于-4或者大于或等于精度时 |
%G | 根椐值的不同,自动选择%f或%e,%e格式用于指数小于-4或者大于或等于精度时(大写) |
%i | 有符号的十进制整数(与%d相同) |
%o | 无符号八进制 |
%p | 指针 |
%s | 字符串 |
%u | 无符号十进制 |
%x | 无符号十六进制,使用十六进制数0f |
%X | 无符号十六进制,使用十六进制数0F |
%% | 打印一个百分号 |
2.printf()的修饰符和标记
标记 | 含义 |
---|---|
- | 待打印项左对齐。即,从字段的左侧开始打印该项。示例:”%-20s” |
+ | 有符号值若为正,则在值前面显示加号;若为负,则在值前面显示减号。示例:”%+6.2f”,可以理解为,+表示要显式打印正负,不能单纯的把+理解为打印正数。 |
空格 | 有符号值若为正,则在值前而显示前导空格(不显示任何符号);若为负,则在值前面显示减号。+标记覆盖一个空格。示例:”%+6.2f” |
# | 把结果转换为另一种形式。如果是%o格式,则以0开始;如果是%x或%X格式,则以0x或0X开始对于所有的浮点格式,保证了即使后面没有任何数字,也打印一个小数点字符。对于%g和%G格式,#防止结果后面的0被删除。示例:”%#o”、”%8.0f”、”%+#10.3e” |
0 | 对于数值格式,用前导0代替空格填充字段宽度。对于整数格式,如果出现-标记(左对齐,如果左对齐补零,从显示上来看数值就变了,所以忽略)或指定精度,则忽略0该标记 |
修饰符 | 含义 |
---|---|
标记 | 上表描述了5种标记(- + 空格 #和0),可以不使用标记或使用多个标记 |
数字 | 最小字段宽度 如果该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段 |
.数字 | 精度 对于%e、%E和%f转换,表示小数点右边数字的位数 对于%g和%G转換,表示有效数字最大位数 对于%s转换,表示待打印字符的最大数量 对于整型转换,表示待打印数字的最小位数 如有必要,使用前导0来达到这个位数 只使用.表示其后跟随一个0,所以号 %.f和 %.0f相同 示例:”%5.2”打印一个浮点数,字段宽度为5字符,其中小数点后有两位数字 |
h | 和整型转換说明一起使用,表示short int或 unsigned short int类型的值 示例:”%hu”、”%hx”、”%6.4hd” |
hh | 和整型转換说明一起使用,表示short char或 unsigned char类型的值 示例:”%hu”、”%hx”、”%6.4hd” |
j | 和整型转换说明一起使用,表示intmax_t或 uintmax_t类型的值。这些类型定义在 stdint.h中 示例:”%jd”、”%jx” |
l | 和整型转换说明一起使用,表示long int或 unsigned long int类型的值 示例: “%ld”、”%lu” |
ll | 和整型转换说明一起使用,表示long long int或 unsigned long long int类型的值(C99) 示例: “%lld”、”%llu” |
L | 和浮点转换说明一起使用,表示long double类型的值 示例:”%Le” |
t | 和整型转换说明一起使用,表示 ptrdiff_t类型的值。ptrdiff_t是两个指针差值的类型(C99) 示例:”%td”、”%12ti” |
z | 和整型转换说明一起使用,表示 size_t类型的值。 size_t是sizeof返回的类型(C99) 示例:”%zd”、”%12zd” |
3.输入函数scanf()和输出函数printf()总结
①从scanf()角度看输入
接下来,我们更详细地研究scanf()怎样读取输入。假设scanf()根据一个%d转换说明读取一个整数。scanf()函数每次读取一个字符,跳过所有的空白字符,直至遇到第1个非空白字符才开始读取。因为要读取整数,所以scanf()希望发现一个数字字符或者一个符号(+或-)。
如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。scanf()不断地读取和保存字符,直至遇到非数字字符。
如果遇到一个非数字字符,它便认为读到了整数的末尾。然后,scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。最后,scanf()计算已读取数字(可能还有符号)相应的数值,并将计算后的值放入指定的变量中。
如果使用字段宽度,scanf()会在字段结尾或第1个空白字符处停止读取(满足两个条件之一便停止)。
如果第1个非空白字符是A而不是数字,会发生什么情况?scanf()将停在那里,并把A放回输入中,不会把值赋给指定变量。程序在下一次读取输入时,首先读到的字符是A。如果程序只使用%d转换说明, scanf()就一直无法越过A读下一个字符。
另外,如果使用带多个转换说明的scanf(),C规定在第1个出错处停止读取输入。
用其他数值匹配的转换说明读取输入和用%d 的情况相同。区别在于scanf()会把更多字符识别成数字的一部分。例如,%x转换说明要求scanf()识别十六进制数a~f和A~F。浮点转换说明要求scanf()识别小数点、e记数法(指数记数法)和新增的p记数法(十六进制指数记数法)。
如果使用%s 转换说明,scanf()会读取除空白以外的所有字符。scanf()跳过空白开始读取第 1 个非空白字符,并保存非空白字符直到再次遇到空白。这意味着 scanf()根据%s 转换说明读取一个单词,即不包含空白字符的字符串。
如果使用字段宽度,scanf()在字段末尾或第1个空白字符处停止读取。无法利用字段宽度让只有一个%s的scanf()读取多个单词。最后要注意一点:当scanf()把字符串放进指定数组中时,它会在字符序列的末尾加上’\0’,让数组中的内容成为一个C字符串。
实际上,在C语言中scanf()并不是最常用的输入函数。这里重点介绍它是因为它能读取不同类型的数据。C 语言还有其他的输入函数,如 getchar()和 fgets()。这两个函数更适合处理一些特殊情况,如读取单个字符或包含空格的字符串。目前,无论程序中需要读取整数、小数、字符还是字符串,都可以使用scanf()函数。
②格式字符串中的普通字符
scanf()函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。例如,假设在两个转换说明中添加一个逗号:scanf("%d,%d", &n, &m);
scanf()函数将其解释成:用户将输入一个数字、一个逗号,然后再输入一个数字。也就是说,用户必须像下面这样进行输入两个整数:88,121
由于格式字符串中,%d后面紧跟逗号,所以必须在输入88后再输入一个逗号。但是,由于scanf()会跳过整数前面的空白,所以下面两种输入方式都可以:88, 121
和88,
121
格式字符串中的空白意味着跳过下一个输入项前面的所有空白。例如,对于下面的语句:scanf("%d ,%d", &n, &m);
以下的输入格式都没问题:88,121
88 ,121
88 , 121
请注意,“所有空白”的概念包括没有空格的特殊情况。除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。因此,scanf(“%d%d”, &n, &m)与scanf(“%d %d”, &n, &m)的行为相同。对于%c,在格式字符串中添加一个空格字符会有所不同。例如,如果把%c放在格式字符串中的空格前面,scanf()便会跳过空格,从第1个非空白字符开
始读取。也就是说,scanf(“%c”, &ch)从输入中的第1个字符开始读取,而scanf(“ %c”, &ch)则从第1个非空白字符开始读取。
③scanf()的返回值
scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测到“文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用#define指令把EOF定义为-1)。