C Primer Plus 第六章 C控制语句:循环 阅读笔记

6.1 再探while循环

summing.c程序:根据用户从键盘输入的整数进行求和。程序利用了scanf()的返回值来结束循环。

#include <stdio.h>
int main(void)
{
	long num;
	long sum = 0L;		//把sum初始化为0
	int status;
	
	printf("Please enter an integer to be summed ");
	printf("(q to quit): ");
	status = scanf("%ld", &num);
	while(status == 1)	// == 的意思是 "等于"
	{
		sum = sum + num;
		printf("Please enter next integer (q to quit): ");
		status = scanf("%ld",&num);
	}
	printf("Those integers sum to %ld.\n", sum);
	
	return 0; 
 } 

运行结果:
在这里插入图片描述

6.1.1 程序注释

  要让程序正常运行,每次循环都要获取num的一个新值,并重置status。程序利用scanf()的两个不同的特性来完成。首先,使用scanf()读取num的一个新值;然后,检查scanf()的返回值判断是否成功获取值。
  scanf()返回成功读取项的数量。如果scanf()成功读取一个整数,就把该数存入num并返回1,随后返回值将被赋给status。这样做同时更新了num和status的值,while循环进入下一次迭代。如果用户输入的不是数字(如, q),scanf()会读取失败并返回0。此时,status的值就是0,循环结束。因为输入的字符q不是数字,所以它会被放回输入队列中(实际上,不仅仅是 q,任何非数值的数据都会导致循环终止,但是提示用户输入q退出程序比提示用户输入一个非数字字符要简单)。
  如果 scanf()在转换值之前出了问题(例如,检测到文件结尾或遇到硬件问题),会返回一个特殊值EOF(其值通常被定义为-1)。这个值也会引起循环终止。

  该程序的结构。总结如下:
    把sum初始化为0
    提示用户输入数据
    读取用户输入的数据
    当输入的数据为整数时,
    输入添加给sum,
    提示用户进行输入,
    然后读取下一个输入
    输入完成后,打印sum的值

  这叫作伪代码,是一种用简单的句子表示程序思路的方法,有助于设计程序的逻辑。确定程序的逻辑无误之后,再把伪代码翻译成实际的编程代码。使用伪代码的好处之一是,可以把注意力集中在程序的组织和逻辑上,不用在设计程序时还要分心如何用编程语言来表达自己的想法。
  总之,因为while循环是入口条件循环,程序在进入循环体之前必须获取输入的数据并检查status的值,所以在 while 前面要有一个 scanf()。要让循环继续执行,在循环内需要一个读取数据的语句,这样程序才能获取下一个status的值,所以在while循环末尾还要有一个scanf(),它为下一次迭代做好了准备。可以把下面的伪代码作为while循环的标准格式:

    获得第1个用于测试的值
    当测试为真时
    处理值
    获取下一个值

6.1.2 C风格读取循环

  根据伪代码的设计思路,summing.c程序,下面的代码:
    status = scanf("%ld", &num);
    while (status == 1)
    {
      /* 循环行为 */
      status = scanf("%ld", &num);
    }
  可以用这些代码替换:
    while (scanf("%ld", &num) == 1)
    {
      /循环行为/
    }
  第二种形式同时使用scanf()的两种不同的特性。首先,如果函数调用成功,scanf()会把一个值存入num。然后,利用scanf()的返回值(0或1,不是num的值)控制while循环。因为每次迭代都会判断循环的条件,所以每次迭代都要调用scanf()读取新的num值来做判断。换句话说,C的语法特性让你可以用下面的精简版本替换标准版本:

    当获取值和判断值都成功
      处理该值

6.2 while语句

while循环的通用形式如下:
while ( expression )
  statement
statement部分可以是以分号结尾的简单语句,也可以是用花括号括起来的复合语句。

6.2.1 终止while循环

  while循环有一点非常重要:在构建while循环时,必须让测试表达式的值有变化,表达式最终要为假。否则,循环就不会终止。

