C#学习 - 操作符

操作符简介

操作符也成为“运算符”,操作符是用来操作数据的,被操作符操作的数据成为操作数(Operand)
大多数情况下从左向右运算,而赋值与lambda表达式是先运算右边,再运算左边

操作符本质

  • 操作符的本质是函数(即算法)的“简记法”
    Add(Add(1, 2), 3) 可以简写为1+2+3;Add(1, Mul(2, 3)) 可以简写成1+2*3
  • 操作符不能脱离与它关联的数据类型
int x = 1;
int y = 2;
int z = x / y;
Console.WriteLine(z);//打印0,此处与“/”相关联的是 int

double a = 1;
double b = 2;
double c = a / b;
Console.WriteLine(c);//打印0.5,此处与“/”相关联的是 double

操作符优先级与运算顺序

操作符优先级

优先级:赋值和lambda表达式 < 条件 < null合并 < 条件或(OR) < 条件与(AND) < 逻辑或(OR) < 逻辑异或(XOR) < 逻辑与(AND) < 相等 < 关系和类型检测 < 移位 < 加法 < 乘法 < 一元操作符 < 基本操作符
可以为一个表达式添加()来增加操作符优先级,而 [ ] 和 { } 在C#中有其他用处

同优先级操作符的运算顺序

  • 除了带有赋值功能的操作符,同优先级操作符都是由左向右进行运算
  • 带有赋值功能的操作符的运算顺序是从右向左
int x;
x = 1 + 2 + 3;
//+ 优先级比 = 高,先算1+2+3,从左向右
int y = 1; int z = 2;
x += y += z;
//此处为赋值功能的操作符,运算从右向左即:y = y + z,然后x = x + y
//z == 2,y == 3,x == 9 
  • 在计算机语言中,同优先级运算没有“结合律”。1+2+3计算机只会理解成Add(Add(1, 2), 3),不会理解为Add(1, Add(2, 3))

基本操作符

C#中有一类表达式是基本表达式(最简单,不能被拆分的表达式),基本操作符就是参与组成基本表达式的运算符

. 操作符

  • 成员访问操作符:可以访问外层名称空间中的子集名称空间;
System.IO;
  • 可以访问名称空间中的类型;
System.IO.File;//最后那个点操作符就是访问类型
  • 可以访问类型的静态成员;
System.IO.File.Create("C:\\ABC.txt");
//Create前面那个点操作符就是访问静态成员
  • 可以访问对象的成员
Form.myForm = new Form();
myForm.Text = "Hello World";//访问了属性成员
myForm.ShowDialog();//访问了方法成员

方法调用操作符

就是方法调用时的(),如:Add(1, 2);myForm.ShowDialog();
注:C#中有中特殊的类型 - 委托,不需要直接去调用方法A,可以通过间接的方法去调用

using System

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Tool t = new Tool();
            Action myAction = new Action(t.ShowHello);//返回类型为void,参数为空
            myAction();//通过调用myAction来间接调用ShowHello
        }
    }
    class Tool
    {
        public void ShowHello()
        {
            Console.WriteLine("Hello World");
        }
    }
}

元素访问操作符

用“ [ ] ”访问集合中的元素

  • 访问数组
int[] intArr = new int[] { 1, 2, 3, 4 };//创建了一个有4个元素的数组
Console.WriteLine(intArr[0]);//数组第一个元素下标为0
Console.WriteLine(intArr[2]);
Console.WriteLine(intArr[intArr.Length - 1]);//数组中最后一个元素
  • 访问字典
using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, Man> manDic = new Dictionary<string, Man>();
            //string当作索引类型;Man当作值类型
            for (int i = 1; i < 100; i++)
            {
                Man m = new Man();
                m.Name = "Man_" + i.ToString();
                m.ID = i;
                manDic.Add(m.Name, m);
            }
            Man num53 = manDic["Man_53"];//访问时“[]”中放入索引
            Console.WriteLine(num53.ID);
        }
    }
    class Man
    {
        public string Name;
        public int ID;
    }
}

后置++与后置- -

int x = 1;
x++;//x = x + 1
Console.WriteLine(x);//打印2
int y = 1;
y--;//y = y - 1
Console.WriteLine(y);//打印0
y = x++;
Console.WriteLine(x);//打印3
Console.WriteLine(y);//打印2

后置++与后置- -遇上其他运算时,先使用操作符前的变量,再对变量+1或者-1

typeof操作符

查看一个类型的内部结构(Metadata),内部结构有类型的名字、属于哪个名称空间等等信息

