十九、高级条件
本书的第三部分以几个关于条件执行的任务结束,这些任务可能被认为是高级的。首先,你将学习条件运算符,然后你将编写一个包含几个复杂条件的程序,最后你将学习一条重要的格言:当你想测试某个东西时,你必须确定它存在。
条件运算符
在许多情况下,if-else
结构可以用条件操作符代替,这将根据条件是否满足而产生两个值中的一个。如果你知道 Excel 的IF
函数,你会发现条件运算符很熟悉。
工作
你将使用条件运算符(见图 19-1 )解决前面的“头尾”任务(来自第十六章)。
图 19-1
使用条件运算符
解决办法
代码如下:
static void Main(string[] args)
{
// Random number generator
Random randomNumbers = new Random();
// Random number 0/1 and its transformation
int randomNumber = randomNumbers.Next(0, 1 + 1);
string message = randomNumber == 0 ? "Head tossed" : "Tail tossed";
Console.WriteLine(message);
// Waiting for Enter
Console.ReadLine();
}
讨论
条件运算符(?:
)的语法如下所示:
condition ? yesValue : noValue
这种表达式的结果如下:
-
yes 值如果条件成立(满足)
-
新值否则
该计划
在这种情况下,条件是randomNumber
变量相对于零的相等测试。如果为真,message
变量被赋予“头被甩”的文本。否则,它将被分配“抛尾”文本。
术语
条件运算符也被称为三元运算符,因为它是唯一接受三个操作数(它处理的值)的运算符:条件、 yesValue 和 noValue 。
汇总评估
现在,您将在现实情况下练习更复杂的条件。
工作
任务是编写一个对学生进行总结评估的程序(见图 19-2 )。用户输入四个科目的等级(从一到五,其中一个是最好的)。用户还指定所考虑的学生是否无故缺席。然后,程序会给出一个总结性的评估,这是一种总体评分:
图 19-2
对学生的总结评价
-
优秀的
-
好的
-
不成功的
细节
我在第十八章中强调,为了能够编写任何东西,你需要准确理解正在解决的任务。在当前练习中,您需要为汇总评估指定确切的标准。
在以下情况下,学生的评估为优秀
-
所有成绩的算术平均值不高于 1.5。
-
该学生没有任何成绩低于 2 分。
-
这个学生没有任何无故缺席。
当学生的成绩中至少有一项是 5 分时,他们被认为是不及格。
在所有其他情况下,总结评估为良好。
你现在大概可以猜到,这个程序不会是无足轻重的。
解决办法
代码如下:
static void Main(string[] args)
{
// 1\. PREPARATIONS
string errorMessage = "Incorrect input";
int mathematics, informationTechnology, science, english;
bool hasUnexcusedAbsences;
// 2\. INPUTS
try
{
Console.WriteLine("Enter grades from individual subjects:");
Console.Write("Mathematics: ");
string input = Console.ReadLine();
mathematics = Convert.ToInt32(input);
if (mathematics < 1 || mathematics > 5)
{
Console.WriteLine(errorMessage);
return;
}
Console.Write("Information technology: ");
input = Console.ReadLine();
informationTechnology = Convert.ToInt32(input);
if (informationTechnology < 1 || informationTechnology > 5)
{
Console.WriteLine(errorMessage);
return;
}
Console.Write("Science: ");
input = Console.ReadLine();
science = Convert.ToInt32(input);
if (science < 1 || science > 5)
{
Console.WriteLine(errorMessage);
return;
}
Console.Write("English: ");
input = Console.ReadLine();
english = Convert.ToInt32(input);
if (english < 1 || english > 5)
{
Console.WriteLine(errorMessage);
return;
}
Console.Write("Any unexcused absences? (yes/no): ");
input = Console.ReadLine();
input = input.ToLower(); // not distinguishing upper/lower
if (input != "yes" && input != "no")
{
Console.WriteLine(errorMessage);
return;
}
hasUnexcusedAbsences = input == "yes";
}
catch (Exception)
{
Console.WriteLine(errorMessage);
return;
}
// 3\. EVALUATION
// You need arithmetic average of all the grades
double average = (mathematics + informationTechnology + science + english) / 4.0;
string message;
// Testing all conditions for excellence
if (average < 1.5001 &&
mathematics <= 2 && informationTechnology <= 2 &&
science <= 2 && english <= 2 &&
!hasUnexcusedAbsences)
{
message = "Excellent";
}
else
{
// Here you know the result is not excellent, so testing the other two possibilities
if (mathematics == 5 || informationTechnology == 5 ||
science == 5 || english == 5)
{
message = "Failed";
}
else
{
message = "Good";
}
}
// 4\. OUTPUT
Console.WriteLine("Summary evaluation: " + message);
// Waiting for Enter
Console.ReadLine();
}
讨论
以下部分解释了该程序。
等级输入
在本练习中,您非常关心输入数据检查。一个try-catch
包装了整个输入部分。你还需要检查分数是否属于一到五的范围。
请注意,坡度小于 1 或大于 5 表示有错误。您使用了||
操作符(“至少一个”)。
程序终止
错误的输入会立即终止程序。这里使用了return
语句来终止子程序。但是在Main
内部使用时,直接终止整个程序。
是/否输入
要输入学生是否无故缺席,用户可以输入 yes 或 no。yes 和 no 的输入差异表示有错误。我使用了&&
操作符(“同时”)。
在检查之前,您将输入转换成小写,这样它就不会介意用户使用大写字母。
有趣的是包含单等号和双等号的行(单等号用于赋值,双等号用于比较):
hasUnexcusedAbsences = input == "yes";
根据等式是否成立,==
操作符的“工作”产生一个true
或false
值。然后将结果值赋给bool
类型的hasUnexcusedAbsences
变量。
当心整数除法!
计算平均成绩时,将总和除以 4.0,而不是 4。您不希望计算机将斜杠视为整数除法运算符。这就是为什么你在避免int
被int
除。
如果您只输入 4,那么 1,2,2,2 等级的情况将被错误地评估为优秀,因为平均值将被精确计算为 1,而不是正确的 1.75!
小数运算
你为什么按如下方式输入平均值的检查?
average < 1.5001
你为什么不用下面的?
average <= 1.5
这是因为十进制算术不一定要精确。有时,计算机可能会计算出类似于 1.500000000001 的值,而不是正确的 1.5。这就是为什么你在测试中使用稍微大一点的数字。
第二个性测试
许多程序崩溃是因为程序员在访问某个程序之前忘记测试它是否存在。这个任务将是你对这个常见问题的第一次了解。
工作
我将向您展示如何测试输入文本的第二个字符。假设一个产品标签必须在第二个位置有一个大写字母 X (见图 19-3 和 19-4 )。
图 19-4
测试第二个字符,不正确
图 19-3
测试第二个字符,对吗
为什么这样一个测试如此重要,以至于我决定让你熟悉它?您需要首先测试第二个字符是否存在。这是你会经常遇到的;在你发现某样东西存在之前,你将无法测试它!
在这种情况下,程序不得因空输入或输入过短而崩溃(见图 19-5 )。
图 19-5
标签不正确,不崩溃
解决办法
代码如下:
static void Main(string[] args)
{
// Input
Console.Write("Enter product label: ");
string label = Console.ReadLine();
// Evaluating
if (label.Length >= 2 && label.Substring(1, 1) == "X")
{
Console.WriteLine("Label is OK");
}
else
{
Console.WriteLine("Incorrect label");
}
// Waiting for Enter
Console.ReadLine();
}
讨论
以下部分解释了该程序。
获得角色
使用Substring
方法访问第二个字符,该方法通常从给定文本中提取特定部分(子串)。该方法需要两个参数:
-
所需子串的第一个字符的位置:位置编号从零开始,所以第二个字符位置是 1。
-
所需子串的字符数:在这种情况下,你需要的只是单个字符,这就是为什么第二个参数也是一个。
存在测试
测试一个给定的字符是否等于某个东西有一个隐藏的陷阱:第二个字符根本不需要存在。当用户输入零个或一个字符时会出现这种情况。
在这种情况下,Substring(1,1)
调用会导致运行时错误。
这意味着您必须首先测试文本是否至少有两个字符长。只有通过这个测试,你才能进入第二个角色。
代码中有一个复合条件,如下所示:
if (label.Length >= 2 && label.Substring(1, 1) == "X")
其功能依赖于&&
操作员的短路评估。如果 AND 连接的第一个部分条件不成立,则第二个部分条件根本不会计算,因为它是无用的。即使它成立,也不会改变整体结果,因为 AND 运算符要求两部分同时成立。
在这种情况下,当一个标签的长度小于 2 时,那么会失败的Substring
调用将被跳过,程序不会崩溃!
注意,对于||
操作符也可以做类似的陈述。
一项实验
尽量省略第一个部分条件(长度检查)。然后输入一个字符作为用户。程序将因运行时错误而终止。这样你会发现第一个条件真的很重要。
摘要
在本章中,您学习了几个高级条件的示例。
您从所谓的条件运算符(?:
)开始,它也被称为三元运算符,因为它与三个值一起工作。根据指定条件(第一个值,在问号之前)的满足情况,操作符的“工作”结果要么是 yesValue (第二个值,在问号和冒号之间),要么是 noValue (第三个值,在冒号之后)。对于某些类型的if-else
情况,条件操作符是一种合适的快捷替代方式。
汇总求值的中间任务是一种对你所学的关于条件执行的所有东西的回顾。在这里,您遇到了各种测试和许多复合条件,以及使用!
操作符的否定。
测试某些文本的第二个字符的最后一个任务向您展示了在探索第二个字符是什么之前测试它是否存在的重要性。这里你使用了短路条件评估。如果复合条件的结果在第一个部分条件被评估之后已经可以被决定,那么第二个部分条件被完全跳过。
二十、第一个循环
你正在进入这本书最难的章节。循环是一个强大的工具,所有程序员都像呼吸空气一样需要它。理解循环是不容易的,这就是为什么你会经历许多循环的练习。
重复相同的文本
循环是一种工具,用于高效地重复编写相同或更常见的类似活动。为了正确理解循环,你将两次解决一些任务,第一次不带循环,第二次带循环。您将从重复相同的活动开始,之后您将继续使用循环来重复类似的活动。
工作
你将编写一个程序,显示“我明天开始学习。”连续十次(见图 20-1 )。
图 20-1
十次重复
解决办法
代码如下:
static void Main(string[] args)
{
// Output
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
Console.WriteLine("I will start learning tomorrow.");
// Waiting for Enter
Console.ReadLine();
}
使用循环的解决方案
想一想之前的练习。你能想象有人想要你改变显示的句子吗?你能想象重复一百次而不是十次吗?你能想象用户输入的重复次数吗?
要解决这些问题,你需要一个新的工具:循环。
解决办法
代码如下:
static void Main(string[] args)
{
// Output
for (int count = 0; count < 10; count++)
{
Console.WriteLine("I will start learning tomorrow.");
}
// Waiting for Enter
Console.ReadLine();
}
for 循环的工作原理
你用for
结构来表示重复。它的一般语法如下所示:
for (initializer; loopCondition; iterator)
{
statement;
statement;
statement;
...
}
for
循环是这样工作的:
-
initializer
在进入循环前执行一次。 -
loopCondition
在循环的每一个回合之前被评估。如果它成立,计算机就进入循环并执行它体内的语句。 -
iterator
语句在循环的每一次循环完成后执行。之后,loopCondition
再次求值。
图 20-2 显示了程序流程。
图 20-2
程序流程
循环
在这种情况下,所需的重复次数是通过计算到目前为止完成的循环圈数来实现的。为此,您可以使用 count 变量。
在开始时(初始化器),变量被设置为零。
完成每一次循环(迭代器)后,变量加 1。
只要(loopCondition
)输出中的行数没有达到十,循环体(显示一行文本)就会重复。一旦count
变量变为 10,条件(count
< 10 或 10 < 10)将不再满足,循环将终止,计算机将继续执行循环后的语句。
自己去探索吧
您应该花时间探索循环的内部工作方式,以便彻底掌握它们。使用你已经知道的调试工具:步进并检查count
变量。
小费
Visual Studio 可以帮你写一个for
循环,不会出错。只需输入for
,按两下 Tab 键,编辑生成的循环头即可。
选择重复次数
for
循环允许您在事先不知道重复次数的情况下解决问题(在编写代码时)。
工作
您将修改之前的练习,让用户指定句子重复的次数(参见图 20-3 )。
图 20-3
让用户指定句子重复的次数
解决办法
代码如下:
static void Main(string[] args)
{
// Input
Console.Write("Enter number of repetitions: ");
string input = Console.ReadLine();
int howManyTimes = Convert.ToInt32(input);
// Output
for (int count = 0; count < howManyTimes; count++)
{
Console.WriteLine("I will start learning tomorrow.");
}
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
与前一个任务相比,您用用户输入的变量值替换了固定的重复次数。
-
仔细选择变量的名称,以存储所需的总重复次数;这里是
howManyTimes
。具体来说,您应该将它与存储当前重复次数的count
变量区分开来。
反复投掷骰子
您将看到另一个重复相同活动的例子。
工作
你将编写一个投掷骰子 20 次的程序(见图 20-4 )。
图 20-4
投掷骰子 20 次
解决办法
代码如下:
static void Main(string[] args)
{
// Random number generator
Random randomNumbers = new Random();
// Output
for (int count = 0; count < 20; count++)
{
int thrown = randomNumbers.Next(1, 6 + 1);
Console.Write(thrown.ToString() + " ");
}
// Waiting for Enter
Console.ReadLine();
}
重复类似的台词
如果重复的活动不是相同的而是相似的呢?
工作
您将输出十个相似的行,不同之处仅在于打印的行号(见图 20-5 )。
图 20-5
输出十次类似的东西
没有循环的解决方案
同样,您可以从没有循环的解决方案开始,以体会循环的重要性。
代码如下:
static void Main(string[] args)
{
// Output
Console.WriteLine("My main to-do list:");
Console.WriteLine("1\. To learn");
Console.WriteLine("2\. To learn");
Console.WriteLine("3\. To learn");
Console.WriteLine("4\. To learn");
Console.WriteLine("5\. To learn");
Console.WriteLine("6\. To learn");
Console.WriteLine("7\. To learn");
Console.WriteLine("8\. To learn");
Console.WriteLine("9\. To learn");
Console.WriteLine("10\. To learn");
// Waiting for Enter
Console.ReadLine();
}
使用循环的解决方案
循环可以有效地解决这类问题。实际上,你会发现自己加入循环来重复相似的活动比重复完全相同的活动更频繁。
static void Main(string[] args)
{
// Output
Console.WriteLine("My main to-do list:");
for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
{
Console.WriteLine(taskNumber.ToString() + ". To learn");
}
// Waiting for Enter
Console.ReadLine();
}
讨论
以下部分讨论了该计划。
决策变量
解决方案的核心是使用循环体内部的控制变量的值。在这个程序中,您将变量命名为taskNumber
,并将它的值用于输出。
这就是如何在循环的第一段显示一个,在第二段显示两个,依此类推。
使用调试工具亲自检查情况。
循环从 1 开始
前面的练习(重复投掷骰子)使用了控制变量从 0 到 19 的循环。与此相反,这次从 1 开始比从 0 开始更方便。这一改变也导致了循环条件的改变。您使用了“小于或等于”测试,而不是“小于”测试。
摘要
本章向您介绍了循环的主题,这是一个强大的编程工具,允许您指定相同的重复,或者更常见的是,类似的活动。
对于循环,C# 提供了几种编程结构;在本章中,你学习了最基本的for
循环。在代码中,for
循环由一个控制循环的标题和一个主体组成,主体由用大括号括起来的要重复的语句组成。标题本身由分号分隔的三部分组成:
-
初始化器是在循环开始“循环”之前要执行一次的语句
-
循环条件是每次循环前评估的条件。如果满足(即评估为
true
),则执行循环体的另一轮语句。如果不满足(即评估为false
),循环终止,程序继续执行循环后的语句。 -
迭代器是循环每一次循环后要执行的语句。
为了更深入地理解for
循环是如何工作的,一定要使用调试工具,比如步进和内存检查。
for
循环通常由一个变量控制,其工作方式或多或少类似于循环圈数的计数器。这个变量叫做控制变量。在上一个任务中,您学习了如何在循环体中使用控制变量的值。
二十一、改善循环
正如您所了解的,循环是强大的,并且它们不是微不足道的。这就是为什么本书的其余章节都致力于更好地理解循环。让我们进行一些更难的练习。
选择文本
首先,你将回到上一章的课文重复练习,并对其进行改进。
工作
在“选择重复次数”部分,用户可以改变给定句子的重复次数。现在你将允许用户改变句子本身(见图 21-1 )。
图 21-1
改变句子
解决办法
代码如下:
static void Main(string[] args)
{
// Inputs
Console.Write("Enter text to repeat: ");
string textToRepeat = Console.ReadLine();
Console.Write("Enter number of repetitions: ");
string input = Console.ReadLine();
int howManyTimes = Convert.ToInt32(input);
// Output
for (int count = 0; count < howManyTimes; count++)
{
Console.WriteLine(textToRepeat);
}
// Waiting for Enter
Console.ReadLine();
}
交替循环
通常,你需要重复一些活动。你做第一件事,然后第二件,再做第一件,以此类推。在代码中查看这样的任务是很有趣的。我将向你展示解决这个问题的三种方法。
工作
您将编写一个程序,在待办事项列表中的两个任务之间进行交替(参见图 21-2 )。
图 21-2
在两个任务之间交替
第一种解决方案
第一种解决方案是基于区分循环的控制变量(从 1 到 10)是奇数还是偶数。当它是奇数时,你显示“学习”当它是偶数时,你显示“约会”
奇数/偶数测试将通过检查整数除以 2 后的余数来执行。提醒您一下,余数是用 C# 中的百分号(%
)运算符计算的。
static void Main(string[] args)
{
// Output
Console.WriteLine("My main to-do list:");
for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
{
string taskText = taskNumber % 2 != 0 ? "Learning" : "Dating";
Console.WriteLine(taskNumber.ToString() + ". " + taskText);
}
// Waiting for Enter
Console.ReadLine();
}
注意
您将奇数/偶数测试合并到一个条件(三元)运算符(?:
)中。你也可以用普通的if-else
。
第二种解决方案
第二种解决方案是来回切换布尔值。
你有一个bool
类型的变量,在循环的每一次循环中,你可以从true
切换到false
,反之亦然。当变量等于true
时,显示第一个文本。当它是false
时,你显示第二个。
static void Main(string[] args)
{
// Preparations
Console.WriteLine("My main to-do list:");
bool learning = true;
for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
{
// Output
string taskText = learning ? "Learning" : "Dating";
Console.WriteLine(taskNumber.ToString() + ". " + taskText);
// Toggling of the flag
learning = !learning;
}
// Waiting for Enter
Console.ReadLine();
}
笔记
请注意以下几点:
-
条件不必作为
learning == true
输入。learning
变量已经是bool
类型的,这意味着你可以直接把它作为一个条件使用。当它是true
时,条件成立。 -
在进入循环之前,需要设置变量的初始值。初始值在循环的第一次循环中使用。在这种情况下,您将其设置为
true
。 -
使用求反运算符(
!
)从true
切换到false
,反之亦然。
第三种解决方案
解决方案的第三种方法是重复循环五次而不是十次,并且在循环的单个回合中执行“奇数活动”和“偶数活动”。
代码如下:
static void Main(string[] args)
{
// Preparations
Console.WriteLine("My main to-do list:");
int taskNumber = 1;
for (int coupleCount = 0; coupleCount < 5; coupleCount++)
{
// Couple output and adjusting task number
Console.WriteLine(taskNumber.ToString() + ". Learning");
taskNumber++;
Console.WriteLine(taskNumber.ToString() + ". Dating");
taskNumber++;
}
// Waiting for Enter
Console.ReadLine();
}
石头剪刀布
在下一个练习中,您将看到for
循环体中有许多语句。循环将代表游戏的单个回合。
工作
您将编写一个程序,与用户玩指定回合数的石头剪刀布游戏(见图 21-3 )。
图 21-3
游戏
得分类似于国际象棋:胜利得一分,平局得半分。
解决办法
代码如下:
static void Main(string[] args)
{
// Preparations
Random randomNumbers = new Random();
double playerPoints = 0;
double computerPoints = 0;
int rock = 1, scissors = 2, paper = 3;
// Inputs
Console.Write("Enter your name: ");
string playerName = Console.ReadLine();
Console.Write("Enter number of game rounds: ");
string input = Console.ReadLine();
int totalRounds = Convert.ToInt32(input);
Console.WriteLine();
// Individual rounds
for (int roundNumber = 0; roundNumber < totalRounds; roundNumber++)
{
// Computer chooses
int computerChoice = randomNumbers.Next(1, 3 + 1);
// Player chooses
Console.Write("Enter R or S or P: ");
string playerInput = Console.ReadLine();
string playerInputUppercase = playerInput.ToUpper();
int playerChoice = playerInputUppercase == "R" ?
rock : (playerInputUppercase == "S" ? scissors : paper);
// Round evaluation
string message = "";
if (computerChoice == rock && playerChoice == scissors ||
computerChoice == scissors && playerChoice == paper ||
computerChoice == paper && playerChoice == rock)
{
// Computer won
computerPoints += 1;
message = "I won";
}
else
{
// Tie or player won
if (computerChoice == playerChoice)
{
// Tie
computerPoints += 0.5;
playerPoints += 0.5;
message = "Tie";
}
else
{
// Player won
playerPoints += 1;
message = "You won";
}
}
// Round output
string playerChoiceInText = playerChoice == rock ?
"Rock" : (playerChoice == scissors ? "Scissors" : "Paper");
string computerChoiceInText = computerChoice == rock ?
"Rock" : (computerChoice == scissors ? "Scissors" : "Paper");
Console.WriteLine(playerName + ":Computer - " +
playerChoiceInText + ":" + computerChoiceInText);
Console.WriteLine(message);
Console.WriteLine();
} // End of loop for game round
// Game evaluation
Console.WriteLine("GAME OVER - OVERALL RESULT");
Console.WriteLine(playerName + ":Computer - " +
playerPoints.ToString() + ":" + computerPoints.ToString());
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
计算机用随机数“选择”:1 代表石头,2 代表剪刀,3 代表布。
-
为了简单起见,当用户输入 R、S 或 P 以外的内容时,您将其视为“纸张”
-
用户输入时不区分大小写。
-
在一些地方,三重分支是使用两个嵌套的条件(三元)运算符解决的,而不是使用两个嵌套的
if-else
s。请仔细注意第一个条件运算符的noValue
是如何使用另一个条件运算符指定的,该运算符用括号括起来。 -
如果您不喜欢条件(三元)操作符,就不要使用它。它只是一个特殊
if-else
案例的捷径。我个人很喜欢,所以经常用。
摘要
在本章中,您继续学习了循环。第一个练习基本上是对你在前一章所学内容的提醒。您修改了以前的一项任务。
接下来,您了解了几种解决交替循环的方法,也就是说,循环重复相似的活动对。具体来说,您研究了以下解决方案:
-
基于控制变量是奇数还是偶数的交替输出
-
切换一个
bool
变量,指示您是否想要第一个输出 -
在单圈中完成两个动作
石头剪子布游戏的最后一个例子实际上不是以循环为中心的。循环只是重复游戏回合的手段。一轮游戏是一个真实的,更复杂的程序的例子,你可以用你在本书中学到的知识来完成。
二十二、数字系列
一些编程任务简化为生成正则数列。这就是你本章要研究的内容。通过这种方式,您还可以更好地理解循环。
每隔一…
你已经能够生成一个简单的数列,比如从 1 到 10。现在,您将着手生成一个稍微复杂一点的序列。
工作
在该任务中,您将显示“每隔一个”的数字,直到 20(参见图 22-1 )。
图 22-1
每隔一个数字显示一次
解决办法
代码如下:
static void Main(string[] args)
{
// Output
for (int number = 2; number <= 20; number += 2)
{
Console.WriteLine(number);
}
// Waiting for Enter
Console.ReadLine();
}
讨论
练习的最大难点是实现如何编写for
循环的迭代器。因为您想将变量number
增加 2,相应的语句将是number += 2
。
可选择的解决方案
用另一种方法解这道题很有趣。你可以有一个从 1 到 10 步进的普通循环,显示两倍的控制变量而不是变量本身。
static void Main(string[] args)
{
// Output
for (int line = 1; line <= 10; line++)
{
int displayedNumber = 2 * line;
Console.WriteLine(displayedNumber);
}
// Waiting for Enter
Console.ReadLine();
}
降级数
如果数列中的数字是递减的呢?那时很多事情都会改变。让我们来看看。
工作
在该任务中,您将显示从十到一的数字(参见图 22-2 )。
图 22-2
数字在下降
解决办法
代码如下:
static void Main(string[] args)
{
// Output
for (int number = 10; number >= 1; number--)
{
Console.WriteLine(number);
}
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
循环的初始化器可能是最简单的。你从十点开始。
-
迭代器也不难;数字会变小,这就是为什么你在每一轮结束的时候减一。
-
最难的是循环条件。你必须以这样一种方式来制定它,只要你想让循环继续下去,它就成立,而当你想退出时,它就停止成立。正确的测试是
number
变量是否大于或等于 1。
十进制数字
带有十进制数字的序列可能会让您感到惊讶。
工作
在该任务中,您将生成一个从 9 到 0 的序列,数字在每一步中递减 0.9(见图 22-3 )。
图 22-3
减少 0.9
看似正确的解决方案
使用上一个练习的样式,您可以编写以下内容:
static void Main(string[] args)
{
// Output
for (double number = 9; number >= 0; number -= 0.9)
{
Console.WriteLine(number.ToString("N1"));
}
// Waiting for Enter
Console.ReadLine();
}
测试
然而,测试揭示了序列中缺失的最后一个成员:零(见图 22-4 )。
图 22-4
缺少最后一个数字
这怎么可能呢?
错误的原因
这个练习展示了处理十进制数字是多么的棘手;你需要小心,因为十进制算术可能不精确!
当您忽略一个小数位(.ToString("N1")
)的格式时,您可以感觉到原因。试试看(见图 22-5 )。
图 22-5
省略格式
您可以看到,预期的倒数第二个系列成员比它应该的稍少。进一步减去 0.9 会得到略低于零的值,这就是为什么预期的最后一个零没有显示出来。
正确的解决方案
使用十进制数时,您需要指定一个循环的终值,并留有一点自由空间。
因此,该练习的正确答案如下所示:
static void Main(string[] args)
{
// Output
for (double number = 9; number >= -0.0001; number -= 0.9)
{
Console.WriteLine(number.ToString("N1"));
}
// Waiting for Enter
Console.ReadLine();
}
检查结果!
第二权力
现在,在一行中显示两个相连的数字怎么样?
工作
除了 1 到 10 系列的数字外,您还可以在每一行输出中显示相应的二次幂(见图 22-6 )。
图 22-6
显示第二功率
解决办法
代码如下:
static void Main(string[] args)
{
// Output
for (int number = 1; number <= 10; number++)
{
int secondPower = number * number;
Console.WriteLine(number.ToString() + " " + secondPower.ToString());
}
// Waiting for Enter
Console.ReadLine();
}
连续两次
让我们保持两个数在一条线上。
工作
在此任务中,您将生成一个 1–20 系列,每行输出中有几个数字(参见图 22-7 )。
图 22-7
在一行上显示多个数字
解决办法
这个练习非常类似于上一章的交替循环任务。它也可以通过多种方式解决。我选其中一个:你在奇数后面加一个空格,偶数后面加一个换行符。
代码如下:
static void Main(string[] args)
{
// Output
for (int number = 1; number <= 20; number++)
{
Console.Write(number);
// What goes after the number depends on the even/odd test
if (number % 2 != 0)
{
// Odd number, displaying space
Console.Write(" ");
}
else
{
// Even number, new line
Console.WriteLine();
}
}
// Waiting for Enter
Console.ReadLine();
}
两个独立系列
另一个有趣的例子是两个独立的数列。
工作
你将会有两个,有点随意的,成员数不同的数列。第一个是每步递减 2(111,109,…,97),第二个是每步递增 3(237,240,…,270)。
程序将在每一行显示第一个系列的数字和第二个系列的数字(见图 22-8 )。
图 22-8
更复杂的交替
解决办法
代码如下:
static void Main(string[] args)
{
// Preparation
int first = 111;
// Output
for (int second = 237; second <= 270; second += 3)
{
// Preparing first text
string firstText = first >= 97 ?
first.ToString().PadLeft(3) : " ";
// Actual output
Console.WriteLine(firstText + " " + second.ToString());
// Changing x
first -= 2;
}
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
使用循环的控制变量显示一个系列(较长的一个)。另一个用另一个自变量。
-
在每一步中,你都要检查较短的系列是否还在继续。
-
为了实现更好的格式化,您使用了
PadLeft
方法调用,该方法在参数的左边添加空格,以达到指定的字符总数。
摘要
在这一章中,你在生成各种数列的任务中练习了循环。具体来说,您学到了以下内容:
-
当级数为 2 步时,如何编写循环的迭代器?
-
如何在循环体中不直接显示控制变量,而是显示从中导出(计算)的值。
-
如何在一个循环的迭代器中使用
--
操作符生成一个降序序列,并使用>=
操作符指定循环条件,这样只要你想进行循环,它就会被满足。 -
由于内存中十进制数的不精确表示,十进制数列需要特别小心。这意味着,例如,您需要在循环条件下提供额外的自由活动。
您还解决了在单个输出行中有两个数字的情况,以及两个独立系列的更困难的最终任务。
二十三、未知的重复次数
在所有你已经解决的循环中,你知道迭代的次数。有时候,当你写一个程序时,你并不知道它,仅仅是因为用户应该输入它。然而,在所有情况下,当一个循环开始时,已经确定了它将迭代多少次。
有时,在循环开始执行时,重复的次数是未知的。通常,您会关心循环应该继续还是终止的问题。
输入密码
第一项任务是登录。您事先不知道用户需要尝试多少次。
工作
您将反复要求用户输入密码,直到用户输入正确的密码(参见图 23-1 )。正确的密码将是的朋友。
图 23-1
反复问一个问题
解决办法
代码如下:
static void Main(string[] args)
{
string correctPassword = "friend";
bool ok; // the variable must be declared outside of the loop!
do
{
// Input
Console.Write("Enter password: ");
string enteredPassword = Console.ReadLine();
// Evaluating
ok = enteredPassword == correctPassword;
} while (!ok); // loop repeats when the condition holds
Console.WriteLine("Come inside, please...");
// Waiting for Enter
Console.ReadLine();
}
边做边施工
要编写循环,可以使用do-while
构造。
计算机在单词do
之后进入循环,执行它的语句,并询问“再来一次?”。如果在while
字之后的条件成立,计算机返回到循环的开始,换句话说,在do
字之后。等等。
当while
字后的条件被评估为未实现(false
)时,循环终止。
这个案子
在这种情况下,程序会在每次输入后评估输入的密码。评估结果随后存储在一个名为ok
的bool
类型变量中。
如果输入的密码不正确,您希望循环继续。这就是为什么在while
条件中使用否定运算符(感叹号)的原因。
循环外的变量
C# 要求循环条件中使用的所有变量都在循环之外声明。当您在内部声明它们时,它们在指定条件时不可见。
小费
Visual Studio 可以帮助您处理do-while
循环。只需输入do
,按两下 Tab 键。
等待下降
想象一下,计算机观察某个在大多数时间增长的量,任务是检测它减少(下降)的(可能很少)时刻。
在挖掘存储在文件或数据库中的大量数据时,您通常会遇到这样的问题。但是,您将根据用户输入的数据来解决这个问题。
工作
你将制作一个反复要求用户输入的程序(见图 23-2 )。每当用户输入一个小于前一个的数字时,程序将通知用户(并终止)。
图 23-2
当数字变小时终止
解决办法
解决方案的核心是记住以前的值,而不仅仅是当前输入的值。
代码如下:
static void Main(string[] args)
{
// Preparations
int previous = int.MinValue;
bool ok;
// Repeating until descend
do
{
// Input
Console.Write("Enter a value (number): ");
string input = Console.ReadLine();
int value = Convert.ToInt32(input);
// Evaluating
ok = value >= previous; // ok = still not descending
// Storing for the next round of the loop
previous = value;
} while (ok);
// Message to the user
Console.WriteLine("Descend detected...");
// Waiting for Enter
Console.ReadLine();
}
讨论
第一个值有些特殊,因为它没有前任。它的缺失可以通过使用一些非常小的数字来模拟它来规避。C# 为您提供了int.MinValue
,这是可以存储在int
类型中的最小值,大约是负 20 亿。
直到年底的每个星期
让我们继续下一个练习,它与日期有关。
工作
任务是显示从今天开始到年底的日期,并以一周为单位进行(见图 23-3 )。
图 23-3
一年中,每次一周
解决办法
代码如下:
static void Main(string[] args)
{
// Today
DateTime today = DateTime.Today;
int thisYear = today.Year;
// Repeating
DateTime date = today;
do
{
// Output
Console.WriteLine(date.ToLongDateString());
// Preparing next output (a week later)
date = date.AddDays(7);
} while (date.Year == thisYear);
// Waiting for Enter
Console.ReadLine();
}
只要 6 号被抛出
随机数可以为您提供不确定循环终止的其他好例子。
工作
你将掷出一个骰子,只要有一个六,你就一直掷出这个骰子(见图 23-4 和 23-5 )。
图 23-5
只要你得到 6,就掷骰子
图 23-4
滚动一次骰子(没有六次,没有重复)
你可能知道一些使用这个原则的棋盘游戏。
解决办法
代码如下:
static void Main(string[] args)
{
// Random number generator
Random randomNumbers = new Random();
// Throwing as long as we have six
int thrown;
do
{
thrown = randomNumbers.Next(1, 6 + 1);
Console.WriteLine(thrown);
} while (thrown == 6);
// Waiting for Enter
Console.ReadLine();
}
直到第二个六
这个任务是关于具有随机值的未知重复次数。
工作
你将编写一个程序,抛出一个骰子,直到第二次抛出 6(见图 23-6 )。
图 23-6
一直等到 6 出现两次
解决办法
你只需数一数掷出六个骰子的次数。
代码如下:
static void Main(string[] args)
{
// Random number generator
Random randomNumbers = new Random();
// Throwing until the second six is thrown
int howManySixes = 0;
do
{
// Actual throwing
int thrown = randomNumbers.Next(1, 6 + 1);
Console.WriteLine(thrown);
// Counting sixes
if (thrown == 6)
{
howManySixes++;
}
} while (howManySixes < 2);
// Waiting for Enter
Console.ReadLine();
}
直到连续两个六
你知道为什么扔骰子的例子那么多吗?我小时候喜欢玩桌游,能看出来吗?
工作
在这个程序中,你将掷出一个骰子,直到连续两次掷出 6(见图 23-7 )。
图 23-7
连续两个六
解决办法
除了当前抛出的数字,您还需要跟踪前一个数字。这类似于“等待下降”部分中的程序。
如果当前和先前的数字都是 6,程序终止。
同样,第一个值是特定的,因为它没有前任。这就是为什么previous
变量从零开始,这是一个永远不会出现在骰子上的值。
代码如下:
static void Main(string[] args)
{
// Random number generator
Random randomNumbers = new Random();
// Preparations
int previous = 0;
bool ending;
// Throwing until two sixes in a row
do
{
// Actually throwing
int thrown = randomNumbers.Next(1, 6 + 1);
Console.WriteLine(thrown);
// Evaluating
ending = thrown == 6 && previous == 6;
// Preparing for next round of the loop
previous = thrown;
} while (!ending);
// Waiting for Enter
Console.ReadLine();
}
摘要
在本章中,你学习了循环开始时不知道重复次数的循环。在 C# 中,这种循环可以使用do-while
构造来编写。它的功能首先是执行身体的陈述,然后问,“再来一次?”评估条件,如果条件成立,就执行另一轮循环。
您还看到,要在do-while
循环条件中使用某个变量,该变量必须在循环之外声明。
使用do-while
循环时的一个常见错误是对其条件的错误表述。你必须小心,以这样的方式写,如果你想继续循环,条件应该评估为true
。
在本章的几个任务中,你需要一些来自前一轮循环的值。为此,您使用了一个特殊的变量来存储值。当然,第一轮循环需要特殊处理。
二十四、累积中间结果
在这一章中,你将学习使用循环处理大型数据集的重要案例。你会经常用一个循环去遍历大量的数据,去积累(聚合)一些中间结果,这些结果在循环终止后就成为最终结果。
输入数字的总和
此类别中的一个典型任务是对大量值求和。
工作
假设用户正在输入数字,最后一个数字是零。换句话说,用户通过输入零来表示他们完成了。然后程序显示所有输入数字的总和(见图 24-1 )。
图 24-1
将所有数字相加直到零
解决办法
解决方案的核心是积累中间结果。你必须把它保存在一个变量中,并把每个输入的数字都加到这个变量中。一旦用户终止输入,变量将包含所有输入值的总和。
代码如下:
static void Main(string[] args)
{
// Preparations
int sum = 0;
int number;
// Entering numbers until zero
do
{
// Input
Console.Write("Enter a number (0 = end): ");
string input = Console.ReadLine();
number = Convert.ToInt32(input);
// Adding to intermediate sum
sum += number;
} while (number != 0);
// Output
Console.WriteLine("Sum of entered numbers is: " + sum.ToString());
// Waiting for Enter
Console.ReadLine();
}
输入数字的乘积
把输入的数字相乘而不是求和怎么样?你认为任务是一样的吗?也不完全是。
工作
在该程序中,用户输入数字,最后一个数字为零(见图 24-2 )。然后,程序显示所有输入数字的乘积,当然不包括最后的零,这将使所有数字为零。
图 24-2
将所有数字相乘
解决办法
代码如下:
static void Main(string[] args)
{
// Preparations
double product = 1;
int number;
// Entering numbers until zero
do
{
// Input
Console.Write("Enter a number (0 = end): ");
string input = Console.ReadLine();
number = Convert.ToInt32(input);
// Accumulating in intermediate product (but not the last zero!)
if (number != 0)
{
product *= number;
}
} while (number != 0);
// Output
Console.WriteLine("Product of entered numbers (excluding zero) is: " + product.ToString("N0"));
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
product
变量从一个与零相反的值开始,这是您在计算总和时使用的。 -
更新产品时,需要注意不要包括最后的零。
-
您在类型
double
中声明了product
变量,以避免结果溢出。当你乘的时候,你很快得到大的数字。
最伟大的
处理大量数据时的另一个典型任务是搜索极值,换句话说,最大值或最小值。
工作
在这个程序中,用户输入十个数字。然后程序输出哪个最大(见图 24-3 )。
图 24-3
输出最大的数字
解决办法
你又要累加中间结果了。这一次,它将是“迄今为止”最大的数字。你必须特别注意第一个值;greatest
变量在开始时被设置为最小可能值,以确定第一个输入的数字总是较大。
因为您期望输入中正好有十个值,所以在这里使用for
循环更方便。
代码如下:
static void Main(string[] args)
{
// Preparation
int greatest = int.MinValue;
// Input of ten numbers
for (int order = 1; order <= 10; order++)
{
// Input
Console.Write("Enter " + order.ToString() + ". number: ");
string input = Console.ReadLine();
int number = Convert.ToInt32(input);
// Is it greater than the greatest so far?
if (number > greatest)
{
greatest = number;
}
}
// Output
Console.WriteLine("The greatest of entered numbers was: " + greatest.ToString());
// Waiting for Enter
Console.ReadLine();
}
第二伟大的
第二大价值呢?这是一个困难得多的练习。
工作
任务是从十个输入的数字中选择第二大的数字(见图 24-4 )。
图 24-4
显示第二大的数字
解决办法
你需要记住并不断更新两个最大的数字。仅仅记住第二大奇迹是不够的。
这种情况类似于滑雪比赛,参赛者一个接一个地到达终点。在某个时刻,有人是第一;过了一会儿,又有人把第一名挤到了第二名。可能在以后的时间里,那个滑雪者甚至可能失去第二名,仅仅是因为有人会比他们更好,甚至比新的领先者更好。
代码如下:
static void Main(string[] args)
{
// Preparation
int greatest = int.MinValue;
int secondGreatest = int.MinValue;
// Input of ten numbers
for (int order = 1; order <= 10; order++)
{
// Input
Console.Write("Enter " + order.ToString() + ". number: ");
string input = Console.ReadLine();
int number = Convert.ToInt32(input);
// Is it greater than the greatest so far?
if (number > greatest)
{
// Moving so far greatest to the second place
secondGreatest = greatest;
// Entered number becomes the greatest so far
greatest = number;
}
else
{
// We did not beat the greatest, will we beat the second greatest at least?
if (number > secondGreatest)
{
secondGreatest = number;
}
}
}
// Output
Console.WriteLine("The second greatest of entered numbers was: " + secondGreatest.ToString());
// Waiting for Enter
Console.ReadLine();
}
所有输入名称的输出
本章的最后一个练习与文本有关,特别是处理大量文本(见图 24-5 )。
图 24-5
以原始顺序打印,然后反转
工作
您将编写一个程序,重复读取用户输入的名称。空输入表示终止。然后程序重复所有输入的名字,首先以相同的顺序,然后以相反的顺序。
解决办法
为了让你能够在最后重复所有的名字,你需要在某个地方记住它们。你需要积累它们。一个变量将在它的末端累积它们(相同顺序的输出),另一个在它的开始累积它们(相反顺序的输出)。
代码如下:
static void Main(string[] args)
{
// Preparation
string inSameOrder = "";
string inReversedOrder = "";
bool terminating;
// Repeating until blank input
do
{
// Input
Console.Write("Enter person: ");
string person = Console.ReadLine();
// Processing input
terminating = person.Trim() == "";
if (!terminating)
{
inSameOrder = inSameOrder + person + ", ";
inReversedOrder = person + ", " + inReversedOrder;
}
} while (!terminating);
// Removing trailing comma and space
if (inSameOrder.EndsWith(", "))
{
int numberOfCharacters = inSameOrder.Length;
inSameOrder = inSameOrder.Remove(numberOfCharacters - 2);
}
if (inReversedOrder.EndsWith(", "))
{
int numberOfCharacters = inReversedOrder.Length;
inReversedOrder = inReversedOrder.Remove(numberOfCharacters - 2);
}
// Output
Console.WriteLine("Entered persons: " + inSameOrder);
Console.WriteLine("In reversed order: " + inReversedOrder);
// Waiting for Enter
Console.ReadLine();
}
讨论
请注意以下几点:
-
您使用
Trim
方法来切断输入文本可能的前导或尾随空格,以便允许终止任何空白输入,包括几个空格。 -
最后,你必须去掉两个累积文本中的最后两个字符。在此之前,您需要测试这两个字符(一个逗号和一个空格)是否出现在文本的末尾。如果用户通过输入空行立即终止程序,这些字符将不会出现。
-
要测试文本是否以某个东西结尾,可以使用
EndsWith
方法。
摘要
循环最常见的用途之一是处理大量数据,无论是数字、文本还是整个对象。在循环体中,您处理单个数据,而循环确定所有数据都得到处理。
您通过求和、相乘和求极值的例子练习了处理大量数据。
最困难的练习是找出第二大数字,这需要仔细考虑根据数据可能出现的情况。
上一个任务向你展示了几种处理文本的方法:Trim
、EndsWith
和Remove
。
二十五、高级循环
在本章中,您将完成简单循环的主题。这在“不嵌套”的意义上是简单的,而不是“琐碎的”没有循环是微不足道的,尤其是本章中的循环。
这一章和整本书将以一个奖励结束:一个登月模拟游戏。如果你觉得本章的练习太难,就只玩游戏。
感谢上帝,今天是星期五
是时候熟悉一下while
循环了,它是您已经熟悉的do-while
循环的表亲。
工作
你将准备一个程序,显示最近的星期五的日期和剩余天数(见图 25-1 )。
图 25-1
显示最近的星期五
解决办法
代码如下:
static void Main(string[] args)
{
// Today's date
DateTime today = DateTime.Today;
// Moving day after day until hit on Friday
DateTime date = today;
while (date.DayOfWeek != DayOfWeek.Friday)
{
date = date.AddDays(1);
}
// Calculating remaining days
TimeSpan dateDifference = date - today;
int daysRemaining = dateDifference.Days;
// Outputs
Console.WriteLine("Nearest Friday: " + date.ToShortDateString());
Console.WriteLine("Remaining days: " + daysRemaining.ToString());
if (daysRemaining == 0)
{
Console.WriteLine("Thanks God!");
}
// Waiting for Enter
Console.ReadLine();
}
讨论
让我们更仔细地看看这个程序。
While 循环
要编写循环,您可以使用while
构造,它在功能上类似于do-while
,除了它的条件是在开始。因此,在进入循环之前,第一次对条件进行评估,如果条件不成立,循环体一次也不执行!
这个案子
在进入循环之前测试条件正是你需要做的。如果今天是星期五,您想让它保持原样。否则,你就是在加一天。
TimeSpan 对象
当你减去两个日期时,产生的结果总是一个TimeSpan
对象。它的Days
属性表示这两个日期之间的“时间跨度”已经过去了多少天。
力量
循环是数学练习中经常用到的。
工作
您将编写一个程序,在给定小数 x 和正整数 n 的情况下,计算数字 x 的 n 次方(参见图 25-2 )。
图 25-2
计算 n 次方
只是提醒一下,210= 2×2×2×2×2×2×2×2 = 1024,这是最终产品中重复 10 次的数字 2。
解决办法
该任务可以通过重复乘以 x 来解决。这意味着你使用你已经学会的中间结果累积方法。
原则上,该解决方案与第二十四章“输入数字的乘积”一节中的解决方案非常接近:
static void Main(string[] args)
{
// Inputs
Console.Write("Enter x (number to be raised): ");
string inputX = Console.ReadLine();
double x = Convert.ToDouble(inputX);
Console.Write("Enter n (power): ");
string inputN = Console.ReadLine();
int n = Convert.ToInt32(inputN);
// Calculating
double result = 1;
for (int count = 0; count < n; count++)
{
result *= x;
}
// Output
Console.WriteLine("x^n=" + result.ToString());
// Waiting for Enter
Console.ReadLine();
}
正弦
继续数学,你知道计算机实际上是如何进行计算的吗,例如,正弦函数?如果你对数学感兴趣,你可能会对它感兴趣。
为了完成这个任务,你可以使用所谓的泰勒展开式。有聪明人发现正弦函数在给定点 x ( x 是以弧度为单位的角度)的值可以计算成无穷级数的和:
)
工作
现在的任务是编写一个程序,计算这个数列的和,并将结果与现成方法Math.Sin
的值进行比较(见图 25-3 )。
图 25-3
计算正弦
如果您对如何计算正弦函数的值不感兴趣,请将此任务作为编写更难的循环的挑战。
分析
首先,你要分析计算。
无穷级数
要求和的级数是无穷的,但是你可能想知道如何对无穷多个数求和。
当然,你不能这样做。诀窍在于,您实际上不需要对该系列的无限个成员求和。在某一点上,它们变得如此之小,以至于它们的贡献远远落后于小数点。
出于实际原因,你只需要一个确定的精度,比如说 15 位小数;double
型反正容纳不下更多的地方。因此,只要系列成员在小数点后第 15 位大于 1,就可以计算总和。
系列成员
所有的系列成员都是相似的。他们有一个奇数幂,奇数阶乘,和一个变化的符号。
提醒你一下,7!= 1 × 2 × 3 × … × 7.换句话说,阶乘是从 1 到给定数字的所有数字的乘积。
阶乘
可以用类似于本章前面计算幂的方法来计算阶乘,换句话说,就是在一个循环中逐步将所有的数相乘。
然而,你可以用更聪明的方法来做。你不需要从头开始计算每个阶乘。你总是可以从之前计算的结果中更快地得到它。
比如 7!= 7 × 6 × 5!。7 的阶乘可以通过 5 的阶乘乘以“缺失数”6 和 7 来计算。
力量
可以使用类似的技巧来计算每个串联成员的“功率部分”。功率不必从头开始计算。下一次幂就是上一次幂乘以 x 的平方。
比如 x7= x5×x2。
解决办法
解决方案如下:
static void Main(string[] args)
{
// Input
Console.Write("Enter an angle in degrees: ");
string input = Console.ReadLine();
double angle = Convert.ToInt32(input);
// Converting to radians
double x = Math.PI / 180 * angle;
// Preparations
double member;
double sum = 0;
double tinyValue = 1e-15;
double sign = 1;
double power = x;
double factorial = 1;
double multiplier = 1;
// Sum of the series
do
{
// Calculating current member of the series
member = sign * power / factorial;
// Appending to sum
sum += member;
// Preparing next step
sign *= -1;
multiplier++;
factorial *= multiplier;
multiplier++;
factorial *= multiplier;
power *= x * x;
} while (Math.Abs(member) > tinyValue);
// Output
Console.WriteLine("Our value: " + sum.ToString());
Console.WriteLine("Math.Sin: " + Math.Sin(x).ToString());
// Waiting for Enter
Console.ReadLine();
}
提高
您可以利用这个事实使计算更好,即对于 0 附近的 x 的值,级数收敛最快。使用正弦函数对称性,对大值 x 的计算可以转换成小值 x 的计算。
我会认为微软在Math.Sin
的代码里有这一招。
月球登陆
自从阿波罗 11 号登月以来,创建登月舱着陆的模拟已经在各种编程平台上流行起来。所以,你要写一个类似的模拟作为本书的总结任务。
工作
你将编写一个模拟登月的程序。它将跟踪模块的高度 h ,月球表面,模块的速度 v ,以及着陆剩余燃料的质量 m F 。
用户的任务是软着陆(以尽可能小的速度)。在每一步中,代表着陆动作的一秒钟,用户根据百分比输入应该施加多少制动。百分比越高,速度越低,但同时消耗的燃料越多,如图 25-4 所示。
图 25-4
登月计划
一旦高度降到零度以下,登月舱就着陆了。程序通知用户着陆速度,并根据下表进行评估:
|着陆速度
|
估价
|
| — | — |
| 小于 4 米/秒 | “软着陆” |
| 4-8 米/秒 | 硬着陆 |
| 大于 8 米/秒 | † |
如果在着陆结束前所有的燃油都消耗完了,程序开始忽略输入的刹车值,制动力设置为零。
物理模型
该计划将基于这里讨论的现实模型。
以下是初始值:
-
h = 50(米)
-
v = 8(米/秒)
-
mF= 35(公斤)
在代表着陆机动的一秒钟的每一步中,被跟踪的物理量的值将根据以下关系变化(意味着相应量的变化,如物理学中通常的那样):
h=–v–a/2
v = a
mF=–F/3000
在哪里
-
制动力为制动百分比的 F = 360 × 。
-
朝向表面的加速度为a= 1.62-F/8000。
解决办法
代码如下:
static void Main(string[] args)
{
// Initial values
double h = 50, v = 8, mF = 35;
// Preparation
bool malfunction = false;
// Repeating individual landing steps
while (h >= 0)
{
// Displaying current values
string height = "Height: " + h.ToString("N1");
string velocity = "Velocity: " + v.ToString("N1");
string fuel = "Fuel: " + mF.ToString("N1");
Console.WriteLine(height + " " + velocity + " " + fuel);
// Input
Console.Write("Enter percentage of breaking (0-100): ");
string input = Console.ReadLine();
double percents = 0;
try
{
percents = Convert.ToDouble(input);
if (percents < 0 || percents > 100)
{
malfunction = true;
}
}
catch (Exception)
{
malfunction = true;
}
if (malfunction)
{
percents = 0;
Console.WriteLine("CONTROL MALFUNCTION!");
}
// Fuel check
if (mF <= 0)
{
percents = 0;
Console.WriteLine("NO FUEL!");
}
// Calculating new values
double F = 360 * percents;
double a = 1.62 - F / 8000;
h -= v + a / 2;
v += a;
mF -= F / 3000;
if (mF <= 0)
{
mF = 0;
}
// Output of an empty line
Console.WriteLine();
} // End of a single landing step
// Output
Console.WriteLine("Landing velocity: " + v.ToString("N1"));
string evaluation = v < 4 ?
"Soft landing, congratulations!" :
(v <= 8 ? "Hard landing." : "Houston, crew is lost...");
Console.WriteLine(evaluation);
// Waiting for Enter
Console.ReadLine();
}
摘要
本章以几个可能被认为是高级循环的例子来结束本书。
第一个练习可能是最容易的。它让你熟悉了while
循环,这是你已经熟悉的do-while
循环的近亲。唯一的区别是while
循环在开始时就有它的条件,这意味着在第一次进入循环之前,它已经被求值了。随后,如果条件在开始时不评估为true
,循环体将永远不会被执行。
这正是你所需要的。如果今天是星期五,你一次也不想执行循环体(多移动一天);你想和星期五在一起。
下一个任务把你转移到数学领域。您练习了重复乘法和渐进结果累加,以获得指定数字的 n 次方。
正弦任务可能是整本书中最难的一个。我在这里把它作为对有数学头脑的读者的奖励。你看到了计算机如何计算所谓的超越数学函数的值。
正弦值可以使用无限泰勒级数来计算。考虑到计算机中十进制数的有限精度,技巧是在有限个成员变得太小而无法给最终结果添加任何内容时截断序列。
该解决方案还向您展示了一些加快计算速度的技巧。您使用以前的系列成员来有效地计算下一个系列成员。
最终的登月任务结合了你在整本书中学到的许多东西,是一个你可以享受的轻松游戏!
个人笔记
既然这本书已经接近尾声,请允许我说几句个人的话。编程不仅仅是关于计算机、关键词和算法思维。对我来说,这是一生的激情和个人。
骰子
我已经注意到这本书里有很多模拟掷骰子的练习,因为我小时候玩过很多棋盘游戏。这些不仅仅是从商店购买的游戏。那时我发明了许多自己的游戏,其中大部分都是模拟体育赛事的。短跑、长跑、跳跃、自行车赛、足球等等都有不同的规则。这可能是成为一名程序员的良好准备。
正弦任务
我承认这一章的正弦任务大大超出了初学者的水平。我把它包括在内,是为了让你对你在编程这个奇妙的领域中可能的未来有所了解。
对我来说,这项任务也与个人有关。在我上学的某个时候,我想知道计算器是如何计算正弦的。我在想,函数值在计算器中被制成表格(“硬连线”)并进一步插值。后来我才发现另一条路,你看到的那条。
月球登陆
登月任务的简化版其实是我第一次接触编程。不,我没有编程;我是它的电脑。
当我年轻的时候,我读过一本杂志的特刊,向像我这样的年轻人解释编程。这期杂志里有一台纸电脑。那是一张写有代表变量的窗口的纸。在这些窗口中,你可以拉出纸条,在上面写下变量的值。给变量赋值?您只需拉动纸条隐藏旧值,并用铅笔在同一纸条上写下新值。
我用电子计算器完成了所有的计算。我正在执行程序的语句,我是计算机的 CPU,以 0.5 赫兹的惊人速度运行(是的,G 是故意省略的),使用巧克力棒可以提高到 0.6 赫兹。
那时,也就是 1982 年在捷克斯洛伐克,我登陆了那个登月舱可能有几百次,后来还在一个可编程计算器上使用了我的软件。也许我是当时最有经验的宇航员。不管怎样,这是一条非常激励人的编程之路。
最后的愿望
从宇宙的角度来看,我希望我已经把你带入了你自己的编程轨道。有时,我讲得有点深,所以也许你会喜欢回到练习题上来,把书看几遍。这是给你的宇宙站补充补给的一种方式。
我祝你在未来的编程中获得更多快乐和成功!