第五章 方法
5.1 方法
- 方法:由方法头和方法体两部分构成,方法头包括返回类型、方法名、参数列表;方法体是一个由花括号括起来的语句块。
- 方法体可以包含本地变量、控制流结构、方法调用和内嵌的块。
- 本地变量:保存本地或者临时的计算数据,而字段保存与对象状态有关的数据。本地变量的存在性与生存期仅限于创建它的块以及内嵌的块,它从声明开始存在,到块执行结束后消失。
- 类型推断关键字var:用于从初始化语句的右边推断出变量或实例的类型,只能用于本地变量,不能用于字段;只能在声明中包含初始化时使用;一旦变量的类型推断出来就不可更变;如
var Varials1 = 15;var Varials2 = new Myclass();
- 块的嵌套:本地变量在嵌套的块中定义时,生存期也只在所声明的那个嵌套块中。
- 本地常量:声明时必须初始化,必须带有关键字const,一旦声明,其值不可改变。
const int varl = 16;
- 控制流:一些控制语句,包括选择语句如if…else、switch语句;迭代循环语句
如for()、while、do…while、foreach()语句;跳转语句如break、continue、goto、return语句。 - 方法调用:格式为 实例名.方法名(参数列表),保证方法是可访问的,否则就是类名.方法名(参数列表),其实在笔记(二)里面,写到访问类的(方法)成员时,就已经说明是方法调用了。
5.2 参数
- 参数:形参和实参。形参是本地变量,声明时必须有类型和名称,且在方法执行之前被赋值,由实参传递,并确保类型匹配。
- 值参数 :用于数据传递,将实参的值赋值给形参,在方法调用时,先在栈中为形参分配空间,然后将实参的值赋给形参。
- 引用参数:在方法的声明和调用时都要有修饰符ref,实参必须是变量,且在用作实参前必须被赋值引用类型变量可以赋值为一个引用或null。用法如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
namespace test1
{
public class Myclass
{
public int val = 20;
}
class Program
{
//引用参数的基本使用
static void Mymethod(ref Myclass f1,ref int f2)
{
f1.val = f1.val + 5; //参数的字段加5
f2 = f2 + 5; //另一参数加5
Console.WriteLine("f1.val:{0},f2:{1}",f1.val,f2);
}
static void Main(string[] args)
{
//格式化的输出
Console.WriteLine("A value of the number is {0,6:C2}:",206.5623);
Myclass a1 = new Myclass();
int a2 = 10; //变量在使用之前必须被赋值
Mymethod(ref a1,ref a2); //实参必须是变量
Console.WriteLine("f1.val:{0},f2:{1}",a1.val,a2);
}
}
}
运行结果:
- 引用类型作为值参数和引用参数:作为值参数传递时,如果在方法内创建一个新对象并赋值给形参,将切断型参与实参之间的关联,方法调用结束后,新对象不复存在;作为引用参数传递,如果在方法内创建一个新对象并赋值给形参,在方法调用结束后该对象依然存在,并且实参为所引用的值,此时形参就好像是实参的别名,同时指向相同的内存空间。用法如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
namespace test2
{
public class Myclass
{
public int val = 20;
}
class Program
{
//引用类型对象作为值参数传递
static void Myrefasparameter1(Myclass f1)
{
f1.val = 50;
Console.WriteLine("after member assignment:{0}",f1.val);
f1 = new Myclass();
Console.WriteLine("after new object creation:{0}",f1.val); //实参与形参同时指向初始值
}
//引用类型对象作为引用参数传递
static void Myrefasparameter2(ref Myclass f2)
{
f2.val = 50;
Console.WriteLine("after member assignment:{0}",f2.val);
f2 = new Myclass();
Console.WriteLine("after new object creation:{0}",f2.val);
}
static void Main(string[] args)
{
Myclass a1 = new Myclass();
Console.WriteLine("引用类型作为值参数传递");
Console.WriteLine("before method call:{0}",a1.val);
Myrefasparameter1(a1);
Console.WriteLine("after method call:{0}",a1.val); //方法调用结束后,方法内的新对象消失,实参指向方法内被赋给的值
Console.WriteLine("----------------------------------------");
Myclass a2 = new Myclass();
Console.WriteLine("引用类型作为引用参数传递");
Console.WriteLine("before method call:{0}",a2.val);
Myrefasparameter2(ref a2);
Console.WriteLine("after method call:{0}",a2.val); //方法调用结束之后,实参和形参同时指向初始值
}
}
}
结果如下:
- 输出参数: 在方法的声明和调用时都要有修饰符out,实参必须是变量,输出参数的形参也是实参的别名,同时指向相同的内存空间,对形参的任何操作在方法调用之后对实参变量都是可见的;不同于引用参数,在方法内部,输出参数在能够被读取之前必须被赋值,也就没有必要在方法调用之前为实参赋值;在方法返回之前,在方法内部要为所有的输出参数赋值。用法如下:
//输出参数的基本使用:修饰符out,在方法内部对输出参数赋值
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
namespace test4
{
public class Myclass
{
public int val = 20;
}
class Program
{
static void Mymethod(out Myclass f1,out int f2) //形参是实参的别名,它们指向相同的内存位置
{
f1 = new Myclass();
f1.val = 66;
f2 = 666;
}
static void Main(string[] args)
{
Myclass a1 = new Myclass();
a1 = null;
int a2; //没有必要在方法调用前为实参赋值
Mymethod(out a1,out a2); //实参必须是变量
Console.WriteLine("a1.val:{0},a2:{1}",a1.val,a2);
}
}
}
执行结果:
- 参数数组: 一个参数列表只能有一个参数数组;如果有参数数组,必须位于列表的最后;参数列表中的所有参数具有相同的数据类型;声明参数数组:数据类型前加修饰符params,数据类型后加中括号,如声明一个整型参数数组:
public void Mymethod(params int[] vals){...};
使用数字下标索引访问数组;注意:参数数组的修饰符params只在声明时使用,方法调用时没有修饰符。 - 为参数数组传递实参:两种方式如下,
Mymethod(10,20,30); //方法一,直接赋值,逗号隔开
int[] inits = {10,20,30}; //方法二,先初始化参数数组,再传递给该方法。
Mymethod(inits);
int[] other = new int[] {10,20,30}; //创建并初始化一个数组 ,逗号间隔
- 命名参数:位置参数与位置有关,实参与形参要匹配;命名参数不讲究顺序,参数的声明与位置参数一样,只是在方法调用时,要用到形参名,在形参名后加冒号和实际的参数值或者表达式,如:
public class Myclass
{
//声明
public void Mythod(int a,int b,int c,int d)
{
Console.WriteLine("a+b+c+d = {0}",a+b+c+d);
}
}
//调用
Myclass a = new Myclass();
a.Mythod(c:2,b:6,a:8,d:10); //形参名:实参值
- 方法重载:指的是类中的多个方法具有相同的名称,但必须保证签名各不相同;方法的签名位于方法头,包括:方法名称,参数数目,参数类型,参数修饰符,但不包括返回类型和形参名。
long Mythod(int a,int b) { return a+b; }
long Mythod(inta,int b,int c) { return a+b+c; }
double Mythod(double a,double b) { return a+b; }
- 栈帧:局部变量和参数存储在栈上,方法调用时,内存从栈顶开始分配,保存和方法相关联的数据项,包括返回地址、参数所分配的内存以及其他与方法调用相关联的数据项。方法调用时,整个栈帧被压入栈,调用结束后,整个又被弹出栈。
- 递归:方法调用自身。
class Program
{
public void Count(int val) //方法调用时形参存入栈
{
if(val == 0)
return;
Count(val - 1); //调用自身
Console.WriteLine("{0}",val);
}
static void Main()
{
Program pr = new Program();
pr.Count(5);
}
}
执行结果: