主题:C#的方法
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]
在面向过程的语言如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]