[Day 005 of C#] 函数(方法) —— I've got such a terrible Functionality

Day 005

part 072 - 089.

函数(方法) function.


方法、函数(Function)的介绍早在part 056里引入tryparse来介绍过:

函数可以看做“帮你做一件事”的一个超级工具人。

下面小冰就开始真正地对函数进行学习


函数的简介

为什么会有函数?

  • Super Mario的时候,顶到小蘑菇的时候,屏幕闪烁,播放一段特殊的背景音乐,屏幕暂停。
  • 那是不是每一次顶到小蘑菇的时候就要把上面的变 化代码重新再全写一遍呢?
  • 当然不是!! 这样代码就冗余了。于是这时函数它出现了!解决了代码冗余的问题。

函数的定义

函数就是将一堆代码进行重用的一种机制,函数就是一段代码。
这段代码可能有输入的值(参数),可能会返回值。一个函数就像一个专门做这件事的人,我们调用它来做一些事情,它可能需要我们提供一些数据给它,它执行完成后可能会有一些执行结果给我们。
提供给的数据就叫做参数,返回的执行结果就是返回值。

面向对象有三大特征: 封装、继承、多态。
而函数就类似于封装。

  • 语法
public static 返回值类型 方法名(参数列表) { //这里的参数是形式上的参数
	方法体; //可以写要通过参数执行的代码
	方法体; 
}
  • 调用语法
类名.方法名(参数); //这里的参数是实实在在的参数
  • 不管是实参还是形参,都是在内存中开辟空间了的
  • public: 访问修饰符。 表示公开的,公共的,哪都可以访问。
  • static: 关键字。 表示静态的。
  • 返回值类型: 如果不需要返回值,写void,表示没有返回值。 (不是所有的函数都有返回值)
  • 方法名: 要符合 Pascal 的命名规范,即每个单词首字母都大写,其余字母小写
  • 参数列表: 完成这个方法所必须要提供给这个方法的条件。(写在中括号里,表示可以省略,即不是所有的函数都有参数。 但是小括号不能省略)
  • return的作用:
  1. 在函数中返回要返回的值
  2. 立即结束本次函数,就算return后面有一万条代码,都不执行

小细节

  1. 一个函数不能写在另一个函数的里面,比如不能写在Main函数里面。要保持和它同级的关系。所以可以写在类的里面,Main函数的下面。
  2. 方法的功能一定要单一
  3. 方法中最忌讳的就是出现提示用户输入的字眼
  4. 实参和形参,类型和个数,前后都要匹配
  5. 在写一个函数的时候,最好最好要写上文档注释,直接三个///,自动生成。详见Day 001.
  6. 代码写在Main函数中才会执行。所以写完一个函数必须要在Main函数中调用,才能够执行
  7. 在调用一个没有学过的函数时,要学会尝试去看函数的解释。在C#中每个函数都给了详细的解释。这时只需在方法名后面输入一个括号"(",在系统自动补全后的同时也会显示这个函数的作用。(前提是写了文档注释)
  8. 光标移到函数上时,弹出的窗口的左上角永远是函数的返回值
  9. 可以选取一段代码,右键,重构,提取方法,系统会自动生成一个方法,然而并没有什么卵用
  10. 在某些情况下,类名是可以被省略的:
  • 如果写的方法跟Main()函数同在一个类中,这时类名可以被省略。

  • 如果写的方法跟Main()函数不在一个类中,这是类名不可以被省略

  • 调用语法

类名.方法名(参数);

一个栗子

  • 计算两个整数之间的最大值
internal class Program {  
	public static void Main(string[] args) {
	int max = Program.GetMax(1027, 203); 
	//声明一个int类型的变量max来接受返回值是int类型的函数GetMax
	//Program类名,GetMax是函数名。这句话调用了Program类中的GetMax函数。
	Console.WriteLine(max);
	Console.ReadKey();
	}

	/// <summary>
	/// 计算了两个整数的最大值并返回
	/// </summary>
	/// <param name="n1">第一个整数</param>
	/// <param name="n2">第二个整数</param>
	/// <returns>将最大值返回</returns>
	public static int GetMax(int n1, int n2) { //n1,n2是参数
		return n1 > n2 ? n1 : n2; 
		//可以直接利用三元表达式,比较n1,n2的大小,若n1 > n2为True,输出第二个值,即n1. 反之懂得都懂。
		//return在这里起到 返回要返回的值 的作用
	}
}

/* 输出:
1027
*/
  • return的作用:
  1. 在函数中返回要返回的值
  2. 立即结束本次函数,就算return后面有一万条代码,都不执行
  • 因为返回的是两个整数的最大值,所以返回值类型应该是int
  • 求两个整数的最大值,说明这个函数的参数就是两个整数,是必须要提供给这个方法的条件。

函数的调用问题

变量的作用域 Day003

  • 变量的作用域就是你能够使用到这个变量的范围
  • 变量的作用域一般从声明这个变量的两个相对应的大括号内起作用,出了这两个大括号,就不能被访问并使用了。
  • 下面的程序运行时会报错:
class Program{
	static void Main(string[] args){
		int a = 3;
		Program.Test();
		Console.WriteLine(a);
		Console.ReadKey();
	}
	static void Test(){
		a = a + 5; //这里会报错,因为Test函数中并未声明变量a,或者说无法访问并使用到Main函数里面的a
	}
}
  • 由变量的作用域可知,能访问并使用变量a的范围就在Main函数里
  • 因此,在Test这个函数中并不能访问并使用到Main函数里所声明的变量a
  • 在上面,我们在Main()函数中,调用Test()函数,我们把Main()函数称之为调用者,把Test()函数称之为被调用者。
  • 如果被调用者想要得到调用者的值,或者说有几个函数都要用到同一种、同一个变量,那么就可以:
  1. 传递参数
class Program{
	static void Main(string[] args){
		int a = 3;
		Program.Test(a); 
		//这时便不报错了,将Main函数中的a作为参数传递给了Test函数里
		//这时,Program.Test(a);中的a,其实是Main函数里面,通过int a = 3所声明的a,是调用函数所给的参数,不是Test函数中的a,两者的性质不同。
		Console.WriteLine(a); //这里会输出3
		Console.ReadKey();
	}
	static void Test(int a){
		a = a + 5; //此时a= 3 + 5 = 8
		//这时便不报错了,将Main函数中的a作为参数传递给了Test函数里
	}
}
  1. 使用静态字段来模拟全局变量
  • 全局变量: 哪都够使用的变量。

  • 静态字段: 因为在c#中并没有全局变量这个东西,所以需要静态字段来实现和全局变量一样的功能。

  • 要声明字段,不能写在函数里面,所以要写在类或者结构。但在这并没有结构的事儿,所以可以直接写在类的里面。

class Program{
	//字段 属于类的字段。
	public static int _number = 10; //给int类型的字段_number赋初值10
	//用public访问修饰符,表示公开公共,哪都能访问
	static void Main(string[] args){
		int a = 3;
		Program.Test(a);
		Console.WriteLine(a); //这里会输出3
		Console.WriteLine(_number) // 这里会输出10
		Console.ReadKey();
	}
	static void Test(int a){
		a = a + 5; //此时a= 3 + 5 = 8
		Console.WriteLine(_number) //这里会输出10
	}
}
  • 为什么称之为静态字段,或者说它能够模拟全局变量呢?

  • 因为它的作用范围在整个Program类中,所以Program类中的每个函数都能访问到字段_number。

  • 但是,在上面的两个程序里,最终输出的a依然是3,说明Test函数里面的a并没有影响到外面的Main函数的a

  • 因为在上面,Test函数并没有返回值

  • 所以,只需要在Test函数中把返回值类型改为int,而不是void,并且要在Test函数中运用return来返回返回值。并且也要在Main函数中再声明一个变量来接受Test函数所返回的返回值

class Program{
	static void Main(string[] args){
		int a = 3;
		int res = Program.Test(a); 
		//这时便不报错了,将Main函数中的a作为参数传递给了Test函数里
		//这时,Program.Test(a);中的a,其实是Main函数里面,通过int a = 3所声明的a,是调用函数所给的参数,不是Test函数中的a,两者的性质不同。
		Console.WriteLine(res); //这里会输出8
		Console.ReadKey();
	}
	static int Test(int a){
		a = a + 5;  //此时a = 8
		return a; //return在这里起到 返回要返回的值 的作用
		//这时便不报错了,将Main函数中的a作为参数传递给了Test函数里
	}
}
  • 由上可知,如果想要在调用者当中得到被调用者的值
  1. 返回值

练习

  • 写一个方法,判断一个年份是不是闰年,所以要判断是否是闰年
class Program{
	static void Main(string[] args){
	bool leapBool = LeapBool(2000);
	Console.WriteLine(leapBool);
	Console.ReadKey();
	}
	/// <summary>
	/// 判断输入的年份是否是闰年
	/// </summary>
	/// <param name="year">用户输入的年份</param>
	/// <returns>返回判断是否是闰年</returns>
	public static bool LeapBool(int year){
	//报错:并非所有的代码路径都有返回值:
	//原因:写了返回值的类型,但没有返回任何值
		bool b = (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
		return b;
	}
}
// 输出: True
  • 通过函数读取用户输入的整数,如果是数字,则返回;如果不是,则提示用户重新输入
class Program{
	static void Main(string[] args){
		Console.WriteLine("请输入一个数字");
		string input = Console.ReadLine();
		int number = output(input);
		Console.WriteLine(number);
		Console.ReadKey();
	}
	
	/// <summary>
	/// 判断用户输入是否是数字,如果是数字则返回,如果不是则提示重新输入
	/// </summary>
	/// <param name="year">用户输入的年份</param>
	/// <returns>返回判断是否是闰年</returns>
	public static int output(string str){
		while (true){
			try{
				int output = Convert.ToInt32(str);
				return output;
			}
			catch{
				Console.WriteLine("输数字!");
				s = COnsole.ReadLine();
				//不写上面这句话,s的值就永远不会改变,于是永远卡在while循环中,无限地报错.
			}
		}
	}
}
  • 计算一个int类型的数组内元素的总和
	/// <summary>
	/// 计算一个int类型数组的总和
	/// </summary>
	/// <param name="nums">数组</param>
	/// <returns>返回数组内元素总和</returns>
public static int GetSum(int[] nums){
	int sum = 0;
	for (int i = 0; i < nums.Length; i++){
		sum += nums[i];
	}
	return sum;
}

函数的重载

函数的重载就是许多函数的名称相同,但是参数不同。(两个条件都要满足),且与返回值的类型无关
函数的重载能够使让我们用一句话去调用很多函数。

例如,在Console.WriteLine这个函数(WriteLine是类Console内部的一个函数),就使用了函数的重载,因此我们才能在这个函数的实参里使用各种类型的变量。

  • 参数不同,分为两种情况 (名称都必须要相同)
  1. 如果参数的个数相同,那么参数的类型就不能相同
  2. 如果参数的类型相同,那么参数的个数就不能相同

函数的递归

递归简单点说就是方法自己调用自己
比如,用程序找出一个文件夹中所有的文件,如果里面还有文件夹,程序就还要再找出这个文件夹里面所有的文件...反反复复,直到没有文件夹后,才算找出了所有的文件。

  • 然而,递归就和循环一样,总得要有个条件让他停下来。例如上一条,找不到文件夹了,就是让这个递归停下来的条件。
  • 所以就需要一个变量来记录这个循环的条件,就像for循环里的i
  • 但是我们如果把int i = 0声明在函数中、形参中,都会陷入一个死循环(i每次都会被重新赋值为0)
  • 所以我们联想到今天学的,通过使用静态字段来模拟全局变量,这样在函数中就无法将i重新赋值啦

但是递归也像我们走门一样。举个例子:
当小冰穿过10扇门去见完保密之后,小冰return了,但也必须再往回一道道地穿过那10道门才能回去。所以一旦小冰要开始递归,他就得走过20道门

高级参数

out参数

在TryParse函数中,其中一个参数就有用到out。
那么下面我们就来看看高级参数out的用法

什么时候会用到out参数?

在一个函数里若要返回多个不同类型的值时,就会用到out参数

  • out参数就侧重于在一个方法中返回多个不同类型的值。
  • 写一个函数,求一个数组中最的大小总均。
  • 但是在一个函数中只能return一个值!所以我们想到用数组包装四个值,所以就return了一个数组
class Program{
	static void Main(string[] args){
		int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,};
		//将要返回的4个值,放到一个数组中返回。
		int[] result = AISA(nums);
		Console.WriteLine("最大值是{0},最小值是{1},总值{2},均值{3}", res[0], res[1], res[2], res[3]);
		Console.ReadKey;		
	}
	static int[] AISA(int[] n){
	//int是返回单个int类型的变量,int[]是返回int类型的数组
		int[] res = new int[4];
		//因为只返回大小总均,所以声明一个int类型的数组res存4个所以需要的值
		/* 假设 
		res[0]存最大值
		res[1]存最小值
		res[2]存总值
		res[3]存均值
		*/

	/// <summary>
	/// 计算一个int类型数组的大小总均
	/// </summary>
	/// <param name="n">一个数组</param>
	/// <returns>用数组包装的大小总均</returns>
		res[0] = nums[0]; //假设最大值是数组的第一个元素
		res[1] = nums[0]; //假设最小值也是数组的第一个元素
		res[2] = 0; //声明总和,也可以当做是假设总和为0
		for (int i = 0; i < nums.Length - 1; i++){
			if(nums[i]>res[0]) res[0] = nums[i];
			//如果当前循环到的元素比假定的最大值还要大,那就将其赋值给它
			if(nums[i]<res[1]) res[1] = nums[i];
			//如果当前循环到的元素比假定的最小值还要小,那就将其赋值给它
			res[2] += nums[i];
		}
		res[3] = res[2] / nums.Length;
		return res;
	}
}
  • 但是,当你如果返回多个不同类型的值后,数组就不行惹。所以我们就用到了高级参数out

out参数的使用实例

  • 通过out。写一个函数,求一个数组中最的大小总均。
class Program{
	static void Main(string[] args){
	// 如何调用通过out参数返回的值
	int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,};
	//声明一个数组nums来接收ASIA函数中返回的数组。
	//但其实nums的元素顺序、大小都没有改变。(因为不需要,只是让函数对其进行读取)
	int max1;
	int min1;
	int sum1;
	int avg1;
	bool b;
	string str;
	double d;
	//声明四个变量来分别接收AISA函数中返回的大小总均
	//声明必须要先赋值再使用,但在这里已经通过AISA函数赋值了。
	AISA(nums, out max1, out min1, out sum1, out avg1, out b, out str, out d);
	// 在变量前对应地写上out,表示这个参数是函数多余返回的值
	Console.WriteLine("最大值是{0},最小值是{1},总值{2},均值{3}", max, min, sum, avg);
	Console.WriteLine(b);
	Console.WriteLine(str);
	Console.WriteLine(d);
	Console.ReadKey();
	}

	/// <summary>
	/// 计算一个int类型数组的大小总均
	/// </summary>
	/// <param name="n">要求大小总均的数组</param>
	/// <param name="max">多余返回的最大值</param>
	/// <param name="min">多余返回的最小值</param>
	/// <param name="sum">多余返回的总值</param>
	/// <param name="abg">多余返回的均值</param>

	static void AISA(int[] n,out int max, out int min, out int sum, out int avg, out bool b, out string str, out double d){ 
	//在形参列表里用out写上多余返回的值
	// out表示跟着out的参数是需要多余返回的参数
	// out参数要求在方法的内部必须为其赋值。

		/* 在这里,max,min,sum,avg这四个值已经在形参列表被声明过了
		   所以不需要写上变量类型*/
		max = nums[0]; 
		min = nums[0];
		sum = 0;
		for (int i = 0; i < nums.Length; i++){
			if(nums[i] > max) max = nums[i];
			if(nums[i] < min) min = nums[i];
			sum += nums[i];
		}
		avg = sum / nums.Length
		b = true;
		str = "mua";
		double d = 20.3;

		//这时不需要return,因为该函数时的返回值是void,没有返回值
		//所以多余返回值不需要return
	}
}
/* 输出: 
最大值是9,最小值是0,总值45,均值4
True
mua
20.3
*/

