C++PrimerPlus 第五章 循环和关系表达式 - 5.2 while循环

本文详细介绍了C++中的while循环,包括其与for循环的关系,以及如何使用while循环进行延时。通过示例程序清单5.13展示了while循环遍历字符串并打印ASCII码的过程,强调了循环设计的原则。同时,文章指出for循环和while循环在本质上是等价的,选择使用哪种取决于编程风格。此外,还探讨了如何利用clock()函数创建延时循环,避免了因处理器速度变化带来的问题。
摘要由CSDN通过智能技术生成

C++PrimerPlus 第五章 循环和关系表达式 - 5.2 while循环

5.2 while循环

while循环是没有初始化和更新部分的for循环,它只有测试条件和循环体:

while (test-condition)
body

首先,程序计算圆括号内的测试条件(test-condition)表达式。如果该表达式为true,则执行循环体中的语句。与for循环一样,循环体也由一条语句或两个花括号定义的语句块组成。执行完循环体后,程序返回测试条件,对它进行重新评估。如果该条件为非零,则再次执行循环体。测试和执行将一直进行下去,直到测试条件为false位置(参见下图)。显然,如果希望循环最终能够结束,循环体中的代码必须完成某种影响测试条件表达式的操作。例如,循环可以将测试条件中使用的变量加1或从键盘输入读取一个新值。和for循环一样,while循环也是一种入口条件循环。因此,如果测试条件一开始便为false,则程序将不会执行循环体。

在这里插入图片描述
程序清单5.13使用了一个while循环。该循环遍历字符串,并显示其中的字符及其ASCII码。循环在遇到空值字符时停止。这种逐字符遍历字符串直到遇到空值字符的技术是C++处理C-风格字符串的标准方法。由于字符串中包含了结尾标记,因此程序通常不需要知道字符串的长度。

程序清单5.13 while.cpp

//while.cpp -- introducing the while loop
#include<iostream>
const int ArSize = 20;
int main()
{
	using namespace std;
	char name[ArSize];
	cout << "Your first name, please: ";
	cin >> name;
	cout << "Here is your name, verticalized and ASCIIized:\n";
	int i = 0;						//start at beginning of string
	while (name[i] != '\0')			//process to end of string
	{
		cout << name[i] << ": " << int(name[i]) << endl;
		i++;						//don't forget this step
	}
	return 0;
}

下面是该程序的运行情况:
Your first name, please: Muffy
Here is your name, verticalized and ASCIIized:
M: 77
u: 117
f: 102
f: 102
y: 121

verticalized和ASCIIized并不是真正的单词,甚至将来也不会是单词。不过它们确实在输出中添加了一种“可爱”的氛围。

程序说明

程序清单5.13中的while条件像这样:

while (name[i] != ‘\0’)

它可以测试数组中特定的字符是不是空值字符。为使该测试最终能够成功,循环体必须修改i的值,这是通过在循环体结尾将i加1来实现的。省略这一步将导致循环停留在同一个数组元素上,打印该字符及其编码,直到强行终止该程序。导致死循环是循环最常见的问题之一。通常,在循环体中忘记更新某个值时,便会出现这种情况。

可以这样修改while行:

while (name[i])

经过这种修改后,程序的工作方式将不变。这是由于name[i]是常规字符,其值为该字符的编码——非零值或true。然而,当name[i]为空值字符时,其编码将为0或false。这种表示法更为简洁(也更常用),但没有程序清单5.13中的表示法清晰。对于后一种情况,“笨拙”的编译器生成的代码的速度将更快,“聪明”的编译器对于这两个版本生成的代码将相同。

要打印字符的ASCII码,必须通过强制类型转换将name[i]转换为整型。这样,cout将把值打印成整数,而不是将它解释为字符编码。

不同于C-风格字符串,string对象不使用空字符来标记字符串末尾,因此要将程序清单5.13转换为使用string类的版本,只需用string对象替换char数组即可。第16章将讨论可用于标识string对象中最后一个字符的技术。

5.2.1 for与while

在C++中,for和while循环本质上是相同的。例如,下面的for循环:

for (init-expression; test-expression; update-expression)
{
	statement(s)
}

可以改写成这样:

init-expression
while (test-expression)
{
	statement(s)
	update-expression;
}

同样,下面的while循环:

while (test-expression)
	body

可以改写成这样:

for ( ; test-expression; )
	body

for循环需要3个表达式(从技术的角度说,它需要1条后面跟两个表达式的语句),不过它们可以是空表达式(语句),只有两个分号是必需的。另外,省略for循环中的测试表达式时,测试结果将为true,因此下面的循环将一直运行下去:

for(	;	;	)
	body

由于for循环和while循环几乎是等效的,因此究竟使用哪一个只是风格上的问题。它们之间存在三个差别。首先,在for循环中省略了测试条件时,将认为条件为true;其次,在for循环中,可使用初始化语句声明一个局部变量,但在while循环中不能这样做;最后,如果循环体中包括continue语句,情况将稍有不同,continue语句将在第6章讨论。通常,程序员使用for循环来为循环计数,因为for循环格式允许将所有相关的信息——初始值、终止值和更新计数器的方法——放在同一个地方。在无法预先知道循环将执行的次数时,程序员常使用while循环。

提示:

