第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、uuint、ulong
L、llong、ulong不推荐使用小写字母l,因为它很容易和数字1弄混
ul、uL、Ul、UL、lu、Lu、lU、LUulong不推荐使用小写字母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、ffloat
D、ddouble
M、mdecimal

9.2.3 字符字面量

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

字符字面量包括:

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

特殊字符

名称转义序列十六进制编码
空字符\00x0000
警告\a0x0007
退格符\b0x0008
水平制表符\t0x0009
换行符\n0x000A
垂直制表符\v0x000B
换页符\f0x000C
回车符\r0x000D
双引号"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

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

更多推荐