本文主要来自<<C#实践入门>>哈里森.费隆 著,仅用为做笔记。
本章将专注以下主题:
- 选择语句。
- 使用数组(Array)、字典(Dictionary)和列表(List)。
- 使用for、foreach和do-while 循环进行选代。
- 使用break、continue和return 关键字执行控制
- 理解C#枚举(Enumeration)。
选择语句
最复杂的编程问题通常可以归结为对游戏或程序进行评估并执行的一系列简单选择。。因为 Visual Studio和Unity 无法自己做出这些选择,所以这些决定要由我们来做。通过使用ifelse和 switch 选择语句,我们可以基于一个或多个条件以及每种情况下将要执行的操作来指定分支路径。
传统上,这些条件包括:
- 检测用户输入。
- 计算表达式和布尔逻辑
- 比较变量或字面值。
if-else语句
使用if-else 语句是在代码中进行决策的最常见方式。抛开语法不讲,if-else的基本思想是:如果条件满足,就执行一段代码;如果不满足,就执行另一段代可以将这些语句视为以条件为钥匙的门。为了顺利通过,条件必须是有效的:否绝通过,代码将被发送到下一个可能的门。
1.基本语法
有效的if-else 语句需要具备以下条件:
- 行首的if关键字。
- 用于存储条件的一对括号。
- 用于存储代码块的一对花括号。
- 带有自己的花括号和代码块的 else 关键字(可选)
语法如下:
if(condition is true) { Execute this code block } else { Execute this code block }
因为这些都是对逻辑思维的很好介绍,至少在编程中是这样,所以我们将更详细地分析三种不同的ifelse 变体。
1.如果不关心条件没有满足时会发生什么,那么只使用 if语句即可。在图41中如果hasDungeonKey为tue,就输出调试日志;如果为 false,则不执行任何代码。
public class LearningCurve : MonoBehaviour { public bool hasDungeonKey = true; // Use this for initialization void Start() { if(hasDungeonKey) { Debug.Log("You possess the sacred key - enter."); } } }
2.在无论条件满足与否都需要采取措施的情况下,可以添加 else 语句。如果hasDungeonKey为false,f语句将失败,并且控制流将跳至else 语句,如图42所示
public class LearningCurve : MonoBehaviour { public bool hasDungeonKey = true; // Use this for initialization void Start() { if(hasDungeonKey) { Debug.Log("You possess the sacred key - enter."); } else { Debug.Log("You have not proved yourself worthy,warrion"): } } }
3. 对于需要两个以上可能结果的情况,可以添加带有括号、条件和花括号的else-if语句。这会是最好的展示而不是解释,我们将在后面介绍。
实践一一小偷的预期
下面编写一条 ifelse 语句,用于检查口袋中的金额,并针对三种不同的情况返回不同的调试日志。
(1)打开 LearningCurve 脚本并添加一个名为 currentGold 的int变量,将值设置为一个介于1和100之间的整数。
(2)添加f语句来检查 currentGold 是否大于50。如果大于,就向控制台打印一条消息。
(3)添加else-if语句来检查 currentGold 是否小于15。如果小于,就打印另一条不同的调试日志。
(4)添加不带任何条件的else 语句和想要打印的默认日志。
(5)如图4-3 所示保存文件并单击 Play 按钮。
public class LearningCurve:MonoBehaviour { public Int currentGold = 32; // Use this for initialization void Start() { if(currentGold >50) { Debug.Log("You're rolling in it - beware of pickpockets."); } else if(currentGold < 15) { Debug.Log("Not much there to steal."); } else { Debug.Log("Looks like your purse is in the sweet spot.“); } } }
这里将currenGold 设置为 32,我们可以按以下方式分解代码。
因为curenrGold 不大于50,所以跳过if语句及其调试日志。
因为currentGold 不小于15,所以也跳过else-if语句及其调试日志
由于之前的条件都不满足,因此执行 else 语句并显示默认日志。
2.使用逻辑非运算符
用例并不总是需要检查条件为 tue 的情况,而这正是逻辑非运算符出现的原因。逻辑非运算符允许我们检查迁或 else-if 语句满足条件为false 时的情况。如前所述,我们可以在 f条件下检查布尔值、字面值或表达式,所以很自然地,逻辑非运算符必须具有适应性。
public class LearningCurve : MonoBehaviour { public bool hasDungeonKey = false; public string weaponType =“Arcane Staff"; // Use this for initialization void Start() { if(!hasDungeonKey) { Debug.Log("You may not enter without the sacred keyl."); } if(weaponType !="Longsword") { Debug.Log("You don't appear to have the right type of weapon..."); } } }
- 第一条if语句可以解释为:如果hasDungeonKey 为false,那么if语句的值为true,执行其代码块
- 第二条if语句可以解释为:如果 weaponType 的值不等于字符串"Longsword"那么执行其代码块。
- 看到调试结果,如果仍然不理解,那么可以复制前面的代码到LearningCurve 脚本中,运行这些变量,直到它们奏效。
3.嵌套语句
ifelse 语句最有价值的功能之一是它们可以相互嵌套,从而创建复杂的逻辑路线在编程中,我们称之为决策树。就像真实的走廊一样,其他门的后面也可以有门,像是迷宫。
首先观察图下的示例:
- 首先,第一条 if 语句检查 weaponEquipped 是否为 tue。这里只关心weaponEquipped 是否为true,而不管 weaponType 是什么。
- 接下来,第二条if语句检查 weaponType 并打印相关的调试日志
- 如果第一条if语句为 false,那么控制流转到 else 语句并打印相关的调试日志
- 如果第二条if 语句为 false,那么因为没有对应的 else 语句,因而不会打印任何内容。
public elass LearningCurve : MonoBehaviour { public bool weaponEquipped = true; public string weaponType ="Longsword"; void Start() { if(weaponEquipped) { if(weaponType==“Longsword") { Debug.Log("For the Queen!"); } } else { Debug.Log("Fists aren't going to work against armor..."); } } }
4.计算多个条件
除了套语句,还可以使用 AND 和 OR 逻辑运算符将多个条件检查组合到单个if或elseif语句中:
- AND逻辑运算符使用&&表示。使用AND 逻辑运算符意味着只有当i语句的所有条件都为tue时,if语句的值才为true。
- OR 逻辑运算符使用|表示。使用 OR 逻辑算符意味着只要语句有一个条件为true,if语句的值就为true。
if语句已更新为检查 weaponEquipped和weaponType,只有当它们两者都为true时才执行代码块。
if(weaponEquipped weapanType =="Longsword”) { Debug.Log("For the queen!“); }
实践一到达宝藏
下面进一步巩固所学知识。
(1)在 LearningCurve 脚本的顶部声明三个变量:pureOfHeart 是布尔值,应该true;hasSecretIncantation 还是布尔值,应该为false; rareltem 是字符串,值取决于你.
(2)创建一个名为OpenTreasureChamber 的没有返回值的 public 方法,并在 Start方法中调用。
(3)在OpenTreasureChamber 方法中声明 ifelse 语句来检查 pureOfHeart 是否为
true,并检查rareltem 是否和赋予的字符串匹配。
(4)在第一条f语句中创建套的 if-else 语句,检查hasSecretIncantation 是否为false。
(5)为每个if-else语句添加调试日志,保存后单击Play 按钮。
刚刚发生了什么
将会打印套的if 语句的调试日志,如图这意味着代码通过第一条 if 语句中的两个条件,但第二条if 语句中的条件没有通过
switch语句
嵌套过多的代码最终将难以理解,且很难修改。switch 语句能让我们为每种可能的结果编写代码,但格式相比 if-else 语句更为简洁。
1.基本语法
switch语句需要具备以下条件。
- switch 关键字,后跟随一对用来放置条件的括号
- 一对花括号。
- 以冒号结尾的每种可能情况的 case 子句:单独的代码行或方法,后跟 break 关键字和分号
- 默认的 default 子句以冒号结尾:单独的代码行或方法,后跟 break 关键字和分号
以蓝图的形式看起来语法如下:
switch (matohExpression) { case matchValvel: Executing code block break; case matchValue2 : Executing code block break; default: Executing code block break; }
在case子句中,冒号和break 关键字之间的任何内容都类似于ifelse语句中的代码块。break关键字用于告诉程序在选定的条件触发后完全退出switch语句。
2模式匹配
在switch语句中,模式匹配是 case 子句用来验证匹配表达式的方式。匹配表达式可以是不为null的任何类型。所有 case 子句的值都要对应匹配表达式的类型。例如,对于计算整型数的 switch 语句,每个 case 子句都需要指定一个整数来进行检查。与匹配表达式匹配的case 子句会被执行。如果没有匹配的case 子句,则执行 defaut 子句.
实践一选择动作
这里包含大量新的语法和信息,但 switch 语句有助于观察实际操作。下面让我们为游戏角色可以采取的不同操作创建一条简单的 switch 语句。
(1)创建一个名为 characterAction 的字符串类型的成员变量或局部变量,设置为"Attack"。
(2)声明一条 switch 语句,使用 characterAction 作为匹配表达式。
(3)创建两个 case 子句以打印不同的调试日志,并且不要忘记在每个 case 子句的末尾添加 break 关键字。
(4)添加带有调试日志的 default 子句和 break 关键字。
(5)保存脚本并在Unity 单击 Play 按钮,
public class LearningCurve : MonoBehaviour { void Start() { string characterAction =“Attack"; switch(characterAction) { case"Heal": Debug.Log("Potion sent,"); break; case"Attack": Debug.Log("To arms!"); break; default: Debug.Log("Shields up."); break; } } }
刚刚发生了什么
由于 characterAction 被设置为"Attack”,因此 switch 语句执行第二个 case 子句并打印相应的调试日志,更改 characterAction 为"Hea"或其他尚未定义的动作,以看到第一个 case 子句和 default 子句的执行情况。
3.fall-through
switch 语句能够在多种情况下执行相同的操作,类似于我们在一条f语句中指定多个条件。这种情况被称为fall-through。如果 case 子句没有代码块和 break 关键字那么控制流将直接进入下一个 case 子句。
实践一掷骰子
下面使用switch语和fall-through来模拟掷子游戏.
(1)创建一个名为diceRoll 的iint 变量,赋值为7
(2)声明switch语句并使用diceRoll作为匹配表达式
(3)为掷散添加3种可能的情况。
(4)当匹配表达式为15和20时,有相应的调试日志和break 关键字,为7时则直接进入掷散结果为15的case 子句。
(5)保存脚本并在Unity单击Play 按钮,
void Start() { int diceRoll=7; switch(diceRoll) { case 7: case 15: Debug.Log("Mediocre damage,not bad"); break; case 20: Debug.Log("Critical hit,the creature goes down!"); break; default: Debug.Log("you comletely missed and fell on your face."); break; } }
刚刚发生了什么
当把diceRoll设置为7时,switch 语句会匹配第一个case子句,但由于其中没有代码块和 break 关键字,因而进入并执行下一个case子句中的代码块,如图如果将 diceRoll 更改为15或20,那么控制台将显示对应的调试日志,对于其他任何值,则直接进入switch 语句末尾的 default 子句。
集合一览
到目前为止,我们只需要使用变量来存储单个值,但很多情况下我们需要存储一组值。此时,集合就派上用场了。C#中的集合类型包括数组(Array)、字典(Dictionary)和列表(List),它们各有优缺点。
数组
数组是 C#提供的最基本的集合。可以将数组视为一组值的容器,这些值在编程术语中被称为元素,每个值都可以单独访问或修改。
- 数组可以存储任何类型的值,但其中的所有元素都必须是同一类型.
- 数组的长度或元素个数是在创建时设置的,且之后不能再修改。
- 如果在创建时没有分配初始值的话,那么每个元素会使用默认值。存储数字类型的数组的默认值为0,而存储其他类型数据的数组的默认值为 null。
- 数组是 C#中最不灵活的集合类型,主要是因为元素在创建后不能添加或删除。不过,当需要存储不太可能改变的信息时,数组特别有用
1.基本语法
声明数组类似于声明我们之前使用的其他变量类型,但也有一些差别。
- 数组需要指定的元素类型、一对方括号和唯一的名称。
- new 关键字用于在内存中创建数组,后跟值的类型和另一对方括号
- 数组将要存储的元素数量则放在第二对方括号中
以蓝图的形式看起来语法如下:
elementType[] name = new elementType[numberOfElementsl;
举个例子,假设我们需要在游戏中存储得分的前三名:
int[] topPlayerScores = new int[3];
topPlayerScores 是存储了3个整型元素的整型数组,由于我们没有添加任何初始值,因此topPlayerScores 数组中的3个元素默认都为0
C#为此提供了两种有效的语法:普通语法和速记语法
// Longhand initializer int[] topPlayerScores = new int[] (713,549,984); // Shortcut initializer int[] topPlayerScores ={ 713,549,984 };
2.索引和下标
每个数组元素都按分配的顺序进行存储,这称为索引。数组元素从0开始索引,这意味着数组元素的顺序从0而不是1开始。可以将数组元素的索引看作引用或位置在 topPlayerScores 数组中,第1个整数452的索引为0,第2个整数713 的索引为1,第3个整数984的索引为2。
使用下标运算符还可以直接修改数组中的值,就像其他变量一样,甚至可以将自己作为表达式来传递:
topPlayerScores[1] = 1001; Debug.Log(topPlayerScores[1]);
topPlayerScores 数组中的值现在是452、1001和984。
3. 范围异常
在创建数组时,元素的数量是固定的,这意味着我们不能访问不存在的元素。对于topPlayerScores数组来说,数组长度为3,因此有效索引的范围是0-2。任何大于或等于 3的索引都将超出数组的索引范围,并在控制台中生成名为IndexOutOfRangeException 的调试日志。