在设计循环时,请记住下面几条指导原则。

  1. 指定循环终止的条件。
  2. 在首次测试之前初始化条件。
  3. 在条件被再次测试之前更新条件。

for循环的一个优点是,其结构提供了一个可实现上述3条指导原则的地方,因此有助于程序员记住应该这样做。但这些指导原则也适用于while循环。

错误的标点符号

for循环和while循环都由用括号括起来的表达式和后面的循环体(包含一条语句)组成。前面讲过,这条语句可以是语句块,其中包含多条语句。记住,语句块是由花括号,而不是由缩进定义的。例如,请看下面的循环:

i = 0;
while (name[i] != '\0')
	cout << name[i] << endl;
	i++;
cout << "Done\n";

缩进表明,该程序的作者希望i++;语句是循环体的组成部分。然而,由于没有花括号,因此编译器认为循环体仅由最前面的cout语句组成。因此,该循环将不断地打印数组的第一个字符。该程序不会执行i++;语句,因为它在循环的外面。

下面的例子说明了另一个潜在的缺陷:

i = 0;
while (name[i] != '\0');		//problem semicolon
{
	cout << name[i] << endl;
	i++;
}
cout << "Done\n";

这一次,代码正确地使用了花括号,但还插入了一个分号。记住,分号结束语句,因此该分号将结束while循环。换句话说,循环体为空语句,也就是说,分号后面没有任何内容。这样,花括号中所有的代码现在位于循环的后面,永远不会被执行。该循环不执行任何操作,是一个死循环。请注意这种分号。

5.2.2 等待一段时间:编写延时循环

有时候,让程序等待一段时间很有用。例如,读者可能遇到过这样的程序,它在屏幕上显示一条消息,而还没来得及阅读之前,又出现了其他内容。这样读者将担心自己错过了重要的、无法恢复的消息。如果程序在显示其他内容之前等待5秒钟,情况将会好很多。while循环可用于这种目的。一种用于个人计算机的早期技术是,让计算机进行计数,以等待一段时间:

long wait = 0;
while (wait < 10000)
wait++;			//counting silently

这种方法的问题是,当计算机处理器的速度发生变化时,必须修改计数限制。例如,有些为IBM PC编写的游戏在速度更快的机器上运行时,其速度将快得无法控制;另外,有些编译器可能修改上述代码,将wait设置为10000,从而跳出该循环。更好的方法是让系统时钟来完成这种工作。

ANSI C和C++库中有一个函数有助于完成这样的工作。这个函数名为clock(),返回程序开始执行后所用的系统时间。这有两个复杂的问题:首先,clcok()返回时间的单位不一定是秒;其次,该函数的返回类型在某些系统上可能是long,在另一些系统上可能是unsigned long或其他类型。

但头文件ctime(较早的实现中为time.h)提供了这些问题的解决方案。首先,它定义了一个符号常量——CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得到秒数。或者将秒数乘以CLOCKS_PER_SEC,可以得到以系统时间单位为单位的时间。其次,ctime将clock_t作为clock()返回类型的别名(参见本章后面的注释“类型别名”),这意味着可以将变量声明为clock_t类型,编译器将把它转换为long、unsigned int或适合系统的其他类型。

程序清单5.14演示了如何使用clock()和头文件ctime来创建延迟循环。

程序清单5.14 waiting.cpp

//waiting.cpp -- using clock() in a time-delay loop
#include<iostream>
#include<ctime>//describes clock() function, clock_t type
int main()
{
	using namespace std;
	cout << "Enter the delay time, in seconds: ";
	float secs;
	cin >> secs;
	clock_t delay = secs * CLOCKS_PER_SEC;	//convert to clock ticks
	cout << "starting\a\n";
	clock_t start = clock();
	while (clock() - start < delay)			//wait until time elapses
		;									//note the semicolon
	cout << "done \a\n";
	return 0;
}

该程序以系统时间单位为单位(而不是以秒为单位)计算延迟时间,避免了在每轮循环中将系统时间转换为秒。

类型别名

C++为类型建立别名的方式有两种。一种是使用预处理器:
#define BYTE char //preprocesor replaces BYTE with char
这样,预处理器将在编译程序时用char替换所有的BYTE,从而使BYTE成为char的别名。

第二种方法是使用C++(和C)的关键字typedef来创建别名。例如,要将byte作为char的别名,可以这样做:
typedef char byte; //makes byte an alias for char
下面是通用格式:
typedef typeName aliasName;
换句话说,如果要将aliasName作为某种类型的别名,可以声明aliasName,如同将aliasName声明为这种类型的变量那样,然后在声明的前面加上关键字typedef。例如,要让byte_pointer成为char指针的别名,可将byte_pointer声明为char指针,然后在前面加上typedef:
typedef char * byte_pointer; //pointer to char type
也可以使用#define,不过声明一系列变量时,这种方法不适用。例如,请看下面的代码:
#define FLOAT_POINTER float *
FLOAT_POINTER pa, pb;
预处理器置换将该声明转换为这样:
float * pa, pb; //pa a pointer to float, pb just a float
typedef方法不会有这样的问题。它能够处理更复杂的类型别名,这使得与使用#define相比,使得typedef是一种更加的选择——有时候,这也是唯一的选择。

注意,typedef不会创建新类型,而只是为已有的类型建立一个新名称。如果将word作为int的别名,则cout将把word类型的值视为int类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hank_W

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值