跳至正文

第5章 使用复合赋值和循环语句

前一章中,我们讲到了如何使用 ifswitch 来选择性地运行语句。本章将要介绍如何使用各种循环语句重复运行一个或多个语句。写循环语句时经常需要控制循环的次数,因此往往需要一个变量来作为“循环变量”,在这些情况下,我们需要使用特殊的赋值操作符来更新变量值。

使用复合赋值操作符

我们之前讲过,可以使用算数操作符对值进行运算:

int a = 17;
Console.WriteLine(a + 1); //18

而我们往往会遇到需要将某个变量参与计算的表达式的值重新赋值给该变量,就像这样:

int a = 16;
a = a + 2;

这是有效的语句,但像这样更新变量的值是一个非常常见的操作,因此C#提供了专门的复合赋值操作符来简化它。+,-,*,/,% 都有对应的复合赋值操作符,且具有和赋值运算符相同的优先级和右结合性

使用复合赋值操作符的例子如下:

int a = 16;
a += 2; //a = a + 2;
a -= 3; //a = a - 3;
a *= 2; //a = a * 2;
a /= 2; //a = a / 2;
a %= 2; //a = a % 2;

此外,+= 还可用于 string 类型的操作数,它将另一个字符串附加到该字符串的末尾:

string text = "hello ";
text += "world!";
Console.WriteLine(text); // hello world!

不过其他的复合赋值操作符都不能用于字符串。

使用while语句

我们经常会遇到需要在满足一定条件时重复运行一个语句的情况。因此我们引入 while 语句,语法如下:

while (布尔表达式)
{
    //语句块
}
//和if一样,不加大括号就只能将 while 的下一行绑定为要循环执行的语句

例如,我们可以用下面的写法向控制台输出一连串的数字:

int i = 0;
while (i < 10)
{
    Console.WriteLine(i);
    i++;
    //当然,你也可以把i++和输出写在一起
}

注意,输出结果应为0~9而非0~10,因为 i 变成10时,while 语句便已终止,不再执行语句块。

while 循环的变量 i 控制循环次数。这是常见的设计模式,具有这个作用的变量往往称为哨兵变量。你还可以写出嵌套的循环,这种情况下一般延续该命名模式,用 jk 甚至是 l 作为哨兵变量名(当然,循环层数过多时就该考虑写法是否有问题了)。

如果忘记了控制循环变量,可能导致死循环,如下面的代码:

int i = 0;
while (i < 10)
    Console.WriteLine(i);
    i++;

这段代码将无限循环,无限向控制台输出0。由于大括号的缺失,i++ 并不被视为 while 循环主体的一部分,因此 i 永远不会更新,从而造成死循环。在实际编程中,往往需要设定合适的边界条件,避免死循环的出现。

使用for语句

for语句的用法

大多数 while 循环都遵循下面的常规结构:

初始化;
while (布尔表达式)
{
    执行语句;
    更新控制变量;
}

for 语句为这种结构提供了更正式的版本。它将初始化、布尔表达式和更新控制变量合并到一起,从而减少了遗漏初始化或更新代码的可能。for 语句的结构如下所示:

for (初始化; 布尔表达式; 更新控制变量)
{
    语句块;
}

注意初始化、布尔表达式和更新控制变量之间以分号隔开。例如,我们可以用这些代码重写之前的例子:

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(i);
}

使用 for 循环要牢记以下三点:

  • 初始化只执行一次。
  • 初始化后先执行循环主体语句,再更新控制变量。
  • 更新变量后重新求值布尔表达式。

不过 for 语句的三个部分都是可以省略的,比如省略布尔表达式可以得到一个死循环:

for (int i = 0; ;i++)
{
    Console.WriteLine("停不下来了啊啊啊啊啊啊啊啊啊啊");
}

也可以省略初始化和更新部分:

int i = 0;
for(; i < 10;)
{
    Console.WriteLine(i++);
    //最好不要这样写,可读性很差
}

省略时不能省略分号哦。

如果有必要,你还可以在 for 循环中提供多个初始化语句和多个更新语句,但布尔表达式只能有一个(当然,你可以使用逻辑运算符),例如下面的情况:

for (int i = 0, j = 10; i <= j; i++, j--)
{
    //...
}

for语句的作用域

for 语句的“初始化”部分声明的新变量,变量作用域限制于 for 循环的主体,for 循环结束,变量消失。该规则意味着,你不能在for 循环结束后使用循环变量:

for (int i = 0; i < 10; i++)
{
    //...
}
Console.WriteLine(i); //编译错误

此外,这还意味着你可以在两个或更多 for 语句中使用相同的变量名,因为每个变量都在不同作用域中,就像下面这样:

for (int i = 0; i < 10; i++)
{
    //...
}
for (int i = 0; i < 20; i += 2)
{
    //...
}

如果你需要在循环结束后使用循环变量,你可以这样写:

int i;
for (i = 0; i < 10; i++)
{
    //...
}
Console.WriteLine(i); //注意此时i为10而非9

使用do-while语句

whilefor 语句都在循环开始时求值布尔表达式。这意味着如果首次求值布尔表达式为 false,那么循环将一次都不运行。如果我们需要一个循环至少运行一次,我们可以使用 do 语句。

do 语句的语法如下(别忘了最后的分号!)

do
{
    语句块;
}
while (布尔表达式);

例如,我们采用短除法将一个正的十进制数转换成八进制的字符串形式,可以这样写:

int dec = int.Parse(Console.ReadLine()); // int.Parse将输入字符串转换为整数
string cur = "";
do
{
    int nextDigit = dec % 8;
    dec /= 8;
    char digit = Convert.ToChar('0' + nextDigit); // 字符类型的一些小技巧,如果不理解可以自己查找资料
    cur = $"{digit}{cur}"; // 我们使用字符串插值来连接字符串
}
while(dec != 0);
Console.WriteLine(cur);

上面的代码似乎有些复杂,你可以使用之前的调试技巧尝试理解上面的代码,并想一想为什么要使用 do

break和continue语句

在之前的章节中我们介绍过使用 break 语句跳出 switch 语句。在循环结构中,我们还可以使用 break 跳出循环。在循环主体中遇到 break 语句时,循环立刻终止,然后从循环之后的第一个语句继续执行。在这种情况下,循环的“更新”和“继续”条件都不会判断。

此外还有 continue 语句,它将本轮循环结束,在求值布尔表达式后进入下一轮循环

注意,你应该在合适的时候使用这两种语句,不要写出下面的Re-Logic风格奇怪的代码:

int i = 0;
while (true)
{
    Console.WriteLine("continue" + i);
    i++;
    if (i < 10)
        continue;
    else
        break;
}
// 第一眼很难直接看出循环的意图

相反,在模组中用循环遍历数组时,往往使用 continue 跳过空值:

// 可能会在mod编程中用到的一种写法
NPC npc;
for (int i = 0; i < Main.npc.Length; i++)
{
    if (Main.npc[i] == null) continue;
    //进行别的操作
}

或者遍历查找某个值时,我们可以用 break 结束循环:

int type = 114; //某种特定类型弹幕
int i; //为了能在循环结束时使用i,我们将i声明在for作用域外
for (i = 0; i < Main.projectile.Length; i++)
{
    if (Main.projectile[i].type == type) break;
    //可能的别的操作
}
Main.NewText(i);
//输出为第一个满足条件的弹幕的index

看不懂的话就去看看主教程吧~

下一章我们将开始介绍简单的异常处理,并结束第一部分。

标签:

《第5章 使用复合赋值和循环语句》有2个想法

发表回复