Type t = typeof(int);
Console.WriteLine(t.FullName);

default操作符

获取一个类型的默认值

int x = default(int);
Console.WriteLine(x);
//值类型默认值为0
Form myForm = default(Form);
Console.WriteLine(myForm);
//引用类型默认值为0
//枚举类型默认值为第一个,如果创建枚举类型时为每个值设置了对应数时,默认值为对应数为0的那个值
//枚举类型设置对应数时一定要设置一个0
enum Level
{
	Mid = 1;
	Low = 0;
	High = 2;
}

new操作符

在内存中创建一个类型的实例,并立即调用实例构造器,如果new左边有赋值符号时,会将实例的内存地址传给左边的变量,new操作符还可以调用实例的初始化器

Form myForm = new Form() { Text = "Hello" };//{}就是初始化器

在内存中创建实例不一定需要new操作符和()

string name = "Jack";//此时没有new
int[] myArr = new int[10];//此时没有(),创建了有10个元素的数组
int[] myArr = { 1,2,3,4 };//没有new,创建了有4个元素的数组

为匿名类型创建对象

Form myForm = new Form();//Form类型有名字(名字就是Form),所以是非匿名类型
var man = new { Name = "Jack",Age = 18 };//创建了一个匿名类型
//var可以创建隐式类型变量
//隐式类型让编译器自己推断变量的类型
//显式类型直接告诉编译器其变量的类型
Console.WriteLine(man.Name);
Console.WriteLine(man.Age);
Console.WriteLine(man.GetType().Name);//可以查看这个匿名类型的名字

注:在一个类中调用了new操作符后,正在编写的类型和创建实例的类型之间会建立非常紧密的关系

internal class Program
{
    static void Main(string[] args)
    {
        Form myForm = new Form(); 
    }
}

上段代码中Program类和Form类型建立了紧密的联系,即依赖关系

new关键字的多用性

  • new作为操作符
  • 子类对父类方法隐藏
class Tool
{
    public void ShowHello()
    {
        Console.WriteLine("Hello");
    }
}
class Tool1 : Tool { }//什么都不写也能用父类Tool的ShowHello方法
class Tool2 : Tool
{
    new public void ShowHello()
    { //子类对父类方法进行隐藏
        Console.WriteLine("Hello World");
    } //此时子类使用ShowHello时打印的结果就和父类Tool不一样
}

checked & unchecked

checked - 检查一个值在内存中是否有溢出
unchecked - 不用检查

uint x = uint.MaxValue;//给x赋值为uint的最大值
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
uint y = x + 1;//出现了溢出
Console.WriteLine(y);//打印0,x+1所有位进1
try
{
    uint z = checked(x + 1);//溢出就会运行catch分支
    //而这里是unchecked时就不会运行catch分支
    Console.WriteLine(z);
}
catch(OverflowException)
{
    Console.WriteLine("Overflow");
}
checked
{
	//这中间所有代码都会检测是否溢出,unchecked就是不检查
}

C#默认时unchecked

delegate操作符

delegate是一种关键字,最主要的用处是用来声明委托,C#中有lambda表达式,用来替代delegate作为操作符使用
delegate可以使用匿名方法,lambda表达式也可以使用匿名方法

///.xaml文件
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Background="Black">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="2"/>
            <RowDefinition Height="Auto"/>
        Grid.RowDefinitions>
        <TextBox x:Name="myTextBox" Grid.Row="0"/>
        <Button x:Name="myButton" Content="Click Me" Grid.Row="2"/>
    Grid>
Window>
//.csharp文件
using System.Windows;

namespace WpfApp2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.myButton.Click += myButton_Click;
            //此处+=让myButton连接了一个事件处理器
        }
        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            this.myTextBox.Text = "Hello World";
        }
    }
}

方法封装了逻辑,可以在其他地方重复使用。如果创建了一个在其他地方不会复用的逻辑,同时也不想让它成为方法,则可以使用一个叫匿名方法的语法

//修改上面的.csharp文件
using System.Windows;

namespace WpfApp2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.myButton.Click += delegate (object sender, RoutedEventArgs e)
            {	//利用delegate创建了一个匿名方法
                this.myTextBox.Text = "Hello World";
            };
        }
    }
}

sizeof操作符

默认只能获取基本数据类型(只能是结构体数据类型)的实例在内存中占用的字节数

int x = sizeof(int);
Console.WriteLine(x);

非默认情况下可以获取自定义类型的实例所占用的字节数,需要在不安全代码中运行(项目属性 - 生成 - 勾选允许不安全代码)

using System

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                int x = sizeof(Tool);
                Console.WriteLine(x);
            }
        }
    }
    struct Tool
    {
        long ID;
        string Name;
    }
}

-> 操作符

指针访问成员的操作符,使用指针需要在不安全代码中使用

using System 

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                Tool t;
                t.ID = 1;
                Tool* pt = &t;
                pt->ID = 2;
                Console.WriteLine(t.ID);
            }
        }
    }
    struct Tool
    {
        long ID;
    }
}

一元操作符

此种操作符只用一个操作数,又称单目操作符

&和*操作符

需要在不安全代码中使用,&是取地址操作符,将变量的地址取出来交给一个指针变量;* 是解引用操作符(*pt).ID = 100;//看上方代码段

正、负、非、反操作符

int x = 100;
int y = +x;//正操作符
int z = -x;//负操作符
int a = -(-x);//连续去两次负,需要加括号

使用正负操作符时要注意是否溢出,比如int类型的最小值的绝对值比最大值的绝对值大一

int x = int.MinValue;
int y = checked(-x);//此时会出现溢出警告

求反操作符是对一个值在二进制下按位取反(1变0,0变1),符号是~

int x = 123;
//0000 0000 0000 0000 0000 0000 0111 1011
int y = ~x;
//1111 1111 1111 1111 1111 1111 1000 0100
Console.WriteLine(y);
//-124

计算机中求一个数的相反数就是将这个数按位取反后+1
非操作符是“ ! ”,只能操作bool类型操作数

bool b1 = true;
bool b2 = !b1;//b2 = false

前置++和前置- -

单独使用前置++、前置- -和单独使用后置++和后置- -效果是一样的

int x = 100
++x;//x = x + 1 = 101
--x;//x = x - 1 = 100
int y = ++x;//x = x + 1; int y = x
Console.WriteLine(y);//打印101

前置++和前置- -如果左边有赋值符号时,先将变量+1或者-1后再对左边的变量进行赋值

强制类型转换操作符

(Type)x,x就是变量,此种转换是显示类型转换

string str1 = "1";
string str2 = "2";
Console.WriteLine(str1 + str2);
int x = Convert.ToInt32(str1);//这里也是一种类型转换
int y = Convert.ToInt32(str2);
Console.WriteLine(x + y);

隐式类型(implicit)转换

代码中不明确写出变量转换后的类型,由编译器自己判断

  • 不丢失精度的转换
int x = int.MaxValue;
long y = x;//进行了转换
//long占8个字节,而int只占4个字节,int转换为long不会丢失精度
满足隐式类型转换的同时不丢失精度(从左到右转换):
sbyte -> short -> int -> long -> float -> double或decimal
byte -> short、ushort、int、uint、long、ulong、float、double或decimal
ushort -> int、uint、long、ulong、float、double或decimal
uint -> long、ulong、float、double或decimal
ulong -> float、double或decimal
char -> ushort、int、uint、long、ulong、float、double或decimal
float -> double
  • 子类向父类的转换
using System;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            C c = new C();
            B b = c;//进行了类型转换
            //b此时还是只有ShowA和ShowB方法
            //当用一个引用变量去访问其所引用的实例的类型的成员时,只能访问到此变量的类型所有的成员
        }
    }
    class A
    {
        public void ShowA()
        {
            Console.WriteLine("A");
        }
    }
    class B : A//这种是“B是A的子类”,B继承了A的ShowA方法
    {
        public void ShowB()
        {
            Console.WriteLine("B");
        }
    }
    class C : B//“C是B的子类”,C继承了B的ShowB方法和A的ShowA方法
    {
        public void ShowC()
        {
            Console.WriteLine("C");
        }
    }
}
当用一个引用变量去访问其所引用的实例的类型的成员时,只能访问到此变量的类型所有的成员
  • 装箱

显示类型(explicit)转换

写代码时明确写出变量转换后的类型

  • 有可能导致精度的丢失(或发生错误),即cast(铸造)
