• 操作符(Operator)也译为 “运算符”
  • 操作符是用来操作数据的,被操作符操作的数据称为操作数(Operand)

# 本质

  • 操作符的本质是函数(即算法)的 “简记法”

  • 操作符不能脱离与它关联的数据类型

    • 可以说操作符就是与固定数据类型相关联的一套基本算法的简记法

    • 为自定义数据类型创建操作符

    • 示例:不能脱离与它关联的数据类型
      int x = 5;
      int y = 4;
      int z = x / y;
      Console.WriteLine(z); // 1
      double a = 5.0;
      double b = 4.0;
      double c = a / b;
      Console.WriteLine(c); // 1.25

# 优先级

img

  • 操作符的优先级
    • 可以使用圆括号提高被括起来表达式的优先级
    • 圆括号可以嵌套
    • 不像数学里有方括号与花括号,在 C# 语言里面 “[]” 与 “{}” 有专门的用途
  • 同优先级操作符的运算顺序
    • 除了带有赋值功能的操作符,同优先级操作符都由左向右进行运算
    • 带有赋值功能的操作符的运算顺序是由右向左
    • 与数学运算不同,计算机语言的同优先级运算没有 “结合率”

# 基本操作符

# . 成员访问

System.IO.File.Create("D:\\HelloWorld.txt");
      1  2    3
var myForm = new Form();
myForm.Text = "Hello, World";
myForm.ShowDialog();
      4
1、访问外层名称空间中的子名称空间
2、访问名称空间中的类型
3、访问类型的静态成员
4、访问对象中的实例成员

# f(x) 方法调用

namespace OperatorsExample
{
  class Program
  {
    static void Main(string[] args)
    {
      var c = new Calculator();
      double x = c.Add(3.0, 4.6);
      Console.WriteLine(x);
      Action myAction = new Action(c.PrintHello); // 委托:只需要知道方法的名称,不调用方法
      myAction();
    }
  }
  class Calculator
  {
    public double Add(double a,double b)
    {
      return a + b;
    }
    public void PrintHello()
    {
      Console.WriteLine("Hello");
    }
  }
}

# a[x] 元素访问

// 访问数组元素
int[] myIntArray = new int[] { 1, 2, 3, 4, 5 }; // {} 初始化器
Console.WriteLine(myIntArray[0]);
Console.WriteLine(myIntArray[myIntArray.Length - 1]);
// 访问字典中的元素
class Program
{
  static void Main(string[] args)
  {
    Dictionary<string, Student> stuDic = new Dictionary<string, Student>(); // 泛型
    for (int i = 0; i < 100; i++)
    {
      var stu = new Student()
      {
        Name = "s_" + i.ToString(),
        Score = 100 - i
      };
      stuDic.Add(stu.Name, stu); //stu.Name 当作索引,stu 对象当作值
    }
    Console.WriteLine(stuDic["s_6"].Score); // 索引不一样是整数
  }
}
class Student
{
  public string Name;
  public int Score;
}

# x++ x-- 后置自增、自减

int x = 100;
int y = x++;
Console.WriteLine(x); // 101
Console.WriteLine(y); // 100

# typeof 检测类型

// Metadata:检测类型元数据
Type t = typeof(int);
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.FullName); // System.Int32
Console.WriteLine(t.Name); // Int32
int c = t.GetMethods().Length;
Console.WriteLine(c);
foreach (var m in t.GetMethods())
{
  Console.WriteLine(m.Name);
}

img

# default

namespace OperatorsExample
{
  class Program
  {
    static void Main(string[] args)
    {
      // 1、结构体类型 - 值类型内存块都刷成 0,值是 0
      int x = default(int);
      Console.WriteLine(x); // 0
      // 2、引用类型 - 内存块刷成 0 没有引用,值是 null
      Form myForm = default(Form);
      Console.WriteLine(myForm == null); // True
      // 3、枚举类型 - 映射到整型上,默认枚举值是对应值为 0 的那个
      // 要注意枚举中是否有对应 0 的;创建枚举类型时,最好有一个对应 0 的,以免他人查找我们枚举的 default 值时报错
      Level level = default(Level);
      Console.WriteLine(level); // High
    }
  }
  enum Level
  {
    // 不给定值,系统默认赋值,依次从 0 开始
    // 手动指定的
    Low = 1,
    Mid = 2,
    High = 0
  }
}