细节

  • out参数要求在方法的内部必须为其赋值

ref参数

什么时候会用到ref参数?

  • 假定小冰未来的月薪是1027元,某天他的老板大彻大悟,决定加赏203元

  • 但是这样写函数太麻烦了,要写返回值,要return,还要声明变量来接受...

  • 想将一个变量以参数的形式传递给一个函数,在这个函数内进行改变,改变后再传递回Main函数进行输出,就可以找高级参数ref 来帮忙

  • 用法

  • 分别在各个形参、实参前加上ref

class Program{
	static void Main(string[] args){
		double bingSalary = 1027 ;
		Prize(ref bingSalary);
		Console.WriteLine(bingSalary);
		Console.ReadKey();
	}
	public static void Prize(ref double s){
		s += 203; 
	}
}

练习

  • 通过方法来交换两个int类型的变量
class Program{
	static void Main(string[] args){
		int n1 = 203;
		int n0 = 1027;
		//ref参数要求必须在函数的外面赋值
		Trade(ref n1, ref n0);
		Console.WriteLine(n1);
		Console.WriteLine(n0);
		Console.ReadKey();
	}
	public static void Trade(ref int n1, ref int n2){
		n1 = n1 - n2;
		n2 = n1 + n2;
		n1 = n2 - n1;
	}
}

