标签:避免 消失 栈帧 混淆 console 关于 因此 代码块 结果
方法是一块具有名称的代码。
可以使用方法的名称从别的地方执行代码,也可以把数据传入方法并接收数据输出。
方法是类的函数成员,主要有两个部分,方法头和方法体。

int MyMethod(int par1,string par2) ↑ ↑ ↑ 返回 方法 参数 类型 名称 列表
方法体可包含以下项目
static void Main()
{
int myInt = 3; //本地变量
while(myInt > 0) //控制流结构
{
--myInt;
PrintMyMessage(); //方法调用
}
}
与类的字段一样,本地变量也保存数据。字段通常保存和对象状态有关的数据,而本地变量通常用于保存本地的或临时的计算数据。

观察下面的代码,你会发现编译器其实能从初始化语句的右边推断出来类型名。
所以在这两种情况中,在声明开始的显式的类型名是多余的。
static void Main()
{
int myInt = 15;
MyExcellentClass mec = new MyExcellentClass();
...
}
为了避免这种冗余,可以在变量声明开始的显式类型名位置使用var关键字
static void Main()
{
var myInt = 15;
var mec = new MyExcellentClass();
...
}
var不是特定的类型变量符号。它表示任何可以从初始化语句的右边推断出来的类型。
使用var有一些重要的条件
说明:var关键字不像JavaScript的var那样可以引用不同的类型。它是从等号右边推断出的实际类型的速记。var关键字并不改变C#的强类型性质。
方法体内部可以嵌套其他的块

说明:在C和C++中,可以先声明一个本地变量,然后在嵌套块中声明另一个同名本地变量。在内部范围,内部变量覆盖外部变量。然而,在C#中不管嵌套级别如何,都不能在第一个本地变量的有效范围内声明另一个同名本地变量。
本地常量一旦被初始化就不能改变了,且必须声明在块的内部
常量的两个重要特征
常量声明语法
关键字
↓
const Type Identifier = Value;
↑
初始化值是必须的
控制流指的是程序从头到尾的执行流程。
默认情况下,程序从上到下执行,控制流语句允许你改变执行顺序。
void SomeMethod()
{
int intVal = 3;
if(intVal == 3)
{
Console.WriteLine("Value is 3.");
}
for(int i=0;i<5;i++)
{
Console.WriteLine("Value of i:{0}",i);
}
}
可以从方法体的内部调用(call/invoke)其它方法
调用方法时要使用方法名并带上参数列表

方法可以向调用代码返回一个值
返回类型
↓
int GetHour()
{
DateTime dt = DateTime.Now;
int hour = dt.Hour; //获取当前小时
return hour;
↑
返回语句
}
也可以返回用户定义类型的对象
返回类型---MyClass
↓
MyClass method3()
{
MyClass mc = new MyClass();
...
return mc;
}
return例
class MyClass
{
↓ void返回类型
void TimeUpdate()
{
DateTime dt = DateTime.Now;
if(dt.Hour<12)
return;
Console.WriteLine("It‘s afternoon!");
}
static void Main()
{
MyClass mc = new MyClass();
mc.TimeUpdate();
}
}

参数允许你在方法开始执行时把数据传入方法,或是在一个方法体中返回多个返回值。
形参是本地变量,它声明在方法的参数列表中,而不是在方法体中
public void PrintSum(int x, float y)
{ ↑
... 形参声明
}
形参在整个方法体内使用,在大部分地方就像其他本地变量一样

第二次调用,编译器把int 5 和 someInt隐式转换成了float

使用值参数,通过将实参的值复制到形参的方式把数据传递给方法。方法被调用时,系统做如下操作
你应该记得第3章介绍了值类型,所谓值类型就是指类型本身包含其值。不要把值类型和这里介绍的值参数混淆,它们是完全不同的两个概念。值参数是把实参的值复制给形参。
class MyClass
{
public int Val=20;
}
class Program
{
static void MyMethod(MyClass f1,int f2)
{
f1.Val=f1.Val+5;
f2=f2+5;
Console.WriteLine("f1.val:{0},f2:{1}",f1.Val,f2);
}
static void Main()
{
MyClass a1=new MyClass();
int a2=10;
MyMethod(a1,a2);
Console.WriteLine("f1.Val:{0},f2:{1}",a1.Val,a2);
}
}


