- 軟件開發大講堂·從入門到精通-第一輯(套裝共5冊)
- 明日科技
- 7955字
- 2020-05-21 17:58:46
第3章 變量與常量
(視頻講解:2小時27分鐘)
應用程序的開發離不開變量與常量的應用。變量本身被用來存儲特定類型的數據,而常量則存儲不變的數據值。本章詳細地介紹變量的類型和基本操作,同時對常量也做了詳細的講解,講解過程中為了便于讀者理解結合了大量的舉例。
通過閱讀本章,您可以:
了解變量的基本概念
掌握變量的聲明及賦值
熟悉變量的作用域
掌握值類型的概念及用法
掌握引用類型的概念及用法
熟悉枚舉類型的概念及用法
掌握常用的類型轉換方式
了解常量的概念及用法
3.1 變量的基本概念

視頻講解
變量本身被用來存儲特定類型的數據,可以根據需要隨時改變變量中所存儲的數據值。變量具有名稱、類型和值。變量名是變量在程序源代碼中的標識。變量類型確定它所代表的內存的大小和類型,變量值是指它所代表的內存塊中的數據。在程序的執行過程中,變量的值可以發生變化。使用變量之前必須先聲明變量,即指定變量的類型和名稱。
3.2 變量的聲明及賦值

視頻講解
變量在使用之前,必須進行聲明并賦值,本節將對變量的聲明及賦值,以及變量的作用域進行詳細講解。
3.2.1 聲明變量
變量的使用是程序設計中一個十分重要的環節。為什么要定義變量呢?簡單地說,就是要告訴編譯器(compiler)這個變量是屬于哪一種數據類型,這樣編譯器才知道需要配置多少空間給它,以及它能存放什么樣的數據。在程序運行過程中,空間內的值是變化的,這個內存空間就稱為變量。聲明變量就是指定變量的名稱和類型,變量的聲明非常重要,未經聲明的變量本身并不合法,也因此沒有辦法在程序當中使用。在C#中,聲明一個變量是由一個類型和跟在后面的一個或多個變量名組成的,多個變量之間用逗號分開,聲明變量以分號結束。
【例3.1】聲明一個整型變量num,然后再同時聲明3個字符型變量str1、str2和str3,代碼如下。

在第1行代碼中,聲明了一個名稱為num的整型變量。在第二行代碼中,聲明了3個字符串型的變量,分別為str1、str2和str3。
聲明變量時,還可以初始化變量,即在每個變量名后面加上給變量賦初始值的指令。
【例3.2】聲明一個整型變量a,并且賦值為927。然后,再同時聲明3個字符型變量,并初始化,代碼如下。

在聲明變量時,要注意變量名的命名規則。C#的變量名是一種標識符,應該符合標識符的命名規則。變量名是區分大小寫的,下面列出變量的命名規則。
? 變量名只能由數字、字母和下畫線組成。
? 變量名的第一個符號只能是字母和下畫線,不能是數字。
? 不能使用關鍵字作為變量名。
? 一旦在一個語句塊中定義了一個變量名,那么在變量的作用域內都不能再定義同名的變量。
說明
在C#語言中允許使用漢字或其他語言文字作為變量名,如“int年齡=21”,在程序運行時并不出現什么錯誤,但建議讀者盡量不要使用這些語言文字作為變量名。
3.2.2 變量的賦值
在C#中,使用賦值運算符“=”(等號)來給變量賦值,將等號右邊的值賦給左邊的變量。
【例3.3】聲明一個變量,并給變量賦值,代碼如下。

在3.2.1節介紹的初始化變量,其實是一種特殊的賦值方式,它在聲明變量的同時給變量賦值。在給變量賦值時,等號右邊也可以是一個已經被賦值的變量。
【例3.4】首先聲明兩個變量sum和num,然后將變量sum賦值為927,最后將變量sum賦值給變量num,代碼如下。

注意
在對多個同類型的變量賦同一個值時,為了節省代碼的行數,可以同時對多個變量進行初始化:int a,b,c,d,e;a=b=c=d=e=0;。
3.2.3 變量的作用域
由于變量被定義出來后只是暫存在內存中,等到程序執行到某一個點后,該變量會被釋放掉,也就是說變量有它的生命周期。因此,變量的作用域是指程序代碼能夠訪問該變量的區域,若超出該區域,則在編譯時會出現錯誤。在程序中,一般會根據變量的“有效范圍”將變量分為“成員變量”和“局部變量”。
1. 成員變量
在類體中定義的變量被稱為成員變量,成員變量在整個類中都有效。類的成員變量又可分為兩種,即靜態變量和實例變量。
【例3.5】聲明靜態變量和實例變量,實例代碼如下。

其中,x為實例變量,y為靜態變量(也稱類變量)。如果在成員變量的類型前面加上關鍵字static,這樣的成員變量稱為靜態變量。靜態變量的有效范圍可以跨類,甚至可達到整個應用程序之內。對于靜態變量,除了能在定義它的類內存取,還能直接以“類名.靜態變量”的方式在其他類內使用。
2. 局部變量
在類的方法體中定義的變量(方法內部定義,“{”與“}”之間的代碼中聲明的變量)稱為局部變量。局部變量只在當前代碼塊中有效。
在類的方法中聲明的變量,包括方法的參數,都屬于局部變量。局部變量只有在當前定義的方法內有效,不能用于類的其他方法中。局部變量的生命周期取決于方法,當方法被調用時,C#編譯器為方法中的局部變量分配內存空間,當該方法的調用結束后,則會釋放方法中局部變量占用的內存空間,局部變量也將會銷毀。
變量的有效范圍如圖3.1所示。

圖3.1 變量的有效范圍
【例3.6】創建一個控制臺應用程序,使用for循環將從0~20的數字顯示出來。然后在for語句中聲明變量i,此時i就是局部變量,其作用域只限于for循環體內,代碼如下。(實例位置:資源包\TM\sl\3\1)

程序運行結果為“0~20的數字”。
互動練習:某著名的在線通信軟件公司出過這么一道面試題,說“經理有3個女兒,她們的年齡和是13歲,年齡的乘積等于經理的年齡。有位員工知道經理年齡,但是不能確定他3個女兒都是多大,這時,經理跟他說,我只有一個女兒超過5歲,于是那位員工就知道了經理3個女兒的年齡,那么經理的3個女兒都分別是多大?”。使用C#推算經理3個女兒的年齡。
3.3 數據類型

視頻講解
C#中的變量類型根據其定義可以分為兩種:一種是值類型;另一種是引用類型。這兩種類型的差異在于數據的存儲方式,值類型的變量本身直接存儲數據。而引用類型則存儲實際數據的引用,程序通過此引用找到真正的數據,在以下內容中將會對這些類型進行詳細講解。
3.3.1 值類型
值類型變量直接存儲其數據值,主要包含整數類型、浮點類型以及布爾類型等。值類型變量在棧中進行分配,因此效率很高,使用值類型主要目的是為了提高性能。值類型具有如下特性。
? 值類型變量都存儲在棧中。
? 訪問值類型變量時,一般都是直接訪問其實例。
? 每個值類型變量都有自己的數據副本,因此對一個值類型變量的操作不會影響其他變量。
? 復制值類型變量時,復制的是變量的值,而不是變量的地址。
? 值類型變量不能為null,必須具有一個確定的值。
值類型是從System.ValueType類繼承而來的類型,下面詳細介紹值類型中包含的幾種數據類型。
1. 整數類型
整數類型用來存儲整數數值,即沒有小數部分的數值。可以是正數,也可以是負數。整型數據在C#程序中有3種表示形式,分別為十進制、八進制和十六進制。
? 十進制:十進制的表現形式大家都很熟悉,如120、0、-127。
注意
不能以0作為十進制數的開頭(0除外)。
? 八進制:如0123(轉換成十進制數為83)、-0123(轉換成十進制數為-83)。
注意
八進制必須以0開頭。
? 十六進制:如0x25(轉換成十進制數為37)、0Xb01e(轉換成十進制數為45086)。
注意
十六進制必須以0X或0x開頭。
在C#中內置的整數類型如表3.1所示。
表3.1 C#內置的整數類型

