表达式和运算符

这篇笔记我们简单介绍下C#语言中表达式和运算符的用法。

表达式和字面量

表达式是包含运算符和操作数的语法结构,C#中的运算符可以有1个、2个或3个操作数,可以作为操作数的结构包括:字面量、常量、变量、其它表达式等,表达式求值就是将运算符应用到操作数并最终得出一个值的过程。字面量是源代码中的数字或字符串,表示一个指定类型的固定值。

整数字面量可以使用后缀标识类型,比如L代表长整型,U代表无符号整型等;此外还可以使用0x前缀表示16进制数,0b前缀表示2进制数。

int a = 236; // 整型
long b = 236L; // 长整型
uint c = 235U; // 无符号整型
ulong d = 236UL; // 无符号长整型
int e = 0xff; // 16进制整型
int f = 0b101011; // 2进制整型

字符字面量使用单引号包裹,\x前缀表示16进制转义序列,\u表示Unicode转义序列;字符串字面量使用双引号包裹,也支持使用16进制转义序列或是Unicode转义序列。

char a = 'd';
char b = '\x0061';
char c = '\u005a';
string str = "abc";

运算符

C#语言中,运算符按功能可以分为算术、关系、逻辑、位、赋值、条件和类型等几类,绝大多数运算符的用法都和类C语言相同,这里我们分类整理一下。运算符之间存在优先级和结合性规则,实际开发中建议使用小括号明确运算顺序,完整的优先级表可以参考微软官方文档。

算术运算符

算术运算符用于执行基本的数学运算。

运算符 说明
+ 加,也可用于字符串拼接
- 减,也可作为一元负号运算符
* 乘
/ 除,整数相除会舍去小数部分
% 取余
++ 递增运算符,使操作数自增1
-- 递减运算符,使操作数自减1

关系运算符

关系运算符用于比较两个操作数的大小关系,运算结果为bool类型。

运算符 说明
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

注:==默认比较值类型的数据和引用类型的引用地址,但string类型比较的是字符串内容,这是因为string类型内部做了运算符重载。

逻辑运算符

逻辑运算符用于bool类型操作数之间的逻辑运算,运算结果为bool类型。

运算符 说明
&& 逻辑与,短路求值
\|\| 逻辑或,短路求值
! 逻辑非

注:&&和||具有短路特性,即左侧表达式已能确定结果时不再计算右侧表达式;此外&和|运算符作用于bool操作数时也可表示逻辑与、逻辑或,但不具备短路特性。

位运算符

位运算符用于对整数类型的二进制位逐位进行运算,它和算术运算没有关系,因此单独归为一类。

运算符 说明
& 按位与
\| 按位或
^ 按位异或
~ 按位取反
<< 左移
>> 有符号右移
>>> 无符号右移(C# 11.0新增)

赋值运算符

赋值运算符除了最基本的=还有一系列带运算的复合赋值运算符。

运算符 说明
= 赋值
+= 加后赋值
-= 减后赋值
*= 乘后赋值
/= 除后赋值
%= 取余后赋值
&= 按位与后赋值
\|= 按位或后赋值
^= 按位异或后赋值
<<= 左移后赋值
>>= 有符号右移后赋值
??= 空合并赋值,仅当左操作数为null时才赋值(C# 8.0新增)

条件运算符和空相关运算符

运算符 说明
?: 三元条件运算符,基于条件返回两个值中的一个
?? 空合并运算符,左操作数为null时返回右操作数
?. 空条件运算符,左操作数为null时不再访问成员
?[] 空条件索引运算符,左操作数为null时不再索引

条件运算符是唯一的三元运算符,它基于条件返回两个值中的一个。

int a = 1;
string result = a > 0 ? "正数" : "非正数";

空合并运算符??常用于可空类型,当左操作数为null时返回右操作数,否则返回左操作数本身;空条件运算符?.和?[]用于在访问成员或索引前判断对象是否为null,是null时整个表达式返回null而不会抛出异常。

int? i = null;
int j = i ?? 0;
// obj或obj.Name为null时,整个表达式返回null而不会抛出异常
int? length = obj?.Name?.Length;

类型运算符

运算符 说明
is 判断对象是否为指定类型
as 安全类型转换,转换失败时返回null而不抛出异常
typeof 获取指定类型的System.Type对象
sizeof 获取非托管类型所占的字节数
nameof 获取变量、类型或成员的标识符名称字符串
default 获取指定类型的默认值

typeof运算符返回一个System.Type对象,Type是一个表示类型的类对象,类似Java中的Class,其中包含了类型、全限定名、父类类型、成员属性等重要信息。下面例子代码从一个类中获取了Type对象,并打印了其全限定类名。

Type t = typeof(Program);
Console.WriteLine(t.FullName);
int i = 1;
bool b = i is int; // true
object o = "hello";
string s = o as string; // "hello"
Console.WriteLine(nameof(i)); // 输出 i

其它运算符

除了上述几类按功能划分的运算符,C#中还有一些常用的操作符,这里一并列出。

运算符 说明
. 成员访问
[] 索引访问
() 方法调用或强制类型转换
new 创建对象或调用构造函数
=> Lambda表达式
-> 指针成员访问,仅用于unsafe代码

运算符重载

类似C++语言,C#允许定义运算符重载来操作自定义操作数,类和结构体都支持定义运算符重载,也支持类或结构体与一个其它类型的运算符重载。

下面例子代码中我们定义了一个篮子Basket类,其中包含苹果数量appleNum和香蕉数量bananaNum,我们重载了运算符+,使其支持Basket对象直接相加。

namespace Gacfox.Demo.DemoNetCore;

public class Basket
{
    public int AppleNum { get; set; } = 0;
    public int BananaNum { get; set; } = 0;

    public Basket() { }

    public Basket(int appleNum, int bananaNum)
    {
        this.AppleNum = appleNum;
        this.BananaNum = bananaNum;
    }

    public static Basket operator +(Basket b1, Basket b2)
    {
        return new Basket(b1.AppleNum + b2.AppleNum, b1.BananaNum + b2.BananaNum);
    }
}

class Program
{
    static void Main()
    {
        Basket b1 = new(0, 3);
        Basket b2 = new(3, 0);
        Basket b3 = b1 + b2;
        Console.WriteLine("{0} {1}", b3.AppleNum, b3.BananaNum);
    }
}

不过这里要注意,不是所有的运算符都支持重载;乱用运算符重载也可能严重降低代码的可读性。运算符重载这部分内容使用上的限制可以查阅MSDN相关的文档。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。