第9章 表达式和运算符 笔记

9.1 表达式

操作符是输入的数据元素,可以作为操作数的结构有:字面量、常量、变量、方法调用、元素访问器、其他表达式。

运算符是一个符号,将操作数作为输入,执行某个操作,返回该操作的结果。

运算符有一个到3个操作数。

表达式是运算符和操作数的字符串,可以使用运算符组合表达式创建更复杂的表达式。

表达式求值是将每个运算符,以适当的顺序应用到它的操作数,产生一个值的过程。

9.2 字面量

字面量是代码中的数字或字符串,表示一个指定类型的、明确的、固定的值。

字面量的值必须在编译时可知。

bool型有两个字面量:true、false

引用类型字面量 null

9.2.1 整数字面量

整数字面量是最常用的字面量,常被书写为十进制数字形式,没有小数点,带有可选的后最,指明整数的类型。

十六进制整数:以 0x 或 0X 开始,数字从0到F。

二进制整数:以 0b 或 0B 开始,数字必须是0或1。

十六进制和二进制记法用前缀指定,实际的数据类型用后缀指定。

整数字面量的后缀

后缀 整数类型 备注
int、uint、long、ulong
U、u uint、ulong
L、l long、ulong 不推荐使用小写字母l,因为它很容易和数字1弄混
ul、uL、Ul、UL、lu、Lu、lU、LU ulong 不推荐使用小写字母l,因为它很容易和数字1弄混

可以在数字字面量中插入分隔符以看清数字大小:

Console.WriteLine("5_000_000_000 is much easier to read than 5000000000");

9.2.2 实数字面量

三种实数数据类型:float、double、decimal,分别对应32位、64位、128位精度。

其内部由两部分组成,实际的数字、表示小数点位置的指数。

double是最常用的实数数据类型。

实数字面量的组成:

十进制数字

一个可选的小数点

一个可选的指数部分

一个可选的后缀

float f1 = 236F;
double d1 = 236.714;
double d2 = .35192;
double d3 = 6.338e-26;

实数字面量的后缀,无后缀的实数字面量是double类型

后缀 实数类型
double
F、f float
D、d double
M、m decimal

9.2.3 字符字面量

字符字面量由两个单引号内 的字符组成,用于表示单个字符、非打印字符、执行特殊任务的字符。

字符字面量包括:

  • 单个字符:char。
  • 简单转义序列:反斜杠 + 单个字符。
  • 十六进制转义序列:反斜杠 + x/X + 4 位十六进制数字。
  • Unicode 转义序列:反斜杠 + u/U + 4 位十六进制数字。
char c1 = 'd';      // 单个字符
char c2 = '\n';     // 简单转义序列
char c3 = '\x0061'; // 十六进制转义序列
char c4 = '\u005a'; // Unicode 转义序列

特殊字符

名称 转义序列 十六进制编码
空字符 \0 0x0000
警告 \a 0x0007
退格符 \b 0x0008
水平制表符 \t 0x0009
换行符 \n 0x000A
垂直制表符 \v 0x000B
换页符 \f 0x000C
回车符 \r 0x000D
双引号 " 0x0022
单引号 0x0027
反斜杠 \ 0x005C

9.2.4 字符串字面量

字符串字面量使用双引号标记,

字符串字面量类型包括两种:

  1. 常规字符串字面量。

    可以包含上述 4 种字符字面量。

  2. 逐字字符串字面量。

    • 以 @ 字符为前缀。
    • 字符串中的转义序列不会被求值。
    • 相邻的双引号组,被解释为单个双引号字符
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"; // Interprets tab esc sequence
string vst3 = @"Value 1 \t 5, Val2 \t 10"; // Does not interpret tab
string rst4 = "C:\\Program Files\\Microsoft\\";
string vst4 = @"C:\Program Files\Microsoft\";
string rst5 = " Print \x000A Multiple \u000A Lines";
string vst5 = @" Print
 Multiple
 Lines";


// output
Hi there!
Hi there!
It started, "Four score and seven..."
It started, "Four score and seven..."
Value 1 5, Val2 10
Value 1 \t 5, Val2 \t 10
C:\Program Files\Microsoft\
C:\Program Files\Microsoft\
Print
Multiple
Lines
Print
Multiple
Lines

编译器让相同的字符串字面量共享堆中的同一内存位置以节约内存。

9.3 求值顺序

