目录
先来简单了解下迭代语句吧:迭代语句重复执行语句或语句块。
1.for语句
1.1 使用
-
结构:
for (初始化; 条件; 迭代器) { 循环体 } -
作用:在指定条件为真时重复执行代码。
-
初始化:在循环开始前执行一次,通常用于声明并初始化循环变量(如
int i = 0) -
注意:在初始化部分声明的变量作用域仅限于循环内部。
-
条件:一个布尔表达式(如
i < 3),在每次迭代前检查。如果为true,则执行循环体;否则退出循环。如果省略条件,则视为无限循环,示例:for ( ; ; ) { } -
迭代器:在每次循环体执行后执行,通常用于更新循环变量(如
i++)。 -
举个例子吧:
for (int i = 0; i < 3; i++)
{
Console.Write(i);
}

1.2 具体执行步骤
1.初始化:int i = 0
2.条件检查(每次迭代前执行):i < 3 ----> 0 < 3 ----> true;
3.循环体执行:Console.Write(i); //输出0
4.迭代器操作(每次循环体后执行):i++ ----> 等价于 i = i + 1 ----> i = 0 + 1;
2.条件检查(每次迭代前执行):i < 3 ----> 1 < 3 ----> true;
3.循环体执行:Console.Write(i); //输出1
4.迭代器操作(每次循环体后执行):i++ ----> 等价于 i = i + 1 ----> i = 1 + 1;
2.条件检查(每次迭代前执行):i < 3 ----> 2 < 3 ----> true;
3.循环体执行:Console.Write(i); //输出2
4.迭代器操作(每次循环体后执行):i++ ----> 等价于 i = i + 1 ----> i = 2 + 1;
2.条件检查(每次迭代前执行):i < 3 ----> 3 < 3 ----> false;循环终止
1.3 迭代器
迭代器部分可以包含零个或多个语句表达式,用逗号分隔。比较常用的表达式如下:
(1)前缀或后缀递增表达式,例如 ++i 或 i++
for (int i = 0; i < 3; ++i) // 前缀递增
{
Console.Write(i);
}

补充:前缀(如 ++i)和后缀(如 i++)的区别
独立语句(效果相同)
当递增操作是独立的语句时,两种写法效果完全一致:
i++; // 相当于 i = i + 1 ++i; // 也相当于 i = i + 1
在表达式中使用(关键区别)
当它们出现在表达式中时:
- 前缀形式(++i):先执行递增,然后返回递增后的值
- 后缀形式(i++):先返回当前值,然后执行递增
举个例子吧:


🎯 核心区别
- 前缀递增:先改变变量的值,再使用它
- 后缀递增:先使用变量的值,再改变它
(2)前缀或后缀递减表达式,例如 --i 或 i--
// 递减操作
for (int i = 5; i > 0; i--)
{
Console.Write(i);
}

(3)赋值表达式(如 x = 5)
for (int i = 1; i < 8; i *= 2) // 每次翻倍
{
Console.Write(i);
}

(4)方法调用(如 PrintSeparator())

补充:(了解即可)
特殊用法:初始化和迭代器部分可以包含多个表达式(用逗号分隔),例如:
int i;
int j = 3;
for (i = 0, Console.WriteLine($"Start: i={i}, j={j}"); i < j; i++, j--, Console.WriteLine($"Step: i={i}, j={j}"))
{
}