包含ref修饰符
↓
void MyMethod(ref int val)
{
...
}
int y = 1;
MyMethod(ref y);
↑
包含ref修饰符
MyMethod(ref 3+5); //报错
↑
必须使用变量
对于值参数,系统在栈上为形参分配内存,引用参数则不同
由于形参名和实参名的行为就好像指向相同内存位置,所以在方法的执行过程中对形参做的任何改变在方法完成后依然有效
clas MyClass
{
public int Val = 20;
}
class Program
{
ref修饰符 ref修饰符
↓ ↓
static void MyMethod(ref MyClass f1,ref int f2)
{
f1.Val=f1.Val+5;
f2=f2+5;
Console.WriteLine("f1.Val:{0},f2:{1}",f1.Val,f2);
}
static void Main()
{
MyClass a1=new MyClass();
int a2 =10;
ref修饰符
↓ ↓
MyMethod(ref a1,ref a2);
Console.WriteLine("f1.Val:{0},f2:{1}",a1.Val,a2);
}
}


从前几节看到,对于引用类型对象,不管是将其作为值参数传递还是引用参数传递,我们都可以在方法成员内部修改它的成员。不过我们并没有在方法内部修改形参本身。本节来看看方法内修改引用类型形参会发生什么。
例:将引用类型对象作为值参数传递
class MyClass{public int Val=20;}
class Program
{
static void RefAsParameter(MyClass f1)
{
f1.Val=50;
Console.WriteLine("After meber assignment:{0}",f1.Val);
f1=new MyClass();
Console.WriteLine("After new object creation:{0}",f1.Val);
}
static void Main()
{
MyClass a1=new MyClass();
Console.WriteLine("Before method call:{0}",a1.Val);
RefAsParameter(a1);
Console.WriteLine("After method call:{0}",a1.Val);
}
}


例:将引用类型对象作为引用参数传递
class MyClass{public int Val=20;}
class Program
{
static void RefAsParameter(ref MyClass f1)
{
f1.Val=50;
Console.WriteLine("After meber assignment:{0}",f1.Val);
f1=new MyClass();
Console.WriteLine("After new object creation:{0}",f1.Val);
}
static void Main(string[] args)
{
MyClass a1=new MyClass();
Console.WriteLine("Before method call:{0}",a1.Val);
RefAsParameter(ref a1);
Console.WriteLine("After method call:{0}",a1.Val);
}
}

引用参数的行为就像是将实参作为形参的别名。

输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数非常类似。
输出参数有以下要求
class MyClass
{
public int Val=20;
}
class Program
{
static void MyMethod(out MyClass f1,out int f2)
{
f1=new MyClass();
f1.Val=25;
f2=15;
}
static void Main()
{
MyClass a1=null;
int a2;
MyMethod(out a1,out a2);
}
}

上述的参数类型都必须严格地一个实参对应一个形参。参数数组则不同,它允许零个或多个实参对应一个特殊的形参
声明参数数组必须做的事如下
例:int型参数数组声明语法
void ListInts(params int[] inVals)
{...}
可以使用两种方式为参数数组提供实参
在使用一个为参数数组分离实参的调用时,编译器做下面的事
class MyClass
{
public void ListInts(params int[] inVals)
{
if((inVals!=null)&&(inVlas.Length!=0))
{
for(int i=0;i<inVals.Length;i++)
{
inVals[i]=inVals[i]*10;
Console.WriteLine("{0}",inVals[i]);
}
}
}
}
class Program
{
static void Main()
{
int first=5,second=6,third=7;
MyClass mc=new MyClass();
mc.ListInts(first,second,third);
Console.WriteLine("{0},{1},{2}",first,second,third);
}
}

关于参数数组,需记住重要的一点是当数组在堆中被创建时,实参的值被复制到数组中。它们就像值参数。

直接把数组变量作为实参传递,这种情况下,编译器使用你的数组而不是重新创建一个。

