表達式和運算符
表達式
本章將定義表達式,並描述C#提供的運算符。
運算符是一個符號,它表示返回單個結果的操作。操作數(operand)指作為運算符輸入的數據元素。一個運算符會:
- 將操作數作為輸入
- 執行某個操作
- 基於該操作返回一個值
表達式是運算符和操作數的字符串。可以作為操作數的結構有:
- 字面量
- 常量
- 變量
- 方法調用
- 元素訪問器,如數組訪問器和索引器
- 其他表達式
例:下面的表達式,有3個運算符和4個操作數
字面量
字面量(literal)是源代碼中鍵入的數字或字符串,表示一個指定類型的明確的、固定的值。
例:字面量
class Program { static void Main() { Console.WriteLine("{0}",1024);//整數字面量 Console.WriteLine("{0}",3.1416F);//浮點型字面量 Console.WriteLine("{0}",true);//布爾型字面量 } }
對於引用類型變量,字面量null表示變量沒有設置為內存中的數據。
整數字面量
例:不同的整數類型
236 //整型 236L //長整型 236U //無符號整型 236UL//無符號長整型
整數字面量還可以寫成十六進制(hex)形式
實數字面量
實數字面量組成如下:
- 十進制數字
- 可選的小數點
- 可選的指數部分
- 可選的后綴
例:實數字面量的不同格式
float f1=236F; double d1=236.712; double d2=.351; double d3=6.338e-26;
無后綴的實數字面量默認是double類型。
字符字面量
字符字面量可以是下面任意一種:
- 單個字符
- 簡單轉義序列:反斜杠+單個字符
- 十六進制轉義序列:反斜杠+大寫或小寫x+4個十六進制數
- Unicode轉義序列:反斜杠+大寫或小寫u+4個十六進制數
例:字符字面量的不同格式
char c1='d'; char c2='\n'; char c3='\x0061'; char c4='\u005a';
一些特殊字符及其編碼見下圖
字符串字面量
兩種字符串字面量類型:
- 常規字符串字面量
- 逐字字符串字面量
常規字符串字面量包含:
- 字符
- 簡單轉義序列
- 十六進制和Unicode轉義序列
string st1="Hi there!"; string st2="Val\t5,val\t10"; string st3="Add\x000ASome\u0007Interest";
逐字字符串以@為前綴,它有以下特征:
- 逐字字符串與常規字符串區別在於轉義字符串不會被求值。在雙引號中間的所有內容,包括通常被認為是轉義序列的內容,都被嚴格按字符串中列出的那樣打印
- 逐字字符串的唯一例外是相鄰的雙引號組,它們被解釋為單個雙引號字符
string rst1="Hi there!"; string vst1=@"Hi there!"; string rst2="It started,\"Four score and seven...\""; string vst2=@"It started,""Four score and seven..."""; string rst3="Value 1 \t 5,val2 \t 10"; string vst3=@"Value 1 \t 5,val2 \t 10"; string rst4="C:\\Program Files\\Microsoft\\"; string vst4=@"C:\Program Files\Microsoft\"; string rst5=" Print \x000A Multiple \u000A Lines"; string vst5=@" Print Multiple Lines";
編譯器讓相同的字符串字面量共享堆中同一內存位置以節約內存
求值順序
表達式可以由許多嵌套的子表達式構成。子表達式的求值順序可以使表達式最終值發生差別。
優先級
正如小學的先乘除再加減,C#中運算符也有優先級。
結合性
表達式中運算符優先級不同的,從高到低依次運算。但若是運算符優先級相同怎么辦?
當連續運算符有相同優先級時,求值順序由操作結合性決定。
- 左結合運算符從左至右
- 右結合運算符從右至左
- 除賦值運算符外,其他二元運算符都是左結合
- 賦值運算符和條件運算符是右結合
簡單算術運算符
求余運算符
求余運算符(%)用第二個操作數除第一個操作數,並返回余數。
求余運算符是二元左結合運算符。
- 0%3=0,因為0除3得0余0
- 2%3=2,因為2除3的0余2
- 4%3=1,因為4除3得1余1
關系比較運算符和相等比較運算符
它們都是二元左結合運算符,對其操作數進行比較並返回bool值。
與C和C++不同,在C#中數字不具有布爾意義
int x=5; if(x)//錯,x是int類型,不是布爾型 if(x==5)//對,返回true
比較操作和相等性操作
對於大多數引用類型來說,比較它們的相等性,將只比較它們的引用。
- 如果引用相等,即它們指向內存中相同對象,相等性為true,否則為false,即使內存中兩個分離的對象在所有其他方面完全相等。
- 這稱為淺比較
下圖闡明了引用類型的比較
- 圖左邊,a和b兩者引用相同,返回true
- 圖右邊,引用不同,所以即使內容相同,也返回false
string類型也是引用類型,但它的比較方式不同。比較字符串的相等性,將比較它們的長度和內容(區分大小寫)
- 如果兩個字符串長度和內容相等則返回true,即使它們占用不同內存區域
- 這稱為深比較(deep comparison)
將在第15章介紹的委托也是引用類型,並且也使用深比較。比較委托的相等性時,讓兩個委托都是null,或兩者的調用列表有相同數目成員,並且調用列表想匹配,則返回true。
比較數值表達式,將比較類型和值。比較enum類型時,比較操作數的實際值。枚舉在第13章闡述。
遞增運算符和遞減運算符
無論運算符前置還是后置,只影響返回給表達式的值。在語句執行后,最終存放在操作數的變量的值相同
條件邏輯運算符
條件邏輯運算符使用“短路”(short circuit)模式操作,意思是,如果計算Expr1之后結果已確定,那么它會跳過Expr2的求值。
例:短路示例
bool bVal; bVal=(1==2)&&(2==2); //左側false,接着&&運算,結果必是false,所以跳過了右側的運算 bVal=(1==1)||(1==2); //左側true,接着是||運算,結果必是true,所以跳過了右側的運算
因為短路特性,不要在Exp2中放置帶有副作用的表達式(比如改變一個值),因為可能不會計算。
bool bVal;int iVal=10; bVal=(1==2)&&(9==iVal++);//結果:bVal=False,iVal=10; ↑ ↑ False 不會計算
邏輯運算符
移位運算符
例:移位運算符示例
- 操作數14的每個位向左移動3個位置
- 右邊結尾騰出位置用0補充
- 結果為112
int a,b,x=14; a=x<<3; b=x>>3; Console.WriteLine("{0}<<3={1}",x,a); Console.WriteLine("{0}>>3={1}",x,b);
賦值運算符
賦值運算符是二元右結合運算符
復合賦值
復合賦值運算符允許一種速記方法,在某些情況下避免左邊的變量在右邊重復出現。
復合賦值不僅更短,也易於理解。
x=x+(y-z);
x+=y-z;
條件運算符
條件運算符是一種強大且簡潔的方法,基於條件的結果,返回兩個值之一。
條件運算符是三元運算符
- 格式:Condition?Expression1:Expression2
- Condition必須返回一個bool類型的值
- 如果Condition求值為true,那么對Expression1求值並返回。否則,對Expression2求值並返回
if...else if(x<y) intVar=5; else intVar=10; 條件運算符 intVar=x<y?5:10;
if…else語句是控制流語句,它應當用來做兩個行為中的一個。條件運算符返回一個表達式,它應當用於返回兩個值中的一個。
用戶定義的類型轉換
用戶定義的轉換將在第16章詳講,在這里稍微提一下。
- 可以為自己的類和結構定義隱式和顯式轉換。這允許把用戶定義類型的對象轉換成某個其他類型
- C#提供隱式轉換和顯示轉換
- 隱式轉換,當決定在特定上下文中使用特定類型時,如有必要,編譯器會自動執行轉換
- 顯式轉換,編譯器只在使用顯式轉換運算符時才執行轉換
聲明隱式轉換的語法如下。
必需的 目標類型 源數據 ↓ ↓ ↓ public static implicit operator TargetType(SourceType Identifier) { ... return ObjectOfTargetType; }
顯式轉換的語法與之相同,但要用explicit替換implicit
例:將LimitedInt轉換為int
class LimitedInt { const int MaxValue=100; const int MinValue=0; public static implicit operator int(LimitedInt li) { return li.TheValue; } public static implicit operator LimitedInt(int x) { var li=new LimitedInt(); li.TheValue=x; return li; } private int _theValue=0; public int TheValue { get{return _theValue;} set { if(value<MinValue) _theValue=0; else _theValue=value>MaxValue?MaxValue:value; } } class Program { static void Main() { LimitedInt li=500; int value=li; Console.WriteLine("li:{0},value:{1}",li.TheValue,value); } } }
顯式轉換和強制轉換運算符
如果把兩個運算符聲明為explicit,你將不得不在實行轉換時顯示使用轉換運算符。
public static explicit operator int(LimitedInt li) { return li.TheValue; } public static explicit operator LimitedInt(int x) { var li=new LimitedInt(); li.TheValue=x; return li; } static void Main() { LimitedInt li=(LimitedInt)500; int value=(int)li; Console.WriteLine("li:{0},value:{1}",li.TheValue,value); }
輸出結果與上例相同
另外有兩個運算符,接受一種類型的值,並返回另一種不同的、指定類型的值。這就是is運算符和as運算符。它們將在第16章結尾闡述。
運算符重載
運算符重載允許你定義C#運算符應該如何操作自定義類型的操作數
- 運算符重載只能用於類和結構
- 聲明必須同時使用static和public修飾符
- 運算符必須是要操作的類或結構的成員
例:類LimitedInt的兩個重載運算符,加運算符和減運算符
class LimitedInt { 必需的 類型 關鍵字 運算符 操作數 ↓ ↓ ↓ ↓ ↓ public static LimitedInt operator + (LimitedInt x,double y) { var li=new LimitedInt(); li.TheValue=x.TheValue+(int)y; return li; } public static LimitedInt operator - (LimitedInt x) { var li=new LimitedInt(); li.TheValue=0; return li; } ... }
運算符重載的限制
不是所有的運算符都能被重載,可以重載的類型也有限制。
遞增和遞減運算符可重載。但和預定義的版本不同,重載運算符的前置和后置之間沒有區別。
運算符重載不能做下面的事情:
- 創建新運算符
- 改變運算符的語法
- 重新定義運算符如何處理預定義類型
- 改變運算符的優先級或結合性
重載運算符應該符合運算符的直觀含義。
運算符重載的示例
例:LimitedInt的3個運算符重載
class LimitedInt { const int MaxValue=100; const int MinValue=0; public static LimitedInt operator - (LimitedInt x) { var li=new LimitedInt(); li.TheValue=0; return li; } public static LimitedInt operator - (LimitedInt x,LimitedInt y) { var li=new LimitedInt(); li.TheValue=x.TheValue-y.TheValue>0?(x.TheValue-y.TheValue):0; return li; } public static LimitedInt operator + (LimitedInt x,double y) { var li=new LimitedInt(); li.TheValue=x.TheValue+(int)y; return li; } private int _theValue=0; public int TheValue { get{return _theValue;} set { if(value<MinValue) _theValue=0; else _theValue=value>MaxValue?MaxValue:value; } } class Program { static void Main() { var li1=new LimitedInt(); var li2=new LimitedInt(); var li3=new LimitedInt(); li1.TheValue=10;li2.TheValue=26; Console.WriteLine("li:{0},li2:{1}",li1.TheValue,li2.TheValue); li3=-li1; Console.WriteLine("-{0}={1}",li1.TheValue,li3.TheValue); li3=li2-li1; Console.WriteLine("{0}-{1}={2}",li2.TheValue,li1.TheValue,li3.TheValue); li3=li1-li2; Consoel.WriteLine("{0}-{1}={2}",li1.TheValue,li2.TheValue,li3.TheValue); } } }
typeof運算符
typeof運算符返回作為其參數的任何類型的System.Type對象。
例:使用typeof運算符獲取SomeClass類的信息
using System.Reflection;//反射 class SomeClass { public int Field1; public int Field2; public void Method1(){} public int Method2(){return 1;} } class Program { static void Main() { var t=typeof(SomeClass); FieldInfo[] fi=t.GetFields(); MethodInfo[] mi=t.GetMethods(); foreach(var f in fi) Console.WriteLine("Field:{0}",f.Name); foreach(var m in mi) Console.WriteLine("Method:{0}",m.Name); } }
GetType方法也會調用typeof運算符,該方法對每個類型的每個對象都有效。
例:使用GetType獲取對象類型名稱
class SomeClass { } class Program { static void Main() { var s=new SomeClass(); Console.WriteLine("Type s:{0}",s.GetType().Name); } }
其他運算符
本章介紹的運算符是內置類型的標准運算符。本書后面部分會介紹其他特殊用法的運算符及操作數類型。例如,可空類型有一個特殊運算符叫空接合運算符(第25章)。