语句、表达式和运算符

语句、表达式和运算符

 

语句

语句是构造所有C#程序的过程构造块。语句可以声明局部变量或常数,调用方法,创建对象或将值赋予变量、属性或字段。控制语句可以创建循环,如for循环,也可以进行判断并分支到新的代码块,如ifswitch语句。语句通常以分号终止。

由大括号括起来的一系列语句构成代码块。方法体是代码块的一个示例。代码块通常出现在控制语句之后。在代码块中声明的变量或常数可用于同一代码块中的语句。例如,下面的代码演示在控制语句之后出现的一个方法块和一个代码块:

bool IsPositive(int number)

{

    if (number > 0)

    {

        return true;

    }

    else

    {

        return false;

    }

}

 

C#中的语句通常包含表达式。C#中的表达式是一个包含文本值、简单名称或运算符及其操作数的代码段。大多数常用表达式在计算时都将产生文本值、变量、对象属性或对象索引器访问。只要从表达式中识别变量、对象属性或对象索引器访问,该项的值都将用作表达式的值。在C#中,表达式可以置于需要值或对象的任意位置,条件是表达式最终的计算结果必须为所需的类型。

有些表达式的计算结果为命名空间、类型、方法组或事件访问。这些具有特殊用途的表达式只在某些情况下(通常是作为较大的表达式的以部分时)有效,如果使用不正确,则将产生编译器错误。

 

表达式

表达式是可以计算且结果为单个值、对象、方法或命名空间的代码片段。表达式可以包含文本值、方法调用、运算符及其操作数,或简单名称。简单名称可以是变量、类型成员、方法参数、命名空间或类型的名称。

表达式可以使用运算符,而运算符又可以将其表达式用作参数,或者使用方法调用,而方法调用的参数又可以是其他方法调用,因此表达式既可以非常简单,也可以非常复杂。

 

文本和简单名称

最简单的两种表达式类型是文本和简单名称。文本是没有名称的常数值。例如,在下面的代码示例中,5和“Hello world”都是文本值:

int i = 5;

string s = "Hello World";

在上面的示例中,is都是用于标识局部变量的简单名称。在表达式中使用这些变量时,变量值检索后将用于表达式中。例如,在下面的代码示例中,当调用DoWork时,该方法默认情况下接收值“5”,且不能访问变量var

int var = 5;

DoWork(var);

 

调用表达式

在下面的代码示例中,对DoWork的调用是另一种表达式,称为调用表达式。

DoWork(var);

具体来说,调用方法即是一个方法调用表达式。方法调用要求使用方法的名称(如前面的示例中那样作为名称,或作为其他表达式的结果),后跟括号和任意方法参数。委托调用使用委托的名称和括号内的方法参数。如果方法返回值,则方法调用和委托调用的计算结果为该方法的返回值。返回void的方法不能替代表达式中的值。

只要从表达式中识别到变量、对象属性或对象索引器访问,该项的值都会用作表达式的值。在C#中,只要表达式的最终计算结果是所需的类型,表达式就可以放置在任何需要值或对象的位置。

 

运算符

C#中,运算符是术语或符号,它接受一个或多个称为操作数的表达式作为输入并返回值。接受一个操作数的运算符被称作一元运算符,例如增量运算符(++)new。接受两个操作数的运算符被称作二元运算符,例如算术运算符+-*/。条件运算符?:接受三个操作数,是C#中唯一的三元运算符。

下面的C#语句包含一个一元运算符和一个操作数。增量运算符++修改操作数y的值:

y++;

下面的C#语句包含两个二元运算符,它们分别有两个操作数。赋值运算符=将一个整数y和一个表达式2+3作为操作数。表达式2+3本身包含加运算符,并使用整数值23作为操作数:

y = 2 + 3;

操作数可以是任何大小、由任何数量的其他操作组成的有效表达式。

表达式中的运算符按照称为运算符优先级的特定顺序计算。下表根据运算符执行的操作类型将它们划分到不同的类别中。类别按优先级顺序列出。

基本

x.yf(x)a[x]x++x--new typeofcheckedunchecked

