4.3 循环
循环就是重复执行语句。这个技术使用起来非常方便,因为可以对操作重复任意多次(数千次,甚至数百万次),而不必每次都编写相同的代码。
举一个简单例子,下面的代码计算一个银行账户在10年后的金额,假定支付每年的利息,且该账户没有其他款项的存取:
double balance = 1000; double interestRate = 1.05; // 5% interest/year balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate; balance *= interestRate;
将相同代码编写10次很费时间,如果把10年改为其他值,又会如何?那就必须把该代码行手工复制需要的次数,这是一件多么痛苦的事!幸运的是,完全不必这样做。使用一个循环就可以对指令执行需要的次数。
循环的另一种重要类型是一直循环到给定的条件满足为止。这些循环比上面描述的循环稍简单些(但同样很有用),所以首先从这类循环开始介绍。
4.3.1 do循环
do循环以下述方式执行:执行标记为循环的代码,然后进行一个布尔测试,如果测试结果为true,就再次执行这段代码,并重复这个过程。当测试结果为false时,就退出循环。
do循环的结构如下:
do { <code to be looped> } while (<Test>);
其中,计算<Test>会得到一个布尔值。
注意:while语句之后必须使用分号。
例如,使用该结构可以把从1到10的数字输出到一列:
int i = 1; do { WriteLine("{0}", i++); } while (i <= 10);
在把i的值写到屏幕上后,使用后缀形式的++运算符递增i的值,所以需要检查一下i <= 10,把10也包含在输出到控制台的数字中。
下面的示例使用这个结构略微修改一下本节前面的代码。该段代码计算一个账户在10年后的余额。这次使用一个循环,根据起始的金额和固定利率,计算该账户的金额需要多少年才能达到某个指定的数额。
试一试:使用do循环:Ch04Ex04\Program.cs
(1)在目录C:\BegVCSharp\Chapter04创建一个新的控制台应用程序Ch04Ex04。
(2)把下述代码添加到Program.cs中:
static void Main(string[] args) { double balance, interestRate, targetBalance; WriteLine("What is your current balance? "); balance = ToDouble(ReadLine()); WriteLine("What is your current annual interest rate (in %)? "); interestRate = 1 + ToDouble(ReadLine()) / 100.0; WriteLine("What balance would you like to have? "); targetBalance = ToDouble(ReadLine()); int totalYears = 0; do { balance *= interestRate; ++totalYears; } while (balance < targetBalance); WriteLine($"In {totalYears} year{(totalYears == 1 ? "": "s")} you'll have a balance of {balance}."); ReadKey(); }
(3)执行代码,输入一些值,示例结果如图4-4所示。
图4-4
示例说明
这段代码利用固定的利率,对年度计算余额的过程重复必要的次数,直到满足结束条件为止。在每次循环中,递增一个计数器变量,就可以确定需要多少年:
int totalYears = 0; do { balance *= interestRate; ++totalYears; } while (balance < targetBalance); 然后就可以将这个计数器变量用作输出结果的一部分: WriteLine($"In {totalYears} year{(totalYears == 1 ? "": "s")} you'll have a balance of {balance}.");
注意:这可能是?: (三元)运算符最常见的用法了—— 用最少的代码有条件地格式化文本。这里,如果totalYears不等于1,就在year后面输出一个s。
但这段代码并不完美,考虑一下目标余额少于当前余额的情况,则结果应如图4-5所示。
图4-5
do循环至少执行一次。有时(如这个示例)这并不是很理想。当然,可以添加一条if语句:
int totalYears = 0; if (balance < targetBalance) { do { balance *= interestRate; ++totalYears; } while (balance < targetBalance); } WriteLine($"In {totalYears} year{(totalYears == 1 ? "": "s")} " + $"you'll have a balance of {balance}.");
这显然无谓地增加了复杂性。更好的解决方案是使用while循环。
4.3.2 while循环
while循环非常类似于do循环,但有一个明显区别:while循环中的布尔测试在循环开始时进行,而不是最后进行。如果测试结果为false,就不会执行循环。程序会直接跳转到循环之后的代码。
按下述方式指定while循环:
while (<Test>) { <code to be looped> }
使用方式几乎与do循环完全相同,例如:
int i = 1; while (i <= 10) { WriteLine($"{i++}"); }
这段代码的执行结果与前面的do循环相同,它在一列中输出从1到10的数字。下面使用while循环修改上一个示例。
试一试:使用while循环:Ch04Ex05\Program.cs
(1)在目录C:\BegVCSharp\Chapter04中创建一个新的控制台应用程序Ch04Ex05。
(2)修改代码,如下所示(使用Ch04Ex04中的代码作为起点,记住删除原来do循环最后的while语句):
static void Main(string[] args)
{
double balance, interestRate, targetBalance;
WriteLine("What is your current balance? ");
balance = ToDouble(ReadLine());
WriteLine("What is your current annual interest rate (in %)? ");
interestRate = 1 + ToDouble(ReadLine()) / 100.0;
WriteLine("What balance would you like to have? ");
targetBalance = ToDouble(ReadLine());
int totalYears = 0;
while (balance < targetBalance)
{
balance *= interestRate;
++totalYears;
}
WriteLine($"In {totalYears} year{(totalYears == 1 ? "": "s")} " +
$"you'll have a balance of {balance}.");
if (totalYears == 0)
WriteLine(
"To be honest, you really didn't need to use this calculator.");
ReadKey();
}
(3)再次执行代码,但这次使用少于起始余额的目标余额,如图4-6所示。
图4-6
示例说明
这段代码只是把do循环改为while循环,就解决了上个示例中的问题。把布尔测试移到开头处,就考虑了不需要执行循环的情况,可以直接跳转到输出结果。
当然,这种情况还有一个解决方案。例如,可以检查用户输入,确保目标余额大于起始余额。此时,可以把用户输入部分放在循环中,如下所示:
WriteLine("What balance would you like to have? ");
do
{
targetBalance = ToDouble(ReadLine());
if (targetBalance <= balance)
WriteLine("You must enter an amount greater than " +
"your current balance! \nPlease enter another value.");
}
while (targetBalance <= balance);
这将拒绝接受无意义的值,得到如图4-7所示的结果。
图4-7
在设计应用程序时,用户输入的有效性检查是一个很重要的主题,本书将列举更多这方面的示例。
4.3.3 for循环
本章介绍的最后一类循环是for循环。这类循环可以执行指定的次数,并维护它自己的计数器。要定义for循环,需要下列信息:
● 初始化计数器变量的一个起始值。
● 继续循环的条件,应涉及计数器变量。
● 在每次循环的最后,对计数器变量执行一个操作。
例如,如果要在循环中,使计数器从1递增到10,递增量为1,则起始值为1,条件是计数器小于或等于10,在每次循环的最后,要执行的操作是给计数器加1。
这些信息必须放在for循环的结构中,如下所示:
for (<initialization>; <condition>; <operation>) { <code to loop> }
它的工作方式与下述while循环完全相同:
<initialization> while (<condition>) { <code to loop> <operation> }
前面使用do和while循环输出了从1到10的数字。下面看看如何使用for循环完成这个任务:
int i; for (i = 1; i <= 10; ++i) { WriteLine($"{i}"); }
计数器变量是一个整数i,它的初始值是1,在每次循环的最后递增1。在每次循环过程中,把i的值写到控制台。
注意,当i的值为11时,将执行循环后面的代码。这是因为在i等于10的循环末尾,i会递增为11。这是在测试条件i <= 10之前发生的,此时循环结束。与while循环一样,在第一次执行前,只在条件计算为true时才执行for循环,所以可能根本就不会执行循环中的代码。
最后注意,可将计数器变量声明为for语句的一部分,重新编写上述代码,如下所示:
for (int i = 1; i <= 10; ++i) { WriteLine($"{i}"); }
但如果这么做,就不能在循环外部使用变量i (参见第6章中的“变量的作用域”一节)。
4.3.4 循环的中断
有时需要更精细地控制循环代码的处理。C#为此提供了以下命令:
● break—— 立即终止循环。
● continue——立即终止当前的循环(继续执行下一次循环)。
● return—— 跳出循环及包含该循环的函数(参见第6章)。
break命令可退出循环,继续执行循环后面的第一行代码,例如:
int i = 1; while (i <= 10) { if (i == 6) break; WriteLine($"{i++}"); }
这段代码输出1-5的数字,因为break命令在i的值为6时退出循环。
continue仅终止当前迭代,而不是整个循环,例如:
int i; for (i = 1; i <= 10; i++) { if ((i % 2) == 0) continue; WriteLine(i); }
在上面的示例中,只要i除以2的余数是0, continue语句就终止当前的迭代,所以只显示数字1、3、5、7和9。
4.3.5 无限循环
在代码编写错误或故意进行设计时,可以定义永不终止的循环,即所谓的无限循环。例如,下面的代码就是无限循环的一个简单例子:
while (true) { // code in loop }
有时这种代码也是有用的,而且使用break语句或者手工使用Windows任务管理器总是可以退出这样的循环。但当出现这种情形意外时,就会出问题。考虑下面的循环,它与上一节的for循环非常类似:
int i = 1; while (i <= 10) { if ((i % 2) == 0) continue; WriteLine($"{i++}"); }
i是在循环的最后一行代码(即continue语句后的那条语句)执行完后才递增的。如果程序执行到continue语句(此时i为2),程序会用相同的i值进行下一个循环,然后测试这个i值,继续循环,一直这样下去。这就冻结了应用程序。注意仍可以用一般方式退出已冻结的应用程序,所以不必重新启动计算机。