C#的方法
在面向过程的语言如C语言中,数据和对数据的操作通常分为两部分。在C++语言中,大多数数据成为类的数据成员,而大多数对数据的操作放在了类的成员方法中。

C#实现了完全意义上的面向对象:任何事物都必须封装在类中,或者作为类的实例成员---没有全局常数、全局变量,也没有全局方法。

 1. C#方法的声明
方法是类中用于执行计算或其它行为的成员。我们看一下方法的声明格式:

method-header method-body

其中方法头method-header的格式:

attributes method-modifiers return-type member-name(formal-parameter-list)

传递给方法的参数在方法的形式化参数表formal-parameter-list中声明,我们将随后进行详细论述。

在方法的声明中,至少应包括方法名称、修饰符和参数类型,返回值和参数名则不是必须的。

注意:方法名member-name不应与同一个类中的其它方法同名,也不能与类中的其它成员名称相同。

修饰符

方法的修饰符method-modifier可以是:

●new

●public

●protected

●internal

●private

●static

●virtual

●sealed

●override

●abstract

●extern

对于使用了abstract和extern修饰符的方法,方法的执行体method-body仅仅只有一个简单的分号。其它所有的方法执行体中应包含调用该方法所要执行的语句。

返回值

方法的返回值的类型可以是合法的C#的数据类型。C#在方法的执行部分通过return语句得到返回值,如:


using System;
class Test
{
  public int max(int x,int y){
    if(x>y)
       return x;
    else
       return y;
  }
  public void Main(){
     Console.WriteLine("the max of 6 adn 8 is:{0}",max(6,8));
  }
}程序的输出是:

the max of 6 and 8 is:8

如果在return后不跟任何值,方法返回值是void型的。
.2  C#方法中的参数
C#中方法的参数有四种类型:

●值参数,不含任何修饰符。

●引用型参数,以ref修饰符声明。

●输出参数,以out修饰符声明。

●数组型参数,以params修饰符声明。
3.2.1  值参数
当利用值向方法传递参数时,编译程序给实参的值做一份拷贝,并且将此拷贝传递给该方法。被调用的方法不传经修改内存中实参的值,所以使用值参数时,可以保证实际值是安全的。在调用方法时,如果形式化参数的类型是值参数的话,调用的实参的值必须保证是正确的值表达式。在下面的例子中,程序员并没有实现他希望交换值的目的:


using System;
class Test
{
  static void Swap(int x,int y){
     int temp=x;
     x=y;
     y=temp;
  }
     static void Main(){
     int i=1,j=2;
     Swap(i,j);
     Console.WriteLine("i={0},j={1}",i,j);
  }
}编译上述代码,程序将输出:

i=1,j=2

3.2.2 引用型参数

和值参不同的是,引用型参数并不开辟新的内存区域。当利用引用型参数向方法传递形参时,编译程序将把实际值在内存中的地址传递给方法。

在方法中,引用型参数通常已经初始化。再看下面的例子。

using System;
class Test
{
  static void Swap(ref int x,ref int y){
      int temp=x;
      x=y;
      y=temp;
  }
      static void Main(){
      int i=1,j=2;
      Swap(ref i,ref j);
      Console.WriteLine("i={0},j={1}",i,j);
   }
}编译上述代码,程序将输出:

i=2,j=1

Main函数中调用了Swap函数,x代表i,y代表j。这样,调用成功地实现了i和j的值交换。

在方法中使用引用型参数,会经常可能导致多个变量名指向同一处内存地址。见示例:

class A
{
  string s;
     void F(ref string a,ref string b){
     s="One";
     a="Two";
     b="Three";
  }
     void G(){
     F(ref s,ref s);
  }
}在方法G对F的调用过程中,s的引用被同时传递给了a和b。此时,s,a,b同时指向了同一块内存区域。
3.2.3 输出参数

与引用参数类似,输出参数也不开辟新的内存区域。与引用型参数的差别在于,调用方法前无需对变量进行初始化。输出型参数用于传递方法返回的数据。

out修饰符后应跟随与形参的类型相同的类型声明。在方法返回后,传递的变量被认为经过了初始化。