一元

+-!~++x--x(T)x

算术 乘法

*/%

算术 加法

+-

移位

<<>>

关系和类型检测

<><=>=isas

相等

==!=

逻辑(按优先级顺序)

&^|

条件(按优先级顺序)

&&||?:

赋值

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

当表达式中出现两个具有相同优先级的运算符时,它们根据结合性进行计算。左结合运算符按从左到右的顺序计算。例如,x*y/z计算为(x*y)/z。右结合运算符按从右到左的顺序计算。赋值运算符和三元运算符(?:)是右结合运算符。其他所有二元运算符都是左结合运算符。然而,C#标准没有指定何时执行表达式中的增量指令的“设置”部分。例如,下面的代码示例的输出为6

int num1 = 5;

num1++;

System.Console.WriteLine(num1);

而下面的代码示例的输出确实未定义的:

int num2 = 5;

num2 = num2++;  //not recommended

System.Console.WriteLine(num2);

因此,建议不要使用后一个示例。可以在表达式两侧使用括号强制在计算其他任何表达式之前计算该表达式。例如,2+3*2正常情况下计算为8.这是因为乘法运算符的优先级高于加法运算符。将该表达式写为(2+3)*2的形式,结果将是10,因为它指示C#编译器必须在计算乘法运算符*之前计算加法运算符+

对于自定义的类和结构,可以更改运算符的行为,此过程成为运算符重载。

 

可重载运算符

C#允许用户定义的类型通过使用operator关键字定义静态成员函数来重载运算符。但不是所有的运算符都可被重载,下表列出了不能被重载的运算符:

运算符

可重载性

+-!~++--truefalse

可以重载这些一元运算符

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

可以重载这些二进制运算符

==!=<><=>=

比较运算符可以重载

&&||

条件逻辑运算符不能重载,但可以使用能够重载的&|进行计算

[]

不能重载数组索引运算符,但可定义索引器

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

赋值运算符不能重载,但+=可使用+计算

=.?:->newissizeoftypeof

不能重载这些运算符

注意

比较运算符(如果重载)必须成对重载;也就是说,如果重载==,也必须重载!=。反之亦然,<>以及<=>=同样如此。

public static Complex operator +(Complex c1, Complex c2)

 

转换运算符

C#允许程序员在类或结构上声明转换,以便类或结构与其他类或结构或者基本类型进行相互转换。转换的定义方法类似于运算符,并根据它们所转换到的类型命名。要转换的类型参数或转换结果的类型必须是(不能两者同时都是)包含类型。

class SampleClass

{

    public static explicit operator SampleClass(int i)

    {

        SampleClass temp = new SampleClass();

        // code to convert from int to SampleClass...

 

        return temp;

    }

}

转换运算符概述

转换运算符具有以下特点:

l         声明为implicit的转换在需要时自动进行

l         声明为explicit的转换需要要调用强制转换

l         所有转换都必须是static转换

 

使用转换运算符

转换运算符可以是explicit,也可以是implicit。隐式转换运算符更容易使用,但是如果您希望运算符的用户能够意识到正在进行转换,则显示运算符很有用。此主题演示了这两种类型。

示例1

说明:这个显式转换运算符的一个示例。此运算符将类型Byte转换为称为Digit的值类型。由于不是所有字节都可以转换为数字,因此转换是显式的,这意味着必须使用强制转换,如Main方法所示。

struct Digit{
        
byte value;
        
        
//constructor
        public Digit(byte value){        
                
if (value > 9){
                        
throw new System.ArgumentException();
                    }

                    
                
this.value = value;
            }

        
        
//explicit byte to digit conversion operator
        public static explicit operator Digit(byte b){
                Digit d 
= new Digit(b);        //explicit conversion
                
                System.Console.WriteLine(
"conversion occurred");
                
                
return d;
            }

    }


class TestExplicitConversion{
        
static void Main(){
                
try{
                        
byte b = 5;
                        Digit d 
= (Digit)b;        //explicit conversion
                    }

                
catch(System.Exception e){
                        System.Console.WriteLine(
"{0} Exception caught.",e);
                    }

            }

    }

 