Console.WriteLine(ushort.MaxValue);//最大值为65535
uint x = 65536;
ushort y = (ushort)x;//进行转换
Console.WriteLine(y);//打印0
可以使用强制转换类型操作符进行转换:
sbyte -> byte、ushort、uint、ulong或char
byte -> sbyte、char
short -> sbyte、byte、ushort、uint、ulong或char
ushort -> sbyte、byte、short或char
int -> sbyte、byte、short、ushort、uint、ulong或char
uint -> sbyte、byte、short、ushort、int或char
long -> sbyte、byte、short、ushort、int、uint、long或char
ulong -> sbyte、byte、short、ushort、int、uint、long或char
char -> sbyte、byte、short
float -> sbyte、byte、short、ushort、int、uint、long、ulong、char或decimal
double -> sbyte、byte、short、ushort、int、uint、long、ulong、char、float或decimal
decimal -> sbyte、byte、short、ushort、int、uint、long、ulong、char、float或double
  • 拆箱
  • 使用Convert类
Console.WriteLine(Convert.ToInt32("1"));
//将字符串转换为整型
Convert类几乎可以将所有类型的数据转换为其他类型
  • ToString方法与各数据类型的Parse/TryParse方法
double x = Convert.ToDouble("100");
double y = Convert.ToDouble("200");
double z = x + y;
Console.WriteLine(Convert.ToString(z));
object有ToString方法,而其他所有类型都源于object,也都有ToString方法
Convert的ToString方法和类型的ToString方法不一样
double x = double.Parse("100");
double y = double.Parse("200");
double z = x + y;
Console.WriteLine(z.ToString());
注:Parse方法只能转换格式正确的字符串
而TryParse的返回值类型是Bool类型,转换成功为True,转换失败为False
TryParse有个输出参数,利用输出参数将转换后的结果交给变量

await操作符

异步编程相关的操作符

算数运算操作符

即乘法和加法操作符(加减乘除、取余)
当运算符两边类型不同时,会进行类型提升,将类型转换为不会损失精度的类型

* 操作符

即乘法运算符

  • 整数乘法:两边为整型类型时进行整数乘法
  • 浮点乘法:两边为浮点数时进行浮点乘法
    +0或-0与+无穷或-无穷进行乘法时会得到NaN(无效数字)

/ 操作符

即除法操作符

  • 整数除法
    整数除法不能除以0,会出现除0异常
  • 浮点除法
    0与0相除得NaN;无穷与无穷相除得NaN;无穷除以0得无穷;0除以无穷得0
  • 小数除法(decimal)

% 操作符

即取余操作符

  • 整数取余
  • 浮点取余
    0与0取余得NaN;无穷为第一个数与其他数取余得NaN;0为第一个数与无穷取余得0;0作第一个数与其他非0非无穷得数取余得0;0作第二个数与任何数取余都得0;无穷为第二个数时与其他非0非无穷的数取余得原数
  • 小数取余(decimal)

± 操作符

“+”还可以连接两个字符串;“±”还可以操作委托
在浮点运算时,正无穷+负无穷=NaN
“±”都有整数、浮点、小数(decimal)运算

位移操作符

左位移:<<;右位移:>>
作用:数据在内存中得二进制结构向左或向右进行一定位数的平移

int x = 7;
//0000 0000 0000 0000 0000 0000 0000 0111
int y = x << 1;//左移一位
//0000 0000 0000 0000 0000 0000 0000 1110
Console.WriteLine(y);//打印14

当在没有溢出的情况下,左移一位就是乘2;右移一位就是除以2
左移无论是正数还是负数,最高位符号位补进来的都是0;右移是正数则补进来0,负数则补进来1

关系和类型检测操作符

关系操作符

“<”、“>”、“<=”、“>=”、“==”(等于)、“!=”(不等于)
关系类型操作符返回值类型都是Bool类型

var x = 3.4 >= 1.3;
Console.WriteLine(x.GetType().FullName);
Console.WriteLine(x);

关系操作符可以比较字符串大小,还有srting.Compara()方法可以比较字符串大小。返回值=0,则相等;返回值<0,则第一个小于第二个;返回值>0,则第一个大于第二个

类型检测操作符

即“is”操作符和“as”操作符
“is”操作符检测一个对象是不是某个类型的对象
返回值类型是Bool类型

Form f = new Form();
var z = f is Form;
Console.WriteLine(z.GetType().FullName);
Console.WriteLine(z);

当类型A是类型B的子类时,a is A返回值为True,a is B返回值也为True
所有类型都是object类型的子类,所以a is object返回值为True
“as”操作符与“is”操作符在某些情况下可以互换

using System;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            object o = new Dead();
            Dead dead = o as Dead;
            //o as Dead 为真返回对象的地址,为假返回NULL
            if(dead != null)
            {
                dead.See();
            }
        }
    }
    class Alive
    {
        public void Say()
        {
            Console.WriteLine("I'm alive");
        }
    }
    class Ill : Alive
    {
        public void Cry()
        {
            Console.WriteLine("I'm ill");
        }
    }
    class Dead : Ill
    {
        public void See()
        {
            Console.WriteLine("Dead...");
        }
    }
}