细节

  • ref参数要求必须在函数的外面赋值

params可变参数

将实参列表中跟可变参数数组类型一致的元素都当做数组的元素去处理
当然填数组也依然适用。

为什么会用到params可变参数

  • 有一个人各科的成绩,求这个人的总成绩
class Program{
	static void Main(string[] args){
		int[] bingScores = { 118, 132, 112, 186, 88, 16}
		Test("小冰大憨憨", bingScores)
	}

	public static void Test(string name, int[] score){
		int sum = 0; //声明总成绩为0,用于累积
		for (int i = 0; i < score.Length; i++){
			sum += score[i];
		}
		Console.WriteLine("{0}这次考试的总成绩是{1}", name, sum)
	}
}
// 输出: 小冰这次考试的总成绩是652
  • 众所周知,程序中变量的数量越少越好,所以我们能否可以不声明数组呢?

  • 当然可以!! 可是这样参数类型要怎么对上去呢?Test函数里要用数组,Main函数里如何不通过数组给定参数? 那就请来了params可变参数来帮帮小冰吧

  • 运用params可变参数

class Program{
	static void Main(string[] args){
		Test("小冰大憨憨", 118, 132, 112, 186, 88, 16)
	}

	public static void Test(string name, params int[] score){
	//在上面 params int[] score就是一个!==可变参数数组==!
		int sum = 0; //声明总成绩为0,用于累积
		for (int i = 0; i < score.Length; i++){
			sum += score[i];
		}
		Console.WriteLine("{0}这次考试的总成绩是{1}", name, sum)
	}
}
// 输出: 小冰这次考试的总成绩是652
  • params可变参数 把这一大串数字(118, 132, 112, 186, 88, 16)处理成了一个数组中的元素

细节

  • 参数数组必须是形参列表中的最后一个参数
  • 不能传递多个可变参数,因此可变参数具有唯一性

练习

  • 求任意长度的,整数类型的数组的总值。
  • 就可以使用params可变参数进行模拟一个数组
class Program{
	static void Main(string[] args){
		int sum = GetSum(1,2,3,4,5,6,7,8,9);
		Console.WriteLine();
		Console.ReadKey;
	}
	static int GetSum(params int[] n){
		int sum = 0;
		for (int i = 0; i < n.Length; i++){
			sum += n[i];
		}
		return sum;
	}
}

其他小东西

  • 保留两位小数
  • (通过占位符保留并不是真正的保留两位小数,原来的数并没有被处理)
string str = avg.ToString("0.00");
avg = Convert.ToDouble(s);
// 先把double类型转为string类型,保留两位,再强转为double类型,完成位数的保留
Console.WriteLine(avg);

上面这个代码,能和占位符实现一样的功能——四舍五入


sentences

这几天跑医院跑麻了w

:3

posted @ 2022-03-24 20:40  冰导有盒饭  阅读(35)  评论(0)    收藏  举报