C#圖解教程 第八章 表達式和運算符


表達式和運算符

表達式


本章將定義表達式,並描述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章)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM