第 4 章 语句
# 第 4 章 语句
在本章中,你将会发现C实现了其他现代高级语言所具有的所有语句
- 它们中的绝大多数都是按照你所预期的方式工作的
if
语句用于在几段备选代码中选择运行其中的一段- 而
while
、for
和do
语句则用于实现不同类型的循环
但是,和其他语言相比,C的语句还是存在一些不同之处
- C并不具备专门的赋值语句,而是统一用“表达式语句”代替
switch
语句实现了其他语言中case
语句的功能,但其实现的方式却非比寻常
书本内容
# 空语句
书本内容
# 表达式语句
书本内容
# 代码块
书本内容
# if
语句
书本内容
# while
语句
书本内容
# break
和 continue
语句
书本内容
# while
语句的执行过程
书本内容
# for
语句
书本内容
# do
语句
书本内容
# switch
语句
书本内容
# switch
中的 break
语句
书本内容
# default
子句
书本内容
# switch
语句的执行过程
书本内容
# goto
语句
书本内容
# 总结
书本内容
# 问题
#
问题 1
下面的表达式是否合法?如果合法,它执行了什么任务?
3 * x * x - 4 * x + 6;
答案
- 这条语句是合法的
- 但是并没有执行任何任务
- 语句中的操作符并没有任何负作用
- 而且表达式语句的结果并没有赋值给任何值
#
问题 2
赋值语句的语法是怎样的?
答案
- C 语言中并没有赋值语句, C 语言的赋值是借助赋值操作符,在表达式语句中进行的,比如
x = y + 3;
#
问题 3
用下面这种方法使用代码块是否合法?如果合法,你是否曾经想这样使用?
...
statement
{
statement
statement
}
statement
2
3
4
5
6
7
答案
- 这种方式使用代码块是合法的
- 适用于引入仅在代码内有效的局部变量
#
问题 4
当你编写 if
语句时,如果在 then
子句(相当于紧跟着 if
的语句)中没有语句,但在 else
子句中有语句,你该如何编写?你还能改用其他形式来达到同样的目的吗
答案
可以使用空语句
if (condition) ; else statement
1
2
3
4也可以对条件取反,将
else
中的子句移到if
后面if (!(condition)) statement
1
2
#
问题 5
下面的循环将产生什么样的输出?
int i;
...
for( i = 0; i < 10; i += 1 )
printf( "%d\n", i);
2
3
4
答案
将会从 0
打印至 9
#
问题 6
什么时候使用 while
语句比使用 for
语句更加合适?
答案
当没有初始条化部分或者调整部分时,用 while
语句可能更合适
#
问题 7
下面的代码片段用于把标准输入复制到标准输出,并计算字符的检验和(checksum),它有什么错误吗?
while( (ch = getchar()) != EOF )
checksum += ch;
putchar( ch );
printf( "Checksum = %d\n", checksum );
2
3
4
答案
- 没有语法错误
- 但是无法达到预期的效果
- 因为
putchar(ch)
并不在while
循环中 - 因此程序只会在最后输出
EOF
,但EOF
在大多数系统中均不是一个合法的字符
- 因为
#
问题 8
什么时候使用do语句比使用while语句更加合适?
答案
- 当在条件错误时,循环体至少也需要执行一次情况下
#
问题 9
下面的代码片段将产生什么样的输出?注意:位于左操作数和右操作数之间的 %
操作符用于产生两者相除的余数。
for( i = 1; i <= 4; i += 1 ){
switch( i % 2 ){
case 0:
printf( "even\n" );
case 1:
printf( "odd\n" );
}
}
2
3
4
5
6
7
8
答案
- 由于
case 0
后的语句中不包含break
,所以对于偶数,会打印出两条语句 - 最终结果如下
odd
even
odd
odd
even
odd
2
3
4
5
6
#
问题 10
编写一些语句,从标准输入读取一个整型值,然后打印一些空白行,空白行的数量由这个值指定
答案
int n_lines;
int count;
scanf("%d", &n_lines);
for(count = 0; count < n_lines; count++)
{
putchar('\n');
}
2
3
4
5
6
7
8
#
问题 11
写一些语句,用于对一些已经读入的值进行检验和报告。如果x小于y,打印单词WRONG。同样,如果a大于或等于b,也打印WRONG。在其他情况下,打印RIGHT。注意:||操作符表示逻辑或,你可能要用到它。
答案
if(x < y || a >= b)
printf("WRONG\n");
else
printf("RIGHT\n");
2
3
4
#
问题 12
能够被4整除的年份是闰年,但其中能够被100整除的却不是闰年,除非它同时能够被400整除。请编写一些语句,判断year这个年份是否为闰年,如果它是闰年,把变量leap_year设置为1,如果不是,把leap_year设置为0。
答案
if (year % 400 == 0 || (year % 4 == 0 && year % 100))
leap_year = 1;
else
leap_year = 0;
2
3
4
#
问题 13
新闻记者都受过训练,善于提问谁?什么?何时?何地?为什么?请编写一些语句,如果变量which_word的值是1,就打印who;如果值为2,打印what,依次类推。如果变量的值不在1到5的范围之内,就打印don’t know。
答案
switch (which_word)
{
case 1:
printf("who");
break;
case 2:
printf("what");
break;
case 3:
printf("when");
break;
case 4:
printf("where");
break;
case 5:
printf("why");
break;
default:
printf("don't know");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
问题 14
假定由一个“程序”来控制你,而且这个程序包含两个函数:eat_hamburger()用于让你吃汉堡包,hungry()函数根据你是否饥饿返回真值或假值。请编写一些语句,允许你在饥饿感得到满足之前爱吃多少汉堡包就吃多少。
答案
while (hungry())
eat_hamburger();
2
#
问题 15
修改你对问题14的答案,使它能够让你的祖母满意——就是你已经吃过一些东西了。也就是说,你至少必须吃一个汉堡包。
答案
do
eat_hamburger();
while(hungry());
2
3
#
问题 16
编写一些语句,根据变量precipitating和temperature的值打印当前天气的简单总结。
答案
if (precipitating)
if (temperature < 32)
printf("snowing");
else
printf("raining");
else
if (temperature < 60)
printf("cold");
else
printf("warm");
2
3
4
5
6
7
8
9
10
# 编程练习
#
编程练习 1
数n的平方根可以通过计算一系列近似值来获得,每个近似值都比前一个更加接近准确值。第一个近似值是1,接下来的近似值则通过下面的公式来获得。
编写一个程序,读入一个值,计算并打印出它的平方根。如果你将所有的近似值都打印出来,你会发现这种方法获得准确结果的速度有多快。原则上,这种计算可以永远进行下去,它会不断产生更加精确的结果。但在实际中,由于浮点变量的精度限制,程序无法一直计算下去。当某个近似值与前一个近似值相等时,你就可以让程序停止继续计算了。
答案
- 代码
/*
** Compute the square root of a number.
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
float number;
float new_guess;
float pre_guess;
/*
** Prompt for and read the data, then check it.
*/
printf("Please Enter a positive number: ");
scanf("%f", &number);
if (number <= 0)
{
printf("Wrong input, can not compute the square root of a non-positive number.");
return EXIT_FAILURE;
}
/*
** Compute approximations to the square root
** until they don't change any more.
*/
new_guess = 1;
printf("%.6g\n", new_guess);
do
{
pre_guess = new_guess;
new_guess = (pre_guess + number / pre_guess) / 2;
printf("%.6g\n", new_guess);
} while (new_guess != pre_guess);
printf("The square root of %g is %.6g\n", number, new_guess);
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- 输出
Please Enter a positive number: 2
1
1.5
1.41667
1.41422
1.41421
1.41421
The square root of 2 is 1.41421
2
3
4
5
6
7
8
Please Enter a positive number: 3
1
2
1.75
1.73214
1.73205
1.73205
The square root of 3 is 1.73205
2
3
4
5
6
7
8
提示
float
的有效字位数有限, 一般为 6 ~ 7 位- 也就是说前 6, 7 位有效数字相同的十进制数,用
float
表示的结果可能是相同的 - 所以可以用来判断
和 是否相等的方法来结束程序的运行 - 使用
%.6g
表示float
,以保留 6 位有效数字
- 也就是说前 6, 7 位有效数字相同的十进制数,用
#
编程练习 2
一个整数如果只能被它本身和1整除,它就被称为质数(prime)。请编写一个程序,打印出1~100之间的所有质数。
答案
- 代码
/*
** Compute and print all the prime numbers from 1 to 100
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int number;
int divisor;
/*
** One and two are prime
*/
printf("1\n2\n");
/*
** No other even number are prime; look at the remaining odd ones;
*/
for (number = 3; number <= 100; number += 2)
{
/*
** If a number i can't be divides by the number below ⌊ i /2 ⌋ , then it's a prime
** An odd can not be divided by an even
*/
for (divisor = 3; divisor <= number / 2; divisor += 2)
if (number % divisor == 0)
break;
if (divisor > number / 2)
printf("%d\n", number);
}
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- 输出
1
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#
编程练习 3
等边三角形的三条边长度都相等,但等腰三角形只有两条边的长度是相等的。如果三角形的三条边长度都不等,那就称为不等边三角形。请编写一个程序,提示用户输入三个数,分别表示三角形三条边的长度,然后由程序判断它是什么类型的三角形。提示:除了边的长度是否相等之外,程序是否还应考虑一些其他的东西?
答案
- 代码
/*
** Classify the type of a triangle given the lengths of its sides.
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
float a, b, c;
float temp;
/*
** Prompt for and read data
*/
printf("Enter the lengths of the three sides of the triangle: ");
scanf("%f %f %f", &a, &b, &c);
/*
** Rearrange the values so that a >= b >= c
*/
if (a < b)
{
temp = a;
a = b;
b = temp;
}
if (a < c)
{
temp = a;
a = c;
c = temp;
}
if (b < c)
{
temp = b;
b = c;
c = temp;
}
/*
** Justify the type of the triangle
*/
if (c <= 0 || b + c <= a)
printf("Not a triangle\n");
else if (a == b && b == c)
printf("The triangle is an equilateral triangle.\n");
else if (a == b || b == c)
printf("The triangle is an isosceles triangle.\n");
else
printf("The triangle is a scalene triangle.\n");
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
需要对用户输入进行校验
- 确保输入均为正数
- 确保三角形的任意两边条的和大于第三条边
- 为了减少比较的次数,可先将输入的边排序
#
编程练习 4
编写函数 copy_n
,它的原型如下所示:
void copy_n( char dst[], char src[], int n );
这个函数用于把一个字符串从数组 src
复制到数组 dst
,但有如下要求:必须正好复制 n
个字符到 dst
数组中,不能多,也不能少。如果 src
字符串的长度小于 n
,你必须在复制后的字符串尾部补充足够的 NUL
字符,使它的长度正好为 n
。如果 src
的长度长于或等于 n
,那么你在 dst
中存储了 n
个字符后便可停止。此时,数组 dst
将不是以 NUL
字符结尾。注意调用 copy_n
时,它应该在 dst[0]
至 dst[n-1]
的空间中存储一些东西,但也只局限于那些位置,这与 src
的长度无关。
如果你计划使用库函数 strncpy
来实现你的程序,祝贺你提前学到了这个知识。但在这里,我的目的是让你自己规划程序的逻辑,所以你最好不要使用那些处理字符串的库函数。
答案
- 代码
/*
** copy n characters form src to dst(if necessary , fill with nul)
*/
#define NUL '\0'
void copy_n(char dst[], char src[], int n)
{
for (int dst_index = 0, src_index = 0; dst_index < n; dst_index++)
{
dst[dst_index] = src[src_index];
if (src[src_index] != NUL)
src_index += 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
采用两个指针,可以简化语句
#
编程练习 5
编写一个程序,从标准输入一行一行地读取文本,并完成如下任务:如果文件中有两行或更多行相邻的文本内容相同,那么就打印出其中一行,其余的行不打印。你可以假设文件中的文本行在长度上不会超过128个字符(127个字符加上用于终结文本行的换行符)。
答案
- 代码
/*
** Print one line from each set of duplicate lines in the standard input.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
#define LINE_SIZE 129
int main(void)
{
char new_line[LINE_SIZE], last_line[LINE_SIZE];
int print_from_group;
print_from_group = FALSE;
if (gets(last_line) != NULL)
while (gets(new_line) != NULL)
{
if (strcmp(new_line, last_line) == 0)
{
if (!print_from_group)
{
puts(new_line);
print_from_group = TRUE;
}
}
else
{
strcpy(last_line, new_line);
print_from_group = FALSE;
}
}
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- 输入
This is the first line.
Another line.
And another.
And another.
And another.
And another.
Still more.
Almost done now.
Almost done now.
Another line.
Still more.
Finished!
2
3
4
5
6
7
8
9
10
11
12
- 输出
And another.
Almost done now.
2
提示
- 换行符
\n
不是\0
,因此字符串数组的长度应设为129
- 先读入
last_line
在逻辑上更加严谨,能避免字符串数组默认初始值的影响
#
编程练习 6
请编写一个函数,它从一个字符串中提取一个子字符串。函数的原型应该如下:
int substr(char dst[], char src[], int start, int len);
函数的任务是从 src
数组起始位置向后偏移 start
个字符的位置开始,最多复制 len
个非 NUL
字符到 dst
数组。在复制完毕之后, dst
数组必须以 NUL
字节结尾。
答案
- 代码
#define NUL '\0'
int substr(char dst[], char src[], int start, int len)
{
int dst_index;
int src_index;
dst_index = 0;
/*
** Advance src_index to right spot to begin, but stop if reach the terminating NUL byte.
*/
for (src_index = 0; src_index < start && src[src_index] != NUL; src_index++)
;
/*
** Copy the desired number of characters, but stop at the NUL.
*/
while (src[src_index] != NUL && len > 0)
{
dst[dst_index++] = src[src_index++];
len--;
}
dst[dst_index] = '\0';
return dst_index;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
提示
- 需要确保,
src
的前start
个字符中不含有NUL
#
编程练习 7
编写一个函数,从一个字符串中去除多余的空白(white space)。函数的原型应该如下:
void deblank( char string[] );
当函数发现字符串中如果有一个地方由一个或多个连续的空白组成,就把它们改成单个空格字符(one space character)。注意当你遍历整个字符串时要确保它以NUL字符结尾
答案
- 代码
#define NUL '\0'
int is_white(int ch)
{
return ch == ' ' || ch == '\t' || ch == '\v' || ch == '\n' || ch == '\r' || ch == '\f';
}
void deblank(char string[])
{
char *src;
char *dest;
int ch;
src = string;
dest = string;
while ((ch = *src++) != NUL)
if (is_white(ch))
{
if (dest == string || !is_white(dest[-1]))
*dest++ = ' ';
}
else
*dest++ = ch;
*dest = NUL;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
提示
- 采用两个字符串指针,一个用于遍历
src
,一个用于改变当前的字符串dest
,- 由地要替换多余的空白,所以
dest
增加的速度不会超过src
- 由地要替换多余的空白,所以
- 空白字符包括
<blank>
,\t
,\v
,\r
,\n
,f