C# 语言入门(三)循环、封装、函数、可空类型、数组

本篇核心知识点:foreach 遍历、循环跳转关键字 (break/continue/return)、面向对象封装与访问修饰符、函数完整规范、函数参数传递(值传递 /ref/out)、函数重载、Lambda 匿名函数、闭包陷阱、可空类型与空合并运算符、一维 / 多维数组、Random 随机工具、洗牌算法

一、foreach 循环遍历

1. 概念

专门用于遍历数组、集合(List 等)的简化循环,直接取出容器内元素本身 ,不提供索引,等价于 TS/JS 的for of,区别于 TSfor in(取索引)。

特性

  1. 语法格式:foreach(元素类型 变量 in 容器)

  2. 仅只读遍历,遍历过程中不能修改容器长度(增删元素会报错);

  3. 无需手动定义循环变量、控制边界,代码简洁。

代码示例
复制代码
int[] arr = { 10, 20, 30, 40 };
// foreach遍历数组元素
foreach(int n in arr)
{
    Console.Write(n + " ");
}

拓展

需要获取下标时,改用普通for循环;集合批量只读遍历优先使用 foreach。

二、循环跳转关键字

1. continue

概念:

终止当前单次循环,直接进入下一轮循环判断。

特性:

仅跳出本轮剩余代码,外层循环不受影响。

2. break

概念:

直接跳出当前所在一层循环,外层循环继续执行。

特性:

多层嵌套循环仅退出最内层;如需一次性退出所有循环可用 return。

3. return

概念:

直接终止整个方法,所有循环同步结束,适合找到目标后直接返回结果。

代码示例(区分三者)

复制代码
for(int i = 1; i <= 10; i++)
{
    if(i == 3) continue; // 跳过3,继续下一轮
    if(i == 6) break;    // 循环直接终止
    Console.WriteLine(i);
}

三、面向对象封装 & 访问修饰符

3.1 封装概念

将类的数据与功能封装在类内部,隐藏内部实现细节,仅对外暴露安全访问入口,避免外部随意篡改成员。

3.2 四大访问修饰符(权限从大到小)

  1. public 公共

    概念:任何类、任何程序集均可访问;

    场景:对外暴露方法、属性。

  2. internal 程序集内

    概念:仅当前项目(程序集)内所有类可访问,跨项目无法调用;

    C++/Java 无该修饰符,C# 独有。

  3. protected 受保护

    概念:本类 + 派生子类可访问,外部普通类无权访问;

    场景:父类预留给子类复用的成员。

  4. private 私有(类默认权限)

    概念:仅当前类内部可访问,外部、子类都无法读写;

    规范:所有字段默认 private,通过 public 方法 / 属性操作。

特性

C# 类、成员默认访问权限为 private;TS 类默认 public,二者核心区别。

代码示例
复制代码
class Player
{
    private int hp;       // 私有,外部无法直接修改
    protected float speed;// 子类可访问
    public void Move()    // 公开对外方法
    {
        hp -= 10;
    }
}

四、函数(方法)完整规范

1. 函数组成四要素

返回类型 + 函数名 + 参数列表 + 函数体

  1. 返回类型:C# 不可省略,无返回写void(TS 可省略);

  2. 命名规范:大驼峰 Pascal 命名(首字母大写);

  3. 函数不能嵌套定义(方法内部不能再写独立函数,仅允许 Lambda);

  4. 文档注释///描述功能、参数、返回值,团队协作必备。

代码示例

复制代码
/// <summary>角色横向移动</summary>
/// <param name="speed">移动速度</param>
public void MoveX(float speed)
{
    x += speed;
}

2. 递归函数

概念:

函数内部调用自身,分治简化代码,必须设置递归终止条件,否则死循环栈溢出。

经典案例:

阶乘、斐波那契数列

复制代码
// 求n阶乘 n! = n*(n-1)!
int Factorial(int n)
{
    if(n == 1) return 1; // 终止条件
    return n * Factorial(n - 1);
}
拓展:

递归优势代码简洁;缺点深度过大会栈溢出,大数据优先迭代循环。

五、函数三种参数传递(面试高频 ref/out/ 值传递)

5.1 值传递(默认方式)

概念:

实参拷贝一份副本传入函数,函数内修改副本不影响外部原始变量

特性:

值类型默认值传递;引用类型本质传递地址(特殊)。

复制代码
void Change(int a)
{
    a++;
}
int hp = 100;
Change(hp);
// hp依旧等于100,无变化

5.2 ref 引用传递

概念:

传递变量别名,函数与外部共享同一块内存,内部修改同步作用于外部。

硬性规则:
  1. 变量传入前必须提前初始化

  2. 函数内可修改、可不修改该参数。

    void ChangeRef(ref int a)
    {
    a++;
    }
    int hp = 100; // 必须初始化
    ChangeRef(ref hp);
    // hp = 101