# new

// 1、在内存中创建类型实例,并调用实例构造器,把实例地址通过赋值操作符交给访问它的变量
var myForm = new Form(); //var:隐式类型变量
// 2、可以调用实例的初始化器
var myForm2 = new Form()
{
  Text = "Hello",
  FormBorderStyle = FormBorderStyle.SizableToolWindow
};
// 一次性变量:利用实例的初始化器直接 new 对象后,马上执行方法,因为没有引用,垃圾收集器会回收
new Form() { Text = "Hello" }.ShowDialog();
// 3、为匿名类型创建对象,用隐式类型变量(var)来引用这个实例
var person = new { Name = "Mr.Okay", Age = 34};
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
Console.WriteLine(person.GetType().Name);

img

通过 C# 的语法糖,我们在声明常用类型的变量时不需要 new 操作符

string name = "Tim";
string name2 = new string(new char[]{ 'T', 'i', 'm' });
int[] myArray1 = { 1, 2, 3, 4 };
int[] myArray2 = new int[4];

new 操作符会导致依赖紧耦合,可以通过依赖注入模式来将紧耦合变成相对松的耦合

//new 操作符功能强大,但会造成依赖。new 出 myForm 后,Program class 就依赖到 myForm 上了,
// 一旦 myForm 运行出错,Program 类也会出问题
// 通过设计模式中的依赖注入模式来将紧耦合变成相对松的耦合
var myForm = new Form() { Text = "Hello" };

new 作为关键字

class Program
{
  static void Main(string[] args)
  {
    var stu = new Student(); // 此处 new 是操作符
    stu.Report(); // I'm a student.
    var csStu = new CsStudent();
    csStu.Report();
  }
}
class Student
{
  public void Report()
  {
    Console.WriteLine("I'm a student.");
  }
}
class CsStudent:Student // 继承 / 派生
{
  new public void Report() // 此处 new 是修饰符,子类把父类的方法隐藏掉
  {
    Console.WriteLine("I'm CS student.");
  }
}

# checked & unchecked

检查一个值在内存中是否有溢出

uint x = uint.MaxValue;
Console.WriteLine(x); // 4294967295
var binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr); // 1111 1111 1111 1111 1111 1111 1111 1111
uint y = x + 1;
Console.WriteLine(y); // 0,值溢出
// checked
try
{
  uint y = checked(x + 1);
  Console.WriteLine(y); // 此处程序抛出异常
}
catch (OverflowException ex)
{
  Console.WriteLine("There's overflow!");
}
//unchecked:不检验溢出
try
{
  // C# 默认采用的就是 unchecked 模式
  uint y = unchecked(x + 1);
  Console.WriteLine(y);
}
catch (OverflowException ex)
{
  Console.WriteLine("There's overflow!");
}

checked 与 unchecked 的另一种用法:上下文用法

uint x = uint.MaxValue;
Console.WriteLine(x);
var binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
// unchecked
checked
{
  try
  {
    uint y = x + 1;
    Console.WriteLine(y);
  }
  catch (OverflowException ex)
  {
    Console.WriteLine("There's overflow!");
  }
}

# delegate

常见的是把 delegate 当做委托关键字使用

delegate 也可以作为操作符使用,但由于 Lambda 表达式的流行,delegate 作为操作符的场景愈发少见(被 Lambda 替代,已经过时)

