C#学习笔记5:函数、函数重载、递归函数

-呈阶梯状分布- 62 次阅读 3487 字 发布于 2026-07-17 最后更新于 2026-07-31 C#学习笔记


函数

函数,是代码复用的基础,通过将代码用函数封装,可以提高代码复用性和灵活性。你可以把函数看作是一段代码被打包了起来,后面想再使用相同代码时只需要用打好的包就可以了,不需要再写一长串代码。
我们可以在类下先写出函数的创建格式(书名号内为可选项):

《作用域》 《静态声明》 返回值 函数名(参数1, 参数2, 参数n...)
{
    代码块...
}

作用域你可以先不写,后面会讲,静态声明(static)暂时都写,返回值暂时写void(空,即没有返回值),函数名和变量名一样,但建议以大写开头,参数可写可不写,但参数的括号()必须有。用大括号{}包住函数内的代码块,用于声明内部的代码块归函数管理。

using System;

namespace Csharp
{
    class Program
    {
        static void Function()
        {
            
        }
        
        static void Main(string[] args)
        {
            
        }
    }
}

我们平时的代码都写在Main函数下,是因为程序在运行时会调用Main函数,从而执行其中的代码。
同样的,我们只要调用我们写的函数,就可以让程序执行其中的代码。
我们完善一下上面的代码:

using System;

namespace Csharp
{
    class Program
    {
        static void Function()
        {
            Console.WriteLine("函数被调用");
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Function();
            }
        }
    }
}

for循环次数为10,所以最后的输出为10个"函数被调用"。

函数的参数

我们来看一个带有参数的函数例子:

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int a, int b)
        {
            Console.WriteLine(a + b);
        }

        static void Main(string[] args)
        {
            Function(13, 78);
        }
    }
}

可以看到,函数名后面的括号内不再为空,而是出现了两个变量,中间用逗号隔开。

a和b这两个变量只能在函数内使用,外界访问不到,且必须在函数被使用时通过 函数名(参数) 赋值,这样的变量我们就称之为函数的参数。

参数声明时需要注明其类型,调用函数时传入的类型必须与参数类型一致。

可以看到,13被赋值给了a,78被赋值给了b,a+b的结果也就是91,所以这段代码最后会输出91。

形式参数与实际参数——形参/实参

我们在函数内使用的a和b都仅仅被当作一个变量使用,没有实际的值,值只有在函数被调用的时候才会被传递,所以a和b被称为形式参数。

13和78是实际的值,在调用函数时传入,所以13和78被称为实际参数。

在调用函数时,实际参数的值会被传入形式参数,从而参与函数内运算。

可选参数

对于上面的a和b这样的参数,在声明时没有默认值,必须传入,否则报错。如果我们希望当用户没有传入参数时使用默认值,应当怎么做呢?

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int a = 0, int b = 0)
        {
            Console.WriteLine(a + b);
        }

        static void Main(string[] args)
        {
            Function(13, 78);
            Function(13);
            Function();
        }
    }
}

我们通过赋值符号给了a和b默认值,此时,如果参数没有被传入,则会调用默认值。

比如Main函数里的三段调用,
第一段和没默认值时一样,输出91(即13+78);
第二段a被赋值为13,b没被赋值,所以b为默认值0,输出13(即13+0);
第三段a和b都没被赋值,所以a和b都为默认值0,输出0(即0+0)。

注意!可选参数必须放在正常参数后方,比如下面代码:

static void Function(int a, int b = 0)
{
    Console.WriteLine(a + b);
}

此处的a没有默认值,必须被赋值,而b可以不被赋值。
为了传递参数时优先给到更重要的、必须被赋值的这些正常参数,所以正常参数必须放在可选参数前面。

还有一点,可选参数的默认值必须为一个常量,不能为变量。例如,你可以是"我是字符串",也可以是1234, 但绝不能是a, b ,c ,d这样的变量名。

参数数组

假如说,我现在希望写一个函数,能输出所有参数的和,那么我可以这样写:

static void Function(int a, int b)
{
    Console.WriteLine(a + b);
}

但是这样只有两个参数,我希望输出更多参数的和,那就只能使用数组:

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int[] a)
        {
            int result = 0;
            foreach (int i in a)
            {
                result += i;
            }
            Console.WriteLine(result);
        }

        static void Main(string[] args)
        {
            int[] my_tuple = new int[] {19, 23, 43, 54, 25};
            Function(my_tuple);
        }
    }
}

显然,这样虽然满足了我们任意数量的数都能相加的要求,但每次想输出和都得先创建一遍数组,实在是太麻烦了。
那么有没有能够直接把参数全部自动塞进一个数组的方式呢?

