Skip to content

10-函数(方法)

函数(又称方法)是将一段具有特定功能的代码封装在一起,便于重复调用。函数是 C# 程序组织代码的基本单元,也是代码复用的核心手段。


一、函数的核心概念

为什么使用函数?

不使用函数使用函数
重复代码到处复制一处定义,多处调用
修改需要改所有复制点只改函数定义即可
代码难以阅读和理解通过函数名即可理解功能
逻辑分散,测试困难可单独测试每个函数

函数定义语法

csharp
访问修饰符 返回值类型 方法名(参数列表)
{
    // 方法体
    return 返回值;  // 如果返回值类型为 void,则不需要
}

组成部分详解

组成部分说明示例值
访问修饰符控制方法的访问范围public(公开)、private(私有)、internal(程序集内)、protected(派生类)
返回值类型方法返回的数据类型,无返回值用 voidintstringboolvoid、自定义类型
方法名方法的标识符,使用 PascalCase(首字母大写)CalculateTotalGetUserName
参数列表方法接收的输入值(可以为空)(int x, int y)(string name)
方法体方法执行的代码块{ return x + y; }

二、基本示例

csharp
// 1. 无参数,无返回值
static void SayHello()
{
    Console.WriteLine("你好!欢迎使用本系统。");
}

// 2. 有参数,无返回值
static void Greet(string name)
{
    Console.WriteLine($"你好,{name}!");
}

// 3. 有参数,有返回值
static int Add(int a, int b)
{
    return a + b;
}

// 4. 无参数,有返回值
static string GetVersion()
{
    return "v1.0.0";
}

// 调用
SayHello();                             // 输出:你好!欢迎使用本系统。
Greet("张三");                          // 输出:你好,张三!
int result = Add(3, 5);                 // 8
string version = GetVersion();          // "v1.0.0"

三、参数传递方式详细对比

C# 提供了多种参数传递方式,理解它们的区别至关重要。

1. 值参数(默认)

传递的是值的副本,方法内修改不影响原变量。

csharp
static void ChangeValue(int x)
{
    x = 100;  // 只修改副本
    Console.WriteLine($"方法内:{x}");  // 输出:100
}

int num = 10;
ChangeValue(num);
Console.WriteLine($"方法外:{num}");    // 输出:10(未改变)

内存原理: numx 在栈上有两个独立的内存空间,方法内修改的是 x 的空间。

2. ref 参数(引用传递)

传递的是变量的引用,方法内修改会影响原变量。

csharp
static void ChangeRef(ref int x)
{
    x = 100;  // 直接修改原变量
}

int num = 10;
ChangeRef(ref num);  // 调用时必须加 ref
Console.WriteLine(num);  // 输出:100(已改变)

特点: 调用前变量必须初始化ref 参数在方法内可以读也可以写

3. out 参数(输出参数)

用于从方法返回多个值,调用前无需初始化,但方法内必须赋值

csharp
static void GetMinMax(int[] numbers, out int min, out int max)
{
    min = numbers[0];
    max = numbers[0];

    foreach (int n in numbers)
    {
        if (n < min) min = n;
        if (n > max) max = n;
    }
    // 方法结束前 min 和 max 必须被赋值
}

int[] arr = { 3, 7, 1, 9, 4 };
GetMinMax(arr, out int min, out int max);
Console.WriteLine($"最小值:{min},最大值:{max}");  // 输出:最小值:1,最大值:9

// 使用下划线忽略不需要的参数(C# 7.0+)
GetMinMax(arr, out _, out int onlyMax);
Console.WriteLine($"最大值:{onlyMax}");

4. in 参数(只读引用)

传递引用但方法内不能修改,适合传递大型结构体以避免复制开销。

csharp
static void PrintValue(in int x)
{
    // x = 100;  // 编译错误:in 参数不可修改
    Console.WriteLine(x);
}

int num = 42;
PrintValue(in num);  // in 可以省略

5. params 参数(可变参数)

允许传入任意数量的参数,编译器自动打包为数组。

csharp
static int Sum(params int[] numbers)
{
    int total = 0;
    foreach (int n in numbers)
        total += n;
    return total;
}

Console.WriteLine(Sum(1, 2, 3));              // 输出:6
Console.WriteLine(Sum(1, 2, 3, 4, 5));        // 输出:15
Console.WriteLine(Sum());                      // 输出:0(不传参数也合法)
Console.WriteLine(Sum(new int[] { 10, 20 }));  // 也可以直接传数组

规则: params 必须是方法参数列表中的最后一个参数。

参数传递方式对比

方式关键字是否必需初始化方法内能否修改是否能影响原变量
值传递(无)可以修改副本
引用传递ref是(调用前)可以修改
输出参数out否(调用前)必须赋值
只读引用in不能修改
可变参数params