逻辑操作符

逻辑与

即“&”,又称按位与
逻辑与是按位与,1为真,0为假,都为真才为真,一个为假就为假

int x = 7;
int y = 28;
int z = x & y;
string strX = Convert.ToString(x, 2).PadLeft(32, '0');
//x二进制:0000 0000 0000 0000 0000 0000 0000 0111
string strY = Convert.ToString(y, 2).PadLeft(32, '0');
//y二进制:0000 0000 0000 0000 0000 0000 0001 1100
string strZ = Convert.ToString(z, 2).PadLeft(32, '0');
//z二进制:0000 0000 0000 0000 0000 0000 0000 0100

逻辑或

即“ | ”,又称按位或
逻辑或就是两个都为假才为假,一个为真就为真

int x = 7;
//x二进制:0000 0000 0000 0000 0000 0000 0000 0111
int y = 28;
//y二进制:0000 0000 0000 0000 0000 0000 0001 1100
int z = x | y;
//z二进制:0000 0000 0000 0000 0000 0000 0001 1111

逻辑异或

即“ ^ ”,又称按位异或
两个不一样才为真,一样就为假

int x = 7;
//x二进制:0000 0000 0000 0000 0000 0000 0000 0111
int y = 28;
//y二进制:0000 0000 0000 0000 0000 0000 0001 1100
int z = x ^ y;
//z二进制:0000 0000 0000 0000 0000 0000 0001 1011

条件与、条件或

条件与

即“&&”,可以认为是“并且”
操作符两边都为真,整体才为真

int x = 1;
int y = 2;
int a = 100;
int b = 200;
if (x < y && a < b)
{
    Console.WriteLine("It's OK");
}

条件或

即“ || ”,可以认为是“或者”
操作符两边一个为真,整体就为真

int x = 1;
int y = 2;
int a = 100;
int b = 200;
if (x < y || a > b)
{
    Console.WriteLine("It's OK");
}

短路效应

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

//条件与
int x = 1;
int y = 2;
int z = 3;
if (x > y && z++ > 3) 
{
	Console.WriteLine("It's OK");
}
Console.WriteLine(z);
//打印出3,&&前的语句就已经为假,后面的语句直接跳过
if (x < y && z++ > 3) 
{
	Console.WriteLine("It's OK");
}
Console.WriteLine(z);
//打印4,&&前为真,继续检测后面的语句
//条件或
int x = 1;
int y = 2;
int z = 3;
if (x < y || z++ > 3) 
{
	Console.WriteLine("It's OK");
}
Console.WriteLine(z);
//打印3,||前为真就直接不看后面的语句了

条件与遇见False就马上停止,条件或遇见True就马上停止

NULL合并

即“??”

int x;
x = null;//此处报错,无法将null赋值给int类型变量

Nullable<int> y = null;//Nullable类型变量才能赋值为null
y = 100;//还能继续赋值为其他值

int? z = null;//这样也能赋值null
z = 100;

判断左边变量是否为null,是则返回右边的(变量的)值

int? x  = null;
int y = x ?? 1;
//如果x为null,则y为1

条件操作符

即“ ? : ”,也是三目操作符,也是唯一 一个可以接收3个操作数的操作符,本质上是if条件句的简写

int x = 100;
if(x > 10)
{
	x = 1;
}
else
{
	x = 0;
}
Console.WriteLine(x);

上段代码是if条件句,用条件操作符改写后就是:

int x = 100;
x = x > 10 ? 1 : 0;
Console.WriteLine(x);

A ? B : C
A为真则返回B,A为假则返回C,一般将A语句用括号括上,来增加可读性

赋值和lambda表达式

赋值操作符

=、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=
除了“ = ”以外,剩下的都是左边的变量先与右边的变量或值进行对应的运算后再将值赋给左边的变量,如+=:

int x = 10;
int y = 20;
x += y;
Console.WriteLine(x);//打印30

lambda表达式

即“ => ”,参考delegate操作符的代码

//修改.csharp文件
using System.Windows;

namespace WpfApp2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.myButton.Click += (ender, e) =>
            {	//使用lambda表达式还不需要输入类型,编译器会自己判断类型
                this.myTextBox.Text = "Hello World";
            };
        }
    }
}

你可能感兴趣的:(C#学习,c#,学习,开发语言)