表达式可以由多个嵌套的子表达式组成,子表达式的求值顺序可以使表达式的最终值发生变化。

9.3.1 优先级

C#超过45个运算符和14个优先级

运算符优先级:从高到低

分类 运算符
初级运算符 a.x、f(x)、a[x]、x++、x–、new、typeof、checked、unchecked
一元运算符 +、-、!、~、++X、–X、(T)X
乘法 *、/、%
加法 +、-
移位 <<、>>
关系和类型 <、>、<=、>=、is、as
相等 ==、!=
位与 &
位异或 ^
位或 |
条件与 &&
条件或 ||
条件选择 ?:
赋值运算符 =、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=
9.3.2 结合性

运算符的优先级不同,编译器根据优先级从高到低计算。

左结合运算符:从左至右求值

右结合运算符:从右至左求值

赋值运算符、条件运算符是右结合的,其他二元运算符是左结合的。

运算符结合性总结

运算符类型 结合性
赋值运算符 右结合
其他二元运算符 左结合
条件运算符 右结合

9.4 简单算术运算符

基本四则运算符:加减乘除,左结合运算符

9.5 求余运算符

求余运算符返回余数

9.6 关系比较运算符和相等比较运算符

比较操作数,返回bool值

比较操作和相等性操作

  • **浅比较:**对于大多数引用类型来说,比较相等性时,只比较它们的引用。

    • 如果指向相同的引用对象,则为 true;否则为 false。
    • 因此,若两个不同的引用指向的对象值内容相同,也返回 false。
  • **深比较:**string 类型对象不使用浅比较,而是比较字符值是否相等。

    • 如果两个字符串有相同的长度和内容(区分大小写),则为 true;否则为 false;
    • 即使两个字符串占用不同的内存区域,如果值相同,也会返回 true。

委托也是引用类型,也使用深比较。

9.7 递增运算符和递减运算符

递增运算符对操作数加1,递减运算符对操作数减1。

两种形式:

前置:放在操作数之前,处理操作数,返回新值

后置:放在操作数之前,处理操作数,返回旧值

9.8 条件逻辑运算符

逻辑运算符用于比较或否定它们操作数的逻辑值,返回结果。

逻辑与 && 、逻辑或 || 是二元左结合运算符

逻辑非 ! 是一元运算符

运算符 名称 描述
&& 如果两个操作数都是 true,结果为 true;否则为 false
|| 如果至少一个操作数是 true,结果为 true;否则为 false
! 如果操作数是 false,结果为 true;否则为 false

条件逻辑运算符使用短路模式,前面结果确定后,会跳过后面的结果

bool bVal; 
int iVal = 10;
// 前面 1 == 2是false,后面不会执行
bVal = (1 == 2) && (9 == iVal++);
// 结果 bVal = False, iVal = 10

9.9 按位逻辑运算符

按位逻辑运算符常用于设置位组的方法参数。

按位非是一元运算符。

运算符 名称 描述
& 位与 产生两个操作数的按位与。仅当两个操作位都为1时结果位才是1
| 位或 产生两个操作数的按位或。只要任意一个操作位为1时结果位就是1
^ 位异或 产生两个操作数的按位异或。仅当一个而不是两个操作数为1时结果位为1
~ 位非 操作数的每个位都取反。该操作得到操作数的二进制反码(数字的反码是其二进制形式按位取反的结果。也就是说,每个0都变成1,每个1都变成0)

9.10 移位运算符

按位运算符将位组向左或向右移动指定数目个位置,空位用0或1填充。

移位运算符是二元左结合运算符,语法格式为 Operand << Count(左移)或 Operand >> Count(右移),其中 Count 指定移动的位置数。

运算符 名称 描述
<< 左移 将位组向左移动给定数目个位置。位从左边移出并丢失。右边打开的位位置用0填充
>> 右移 将位组向右移动给定数目个位置。位从右边移出并丢失

9.11 赋值运算符

9.12 条件运算符

条件运算符是三元运算符,基于条件的结果,返回两个值之一。

intVar = x < y ? 5 : 10;

9.13 一元算术运算符

一元算法运算符 设置数字值的符合

9.14 用户定义的类型转换

可以位自己的类和结构,定义隐式转换和显示转换。

隐式转换

使用 public 和 static 修饰符,implicit 表明为隐式转换:

public static implicit operator TargetType ( SourceType Identifier )
{
    // ...
    return ObjectOfTargetType;
}
class LimitedInt
{
    const int MaxValue = 100;
    const int MinValue = 0;
    public static implicit operator int(LimitedInt li) // Convert type
    {
        return li.TheValue;
    }
    
    public static implicit operator LimitedInt(int x) // Convert type
    {
        LimitedInt li = new LimitedInt();
        li.TheValue = x;
        return li;
    }
    
    private int mTheValue = 0;
    public int TheValue { // Property
        get { return mTheValue; }
        set
        {
            if (value < MinValue)
                mTheValue = 0;
            else
                mTheValue = value > MaxValue
                ? MaxValue
                : value;
        }
    }
}

class Program {
    static void Main() // Main
    {
        LimitedInt li = 500; // Convert 500 to LimitedInt
        int value = li; // Convert LimitedInt to int
        Console.WriteLine($"li: { li.TheValue }, value: { value }");
    }
}

// output
li: 100, value: 100

显示转换

和隐式转换一样,但使用 explicit 而不是 implicit:

public static explicit operator int(LimitedInt li)
{
    return li.TheValue;
}

class LimitedInt
{
    const int MaxValue = 100;
    const int MinValue = 0;
    public static explicit operator int(LimitedInt li) // Convert type
    {
        return li.TheValue;
    }
    
    public static explicit operator LimitedInt(int x) // Convert type
    {
        LimitedInt li = new LimitedInt();
        li.TheValue = x;
        return li;
    }
    
    private int mTheValue = 0;
    public int TheValue { // Property
        get { return mTheValue; }
        set
        {
            if (value < MinValue)
                mTheValue = 0;
            else
                mTheValue = value > MaxValue
                ? MaxValue
                : value;
        }
    }
}

class Program {
    static void Main() // Main
    {
        LimitedInt li = (LimitedInt)500; // Convert 500 to LimitedInt
        int value = (int)li; // Convert LimitedInt to int
        Console.WriteLine($"li: { li.TheValue }, value: { value }");
    }
}

// output
li: 100, value: 100

9.15 运算符重载

C#操作自定义类型的操作数

  • 运算符重载只能用于类和结构。
  • 使用 operator x 重载运算符 x。
    • 一元运算符重载方法只带一个单独的 class 或 struct 参数。
    • 二元运算符重载方法带两个参数,至少有一个需要为 class 或 struct。
public static LimitedInt operator -(LimitedInt x) // Unary
public static LimitedInt operator +(LimitedInt x, double y) // Binary

运算符重载的说明:

  • 必须同时使用 public 和 static。
  • 运算符方法必须是要操作的类或结构的成员。
class LimitedInt Return
{
    // Required Type Keyword Operator Operand
    public static LimitedInt operator + (LimitedInt x, double y)
    {
        LimitedInt li = new LimitedInt();
        li.TheValue = x.TheValue + (int)y;
        return li;
    }
    
    public static LimitedInt operator - (LimitedInt x)
    {
        // In this strange class, negating a value just sets it to 0.
        LimitedInt li = new LimitedInt();
        li.TheValue = 0;
        return li;
    }
    // ...
}

9.15.1 运算符重载的示例

class LimitedInt
{
    const int MaxValue = 100;
    const int MinValue = 0;
    public static LimitedInt operator -(LimitedInt x)
    {
        // In this strange class, negating a value just sets its value to 0.
        LimitedInt li = new LimitedInt();
        li.TheValue = 0;
        return li;
    }
    
    public static LimitedInt operator -(LimitedInt x, LimitedInt y)
    {
        LimitedInt li = new LimitedInt();
        li.TheValue = x.TheValue - y.TheValue;
        return li;
    }
    
    public static LimitedInt operator +(LimitedInt x, double y)
    {
        LimitedInt 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()
    {
        LimitedInt li1 = new LimitedInt();
        LimitedInt li2 = new LimitedInt();
        LimitedInt li3 = new LimitedInt();
        li1.TheValue = 10; li2.TheValue = 26;
        Console.WriteLine($" li1: { li1.TheValue }, li2: { li2.TheValue }");
        li3 = -li1;
        Console.WriteLine($"-{ li1.TheValue } = { li3.TheValue }");
        li3 = li2 - li1;
        Console.WriteLine($" { li2.TheValue } - { li1.TheValue } = { li3.TheValue }");
        li3 = li1 - li2;
        Console.WriteLine($" { li1.TheValue } - { li2.TheValue } = { li3.TheValue }");
    }
}

// output
li1: 10, li2: 26
-10 = 0
26 - 10 = 16
10 - 26 = 0

9.15.2 运算符重载的限制

可重载的一元运算符:+、-、!、~、++、–、true、false。

可重载的二元运算符:+、-、*、/、%、&、|、^、<<、>>、==、!=、>、<、>=、<=。

运算符重载不能:

  • 创建新运算符。
  • 改变运算符的语法。
  • 重新定义运算符如何处理预定义类型。
  • 改变运算符的优先级或结合性。

对于 ++ 和 –- 运算符,重载只能提供一个方法体。当对对象使用前置或后置运算时,编译器将做出不同操作的处理:

  • 前置运算:
    • 直接在对象上执行重载代码。
    • 返回对象。
  • 后置运算:
    • 首先浅拷贝对象。
    • 在拷贝对象上执行重载代码。
    • 返回拷贝对象。

因此,对引用类型(class)使用后置操作,返回结果将和前置操作一样,因为浅拷贝引用指向的对象就是原对象。但是对值类型(struct)使用后置操作不会出现这种现象。

最好对 struct 使用后置递增 / 递减,而不是 class。

using static System.Console;
public struct MyType // Run twice; once as a struct and again as a class.
// public class MyType
{
    public int X;
    public MyType( int x )
    {
        X = x;
    }
    public static MyType operator ++( MyType m )
    {
        m.X++;
        return m;
    }
}

class Test
{
    static void Show( string message, MyType tv )
    {
        WriteLine( $"{message} {tv.X}" );
    }
    static void Main()
    {
        MyType tv = new MyType( 10 );
        WriteLine( "Pre-increment" );
        Show( "Before ", tv );
        Show( "Returned ", ++tv );
        Show( "After ", tv );
        WriteLine();
        tv = new MyType( 10 );
        WriteLine( "Post-increment" );
        Show( "Before ", tv );
        Show( "Returned ", tv++ );
        Show( "After ", tv );
    }
}

// 结构体 output
Pre-increment
Before 10
Returned 11
After 11
Post-increment
Before 10
Returned 10
After 11

// 类 output
Pre-increment
Before 10
Returned 11
After 11
Post-increment
Before 10
Returned 11 Not what you would expect.
After 11
    

9.16 typeof 运算符

typeof 运算符返回参数对应的 System.Type 对象。

Type t = typeof ( SomeClass )
using System.Reflection; // Use the Reflection namespace to take full advantage
// of determining information about a type.
class SomeClass
{
    public int Field1;
    public int Field2;
    public void Method1() { }
    public int Method2() { return 1; }
}

class Program
{
    static void Main()
    {
        Type t = typeof(SomeClass);
        FieldInfo[] fi = t.GetFields();
        MethodInfo[] mi = t.GetMethods();
        foreach (FieldInfo f in fi)
            Console.WriteLine($"Field : { f.Name }");
        foreach (MethodInfo m in mi)
            Console.WriteLine($"Method: { m.Name }");
    }
}

// output
Field : Field1
Field : Field2
Method: Method1
Method: Method2
Method: ToString
Method: Equals
Method: GetHashCode
Method: GetType

GetType方法也会调用typeof运算符

class SomeClass
{
}

class Program
{
    static void Main()
    {
        SomeClass s = new SomeClass();
        Console.WriteLine($"Type s: { s.GetType().Name }");
    }
}

// output 
Type s: SomeClass

9.17 nameof 运算符

即使参数使用完全限定名,nameof 运算符也只返回其参数的非限定名称。

string var1 = "Local Variable";
Console.WriteLine (nameof (var1)); // Local Variable ("var1")
Console.WriteLine (nameof (MyClass)); // Class ("MyClass")
Console.WriteLine (nameof (MyClass.Method1)); // Public Method ("Method1")
Console.WriteLine (nameof (parameter1)); // Method Parameter ("parameter1")
Console.WriteLine (nameof (MyClass.Property1)); // Public Property ("Property1")
Console.WriteLine (nameof (MyClass.Field1)); // Public Field ("Field1")
Console.WriteLine (nameof (MyStruct)); // Struct ("MyStruct ")

9.18 其他运算符

  • 空接合运算符
  • 空条件运算符
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