技術(shù)內(nèi)幕事實(shí)上,每種語言都有自己的數(shù)據(jù)類型,比如VB.NET中的Integer,在C#中對應(yīng)的數(shù)據(jù)類型為int。但不管是VB.NET還是C#,或是另外一種編程語言,只要這個(gè)語言編出來的程序是運(yùn)行于.NET之上的,則都要被轉(zhuǎn)換為NET Framework中的公共語言運(yùn)行時(shí)所提供的數(shù)據(jù)類型。正是由于有這個(gè)特點(diǎn),才使得.NET Framework支持混合語言開發(fā),即VB.NET代碼可以調(diào)用C#代碼,這在以前的軟件運(yùn)行平臺(tái)上是很難實(shí)現(xiàn)的。在這些數(shù)據(jù)類型中,用得最多的是整數(shù)(Integer和Long)和小數(shù)(Double和Single),小的整數(shù)用Integer,大的整數(shù)用Long。小的小數(shù)用Single,值很大的小數(shù)用Double。另一種非常重要的類型是String,它代表由字符組成的字符串。在前面的程序中已看到過它的身影,由于其特殊的重要性,將在2.2.6節(jié)中專門介紹其使用方法。知道了VB.NET擁有的數(shù)據(jù)類型,那么這些數(shù)據(jù)類型有什么用?它們是怎樣用在編程中的?可以用一句話概括為:數(shù)據(jù)類型在編程中主要用于定義常量和變量。解釋一下:在程序中會(huì)用到許許多多的數(shù)據(jù),每種數(shù)據(jù)一定屬于某種數(shù)據(jù)類型。打個(gè)比方,一周有7天,那么,這個(gè)“7”就是一個(gè)數(shù)據(jù),它的類型是Integer(整型)。在軟件中,數(shù)據(jù)本身用常量和變量來表達(dá),由此可以得出一個(gè)結(jié)論:變量和常量一定屬于一種數(shù)據(jù)類型。下面介紹變量和常量的知識。1.常量所謂常量,就是在程序運(yùn)行過程中始終不變的量。比如,公歷一年有12個(gè)月,這個(gè)概念可以用以下的定義來表達(dá):Public Const MonthsPerYear As Integer = 12一旦這樣定義了之后,就可以在代碼中使用MonthsPerYear這個(gè)標(biāo)識符來代替12這個(gè)數(shù)字。把上述信息用標(biāo)簽控件(假設(shè)其為Label1)表達(dá),可以這樣寫代碼:Label1.Text= "公歷一年有" + cstr(MonthsPerYear) + "個(gè)月"這句完全等同于:Label1.Text= "公歷一年有" + cstr(12) + "個(gè)月"要定義一個(gè)常量,應(yīng)按照以下格式:Public Const 常量名 As 數(shù)據(jù)類型=值在上面的格式中,數(shù)據(jù)類型可以是.NET支持的所有數(shù)據(jù)類型。下面是一個(gè)字符串類型常量的例子:Public Const ErrorMsg As String= "出錯(cuò)了!"
試一試請定義一個(gè)常量來表示“中國有56個(gè)民族”。
注意在程序中用常量代替數(shù)字和字符串,是個(gè)好習(xí)慣。為什么要這么做?想想就明白了:假如程序中在許多地方都要用到一個(gè)特殊意義的數(shù)字,比如999,那么,在需要使用這個(gè)特殊意義的場合使用一個(gè)單詞(比如MaxNumber)來表達(dá)無疑是更易于閱讀與理解的。比對一下以下兩段代碼:(1)Private const MaxNumber As Integer=999'……If i>MaxNumber thenMsgbox "數(shù)據(jù)太大了!"End if(2)If i>999 thenMsgbox "數(shù)據(jù)太大了!"End if哪個(gè)更易懂?再想想下面這個(gè)情況:某個(gè)程序中多次需要將一個(gè)數(shù)與最大的三位數(shù)999進(jìn)行比較,因此在某個(gè)程序代碼中“999”這個(gè)數(shù)字出現(xiàn)了10次。現(xiàn)在,程序版本需要升級,可以支持四位數(shù)字的比較,因而原有代碼中的“999”就必須修改為“9999”,必須修改10處代碼。在修改過程中還可能發(fā)現(xiàn)代碼中出現(xiàn)的某個(gè)“999”只是一個(gè)普通的數(shù)字999,并不具備“最大的三位數(shù)”這個(gè)含義,不應(yīng)被改為“9999”。這樣一來,修改代碼工作就很麻煩了,必須仔細(xì)地閱讀其前后的代碼,才能確定是否應(yīng)該將此“999”改為“9999”。而如果在需要表達(dá)“最大的數(shù)字”這個(gè)含義的地方不用具體的數(shù)字,而用一個(gè)常量MaxNumber來表達(dá)(即按第1種方式編程),則只需要修改常量的定義就行了,上面出現(xiàn)的問題全部都可以避免。字符串常量是用雙引號定界的,那么,如果需要在字符串中包含雙引號,應(yīng)怎樣處理?例如,要把以下這句話:He said "Good Morning!"定義為字符串常量,必須這樣做:Public const WhatHeSaid as String=" He said "" Good Morning! "" "注意需要把雙引號連續(xù)寫兩次。2.變量在使用計(jì)算機(jī)語言編程時(shí),經(jīng)常需要定義變量。所謂變量,可以這樣理解,它就是一個(gè)存放信息的容器,容器的大小和類型是固定的,而容器中存放的具體東西可以不同。例如,以下代碼定義了一個(gè)整型變量:Dim i as Integer這表明i是一個(gè)可用于存放整數(shù)的容器,可以把任何一個(gè)整數(shù)放到容器中。如:i=100上述代碼意味著這個(gè)“整數(shù)瓶子”中裝的數(shù)是100,再來一句:i=200這意味著這個(gè)“整數(shù)瓶子”現(xiàn)在裝的數(shù)是200,原來的“100”被丟棄。如果接著寫一句代碼:i=i+1則意味著先取出“整數(shù)瓶子”中裝的數(shù),把這個(gè)整數(shù)加一,之后再重新裝到瓶子中,最終瓶子內(nèi)保存的數(shù)是201。下面從軟件技術(shù)的角度來解釋變量。每個(gè)變量一定屬于一種數(shù)據(jù)類型,表2-5中列出了VB.NET常用的數(shù)字類型,并給出了每種數(shù)據(jù)類型所占的存儲(chǔ)空間。定義一個(gè)變量,其實(shí)就是在內(nèi)存中分配一個(gè)剛好可以容納這個(gè)數(shù)據(jù)類型的空間(比如Integer占4個(gè)字節(jié));向變量賦值,其實(shí)就相當(dāng)于把這個(gè)值寫到這個(gè)變量所代表的內(nèi)存空間中。請看以下代碼:Dim i as Integer計(jì)算機(jī)執(zhí)行完上述語句之后,其內(nèi)存分配如圖2-45所示。對于VB.NET,如果定義一個(gè)整數(shù)變量時(shí)沒有指定其初始值,則自動(dòng)設(shè)置為“0”。
技術(shù)內(nèi)幕在VB.NET中,定義數(shù)字類型的變量(不管是整數(shù)還是小數(shù))都會(huì)自動(dòng)初始化為0值;定義一個(gè)字符串變量,則自動(dòng)初始化為空串;定義一個(gè)對象類型的變量,則自動(dòng)初始化為Nothing。對于C/C++這樣的語言,定義一個(gè)變量只會(huì)根據(jù)這個(gè)變量的類型而在內(nèi)存中分配一個(gè)空間,但并不會(huì)自動(dòng)給定一個(gè)初值。因此,在用C/C++編程時(shí),在定義一個(gè)變量的同時(shí)給定一個(gè)初始值是一個(gè)良好的習(xí)慣。執(zhí)行以下語句給變量i賦值:i=100上述代碼執(zhí)行之后,內(nèi)存中的情況如圖2-46所示。變量之間可以相互賦值,請看以下代碼:Dim i As Integer = 100Dim j As Integerj = i上述代碼執(zhí)行完畢以后,其內(nèi)存布局如圖2-47所示。
技術(shù)內(nèi)幕除一些特殊情況外,不同類型的變量不能相互直接賦值。如果確實(shí)需要這樣做,就必須對變量進(jìn)行類型轉(zhuǎn)換。VB.NET可以自動(dòng)完成許多轉(zhuǎn)換工作,比如可以把一個(gè)整數(shù)直接賦值給字符串類型的變量。VB.NET提供了一個(gè)新的命令用于設(shè)置類型轉(zhuǎn)換是否自動(dòng)進(jìn)行:Option Strict { On | Off } '強(qiáng)制類型轉(zhuǎn)換開關(guān)此開關(guān)默認(rèn)是關(guān)閉的,當(dāng)此開關(guān)打開時(shí),VB.NET將不會(huì)自動(dòng)進(jìn)行類型轉(zhuǎn)換。打開此開關(guān)后,以下代碼將無法通過編譯:Dim i As Integer=100Dim s As Strings = i必須修改為:Dim i As Integer=100Dim s As Strings =Ctr(i)cstr()函數(shù)將整數(shù)轉(zhuǎn)為字符串。若將Option Strict一句刪除,原來的代碼就可以通過編譯了。4.枚舉(Enum)
技術(shù)探索請使用MSDN查找MsgBox()函數(shù)的使用方法。現(xiàn)在的問題是:怎樣知道用戶單擊的是哪個(gè)按鈕?這是由MsgBox()函數(shù)的返回值來決定的。MsgBox()函數(shù)返回如表2-6所示的值。表2-6 MsgBox()函數(shù)返回值返 回 值值含 義MsgBoxResult.OK1按下【確定】按鈕MsgBoxResult.Cancel2按下【取消】按鈕MsgBoxResult.Abort3按下【中止】按鈕MsgBoxResult.Retry4按下【重試】按鈕MsgBoxResult.Ignore5按下【忽略】按鈕MsgBoxResult.Yes6按下【是】按鈕MsgBoxResult.No7按下【否】按鈕可以看到,MsgBox()函數(shù)其實(shí)是返回一系列數(shù)字的,但這些數(shù)字都有一個(gè)名稱來表達(dá)(比如MsgBoxResult.OK)。這種類似于數(shù)字常量的數(shù)值稱為枚舉。MsgBoxResult是此枚舉類型的名字。枚舉類型的使用方法如下:Dim ret As MsgBoxResultret = MsgBox("是否退出?", MsgBoxStyle.YesNo Or MsgBoxStyle.Information,_"詢問操作")If ret = MsgBoxResult.Yes Then'單擊了Yes按鈕Else'單擊了No按鈕End If除了VB.NET預(yù)先定義的枚舉類型之外,還可以定義自己的枚舉類型,例如,可以為一組與一周中的7天相對應(yīng)的整型常數(shù)聲明一個(gè)枚舉,然后在代碼中使用這七天的名稱而不是它們的整數(shù)值。Public Enum WeekDaysSunday = 0Monday = 1Tuesday = 2Wednesday = 3Thursday = 4Friday = 5Saturday = 6End Enum現(xiàn)在,WeekDays.Sunday就表示0這個(gè)數(shù)字了。一般用Enum語句在類的外部或模塊的聲明部分定義枚舉類型,這樣在整個(gè)類或項(xiàng)目中就都可以使用這個(gè)數(shù)據(jù)類型了。
注意在VB.NET中,幾乎所有的代碼(除了一些特殊類型的語句,如前面介紹的Option語句)都必須放在類(Class)或模塊(Module)中,沒有獨(dú)立存在的函數(shù)和Sub過程。5.?dāng)?shù)組(Array)數(shù)組用于存放一批具有相同類型的元素,這點(diǎn)與一般的變量是不一樣的。一個(gè)定義為Integer類型的變量只能存入一個(gè)整數(shù),而一個(gè)整數(shù)數(shù)組則可以存入一組整數(shù)。以下代碼定義了一個(gè)數(shù)組:Dim myIntegers() As Integer = {99, 32, 100, 16}這個(gè)數(shù)組按如圖2-49所示的形式存儲(chǔ)數(shù)字。
數(shù)組中的元素按順序存放,每個(gè)元素都有一個(gè)位置編號(稱為數(shù)組元素下標(biāo)),從0開始。訪問數(shù)組元素是通過下標(biāo)進(jìn)行的,格式為:數(shù)組名(元素下標(biāo)值)例如,以下代碼取出數(shù)組中的第3個(gè)元素值并用消息框顯示出來:MsgBox(myIntegers(2))依次訪問全部數(shù)組元素有兩種方法:(1)可以使用循環(huán)語句來訪問全部的數(shù)組元素:Dim myIntegers() As Integer = {99, 32, 100, 16}Dim i As IntegerFor i = 0 To myIntegers.GetLength(0) - 1MsgBox(myIntegers(i))Next注意數(shù)組其實(shí)是一個(gè)對象,GetLength()是它的一個(gè)方法,用于獲取數(shù)組中的元素個(gè)數(shù)。上述代碼把所有的數(shù)組元素遍歷了一遍,并用消息框顯示出來。(2)可以使用For Each語句來訪問全部的數(shù)組元素:Dim myIntegers() As Integer = {99, 32, 100, 16}Dim i As IntegerFor Each i In myIntegersMsgBox(myIntegers(i))Next使用For Each語句除了可以訪問數(shù)組,還可以訪問集合(將在2.2.7節(jié)中介紹)。6.結(jié)構(gòu)(Structure)可以合并不同類型的變量來創(chuàng)建“結(jié)構(gòu)”。聲明結(jié)構(gòu)后,它成為一種復(fù)合的數(shù)據(jù)類型,進(jìn)而可以聲明該類型的變量。除變量外,結(jié)構(gòu)還可以有屬性、方法和事件。想讓單個(gè)變量包含有幾個(gè)相關(guān)信息時(shí)結(jié)構(gòu)很有用。例如,可能想將一個(gè)學(xué)生的姓名、年齡和學(xué)號信息放在一起??梢詫@些信息分別定義幾個(gè)變量,再將這些變量組合為一個(gè)結(jié)構(gòu)以簡化信息的處理。(1)定義結(jié)構(gòu)使用Structure語句作為結(jié)構(gòu)聲明的開始,并使用End Structure語句作為結(jié)構(gòu)聲明的結(jié)束。在這兩條語句之間必須至少聲明一個(gè)成員。成員可以屬于任何數(shù)據(jù)類型。如果要將學(xué)生的姓名、年齡和學(xué)號信息一起保存在單個(gè)變量中,可以為這些信息聲明一個(gè)結(jié)構(gòu),如下所示:Public Structure StudentPublic SerialNo As String '學(xué)號Public Name As String '姓名Public Age As Integer '年齡End Structure(2)使用結(jié)構(gòu)變量創(chuàng)建了結(jié)構(gòu)后,即可聲明該類型的變量:Dim Stu1,Stu2 As Employee通過以下方式來使用結(jié)構(gòu)變量:變量名.結(jié)構(gòu)成員名示例代碼如下:Dim Stu1,Stu2 As StudentStu1.SerialNo = "2004110107"Stu1.Name = "張三"Stu1.Age =20Stu1和Stu2都是Employee類型的變量,因而可以相互賦值:Stu2 = Stu1其結(jié)果是Stu2中的所有成員值都與Stu1相同。
注意雖然賦值后Stu1和Stu2的所有成員值是一樣的,但這兩個(gè)變量完全是相互獨(dú)立的,可以給Stu1的Name屬性賦予一個(gè)新值(如“李四”),這并不會(huì)引起Stu2的Name屬性值改變,它仍是“張三”。2.2.2 語句與控制結(jié)構(gòu)在前面的學(xué)習(xí)中,讀者已經(jīng)看到并親自動(dòng)手編寫過一些程序代碼了。從這些代碼中可以發(fā)現(xiàn)一個(gè)個(gè)單詞構(gòu)成一個(gè)語句,多條語句被包在“Sub…End Sub”內(nèi)部(也可能是“Function…End Function”內(nèi)部),多個(gè)Sub和Function又被包在“Class…End Class”內(nèi)部。在計(jì)算機(jī)編程語言(以VB.NET為例)中,把單詞稱為標(biāo)志符(Identifier),由標(biāo)志符構(gòu)成語句(Statement),由語句構(gòu)成函數(shù)(Function)或過程(Sub),由函數(shù)或過程構(gòu)成類(Class),由類構(gòu)成程序(Programme)。在面向?qū)ο蟮某绦蛑?,類是最重要的組成元素,但要編寫一個(gè)類,還得從寫好一條條的語句開始。1.語句的基本概念一條計(jì)算機(jī)語句完成一個(gè)特定的功能,在VB.NET中,一行就是一條語句,語句由標(biāo)識符構(gòu)成,標(biāo)識符不區(qū)分大小寫。如果某行語句太長了,可以加入換行符(即一個(gè)空格加一個(gè)下劃線),例如:Private Sub Button1_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) _Handles Button1.Click上述示例中,雖分為三行,卻只是一條語句。
注意不能在一個(gè)字符串內(nèi)部換行,以下代碼是不合法的:Dim str As String = "我們一定能學(xué)好VB.NET,開發(fā)出好軟件, _讓比爾 蓋茨也破產(chǎn)!"
試一試如果要求出100以內(nèi)的奇數(shù)之和,應(yīng)怎樣修改代碼?另一種常見的循環(huán)語句是While循環(huán)語句,其格式為:While 條件表達(dá)式[語句]End While只要給定條件表達(dá)式值為True就執(zhí)行循環(huán)體內(nèi)的語句。While循環(huán)與For循環(huán)是等價(jià)的,從1加到100的例子,可以使用While循環(huán)改寫如下:Dim i As Integer = 1Dim Result As Integer = 0While i <= 100Result = Result + ii = i + 1End WhileMsgBox("1+2+...+100=" + CStr(Result))While循環(huán)有一個(gè)特性,就是它可以很方便地執(zhí)行不定次數(shù)的循環(huán),以下實(shí)例不斷地要求用戶輸入一個(gè)整數(shù),計(jì)算機(jī)自動(dòng)地計(jì)算從1加到此整數(shù)的和,直到用戶輸入“-1”為止。Dim InputNum As Integer = 0Dim Result As Integer = 0Dim i As IntegerWhile InputNum <> -1InputNum = CInt(InputBox("請輸入一個(gè)大于0的整數(shù),要結(jié)束輸入請輸入-1"))i = 1Result = 0While i <= InputNumResult += ii += 1End WhileMsgBox("從1加到" & InputNum & "的結(jié)果是:" & Result)End While上面代碼中有一個(gè)以前沒見過的語句:Result += i“+=”稱為復(fù)合運(yùn)算符,上面的語句相當(dāng)于:Result=Result+i
技術(shù)探索除了“+=”之外,還有“*=”、“/=”這樣的復(fù)合運(yùn)算符,請自行查閱MSDN了解這些運(yùn)算符,并學(xué)會(huì)使用。還有一種很有意思的循環(huán),稱為“死循環(huán)”。它的樣子是這樣的:While True[語句]End While除非在“[語句]”部分中有退出的代碼,否則它將永遠(yuǎn)運(yùn)行下去。要退出一個(gè)循環(huán),可以使用Exit語句。While循環(huán)的上一個(gè)例子可以用“死循環(huán)”的方式改寫如下:Dim InputNum As Integer = 0Dim Result As Integer = 0Dim i As IntegerWhile TrueInputNum = CInt(InputBox("請輸入一個(gè)大于0的整數(shù),要結(jié)束輸入請輸入-1"))If InputNum = -1 ThenExit WhileEnd Ifi = 1Result = 0While i <= InputNumResult += ii += 1End WhileMsgBox("從1加到" & InputNum & "的結(jié)果是:" & Result)End While注意,上面的示例代碼中使用一個(gè)條件語句檢測是否用戶輸入了“-1”,一旦確認(rèn),就調(diào)用Exit While語句退出循環(huán)。
技術(shù)探索VB.NET還提供了Do…Loop While和Do Until…Loop循環(huán)語句,請自行查閱MSDN文檔,了解相關(guān)的知識。各種類型的循環(huán)語句都是等價(jià)的,可以用一種類型的循環(huán)語句替換另一種類型的循環(huán)語句而不會(huì)影響到程序的功能。至此,介紹完了VB.NET中最基本的編程元素。下面將介紹如何把多個(gè)語句組合成函數(shù)和過程,并進(jìn)一步裝配出一個(gè)類。2.2.3 對象與類在上一節(jié)中學(xué)習(xí)了基本的VB.NET編程元素——語句,多個(gè)語句組合在一起,共同完成一個(gè)功能,稱為Sub過程或函數(shù)。多個(gè)Sub過程或函數(shù)加上多個(gè)變量聲明,以及其他一些組成元素,就構(gòu)成了類。在本小節(jié)中將介紹Sub過程、函數(shù)和類的基礎(chǔ)知識。1.Sub過程在VB.NET中,完成某個(gè)功能的多條語句可以放在一起,包含在Sub語句和End Sub語句中,這樣的一個(gè)代碼集合稱為“Sub過程”。每次調(diào)用Sub過程時(shí)都執(zhí)行Sub過程中的語句,從Sub語句后的第一個(gè)可執(zhí)行語句開始,到遇到的第一個(gè)End Sub、Exit Sub或Return語句結(jié)束。聲明Sub過程的語法如下所示:Sub 過程名[(參數(shù)列表)]'VB.NET語句End Sub來看一個(gè)Sub過程實(shí)例,這個(gè)Sub過程接收一個(gè)圖像文件的完整路徑名,并把這個(gè)圖像顯示在窗體上。新建一個(gè)Windows應(yīng)用程序項(xiàng)目(取名SubAndFunction,源代碼見配套光盤),切換到Form1的代碼視圖,在End Class語句之前的空白處輸入以下代碼:'將指定的圖片畫到窗體上Private Sub DrawPicToForm(ByVal fileName As String)Dim img As Bitmap'裝入圖片img = New Bitmap(fileName)'獲取窗體的繪圖對象Dim g As Graphicsg = Me.CreateGraphics'將圖片繪制到窗體上去,左上角坐標(biāo)為(0,0)g.DrawImage(img, New Point(0, 0))'釋放繪圖對象g.Dispose()End Sub往窗體上拖入一個(gè)Button,設(shè)其名字為“btnDrawPic”,再從工具箱中選擇OpenFileDialog組件拖到窗體上,設(shè)置其Filter屬性為“所有圖像文件|*.jpg;*.gif;*.bmp;*.ico;*.wmf|所有文件|*.*”。
提示OpenFileDialog組件用于顯示標(biāo)準(zhǔn)的Windows打開文件對話框(參見圖2-54),而其Filter屬性用于指定顯示哪種類型的文件,其格式為:提示信息一|*.文件擴(kuò)展名一|提示信息二|*.文件擴(kuò)展名二|……如果想在一項(xiàng)中同時(shí)顯示多種擴(kuò)展名的文件,可以串聯(lián)多項(xiàng)“*.文件擴(kuò)展名”,中間以分號間隔(必須是英文的分號)。給按鈕btnDrawPic的Click事件編碼如下:Private Sub btnDrawPic_Click(ByVal sender As System.Object,_ByVal e As System.EventArgs) Handles btnDrawPic.ClickDim fileName As StringIf Me.OpenFileDialog1.ShowDialog = DialogResult.OK Then'如果用戶選擇了一個(gè)文件,并單擊了“打開”按鈕fileName = Me.OpenFileDialog1.FileName'顯示圖片Me.DrawPicToForm(fileName)End IfEnd Sub程序運(yùn)行時(shí),單擊btnDrawPic按鈕,在對話框窗口中選取一個(gè)圖片,如圖2-54所示。單擊【打開】按鈕之后,可以看到圖片出現(xiàn)在窗體上,如圖2-55所示。
提示在VB.NET中區(qū)分函數(shù)與Sub過程兩個(gè)不同的概念,而在C++、C#和Java中,沒有過程這個(gè)概念,Sub過程全部以返回void值的函數(shù)代替。來看一個(gè)實(shí)例:編寫一個(gè)函數(shù)計(jì)算兩數(shù)之和。Private Function AddTwoNumber(ByVal x As Single, _ByVal y As Single) As SingleDim result As Singleresult = x + yReturn resultEnd Function仔細(xì)看看上面的代碼,可以發(fā)現(xiàn)函數(shù)與Sub過程有以下不同點(diǎn):(1)函數(shù)的關(guān)鍵字是Function(過程是Sub)。(2)函數(shù)聲明最后有一個(gè)As子句,表明函數(shù)返回值的類型是單精度實(shí)數(shù)。(3)代碼的最后有一個(gè)Return語句,把結(jié)果返回給了函數(shù)調(diào)用者。使用此函數(shù)的實(shí)例如下:MsgBox(AddTwoNumber(20, 30) + 100)其結(jié)果是150。可以看到,AddTwoNumber()函數(shù)可以用在表達(dá)式中,可以把它當(dāng)成一個(gè)數(shù)字來使用。如果某個(gè)函數(shù)返回一個(gè)對象,比如String類型的對象,則函數(shù)本身就可以當(dāng)成一個(gè)字符串使用,下面是一個(gè)實(shí)例:'將一個(gè)字符串反序Private Function ReverseStr(ByVal str As String) As StringReturn StrReverse(str)End Function使用實(shí)例:MsgBox(ReverseStr("abcd").ToUpper)代碼中“ReverseStr("abcd")”可以看成是一個(gè)String類型的對象,因而可以調(diào)用其ToUpper方法把字符串中的小寫字母全部變?yōu)榇髮?。這行代碼的最終結(jié)果為:“DCBA”。現(xiàn)在可以給出函數(shù)的語法格式:Private/Public Function 函數(shù)名(函數(shù)參數(shù)列表) As 返回值類型'各種代碼Return 返回值End Function3.類的定義在面向?qū)ο蟪绦蛑校愂亲罨镜慕M成單位。換句話說,是由類構(gòu)成了整個(gè)程序,就如同磚塊構(gòu)建起了整棟大樓。那么,類到底是什么?可以把類看成是現(xiàn)實(shí)事物在計(jì)算機(jī)中的一種反映,舉個(gè)典型的例子——電視機(jī)。一臺(tái)電視機(jī)具有以下的特性:(1)生產(chǎn)廠家(2)是否支持?jǐn)?shù)字信號(即是否可當(dāng)電腦顯示器用)(3)價(jià)格……一臺(tái)電視機(jī)必須擁有以下的操作功能:(1)打開、關(guān)閉(2)選臺(tái)(3)顯示當(dāng)前頻道數(shù)……如何在計(jì)算機(jī)中表達(dá)電視機(jī)這一概念?可以使用變量來表示電視機(jī)所具有的特性,如表2-7所示。表2-7 電視機(jī)特性特 性變 量 名類 型說 明生產(chǎn)廠家ProducerString是否支持?jǐn)?shù)字信號IsDigitalBoolean為True表示數(shù)字電視價(jià)格PriceSingle用小數(shù)表示金額當(dāng)前頻道CurChannelInteger當(dāng)前的頻道使用函數(shù)或Sub過程來表達(dá)電視機(jī)所擁有的功能,如表2-8所示。表2-8 電視機(jī)功能操作功能函數(shù)或Sub過程名參 數(shù)打開Sub Open()無關(guān)閉Sub Close()無選臺(tái)Sub ChooseChannel()Channel(整型)把上述的變量和函數(shù)(或Sub過程)用兩個(gè)語句“Class”和“End Class”包圍起來,再起一個(gè)名字,就形成了一個(gè)類,如下所示:'電視機(jī)類Public Class TV'電視機(jī)的特性Public Producer As String '生產(chǎn)廠家Public IsDigital As Boolean '是否支持?jǐn)?shù)字信號Public Price As Single '價(jià)格Public curChannel As Integer '當(dāng)前頻道'電視機(jī)的操作功能'打開Public Sub Open()End Sub'關(guān)閉Public Sub Close()End Sub'選臺(tái)Public Sub ChooseChannel(ByVal Channel As Integer)End SubEnd Class這個(gè)框架搭好以后,就可以往里面寫代碼了。現(xiàn)在看看如何在電腦中“模擬出”人使用電視機(jī)的過程:Dim myTV As TVmyTV = New TV '我買了一臺(tái)電視機(jī)myTV.Producer = "海信" '是海信生產(chǎn)的電視機(jī)myTV.Price = 2005 '2005元買的myTV.IsDigital = True '是數(shù)字電視'打開電視myTV.Open()'選擇第10頻道myTV.ChooseChannel(10)'我在看電視……myTV.Close() '關(guān)閉電視機(jī)可以看到在代碼中并不能直接使用一個(gè)類,必須先定義一個(gè)TV類的對象變量myTV,然后再用New關(guān)鍵字創(chuàng)建一個(gè)類的實(shí)例,之后才能使用這個(gè)類變量。一定要區(qū)分類與類變量。類變量是一種變量,它的類型是某個(gè)具體的類。一個(gè)類可以創(chuàng)建許多個(gè)實(shí)例,類變量可以指向它們。一個(gè)比較容易理解的例子是可以把類看成是一個(gè)大印章,它可以在一張紙上蓋出無數(shù)個(gè)大印。這些蓋出的印就是印章類的實(shí)例(又可稱為印章對象)。在上面的例子中,印章本身是前面寫的TV類及其全部源代碼,而myTV就是一個(gè)對象變量,它可以代表由印章(TV類)蓋出的一個(gè)?。═V類的一個(gè)實(shí)例),印章蓋印的過程是用New來表達(dá)的。這些概念可能比較抽象難懂,看一個(gè)實(shí)例可能更易于理解。打開VS .NET,創(chuàng)建一個(gè)“Windows應(yīng)用程序”項(xiàng)目,起名為UseClass(參見本書配套光盤)。從“項(xiàng)目”菜單中選擇“添加類”,給類起名字“TV”,如圖2-56所示。單擊【打開】按鈕之后,VS .NET將會(huì)在項(xiàng)目中創(chuàng)建好一個(gè)空的類框架代碼,在此代碼框架中敲入上面介紹的代碼,如圖2-57所示。
使用VS .NET創(chuàng)建類接著,按如圖2-58所示設(shè)計(jì)窗體。
給類書寫代碼圖2-58 “電視機(jī)”示例主窗體注意,在打開電視機(jī)之前,是不能選臺(tái)的,所以,程序運(yùn)行時(shí),【選臺(tái)】和【關(guān)閉】按鈕是灰色的。切換到代碼視圖,在所有Sub過程的外部書寫以下代碼:Private myTV As TV這就在窗體類中定義了一個(gè)成員變量,此變量可以被窗體中的所有函數(shù)和Sub過程所訪問。給【打開】按鈕的事件編碼:Private Sub btnOpen_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnOpen.ClickmyTV = New TV '我買了一臺(tái)電視機(jī)myTV.Producer = "海信" '是海信生產(chǎn)的電視機(jī)myTV.Price = 2005 '2005元買的myTV.IsDigital = True '是數(shù)字電視myTV.curChannel = 10 '設(shè)置10頻道myTV.Open() '打開電視機(jī)電源'顯示當(dāng)前頻道數(shù)Me.lblChannel.Text = CStr(myTV.curChannel)'讓按鈕可用Me.btnClose.Enabled = TrueMe.btnChooseChannel.Enabled = True'不允許第二次打開電視機(jī)電源Me.btnOpen.Enabled = False '讓自己變灰End Sub當(dāng)程序運(yùn)行后,單擊【打開】按鈕,運(yùn)行結(jié)果如圖2-59所示。給【選臺(tái)】按鈕添加代碼如下:myTV.curChannel = CInt(Me.txtChannel.Text)'顯示當(dāng)前頻道數(shù)Me.lblChannel.Text = CStr(myTV.curChannel)編譯運(yùn)行,在文本框中輸入一個(gè)數(shù)字,單擊【選臺(tái)】按鈕,可以看到當(dāng)前頻道顯示出相應(yīng)的數(shù)字,如圖2-60所示,這是通過直接設(shè)置類的curChannel變量值來實(shí)現(xiàn)的。
圖2-59 正在看電視…… 圖2-60 換了一個(gè)臺(tái)從上面例子可以看到,通過使用一個(gè)TV類對象在計(jì)算機(jī)中虛擬出了一臺(tái)電視機(jī),可以“打開”、“關(guān)閉”和“選臺(tái)”,所有這些功能都是通過函數(shù)和 Sub 過程來表達(dá)的。而當(dāng)前頻道屬于一種信息,在TV類對象中以變量來表達(dá)。這個(gè)例子展示了許多重要的東西:(1)它直觀地展示了現(xiàn)實(shí)中的事物是如何被抽象成計(jì)算機(jī)可以處理的一個(gè)類的。(2)現(xiàn)實(shí)事物中的功能往往用類的函數(shù)和Sub過程來表示,而現(xiàn)實(shí)事物的屬性可以使用變量來表示。在一個(gè)類中聲明的函數(shù)和Sub過程,在面向?qū)ο罄碚撝型Q為“方法(Method)”,而聲明的變量,則稱為類的“數(shù)據(jù)成員(Data Member)”或“字段(Field)”,有時(shí)也稱為“成員變量(Member Variable)”。另外,在C#、Java、C++這些面向?qū)ο蟮恼Z言中,不區(qū)分函數(shù)和Sub過程,全部以函數(shù)稱之。4.給類設(shè)置屬性前面TV類提供了一個(gè)curChannel字段,這是一個(gè)類型為Integer的整數(shù),但實(shí)際上,電視臺(tái)的頻道數(shù)是有限的,而且不可能有小于0的頻道數(shù)。如何在TV類中表現(xiàn)這種附加的信息呢?這就引入了另一個(gè)非常重要的概念——對象的屬性。在TV類中,如何表達(dá)電視臺(tái)數(shù)是有限的這一條件?解決方法是定義一個(gè)常量:Const MAX_CHANNEL As Integer = 100現(xiàn)在即可定義一個(gè)curChannel屬性,先把以下這條語句刪除:Public curChannel As Integer '當(dāng)前頻道加入以下代碼:Private _curChannel As Integer = 0Public Property curChannel() As Integer '當(dāng)前頻道GetReturn _curChannelEnd GetSet(ByVal Value As Integer)'電視頻道數(shù)必須大于0,小于最大的頻道數(shù)If Value >= 0 And Value <= MAX_CHANNEL Then_curChannel = ValueElse_curChannel = 0End IfEnd SetEnd Property上述代碼中有一些有趣的東西。Property是一個(gè)關(guān)鍵字,表明curChannel現(xiàn)在不是一個(gè)簡單的變量,而是一個(gè)屬性了。一個(gè)屬性由兩個(gè)特殊的過程來實(shí)現(xiàn)。(1)Get過程:當(dāng)讀此屬性時(shí),會(huì)執(zhí)行此過程。(2)Set過程:當(dāng)向此屬性賦值時(shí),會(huì)執(zhí)行此過程。注意到這一過程有一個(gè)Value參數(shù),在使用屬性的代碼中對此屬性所賦的值被Value參數(shù)所捕獲。一個(gè)屬性并不能真正保存東西,真正能保存信息的是變量,所以,絕大多數(shù)屬性都使用一個(gè)類的私有數(shù)據(jù)成員變量來保存此信息。比如上例中_curChannel變量就是一個(gè)用于保存信息的變量:Private _curChannel As Integer = 0_curChannel的默認(rèn)值在定義時(shí)直接用賦值運(yùn)算符指定為0。特別要注意這個(gè)變量被聲明為私有的(Private)。
技術(shù)內(nèi)幕如果一個(gè)類的數(shù)據(jù)成員被聲明為私有的,則類外部的任何代碼都不能訪問此數(shù)據(jù)成員。例如,以下類聲明了一個(gè)公有和一個(gè)私有的數(shù)據(jù)成員:Class ShowPrivateAndPublicPrivate i As IntegerPublic j As IntegerEnd Class可以使用這個(gè)類創(chuàng)建一個(gè)對象,并訪問這個(gè)變量的j字段,如下所示:Dim obj As ShowPrivateAndPublicobj = New ShowPrivateAndPublicobj.j = 100但是不能寫出以下代碼:Obj.i=100因?yàn)閕是私有的,所以不能在類的外部訪問變量i。但在類ShowPrivateAndPublic中如果有Sub過程和函數(shù),則其中的代碼可以訪問變量i。再回來看看例子中實(shí)現(xiàn)屬性的代碼,當(dāng)需要獲取屬性的值時(shí),僅須簡單地返回_curChannel變量值就行了,而當(dāng)設(shè)置屬性值時(shí),先判斷一下值是否有效,有效就設(shè)置_curChannel變量為期望的值,否則,一律置為0。可以在屬性的Get和Set過程中設(shè)置程序斷點(diǎn),從而看到屬性的代碼執(zhí)行流程。
技術(shù)探索在本節(jié)中介紹的是既可以讀也可以寫的屬性。事實(shí)上,VB.NET還提供只讀與只寫屬性,分別以ReadOnly和WriteOnly兩個(gè)關(guān)鍵字表達(dá)。請?jiān)贛SDN中通過查找這兩個(gè)關(guān)鍵字掌握只讀與只寫屬性的使用方法。5.名字空間與類在前面讀者已經(jīng)接觸過名字空間的概念了,本節(jié)在原有基礎(chǔ)上加深讀者對這一概念的認(rèn)識。.NET Framework使用名字空間(Name Space)來管理所有的類。如果把類比喻成書的話,則名字空間就是用于放書的地方,書保存放在書架上,類放在名字空間里。當(dāng)我們?nèi)D書館查找一本書時(shí),需要指定這本書的編號,編號往往規(guī)定了書放在哪個(gè)書庫的哪個(gè)書架上。通過逐漸縮小的范圍:圖書館→書庫→書架,最終可以在某個(gè)書架中找到這本書。可以采用類似的方法來管理類,通過逐漸縮小的范圍:最大的名字空間→子名字空間→孫名字空間→……,最終找到一個(gè)類。在代碼中可使用Namespace關(guān)鍵字定義自己的名字空間:'父名字空間Namespace NS1Public Class Class1End Class'子名字空間Namespace NS2Public Class Class2End ClassEnd NamespaceEnd Namespace上述代碼定義了一個(gè)父名字空間NS1,其中放置了一個(gè)類Class1,又定義了一個(gè)子名字空間NS2,其中放置了另一個(gè)類Class2。在同一項(xiàng)目中,可以這樣來使用這兩個(gè)類:Dim o1 As New NS1.Class1Dim o2 As New NS1.NS2.Class2如果上述代碼放在一個(gè)類庫項(xiàng)目(項(xiàng)目名稱為ExampleClassLibrary)中,VS .NET默認(rèn)將項(xiàng)目名作為頂級的名字空間。所以,如果要在其他項(xiàng)目中使用Class1和Class2,就必須這樣來使用兩個(gè)類:Dim o1 As New ExampleClassLibrary.NS1.Class1Dim o2 As New ExampleClassLibrary.NS1.NS2.Class2也可以使用Imports語句來簡化代碼的書寫:Imports ExampleClassLibrary.NS1Imports ExampleClassLibrary.NS1.NS2'……Dim o1 As New Class1Dim o2 As New Class2
把這個(gè)圖與TV類的源代碼相互對照,可以很容易明白這個(gè)圖的含義。展示一個(gè)類的所有數(shù)據(jù)成員和方法的圖在UML中稱為類圖。一個(gè)類圖是一個(gè)有三欄的方框,第一個(gè)方框?qū)懮项惖拿郑诙€(gè)方框列出類中所有字段,第三個(gè)方框列出所有的方法。圖2-68中的“+”表示Public,“-”表示Private,還有一個(gè)“#”號圖中沒有,它表示Protected。有關(guān)Protected的含義將在第3章介紹。2.2.4 變量的類型以下代碼定義了一個(gè)結(jié)構(gòu)和一個(gè)類用于表示一個(gè)點(diǎn)的坐標(biāo)(x,y):Public Structure ValPointDim x As IntegerDim y As IntegerEnd StructurePublic Class RefPointPublic x As IntegerPublic y As IntegerEnd Class一旦定義好了這兩個(gè)數(shù)據(jù)類型,就可以使用它們來定義變量。使用結(jié)構(gòu)ValPoint的示例代碼如下:'測試值變量Dim p1 As ValPointp1.x = 100p1.y = 100MsgBox(CStr(p1.x))運(yùn)行上述代碼(可以參見本章示例代碼RefTypeAndValueType),可以看到程序正確地顯示p1.x的值為100。現(xiàn)在使用類變量,示例代碼如下:'測試引用變量Dim p1 As RefPointp1.x = 100p1.y = 100MsgBox(CStr(p1.x))可以看到除了第一句以外,所有代碼都是與使用結(jié)構(gòu)變量ValPoint一樣的。運(yùn)行,看看出現(xiàn)了什么情況?系統(tǒng)報(bào)告出現(xiàn)了一個(gè)錯(cuò)誤:“未將對象引用設(shè)置到對象的實(shí)例”(圖2-69)。
圖2-69 NullReferenceException異常為什么會(huì)出現(xiàn)這種情況?原來,變量是分類型的,定義為ValPoint類型的變量稱為值類型(Value Type)的變量,定義為RefPoint類型的變量稱為引用類型(Reference Type)的變量。如果一個(gè)變量是值類型的,那么一旦定義完后就可以直接使用了,而引用類型的變量則必須先創(chuàng)建(New)一個(gè)對象,之后才能使用,否則就會(huì)出現(xiàn)以上的錯(cuò)誤。1.引用類型與值類型變量引用類型與值類型到底差別何在?這涉及計(jì)算機(jī)內(nèi)存(Memory)的分配方式。先來看值變量:Dim p1 As ValPoint此代碼執(zhí)行之后,內(nèi)存布局如圖2-70所示。從圖2-70可以看到,如果定義了一個(gè)ValPoint類型的變量,那么計(jì)算機(jī)會(huì)在內(nèi)存中分配一塊空白區(qū)域,大小能容納值類型所定義的所有成員(ValPoint中有兩個(gè)類型為Integer的整型變量成員)。而變量名p1就代表了這塊內(nèi)存區(qū)域,可以通過p1.x直接訪問內(nèi)存中x的值。但引用類型的變量就不是這樣了,當(dāng)計(jì)算機(jī)執(zhí)行完以下定義變量語句時(shí):Dim p1 As RefPoint內(nèi)存布局如圖2-71所示。
虛線框表示此內(nèi)存并未真正分配。從圖2-71中可以看到,定義一個(gè)引用變量并不會(huì)導(dǎo)致計(jì)算機(jī)為此變量分配足夠容納此變量所包含所有成員數(shù)據(jù)的內(nèi)存空間,而只是分配了一個(gè)固定大小的空間(4個(gè)字節(jié))并將其內(nèi)容設(shè)為0。這個(gè)被初始化為0的空間即p1本身,其內(nèi)存代表真實(shí)的數(shù)據(jù)在內(nèi)存中的位置(又稱為內(nèi)存地址)。正因?yàn)榭扇菁{x和y的內(nèi)存還未分配,p1.x是不存在的,所以,MsgBox(CStr(p1.x))會(huì)引發(fā)NullReferenceException異常。修正錯(cuò)誤很簡單,只需要增加一句就行了,代碼如下所示:'測試引用變量Dim p1 As RefPointp1 = New RefPointp1.x = 100p1.y = 100MsgBox(CStr(p1.x))第2句代碼使用New關(guān)鍵字創(chuàng)建一個(gè)RefPoint對象,其實(shí)質(zhì)是讓計(jì)算機(jī)給變量分配可容納真實(shí)數(shù)據(jù)的內(nèi)存空間,執(zhí)行之后,內(nèi)存布局如圖2-72所示。現(xiàn)在,p1.x就可以正確地訪問了。
提示在.NET編程中,一定要注意.NET Framework所提供的數(shù)據(jù)類型是引用類型還是值類型。有一個(gè)簡單的判斷方法:凡是類類型的變量一定是引用類型的。使用它之前一定要先創(chuàng)建一個(gè)對象出來。其他的類型都是值類型的,可以直接使用。引用變量的內(nèi)存布局是十分重要的,如果理解不清,在今后的編程工作中會(huì)引發(fā)許多隱含的錯(cuò)誤。請看以下問題:Dim p1, p2 As RefPointp1 = New RefPointp1.x = 100p1.y = 100p2 = p1問:p2.x會(huì)不會(huì)引發(fā)錯(cuò)誤?如果沒錯(cuò),它的值是多少?答案:p2.x不會(huì)引發(fā)錯(cuò)誤,它的值等于100。變量賦值之后,其內(nèi)存布局如圖2-73所示。
試一試理解了本章所介紹的內(nèi)存變量分配模型,請做以下練習(xí):1 Dim p1, p2 As RefPoint2 p1 = New RefPoint3 p1.x = 1004 p1.y = 1005 p2 = p16 p2 = New RefPoint7 MsgBox(CStr(p2.x))問:(1)p2.x現(xiàn)在的值是多少?(2)如果把第6句移到第3句前面,則p2.x的值又是多少?(3)請繪出這兩種情況下的內(nèi)存分配模型。上機(jī)編程驗(yàn)證您的推測。現(xiàn)在再來看一下數(shù)組。Dim ps(100) As ValPointDim i As IntegerFor i = 0 To 99ps(i).x = ips(i).y = iNext上述代碼定義了一個(gè)有100個(gè)ValPoint元素的數(shù)組,并且通過循環(huán)初始化了數(shù)組元素。再看以下代碼:Dim ps(100) As RefPointDim i As IntegerFor i = 0 To 99ps(i).x = ips(i).y = iNext能實(shí)現(xiàn)同樣的目的嗎?
請編程驗(yàn)證您的推測。2.類內(nèi)部信息的相互傳送先看如圖2-74所示的示例(參見示例VariantScope)。當(dāng)單擊按鈕時(shí),窗體上的標(biāo)簽會(huì)顯示出單擊的次數(shù)。按鈕對象名為“btnClickMe”,顯示次數(shù)是一個(gè)標(biāo)簽對象lblClickCount,這個(gè)功能如何實(shí)現(xiàn)?經(jīng)過思索,讀者可能會(huì)寫出以下代碼:1 Private Sub btnClickMe_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnClickMe.Click2 Dim ClickCount As Integer3 '單擊次數(shù)加14 ClickCount += 15 '用標(biāo)簽(Label)控件顯示單擊次數(shù)6 Me.lblClickCount.Text = ClickCount7 End Sub然而很遺憾,不管單擊按鈕多少次,標(biāo)簽總顯示為“1”。
試一試(1)現(xiàn)在把第2句代碼移到Sub外面,再運(yùn)行試一試。(2)注釋掉移動(dòng)到Sub外部的代碼。把原來的第2句代碼改為:Static ClickCount As Integer再試一試。試驗(yàn)結(jié)果表明程序運(yùn)行結(jié)果正常。上述兩個(gè)試驗(yàn)蘊(yùn)涵著重要的程序設(shè)計(jì)基礎(chǔ)知識。最初定義的變量是在Sub過程內(nèi)部,稱為“局部動(dòng)態(tài)變量”,其特點(diǎn)是它的值在離開Sub過程后會(huì)被丟棄,下次執(zhí)行時(shí)又會(huì)重新設(shè)置(取其默認(rèn)值0),所以標(biāo)簽總顯示為“1”。第一次試驗(yàn)中把這個(gè)變量定義在Sub過程之外,則此變量現(xiàn)在成了類的一個(gè)“成員變量”。在類內(nèi)部的Sub過程可以訪問并修改這個(gè)變量,變量的值能夠在Sub過程運(yùn)行結(jié)束之后繼續(xù)保存,再次運(yùn)行Sub過程時(shí),ClickCount變量的值就是上次運(yùn)行的結(jié)果。第二次試驗(yàn)中變量還是定義在Sub過程之內(nèi),所不同之處在于使用“Static”關(guān)鍵字定義,在本例中,其效果與類的“成員變量”是一樣的。這種變量稱為“局部靜態(tài)變量”。讀者可能會(huì)奇怪:類成員變量與局部靜態(tài)變量都可以記錄下上次Sub過程執(zhí)行的結(jié)果,那么它們在使用上有何差別?實(shí)情是這樣的:類的成員變量可以被類的任何方法(包括函數(shù)和Sub過程)所訪問,而局部靜態(tài)變量只能被本身(即它所定義于其中的函數(shù)或Sub過程)所訪問。在實(shí)際開發(fā)中,經(jīng)常需要解決信息的流動(dòng)問題,這種流動(dòng)可以是在類與類之間的,也可以是類內(nèi)部的。類與類之間一般通過聲明為Public的屬性和方法相互交換信息,而類內(nèi)部的信息交換則主要通過類成員變量實(shí)現(xiàn)。如果對這些問題理解不清,在實(shí)際開發(fā)時(shí),可能會(huì)引發(fā)許多難以排除的錯(cuò)誤,因此,讀者一定要清晰地理解這些概念。3.類的共享數(shù)據(jù)成員類的成員(包括變量、函數(shù)和Sub過程)也可以是靜態(tài)的。其方法就是在定義成員時(shí)加上Shared關(guān)鍵字(注意不是Static)。Public Class AllStaticMemberClassPublic Shared Counter As IntegerPublic Shared Sub StaticSub()'代碼略End SubPublic Shared Function StaticFunction() As Integer'代碼略End FunctionEnd Class要訪問類的靜態(tài)成員,有兩種方法。(1)使用“類名.成員名”的方法,如:AllStaticMemberClass.Counter = 0 '訪問共享變量MsgBox(AllStaticMemberClass.StaticFunction()) '訪問共享函數(shù)AllStaticMemberClass.StaticSub() '訪問共享Sub過程(2)使用“對象.成員名”的方法:Dim obj As New AllStaticMemberClassobj.Counter = 0 '訪問共享變量MsgBox(obj.StaticFunction) '訪問共享函數(shù)obj.StaticSub() '訪問共享Sub過程需要注意的是:凡聲明為Shared的類成員,不管這個(gè)類生成了多少個(gè)對象,它們都共享同一個(gè)類成員。
技術(shù)內(nèi)幕MsgBox的歷史非常悠久,早在VB 3.0(20世紀(jì)90年代中期推出)時(shí)就提供這一函數(shù)了。除了MsgBox之外,早期的VB還提供了一系列的函數(shù),比如CStr、CInt、Left、Kill等。為了兼容早期的版本,在VB.NET中仍然提供這些函數(shù),只不過把這些函數(shù)全部放到了Microsoft.Visual- Basic名字空間中。此名字空間是被VB.NET默認(rèn)引入的,所以可以直接使用其中的函數(shù)。其他的編程語言,比如C#,則必須顯式引入此名字空間(C#使用using語句)后才能使用其中的函數(shù)。可以把一個(gè)整數(shù)直接傳給MsgBox函數(shù),比如以下代碼是正確并且等價(jià)的:MsgBox(100)MsgBox("100")細(xì)心的讀者一定會(huì)覺得奇怪,數(shù)字與字符串怎么可以作為參數(shù)傳給同一個(gè)函數(shù)?這在以前的VB中是不可以的。這種情況由“重載(Overloads)”這一面向?qū)ο蟮恼Z言特性實(shí)現(xiàn)。請打開MSDN,查詢MessageBox類的Show()方法,您會(huì)發(fā)現(xiàn)多達(dá)12個(gè)Show()方法的定義,下面列出3個(gè):Overloads Public Shared Function Show(String) As DialogResultOverloads Public Shared Function Show(IWin32Window, String) As DialogResultOverloads Public Shared Function Show(String, String) As DialogResult……正是這12個(gè)名字相同的函數(shù),讓軟件工程師可以方便地把不同數(shù)目、不同類型的數(shù)據(jù)傳送給MessageBox.Show()函數(shù),以顯示出相應(yīng)的消息框。上述12個(gè)函數(shù)即為“函數(shù)重載”特性的表現(xiàn)形式。那么,構(gòu)成重載情況的函數(shù)擁有哪些特性?請記住以下規(guī)則:(1)函數(shù)名相同。(2)參數(shù)類型不同,或參數(shù)個(gè)數(shù)不同。符合以上特性的函數(shù)構(gòu)成重載關(guān)系。當(dāng)在代碼中使用重載函數(shù)時(shí),根據(jù)傳入的參數(shù)由計(jì)算機(jī)自動(dòng)選擇最合適的一個(gè)函數(shù)運(yùn)行。在VB.NET中,除了函數(shù),Sub過程也是可以重載的。以下是一個(gè)實(shí)例(參見配套光盤上的實(shí)例FunctionOverLoads)。定義四個(gè)重載的IknowWhatYouGiveMe():'重載的函數(shù)Public Sub IKnowWhatYouGiveMe()MsgBox("您什么也沒有給我?。?!")End SubPublic Sub IKnowWhatYouGiveMe(ByVal arg As Integer)MsgBox("您傳入了一個(gè)整數(shù):" & arg)End SubPublic Sub IKnowWhatYouGiveMe(ByVal arg As String)MsgBox("您傳入了一個(gè)字符串:" & arg)End SubPublic Sub IKnowWhatYouGiveMe(ByVal arg As Control)MsgBox("您傳入了一個(gè)控件:" & arg.GetType.Name)End Sub在按鈕的單擊事件中可以這樣調(diào)用重載的Sub過程:1 Private Sub btnUseOverloadsFunc_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnUseOverloadsFunc.Click2 '調(diào)用重載的Sub過程3 IKnowWhatYouGiveMe()4 IKnowWhatYouGiveMe(100)5 IKnowWhatYouGiveMe("Hello.VB.NET")6 IKnowWhatYouGiveMe(New TextBox)7 End Sub特別注意第4個(gè)重載的IknowWhatYouGiveMe() 過程其參數(shù)是一個(gè)Control對象。類Control是所有可視化窗體控件的祖先,可以把任何一個(gè)由它繼承而來的類對象傳給它。第6句動(dòng)態(tài)創(chuàng)建了一個(gè)文本框?qū)ο笞鳛閰?shù),可以通過Control類對象的GetType方法獲取對象的類型(是Type類的一個(gè)對象),再調(diào)用Type類對象的Name方法獲取其名稱(這一句涉及許多面向?qū)ο蟮闹R,初學(xué)者可以不必深究,在掌握了更多的.NET知識之后就會(huì)明白其中的奧秘)。
提示上述示例是一種新的項(xiàng)目類型,稱為控制臺(tái)程序,有關(guān)它的知識請參看本節(jié)的擴(kuò)充閱讀資料。String變量與其他類型的變量連接時(shí),需要進(jìn)行類型轉(zhuǎn)換,可以使用CStr() 函數(shù)完成此功能。以下代碼將整數(shù)轉(zhuǎn)為字符串類型:Dim str As Stringstr = "1+100=" + CStr(1 + 100)字符串由字符組成,可以通過字符串變量的Chars()屬性訪問單個(gè)字符,以下代碼逆序輸出字符串中的每個(gè)字符:Dim str As Stringstr = "尺千三下直流飛"Dim c As CharDim i As IntegerFor i = str.Length - 1 To 0 Step -1c = str.Chars(i)Console.WriteLine(c)Next字串中的字符按Unicode進(jìn)行編碼,每個(gè)字符占兩個(gè)字節(jié)。可以使用String類的SubString方法獲取子字符串,參看以下代碼:Dim str As Stringstr = "子在川上曰:逝者如斯夫!"Dim str1, str2 As Stringstr1 = str.Substring(0, 6)str2 = str.Substring(6)第一句SubString從頭開始,取6個(gè)字符,注意字符位置是從0開始的。運(yùn)行完后,str1=“子在川上曰:”。第二句從第7個(gè)字符開始,由于省略了第二個(gè)參數(shù),就直接截取到字符串結(jié)束。運(yùn)行完后,str2=“逝者如斯夫!”。String類對象具有一個(gè)很獨(dú)特的特性,即字符串內(nèi)容的恒定性。一個(gè)String對象一旦創(chuàng)建,其內(nèi)容就是不可改變的。上述代碼中的SubString方法并不是把Str本身給截短了,而是將Str中的一段取出來,生成一個(gè)新的字符串傳給Str1(和Str2)。
理解在軟件技術(shù)中的遞歸概念對于初學(xué)者而言是不容易的,因?yàn)樗c人們常規(guī)的思維方式不同。有趣的是,在藝術(shù)家那里,遞歸往往以一種更易于理解的方式呈現(xiàn),請看如圖 2-77所示的畫。圖2-77是著名的荷蘭設(shè)計(jì)師M.C.Escher(1898—1972)的作品,在畫中他繪制了一群魚,有趣的是,魚身上的魚鱗又是一條魚……這種“用魚來繪制魚”的表現(xiàn)方法,就是遞歸思想在藝術(shù)中的表現(xiàn)。回到軟件技術(shù),以下代碼就是非常明顯的遞歸代碼:Sub DoNotRunMe()'……DoNotRunMe()End Sub可以看到,這種代碼在執(zhí)行過程中又會(huì)調(diào)用自己,于是又執(zhí)行一次自己,在新的一次調(diào)用中再次重復(fù)這個(gè)過程……這是一個(gè)無窮無盡的過程,理論上講可以運(yùn)行到宇宙末日。這個(gè)過程像不像愚公說的“子又生孫,孫又生子”?所以說,計(jì)算機(jī)可算是當(dāng)代最大的愚公了。然而,一段永遠(yuǎn)也不會(huì)結(jié)束的代碼是沒有實(shí)際意義的,就是愚公移山,也有它終止的一日,那就是山給移走了。因此,如果在編程中使用遞歸,就一定要給它設(shè)定一個(gè)結(jié)束條件。愚公移山之所以能夠成功,關(guān)鍵在于“山不加增”,而“子子孫孫無窮匱”,如果山也同步加增,則永遠(yuǎn)也無法移走大山。因此,要使遞歸最終得以結(jié)束,一定要在每次調(diào)用自己時(shí)讓某個(gè)變量(在故事中就是山的高度)隨之變化,當(dāng)其達(dá)到一個(gè)最終界限時(shí)(故事中的山的高度為零),則結(jié)束調(diào)用過程(愚公移山成功)。現(xiàn)在我們來看如圖2-78所示的有趣實(shí)例(示例項(xiàng)目Recursion,配套光盤中有)。您一定對這首著名的“兒歌”很熟悉:從前有座山,山里有座廟。廟里有兩個(gè)和尚,在講故事。講什么故事呢?……從前有座山,山里有座廟。廟里有兩個(gè)和尚,在講故事。講什么故事呢?……這就是一個(gè)很典型的遞歸。示例Recursion用遞歸模擬了這個(gè)小時(shí)候常唱的兒歌。這個(gè)程序是如何寫出來的?下面介紹一下開發(fā)過程。在窗體上放了一個(gè)RichTextBox控件用于顯示遞歸的結(jié)果,使用一個(gè)NumericUpDown控件updnTimes來限制遞歸調(diào)用次數(shù)。實(shí)現(xiàn)代碼如下:'定義一個(gè)Story變量用于存放故事主體Private Story As String = ""'故事主體Private Sub WriteStory()Story = "從前有座山,山里有座廟。" & vbCrLfStory += "廟里有兩個(gè)和尚,在講故事。" & vbCrLfStory += "講什么故事呢?……" & vbCrLfEnd Sub以下代碼實(shí)現(xiàn)遞歸:'用于實(shí)現(xiàn)遞歸調(diào)用的Sub過程Private Sub DoRecursion(ByVal times As Integer)'結(jié)束條件If times = 0 ThenExit SubEnd If'每次遞歸調(diào)用時(shí)要完成的工作Me.RichTextBox1.AppendText("第 " & CStr(times) & " 次" & vbCrLf)Me.RichTextBox1.AppendText(Story)'遞歸調(diào)用,參數(shù)減1DoRecursion(times - 1)End Sub在這個(gè)遞歸調(diào)用的Sub過程中,參數(shù)times就是用于控制遞歸深度的控制變量,每次調(diào)用時(shí)它減1。減到0時(shí),遞歸結(jié)束。在按鈕單擊事件中運(yùn)行遞歸過程:Private Sub btnExecute_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnExecute.Click'清空文本Me.RichTextBox1.Text = ""'老和尚開始沒完沒了地講故事,直到小和尚不耐煩為止DoRecursion(Me.updnTimes.Value)End Sub運(yùn)行的結(jié)果如圖2-78所示。
圖2-78 遞歸的實(shí)例
試一試將Sub DoRecursion()中干活的兩句:Me.RichTextBox1.AppendText("第 " & CStr(times) & " 次" & vbCrLf)Me.RichTextBox1.AppendText(Story)移到DoRecursion(times - 1)之后,會(huì)發(fā)生什么情況?您能想得清楚嗎?遞歸最能體現(xiàn)出“計(jì)算機(jī)的思維”與“人的常規(guī)思維”的不同。理解它也是初學(xué)編程者最頭痛的問題之一。然而,一旦您真正理解了它,就會(huì)發(fā)現(xiàn)這種思維方法是很巧妙的。而在實(shí)際開發(fā)中,遞歸是非常常用的編程技巧,在本書的后面,有不少地方使用了遞歸的編程方法。希望讀者好好體會(huì)。2.2.8 .NET中的集合在高中代數(shù)中學(xué)過集合的概念,集合是若干有著相同特性的元素的整體。在程序設(shè)計(jì)中,集合有著非常多的應(yīng)用。本節(jié)介紹.NET中最常使用的兩個(gè)集合數(shù)據(jù)類型:ArrayList和HashTable。1.ArrayListArrayList與數(shù)組非常相像,放在ArrayList中的元素也是通過下標(biāo)訪問的,但與數(shù)組不同的是,ArrayList中的元素可以隨時(shí)增加和刪除,所以,ArrayList又可以被看成是一個(gè)“動(dòng)態(tài)數(shù)組”。以下是一個(gè)使用實(shí)例(見配套光盤中的UseCollection)。'使用ArrayListPrivate Sub btnUseArrayList_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnUseArrayList.Click'創(chuàng)建ArrayList對象Dim arr As New ArrayList'向ArrayList中增加元素arr.Add("張三")arr.Add("李四")arr.Add("王五")'顯示ArrayList內(nèi)容ShowArrayList(arr)'刪除第二項(xiàng)(注意下標(biāo)從0開始)arr.RemoveAt(1)'再次顯示ArrayList內(nèi)容ShowArrayList(arr)End Sub'顯示ArrayList內(nèi)容Private Sub ShowArrayList(ByVal arr As ArrayList)Dim i As IntegerFor i = 0 To arr.Count - 1MsgBox("下標(biāo):" & i & " 值:" & arr(i))NextEnd Sub可以看到集合元素通過下標(biāo)訪問,上述代碼中刪除了ArrayList中間的一個(gè)元素,后面的元素自動(dòng)補(bǔ)上。除了調(diào)用Add方法增加元素,還可以使用Insert和AddRange等方法。同樣ArrayList也提供了多種刪除元素的手段,請讀者自行參閱MSDN。需要指出的是,ArrayList中可以存放多種數(shù)據(jù),除了例子中的字符串?dāng)?shù)據(jù),也可以是數(shù)字,甚至是一個(gè)窗體變量。但一般而言,最好在ArrayList中存入同一種數(shù)據(jù)類型的信息,使得代碼便于閱讀和排錯(cuò)。2.HashTable存放在HashTable中的數(shù)據(jù)是一種“鍵∣值(Key-Value)”形式的數(shù)據(jù)。換句話說,HashTable中的每個(gè)數(shù)據(jù)都有一個(gè)惟一的標(biāo)志。與ArrayList不同,要訪問HashTable中的元素,必須給定一個(gè)鍵(Key)。以下是一個(gè)實(shí)例(見配套光盤中的UseCollection):'使用HashTablePrivate Sub btnUseHashTable_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnUseHashTable.Click'創(chuàng)建HashTable對象Dim hst As New Hashtable'向HashTable中增加元素hst.Add("Monday", "星期一")hst.Add("Tuesday", "星期二")hst.Add("Wednesday", "星期三")hst.Add("Thursday", "星期四")hst.Add("Friday", "星期五")hst.Add("Saturday", "星期六")hst.Add("Sunday", "星期日")'訪問HashTable中的元素MsgBox("英文:Monday的中文意思是:" & hst("Monday"))'遍歷HashTable的所有元素ShowHashTable(hst)MsgBox("現(xiàn)在刪除了Wednesday")'刪除星期三hst.Remove("Wednesday")MsgBox("開始查看刪除了Wednesday之后HashTable中的內(nèi)容")'再次遍歷HashTable的所有元素ShowHashTable(hst)End Sub'顯示HashTable內(nèi)容Private Sub ShowHashTable(ByVal hst As Hashtable)Dim myDE As DictionaryEntryFor Each myDE In hstMsgBox("key:" & myDE.Key & " Value:" & myDE.Value & _" Hash值:" & myDE.Key.GetHashCode())Next myDEEnd Sub運(yùn)行這個(gè)示例,讀者會(huì)發(fā)現(xiàn)HashTable中的元素與ArrayList中的元素訪問方式是不一樣的,它必須通過一個(gè)給定的Key來訪問。而要遍歷一個(gè)HashTable,其方法也與ArrayList不一樣,必須使用For Each語句。特別要注意在For Each語句中,用到的變量myDE是DictionaryEntry類型的,這是因?yàn)橛捎贖ashtable 的每個(gè)元素都是一個(gè)鍵/值對,因此元素類型既不是鍵的類型,也不是值的類型,而是 DictionaryEntry 類型。另外,ShowHashTable()過程中用到了一個(gè)GetHashCode()方法,這個(gè)方法由類Object提供,用于返回一個(gè)對象的哈希(Hash)值。所謂哈希值,就是一個(gè)很長的數(shù)字,這個(gè)數(shù)字由特定的數(shù)學(xué)算法(稱為哈希函數(shù))生成。不同的對象擁有不同的哈希值,HashTable就是利用這個(gè)值實(shí)現(xiàn)元素的快速定位的。
圖2-79 不允許向HashTable中加入相同Key的元素解決方法是在向HashTable中添加新元素時(shí),先判斷一下Key是否已存在。下面這個(gè)方法可以安全地向HashTable添加元素:'安全地向HashTable中增加元素的Sub過程Private Sub AddElementToHashTable(ByVal key As Object, _ByVal value As Object,_ByVal hst As Hashtable)'判斷參數(shù)是否有效If (key Is Nothing) OrElse (hst Is Nothing) ThenExit SubEnd If'是否已存在此Key?If hst.ContainsKey(key) ThenExit SubEnd If'將元素加入到HashTable中hst.Add(key, value)End Sub閱讀AddElementToHashTable()過程時(shí)要注意以下幾點(diǎn):(1)第一段代碼先檢測參數(shù)是否有效。這段代碼看似多余,但卻能保證后面的代碼運(yùn)行正常,在開發(fā)大規(guī)模的軟件時(shí)是非常必要的,這種編碼方式稱為“防衛(wèi)性編碼”。其中的OrElse是VB.NET提供的關(guān)鍵字,可以用Or代替,其差別在于使用Or時(shí),其兩邊的表達(dá)式必須都被計(jì)算出來后才可以得出整個(gè)邏輯表達(dá)式的值,而使用OrElse時(shí),只要有一個(gè)表達(dá)式計(jì)算后確認(rèn)其結(jié)果為True,就不再計(jì)算后面的表達(dá)式,這就意味著程序使用OrElse會(huì)比Or運(yùn)行得快一點(diǎn)。
2 實(shí)例運(yùn)行效果分析一下主窗體與輔助窗體之間的信息傳送是如何實(shí)現(xiàn)的。在主窗體中定義了一個(gè)窗體級的變量:'定義輔助窗體的變量Private frm As frmOther = Nothing在btnShowOtherForm按鈕單擊事件中編寫代碼顯示輔助窗體:Private Sub btnShowOtherForm_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnShowOtherForm.ClickIf frm Is Nothing Then '輔助窗體還沒有創(chuàng)建frm = New frmOtherEnd If'顯示輔助窗體frm.Show()End Sub注意一下If條件語句,在前面定義frm變量時(shí)它被初始化為Nothing,因此可以通過這點(diǎn)來判斷是否已創(chuàng)建了一個(gè)窗體對象。如果沒有創(chuàng)建就直接顯示此窗體,會(huì)引發(fā)一個(gè)NullException- Exception異常錯(cuò)誤,這種異常在前面已經(jīng)見過了。那為何不直接創(chuàng)建窗體然后顯示它,非得要先判斷一下呢?這是因?yàn)橛脩艨赡芏啻螁螕暨@個(gè)按鈕,如果每次都新建一個(gè)窗體,則屏幕上會(huì)有多個(gè)輔助窗體,只有最后出現(xiàn)的那個(gè)輔助窗體的用戶輸入才能被主窗體獲取。
試一試注釋掉If和End If語句,保留當(dāng)中的New語句,編譯運(yùn)行看看結(jié)果。在btnGetUerInputText按鈕單擊事件處理程序中書寫代碼,獲取用戶在輔助窗體中輸入的信息:Private Sub btnGetUerInputText_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnGetUerInputText.Click'如果輔助窗體還沒創(chuàng)建,則退出此Sub過程If frm Is Nothing ThenExit SubEnd If'獲取輔助窗體的控件內(nèi)容Me.lblUserInput.Text = frm.txtUserInput.TextEnd Sub可以看到,其中的關(guān)鍵部分只有一句:Me.lblUserInput.Text = frm.txtUserInput.Text可以通過frm變量來直接訪問另一窗體上控件的內(nèi)容。