public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
    // 方法封装提高了复用性,但如果我这个方法在别的地方不太可能用到,我就可以使用匿名方法
    // 使用 delegate 来声明匿名方法
    this.myButton.Click += delegate (object sender, RoutedEventArgs e)
    {
      this.myTextBox.Text = "Hello, World!";
    };
    // 现在推荐使用的是 Lambda 表达式
    this.myButton.Click += (sender, e) =>
    {
      this.myTextBox.Text = "Hello, World!";
    };
  }
  // 非匿名方法
  private void MyButton_Click(object sender, RoutedEventArgs e)
  {
    this.myTextBox.Text = "Hello, World!";
  }
}

# sizeof 获取所占字节数

用于获取对象在内存中所占字节数默认情况下

⚠️ 注:

  • sizeof 只能获取结构体类型的实例在内存中的字节数
    • int、uint、double 可以
    • string、object 不行
  • 非默认情况下,可以使用 sizeof 获取自定义结构体类型的大小,但需要把它放在不安全的上下文中

需要在 “项目属性” 里面开启 “允许不安全代码”

img

class Program
{
  static void Main(string[] args)
  {
    var x = sizeof(int);
    Console.WriteLine(x); // 4
    unsafe
    {
      int y = sizeof(Student);
      Console.WriteLine(y); // 16
    }
  }
}
struct Student
{
  int ID;
  long Score;
}

⚠️ 注:int 字节数为 4,long 字节数为 8。但 sizeof (Student) 结果是 16。这涉及到了 .NET 对内存的管理,超出了现在所学内容

# ->

也必须放在不安全的上下文中才能使用

C# 中指针操作、取地址操作、用指针访问成员的操作,只能用来操作结构体类型,不能用来操作引用类型

class Program
{
  static void Main(string[] args)
  {
    unsafe
    {
      Student stu;
      stu.ID = 1;
      stu.Score = 99; // 直接访问
      Student* pStu = &stu;
      pStu->Score = 100;  // 间接访问
      Console.WriteLine(stu.Score);
    }
  }
}
struct Student
{
  public int ID;
  public long Score;
}

# 一元操作符

# &x *x

也需要在不安全的上下文中

class Program
{
  static void Main(string[] args)
  {
    unsafe
    {
      Student stu;
      stu.ID = 1;
      stu.Score = 99;
      Student* pStu = &stu; // & 取地址
      pStu->Score = 100;
      (*pStu).Score = 1000; // * 取引用
      Console.WriteLine(stu.Score);
    }
  }
}
struct Student
{
  public int ID;
  public long Score;
}

# + -

int x = 100;
int y = +x;
Console.WriteLine(y); // +100
y = -x;
Console.WriteLine(y); // -100
// 使用不当,可能导致溢出
int x = int.MinValue;
int y = checked(-x);
Console.WriteLine("x = " + x);
Console.WriteLine("y = " + y);

# ~ 求反

按位取反再加一

int x = 12345678;
int y = ~x;
Console.WriteLine(y); // -12345679
int z = y + 1;
Console.WriteLine(z); // -12345678
string xStr = Convert.ToString(x, 2).PadLeft(32, '0');
string yStr = Convert.ToString(y, 2).PadLeft(32, '0');
Console.WriteLine(xStr); // 00000000101111000110000101001110
Console.WriteLine(yStr); // 11111111010000111001111010110001

# !

真变假,假变真

实际应用
class Program
{
  static void Main(string[] args)
  {
    var stu = new Student(null);
    Console.WriteLine(stu.Name);
  }
}
class Student
{
  public Student(string initName)
  {
    if (!string.IsNullOrEmpty(initName))
    {
      this.Name = initName;
    }
    else
    {
      throw new ArgumentException("initName cannot be null or empty.");
    }
  }
  public string Name;
}

# ++x --x 前置自增自减

无论前置、后置,在实际工作中,尽量单独使用它们,不要把它们和别的语句混在一起,会降低可读性

int x = 100;
// 单独使用时,前置与后置没有区别
//++x;
// 先赋值再自增
int y = x++;
Console.WriteLine(x); // 101
Console.WriteLine(y); // 100
// 先自增再赋值
int y = ++x;
Console.WriteLine(x); // 101
Console.WriteLine(y); // 101

# (T)x 强制类型转换