如何:在结构之间实现用户定义的转换

本示例定义RomanNumeralBinaryNumeral两个结构,并演示二者之间的转换。

示例

struct RomanNumeral
{
    
private int value;

    
public RomanNumeral(int value)  //constructor
    {
        
this.value = value;
    }


    
static public implicit operator RomanNumeral(int value)
    
{
        
return new RomanNumeral(value);
    }


    
static public implicit operator RomanNumeral(BinaryNumeral binary)
    
{
        
return new RomanNumeral((int)binary);
    }


    
static public explicit operator int(RomanNumeral roman)
    
{
        
return roman.value;
    }


    
static public implicit operator string(RomanNumeral roman)
    
{
        
return ("Conversion not yet implemented");
    }

}


struct BinaryNumeral
{
    
private int value;

    
public BinaryNumeral(int value)  //constructor
    {
        
this.value = value;
    }


    
static public implicit operator BinaryNumeral(int value)
    
{
        
return new BinaryNumeral(value);
    }


    
static public explicit operator int(BinaryNumeral binary)
    
{
        
return (binary.value);
    }


    
static public implicit operator string(BinaryNumeral binary)
    
{
        
return ("Conversion not yet implemented");
    }

}


class TestConversions
{
    
static void Main()
    
{
        RomanNumeral roman;
        BinaryNumeral binary;

        roman 
= 10;

        
// Perform a conversion from a RomanNumeral to a BinaryNumeral:
        binary = (BinaryNumeral)(int)roman;

        
// Perform a conversion from a BinaryNumeral to a RomanNumeral:
        
// No cast is required:
        roman = binary;

        System.Console.WriteLine((
int)binary);
        System.Console.WriteLine(binary);
    }

}


可靠编程

在上面的示例中,语句:

binary = (BinaryNumeral)(int)roman;

执行从RomanNumeralBinaryNumeral的转换。由于没有从RomanNumeralBinaryNumeral的直接转换,所以使用一个转换将RomanNumeral转换为int,并使用另一个转换将int转换为BinaryNumeral

另外,语句:

roman = binary;

执行从BinaryNumeralRomanNumeral的转换。由于RomanNumeral定义了从BinaryNumeral的隐式转换,所以不需要转换。

 

如何:使用运算符重载创建复数类

本示例展示如何使用运算符重载创建定义复数加法的复数类Complex。本程序使用ToString方法的重载显示数字的虚部和实部以及加法结果。

示例

public struct Complex
{
    
public int real;
    
public int imaginary;

    
public Complex(int real, int imaginary)  //constructor
    {
        
this.real = real;
        
this.imaginary = imaginary;
    }


    
// Declare which operator to overload (+),
    
// the types that can be added (two Complex objects),
    
// and the return type (Complex):
    public static Complex operator +(Complex c1, Complex c2)
    
{
        
return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
    }


    
// Override the ToString() method to display a complex number in the traditional format:
    public override string ToString()
    
{
        
return (System.String.Format("{0} + {1}i", real, imaginary));
    }

}


class TestComplex
{
    
static void Main()
    
{
        Complex num1 
= new Complex(23);
        Complex num2 
= new Complex(34);

        
// Add two Complex objects through the overloaded plus operator:
        Complex sum = num1 + num2;

        
// Print the numbers and the sum using the overriden ToString method:
        System.Console.WriteLine("First complex number:  {0}", num1);
        System.Console.WriteLine(
"Second complex number: {0}", num2);
        System.Console.WriteLine(
"The sum of the two numbers: {0}", sum);
    }

}


Equals()和运算符==的重载准则

C#中有两种不同的相等:引用相等和值相等。值相等是大家普遍理解的意义上的相等:它意味着两个对象包含相同的值。例如,两个值为2的整数具有值相等性。引用相等意味着要比较的不是两个对象,而是两个对象引用,这两个对象引用引用的是同一个对象。这可以通过简单的赋值来实现,如下面的示例所示:

System.Object a = new System.Object();