byte類型以及short類型是范圍比較小的整數,如果正整數的范圍沒有超過65535,聲明為ushort類型即可,當然更小的數值直接以byte類型作處理即可。只是使用這種類型時必須特別注意數值的大小,否則可能會導致運算溢出的錯誤。
【例3.7】創建一個控制臺應用程序,在其中聲明一個int類型的變量ls并初始化為927、一個byte類型的變量shj并初始化為255,最后輸出,代碼如下。(實例位置:資源包\TM\sl\3\2)

程序運行結果為:

此時,如果將byte類型的變量shj賦值為266,重新編譯程序,就會出現錯誤提示。主要原因是byte類型的變量是8位無符號整數,它的范圍為0~255,266已經超出了byte類型的范圍,所以編譯程序會出現錯誤提示。
注意
在定義局部變量時,要對其進行初始化。
2. 浮點類型
浮點類型變量主要用于處理含有小數的數值數據,浮點類型主要包含float和double兩種數值類型。表3.2列出了這兩種數值類型的描述信息。
表3.2 浮點類型及描述

如果不做任何設置,包含小數點的數值都被認為是double類型,例如9.27,沒有特別指定的情況下,這個數值是double類型。如果要將數值以float類型來處理,就應該通過強制使用f或F將其指定為float類型。
【例3.8】下面的代碼就是將數值強制指定為float類型。

如果要將數值強制指定為double類型,則應該使用d或D進行設置,但加不加d或D沒有硬性規定,可以加也可以不加。
【例3.9】下面的代碼就是將數值強制指定為double類型。

注意
如果需要使用float類型變量時,必須在數值的后面跟隨f或F,否則編譯器會直接將其作為double類型處理。也可以在double類型的值前面加上(float),對其進行強制轉換。
3. 布爾類型
布爾類型主要用來表示true和false值,一個布爾類型的變量,其值只能是true或者false,不能將其他的值指定給布爾類型變量,布爾類型變量不能與其他類型之間進行轉換。布爾類型通常被用在流程控制中作為判斷條件。
【例3.10】將927賦值給布爾類型變量x,代碼如下。

這樣賦值顯然是錯誤的,編譯器會返回錯誤提示“常量值927無法轉換為bool”。布爾類型變量大多數被應用到流程控制語句當中,例如,循環語句或者if語句等。
說明
在定義全局變量時,如果沒有特定的要求不用對其進行初始化,整數類型和浮點類型的默認初始化為0,布爾類型的初始化為false。
闖關訓練:開發財務系統時,通過值類型創建存儲流動資金金額的臨時性變量。
3.3.2 引用類型
引用類型是構建C#應用程序的主要對象類型數據。在應用程序執行的過程中,預先定義的對象類型以new創建對象實例,并且存儲在堆中。堆是一種由系統彈性配置的內存空間,沒有特定大小及存活時間,因此可以被彈性地運用于對象的訪問。引用類型就類似于生活中的代理商,代理商沒有自己的產品,而是代理廠家的產品,使其就好像是自己的產品一樣。
引用類型具有如下特征。
? 必須在托管堆中為引用類型變量分配內存。
? 使用new關鍵字來創建引用類型變量。
? 在托管堆中分配的每個對象都有與之相關聯的附加成員,這些成員必須被初始化。
? 引用類型變量是由垃圾回收機制來管理的。
? 多個引用類型變量可以引用同一對象,這種情形下,對一個變量的操作會影響另一個變量所引用的同一對象。
? 引用類型被賦值前的值都是null。
所有被稱為“類”的都是引用類型,主要包括類、接口、數組和委托。下面通過一個實例來演示如何使用引用類型。
【例3.11】創建一個控制臺應用程序,在其中創建一個類C,在此類中建立一個字段Value,并初始化為0,然后在程序的其他位置通過new關鍵字創建對此類的引用類型變量,最后輸出,代碼如下。(實例位置:資源包\TM\sl\3\3)