详见类型转换

# 加减乘除取余

# 乘除

var x = 3.0 * 4; // 12,自动类型提升为 double
double x = (double)5 / 4; // 1.25,类型提升,进行浮点除法
double x = double.PositiveInfinity; // 正无穷大
double y = double.NegativeInfinity; // 负无穷大
double z = x / y;
Console.WriteLine(z); // NaN

# % 取余

double x = 3.5;
double y = 3;
Console.WriteLine(x % y); // 0.5

# 加法

  • 计算加法

  • 事件绑定(委托)

  • 字符串拼接

var x = 3.0 + 4; // 7,类型提升,进行浮点加法

# 位移操作符

数据在内存中的二进制的结构,向左或向右进行一定位数的平移

当没有溢出时,左移就是乘 2 ,右移就是除 2

  • << :左移
  • >> :右移
int x = 7;
int y  = x << 1;
// 以二进制的形式转化为字符串,32 位并且拿 0 补齐
string strX = Convert.ToString(x, 2).PadLeft(32, '0');
string strY = Convert.ToString(y, 2).PadLeft(32, '0');
Console.WriteLine(strX); // 00000000000000000000000000000111
Console.WriteLine(strY); // 00000000000000000000000000001110
// 右移时最高位:正数补 0 ,负数补 1
int x = -7;
int y = x >> 1;
string strX = Convert.ToString(x, 2).PadLeft(32, '0');
string strY = Convert.ToString(y, 2).PadLeft(32, '0');
Console.WriteLine(strX); // 11111111111111111111111111111001
Console.WriteLine(strY); // 11111111111111111111111111111100
Console.WriteLine(y); // -4

# 关系操作符

运算结果为布尔类型

假设 A=10 B=20

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真(A == B) 不为真
!=检查两个操作数的值是否相等,如果不相等则条件为真(A != B) 为真
>检查左操作数的值是否大于右操作数的值,如果是则条件为真(A> B) 不为真
<检查左操作数的值是否小于右操作数的值,如果是则条件为真(A < B) 为真
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真(A>= B) 不为真
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真(A <= B) 为真

char 类型:比较 Unicode 码

char char1 = 'a';
char char2 = 'A';
Console.WriteLine(char1 > char2); // True
var u1 = (ushort)char1;
var u2 = (ushort)char2;
Console.WriteLine(u1); // 97
Console.WriteLine(u2); // 65

字符串比较:只能够用来比较相不相等

string str1 = "abc";
string str2 = "Abc";
Console.WriteLine(str1 == str2); // False
Console.WriteLine(str1.ToLower() == str2.ToLower()); // True,忽略大小写
// 0:两个数相等,正数:数 1 大于数 2,负数:数 1 小于数 2
string.Compare(str1, str2); // -1

# 类型检测操作符

# is

检验一个对象是不是某个类型的对象

Animal类 <- Human类 <- Teacher类
Car类
Teacher t = new Teacher();
var result = t is Teacher; // 检测 t 所引用的实例是否为 Teacher
Console.WriteLine(result.GetType().FullName); // System.Boolean
Console.WriteLine(result); // True
Console.WriteLine(t is Animal); // True
Car car = new Car();
Console.WriteLine(car is Animal); // False
Console.WriteLine(car is object); // True,所有类都从 object 派生出来
Human h = new Human();
Console.WriteLine(h is Teacher); // False

# as

object o = new Teacher();
if(o is Teacher)
{
  Teacher t = (Teacher)o;
  t.Teach();
}
//as 操作符使用
Teacher t = o as Teacher;
if (t != null)
{
  t.Teach();
}

# 逻辑与 & 、或 | 、异或 ^

一般在操作二进制数据,图像数据时用

&、 | 和 ^ 的真值表如下所示:

pqp & q(一假为假)p | q(一真为真)p ^ q(不一样才为真)
00000
01011
11110
10011

假设 A=60 B=13

