- 操作符(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
# 优先级
- 操作符的优先级
- 可以使用圆括号提高被括起来表达式的优先级
- 圆括号可以嵌套
- 不像数学里有方括号与花括号,在 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); | |
} |
# 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); |
通过 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 获取自定义结构体类型的大小,但需要把它放在不安全的上下文中
需要在 “项目属性” 里面开启 “允许不安全代码”
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(); | |
} |
# 逻辑与 &
、或 |
、异或 ^
一般在操作二进制数据,图像数据时用
&、 | 和 ^ 的真值表如下所示:
p | q | p & q(一假为假) | p | q(一真为真) | p ^ q(不一样才为真) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设 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 操作符讲过了)