四、可选参数和命名参数

可选参数(默认值)

给参数指定默认值,调用时可省略该参数。

csharp
static void CreateUser(string name, int age = 18, string city = "北京")
{
    Console.WriteLine($"创建用户:{name},{age}岁,来自{city}");
}

CreateUser("张三");                    // 使用全部默认值
CreateUser("李四", 25);               // 指定 age,city 使用默认
CreateUser("王五", 30, "上海");       // 全部指定

规则: 可选参数必须放在必需参数之后

命名参数

调用时指定参数名称,可以不按顺序传递,提高可读性。

csharp
static void Register(string account, string password, string email, string phone)
{
    Console.WriteLine($"注册账户:{account},邮箱:{email}");
}

// 普通调用
Register("zhangsan", "123456", "zhang@test.com", "13800000000");

// 命名参数调用(顺序可以打乱)
Register(
    account: "zhangsan",
    phone: "13800000000",
    email: "zhang@test.com",
    password: "123456"
);

// 混合使用(命名参数在最后)
Register("zhangsan", "123456", email: "zhang@test.com", phone: "13800000000");

五、方法重载(Overload)

同一个类中允许定义多个同名方法,但参数列表必须不同。编译器根据传入的参数决定调用哪个版本。

csharp
public class Calculator
{
    // 两个整数相加
    public int Add(int a, int b)
    {
        return a + b;
    }

    // 三个整数相加(参数数量不同)
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }

    // 浮点数相加(参数类型不同)
    public double Add(double a, double b)
    {
        return a + b;
    }

    // 数组求和(参数类型不同)
    public int Add(int[] numbers)
    {
        int sum = 0;
        foreach (int n in numbers) sum += n;
        return sum;
    }
}

Calculator calc = new Calculator();
Console.WriteLine(calc.Add(1, 2));          // 3
Console.WriteLine(calc.Add(1, 2, 3));       // 6
Console.WriteLine(calc.Add(1.5, 2.5));      // 4.0
Console.WriteLine(calc.Add(new[] { 1,2,3,4,5 })); // 15

重载规则

规则说明示例
参数数量不同参数个数不同Add(int, int) vs Add(int, int, int)
参数类型不同参数类型不同Add(int, int) vs Add(double, double)
参数顺序不同参数顺序不同Method(int, string) vs Method(string, int)
返回值不同不能仅靠返回值区分重载编译错误