运算符描述实例
&如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中(A & B) 将得到 12,即为 0000 1100
|如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中(A | B) 将得到 61,即为 0011 1101
^如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中(A ^ B) 将得到 49,即为 0011 0001
~按位取反运算符是一元运算符,具有 "翻转" 位效果,即 0 变成 1,1 变成 0,包括符号位(~A) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式
<<二进制左移运算符。左操作数的值向左移动右操作数指定的位数A << 2 将得到 240,即为 1111 0000
>>二进制右移运算符。左操作数的值向右移动右操作数指定的位数A >> 2 将得到 15,即为 0000 1111
int x = 7;
int y = 28;
int z = x & y;
string strX = Convert.ToString(x, 2).PadLeft(32, '0');
string strY = Convert.ToString(y, 2).PadLeft(32, '0');
string strZ = Convert.ToString(z, 2).PadLeft(32, '0');
Console.WriteLine(strX); // 00000000000000000000000000000111
Console.WriteLine(strY); // 00000000000000000000000000011100
Console.WriteLine(strZ); // 00000000000000000000000000000100
示例
int a = 60; // 60 = 0011 1100
int b = 13; // 13 = 0000 1101
int c = 0;
c = a & b; // 12 = 0000 1100
Console.WriteLine("Line 1 - c 的值是 {0}", c);
c = a | b; // 61 = 0011 1101
Console.WriteLine("Line 2 - c 的值是 {0}", c);
c = a ^ b; // 49 = 0011 0001
Console.WriteLine("Line 3 - c 的值是 {0}", c);
c = ~a; // -61 = 1100 0011
Console.WriteLine("Line 4 - c 的值是 {0}", c);
c = a << 2; // 240 = 1111 0000
Console.WriteLine("Line 5 - c 的值是 {0}", c);
c = a >> 2; // 15 = 0000 1111
Console.WriteLine("Line 6 - c 的值是 {0}", c);
Console.ReadLine();

# 条件与 && 、条件或 ||

用来操作布尔类型值的,结果也是布尔类型

假设 A=true B=false

运算符描述实例
&&逻辑与运算符。如果两个操作数都非零,则条件为真(A && B) 为假
||逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真(A || B) 为真
!逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假

条件与和条件或有短路效应

int x = 3;
int y = 4;
int a = 3;
if (x > y && a++ > 3) // 短路,跳过右边的表达式
{
  Consolse.WriteLine("Hello");
}
Consolse.WriteLine(a); // 3

# ?? null 合并操作符

合并运算符 ?? :如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值

// Nullable<int> x = null; 可空类型
int? x = null;
Console.WriteLine(x.HasValue); // False,是否有值
int y = x ?? 1; // 如果 x 为 null,就拿 1 来代替
Console.WriteLine(y); // 1
int y = (x == null) ? 1 : x; // 可以理解为三元运算符的简化形式

# ?: 条件操作符

唯一一个三元操作符,本质上就是 if else 的简写

int x = 80;
string str = (x >= 60) ? "Pass" : "Failed"; // 使用 () 将条件括起来,提高可读性
Console.WriteLine(str); // Pass

# 赋值和 Lambda 表达式

运算符描述实例
=简单的赋值运算符,把右边操作数的值赋给左边操作数C = A + B 将把 A + B 的值赋给 C
+=加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数C += A 相当于 C = C + A
-=减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数C -= A 相当于 C = C - A
*=乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数C = A 相当于 C = C A
/=除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数C /= A 相当于 C = C / A
%=求模且赋值运算符,求两个操作数的模赋值给左边操作数C %= A 相当于 C = C % A
<<=左移且赋值运算符C <<= 2 等同于 C = C << 2
>>=右移且赋值运算符C >>= 2 等同于 C = C >> 2
&=按位与且赋值运算符C &= 2 等同于 C = C & 2
^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
|=按位或且赋值运算符C |= 2 等同于 C = C | 2

=> (Lambda 表达式:在前面 delegate 操作符讲过了)

更新于

请我喝奶茶⌯oᴗo⌯

呆鸭 微信支付

微信支付

呆鸭 支付宝

支付宝