5.3 out 输出参数(C# 独有)

概念:

专门用于一个函数返回多个结果,弥补 return 仅能返回一个值的限制。

硬性规则(与 ref 核心区别)
  1. 变量传入无需提前初始化

  2. 函数内部必须对 out 参数赋值,编译强制校验,不赋值报错;

游戏实战:

射线检测(return 返回是否碰撞,out 输出碰撞点信息)

复制代码
// 一个返回值+out输出第二个结果
bool Calc(int x, int y, out int sumY)
{
    sum = x + y; // out必须赋值
    return x > y;
}
// out变量无需初始化
int resY;
bool flag = Calc(2, 4, out resY);

ref vs out 对比表

对比项 ref out
传入前 必须初始化 无需初始化
函数内 可改可不改 必须赋值

| 用途 | 双向读写外部变量 | 仅向外输出计算结果 |

拓展:

数组 / 自定义类为引用类型,直接传递地址,无需 ref 即可修改内部元素

复制代码
void ModifyArr(int[] data)
{
    data[0] = 999;
}
int[] arr = {1,2,3};
ModifyArr(arr);
// 数组第一个元素被修改

六、函数重载

概念:

同一作用域(同类内),函数名相同、参数列表不同 ,编译器根据实参自动匹配对应函数,属于编译期静态多态

合法重载条件(同时满足)

  1. 函数名完全一致;

  2. 参数个数 / 参数类型 / 参数顺序至少一处不同;

不参与重载判断:返回值、访问修饰符

复制代码
// 合法重载
public int Add(int a, int b) { return a + b; }
public float Add(float a, float b) { return a + b; }
// 仅返回值不同,非法重载,编译报错
// public void Add(int a, int b){}

拓展:

泛型可大量减少重复重载代码。

七、Lambda 匿名函数(委托基础)

1. 概念

无独立函数名的内联函数,等价 TS 箭头函数,底层依托Action/Func委托类型,常用于事件、集合筛选、回调。

分类

  1. Action:无返回值委托;

  2. Func<T>:带返回值委托。

基础语法

复制代码
// 无参数无返回
Action act = () => Console.WriteLine("Hello");
act();

// 带两个参数,有返回值 Func<入参1,入参2,返回值>
Func<int,int,int> Add = (a,b) => a + b;
Console.WriteLine(Add(3,5));

2. Lambda 捕获外部变量 & 闭包陷阱

概念:

Lambda 可捕获方法内局部变量,形成闭包。

经典 bug:

循环中直接捕获循环变量 i,所有 Lambda 共享同一个 i(循环结束 i 为固定值)

解决方案:

循环内创建临时变量拷贝 i,每个 Lambda 捕获独立副本

复制代码
List<Action> list = new List<Action>();
for(int i = 0; i < 3; i++)
{
    // 错误:捕获共享i
    list.Add(()=>Console.WriteLine(i));
    
    // 正确:临时变量拷贝,独立值
    int temp = i;
    list.Add(()=>Console.WriteLine(temp));
}

实战场景:

按钮点击事件、集合 Where 筛选、异步回调

八、可空类型 & 空合并运算符

8.1 可空类型 值类型?

概念:

C# 值类型(int/bool/double)默认不能为 null,加问号?变为可空类型,支持赋值 null;引用类型天然可空。

代码示例
复制代码
int? num = null; // 合法
int a = num ?? 0;// 空合并给默认值

8.2 空合并运算符 ??

概念:

判断左侧可空变量,若为 null,返回右侧默认值;不为 null 返回自身。

特性:

仅处理 null 空值,解决可空类型赋值报错问题。

复制代码
double? score = null;
double real = score ?? 60.0; // score为空,赋值60

拓展:

数据库字段大量使用可空类型(字段无数据存 null)。

九、数组(一维、多维)

9.1 一维数组

概念:

连续内存存储同类型数据,长度固定不可扩容,下标从 0 开始,越界抛异常。

两种初始化方式
  1. 动态初始化(指定长度,默认 0)

    int[] arr = new int[6];

  2. 静态初始化(直接赋值元素)

    int[] arr = {1,2,3,4,5};

属性:数组.Length 获取元素个数(属性,非方法,无括号)

9.2 多维数组(C# 特殊语法)

概念:

不用双层[][],使用逗号分隔维度 int[,];三维int[,,]

二维数组示例(3 行 4 列)
复制代码
// 静态初始化
int[,] map = {
    {1,2,3,4},
    {5,6,7,8},
    {9,10,11,12}
};
// 访问第二行第三列
Console.WriteLine(map[1,2]);

特性

  1. 内存整块连续;

  2. 长度固定,无法动态增减;

  3. TS/Java 使用[][]交错数组,C# 多维数组语法区分。

十、Random 随机类 & 洗牌算法

1. Random 工具类

概念:

生成伪随机数,全局仅实例化一次,循环内重复 new 会生成相同序列。

核心方法
  1. Next():0~int 最大值随机整数;

  2. Next(max):0 ≤ x < max;

  3. Next(min,max):min ≤ x < max;

  4. NextDouble():0~1 浮点随机数。

    // 全局只创建一次
    static Random rd = new Random();
    int num = rd.Next(1, 101); // 1~100

2. 洗牌算法(数组乱序)

思路:

从后往前,当前下标与随机下标交换,打乱数组元素。

复制代码
static void Shuffle(int[] arr)
{
    Random rd = new Random();
    for(int i = arr.Length - 1; i > 0; i--)
    {
        int idx = rd.Next(0, i + 1);
        int temp = arr[i];
        arr[i] = arr[idx];
        arr[idx] = temp;
    }
}

拓展:

游戏道具随机掉落、卡牌洗牌核心算法。

十一、面试 & 工程拓展考点

  1. ref/out 核心区别、射线检测 out 参数实战;

  2. Lambda 闭包循环捕获陷阱,项目高频 bug;

  3. 四种访问修饰符权限区分,internal 独有特性;

  4. 函数重载判断标准,返回值不参与重载;

  5. 可空类型???空合并运算符数据库用途;

  6. Random 禁止循环重复实例,伪随机数原理;

  7. C# 多维数组与 TS/Java 交错数组语法差异。