程序運行結果如下。

3.3.3 值類型與引用類型的區別
從概念上看,值類型直接存儲其值,而引用類型存儲對其值的引用。這兩種類型存儲在內存的不同地方。在C#中,必須在設計類型時就決定類型實例的行為。如果在編寫代碼時不能理解引用類型和值類型的區別,那么將會給代碼帶來不必要的異常。
從內存空間上看,值類型是在棧中操作,而引用類型則在堆中分配存儲單元。棧在編譯時就分配好內存空間,在代碼中有棧的明確定義,而堆是程序運行中動態分配的內存空間,可以根據程序的運行情況動態地分配內存的大小。因此,值類型總是在內存中占用一個預定義的字節數。而引用類型的變量則在堆中分配一個內存空間,這個內存空間包含的是對另一個內存位置的引用,這個位置是托管堆中的一個地址,即存放此變量實際值的地方。
也就是說值類型相當于現金,要用就直接用,而引類型相當于存折,要用得先去銀行取。
說明
C#的所有值類型均隱式派生自System.ValueType,而System.ValueType直接派生于System.Object。即System.ValueType本身是一個類類型,而不是值類型。其關鍵在于ValueType重寫了Equals()方法,從而對值類型按照實例的值來比較,而不是引用地址來比較。
下面以一段代碼來詳細講解一下值類型與引用類型的區別,代碼如下。

運行結果如圖3.2所示。

圖3.2 值類型與引用類型
從圖3.2中可以看出,當改變了Stamp_1.Age的值時,age沒跟著變,而在改變了Stamp_2.Name的值后,guru.Name卻跟著變了,這就是值類型和引用類型的區別。在聲明age值類型變量時,將Stamp_1.Age的值賦給它,這時,編譯器在棧上分配了一塊空間,然后把Stamp_1.Age的值填進去,二者沒有任何關聯,就像在計算機中復制文件一樣,只是把Stamp_1.Age的值復制給age了。而引用類型則不同,在聲明guru時把Stamp_2賦給它。前面說過,引用類型包含的只是堆上數據區域地址的引用,其實就是把Stamp_2的引用也賦給guru,因此它們指向了同一塊內存區域。既然是指向同一塊區域,不管修改誰,另一個的值都會跟著改變。就像信用卡跟親情卡一樣,用親情卡取了錢,與之關聯的信用卡賬上也會跟著發生變化。
3.3.4 枚舉類型
枚舉類型是一種獨特的值類型,它用于聲明一組具有相同性質的常量,編寫與日期相關的應用程序時,經常需要使用年、月、日、星期等日期數據,可以將這些數據組織成多個不同名稱的枚舉類型。使用枚舉可以增加程序的可讀性和可維護性。同時,枚舉類型可以避免類型錯誤。
說明
在定義枚舉類型時,如果不對其進行賦值,默認情況下,第一個枚舉數的值為0,后面每個枚舉數的值依次遞增1。
在C#中使用關鍵字enum類聲明枚舉,其形式如下。

其中,大括號“{}”中的內容為枚舉值列表,每個枚舉值均對應一個枚舉值名稱,value1~valueN為整數數據類型,list1~listN則為枚舉值的標識名稱。下面通過一個實例來演示如何使用枚舉類型。
【例3.12】創建一個控制臺應用程序,通過使用枚舉來判斷當前系統日期是星期幾,代碼如下。(實例位置:資源包\TM\sl\3\4)

