《C#高級編程》【第7章】運算符與類型強制轉換 -- 學習筆記

       運算符編程語言的基本元素,它使得我們的代碼更加的簡潔明瞭。然而我們爲了解決操作數類型不同的問題,我們又有引入了強制轉換的概念。我們先看看本章的內容構成吧。

1、運算符
      我們來看看一些常見的運算符:
<1>條件運算符
       其語法如下:

Condition ? true_Expression : false_Expression

       當條件Condition爲真時,其將執行true_Expression,否則執行false_Expression。
<2> checked 和 unchecked運算符
       使用語法如下:

checked
{
//代碼塊
}

       使用checked運算符可以檢查被標記的代碼塊中是否有溢出。如果發生溢出,則拋出OverflowException異常。如果我們要禁止溢出檢查,則使用unchecked運算符。
       注意:unchecked是默認行爲。如果沒有被顯式的標記爲checked,則都默認是unchecked。
<3>  is 運算符
        is 運算符可以檢查對象是否與特定類型兼容。例如,我們有兩個對象A和B。如果A和B兼容,那麼表達式A  is  B將爲true,否則就爲false。兼容表示的是,A和B是同一類型或者它們之間具有派生關係。
<4> as 運算符
       as 運算符用於執行引用類型的顯式轉換。如果與要轉換的類型兼容,轉換就會成功進行,否則as運算符就會返回null值。
<5> sizeof 運算符
       sizeof運算符可以確定棧中值類型的長度(單位:字節)。如果對非基本類型使用sizeof運算符,就需要把代碼放入unsafe塊中。
<6>typeof運算符
       typeof運算符可以返回一個特定類型的System.Type對象。例如,typeof(string)返回表示System.String類型的Type對象。
<7>可空類型和運算符
        通常可空類型使用一元或二元運算符,只要有一個操作數爲null,那麼結果就爲null。在比較可空類型時,只要有一個操作數爲null那麼比較結果就是false。所以不能因爲一個條件是false其對立面就是true。
<8>空合併運算符(??)
       空合併運算符(??)爲可空類型轉換爲非空類型提供了便利。我們以int的可空類型爲例:

int? a = null;
int b = a ?? 10;	//這時b就等於10
a = 3;
b = a ?? 10;		//這時b就等於3

2、比較對象的相等性
      根據比較機制的不同,我們可以分爲引用類型的比較和值類型的比較:
<1>比較引用類型的相等性
      System.Object提供了4種方式來比較對象的相等性。 

①ReferenceEquals()方法
      ReferenceEquals()方法是一個靜態方法,用於測試兩個引用是否爲同一個實例。作爲靜態方法,所以它不能被重寫。
②虛擬的Equals()方法
       虛擬的Equals()方法,這它默認的比較引用。不過因爲它是虛擬的方法,所以我們可以重寫它,使它按值來比較對象。
③靜態的Equals()方法
       Equals()的靜態版本與虛擬版本的功能一致,區別:靜態版本帶有兩個參數,可以處理兩個對象中有一個是null的方法。但是如果比較兩個引用,它就相當於調用Equals()的虛擬版本,即相當於重寫了靜態的Equals()方法
④比較運算符(==)
       通常引用類型使用比較運算符(沒有重載前)都是比較引用(System.String除外,因爲.Net對String重寫了比較運算符)。
<2>比較值類型的相等性
       在比較值類型的相等性時,與引用類型相同:ReferenceEquals()用於比較引用,Equals用於比較值,對於基本類型==則是比較值,然而結構需要重載==運算符纔可以進行比較。
注意:ReferenceEquals()方法引用於值類型時,它總是false。因爲調用這個方法,值類型需要被裝箱到對象中,故不會得到相同的引用。
3、運算符重載
       在很多時候,我們使用運算符來表示一個表達式,會使我們的程序變得更加簡潔易懂,另一方面運算符的重載還可以提升我們的開發效率。假設我們定義了一個矩陣類Matrix的對象a, b, c。假設我們來表達 c = a + b,用方法來表達的話可能是這樣 c = Matrix.Add(a, b); 相信通過這個例子大家應該都瞭解了運算符重載的好處了吧。
      現在我們來看看運算符重載的語法吧:

//參數列表中的參數個數,取決於重載的運算符是幾元運算符
public static [返回的類型] operator [重載的運算符] (參數列表)
{
	//processing
}

      知道了重載的語法我們現在來實戰一下:

//我們以向量vector類爲例,假設它有三個整型字段x, y, z, 定義向量加法
public static vector operator + (vector lhs, vector rhs)
{
	vector ans = new vector();
	ans.x = lhs.x + rhs.x;
	ans.y = lhs.y + rhs.y;
	ans.z = lhs.z + rhs.z;
	return ans;
}

       這時我們就完成了vector類的+運算符的重載,現在我們就可以直接使用+進行加法運算。同時編譯器還自動完成了+=的重載。
       還有兩個運算符我們不能直接重載,分別是索引運算符和強制轉換運算符。
       我們先看看索引運算符的重載吧,語法如下:

[訪問屬性] [返回類型] this[int index]
{
	get {  //添加返回第index個元素的代碼  }
	set{  //設置第index個元素的值   }
}

       我們現在以鏈表類(LinkedList)爲例:假設LinkedList基本元素類型爲LinkedListNode,且有Query(int k)方法用於返回第k個元素的引用和Mod(int k, LinkedListNode node)方法用於修改第k個元素的值。所以我們重載索引運算符,如下:

public LinkedListNode this[int index]
{
	get
	{
		return Query(index);
	}
	set
  	{
		Mod(index, value);
	}
}


        我們已經完成了索引運算符的重載,現在我們可以直接使用下標進行訪問集合的元素了。
注意:在C# 中比較運算符必須成對的重載。對於自增運算符(++)或者自減運算符(--)運算符,在C# 中只要重載後置++,那麼編譯器就會自動重載好前置++,同理自減運算符(--)也一樣。
4、類型強制轉換
        在很多時候我們不能保證所有操作數的類型相同,這是我們就需要強制轉換。然而強制轉換又分爲顯式強制轉換和隱式強制轉換。不同數據類型之間的轉換有所不同,強制轉換可以分爲五類:
<1>預定義類型強制轉換
       在預定義的數據類型執行強制轉換,我們只需謹記一個準則就好:
       大數據轉換爲小數據需要顯式強制轉換(因爲這是不安全的,可能會丟失數據),小數據轉換爲大數據可以顯式強制轉換也可以隱式的(因爲它總是安全的)。
<2>裝箱和拆箱
       在強制轉換的過程中往往會遇到值類型和引用類型之間的相互轉換。然而因爲值類型在棧上,引用類型在託管堆上。這是他們要互相轉換就需要裝箱和拆箱的操作了。
       裝箱(boxing):在堆上創建一個臨時的引用類型”箱子”,把值類型裝到”箱子”裏。
       拆箱(unboxing):與裝箱相反的一個過程。把之前裝進”箱子”裏的值類型變回原樣。
注意:裝箱(值類型轉換爲引用類型)可以顯式轉換也可以隱式轉換。然而拆箱必須爲顯式強制轉換。且拆箱時,必須保證現有的值類型變量必須有足夠的空間儲存拆箱的值的所有字節。否則將會拋出InvalidCastException異常。
<3>基類與派生類之間的強制轉換
        編譯器提供了基類和派生類之間的強制轉換,但是事實上這種轉換並沒有對對象進行任何數據轉換。只是改變了對象的引用。因爲一個基類引用可以引用一個派生類的實例。所以就可以完成派生類對基類的轉換。如果基類引用的對象不是派生類的對象,那麼基類轉換爲派生類將會失敗,並且拋出一個異常。
       我們用一段代碼來說明(MyBase爲基類,MyDerived爲MyBase的派生類):

MyBase B1 = new MyDerived();		//隱式的從派生類轉換爲基類
MyBase B2 = new MyBase();
MyDerived D1 = (MyDerived)B1;		//成功
MyDerived D2 = (MyDerived)B2		//拋出異常

<4>自定義的類型強制轉換
      自定義的類型強制轉換,類似於重載運算符。假設我們要從類型A轉換爲類型B類型。那麼我們定義以強制轉換的語法如下:

public static [強制類型轉換方式] B (A value)
{
	//processing
}

       強制轉換方式分爲顯示轉換(explicit)和隱式轉換(implicit)。
       這裏我們用一個例子來了解自定義的類型強制轉換。我們以之前的vector類爲例,假設我們vector顯式強制轉換爲double(計算向量的模長的平方)。

public static explicit double(vector v)
{
	return (v.x*v.x + v.y*v.y + v.z*v.z);
}

       假設我們有vector的一個對象Test和一個double型的 Len。

Len = (double)Test;	//現在我們就完成了自定義類型強制轉換的調用

注意:在不斷的轉換的過程中,有時因爲數據類型的精度不夠會造成精度的損失。只是我們可以使用Convert.ToUInt16()方法來避免精度的損失。但是會有性能損失。還可以用精度高的數據類型來避免這個問題。
        當然我們在實際過程中不可能總是和基本數據類型進行轉換,類與類之間的轉換纔是我們日常中運用比較多的。類與類之間的轉換和類與基本數據類型的轉換類似。唯一的區別就是,類與類之間的強制轉換有兩個限制
①類與類之間具有繼承派生關係,那麼就不能定義類型之間的強制轉換(因爲它們之間已經存在強制轉換)
②類型強制轉換必須在源數據類型或目標數據類型的內部定義
(這是爲了防止第三方把類型強制轉換引入類中)
<5>多重類型強制轉換
       在轉換的過程中沒有直接的強制轉換方式C#編譯器會需找一種轉換方式把幾種強制轉換合併起來。例如:我們有一個Foo類,定義了Foo –> int 的強制轉換,現在我們讓Foo的對象轉換爲double類型,那麼編譯器就會這樣轉換:Foo -> int -> double。但是如果依賴於多重強制轉換,那麼程序的性能將會不足。也就是說如果我們需要Foo -> double的轉換,我們直接定義這種轉換,性能比多重轉換將會更具有優勢。

1、C#支持的運算符


運算符

初級運算符

()  .  []  x++  x--  new  typeof  sizeof  checked  unchecked

一元運算符

+  -  !  ~  ++x  --x  數據類型強制轉換

乘/除運算符

*  /  %

加/減運算符

+  -

移位運算符

<<  >>

關係運算符

<  >  <=  >=  is  as

比較運算符

==  !=

按位AND運算符

&

按位XOR運算符

^

按位OR運算符

|

布爾AND運算符

&&

布爾OR運算符

||

條件運算符

?:

賦值運算符

=  +=  -=  *=  /=  %=  &=  |=  ^=  <<=  >>=   >>>=

2、支持重載的運算符

類別

運算符

限制

算術二元運算符

+  *   /   -   %


算術一元運算符

+   -   ++   --


按位二元運算符

&   |    ^   <<    >>


按位一元運算符

!   ~    true    false

true和false必須成對重載

比較運算符

 ==   !=   >=  <=   <   >

比較運算符必須成對重載

賦值運算符

+=   -=   *=  /=  %=  &=  |=  ^=  <<=  >>=

不要顯式的重載這些運算符,編譯器會隱式的重載

索引運算符

[]

不能夠直接重載索引運算符

數據類型強制轉換運算符

()

不能夠直接重載強制轉換運算符



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章