函数
函数,是代码复用的基础,通过将代码用函数封装,可以提高代码复用性和灵活性。你可以把函数看作是一段代码被打包了起来,后面想再使用相同代码时只需要用打好的包就可以了,不需要再写一长串代码。
我们可以在类下先写出函数的创建格式(书名号内为可选项):
《作用域》 《静态声明》 返回值 函数名(参数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后面的内容和正常调用函数一样。
要注意的是,不要让函数无限调用自己,否则会死循环。必须通过分支语句检测参数,到达某个特定值就直接返回,否则继续调用自己。我们把这种函数自己调用自己的过程称作递归,带有这种写法的函数被称作递归函数。
这篇真的超长~感谢阅读!

Comments NOTHING