我们在执行很多方法都要传参才能调用,同样我们写的很多方法都要接收一个参数才能完成逻辑。传参去调用别人写的方法,这好像并没有多少要注意的,只需要按要求传就可以了。但是当我们自己写的方法时,怎么设置这个参数呢?需要什么拿什么,这句话没错。但是有时候我们往往可以不需要传参。
说到参数我们都会先分一下形参和实参。
形参,形参就是在创建方法时括号里面声明的那个参数。你可以理解成一个“模型”。调用方法时,实参需要跟这个“模型”做对比,符合才能够传进去。也就是说实参和形参类型必须一致,需要传多个参数时,顺序也必须一致。
实参,就是调用方法是你传进去的那个变量的值。
也就是说,我创建方法时提供了一个壳,我方法里边的逻辑算法全都是用这个壳先顶着。如果方法一运行,它从我这里拿不到值,会给我报错,这我不干。所以当你需要调用我时,你得给我一个东西,让别人向我拿的时候,我能给到别人。还有我原本说这是个苹果,你总不能让我那个西瓜出来吧。所以,我问你拿什么,你就得给什么。壳理解为形参,因为它的样子(类型)跟实参一样。传进来的实参就是那个实实在在的东西。
当然,在创建方法时,可以给形参赋个默认值,这样调用方法的时候就可以不传参数。
接下来我们了解下传参。
值参数
按值传递,传递实参变量存储的内容。存什么传什么,值类型就传存的值,引用类型就传引用。作用:传递信息。
我们一般的传参默认都是值传参。
static void Main(string[] args) { int number = 1; Fun(number); Console.WriteLine("number={0}", number);//结果number = 1 //number是int,值类型,在栈中的{number地址:1} //值传参,传值,传的是1 //也就是Fun(number)跟Fun(1)没有区别,那么这样一看Fun(1)跟number就没有关系了, //所以Fun()内部怎么操作这个数值都跟number没有关系。 Console.ReadKey(); } static private void Fun(int parameter) { //parameter=1 parameter = 2;//{parameter地址:1}==>{parameter地址:2}改的是parameter }
PS:值类型在栈中也是有地址的:{地址:值},引用类型在栈中:{地址:堆中数据的地址}。
引用参数
按引用传递,传递参数变量自身的内存地址。在创建方法时和调用方法,都需要在参数前面加上ref关键字。作用:改变数据
static void Main(string[] args) { int number = 1; Fun(ref number);//引用传参必须加ref Console.WriteLine("number={0}", number);//结果number = 2 //PS:值类型在栈中也是有地址的:{地址:值},引用类型在栈中:{地址:堆中数据的地址}。 //number是int,值类型,在栈中的{number地址:1} //引用传参,传引用,传的是number地址 //Fun(number地址)跟Fun(1)肯定有区别了,number地址不等于1 //那么这样Fun(number地址)跟number能直观的看到有关系 Console.ReadKey(); } static private void Fun(ref int parameter) { //方法内部修改引用参数,实质上就是修改实参变量。 //parameter此时此刻就是number //parameter=1 parameter = 2;//{number地址:1}==>{number地址:2}改的是number }
输出参数
也是按引用传递,传递参数变量自身的内存地址。在创建方法时和调用方法,都需要在参数前面加上out关键字。作用:返回结果
static void Main(string[] args) { int number;//输出参数,传递前可以不赋值 Fun(out number);//传递输出参数必须加out Console.WriteLine("number={0}", number);//结果number = 2 Console.ReadKey(); } static private void Fun(out int parameter) { //跟引用参数的区别:1,方法内部必须为输出参数赋值,输出参数在传递之前可以不赋值 //parameter此时此刻就是{number地址:} parameter = 2;//{number地址:}==>{number地址:2}本来没有值,赋了2. }
如果有多个结果需要返回时,可以使用输出参数。
优化传参
有些时候我们可以不用参数。这样可以简化我们的代码,让代码看起来更简洁和清晰。
static void Main(string[] args) { int[] numberArray = new int[10]; numberArray = Fun(numberArray);//调用方法赋值 //numberArray数组是引用类型,引用类型在栈中:{numberArray地址:numberArray在堆中数据的地址} //这里是值传递,传的是:numberArray在堆中数据的地址 } /// <summary> /// 给数组赋值 /// </summary> /// <param name="array">目标数组</param> /// <returns>目标数组</returns> static private int[] Fun(int[] array) { //array在栈中{array地址:numberArray在堆中数据的地址} //numberArray在堆中数据的地址==>数据1 for (int i = 0; i < array.Length; i++) { array[i] = i; } //到最后我只是把数据改变了,数据1变成了数据2:numberArray在堆中数据的地址==>数据2, //而array在栈中{array地址:numberArray在堆中数据的地址},没有任何变化。 //甚至最后需要返回的值 numberArray在堆中数据的地址 跟传进来的一模一样。 //也就是说,方法内部并不需要对 numberArray在堆中数据的地址 进行操作。要操作的是这个地址指向的那些数据。 //所以,不需要传给我,但是又能让我拿的到就行。 return array; }
只要将numberArray声明为全局变量,那么所有的方法都能不需要通过传参随意拿到。因为numberArray是数组,而数组是引用类型。所以我们不管在哪个方法中改变了该引用(地址)在堆中的数据,那么通过同一个引用(地址),其他方法获取的都是同一份已改过的数据。
所以以上代码可以改为
static int[] numberArray; static void Main(string[] args) { //Fun();在这里调这个方法会出现错误,因为这时的numberArray在栈中{numberArray地址:},没有堆中数据的地址,找不到空间给它赋值 numberArray = new int[10];//{numberArray地址:}==>{numberArray地址:堆中数据的地址},给它在堆中开辟一块空间。并将堆中空间的地址赋该变量 Fun();//这时再调这个方法,numberArray在栈中就变成了:{numberArray地址:堆中数据的地址} } /// <summary> /// 给数组赋值 /// </summary> static private void Fun() { //根据numberArray堆中数据的地址找到堆中那块空间,将数据存进去 for (int i = 0; i < numberArray.Length; i++) { numberArray[i] = i; } }