using System;

namespace Csharp
{
    class Program
    {
        static void Function(params int[] a)
        {
            int result = 0;
            foreach (int i in a)
            {
                result += i;
            }
            Console.WriteLine(result);
        }

        static void Main(string[] args)
        {
            Function(19, 23, 43, 54, 25);
        }
    }
}

可以看到,我只是在int[] a的前面加上了关键字params,下面Main函数中调用时的5个参数19, 23, 43, 54, 25就都进入了int[] a这个数组里,我们称这个带有关键字的数组a为参数数组。

使用参数数组可以大大便利我们的传参,但是有几点需要注意一下:
首先,参数数组只能有一个,并且必须写在参数末尾。所以参数顺序为(正常参数, 可选参数, 参数数组),这样才会按照(必写参数, 可选参数, 多余参数)的逻辑传递参数。
其次,如果用我们现在的这种传参方法,从左到右依次传参的话,我们必须传递完正常参数和可选参数后才能传递参数数组,但是可选参数我们不一定要传递,有没有什么方法能让我们传递完正常数组后,跳过可选参数,直接赋值给参数数组呢?

命名参数

C#中除了按照下标传递参数(即从左往右传递)外,还可以按照参数名称传递。

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int a, int b = 0, int c = 0)
        {
            Console.WriteLine(a + b + c);
        }

        static void Main(string[] args)
        {
            Function(a: 13, b: 91, c: 78);
        }
    }
}

可以看到,只需要使用 形参名:传入的实参 就可以进行传递参数。因此,我们可以用这种方式跳过可选参数,如下:

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int a, int b = 0, int c = 0)
        {
            Console.WriteLine(a + b + c);
        }

        static void Main(string[] args)
        {
            Function(a: 13, c: 78);
        }
    }
}

我们还可以将按下标传递参数和命名参数传递参数混用,但要注意:按下标传递必须在命名参数前面,如下:

using System;

namespace Csharp
{
    class Program
    {
        static void Function(int a, int b = 0, int c = 0)
        {
            Console.WriteLine(a + b + c);
        }

        static void Main(string[] args)
        {
            Function(13, 91, c: 78);
        }
    }
}

如此,我们就能解决一开始的问题了:跳过可选参数,直接传递给参数数组。

using System;
using System.Xml.XPath;

namespace Csharp
{
    class Program
    {
        static void Function(int a, int b = 0, params int[] c)
        {
            int result = 0;
            result += a;
            result += b;
            foreach (int i in c)
            {
                result += i;
            }
            Console.WriteLine(result);
        }

        static void Main(string[] args)
        {
            Function(13, c: new int[] {78, 91, 13, 25, 235});
        }
    }
}

尽管参数数组已经发挥不了它的用处,和一般数组没什么区别,但我们确实完成了我们的目标。

函数返回值

我们在第1篇笔记就讲到,Console.ReadLine()会返回读取到的输入内容,这个返回的内容就叫做函数的返回值,可以用变量接收,如下:

string a = Console.ReadLine();

我们也可以修改前面的求和函数,让他不再是输出和,而是返回和:

using System;

namespace Csharp
{
    class Program
    {
        static int Function(params int[] a)
        {
            int result = 0;
            foreach (int i in a)
            {
                result += i;
            }
            return result;
        }

        static void Main(string[] args)
        {
            int func_result = Function(19, 23, 43, 54, 25);
            Console.WriteLine(func_result);
        }
    }
}

可以看到,我将原来的Console.WriteLine(result);改成了return result;,这也就是返回的格式了:
result 返回的值;

返回值可以是变量或常量,但你需要在函数名前方声明返回值的类型,这也就意味着你只能返回一种类型的返回值,即使你使用了分支语句。

需要注意的是,一旦返回,整个函数会立刻结束,也就是return后面的所有代码都不再会被执行。

函数重载

由于参数类型固定,我们前面的求和函数只能处理int类型,不能处理double类型。为了处理double类型,我们得写两个函数:

using System;

namespace Csharp
{
    class Program
    {
        static int SumForInt(params int[] a)
        {
            int result = 0;
            foreach (int i in a)
            {
                result += i;
            }
            return result;
        }

        static double SumForDouble(params double[] a)
        {
            double result = 0;
            foreach (double i in a)
            {
                result += i;
            }
            return result;
        }

        static void Main(string[] args)
        {
            int a = SumForInt(19, 23, 43, 54, 25);
            Console.WriteLine(a);

            double b = SumForDouble(19.1, 23.2, 43.3, 54.4, 25.5);
            Console.WriteLine(b);
        }
    }
}

