C# 循环结构

循环结构用于进行重复的步骤。循环也是程序的强大之处。

while循环

语法

while(表达式)
{
    语句
}

表达式是一个返回true/false的表达式,如果为true,则执行语句,然后再次判断表达式,否则结束循环。

无限循环

while(true){
    Console.WriteLine(DateTime.Now);
}

上面的语句会无限输出当前时间。

一般在循环中,需要提供结束循环的条件,否则程序会进入无限循环,无法执行后续的代码。

循环一定次数

int n = 0;
while(n<10){
    Console.WriteLine(n);
    n++;
}

上面的代码会输出0到9。当n为10的时候,不符合继续循环的条件,不再执行循环。

do while 循环

语法

do
{
    语句
}
while(表达式)

执行语句,然后判断表达式是否为true,如果为true,再次执行语句,否则结束循环。

和`while循环相比,do while循环中的语句至少执行1次,而while循环可能一次不执行。 如果符合至少执行1次这个条件,则使用do while循环更好。

可以看到,语句是重复部分,如果是多个语句,则do while会更简洁。

循环一定次数

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

for 循环

语法

for(初始化表达式;表达式;循环表达式)
{
    语句
}

先执行初始化表达式,再判断表达式,如果为true,则执行语句,再执行循环表达式,再次执行判断表达式,否则结束循环。 for中的3个表达式都可以为空,都为空就是无限循环。 for循环也可能一次都不执行。

循环一定次数

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

初始化表达式中定义的变量在循环外无法使用,如果要定义循环外可以使用的变量,需要将初始化表达式放到循环外面。

循环外使用变量

int i=0;
for(;i<10;i++){
    Console.WriteLine(i);
}
//这里可以使用变量i,值是10

foreach 循环

可以使用foreach循环访问集合类型的元素。

语法:

foreach(类型 元素 in 集合){

}

其中集合是一个实现迭代器IEnumerable,IEnumerable<T>接口的类型,包括数组,列表,字典。 元素是当前元素,这个元素只能用来读,不能进行赋值。

使用foreach输出数组元素

List<int> values = new List<int>{1,2,3,4,5};
foreach(int value in values){
    Console.WriteLine(value);
}

在内部,foreach相当于下面的代码

List<int> values = new List<int>{1,2,3,4,5};

IEnumerator enumerator = values.GetEnumerator();
int value;

try
{
    while(enumerator.MoveNext())
    {
        value = enumerator.Current;
        Console.WriteLine(value);
    }
}
finally
{
    IDisposable disposable = enumerator as IDisposable;
    if(disposable!=null)
    {
        disposable.Dispose();
    }
}

foreach就是C#对集合访问的一个语法糖。

编译器对数组的foreach有优化,使用的还是for(int i=0;i<array.Length;i++)

goto 循环

通过标签名+:,可以为语句定义标签。 goto 可以跳转到的标签。配合选择语句,可以实现循环。

goto的while循环

标签:
    if(表达式){
        语句
        goto 标签;
    }

goto的do while循环

标签:
    语句
    if(表达式){
        goto 标签;
    }

goto的for循环

    初始化表达式
标签:
    if(表达式){
        语句
        循环表达式
        goto 标签;
    }

goto的功能十分强大,但一般我们不使用goto,因为其可以任意跳转,令代码结构混乱。

循环一定次数

int i=0;
repeat:
    if(i<10){
        Console.WriteLine(i);
        i++;
        goto repeat;
    }

break 提前跳出循环

在循环语句中,可以通过break,提前跳出循环。

提前结束循环

int i=0;
while(true){
    Console.WriteLine(i);
    i++;
    if(i>=10){
        break;
    }
}

一般的,通过break跳出的循环,都可以不使用break。建议不要滥用,导致循环有过多的跳出点,使结构混乱。

break 只能跳出本层循环,不能跳出嵌套循环。

continue 提前跳出本次循环

在循环语句中,可以通过continue,提前跳出本次循环。

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

输出1 3 5 7 9。

在使用continue的地方,也可以不使用continue。建议不要滥用,continue会使代码有跳出点,是结构混乱。

continue只能提前跳出本级循环,不能跳出嵌套循环。

嵌套循环

在一个循环内部,还可以再使用循环语句。

输出乘法口诀表

for (int i = 1; i <= 9; i++)
{
    for (int j = 1; j <= i; j++)
    {
        Console.Write("{0}*{1}={2}\t", i, j, i * j);
    }
    Console.WriteLine();
}

一般的,在一个方法中循环嵌套不超过3层,超过3层说法方法过于复杂,建议通过重构到另一个辅助方法。

递归

在一个方法中,再次直接或间接调用本身,叫做递归。

无限递归

static void F(){
    F();
}

static void Main(string[] args){
    F();
}

上面的方法再被调用时,会抛出StackOverFlowException异常。

因为函数调用会使用栈空间,而栈空间是有限的,无限制的递归会耗光栈空间。

有限的递归

static void F(int i)
{
    if (i < 10)
    {
        Console.WriteLine(i);
        F(i + 1);
    }
}

static void Main(string[] args){
    F(0);
}

输出0到9。

如果觉得递归难以理解,可以使用单步调试,看看递归的执行步骤。

递归一般分为递推和回归部分,递推部分重复调用自身,回归是不再符合递归条件后,函数返回部分。

递归与循环的选择

能够使用循环解决问题时,优先选择循环。

使用递归更符合思维,使用递归。

对效率要求高时,使用栈数据结构将递归转换为循环。