6.2.2 何时终止循环

  要明确一点:只有在对测试条件求值时,才决定是终止还是继续循环。

when.c程序:何时退出循环

#include <stdio.h>
int main(void)
{
	int n = 5;
	
	while (n < 7) 					// 第7行
	{
		printf("n = %d\n", n);
		n++;						// 第10行						
		printf("Now n = %d\n", n);  // 第11行
	}
	printf("The loop has finished.\n");
	
	return 0;
}

运行结果:
在这里插入图片描述
程序分析:
  在第2次循环时,变量n在第10行首次获得值7。但是,此时程序并未退出,它结束本次循环(第11行),并在对第7行的测试条件求值时才退出循环(变量n在第1次判断时为5,第2次判断时为6)。

6.2.3 while:入口条件循环

  while循环是使用入口条件的有条件循环。所谓“有条件”指的是语句部分的执行取决于测试表达式描述的条件,如(index < 5)。该表达式是一个入口条件(entry condition),因为必须满足条件才能进入循环体。

6.2.4 语法要点

  使用while时,要牢记一点:只有在测试条件后面的单独语句(简单语句或复合语句)才是循环部分。

while1.c程序:

/* while1.c -- 注意花括号的使用 */
/* 糟糕的代码创建了一个无限循环 */
#include <stdio.h>
int main(void)
{
	int n = 0;
	while (n < 3)
		printf("n is %d\n", n);
		n++;
	printf("That's all this program does\n");
	return 0;
}

运行结果:屏幕上会一直输出以上内容,除非强行关闭这个程序。
在这里插入图片描述
程序说明:
  只有直接跟在测试条件后面的一条语句是循环的一部分。变量n的值不会改变,条件n < 3一直为真。该循环会一直打印n is 0,除非强行关闭程序。这是一个无限循环的例子,没有外部干涉就不会退出。

  即使while语句本身使用复合语句,在语句构成上,它也是一条单独的语句。该语句从while开始执行,到第1个分号结束。在使用了复合语句的情况下,到右花括号结束。

while2.c 程序:注意分号的位置