一个类中可以用一个以上的方法拥有相同名称,这叫方法重载(method overload)。使用相同名称的方法必须有一个和其他方法不同的签名(signature)
例:4个AddValue的重载
class A
{
long AddValues(int a,int b){return a+b;}
long AddValues(int c int d,int e){return c+d+e;}
long AddValues(float f,float g){return (long)(f+g);}
long AddValues(long h,long m){return h+m;}
}
例:错误的重载
class B
{
long AddValues(long a,long b){return a+b;}
int AddValues(long c,long d){return c+d;}
}
至今我们所用到的参数都是位置参数,每个实参的位置都必须与相应的形参位置一一对应。
C#允许我们使用命名参数(named parameter),只要显式指定参数名字,就可以以任意顺序在方法调用中列出实参
例:使用命名参数的结构
class MyClass
{
public int Calc(int a,int b,int c)
{
return (a+b)*c;
}
static void Main()
{
MyClass mc=new MyClass();
int r0 = mc.Calc(4,3,2);
int r1 = mc.Calc(4,b:3,c:2);
int r2 = mc.Calc(4,c:2,b:3);
int r3 = mc.Calc(c:2,b:3,a:4);
int r4 = mc.Calc(c:2,b:1+2,a:3+1);
Console.WriteLine("{0},{1},{2},{3},{4}",r0,r1,r2,r3,r4);
}
}
代码输出

命名参数对于自描述程序来说很有用,我们可以在方法调用时显示那个值赋给那个形参。
例:使用命名参数 增强程序易读性
class MyClass
{
double GetCylinderVolume(double radius,double height)
{
return 3.1416*radius*radius*height;
}
static void Main(string[] args)
{
MyClass mc=new MyClass();
double volume;
volume = mc.GetCylindreVolume(3.0,4.0);
...
volume = mc.GetCylindreVolume(radius:3.0,height:4.0)
}
}
可选参数就是我们可以在调用方法时包含这个参数,也可以省略。
为了表名某参数可选,你需要在方法声明时为参数提供默认值
class MyClass
{
public int Calc(int a ,int b=3)
{
return a+b;
}
static void Main()
{
MyClass mc=new MyClass();
int r0=mc.Calc(5,6);
int r1=mc.Calc(5);
Console.WriteLine("{0},{1}",r0,r1);
}
}



当有多个可选参数时,默认情况下只能省略后面几个
class MyClass
{
public int Calc(int a=2,int b=3,int c=4)
{
return (a+b)*c;
}
static void Main()
{
MyClass mc=new MyClass();
int r0=mc.Calc(5,6,7);
int r1=mc.Calc(5,6);
int r2=mc.Calc(5);
int r3=mc.Calc();
Console.WriteLine("{0},{1},{2},{3}",r0,r1,r2,r3);
}
}

当有多个可选参数时,可以通过参数名字来选择可选参数
class MyClass
{
double GetCylinderVolume(double radius=3.0,double height=4.0)
{
return 3.1416*radius*radius*height;
}
static void Main()
{
MyClass mc=new MyClass();
double volume;
volume =mc.GetCylindervoume(3.0,4.0)://位置参数
Condole.WriteLine("Volume="+volume);
volume =mc.GetCylindervoume(radius:2.0)://使用hieght默认参数
Condole.WriteLine("Volume="+volume);
volume =mc.GetCylindervoume(height:2.0)://使用radius默认参数
Condole.WriteLine("Volume="+volume);
volume =mc.GetCylindervoume()://使用默认值
Condole.WriteLine("Volume="+volume);
}
}

至此,我们已经知道局部变量和参数是位于栈上的,再来深入探讨一下其组织。
调用方法时,内存从栈顶开始分配,保存和方法关联的一些数据项。这块内存叫做方法的栈帧(stack frame)。
例:下面代码声明了3个方法。Main调用MethodA,MethodA调用MethodB,创建了3个栈帧。方法退出时,栈展开。
class Program
{
static void MethodA(int par1,int par2)
{
Console.WriteLine("Enter MethodA:{0},{1}",par1,par2);
MethodB(11,18);
Console.WriteLine("Exit MethodA");
}
static void MethodB(int par1,int par2)
{
Console.WriteLine("Enter MethodB:{0},{1}",par1,par2);
Console.WriteLine("Exit MethodB");
}
static void Main()
{
Console.WriteLine("Enter Main");
MethodA(15,30);
Console.WriteLine("Exit Main");
}
}

调用方法时栈帧压栈和栈展开的过程

除了调用其他方法,方法也可以调用自身。这就是递归。
递归会产生很优雅的代码。
class Program
{
public void Count(int inVal)
{
if(inVal==0)
{
return;
}
else
{
Count(inVal-1);
Console.WriteLine("{0}",inVal);
}
}
static void Main()
{
Program pr=new Program();
pr.Count(3);
}
}


from: http://www.cnblogs.com/moonache/p/6063051.html
标签:避免 消失 栈帧 混淆 console 关于 因此 代码块 结果
原文地址:https://www.cnblogs.com/GarfieldEr007/p/10126552.html