System.Object b = a;

System.Object.ReferenceEquals(a, b);  //returns true

从上面的代码中,只存在一个对象,但存在对该对象的多个引用:ab。由于它们引用的是同一个对象,因此具有引用相等性。如果两个对象具有引用相等性,则它们也具有值相等性,但是值相等性不能保证引用相等性。

若要检查引用相等性,应使用ReferenceEquals。若要检查值相等性,应使用Equals

重写Equals

Equals是一个虚方法,允许任何类重写其实现。表示某个值(本质上可以是任何值类型)或一组值(如复数类)的任何类都应该重写Equals。如果类型要实现IComparable,则它应该重写Equals

Equals的新实现应该遵循Equals的所有保证:

l         x.Equals(x)返回true

l         x.Equals(y)y.Equals(x)返回相同的值

l         如果(x.Equals(y)&&y.Equals(z))返回true,则x.Equals(z)返回true

l         只要不修改xy所引用的对象,x.Equals(y)的后续调用就返回相同的值。

l         x.Equals(null)返回false

Equals的新实现不应该引发异常。建议重写Equals的任何类同时也重写System.Object.GetHashCode。除了实现Equals(对象)外,还建议所有的类为自己的类型实现Equals(类型)以增强性能。例如:

class TwoDPoint : System.Object
{
    
public readonly int x, y;

    
public TwoDPoint(int x, int y)  //constructor
    {
        
this.x = x;
        
this.y = y;
    }


    
public override bool Equals(System.Object obj)
    
{
        
// If parameter is null return false.
        if (obj == null)
        
{
            
return false;
        }


        
// If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        
if ((System.Object)p == null)
        
{
            
return false;
        }


        
// Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }


    
public bool Equals(TwoDPoint p)
    
{
        
// If parameter is null return false:
        if ((object)p == null)
        
{
            
return false;
        }


        
// Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }


    
public override int GetHashCode()
    
{
        
return x ^ y;
    }

}


可调用基类的Equals的任何派生类在完成其比较之前都应该这样做。在下面的示例中,Equals调用基类Equals,后者将检查空参数并将参数的类型与派生类的类型做比较。这样就把检查派生类中声明的新数据字段的任务留给了派生类中的Equals实现:

class ThreeDPoint : TwoDPoint
{
    
public readonly int z;

    
public ThreeDPoint(int x, int y, int z)
        : 
base(x, y)
    
{
        
this.z = z;
    }


    
public override bool Equals(System.Object obj)
    
{
        
// If parameter cannot be cast to ThreeDPoint return false:
        ThreeDPoint p = obj as ThreeDPoint;
        
if ((object)p == null)
        
{
            
return false;
        }


        
// Return true if the fields match:
        return base.Equals(obj) && z == p.z;
    }


    
public bool Equals(ThreeDPoint p)
    
{
        
// Return true if the fields match:
        return base.Equals((TwoDPoint)p) && z == p.z;
    }


    
public override int GetHashCode()
    
{
        
return base.GetHashCode() ^ z;
    }

}


Overriding Operator ==

默认情况下,运算符==通过判断两个引用是否指示同一对象来测试引用是否相等,因此引用类型不需要实现运算符==就能获得此功能。当类型不可变时,意味着实例中包含的数据不可更改,此时通过重载运算符==来比较值是否相等而不是比较引用是否相等可能会很有用,因为作为不可变的对象,只要它们具有相同的值,就可以将它们看作是相同的。建议不要在非不可变类型中重写运算符==

重载的运算符==实现不应引发异常。重载运算符==的任何类型还应重载运算符!=。例如:

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    
// If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    
{
        
return true;
    }


    
// If one is null, but not both, return false.
    if (((object)a == null|| ((object)b == null))
    
{
        
return false;
    }


    
// Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}


public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    
return !(a == b);
}


注意

运算符==的重载只能够的常见错误是使用(a==b)(a==null)(b==null)来检查引用相等性。这会导致调用重载的运算符==,从而导致无限循环。应使用ReferenceEquals或将类型强制转换为Object来避免无限循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值