#include <stdio.h>
int main(void)
{
	int n = 0;
	
	while (n++ < 3);			/* 第7行 */
		printf("n is %d\n", n); /* 第8![在这里插入图片描述](https://img-blog.csdnimg.cn/2020042822483464.png)
行 */
	printf("That's all this program does\n");
	
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  该程序中第7行的测试条件后面直接跟着一个分号,循环在此进入下一轮迭代,因为单独一个分号被视为一条语句。虽然n的值在每次循环时都递增1,但是第8行的语句不是循环的一部分,因此只会打印一次循环结束后的n值。
  在该例中,测试条件后面的单独分号是空语句,它什么也不做。有时,会故意使用带空语句的while语句,因为所有的任务都在测试条件中完成了,不需要在循环体中做什么。
  例如,假设你想跳过输入到第1个非空白字符或数字,可以这样写:
  while (scanf("%d", &num) == 1)
    ; /* 跳过整数输入 */
  只要scanf()读取一个整数,就会返回1,循环继续执行。

6.3 用关系运算符和表达式比较大小

在这里插入图片描述
  注意:比较浮点数时,尽量只使用<和>。因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等。例如,3乘以1/3的积是1.0。如果用把1/3表示成小数点后面6位数字,乘积则是.999999,不等于1。
  使用fabs()函数(声明在math.h头文件中)可以方便地比较浮点数,该函数返回一个浮点值的绝对值(即,没有代数符号的值)。

cmpflt.c程序:浮点数比较

#include <math.h>
#include <stdio.h>
int main(void)
{
	const double ANSWER = 3.14159;
	double response;
	
	printf("What is the value of pi?\n");
	scanf("%lf", &response);
	
	while (fabs(response - ANSWER) > 0.0001)
	{
		printf("Try again!\n");
		scanf("%lf", &response);
	}
	printf("Close enough!\n");
	
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  循环会一直提示用户继续输入,除非用户输入的值与正确值之间相差0.0001。

6.3.1 什么是真

  对C而言,表达式为真的值是1,表达式为假的值是0。

t_and_f.c程序:打印两个关系表达式的值,一个为真,一个为假

/* t_and_f.c -- C中的真和假的值 */
#include <stdio.h>
int main(void)
{
	int true_val, false_val;
	
	true_val = (10 > 2); 		// 关系为真的值
	false_val = (10 == 2);		// 关系为假的值
	printf("true = %d; false = %d \n", true_val, false_val);
	
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  把两个关系表达式的值分别赋给两个变量,即把表达式为真的值赋给true_val,表达式为假的值赋给false_val。

6.3.2 其他真值

truth.c程序:哪些值为真

#include <stdio.h>
int main(void)
{
	int	n = 3;
	
	while (n)
		printf("%2d is true\n", n--);
	printf("%2d is false\n", n);
	
	n = -3;
	while (n)
		printf("%2d is true\n", n++);
	printf("%2d is false\n", n);

	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  执行第1个循环时,n分别是3、2、1,当n等于0时,第1个循环结束;执行第2个循环时,n分别是-3、-2和-1,当n等于0时,第2个循环结束。一般而言,所有的非零值都视为真,只有0被视为假。

  许多C程序员都会很好地利用测试条件的这一特性。例如,用while(goats)替换while (goats !=0),因为表达式goats != 0和goats都只有在goats的值为0时才为0或假。

6.3.3 真值的问题

  C对真的概念约束太少会带来一些麻烦。

trouble.c程序:误用 = 会导致无限循环

#include <stdio.h>
int main(void)
{
	long num;
	long sum = 0L;
	int status;

	printf("Please enter an integer to be summed ");
	printf("(q to quit): ");
	status = scanf("%ld", &num);
	while (status = 1)
	{
		sum = sum + num;
		printf("Please enter next integer (q to quit): ");
		status = scanf("%ld", &num);
	}
	printf("Those integers sum to %ld.\n", sum);
	
	return 0;
}

运行程序:屏幕上会一直显示最后的提示内容,除非强行关闭程序。
在这里插入图片描述
程序说明:
  把status == 1替换成status = 1。后者是一个赋值表达式语句,所以 status 的值为 1。while (status = 1)实际上相当于while (1),也就是说,循环不会退出。虽然用户输入q,status被设置为0,但是循环的测试条件把status又重置为1,进入了下一次迭代。
  读者可能不太理解,程序的循环一直运行着,用户在输入q后完全没机会继续输入。如果scanf()读取指定形式的输入失败,就把无法读取的输入留在输入队列中,供下次读取。当scanf()把q作为整数读取时失败了,它把 q留下。在下次循环时,scanf()从上次读取失败的地方(q)开始读取,scanf()把q作为整数读取,又失败了。这是一个无限失败的循环。

  赋值运算符把一个值赋给它左侧的变量;而关系相等运算符检查它左侧和右侧的值是否相等,不会改变左侧变量的值(如果左侧是一个变量)。

  C语言不允许给常量赋值,编译器会把赋值运算符的这种用法作为语法错误标记出来。许多经验丰富的程序员在构建比较是否相等的表达式时,都习惯把常量放在左侧。
在这里插入图片描述

6.3.4 新的_Bool类型

  表示真或假的变量被称为布尔变量,所以_Bool是C语言中布尔变量的类型名。_Bool类型的变量只能储存1(真)或0(假)。如果把其他非零数值赋给_Bool类型的变量,该变量会被设置为1。这反映了C把所有的非零值都视为真。

boolean.c程序:使用_Bool类型的变量 variable

#include <stdio.h>
int main(void)
{
	long num;
	long sum = 0L;
	_Bool input_is_good;
	
	printf("Please enter an integer to be summed ");
	printf("(q to quit): ");
	input_is_good = (scanf("%ld", &num) == 1);
	while (input_is_good)
	{
		sum = sum + num;
		printf("Please enter next integer (q to quit): ");
		input_is_good = (scanf("%ld", &num) == 1);
	}
	printf("Those integers sum to %ld.\n", sum);
	
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  程序中把比较的结果赋值给_Bool类型的变量input_is_good:input_is_good = (scanf("%ld", &num) == 1);这样做没问题,因为==运算符返回的值不是1就是0。

  C99提供了stdbool.h头文件,该头文件让bool成为_Bool的别名,而且还把true和false分别定义为1和0的符号常量。

6.3.5 优先级和关系运算符

  关系运算符的优先级比算术运算符(包括+和-)低,比赋值运算符高。例如,x > y + 2和x > (y+ 2)相同,x = y > 2和x = (y > 2)相同。

  关系运算符之间有两种不同的优先级。
    高优先级组: < <= > >=
    低优先级组: == !=
在这里插入图片描述

6.4 不确定循环和计数循环

  不确定循环,指在测试表达式为假之前,预先不知道要执行多少次循环。
  计数循环,这类循环在执行循环之前就知道要重复执行多少次。

sweetie1.c程序:一个计数循环

#include <stdio.h>
int main(void)
{
	const int NUMBER = 22;
	int count = 1; 						// 初始化
					
	while (count <= NUMBER)     		// 测试
	{
		printf("Be my Valentine!\n"); 	// 行为
		count++;						// 更新计数               		
	}
	
	return 0;
}

运行结果:
在这里插入图片描述

程序说明:
  在创建一个重复执行固定次数的循环中涉及了3个行为:
    1.必须初始化计数器;
    2.计数器与有限的值作比较;
    3.每次循环时递增计数器。

6.5 for循环

  for循环把初始化、测试和更新组合在一处。

sweetie2.c程序:使用for循环的计数循环

#include <stdio.h>
int main(void)
{
	const int NUMBER = 22;
	int count;
	
	for (count = 1; count <= NUMBER; count++)
		printf("Be my Valentine!\n");
		
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  for后面的圆括号中有3个表达式,分别用两个分号隔开。第1个表达式是初始化,只会在for循环开始时执行- -次。第2个表达式是测试条件,在执行循环之前对表达式求值。如果表达式为假,循环结束。第3个表达式执行更新,在每次循环结束时求值。程序这个表达式递增count的值, 更新计数。完整的for语句还包括后面的简单语句或复合语句。
在这里插入图片描述
for_cube.c程序:使用for循环创建一个立方表

#include <stdio.h>
int main(void)
{
	int num;
	
	printf("    n  n cubed\n");
	for (num = 1; num <= 6; num++)
		printf("%5d %5d\n", num, num*num*num);
		
	return 0;
}

运行程序:
在这里插入图片描述
程序说明:打印整数1~6及其对应的立方

6.5.1 利用for的灵活性

  灵活性源于如何使用for循环中的3个表达式。以前面程序示例中的for循环为例,第1个表达式给计数器赋初值,第2个表达式表示计数器的范围,第3个表达式递增计数器。除此之外,for循环还有其他9种用法。

1.可以使用递减运算符来递减计数器:
for_down.c程序:

#include <stdio.h>
int main(void)
{
	int secs;
	for (secs = 5; secs > 0; secs--)
		printf("%d seconds!\n", secs);
	printf("We have ignition!\n");
	return 0;
}

运行结果:
在这里插入图片描述
2.可以让计数器递增x
for_13s.c 程序:

#include <stdio.h>
int main(void)
{
	int n; 	// 从2开始,每次递增13
	for (n = 2; n < 60; n = n + 13)
		printf("%d \n", n);
	return 0;
}

运行结果:
在这里插入图片描述
3.可以用字符代替数字计数
for_char.c 程序:

#include <stdio.h>
int main(void)
{
	char ch;
	
	for (ch = 'a'; ch <= 'z'; ch++)
		printf("The ASCII value for %c is %d.\n", ch, ch);
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:该程序能正常运行是因为字符在内部是以整数形式储存的,因此该循环实际上仍是用整数来计数。

4.除了测试迭代次数外,还可以测试其他条件。
  在for_cube程序中,可以把for (num= 1; num<= 6; num++)替换成:for (num= 1; numnumnum <= 216; num++)
  如果与控制循环次数相比,你更关心限制立方的大小,就可以使用这样的测试条件。

5.可以让递增的量几何增长,而不是算术增长。也就是说,每次都乘上而不是加上一个固定的量:
for_geo.c程序:

#include <stdio.h>
int main(void)
{
	double debt;
	for (debt = 100.0; debt < 150.0; debt = debt * 1.1)
		printf("Your debt is now $%.2f.\n", debt);
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:每次循环都把debt乘以1.1,即debt的值每次都增加10%

6.第3个表达式可以使用任意合法的表达式。无论是什么表达式,每次迭代都会更新该表达式的值。
for_wild.c程序:

#include <stdio.h>
int main(void)
{
	int x;
	int y = 55;
		for (x = 1; y <= 75; y = (++x * 5) + 50)
	printf("%10d %10d\n", x, y);
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:测试涉及y,而不是x。for循环中的3个表达式可以是不同的变量

7.可以省略一个或多个表达式(但是不能省略分号),只要在循环中包含能结束循环的语句即可。
for_none.c程序:

#include <stdio.h>
int main(void)
{
	int ans, n;
	ans = 2;
	for (n = 3; ans <= 25;)
		ans = ans * n;
	printf("n = %d; ans = %d.\n", n, ans);
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:该循环保持n的值为3。变量ans开始的值为2,然后递增到6和18,最终是54。

省略第2个表达式被视为真,所以下面的循环会一直运行:
for( ; ; )
  printf(“I want some action\n”);

8.第1个表达式不一定是给变量赋初值,也可以使用printf()。记住,在执行循环的其他部分之前,只对第1个表达式求值一次或执行一次。
for_show.c程序:

#include <stdio.h>
int main(void)
{
	int num = 0;
	
	for (printf("Keep entering numbers!\n"); num != 6;)
		scanf("%d", &num);
	printf("That's the one I want!\n");
	return 0;
}

运行结果:该程序打印第1行的句子一次,在用户输入6之前不断接受数字
在这里插入图片描述
9.循环体中的行为可以改变循环头的表达式
  for (n = 1; n < 10000; n = n + delta)
  如果程序经过几次迭代后发现delta太小或太大,循环中的if语句可以改变delta的大小。在交互式程序中,用户可以在循环运行时才改变 delta 的值。这样做也有危险的一面,例如,把delta设置为0就没用了。

6.6 其他赋值运算符:+=、-=、*=、/=、%=

  其他赋值运算符都用于更新变量,其用法都是左侧是一个变量名,右侧是一个表达式。例如:
  scores += 20 与 scores = scores + 20 相同
  dimes -= 2 与 dimes = dimes- 2 相同
  bunnies *= 2 与 bunnies = bunnies * 2 相同
  time /= 2.73 与 time = time / 2.73 相同
  reduce %= 3 与 reduce = reduce % 3 相同
  在这里插入图片描述

6.7 逗号运算符

  逗号运算符扩展了for循环的灵活性,以便在循环头中包含更多的表达式。
postage.c程序:打印一类邮件资费

#include <stdio.h>
int main(void)
{
	const int FIRST_OZ = 46; 	//2013邮资
    const int NEXT_OZ = 20; 	//2013邮资
	int ounces, cost;
	
	printf(" ounces cost\n");
	for (ounces = 1, cost = FIRST_OZ; ounces <= 4; ounces++, cost += NEXT_OZ)
		printf("%5d $%4.2f\n", ounces, cost / 100.0);
		
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:
  初始化表达式中的逗号使ounces和cost都进行了初始化,更新表达式中的逗号使每次迭代ounces递增1、cost递增20 (NEXT_ Z的值是20)。

逗号运算符有两个其他性质:
  首先,被逗号分隔的表达式从左往右求值。换言之,逗号是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生。
  其次,整个逗号表达式的值是石侧项的值。例如,下面语句x=(y=3, (z=++y+ 2) + 5);的效果是:先把3赋给y,递增y为4, 然后把4加2之和(6)赋给z,接着加上5,最后把结果11赋给x。

逗号运算符:把两个表达式连接成一个表达式,并保证最左边的表达式最先求值。逗号运算符通常在for循环头的表达式中用于包含更多的信息。整个逗号表达式的值是逗号右侧表达式的值。

6.7.1 当 Zeno 遇到 for 循环

  我们看看 for 循环和逗号运算符如何解决古老的悖论。希腊哲学家 Zeno 曾经提出箭永远不会达到它的目标。首先,他认为箭要到达目标距离的一半,然后再达到剩余距离的一半,然后继续到达剩余距离的一半,这样就无穷无尽。Zeno认为箭的飞行过程有无数个部分,所以要花费无数时间才能结束这一过程。
  我们采用一种定量的方法,假设箭用1秒钟走完一半的路程,然后用1/2秒走完剩余距离的一半,然后用1/4秒再走完剩余距离的一半,等等。可以用下面的无限序列来表示总时间:1 + 1/2 + 1/4 + 1/8 + 1/16 +…

zeno.c程序:求序列的和

#include <stdio.h>
int main(void)
{
	int t_ct;  		// 项计数
	double time, power_of_2;
	int limit;
	
	printf("Enter the number of terms you want: ");
	scanf("%d", &limit);
	for (time = 0, power_of_2 = 1, t_ct = 1; t_ct <= limit; t_ct++, power_of_2 *= 2.0)
	{
		time += 1.0 / power_of_2;
		printf("time = %f when terms = %d.\n", time, t_ct);
	}
	return 0;
}

运行结果:
在这里插入图片描述
程序说明:当项的数目接近无穷时,总和无限接近2.0。

6.8 出口条件循环:do while

  入口条件循环:while循环和for循环,即在循环的每次迭代之前检查测试条件,所以有可能根本不执行循环体中的内容。
  出口条件循环:do while循环,即在循环的每次迭代之后检查测试条件,这保证了至少执行循环体中的内容一次。

do_while.c程序:出口条件循环

#include <stdio.h>
int main(void)
{
	const int secret_code = 13;
	int code_entered;
	
	do{
		printf("To enter the triskaidekaphobia therapy club,\n");
		printf("please enter the secret code number: ");
		scanf("%d", &code_entered);
	}while(code_entered != secret_code);
	printf("Congratulationa! You are cured!\n");
	
	return 0;
 } 

运行结果:
在这里插入图片描述
entry.c程序:出口条件循环

#include <stdio.h>
int main(void)
{
	const int secret_code = 13;
	int code_entered;
	
	printf("To enter the triskaidekaphobia therapy club,\n");
	printf("please enter the secret code number: ");
	scanf("%d", &code_entered);
	
	while(code_entered != secret_code)
	{
		printf("To enter the triskaidekaphobia therapy club,\n");
		printf("please enter the secret code number: ");
		scanf("%d", &code_entered);
	}
	printf("Congratulationa! You are cured!\n");
	
	return 0;
 } 

运行结果:
在这里插入图片描述
do while循环的通用形式:

do
  statement
while ( expression );

statement可以是一条简单语句或复合语句。注意,do while循环以分号结尾

6.9 如何选择循环

  首先,确定是需要入口条件循环还是出口条件循环。通常,入口条件循环用得比较多,有几个原因。其一,一般原则是在执行循环之前测试条件比较好。其二,测试放在循环的开头,程序的可读性更高。当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更好。

6.10 嵌套循环

  嵌套循环指在一个循环内包含另一个循环。嵌套循环常用于按行和列显示数据,也就是说,一个循环处理一行中的所有列,另一个循环处理所有的行。

rows1.c程序:

#include <stdio.h>
#define ROWS 6
#define CHARS 10
int main(void)
{
	int row;
	char ch;
	
	for(row = 0; row < ROWS; row++)					/* 第10行 */
	{
		for(ch = 'A'; ch < ('A' + CHARS); ch++)	 	/* 第11行 */
			printf("%c", ch);
		printf("\n"); 
	} 
	
	return 0;
 } 

运行结果:
在这里插入图片描述

6.10.1 程序分析

  第10行为外层循环,第12行为内层循环。外层循环从row为0开始循环,到row为6时结束。因此,外层循环要执行6次,row的值从0变为5。每次迭代要执行的第1条语句是内层的for循环,该循环要执行10次,在同一行打印字符A~J。内层循环一行打印10个字符,外层循环创建6行。
  嵌套循环中的内层循环在每次外层循环迭代时都执行完所有的循环。

6.10.2 嵌套变式

  通过外层循环控制内层循环,在每次外层循环迭代时内层循环完成不同的任务。

rows2.c程序:内层循环开始打印的字符取决于外层循环的迭代次数

#include <stdio.h>
int main(void)
{
	const int ROWS = 6;
	const int CHARS = 6;
	int row;
	char ch;
	
	for(row = 0; row < ROWS; row++)
	{
		for(ch = ('A' + row); ch <('A' + CHARS); ch++)
			printf("%c",ch);
		printf("\n");
	}
	
	return 0;
 } 

运行程序:
在这里插入图片描述
程序说明:
  因为每次迭代都要把row的值与‘A’相加,所以ch在每一行都被初始化为不同的字符。然而,测试条件并没有改变,所以每行依然是以F结尾,这使得每一行打印的字符都比上一行少一个。

6.11 数组简介

  数组是按顺序储存的一系列类型相同的值。数组的元素被依次储存在内存中相邻的位置

  整个数组有一个数组名,通过整数下标访问数组中单独的项或元素。用于识别数组元素的数字被称为下标、索引或偏移量。下标必须是整数,而且要从0开始计数
  例如:float debts[20];
  声明debts是一个内含20个元素的数组,每个元素都可以储存float类型的值。数组的第1个元素是debts[0],第2个元素是debts[1],以此类推,直到debts[19]。注意,数组元素的编号从0开始,不是从1开始。可以给每个元素赋float类型的值。例如,debts[5] = 32.54;
在这里插入图片描述
  这里要注意一个潜在的陷阱:考虑到影响执行的速度,C 编译器不会检查数组的下标是否正确。下面的代码,都不正确:
  debts[20] = 88.32;  // 该数组元素不存在
  debts[33] = 828.12;  // 该数组元素不存在
  编译器不会查找这样的错误。当运行程序时,这会导致数据被放置在已被其他数据占用的地方,可能会破坏程序的结果甚至导致程序异常中断。

6.11.1 在for循环中使用数组

scores_in.c程序:读取10个高尔夫分数,打印总分、平均分、平均分与标准分的差值

#include <stdio.h>
#define SIZE 10
#define PAR 72
int main(void)
{
	int index, score[SIZE];
	int sum = 0;
	float average;
	
	printf("Enter %d golf scores:\n", SIZE);
	for(index = 0; index < SIZE; index++)
		scanf("%d", &score[index]); 		// 读取10个分数
	printf("The scores read in are as follows:\n");
	for(index = 0; index < SIZE; index++)
		printf("%5d", score[index]); 		// 验证输入
	printf("\n");
	for(index = 0; index < SIZE; index++)
		sum += score[index];				// 求总分数
	average = (float)sum/SIZE;				// 求平均分 
	printf("Sum of scores = %d, average = %.2f\n", sum, average);
	printf("That's a handicap of %.0f.\n", average - PAR);

	return 0;
 } 

运行结果:
在这里插入图片描述
程序说明:
  首先,注意程序示例虽然打印了11个数字,但是只读入了10个数字,因为循环只读了10个值。由于scanf()会跳过空白字符,所以可以在一行输入10个数字,也可以每行只输入一个数字,或者像本例这样混合使用空格和换行符隔开每个数字(因为输入是缓冲的,只有当用户键入Enter键后数字才会被发送给程序)。
  下面的代码可以很方便地处理一个大小为SIZE的数组:
  for (index = 0; index < SIZE; index++)
  设置正确的数组边界很重要。第1个元素的下标是0,因此循环开始时把index设置为0。因为从0开始编号,所以数组中最后一个元素的下标是SIZE -1。也就是说,第10个元素是score[9]。通过测试条件index < SIZE来控制循环中使用的最后一个index的值是SIZE - 1。

6.12 使用函数返回值的循环示例

该示例有3个主要任务:设计算法、在函数中表示算法并返回计算结果、提供一个测试函数的便利方法。
power.c程序:计算数的整数幂(math.h库提供了一个更强大幂函数pow(),可以使用浮点指数)

#include <stdio.h>
double power(double n, int p); // ANSI函数原型
int main(void)
{
	double x, xpow;
	int exp;
	
	printf("Enter a number and the positive integer power");
	printf(" to which\nthe number will be raised. Enter q");
	printf(" to quit.\n");
	while (scanf("%lf%d", &x, &exp) == 2)
	{
		xpow = power(x, exp); // 函数调用
		printf("%.3g to the power %d is %.5g\n", x, exp, xpow);
		printf("Enter next pair of numbers or q to quit.\n");
	}
	printf("Hope you enjoyed this power trip -- bye!\n");

	return 0;
}

double power(double n, int p) // 函数定义
{
	double pow = 1;
	int i;
	
	for (i = 1; i <= p; i++)
	pow *= n;
	
	return pow; // 返回pow的值
}

运行结果:
在这里插入图片描述

6.12.1 程序分析

  main()是一个驱动程序,即被设计用来测试函数的小程序。

  power()函数在程序中出现了3次:
  首次:double power(double n, int p);   // ANSI函数原型
  这是power()函数的原型,它声明程序将使用一个名为power()的函数。开头的关键字double表明power()函数返回一个double类型的值。编译器要知道power()函数返回值的类型,才能知道有多少字节的数据,以及如何解释它们。这就是为什么必须声明函数的原因。
  第2次出现是:xpow = power(x,exp);   // 函数调用
  程序调用power(),把两个值传递给它。该函数计算x的exp次幂,并把计算结果返回给主调函数。在主调函数中,返回值将被赋给变量xpow。
  第3次出现是:double power(double n, int p)   // 函数定义
  注意,函数定义的末尾没有分号,而函数原型的末尾有分号。

6.12.2 使用带返回值的函数

  声明函数、调用函数、定义函数、使用关键字return,都是定义和使用带返回值函数的基本要素。

  问:为什么在定义中说明了power()的返回类型为double,还要单独声明这个函数?
  答:编译器在程序中首次遇到power()时,需要知道power()的返回类型。此时,编译器尚未执行到power()的定义,并不知道函数定义中的返回类型是double。因此,必须通过前置声明预先说明函数的返回类型。前置声明告诉编译器,power()定义在别处,其返回类型为double。如果把power()函数的定义置于main()的文件顶部,就可以省略前置声明,因为编译器在执行到main()之前已经知道power()的所有信息。但是,这不是C的标准风格。因为main()通常只提供整个程序的框架,最好把 main()放在所有函数定义的前面。另外,通常把函数放在其他文件中,所以前置声明必不可少。

  问:既然在使用函数返回值之前要声明函数,那么为什么在使用scanf()的返回值之前没有声明scanf()?
  答:stdio.h 头文件中包含了scanf()、printf()和其他I/O函数的原型。scanf()函数的原型表明,它返回的类型是int。

6.13 关键概念

  1. 在创建循环时,要特别注意以下3个方面:
      注意循环的测试条件要能使循环结束;
      确保循环测试中的值在首次使用之前已初始化;
      确保循环在每次迭代都更新测试的值。
  2. 使用函数涉及3个步骤:
      通过函数原型声明函数;
      在程序中通过函数调用使用函数;
      定义函数。
  3. 函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代的编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所做的;实现部分描述了具体的行为,这正是函数定义所做的。
  4. C编译器不会检查数组下标值是否有效。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值