using System;
class Test
{
  static void SplitPath(string path,out string dir,out string name){
     int i=path.Length;
     while(i>0){
        char ch=path[i-1];
        if(ch=='\\'||ch=='/'||ch==':')break;
        i--;
     }
     dir=path.Substring(0,i);
     name=path.Substring(i);
   }
     static void Main(){
     string dir,name;
     SplitPath("c:\\Windows\\System\\hello.txt",out dir,out name);
     Console.WriteLine(dir);
     Console.WriteLine(name);
   }
}可以预计,程序的输出将会是:

c:\windows\System\
hello.txt

我们注意到,变量dir和name在传递给SplitPath之前并未初始化,在调用之后它们则有了明确的值。

3.2.4 数组型参数

如果形参列表中包含了数组参数,那么它必须在参数列表中位于最后。另外,参数只允许是一维数组。比如,string[]和string[][]类型都可以作为数组形参数,而string[,]则不能。最后,数组型参数不能再有ref和out修饰符。

using System;
class Test
{
 static void F(params int[] args){
      Console.WriteLine("Array contains{0} elements:",args.Length);
      foreach(int i in args) Console.Write("{0}",i);
      Console.WriteLine();
 }
      public static void Main(){
      int[] a={1,2,3};
      F(a);
      F(10,20,30,40);
      F();
   }
}程序输出:

Array contains 3 elements:1 2 3
Array contains 4 elements:10 20 30 40
Array contains 0 elements:

在上例中,第一次调用F是简单地把数组a作为值参数传递;第二次调用把已给出数值的数组传递给了F;而在第三次调用中,F创建了含有0个元素的整型数组作为参数传递。后两次调用完整的写法应该是:

F(new int[] {10,20,30});
F(new int[] {});

3.3  C#静态和非静态的方法
C#的类定义中可以包含两种方法:静态和非静态的。使用了static修饰符的方法为静态方法,反之则是非静态的。

静态方法是一种特殊的成员方法,它不属于类的某一个具体的实例。非静态方法可以访问类中的任何成员,而静态只能访问类中的静态成员。看这个例子:

class A
{
 int x;
 static int y;
 static int F(){
      x=1; //错误,不允许访问
      y=2; //正确,允许访问
 }在这个类定义中,静态方法F()可以访问类中静态成员y,但不能访问非静态成员x。这是因为,x作为非静态成员,在类的每个实例中都占有一个存储(或者说具有一个副本),而静态方法是类所共享的,它无法判断出当前的x是属于哪个类的实例,所以不知道应该到内存的哪个地址去读取当前x的值。而y是静态成员,所有类的实例都公用一个副本,静态方法F使用它就不存在什么问题。
那么,是不是静态方法就无法识别类的实例了呢?在C#中,我们可以灵活地采用传递参数的办法。第十章我们提到了一个Windows窗口的例子,这里我们再对这个例子进行一些改变。


using System;
class Window
{
 public string m_caption;  //窗口的标题
 public bool IsActive; //判断是否被激活
 public handle m_handle; //窗口的句柄
 public static int m_total; //当前打开的窗口数目
 public handle Window(){
    m_total++; //窗口总数加1
    //......创建窗口的一些执行代码
    return m_handle; //窗口的返回值作为句柄
 }
 ~Window(){
     m_total--; //窗口总数减1
     //......撤消窗口的一些执行代码
 }
  public static string GetWindowCaption(Window w)
  {
    return w.m_caption;
  }
    //......窗口的其它成员
}分析一下上面例子中的代码。每个窗口都有窗口标题m_caption、窗口句柄m_handle、窗口是否激活IsActive三个非静态的数据成员(窗口句柄是Windows操作系统中保存窗口相关信息的一种数据结构,我们在这个例子中简化了对句柄的使用)。系统中总共打开的窗口数目m_total作为一个静态成员。每个窗口调用构造函数创建,这时m_total的值加1。窗口关闭或因为其它行为撤消时,通过析构函数m_total的值减1.

我们要注意窗口类的静态方法GetWindowCaption(Window w)。这里它通过参数w将对象传递给方法执行,这样它就可以通过具体的类的实例指明调用的对象,这时它可以访问具体实例中的成员,无论是静态成员还是非静态成员。
    
    
[url]http://www.ithuhang.cn[/url]