3. 字段和局部變量
為了區(qū)分作用域不同的對(duì)象或值類(lèi)型的變量,C#對(duì)其進(jìn)行了更細(xì)的劃分:把類(lèi)一級(jí)的對(duì)象或值類(lèi)型的變量叫做字段;把在方法、事件以及構(gòu)造函數(shù)內(nèi)聲明的變量叫做局部變量。聲明變量的位置不同,其作用域也不同。
【例3-2】定義字段和局部變量。
using System;
using System.Collections.Generic;
using System.Text;
namespace DefinitionExample
{
public class Program
{
public static int j=20; //定義字段j
public static void Main()
{
int j=30; //定義局部變量j
Console.WriteLine(j); //輸出結(jié)果:30
Console.WriteLine(Program.j); //輸出結(jié)果:20
Console.ReadLine();
}
}
}
在這個(gè)例子中,定義了一個(gè)字段j,是類(lèi)一級(jí)的變量;在Main方法中又定義了一個(gè)同名的局部變量j,此時(shí),在Main方法中聲明的新變量j隱藏了同名的類(lèi)一級(jí)的變量,所以第一條Console語(yǔ)句輸出的結(jié)果是30。
當(dāng)字段和局部變量名相同時(shí),如果要引用靜態(tài)字段,可以使用下面的形式:
類(lèi)名.字段名
如果是實(shí)例字段,則使用下面的形式:
this.字段名
這里的this指當(dāng)前實(shí)例。
當(dāng)然,如果沒(méi)有出現(xiàn)字段和局部變量名重名的情況,引用字段的形式和引用局部變量的形式相同。
在類(lèi)中定義的數(shù)據(jù)稱(chēng)為類(lèi)的數(shù)據(jù)成員,數(shù)據(jù)成員包含字段、常量和事件等。而函數(shù)成員則提供操作類(lèi)中數(shù)據(jù)的某些功能,函數(shù)成員包括方法、屬性、構(gòu)造函數(shù)等。對(duì)象中的數(shù)據(jù)成員和方法一般都是對(duì)象私有的,即只有對(duì)象本身才能夠操作這些數(shù)據(jù)成員和調(diào)用這些方法,其他對(duì)象不能直接對(duì)其進(jìn)行操作??墒怯幸恍┏蓡T是所有對(duì)象共用的,如果把這樣的成員保存在每一個(gè)對(duì)象上,當(dāng)創(chuàng)建了一個(gè)類(lèi)的多個(gè)對(duì)象時(shí),在堆中就會(huì)出現(xiàn)很多相同的內(nèi)容,顯然浪費(fèi)資源。此外,若要修改這些成員,也必須對(duì)每一個(gè)對(duì)象都進(jìn)行修改,很明顯這樣做效率也比較低。
解決這個(gè)問(wèn)題的辦法是將成員定義為靜態(tài)(static)的,當(dāng)該類(lèi)被裝入內(nèi)存時(shí),系統(tǒng)就會(huì)在內(nèi)存中專(zhuān)門(mén)開(kāi)辟一部分區(qū)域保存這些靜態(tài)成員,這樣一來(lái),其他類(lèi)不必建立該類(lèi)的實(shí)例就可以直接使用該類(lèi)的靜態(tài)成員。
把只有創(chuàng)建了類(lèi)的實(shí)例才能夠使用的成員稱(chēng)為實(shí)例成員。
需要注意的是,靜態(tài)成員在內(nèi)存中只有一份,不像實(shí)例成員可以有多個(gè)。而且靜態(tài)成員要等到應(yīng)用程序結(jié)束時(shí)才會(huì)消失,所以使用時(shí)要根據(jù)具體情況決定是否使用靜態(tài)成員。
在C#中通過(guò)指定類(lèi)名來(lái)調(diào)用靜態(tài)成員,通過(guò)指定實(shí)例名來(lái)調(diào)用實(shí)例成員。
將數(shù)據(jù)和方法封裝在類(lèi)中是為了便于對(duì)數(shù)據(jù)和方法進(jìn)行控制和修改,訪問(wèn)修飾符用于控制類(lèi)中數(shù)據(jù)和方法的訪問(wèn)權(quán)限,C#中有以下幾種成員訪問(wèn)修飾符。
1) public
指任何外部的類(lèi)都可以不受限制的存取這個(gè)類(lèi)的方法和數(shù)據(jù)成員。
2) private
指類(lèi)中的所有方法和數(shù)據(jù)成員只能在此類(lèi)中使用,外部無(wú)法存取。
3) protected
除了讓本身的類(lèi)可以使用之外,任何繼承自此類(lèi)的子類(lèi)都可以存取。
4) internal
在當(dāng)前項(xiàng)目中都可以存取。該訪問(wèn)權(quán)限一般用于基于組件的開(kāi)發(fā),因?yàn)樗梢允菇M件以私有方式工作,而該項(xiàng)目外的其他代碼無(wú)法訪問(wèn)。
5) protected internal
只限于當(dāng)前項(xiàng)目,或者從該項(xiàng)目的類(lèi)繼承的類(lèi)才可以存取。
6) partial
局部類(lèi)型,類(lèi)的定義和實(shí)現(xiàn)可以分布在多個(gè)文件中,但都要使用partial標(biāo)注。注意,基類(lèi)只需要聲明一次,若多次聲明則必須完全一致。
3.1.2 構(gòu)造函數(shù)
構(gòu)造函數(shù)是一個(gè)特殊的方法,用于在建立對(duì)象時(shí)進(jìn)行初始化的動(dòng)作,在C#中,每當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),都會(huì)先調(diào)用類(lèi)中定義的構(gòu)造函數(shù)。
使用構(gòu)造函數(shù)的好處是它能夠確保每一個(gè)對(duì)象在被使用之前都適當(dāng)?shù)剡M(jìn)行了初始化的動(dòng)作。
另外,構(gòu)造函數(shù)還具有以下特點(diǎn):
1) 每個(gè)類(lèi)至少有一個(gè)構(gòu)造函數(shù)。如果程序代碼中沒(méi)有構(gòu)造函數(shù),則系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的構(gòu)造函數(shù)。
2) 一個(gè)構(gòu)造函數(shù)總是和它的類(lèi)名相同。
3) 構(gòu)造函數(shù)不包含任何返回值。
4) 構(gòu)造函數(shù)總是public的。
一般在構(gòu)造函數(shù)中作初始化工作,對(duì)于執(zhí)行過(guò)程用時(shí)比較長(zhǎng)的程序代碼,最好不要放在構(gòu)造函數(shù)中。
如果在類(lèi)中不定義構(gòu)造函數(shù),系統(tǒng)會(huì)自動(dòng)提供一個(gè)默認(rèn)的構(gòu)造函數(shù),默認(rèn)構(gòu)造函數(shù)沒(méi)有參數(shù)。提供默認(rèn)構(gòu)造函數(shù)的目的是為了保證能夠在使用對(duì)象前先對(duì)未初始化的非靜態(tài)類(lèi)成員進(jìn)行初始化工作,即將非靜態(tài)成員初始化為下面的值:
1) 對(duì)數(shù)值型,比如int、double等,初始化為0。
2) 對(duì)bool類(lèi)型,初始化為false。
3) 對(duì)引用類(lèi)型,初始化為null。
如果在程序代碼中定義了類(lèi)的構(gòu)造函數(shù),則所有初始化工作由編程者自己完成。
有時(shí)候可能會(huì)遇到這樣的情況:在一個(gè)類(lèi)中的多個(gè)方法中都要用到某一個(gè)數(shù)據(jù)成員,而該成員值必須從其他類(lèi)中傳遞過(guò)來(lái)。這時(shí),無(wú)參數(shù)的構(gòu)造函數(shù)就不能勝任了,解決這個(gè)問(wèn)題最好的辦法就是:重載(Overloading)構(gòu)造函數(shù)。
【例3-3】重載構(gòu)造函數(shù)。
using System;
using System.Collections.Generic;
using System.Text;
namespace OverloadingExample
{
class Program
{
public Program()
{
Console.WriteLine("null");
}
public Program(string str)
{
Console.WriteLine(str);
}
static void Main()
{
Program aa = new Program();
Program bb = new Program("How are you!");
Console.ReadLine();
}
}
}
輸出結(jié)果:
null
How are you!3.1.3 方法
方法(Method)是一組程序代碼的集合,每個(gè)方法都有一個(gè)方法名,便于識(shí)別和讓其他方法調(diào)用。
C#程序中定義的方法都必須放在某個(gè)類(lèi)中。定義方法的一般形式為:
訪問(wèn)修飾符 返回值類(lèi)型 方法名稱(chēng)(參數(shù)序列)
{
語(yǔ)句序列
}
在定義方法時(shí),需要注意以下幾點(diǎn):
1) 方法名稱(chēng)后面的小括號(hào)中可以有參數(shù)序列,也可以沒(méi)有參數(shù),但是不論是否有參數(shù),小括號(hào)都是必需的。如果參數(shù)序列中的參數(shù)有多個(gè),則以逗號(hào)分隔開(kāi)。
2) 如果要結(jié)束某個(gè)方法的執(zhí)行,可以使用return語(yǔ)句。程序遇到return語(yǔ)句后,會(huì)將執(zhí)行流程交還給調(diào)用此方法的程序代碼段。此外,還可以利用return語(yǔ)句返回一個(gè)值,注意,return語(yǔ)句只能返回一個(gè)值。
3) 如果聲明一個(gè)void類(lèi)型的方法,return語(yǔ)句可以省略不寫(xiě);如果聲明一個(gè)非void類(lèi)型的方法,則方法中必須至少有一個(gè)return語(yǔ)句。
【例3-4】定義和調(diào)用方法。
using System;
using System.Collections.Generic;
using System.Text;
namespace MethodExample
{
class Program
{
public int MyMethod()
{
Console.WriteLine("this is MyMethod.");
int i = 10;
return i;
}
static void Main()
{
Program method = new Program();
int j = 5;
j = method.MyMethod();
Console.WriteLine("the value is {0}.", j);
Console.ReadLine();
}
}
}
輸出結(jié)果:
this is MyMethod.
the value is 10.
定義方法時(shí)可以將參數(shù)傳入方法中進(jìn)行處理,也可以將方法中處理過(guò)的信息返回給調(diào)用者。傳遞變量參數(shù)到方法的方式有下面幾種:
1) 傳遞值類(lèi)型的參數(shù)
值類(lèi)型參數(shù)的格式為:
參數(shù)類(lèi)型 參數(shù)名
定義值類(lèi)型參數(shù)的方式很簡(jiǎn)單,只要注明參數(shù)類(lèi)型和參數(shù)名即可。當(dāng)該方法被調(diào)用時(shí),便會(huì)為每個(gè)值類(lèi)型參數(shù)分配一個(gè)新的內(nèi)存空間,然后將對(duì)應(yīng)的表達(dá)式運(yùn)算的值復(fù)制到該內(nèi)存空間。在方法中更改參數(shù)的值不會(huì)影響到這個(gè)方法之外的變量。
【例3-5】方法中值類(lèi)型參數(shù)的傳遞。
using System;
using System.Collections.Generic;
using System.Text;
namespace ValueTransferExample
{
class Program
{
public static void AddOne(int a)
{
a++;
}
static void Main()
{
int a = 3;
Console.WriteLine("調(diào)用AddOne之前,a={0}", a);
AddOne(a);
Console.WriteLine("調(diào)用AddOne之后,a={0}", a);
Console.ReadLine();
}
}
}
輸出結(jié)果:
調(diào)用AddOne之前,a=3
調(diào)用AddOne之后,a=3
2) 傳遞引用類(lèi)型的參數(shù)
引用類(lèi)型參數(shù)的格式為:
ref 參數(shù)類(lèi)型 參數(shù)名
與傳遞值類(lèi)型參數(shù)不同,引用類(lèi)型的參數(shù)并沒(méi)有再分配內(nèi)存空間,實(shí)際上傳遞的是指向原變量的指針,既引用參數(shù)和原變量保存的是同一個(gè)地址。為了和傳遞值類(lèi)型參數(shù)區(qū)分,前面加上ref關(guān)鍵字(Reference),在方法中修改引用參數(shù)的值實(shí)際上也就是修改被引用的變量的值。
【例3-6】方法中引用類(lèi)型參數(shù)的傳遞。
using System;
using System.Collections.Generic;
using System.Text;
namespace ReferenceTransferExample
{
class Program
{
public static void AddOne(ref int a)
{
a++;
}
static void Main()
{
int x = 3;
Console.WriteLine("調(diào)用AddOne之前,x={0}", x);
AddOne(ref x);
Console.WriteLine("調(diào)用AddOne之后,x={0}", x);
Console.ReadLine();
}
}
}
輸出結(jié)果:
調(diào)用AddOne之前,x=3
調(diào)用AddOne之后,x=4
3) 輸出多個(gè)引用類(lèi)型的參數(shù)
有時(shí)候一個(gè)方法計(jì)算的結(jié)果有多個(gè),而return語(yǔ)句一次只能返回一個(gè)結(jié)果,這時(shí)就用到了out關(guān)鍵字,使用out表明該引用參數(shù)是用于輸出的,而且調(diào)用該參數(shù)時(shí)不需要對(duì)參數(shù)進(jìn)行初始化。
輸出引用類(lèi)型參數(shù)的格式為:
out 參數(shù)類(lèi)型 參數(shù)名
【例3-7】輸出多個(gè)引用類(lèi)型的參數(shù)。
using System;
using System.Collections.Generic;
using System.Text;
namespace ReferenceOutExample
{
class Program
{
public static void MyMethod(out int a, out int b)
{
a = 5;
b = 6;
}
static void Main()
{
int x, y;
MyMethod(out x, out y);
Console.WriteLine("調(diào)用MyMethod之后,x={0},y={1}", x, y);
Console.ReadLine();
}
}
}
輸出結(jié)果:
調(diào)用MyMethod之后,x=5,y=6
4) 傳遞個(gè)數(shù)不確定的參數(shù)
當(dāng)需要傳遞的參數(shù)個(gè)數(shù)不確定時(shí),比如求幾個(gè)數(shù)的平均值,由于沒(méi)有規(guī)定是幾個(gè)數(shù),運(yùn)行程序時(shí),每次輸入的值的個(gè)數(shù)就不一定一樣。為了解決這個(gè)問(wèn)題,C#采用params關(guān)鍵字聲明參數(shù)的個(gè)數(shù)是不確定的。
【例3-8】傳遞個(gè)數(shù)不確定的參數(shù)。
using System;
using System.Collections.Generic;
using System.Text;
namespace UncertaintyTransferExample
{
class Program
{
public static float Average(params long[] v)
{
long total, i;
for (i = 0, total = 0; i < v.Length; ++i)
total += v[i];
return (float)total / v.Length;
}
static void Main()
{
float x = Average(1, 2, 3, 5);
Console.WriteLine("1、2、3、5的平均值為{0}", x);
x = Average(4, 5, 6, 7, 8);
Console.WriteLine("4、5、6、7、8的平均值為{0}", x);
Console.ReadLine();
}
}
}
輸出結(jié)果:
1、2、3、5的平均值為2.75
4、5、6、7、8的平均值為6
方法重載是指具有相同的方法名,但參數(shù)類(lèi)型或參數(shù)個(gè)數(shù)不完全相同的多個(gè)方法可以同時(shí)出現(xiàn)在一個(gè)類(lèi)中。這種技術(shù)非常有用,在開(kāi)發(fā)過(guò)程中,我們會(huì)發(fā)現(xiàn)C#中的很多方法均使用了重載技術(shù)。
【例3-9】方法重載。
using System;
using System.Collections.Generic;
using System.Text;
namespace MethodOverloadingExample
{
class Program
{
public static int Add(int i, int j)
{
return i + j;
}
public static string Add(string s1, string s2)
{
return s1 + s2;
}
public static long Add(long x)
{
return x + 5;
}
static void Main()
{
Console.WriteLine(Add(1, 2));
Console.WriteLine(Add("1", "2"));
Console.WriteLine(Add(10));
//按回車(chē)鍵結(jié)束
Console.ReadLine();
}
}
}
輸出結(jié)果:
3
12
15
在這個(gè)例子中,雖然有多個(gè)Add方法,但由于方法中參數(shù)的個(gè)數(shù)和類(lèi)型不完全相同,所以系統(tǒng)在調(diào)用時(shí)會(huì)自動(dòng)找到最匹配的方法。