2.foreach语句
- 作用:遍历集合中的每个元素(数组、列表等)。
- 语法:
foreach (var item in collection) { // 处理 item } - 示例:
补充:List<int>
List= 列表(类似一个盒子),<int>= 盒子专门装整数numbers是给这个盒子起的名字new表示创建一个新的盒子,括号()表示创建一个空盒子- 大括号
{ }表示盒子里的内容;1, 2, 3是放进去的三个数字
List<int> numbers = new() { 1, 2, 3 };
foreach (int num in numbers)
{
Console.Write(num);
}

👉 相当于:
从 numbers 中取出第一个数字(0) → 赋值给 num→ 执行代码
再取第二个数字(1) → 赋值给 num→ 执行代码
...... 直到最后一个数字(3)
2.1 适用类型
只要满足以下两点就能使用:
(1)集合类型有 GetEnumerator() 方法
(2)这个方法返回的对象包含:Current 属性 和 MoveNext() 方法
我来用通俗一点的方式解释一下:
想象你有一本书(这就是集合),foreach 想帮你一页一页地读这本书:
阅读这本书,foreach 需要三件工具:
(1)书签,用来记住你读到哪一页了(GetEnumerator() 方法)
(2)下一页检查器(MoveNext() 方法),检查还有下一页吗?
返回结果:true → 还有下一页;
false → 这是最后一页了
(3)当前页阅读器(Current 属性),这是你的"放大镜",用来读取当前页的内容
2.2 修改元素的值(ref用法)
在C#中,默认情况下,foreach循环中的迭代变量是集合中元素的副本。
这意味着修改迭代变量不会影响原始集合中的元素。
// 创建一个整数数组
int[] numbers = { 1, 2, 3, 4, 5 };
// 尝试在foreach循环中修改元素
foreach (int number in numbers)
{
number = number * 2; // 错误!因为number是只读的,不能修改
}
但是,如果集合支持直接通过引用访问元素,则可以使用ref关键字来修改原始集合中的元素。
注意:这里使用ref关键字声明迭代变量,但数组的foreach并不直接支持ref,但是Span<T> 支持。
2.3 Span<T>
Span<T>是 C#引入的一种内存安全、高性能的数据视图类型,特别适合处理内存连续的数据块,允许你高效地操作内存而不需要复制数据。
创建 Span<T> 的常用方式是从数组创建
补充:
int= 整数,[]= 表示这是一个数组numbers是起的名字- 大括号
{ }表示内容;1, 2, 3, 4, 5是放进去的五个数字
int[] numbers = {1, 2, 3, 4, 5};
// 创建指向整个数组的 Span
Span<int> fullSpan = numbers.AsSpan();
// 创建部分数组的 Span (索引2开始,取3个元素)
Span<int> partialSpan = numbers.AsSpan(2, 3);
int[] numbers = { 1, 2, 3, 4, 5 };
// 正确示例:使用Span<T>(Span支持通过ref修改)
Span<int> numbersSpan = numbers.AsSpan(); // 将数组转换为Span
foreach (ref int number in numbersSpan)
{
number *= 2; // 直接修改原始数组中的元素
}
foreach (var number in numbers)
{
Console.Write(number + " "); // 输出: 2 4 6 8 10
}
2.4 只读访问
对于大型结构体,用 ref readonly 提升性能(避免复制开销):
stackalloc int[10]
stackalloc:在栈(stack)上分配内存int[10]:创建包含 10 个整数的连续内存块- 相当于:开辟一块能存放 10 个整数的内存空间
Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
item = num++;//修改值
}
foreach (ref readonly var item in storage)
{
Console.Write($"{item} "); // 结果:0 1 2 3 4 5 6 7 8 9
}
那就再多补充一点吧:
传统写法 (堆分配)
int[] array = new int[10]; // 在堆上分配
高效写法 (栈分配)
Span<int> storage = stackalloc int[10]; // 在栈上分配
| 特点 | 传统 int[] | stackalloc + Span |
|---|---|---|
| 内存位置 | 托管堆(Heap) | 栈(Stack) |
| 分配速度 | 慢(需要GC参与) | 极快(栈指针移动) |
| 释放方式 | 等待垃圾回收(GC) | 自动随栈帧弹出 |
| 内存安全 | 普通安全检查 | 有边界检查的Span |
| 适用场景 | 长期存在的大数据 | 短生命周期的小数据 |
⚠️ 重要限制与特性:
自动释放;大小限制(栈空间有限,通常约1MB);必须声明为Span;
值类型专用:只能分配值类型(int, double, struct等),不能分配引用类型(如string, class)
这种栈分配的缓存只适用于小型临时数据,大块数据仍需使用堆分配!
2.5 空集合和 null
| 情况 | 结果 | 示例 |
|---|---|---|
| 空集合 | 循环体不执行 | new int[0] |
| null 集合 | 抛出异常 | null |
空集合的例子:
int[] emptyNumbers = new int[0];
foreach (int num in emptyNumbers)
{
Console.Write(num); // 永远执行不到这里
}
null集合的例子:
int[]? nullNumbers = null;
foreach (int num in nullNumbers) //尝试访问不存在的集合,会报错
{
Console.Write(num);
}
解释:空集合就像是猫粮袋子里面是空的,但是你可以打开;null集合就像猫粮袋子被偷走了,你又要打开猫粮袋子,所以会报错(因为你根本没有)
3.do语句
- 特点:先执行循环体,然后检查条件(条件为真则继续循环)。
- 确保循环体至少执行一次。
- 语法:
do { // 循环体 } while (条件);
举个例子:
int n = 0;
do
{
Console.Write(n);
n++;
} while (n < 5);

执行步骤:
- 初始值
n = 0 - 第一次循环:输出
0→n变为1→ 检查条件1 < 5为真 → 继续 - 第二次循环:输出
1→n变为2→ 检查条件2 < 5为真 → 继续 - 第三次循环:输出
2→n变为3→ 检查条件3 < 5为真 → 继续 - 第四次循环:输出
3→n变为4→ 检查条件4 < 5为真 → 继续 - 第五次循环:输出
4→n变为5→ 检查条件5 < 5为假 → 循环结束
👉 特点:即使一开始就不满足条件,也会执行一次

4.while循环
- 特点:先检查条件,条件为真才执行循环体。
- 可能一次都不执行(如果初始条件为假)。
- 语法:
while (条件) { // 循环体 }
举个例子:
int n = 0;
while (n < 5)
{
Console.Write(n);
n++;
}

执行步骤:
- 初始值
n = 0 - 检查条件
0 < 5为真 → 执行循环体:输出0→n变为1 - 检查条件
1 < 5为真 → 执行循环体:输出1→n变为2 - 检查条件
2 < 5为真 → 执行循环体:输出2→n变为3 - 检查条件
3 < 5为真 → 执行循环体:输出3→n变为4 - 检查条件
4 < 5为真 → 执行循环体:输出4→n变为5 - 检查条件
5 < 5为假 → 循环结束
👉 特点:只有条件为真时才会执行

在迭代语句正文中的任何时间点,可以使用该break语句中断循环。 可以使用continue语句跳至循环中的下一次迭代。
5.break语句
- 作用:立即终止当前所在的整个循环
- 特性:完全结束循环,不再进行后续任何迭代
举例:找到32了!就停止循环,不再看后续的数字了。
int[] numbers = { 2, 8, 15, 32, 64, 128 };
foreach (int num in numbers)
{
if (num == 32)
{
Console.WriteLine("over");
break;
}
Console.WriteLine(num);
}

6.continue语句
- 作用:跳过本次循环剩余的代码,直接开始下一次迭代
- 特性:仅终止当前这次迭代,循环会继续执行
举个例子吧:
for (int i = 1; i <= 10; i++)
{
// 如果是偶数,跳过
if (i % 2 == 0)
{
Console.WriteLine("double");
continue; // 跳到下一轮
}
Console.WriteLine(i);
}

现在进行一个总结:
| 类型 | 执行次数 | 特点 |
|---|---|---|
for | 0-N 次 | 精确控制循环变量 |
foreach | 集合元素数量 | 简化集合遍历,只读默认 |
do | 至少 1 次 | 先执行后判断 |
while | 0-N 次 | 先判断后执行 |
学到了这里,咱俩真棒,记得按时吃饭(周末的快乐时光总是很短暂)
【本篇结束,新的知识会不定时补充】
感谢你的阅读!如果内容有帮助,欢迎 点赞❤️ + 收藏⭐ + 关注 支持! 😊

被折叠的 条评论
为什么被折叠?