但这样其实很麻烦,用户只想求和却要用一堆函数。有没有什么方法能够让一个函数同时处理多种类型的参数情况呢?
看下面这段代码:

using System;

namespace Csharp
{
    class Program
    {
        static int Sum(params int[] a)
        {
            int result = 0;
            foreach (int i in a)
            {
                result += i;
            }
            return result;
        }

        static double Sum(params double[] a)
        {
            double result = 0;
            foreach (double i in a)
            {
                result += i;
            }
            return result;
        }

        static void Main(string[] args)
        {
            int a = Sum(19, 23, 43, 54, 25);
            Console.WriteLine(a);

            double b = Sum(19.1, 23.2, 43.3, 54.4, 25.5);
            Console.WriteLine(b);
        }
    }
}

我们会发现,有两个名称都是Sum的函数,这两个函数的核心区别是参数不同:一个是int数组,一个是double数组。
然而,两个名称相同的函数竟然不报错,这是为什么呢?

C#会根据函数名相同、但参数不同的原则为函数编排重载,也就是看人下菜碟。你传入的参数是int数组,那就用参数为int数组的函数;你传入的参数是double数组,那就用参数为double数组的函数。也就是虽然函数名一致,但你传入不同的参数,就会执行不同的函数。

注意,同名称函数判断是否为重载的标准只有参数不同。可以是参数数量相同类型不同,也可以是参数数量不同,是否为重载和返回值、函数体(即函数内的代码块)、参数名称是否相同无关。即使两个同为Sum的函数函数体不同,他们的参数完全相同也会报错,而不是重构。毕竟输入一种参数,给我两种函数,谁也不知道该执行哪一个。

学会使用函数重载,能大大增加代码的简并性和可读性,方便日后维护。

递归函数

正常来说,我们都是在Main函数内调用我们的函数,以此来调用函数内的代码块。但是,函数竟然可以自己调用自己~

tip:下面的问题较为复杂,你可以跟着函数调用的过程思考一遍,不要求理解,只需要明白递归函数的过程

现在有3n+1问题(n>1且n为自然数,n为奇数则变为3n+1,否则变为n的一半,最终n一定变为1,统计变换次数)

如果用循环可以这么写:

int change_times = 0;
int n2 = 3;  // 测试用例:输入3,输出7;输入10,输出6.
while (n2 != 1)
{
    if (n2 % 2 == 0)
    {
        n2 /= 2;
    }
    else
    {
        n2 = 3 * n2 + 1;
    }
    change_times++;
}
Console.WriteLine(change_times);

但是,如果用递归函数,可以这么写:

using System;

namespace Csharp
{
    class 递归函数
    {
        // 3n+1问题(来自3循环结构)改写,第一个输入n,第二个输入0
        static int Func(params int[] n)
        {
            if (n[0] == 1)
            {
                return n[1];
            }
            else if (n[0] % 2 == 0)
            {
                n[0] /= 2;
                n[1] += 1;
                return Func(n);
            }
            else
            {
                n[0] = 3 * n[0] + 1;
                n[1] += 1;
                return Func(n);
            }
        }
        static void Main(string[] args)
        {
            Console.WriteLine(Func(3, 0));
        }
    }
}

先讲一下思路,再来看代码。
第1轮,输入的n为3,奇数,则变为3n+1,即10;
第2轮,输入的n为10,偶数,则变为一半,即5;
第3轮,输入的n为5,奇数,则变为3n+1,即16;
第4轮,输入的n为16,偶数,则变为一半,即8;
第5轮,输入的n为8,偶数,则变为一半,即4;
第6轮,输入的n为4,偶数,则变为一半,即2;
第7轮,输入的n为2,偶数,则变为一半,即1,结束。所以变化次数为7。

那么我们可以建立一个数组,第0位存储n,第1位存储变化次数。
如果n==1,那么变化结束,返回变化次数;
否则如果n为偶数(代码中用整除2后余数为0检测),n变为一半,变化次数+1,进入下一轮;
否则n变为3n+1,变化次数+1,进入下一轮。

如此,我们就能得到变化次数了。

此处函数自己调用自己使用return 函数名(参数...);,return后面的内容和正常调用函数一样。

要注意的是,不要让函数无限调用自己,否则会死循环。必须通过分支语句检测参数,到达某个特定值就直接返回,否则继续调用自己。我们把这种函数自己调用自己的过程称作递归,带有这种写法的函数被称作递归函数。

这篇真的超长~感谢阅读!