11.1 方法的简介
函数(方法)就是将一堆代码进行重用的一种机制。函数就是一段代码,这段代码可能有输入的值(参数),可能会返回值。一个函数就像一个专门做这件事的人,我们调用它来做一些事情,它可能需要我们提供一些数据给它,它执行完成后可能会有一些执行结果给我们。要求的数据叫做参数。返回的执行结果就是返回值。
有了函数,我们在写代码的时候就像拼积木,C#中的各种各样的技术其实就是通过for、if等这些基础的语法,将不同的函数按照一定的逻辑组织起来。
补充知识:方法的功能一定要单一,方法中最忌讳的就是出现让用户输入等字眼。
1)作用:减少重复的代码,提高代码的复用性。
2)过程、方法、函数的区别:
过程: 无返回值
函数:有返回值
方法:不管有无返回值(过程+函数)
3)函数返回值的关键字:return,作用:在方法中返回要返回的值(return后面接其他东西),立即结束本次方法(return后面不接东西)。
4)函数的参数:参数其实相当于一个值的副本,传递副本的好处就是可以避免因误操作而影响了原值,缺陷就是会因复制而产生性能损耗。
5) 参数的关键字:ref、out、params
ref:ref参数侧重于将一个变量以参数的形式带到一个方法中进行改变,改变完成后,再将改变后的值带出来。在使用ref参数的时候需要注意:ref参数在方法外必须为其赋值,也就是初始化。
out:可以帮助我们在一个方法中返回多个值,不限类型。使用out参数的时候,要求out参数必须在方法内为其赋值,也就是初始化。
params:不定参数个数关键字,如果参数列表还有其他参数,params修饰的参数一定要放到最后一个。
11.2 方法的声明
1)语法:
[public] static 返回值类型 方法名([参数列表])
{
方法体;
}
2)注意:
① public:修饰符,可以省略,写上这个代表此方法(函数)是公共的,也就是谁都可以进行访问。当然也有其他访问修饰符。
② static:静态的
③ 返回值类型:int、string、char等,若没有返回值就是void。
④ 方法名:遵循Pascal命名法,每个单词的首字母都大写,其余字母小写
⑤ 参数列表:完成这个方法所必须要提供给这个方法的条件,如果没有参数,小括号也不能省略。
11.3 方法的调用
在C#中,方法调用是通过在代码中使用方法名和传递参数来实现的
1)调用的语法:
类名.方法名([参数]);
注意:如果调用同一个类里面的方法,类名可以省略。方法写好后,如果要被执行,必须要在Main()函数中调用。
例如:我们在Main()函数中,调用Test()函数,我们管Main()函数称之为调用者,管Test()函数称之为被调用者。
如果被调用者想要得到调用者的值:1.传递参数。2.使用静态字段来模拟全局变量。
例如,假设有一个名为Add的方法,在类Calculator中定义了两个int类型参数,并且返回他们的和,在Main方法中调用这个方法的示例代码如下:
// 定义一个Calculator类
class Program
{
public static int Add(int a, int b)
{
return a + b;
}
}
// 在Main方法中调用Add方法
int result = Calculator.Add(2, 3);
Console.WriteLine(result);
输出结果:
5
2)方法示例:
//创建函数
public static int MaxArr(int[] a)
{
int max = 0;
for (int i = 0; i < a.Length; i++)
{
if (a[i]> max)
{
max = a[i];
}
}
return max;
}
//调用函数
int result = MaxArr(new int[]{20,0,-10,30,25,-100});
Console.WriteLine($"数组中最大值为:{result}");
//不自动关闭控制台,等待输入
Console.ReadLine();
输出结果:
数组中最大值为:30
11.4 方法的参数
11.4.1 形参
形参是本地变量,它声明在方法的参数列表中,而不是在方法体中。
public void PrintSum( int x, double y ) //x,y就是形参
{
方法体
}
注意:
① 因为形参是变量,所以他们有类型和名称,并能被写入和读取。
② 和方法中的其他本地变量不同,参数在方法体的外面定义并在方法开始之前初始化(但有一种类型例外,称为输出参数,下面会讲到)。
③ 参数列表中可以由任意数目的形参声明,而且声明必须用逗号隔开。
11.4.2 实参
用于初始化形参的表达式或变量称作为实参。
注意:
① 代码调用一个方法时,形参的值必须在方法的代码开始执行之前被初始化。
② 实参位于方法调用的参数列表中。
③ 每一个实参必须与对应形参的类型相匹配,或是编译器必须能够把实参隐式转换为那个类型。
④ 不管是形参还是实参,都是在内存中开辟了空间的。
例如:
int year = int.Parse(Console.ReadLine());
bool a = Years(year); //实参
public static bool Years(int year) //形参
{}
11.5 特殊参数标识
11.5.1 ref参数
ref参数要求在方法外必须为其赋值,而方法内可以不赋值。能够将一个变量带入一个方法中进行改变,改变完成后,再将改变后的值带出方法。
被 ref 修饰的参数拥有了输入输出二合一的属性,就是被这个修饰的变量,就既是方法的输入参数,又是返回参数。
1)ref参数示例1:使用 ref 参数来交换两个整数变量的值。
public static void Swap(ref int a, ref int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
static void Main(string[] args)
{
int max = 1;
int min = 0;
Console.WriteLine($"交换前max是{max},min是{min}");
Swap(ref max, ref min);
Console.WriteLine($"交换后max是{max},min是{min}");
Console.ReadKey();
}
输出结果:
交换后max是0,min是1
2)ref参数示例2:使用 ref 参数来将整数数组中的每个元素增加一个指定的值。
public static void IncrementArray(ref int[] arr, int value)
{
for (int i = 0; i < arr.Length; i++)
{
arr[i] += value;
}
}
static void Main(string[] args)
{
int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine("Before incrementing:");
foreach (int item in numbers)
{
Console.Write($"{item}");
}
// 调用 IncrementArray() 方法并传递 numbers 数组的引用
IncrementArray(ref numbers, 10);
Console.WriteLine("\nAfter incrementing:");
foreach (int item in numbers)
{
Console.Write($"{item}");
}
}
输出结果:
Before incrementing:
1 2 3 4 5
After incrementing:
1 12 13 14 15
3)ref参数示例3:定义一个方法,使用 ref 参数来获取整数数组中的最小值和最大值。
public static void GetMinMax(ref int[] arr, ref int min, ref int max)
{
if (arr.Length > 0)
{
min = arr[0];
max = arr[0];
for (int i = 1; i < arr.Length; i++)
{
if (arr[i] < min)
{
min = arr[i];
}
else if (arr[i] > max)
{
max = arr[i];
}
}
}
}
static void Main(string[] args)
{
int[] numbers = { 5, 3, 8, 2, 9 };
int minValue = 0;
int maxValue = 0;
// 调用 GetMinMax() 方法并传递 numbers 数组的引用以及 minValue 和 maxValue 的引用
GetMinMax(ref numbers, ref minValue, ref maxValue);
Console.WriteLine("Minimum value: {0}", minValue);
Console.WriteLine("Maximum value: {0}", maxValue);
}
输出结果:
Minimum value: 2
Maximum value: 9
11.5.2 out参数
如果在一个方法中,返回多个相同类型的值的时候,可以考虑返回一个数组。但是,如果返回多个不同类型的值的时候,返回数组就不行了,那么这个时候,我们可以考虑使用out参数。
out参数就侧重于在一个方法中可以返回多个不同类型的值。声明方法的时候,在参数列表中加 out 关键字修饰的变量,被 out 修饰的变量就是方法的返回值。
注意:
调用方法之前,对out参数传递的变量只需声明,可以赋值也可以不赋值,赋值也会在方法中被覆盖掉。
使用out参数传递变量时,必须在方法内为其赋值。
方法的参数使用out修饰时,调用该方法时也要加上out关键字。
1)out参数示例1:
static void Main(string[] args)
{
int[] nums = { 1, 5, 4, 3, 6, 2 };
int max, min, sum;//最大值,最小值,总和
double avg;//平均值
//在调用方法的时候也需要加 out 来修饰变量。
GetSumMinMaxAvg(nums, out max, out min, out sum, out avg);
Console.WriteLine($"数组最大值是{max},最小值是{min},总和是{sum},平均值是{avg}");
Console.ReadKey();
}
public static void GetSumMinMaxAvg(int[] nums, out int max, out int min, out int sum, out double avg)
{
min = nums[0];
max = nums[0];
sum = 0;
for (int i = 0; i < nums.Length; i++)
{
max = max < nums[i] ? nums[i] : max;
min = min > nums[i] ? nums[i] : min;
sum += nums[i];
}
avg = ((double)sum) / nums.Length;
}
输出结果:
数组最大值是6,最小值是1,总和是21,平均值是3.5
2)out参数示例2:分别提示用户输入账号密码,写个方法判断用户输入是否正确,返回用户登陆结果,并且需要单独返回用户登陆信息:用户名错误,除了返回登陆结果,还要返回用户名错误。密码错误,除了返回登陆结果,还要返回密码错误。
static void Main(string[] args)
{
Console.WriteLine("请输入用户名");
string user = Console.ReadLine();
Console.WriteLine("请输入密码");
string password = Console.ReadLine();
string msg;
bool b = isLogin(user, password, out msg);
Console.WriteLine("登陆结果{0}", b);
Console.WriteLine("登陆信息{0}", msg);
Console.ReadKey();
}
public static bool isLogin(string name, string pwd, out string msg)
{
if (name == "admin" && pwd == "888888")
{
msg = "登陆成功";
return true;
}
else if (name == "admin")
{
msg = "密码错误";
return false;
}
else if (pwd == "888888")
{
msg = "用户名错误";
return false;
}
else
{
msg = "未知错误";
return false;
}
}
3)out参数示例3:实现斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13……,就是数列从数列的第3项开始,每一项都等于前两项之和。
public static void Fibonacci(int n, out int[] result)
{
result = new int[n];
if (n >= 1) result[0] = 0;
if (n >= 2) result[1] = 1;
for (int i = 2; i < n; i++)
{
result[i] = result[i - 1] + result[i - 2];
}
}
static void Main(string[] args)
{
//调用函数并输出结果
int[] fibonacciArray;
Fibonacci(10, out fibonacciArray);
Console.WriteLine("Fibonacci sequence:");
foreach (int item in fibonacciArray)
{
Console.WriteLine(item);
}
}
在这个示例中,定义了一个名为Fibonacci的函数,它接受一个整数n作为输入,并返回一个输出参数result,它是一个整数数组,包含前n个斐波那契数列的值。
在函数内部,我们创建一个长度为n的空数组result,然后使用递归算法计算斐波那契数列的值,并将其存储在数组中。
在主程序中,声明一个数组fibonacciArray,然后调用Fibonacci函数,并将fibonacciArray用作输出参数。 最后,我们遍历数组并将其打印到控制台上。
4)out参数示例4:定义函数,计算矩形的面积和周长。
// 定义矩形结构体
public struct Rectangle
{
public double length;
public double width;
// 定义函数,计算矩形的面积和周长
public static void Calculate(double length, double width, out double area, out double perimeter)
{
area = length * width;
perimeter = 2 * (length + width);
}
}
static void Main(string[] args)
{
// 调用函数并输出结果
Rectangle rect = new Rectangle { length = 5.0, width = 3.0 };
double area, perimeter;
Rectangle.Calculate(rect.length, rect.width, out area, out perimeter);
Console.WriteLine($"Length: {rect.length}, Width: {rect.width}");
Console.WriteLine($"Area: {area:F2}, Perimeter: {perimeter:F2}");
}
11.5.3 ref和out的区别
1)ref 参数表示在将值传递给方法之前必须初始化变量,而 out 参数则不需要。在使用 out 参数时,方法负责初始化变量的值。
2)在方法内部,ref 参数和 out 参数都可以更改变量的值,但是在调用方法之前,out 参数必须被赋予一个值,否则会导致编译错误。
3)out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
4)当希望方法返回多个值时,声明out非常有用。使用out参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。
ref:有进有出
out:只出不进
总体来说,ref 和 out 参数都提供了一种方便的方式来操作传递给方法的变量的引用,但是应该谨慎使用它们,以避免代码产生歧义或难以维护。
11.5.4 params参数
params是ParamArrayAttribute(参数数组属性)的缩写,也叫可变参数,这里的可变,不是说的类型可变,而是指的个数可变。将实参列表中跟可变参数类型一致的元素都当作数组的元素去处理。使用params关键字声明一个参数数组,可以让方法接收任意数量的参数,这些参数会被打包成一个数组传递给方法。params可变参数必须是形参列表中的最后一个元素。
params参数示例:
public static int sums(params int[] nums)
{
int sum = 0;
for (int i = 0; i < nums.Length; i++)
{
sum += nums[i];
}
return sum;
}
static void Main(string[] args)
{
Console.WriteLine(sums(1, 2, 3, 4, 5, 6, 7));
Console.ReadKey();
}
输出结果:
28
注意:
1)若形参表中含一个参数数组(params形参数组),则该参数数组必须位于形参列表的最后,并且只能使用一次。即在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。
2)参数数组必须是一维数组。
3)不允许将params修饰符与ref和out修饰符组合起来使用。
4)如果想要这个形参数组可以传不同类型的,则可以写成object类型。因为object类型是所有类型的基类,无论是string类型、int类型还是其他自己定义的类的类型,都是object类型的子类。
5)若实参是数组则按引用传递,若实参是变量或表达式则按值传递。
6)C#语法规定,params后边必定跟数组。作用是把不定数量的、同类型的参数装入这个数组中。
11.6 方法的重载
方法的重载指的是在同一个类中定义多个方法,它们具有相同的名称但参数列表不同。这样做的好处是可以根据不同的参数类型或参数个数来选择调用哪个方法,从而提高代码的灵活性和可读性。
概念:方法的重载指的就是方法的名称相同,但是参数不同。
① 如果参数的个数相同,那么参数的类型就不能相同。
② 如果参数的类型相同,那么参数的个数就不能相同。
③ 方法的重载跟返回值没有关系。
方法重载示例:
public static int result1, result2;
public static void M(int n1,int n2)
{
result1 = n1 + n2;
}
public static double M(double d1,double d2)
{
return d1 + d2;
}
public static void M(int n1,int n2,int n3)
{
result2 = n1 + n2 + n3;
}
public static string M(string s1,string s2)
{
return s1 + s2;
}
static void Main(string[] args)
{
M(1, 2);
Console.WriteLine(result1);
M(1, 2, 3);
Console.WriteLine(result2);
Console.WriteLine(M(1.1, 2.1));
Console.WriteLine(M("Y", "S"));
Console.ReadKey();
}
输出结果:
3
6
3.2
YS
11.7 方法的递归
递归的本质:就是自己调用自己,递归本身就是一个循环。
递归的思想:越来越接近已知值。
递归的总结:
① 通过自己调用自己,把复杂的逻辑简单化,可以求得最终结果。
② 递归要有开始条件,也要有一个结束条件。
③ 在实际开发中,不建议轻易选择递归,能用for循环while循环代替的,尽量使用循环来做。因为循环的效率高,耗费的内存少,递归耗费的内存比较大。
递归示例:
static void Main(string[] args)
{
TellStory();//调动函数
Console.ReadKey();
}
public static void TellStory()
{
Console.WriteLine("从前有座山");
Console.WriteLine("山里有座庙");
Console.WriteLine("庙里有个老和尚和小和尚");
Console.WriteLine("老和尚给小和尚讲了个故事");
TellStory();//自己调用自己形成递归
}
11.8 方法的练习
1)方法练习1:接收输入判断其登记并显示出来,判断一局是:90-100为优,80-89为良,70-79为中,60-69为差,60以下为不及格。
public static void Main(String[] args)
{
Console.WriteLine("请输入考试成绩");
int score=Convert.ToInt32(Console.ReadLine());
string result=GetLevel(score);
Console.WriteLine("你的考试等级为:{0}",result);
Console.ReadLine();
}
public static string GetLevel(int score)
{
switch(score/10)
{
case 10:return "优";break;
case 9:return "优";break;
case 8:return "良";break;
case 7:return "中";break;
case 6:return "差";break;
default:return "不及格";break;
}
}
2)方法练习2:倒序输出
public static void Main(String[] args)
{
string[] names = { "英国", "法国", "美国", "德国", "中国" };
string[] result = Test(names);
for (int i = 0; i < result.Length; i++)
{
Console.WriteLine(result[i]);
}
Console.ReadLine();
}
public static string[] Test(string[] names)
{
for (int i = 0; i < names.Length / 2; i++)
{
string temp = names[i];
names[i] = names[names.Length - i - 1];
names[names.Length - 1 - i] = temp;
}
return names;
}
输出结果:
中国
德国
美国
法国
英国
3)方法练习3:用方法,计算圆的面积与周长
public static void Main(String[] args)
{
double r = 6;double area;//面积
double perimeter;//周长
GetPerimeterArea(r, out perimeter, out area);
Console.WriteLine("{0:0.00}", perimeter);//留小数点后两位
Console.WriteLine("{0:0.00}", area);
Console.ReadLine();
}
public static void GetPerimeterArea(double r, out double perimeter, out double area)
{
area = 3.14 * r * r;
perimeter = 2 * 3.14 * r;
}
输出结果:
37.68
113.04
4)方法练习4:计算任意多个数之间总和(params)
public static void Main(String[] args)
{
int sum=GetSum(1,2,3,4,5,6,7,8);
Console.WriteLine(sum);
Console.ReadLine();
}
public static int GetSum(params int[] nums)
{
int sum=0;
for(int i=0;i<nums.Length;i++)
{
sum+=nums[i];
}
return sum;
}
输出结果:
36
5)方法练习5:计算出一个整形数组的平均值,保留两位小数。
public static double Avg(int[] s)
{
double sum = 0;
for(i = 0;i < s.Length;i++)
{
sum += s[i];
}
return Convert.ToDouble((sum/s.Length).ToString("0.00"));//真正保留两位
}
static void Main(string[] args)
{
int[] nums = {1,2,7};
double avg = Avg(nums);
Console.WriteLine(avg);
Console.ReadKey();
}
输出结果:
3.33