程序運行的結果為“今天是星期三”。
查看程序運行的結果,因為當前日期是2019年5月22日星期三,所以輸出的結果顯示當天是星期三。程序首先通過enum關鍵字建立一個枚舉,枚舉值名稱分別代表一周的七天,如果枚舉值名稱是Sun,說明其代表的是一周中的星期日,其枚舉值為0,以此類推。然后,聲明一個int類型的變量k,用于獲取當前表示的日期是星期幾。最后,調用switch語句,輸出當天是星期幾。
3.3.5 類型轉換
類型轉換就是將一種類型轉換成另一種類型,轉換可以是隱式轉換,也可以是顯式轉換,本節將詳細介紹這兩種轉換方式,并講解有關裝箱和拆箱的內容。
說明
要理解類型轉換,讀者可以這么想象,大腦前面是一片內存,源和目標分別是兩個大小不同的內存塊(由變量及數據的類型來決定),將源數據賦值給目標內存的過程,就是用目標內存塊去套取源內存中的數據,能套多少算多少。
1. 隱式轉換
所謂隱式轉換就是不需要聲明就能進行的轉換。進行隱式轉換時,編譯器不需要進行檢查就能自動進行轉換。表3.3列出了可以進行隱式轉換的數據類型。
表3.3 隱式類型轉換表

從int、uint、long或ulong到float,以及從long或ulong到double的轉換可能導致精度損失,但是不會影響其數量級。其他的隱式轉換不會丟失任何信息。
說明
當一種類型的值轉換為大小相等或更大的另一類型時,則發生擴大轉換;當一種類型的值轉換為較小的另一種類型時,則發生收縮轉換。
【例3.13】將int類型的值隱式轉換成long類型,代碼如下。

2. 顯式轉換
顯式轉換也可以稱為強制轉換,需要在代碼中明確地聲明要轉換的類型。如果要把高精度的變量的值賦給低精度的變量,就需要使用顯式轉換。表3.4列出了需要進行顯式轉換的數據類型。
表3.4 顯式類型轉換表

由于顯式轉換包括所有隱式轉換和顯式轉換,因此總是可以使用強制轉換表達式從任何數值類型轉換為任何其他的數值類型。
【例3.14】創建一個控制臺應用程序,將double類型的x進行顯式類型轉換,代碼如下。(實例位置:資源包\TM\sl\3\5)

程序運行結果為19810927。
也可以通過Convert關鍵字進行顯式類型轉換,上述例子還可以通過下面的代碼實現。
【例3.15】創建一個控制臺應用程序,通過Convert關鍵字進行顯式類型轉換,代碼如下。

3. 裝箱和拆箱
將值類型轉換為引用類型的過程叫作裝箱,相反,將引用類型轉換為值類型的過程叫作拆箱,下面將通過例子詳細介紹裝箱與拆箱的過程。
(1)裝箱。裝箱允許將值類型隱式轉換成引用類型,下面通過一個實例演示如何進行裝箱操作。
【例3.16】創建一個控制臺應用程序,聲明一個整型變量i并賦值為2019,然后將其復制到裝箱對象obj中,最后再改變變量i的值,代碼如下。(實例位置:資源包\TM\sl\3\6)

程序的運行結果為:

從程序運行結果可以看出,值類型變量的值復制到裝箱得到的對象中,裝箱后改變值類型變量的值,并不會影響裝箱對象的值。
(2)拆箱。拆箱允許將引用類型顯式轉換為值類型,下面通過一個示例演示拆箱的過程。
【例3.17】創建一個控制臺應用程序,聲明一個整型變量i并賦值為112,然后將其復制到裝箱對象obj中,最后,進行拆箱操作將裝箱對象obj賦值給整型變量j,代碼如下。(實例位置:資源包\TM\sl\3\7)

程序運行結果為:

查看程序運行結果,不難看出,拆箱后得到的值類型數據的值與裝箱對象相等。需要讀者注意的是,在執行拆箱操作時要符合類型一致的原則,否則會出現異常。
說明
裝箱是將一個值類型轉換為一個對象類型(object),而拆箱則是將一個對象類型顯式轉換為一個值類型。對于裝箱而言,它是將被裝箱的值類型復制一個副本來轉換;而對于拆箱而言,需要注意類型的兼容性,例如,不能將一個值為“string”的object類型轉換為int類型。
互動練習:嘗試使用C#制作一個可以在窗體上畫桃花的游戲,具體要求為:在窗體的左側顯示桃花的3種狀態:花骨朵、花蕾、開花,然后用鼠標單擊某一種狀態,即可在右側顯示的桃枝上繪制桃花的相應狀態。
3.4 常量

視頻講解
常量就是其值固定不變的量,而且常量的值在編譯時就已經確定了。常量的類型只能為下列類型之一:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string等。C#中使用關鍵字const定義常量,并且在創建常量時必須設置它的初始值。常量就相當于每個公民的身份證號,一旦設置就不允許修改。
【例3.18】聲明一個正確的常量,同時再聲明一個錯誤的常量,以便讀者對比參考,代碼如下。

與變量不同,常量在整個程序中只能被賦值一次。在為所有的對象共享值時,常量是非常有用的。下面通過一個例子演示常量與變量的差異。
【例3.19】創建一個控制臺應用程序,首先聲明一個變量MyInt并且賦值為927,然后再聲明一個常量MyWInt并賦值為112,最后將變量MyInt賦值為1039,關鍵代碼如下。(實例位置:資源包\TM\sl\3\8)

執行程序,輸出的結果為:

變量MyInt的初始化值為927,而常量MyWInt的值等于112,由于變量的值是可以修改的,所以變量MyInt可以重新被賦值為1039后輸出。通過查看輸出結果,可以看到變量MyInt的值已經被修改,如果嘗試修改常量MyWInt的值,編譯器會出現錯誤信息,阻止進行這樣的操作。
3.5 小結
本章重點講解了變量和常量,通過大量的舉例說明,使讀者更好地理解所學知識的用法。在閱讀本章時,要重點掌握值類型、引用類型和枚舉類型的概念及用法,并且要了解如何進行類型轉換。了解變量的基本知識后,要掌握如何對變量進行操作,了解變量的作用域以及如何為變量賦值。本章最后對常量進行了詳細的敘述,包括常量的概念及常量的基本類型。
3.6 實踐與練習
(1)嘗試開發一個程序,在該程序中建立一個靜態方法,在靜態方法中聲明一個局部變量,并對其賦值,然后輸出。(答案位置:資源包\TM\sl\3\9)
(2)嘗試開發一個程序,要求聲明一個常量,然后試著更改這個常量的值,看會引發什么錯誤。(答案位置:資源包\TM\sl\3\10)
3.7 動手糾錯
(1)運行“資源包\TM\排錯練習\03\01”文件夾下的程序,出現“常量值300無法轉換為byte”的錯誤提示,請根據注釋改正程序。
(2)運行“資源包\TM\排錯練習\03\02”文件夾下的程序,出現“意外的字符‘;’”的錯誤提示,請根據注釋改正程序。
(3)運行“資源包\TM\排錯練習\03\03”文件夾下的程序,出現“不能隱式地將double類型轉換為float類型;請使用F后綴創建此類型”的錯誤提示,請根據注釋改正程序。
(4)運行“資源包\TM\排錯練習\03\04”文件夾下的程序,出現“無法將類型double隱式轉換為int。存在一個顯式轉換(是否缺少強制轉換?)”的錯誤提示,請根據注釋改正程序。
(5)運行“資源包\TM\排錯練習\03\05”文件夾下的程序,出現“未處理InvalidCastException指定的轉換無效”的錯誤提示,請根據注釋改正程序。
(6)運行“資源包\TM\排錯練習\03\06”文件夾下的程序,出現“賦值號左邊必須是變量、屬性或索引器”的錯誤提示,請根據注釋改正程序。
(7)運行“資源包\TM\排錯練習\03\07”文件夾下的程序,出現“無法使用實例引用訪問成員Test07.Test.PI;請改用類型名稱對其加以限定”的錯誤提示,請根據注釋改正程序。