六、局部函数(C# 7.0+)

在方法内部定义方法,作用域仅限于包含它的方法。

csharp
static void ProcessOrder(int orderId)
{
    // 验证订单
    bool Validate()
    {
        return orderId > 0;
    }

    // 计算折扣
    decimal CalculateDiscount(decimal amount)
    {
        return amount * 0.9m;
    }

    // 发送通知
    void Notify(string message)
    {
        Console.WriteLine($"[订单 {orderId}] {message}");
    }

    if (Validate())
    {
        decimal total = CalculateDiscount(100);
        Notify($"订单处理完成,金额:{total}");
    }
}

局部函数的特性

  • 可以访问包含它的方法中的变量(闭包)
  • 包含 static 关键字的局部函数不能访问外部变量(C# 8.0+)
  • 可以在迭代器方法和异步方法中使用
csharp
// 静态局部函数(C# 8.0+)
void Calculate()
{
    int multiplier = 10;

    // 静态局部函数不能访问外部变量
    static int Multiply(int x, int y) => x * y;

    // 普通局部函数可以访问外部变量
    int MultiplyBy(int x) => x * multiplier;

    Console.WriteLine(Multiply(3, 5));     // 15
    Console.WriteLine(MultiplyBy(3));      // 30
}

七、递归函数

函数调用自身称为递归。递归必须有终止条件,否则会栈溢出。

经典案例:阶乘

csharp
// n! = n × (n-1) × (n-2) × ... × 1
static int Factorial(int n)
{
    if (n <= 1)
        return 1;  // 终止条件:0! = 1! = 1
    return n * Factorial(n - 1);  // 递归调用
}

Console.WriteLine(Factorial(5));  // 输出:120

执行过程:

Factorial(5) = 5 * Factorial(4)
            = 5 * 4 * Factorial(3)
            = 5 * 4 * 3 * Factorial(2)
            = 5 * 4 * 3 * 2 * Factorial(1)
            = 5 * 4 * 3 * 2 * 1
            = 120

案例:斐波那契数列

csharp
// Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21...
static int Fibonacci(int n)
{
    if (n <= 2)
        return 1;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}

// 打印前 10 项
for (int i = 1; i <= 10; i++)
    Console.Write($"{Fibonacci(i)} ");
// 输出:1 1 2 3 5 8 13 21 34 55

注意: 递归虽然代码简洁,但性能较差(重复计算)。可用循环或记忆化优化。

递归 vs 循环

对比递归循环
代码简洁性好(自然表达分治思想)一般
性能较差(函数调用开销)
栈溢出风险有(层级过深时)
适用场景树遍历、分治算法一般迭代

八、表达式主体方法(箭头方法,C# 6.0+)

对于单行方法,可以使用 => 简化语法。

csharp
// 传统写法
static int Add(int a, int b)
{
    return a + b;
}

// 箭头方法
static int Add(int a, int b) => a + b;

// 无返回值
static void Print(string msg) => Console.WriteLine(msg);

// 只读属性
public string FullName => $"{FirstName} {LastName}";

// 构造函数
public Person(string name) => Name = name;

九、综合案例

学生成绩管理系统(函数版)

csharp
class Program
{
    static List<int> scores = new List<int>();

    static void Main()
    {
        while (true)
        {
            ShowMenu();
            string choice = Console.ReadLine();
            switch (choice)
            {
                case "1": AddScore(); break;
                case "2": ShowScores(); break;
                case "3": ShowStatistics(); break;
                case "4": SearchScore(); break;
                case "0":
                    Console.WriteLine("再见!");
                    return;
                default:
                    Console.WriteLine("无效选项");
                    break;
            }
        }
    }

    static void ShowMenu()
    {
        Console.WriteLine("\n=== 成绩管理系统 ===");
        Console.WriteLine("1. 添加成绩");
        Console.WriteLine("2. 查看成绩");
        Console.WriteLine("3. 统计信息");
        Console.WriteLine("4. 查找成绩");
        Console.WriteLine("0. 退出");
        Console.Write("请选择:");
    }

    static void AddScore()
    {
        Console.Write("请输入成绩(0-100):");
        if (int.TryParse(Console.ReadLine(), out int score) && score >= 0 && score <= 100)
        {
            scores.Add(score);
            Console.WriteLine($"添加成功!当前共 {scores.Count} 个成绩。");
        }
        else
        {
            Console.WriteLine("输入无效,请输入 0-100 之间的整数。");
        }
    }

    static void ShowScores()
    {
        if (scores.Count == 0)
        {
            Console.WriteLine("暂无成绩数据。");
            return;
        }
        Console.WriteLine($"共 {scores.Count} 个成绩:");
        for (int i = 0; i < scores.Count; i++)
            Console.WriteLine($"  [{i}] {scores[i]}");
    }

    static void ShowStatistics()
    {
        if (scores.Count == 0)
        {
            Console.WriteLine("暂无成绩数据。");
            return;
        }

        int sum = 0, max = scores[0], min = scores[0], passCount = 0;
        foreach (int s in scores)
        {
            sum += s;
            if (s > max) max = s;
            if (s < min) min = s;
            if (s >= 60) passCount++;
        }

        double avg = (double)sum / scores.Count;
        double passRate = (double)passCount / scores.Count * 100;

        Console.WriteLine($"总人数:{scores.Count}");
        Console.WriteLine($"总  分:{sum}");
        Console.WriteLine($"平均分:{avg:F1}");
        Console.WriteLine($"最高分:{max}");
        Console.WriteLine($"最低分:{min}");
        Console.WriteLine($"及格率:{passRate:F1}%");
    }

    static void SearchScore()
    {
        Console.Write("请输入要查找的分数:");
        if (int.TryParse(Console.ReadLine(), out int target))
        {
            var indices = new List<int>();
            for (int i = 0; i < scores.Count; i++)
                if (scores[i] == target)
                    indices.Add(i);

            if (indices.Count > 0)
                Console.WriteLine($"找到 {indices.Count} 个匹配,索引位置:{string.Join(", ", indices)}");
            else
                Console.WriteLine("未找到该成绩。");
        }
    }
}

核心知识点总结

  1. 函数定义访问修饰符 + 返回值类型 + 方法名 + 参数列表 + 方法体
  2. 参数传递
    • 值参数:传递副本,不影响原变量
    • ref:传递引用,必须初始化,可读可写
    • out:输出参数,无需初始化,必须赋值
    • in:只读引用,适合大型结构体
    • params:可变数量参数
  3. 方法重载:同名不同参,编译器根据参数自动匹配
  4. 递归:函数调用自身,必须有终止条件
  5. 局部函数:方法内定义方法,C# 7.0+
  6. 箭头方法:单行方法用 => 简化,C# 6.0+

常见错误

csharp
// 错误1:返回值类型不匹配
static int GetNumber() { return "string"; }  // 编译错误

// 错误2:方法名未使用 PascalCase
// ✅ public int CalculateTotal() 
// ❌ public int calculateTotal()

// 错误3:out 参数未在方法内赋值
static void Test(out int x) { }  // 编译错误:out 参数必须赋值

Released under the MIT License.