类别 | 运算符 |
---|---|
基本 | x.y f(x) a[x] x++ x-- new typeof default checked unchecked delegate sizeof -> |
一元 | + - ! ~ ++x --x (T)x await &x *x |
乘除 | * / % |
加减 | + - |
位移 | << >> |
关系和类型检测 | <> <= >= is as |
相等 | == != |
逻辑"与" | & |
逻辑XOR | ^ |
逻辑OR | | |
条件AND | && |
条件OR | | | |
null合并 | ?? |
条件 | ?: |
赋值和lambda表达式 | = *= /= %= += -= <<= >>= &= ^= |= => |
*越靠上的操作符运算优先级越高,当同行操作符组成表达式时,大多数情况下,由左至右运算(赋值操作符不同,它是从右至左)
操作符的本质是函数(即算法)的简记法
Add(Add(3,4),5)
Add(3,Mul(4,5)) //注意优先级
操作符不能脱离与它关联的数据类型
int x = 3;
int y = 2;
int z = x / y;
Console.WriteLine(z);
// Z输出为1 这是因为这是整数类型里的除号,所以进行整除
double a = 3;
double b = 2;
double c = a / b;
Console.WriteLine(c)
//C将输出1.5 double是双精度浮点数,将进行浮点数运算
每一种数据类型都有一套与该类型相关的操作与运算
usr
class Program
{
static void Main(string[] args)
{
Person persion1 = new Person();
Person person2 = new Person();
persion1.Name = "lz";
person2.Name = "lz's wife";
List<Person> nation = persion1 + person2;
foreach (var p in nation)
{
Console.WriteLine(p.Name);
}
}
}
class Person
{
public string Name;
public static List<Person>operator +(Person p1,Person p2)
{
List<Person> people = new List<Person>();
people.Add(p1);
people.Add(p2);
for (int i = 0; i < 11; i++)
{
Person child = new Person();
child.Name = p1.Name + "&" + p2.Name + "s'fz";
people.Add(child);
}
return people;
}
}
int a = 100;
int b = 200;
a = a + b;
Console.WriteLine(a); //可以看出先运行a+b 然后再给a赋值 所以最后输出a=300
int x = 1;
int y = 2;
int z = 3;
x += y += z;
Console.WriteLine(x); //最后运算x+=y 所以x值为6
Console.WriteLine(y); //先运算y+=z 所以输出y值为5
Console.WriteLine(z); // z输出为3
优先级最高,也就是说在运算中出现了基本运算符和其他运算符混用的情况下基本操作符最先运算
System.IO //访问System命名空间中的IO命名空间
IO.file //访问名称空间中的类型
File.Create("D:\\HelloWorld.txt"); //访问类型当中的静态成员
Form myForm = new Form();
myForm.Text = "Hello,World";
myFrom.ShowDialog();
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
double x = c.Add(3.0, 5.0);
Console.WriteLine(x);
c.PrintHello(); //()不能省略,哪怕他没有参数
}
}
class Calculator
{
public double Add(double a , double b)
{
return a + b;
}
public void PrintHello()
{
Console.WriteLine("Hello");
}
}
int[] myIntArray = new int[] {
1, 2, 3, 4, 5 };
Console.WriteLine(myIntArray[0]);
Console.WriteLine(myIntArray.Length);
static void Main(string[] args)
{
Dictionary<string, Student> stuDic = new Dictionary<string, Student>();
for (int i = 0; i < 100; i++)
{
Student stu = new Student();
stu.Name = "s_" + i.ToString();
stu.Score = 100 + i;
stuDic.Add(stu.Name, stu);
}
Student number6 = stuDic["s_6"];
Console.WriteLine(number6.Score);
}
}
class Student
{
public string Name;
public int Score;
}
int x = 100;
x++
Console.WriteLine(x) //x输出101
int y = 100;
y--
Console.WriteLine(y); //y输出99
int x = 100;
int y = x++;
Console.WriteLine(x); //此时x输出101
Console.WriteLine(y); //此时y输出100
//当后置自增(自减)赋值时,先将未自增(自减)的值赋值给变量,然后再自增;
int x =100
int y = x ;
x++;
Console.WriteLine(x);
Console.WriteLine(y);
了解new 之前先了解var
var : 声明一个隐式类型变量
C#是强类型语言,强类型语言要求每一个变量都拥有自己的数据类型 比如说 int x 这就是一个显式类型变量
隐式类型,如下实例
var x = 100; //这里100后面什么都不加那么100就是整型,赋值给x,x就是整型了
Console.WriteLine(x.GetType().Name);//输出int32
var y = 100D; //这里100后面加了个D代表着这个100是double类型,赋值给y之后 y就是double类型;
Console.WriteLine(y.GetType().Name));//输出double
var z = "100"; //这里100用双引号括起来,代表这个100为字符串类型,赋值给z之后,z就是字符串类型;
Console.WriteLine(z.GetType().Name));//输出string
//因为c#是强类型语言,所以当一个你给一个隐式类型赋值了之后确定了它的值类型,那么就不可以再给它赋其他类型的值了如下
z = 100; //这里会报错,字符串不能转化成整数类型
在内存当中为我们创建实例并立刻调用它的实例构造器,如果new操作符左边有赋值符号,那么new操作符会将自己拿到的实例内存地址通过赋值符号赋值给变量
new Form(); //new操作符后面跟上一个类型,就是在内存空间中创建Form这个类型的实例,创建完了之后()调用它的实例构造器
这个时候我们运行时什么显示都没有,这是因为我们在内存当中没有任何变量引用它
From myFrom;//声明一个同类型的变量;
myFrom=new Form();//引用它`在这里插入代码片`
//从这里我们可以看出new这个操作符除了能在内存上创建它的实例并且调用它的实例构造器之外呢,
//还能得到这个实例的内存地址,并且把这个内存地址通过赋值符号交给负值访问这个实例的变量,
//这样就在变量和实例之间构成了引用关系,有了这个引用关系,我们就可以通过变量来访问实例.
myFrom.text = "Hello"; //现在可以更改实例的属性了
myFrom.ShowDialog();
new 能调用实例的初始化器,就是在调用实例的构造器()之后加上一对花括号{},我们可以在这对花括号里立刻为实例初始化值,示例如下
From myFrom = new From() {
Text="Hello" }; //在调用构造器之后调用初始化器,将Text值初始化为Hello
//也可以初始化多个属性 使用逗号 " , " 分割就好
myFrom.ShowDialog();
并不是所有实例都需要使用new操作符创建 比如 string 我们都知道 string是类类型,创建类类型实例需要使用new操作符 ,但我们平常声明string时并没有使用new操作符
string name = "猪头";//编译器并没有报错,我们也一直是这样使用的
int age = 100; //和声明结构类型的int 语法一样
这是因为string 和int类型都是最基本的类型,微软在创建它们的时候为了统一它们的使用体验,隐藏了new操作符,当然如果你非要使用new操作符也不是不可以
string a = new string("Hello");
Console.WriteLine(a);
//a打印出来的是Hello 可见 string a = new string("Hello") 与 string a = "Hello"并没什么两样
类似的还有我们的数组类型
int[] myArray = new int[10];
//在这里,我们使用new创建int数组实例 但我们也可以不使用new创建
int[] myArrays = {
1,2,3,4,5,6,7,8,9}
//这样也能创建int数组 也没有使用new从操作符
在了解匿名对象前,先了解非匿名对象如下
Form是一个对象,且有自己的名字
Form myForm = new Form(){
Text="Hello"}; //这是我们对于new操作符的最常见使用方法
//是不是new后面必须要跟一个数据类型才能创建对象呢? 答案是否的
new 在创建实例对象的时候要跟数据类型,这是对于非匿名对象来说才需要对于匿名对象来说
new{
Text="猪头",Age=100}; //这里我们在new操作符后面不声明类型,直接跟上一个初始化器并随意初始化了两个属性
//属性名不重要,可以自定义,
//在这里我们并没有告诉编译器,text,与age是什么数据类型,但是没关系编译器会自己推断
//比如说text,赋的值是"猪头"那么它就是string类型,age赋的值是100,那么他就是int类型
上诉示例已经初始化完了对象了,要想这个初始化完的对象能使用我们还需要一个变量来引用这个对象,但我们并不知道我们初始化的对象是什么类型,所有我们不知道该声明什么变量来引用,
这个时候,就可以使用我们刚刚写到的var了
//var声明一个隐式类型变量
//也就是说你并不能提前知道var声明的是什么变量,=赋值给它什么类型的变量,那么它就是什么类型
//所有
var myVariable; //var声明的变量没有类型,你给它什么类型,它便是什么类型
myVariable=new{
Text="猪头",Age=100}; //引用刚刚声明的对象
我们可以看看刚刚声明对象能不能使用
var myVariable = new{
Text="猪头",Age=100};
Console.WriteLine(myVariable.Text);//输出猪头
Console.WriteLine(myVariable.Age);//输出100
//可以正常使用
我们也可以看看刚刚var声明的引用变量类型到底是什么
var myVariable = new{
Text="猪头",Age=100};
Console.WriteLine(myVariable.Get);
控制台输出如下
如图可见,输入为<>f__AnonymousType0`2[System.String, System.Int32]
<>f__AnonymousType : 匿名类型前缀
0 : 我们在程序当中创建的第一个
'2:这是一个泛型类,构成这个类型需要两个类型
[System.String, System.Int32] : 构成的类型为 System.String, System.Int32
上面的示例可以总结为:
为匿名类型创建对象,并且用隐式类型变量来引用这个实例对象
new操作符的功能非常强大也是一个非常重要的操作符,但功能越强大,却越容易滥用,后果也越难预料越难控制;比如你在一个类里面调用new操作符创建一个实例,那么你正在编写的这个类型,和你正在创建实例的这个类型之间就构成了非常紧密的耦合
class Program
{
static void Main(string[] args)
{
Form myForm = new Form() {
Text = "猪头" };
//我在Program 这个类里面创建了Form这个类类型的实例
//这个时候Program 与Form就紧紧的耦合在了一起
//当Form这个类出错的时候,Program也就没办法运行了
}
//所以在使用new的时候要慎用
}
class Program
{
static void Main(string[] args)
{
var myClass = new IsClass();
myClass.Report();
var myClasss = new Classs();
myClasss.Report();
}
}
class IsClass
{
public void Report()
{
Console.WriteLine("I'm a Class");
}
}
class Classs:IsClass //继承IsClass类
{
new public void Report() //子类对父类方法的隐藏
{
Console.WriteLine("I'm Classs");
}
}
Metadata(元数据)它包含命名空间的名字,它的父类是谁,包含多少个方法多少个事件等等
static void Main(string[] args)
{
Type t = typeof(int);
Console.WriteLine(t.Namespace);//查看命名空间
Console.WriteLine(t.FullName);//全名
Console.WriteLine(t.Name);//名称
int c = t.GetMethods().Length;
foreach (var mi in t.GetMethods())
{
Console.WriteLine(mi.Name);
}
Console.WriteLine(c); //查看有多少方法
}
double x = default(double);
Console.WriteLine(x);
//x返回值为0
class Program
{
static void Main(string[] args)
{
Level level = default(Level);
Console.WriteLine(level);
//返回Low,
这是因为在你声明枚举类型的时候,编译器会将它和整数值对应起来,第一个为零,往后依次加一
}
}
enum Level
{
Low,
Mid,
High
}
当已经为枚举类型赋值时
class Program
{
static void Main(string[] args)
{
Level level = default(Level);
Console.WriteLine(level);
//这里输出的值为Mid/因为我已经为Mid赋值为0
}
}
enum Level
{
Low=3,
Mid=0,
High=1
}
当我们既给枚举赋值了又没有赋值零的属性时
class Program
{
static void Main(string[] args)
{
Level level = default(Level);
Console.WriteLine(level);
//这里不会输出任何一个枚举属性,因为没有任何一个枚举属性值为零,所以,这里单单输出一个参数0
}
}
enum Level
{
Low=3,
Mid=2,
High=1
}
一个值在内存空间所占大小决定了这个值的表达范围,一旦超出了这个范围,那么这个值就产生了溢出,checked 便是用来检查有没有溢出,而uncheckecd就是告诉编译器不用检查溢出
示例
//uint:无符号整型
//占四个字节(32个bit)来表示它的数值,也就是说它能表示0至2的32次方-1
uint x = uint.MaxValue; //获取到 uint能表示的最大值
Console.WriteLine(x); //最大值为4294967295
string binStr = Convert.ToString(x,2);//查看它的二进制表示
Console.WriteLine(binStr);//表示为:11111111111111111111111111111111
uint y = x + 1; //这个时候我们给x加一
//因为x已经是uint能表示的最大值了,那么再加一显然是不行的
Console.WriteLine(y);//打印出y看看有什么
//0这是y所打印出来的值
为什么会这样呢?
因为x的值为11111111111111111111111111111111加一便全部产生进位表示为100000000000000000000000000000000但因为32位已经是uint所能表示的最大值了,所有进位所多出来了那个1便被舍弃了变成了00000000000000000000000000000000了表示为0
用checked可以让浏览器抛出值溢出异常.示例如下
uint y =checked( x + 1);
Console.WriteLine(y);
static void Main(string[] args)
{
//uint:无符号整型
//占四个字节(32个bit)来表示它的数值,也就是说它能表示0至2的32次方-1
uint x = uint.MaxValue;
Console.WriteLine(x);//
string binStr = Convert.ToString(x,2);
Console.WriteLine(binStr);
try
{
uint y = checked(x + 1);
Console.WriteLine(y);
}
catch (OverflowException e)
{
Console.WriteLine("There‘s overflow"); ;
}
}
我们将checked改为uncheckecd,就不会检查到异常了
static void Main(string[] args)
{
//uint:无符号整型
//占四个字节(32个bit)来表示它的数值,也就是说它能表示0至2的32次方-1
uint x = uint.MaxValue;
Console.WriteLine(x);//
string binStr = Convert.ToString(x,2);
Console.WriteLine(binStr);
try
{
uint y = unchecked(x + 1);//将checked改为unchecked,编译器将不报错
Console.WriteLine(y);
}
catch (OverflowException e)
{
Console.WriteLine("There‘s overflow"); ;
}
}
checked&uncheckecd作为关键字使用能检查一段代码块的值溢出 示例如下
static void Main(string[] args)
{
//uint:无符号整型
//占四个字节(32个bit)来表示它的数值,也就是说它能表示0至2的32次方-1
uint x = uint.MaxValue;
Console.WriteLine(x);//
string binStr = Convert.ToString(x,2);
Console.WriteLine(binStr);
checked //checked作为关键字使用能够检查它后面花括号括起来的的代码块是否有值溢出
{
try
{
uint y = x + 1;
Console.WriteLine(y);
}
catch (OverflowException e)
{
Console.WriteLine("There‘s overflow"); ;
}
}
}
unchecked使用方法同理
把delegate当作操作符来使用已经是过时的技术了有些特殊,我这里使用wpf;来演示
首先使用xml做一个按钮和文本框
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto">RowDefinition>
<RowDefinition Height="2">RowDefinition>
<RowDefinition Height="Auto">RowDefinition>
Grid.RowDefinitions>
<TextBox x:Name="myTextBox" Grid.Row="0">TextBox>
<Button x:Name="myButto" Content="Click Me!" Grid.Row="2">Button>
Grid>
public MainWindow()
{
InitializeComponent();
this.myButton.Click += MyButton_Click; //通过+=挂接上一个事件处理器
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
this.myTextBox.Text = "Hello,World!";
}
实现效果: 点击按钮Click Me! textBook,显示文本Hello.World!
实现成功
我们为什么需要方法,或者函数呢?:是因为这个方法或者函数它封装了一定逻辑,可以在其他的地方得到重用,如果我们发现我写的这个逻辑永远不会在其他的地方被重用,我们也就可以不将它写成方法,我们可以使用匿名方法的方法,将方法的名字删掉,返回值也删掉,他就变成匿名方法了,然后将它放在原先挂接函数名称的地方,函数并在前方加上delegate如下图:
public MainWindow()
{
InitializeComponent();
this.myButton.Click += delegate (object sender, RoutedEventArgs e)
{
this.myTextBox.Text = "Hello,World!";
};
}
然后运行程序 ,并点击Click Me! 执行成功
在前面说过,这种方法已经过时了,因为我们有拉姆达表达式,拉姆达表达式,比隐式方法还要简洁
实现方法将delegate和数据类型删掉,数据类型也能够删掉是因为编译器能自动推断出来然后再在后面加上一个箭头符号(拉姆达表达式符号),示例如下
public MainWindow()
{
InitializeComponent();
this.myButton.Click += (sender, e) =>
{
this.myTextBox.Text = "Hello,World!";
};
}
然后再点击程序运行,点击Click Me!.实现成功
从有名字的函数/方法进化成没有名字的匿名方法,再到现在的拉姆达表达式,C#越来越简洁
int a = sizeof(int); //查看int所占字节
Console.WriteLine(a); //占四个字节
int b = sizeof(double); //查看double占多少个字节
Console.WriteLine(b);//占八个字节
int c = sizeof(uint);//查看uint占多少个字节
Console.WriteLine(c);//占四个字节
int d = sizeof(decimal);//查看decimal占多少个字节
Console.WriteLine(d);//占一十六个字节
class Program
{
static void Main(string[] args)
{
unsafe //编译器编译不过去,需要将代码放在unsafe代码块中,并将项目属性调整为允许unsafe
//这样我们就能查看自定义实例对象在内存中所占字节数了
{
int x = sizeof(Student);//查看该实例所占字节
Console.WriteLine(x);
}
}
}
class Student //自定义一个类
{
int ID;
string Name;
}
-> 用于直接操作内存,需要放在不安全的代码块内才能运行,指针只能用于操作结构体类型,不能用于操作引用类型,示例如下
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);
}
}
}
class Student
{
public int ID;
public long Score;
}
一元操作符优先级低于基本操作符,一元操作符只有一个操作数
int a = 100; //声明一个int a 并赋值100
int b = +a; //取a的正数
Console.WriteLine(b); //输出为100
int c = -a;//取a的负数
Console.WriteLine(c);//输出为-100
int d = --a;//取负再取负
Console.WriteLine(d);//输出为99,因为--是前置自减
int e = -(-a);//取负再取负
Console.WriteLine(e);//输出为100
在数学里一个数值你想写多大就写多大位数不够添加位数就好,但计算机做不到这一点,它有位数限制,这就决定了它能表达的数据范围,当你想表达的这个值,超出了这个范围,它就溢出了,因为像int doube 这种带符号的数据类型他们的大小值是不对称的,如下示例
Console.WriteLine(int.MaxValue);//int最大值:为2147483647
Console.WriteLine(int.MinValue);//int最小值为-2147483648
我们会发现int的最小值的绝对值比int的最大值的绝对值大一这就会引发一些问题;
//从常识上来讲int既然能装下它的最小值,那也应该能装下它最小值的绝对值
int x = int.MinValue; //获取int最小值
int y = -x; //取负,并赋值给y
Console.WriteLine(x); //打印出x 值为-2147483648;
Console.WriteLine(y); //打印出y 值为-2147483648;
为什么会一样呢?,这是因为y值已经溢出了,我们可以使用checked来检查一下
int x = int.MinValue;
int y = checked( -x);//运行不下去,编译器会报错System.OverflowException:“Arithmetic operation resulted in an overflow.”
//这个值溢出了
Console.WriteLine(x);
Console.WriteLine(y);
为什么会溢出呢?,这个时候我们可以使用求反操作符~,
~,求反操作符,功能是在一个结构数在二进制级别上进行按位取反(意思就是,0就变为1,1就变为0,二进制)
示例如下:
int x = 12345678;
int y =~x;
Console.WriteLine(y);//y打印值为:-12345679
//这是为什么呢?,我们可以再来看看y和x二进制值
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
//可以看出,在y是x按位取反的数
那为什么,x取反的绝对值会比x大一呢?,这是因为计算机当中取相反数是按位取反再加一这个时候我们可以看一下先前int.MinValue值取反出错的操作了
int x = int.MinValue; //获得int最小值
int y = -x;//取反
Console.WriteLine(y);//值输出=x
string xStr = Convert.ToString(x, 2).PadLeft(32, '0');//查看x的二进制
// x的值为10000000000000000000000000000000
//按位取反01111111111111111111111111111111
//根据规则,取反等于按位取反加一,所以
//取反值为10000000000000000000000000000000
//所以y值输出为x
Console.WriteLine(xStr);
示例:
bool b1 = true;
bool b2 = !b1; //取非
Console.WriteLine(b2) //输出False
实际应用示例
class Program
{
static void Main(string[] args)
{
Student 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("inittName cannot be null or empty");
}
}
public string Name;
}
int x = 100;
int x1 = x++; //后置自增
int x2 = ++x;//前置自增
int x3 = x--;//后置自减
int x4 = --x;//前置自减
Console.WriteLine(x1);//输出值为101
Console.WriteLine(x2);//输出值为101
Console.WriteLine(x3);//输出值为99
Console.WriteLine(x4);//输出值为99
可见在单独使用前置自增(前置自减)时与单独使用后置自增(后置自减)时无区别;
那区别是什么呢?我们先回顾一下前置自增(自减)
int x = 100;
int y = x++;//当后置自增前面是赋值符号时,会原先将自己的值赋值给变量,然后再自增
Console.WriteLine(x); //输出值为101
Console.WriteLine(y);//输出值为100
可以看出,当后置自增(自减)要赋值时,会优先赋值给变量,然后再自增(自减)
那前置自增(自减)呢
int x = 100;
int y = --x;//当前置自增(自减)前面是赋值符号时,会先自增(自减)后再将值赋值给变量
Console.WriteLine(x); //输出值为101
Console.WriteLine(y);//输出值为101
Console.WriteLine(ushort.MaxValue);//查看ushort类型的最大值,其值为65535
uint x = 65536; //给类型为uint的变量赋值为65536(比ushort所能存储的最大值大一)
/*
ushort y=x //这里这样直接赋值不行,会出错我们需要cast强制将x转换成ushort然后赋值给y
*/
ushort y = (ushort)x;//使用cast,即(T)x的方式强制将x转换为ushort类型,然后赋值给y
Console.WriteLine(y);//输出y,值为零
计算机算术运算符和数学当中的算术运算符但是有三点需要注意
int x = 1; //int数据类型
int y = 2;//int数据类型
int z = x + y; //int数据类型加int数据类型
Console.WriteLine(z); //输出值为两个变量相加.结果为3
string a = "1";//string类型
string b = a + y;//string类型加int类型
Console.WriteLine(b);//输出值为两个字符串拼接.结果为12
2.C#中有取余这个操作符,在数学运算当中是没有的
示例如下:
int x = 3; ;
int y = 4;
int z = x%y; //取余
Console.WriteLine(z);//余数为3
因为double类型的精度要比int类型的精度更高所以int类型向double进行转换的时候不会丢失精度,
所有当两个不同数值类型进行算数运算时,精度低的会向精度高的类型隐式转换
int a = 3;
double b = 4;
//乘法
var c = a * b;
//除法
var d = a / b;
//求余
var e = a % b;
//加
var f = a + b;
//减
var g = a - b;
Console.WriteLine(c.GetType().FullName);//输出double
Console.WriteLine(d.GetType().FullName);//输出double
Console.WriteLine(e.GetType().FullName);//输出double
Console.WriteLine(f.GetType().FullName);//输出double
Console.WriteLine(g.GetType().FullName);//输出double
需要注意的是当两个类型精度一样但表示范围不一样的两个类型进行运算的时候,是两个类型都向精度高的类型隐式转换
示例如下:(这里只示例加法)
int x = 100;
uint y = 200;
var z = x + y;
Console.WriteLine(z.GetType().FullName);//输出int64,也就是长整型long
而且当两个精度最高的不同类型之间不能发生隐式转换
long x = 100;
ulong y = 200;
var z = x + y;//报错运算符"+"对于long和ulong类型的操作具有二义性
Console.WriteLine(z.GetType().FullName);
提升
当我们将一个int类型的数字使用(T)x也就是cast方法强制将其转换为double方法,那么运算符号会进行那种方式运算
var x = (double)5 / 4;
var y = (double)(5 / 4);
var z = 5 / (double)4;
Console.WriteLine(x);//输出1.25 //进行浮点除法运算
Console.WriteLine(y);//输出1 //进行整数除法运算
Console.WriteLine(z);//输出1.25 //进行浮点除法运算
对于 x * y 形式的运算,应用二元运算符重载决策(第 7.3.4 节)以选择特定的运算符实现。操作数转换为所选运算符的参数类型,结果的类型是该运算符的返回类型。
下面列出了预定义的乘法运算符。这些运算符均计算 x 和 y 的乘积。
int operator *(int x, int y)
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
在 checked 上下文中,如果积超出结果类型的范围,则引发 System.OverflowException。在 unchecked 上下文中,不报告溢出并且结果类型范围外的任何有效高序位都被放弃。
示例如下(这里只列举int类型溢出)
unchecked
{
//这里就不使用checked来检测溢出了我们知道int.MaxValue*5是肯定溢出的,我们来看它溢出的是什么
int x = int.MaxValue;//获取intMax值
int y = 5;
int intZ = x * y;//使用int类型接收这个超出int最大结果范围的数
string stringIntZ = Convert.ToString(intZ, 2).PadLeft(64, '0');//查看这个数的二进制,不足64位使用0补足
long longZ = (long)x * (long)y;//使用long接收int最大值乘2,long是长整型64位肯定能接收下这个数
string stringLongZ = Convert.ToString(longZ, 2).PadLeft(64,'0');//查看这个数的二进制,不足64位使用0补足
Console.WriteLine(intZ); //输出2147483643
Console.WriteLine(longZ);//输出10737418235
Console.WriteLine(stringIntZ); //输出//0000000000000000000000000000000001111111111111111111111111111011
Console.WriteLine(stringLongZ);//输出//0000000000000000000000000000001001111111111111111111111111111011
Console.WriteLine(Convert.ToString(intZ,2));//输出 1111111111111111111111111111011
//为了更直观,我这里输出一个没有使用0补足的intZ的二进制
}
由上面可以看出在unchecked上下文中当值超出结果类型的范围时,C#会将超出的高序位抛弃,并且,由于int long这一类能表示正负数的类型中,最高位是用来表示正负符号的,而因为C#抛弃的是高序位,这就会导致正负符号直接被抛弃,编译器会在剩下的值的最高位当作正负符号,所以,有些时候溢出数据输出的值会有些离谱比如说int.MaxValue*2输出的就是一个负数
float operator *(float x, float y);
double operator *(double x, double y);
根据 IEEE 754 算术运算法则计算乘积。下表列出了非零有限值、零、无穷大和 NaN 的所有可能组合的结果。在该表中,x 和 y 是正有限值,z 是 x * y 的结果。如果结果对目标类型而言太大,则 z 为无穷大。如果结果对目标类型而言太小,则 z 为零。
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
---|---|---|---|---|---|---|---|
+x | +z | –z | +0 | –0 | +∞ | –∞ | NaN |
–x | –z | +z | –0 | +0 | –∞ | +∞ | NaN |
+0 | +0 | –0 | +0 | –0 | NaN | NaN | NaN |
–0 | –0 | +0 | –0 | +0 | NaN | NaN | NaN |
+∞ | +∞ | –∞ | NaN | NaN | +∞ | –∞ | NaN |
–∞ | –∞ | +∞ | NaN | NaN | –∞ | +∞ | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator *(decimal x, decimal y);
如果结果值太大,不能用 decimal 格式表示,则将引发 System.OverflowException。如果结果值太小,无法用 decimal 格式表示,则结果为零。在进行任何舍入之前,结果的小数位数是两个操作数的小数位数的和。
小数乘法等效于使用 System.Decimal 类型的乘法运算符。
对于 x / y 形式的运算,应用二元运算符重载决策(第 7.3.4 节)以选择特定的运算符实现。操作数转换为所选运算符的参数类型,结果的类型是该运算符的返回类型。
下面列出了预定义的除法运算符。这些运算符均计算 x 和 y 的商。
int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);
如果右操作数的值为零,则引发 System.DivideByZeroException 导常。
示例如下:
int x = 5;
int y = 0;
int z = x / y;
Console.WriteLine(z);
//未经处理异常System.DivideByZeroException
//HResult=0x80020012
//Message=Attempted to divide by zero.
//Source=ConsoleApp1
//StackTrace:
//at ConsoleApp1.Program.Main(String[] args) in E:\ProgramData\Visual Studio\ConsoleApp1\ConsoleApp1\Program.cs:line 13
除法将结果舍入到零。因此,结果的绝对值是小于或等于两个操作数的商的绝对值的最大可能整数。当两个操作数符号相同时,结果为零或正;当两个操作数符号相反时,结果为零或负。
如果左操作数为最小可表示 int 或 long 值,右操作数为 –1,则发生溢出。在 checked 上下文中,这会导致引发 System.ArithmeticException(或其子类)。在 unchecked 上下文中,它由实现定义为或者引发 System.ArithmeticException(或其子类),或者不以左操作数的结果值报告溢出。
float operator /(float x, float y);
double operator /(double x, double y);
根据 IEEE 754 算法法则计算商。下表列出了非零有限值、零、无穷大和 NaN 的所有可能组合的结果。在该表中,x 和 y 是正有限值,z 是 x / y 的结果。如果结果对目标类型而言太大,则 z 为无穷大。如果结果对目标类型而言太小,则 z 为零。
示例
double x = 5;
double y = 0;
double z = x / y;
Console.WriteLine(z);//输出正无穷大
double a = double.PositiveInfinity;//正无穷大
double b = double.NegativeInfinity;//负无穷大
double c = a / b;
Console.WriteLine(c);//输出NaN
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
---|---|---|---|---|---|---|---|
+x | +z | –z | +∞ | –∞ | +0 | –0 | NaN |
–x | –z | +z | –∞ | +∞ | –0 | +0 | NaN |
+0 | +0 | –0 | NaN | NaN | +0 | –0 | NaN |
–0 | –0 | +0 | NaN | NaN | –0 | +0 | NaN |
+∞ | +∞ | –∞ | +∞ | –∞ | NaN | NaN | NaN |
–∞ | –∞ | +∞ | –∞ | +∞ | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator /(decimal x, decimal y);
如果右操作数的值为零,则引发 System.DivideByZeroException 导常。如果结果值太大,不能用 decimal 格式表示,则将引发 System.OverflowException。如果结果值太小,无法用 decimal 格式表示,则结果为零。结果的小数位数是最小的小数位数,它保留等于最接近真实算术结果的可表示小数值的结果。
小数除法等效于使用 System.Decimal 类型的除法运算符。
对于 x % y 形式的运算,应用二元运算符重载决策(第 7.3.4 节)以选择特定的运算符实现。操作数转换为所选运算符的参数类型,结果的类型是该运算符的返回类型。
下面列出了预定义的余数运算符。这些运算符均计算 x 除以 y 的余数。
int operator %(int x, int y);
uint operator %(uint x, uint y);
long operator %(long x, long y);
ulong operator %(ulong x, ulong y);
x % y 的结果是由 x – (x / y) * y 生成的值。如果 y 为零,则将引发 System.DivideByZeroException。
如果左侧的操作数是最小的 int 或 long 值,且右侧的操作数是 -1,则将引发 System.OverflowException。只要 x % y 不引发异常,x / y 也不会引发异常。
float operator %(float x, float y);
double operator %(double x, double y);
下表列出了非零有限值、零、无穷大和 NaN 的所有可能组合的结果。在该表中,x 和 y 是有限的正值。z 是 x % y 的结果,按照 x – n * y 进行计算,其中 n 是小于或等于 x / y 的最大可能整数。这种计算余数的方法类似于用于整数操作数的方法,但不同于 IEEE 754 定义(在此定义中,n 是最接近 x / y 的整数)。
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
---|---|---|---|---|---|---|---|
+x | +z | +z | NaN | NaN | x | x | NaN |
–x | –z | –z | NaN | NaN | –x | –x | NaN |
+0 | +0 | +0 | NaN | NaN | +0 | +0 | NaN |
–0 | –0 | –0 | NaN | NaN | –0 | –0 | NaN |
+∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
–∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator %(decimal x, decimal y);
如果右操作数的值为零,则引发 System.DivideByZeroException 导常。在进行任何舍入之前,结果的小数位数是两个操作数中较大的小数位数,而且结果的符号与 x 的相同(如果非零)。
小数余数等效于使用 System.Decimal 类型的余数运算符。
对于 x + y 形式的运算,应用二元运算符重载决策(第 7.3.4 节)以选择特定的运算符实现。操作数转换为所选运算符的参数类型,结果的类型是该运算符的返回类型。
下面列出了预定义的加法运算符。对于数值和枚举类型,预定义的加法运算符计算两个操作数的和。当一个或两个操作数为 string 类型时,预定义的加法运算符把两个操作数的字符串表示形式串联起来。
int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);
在 checked 上下文中,如果和超出结果类型的范围,则引发 System.OverflowException。在 unchecked 上下文中,不报告溢出并且结果类型范围外的任何有效高序位都被放弃。
float operator +(float x, float y);
double operator +(double x, double y);
根据 IEEE 754 算术运算法则计算和。下表列出了非零有限值、零、无穷大和 NaN 的所有可能组合的结果。在该表中,x 和 y 是非零有限值,z 是 x + y 的结果。如果 x 和 y 的绝对值相同但符号相反,则 zz 为正零。如果 x + y 太大,不能用目标类型表示,则 z 是与 x + y 具有相同符号的无穷大。
示例如下
int x = 9;
int y = x << 2;//使用位移操作符将x的二进制数据向左位移两位
string strX = Convert.ToString(x, 2).PadLeft(32,'0');//查看x的二进制
string strY = Convert.ToString(y, 2).PadLeft(32,'0');//查看y的二进制
Console.WriteLine(strX);//输出"00000000000000000000000000001001"
Console.WriteLine(strY);//输出"00000000000000000000000000100100"
规则:左移,不管移的是正数还是负数不位的都是零,右移,当你是操作正数右移,那么最高位补的是0,操作的是负数的话最高位补的是1(因为像int,long这样的数据类型最高位是符号位,0代表正数,1代表负数)
在不溢出的情况下,每向左移一位,数值乘二,右移反之
y = x << z;
x位需要位移的数值
z为x需要位移的位数
y等于x乘2的z次幂
(>>右移符号反之)
y = x ∗ 2 z y=x*2^{z} y=x∗2z
所有关系运算符产生的结果都是bool类型
int x = 5;
double y = 6;
var z = x >= y;//比较x是否大于等于y
Console.WriteLine(z.GetType().FullName);//输出bool
Console.WriteLine(z);//false
关系操作符还能比较字符类型;
char a = 'a';
char b = 'A';
var c = a > b;
Console.WriteLine(c);//输出True
为什么会输出true呢?我们知道char是四个字节16位bit位用正数来存储是 Unicode 字符编码,
我们可以查看一下a和A的编码
char a = 'a';
char b = 'A';
var c = a > b;
Console.WriteLine(c);
ushort ua = (ushort)a;
ushort ub = (ushort)b;
Console.WriteLine(ua);//输出97
Console.WriteLine(ub);//输出65
可以看出a的编码比A的编码大,所以a>A是True
关系操作符还可以用来操作字符串类型的数据,但只能比较是否相同不能比较大小
string strA = "abc";
string strB = "ABC";
var c = strA == strB;
Console.WriteLine(c);//输出False///因为字符大小不一样
class Program
{
static void Main(string[] args)
{
Son s = new Son();
var result = s is Son; //这里所检验的不是这个变量,而是这个变量所引用的实例
Console.WriteLine(result);//输出true
//检验看看是不是说 is所检验的不是引用变量的类型而是引用变量的实例
Son s1 = null;//给s1 null
var result1 = s1 is Son;//检验
Console.WriteLine(result1);//输出flase 说明is所检验的不是引用变量的类型
var result2 = s is Father;//我们试试输入它的父类
Console.WriteLine(result2);//输出true,这就好比说,你是一个男人,你也是一个人,这没毛病老铁
}
}
class Grandfather//创建一个基类
{
public void Grandfathers()
{
Console.WriteLine("这是Grandfather");
}
}
class Father : Grandfather//继承Father
{
public void Fathers()
{
Console.WriteLine("这是Father");
}
}
class Son : Father//继承Father
{
public void Sons()
{
Console.WriteLine("这是Son");
}
}
示例如下
class Program
{
static void Main(string[] args)
{
object o = new Son();//给引用变量o引用son实例
Son s = o as Son;//o判断是不是和son一样的,如果是就将son实例引用地址给s
if (s !=null)
{
s.Sons();
}
}
}
class Grandfather//创建一个基类
{
public void Grandfathers()
{
Console.WriteLine("这是Grandfather");
}
}
class Father : Grandfather//继承Father
{
public void Fathers()
{
Console.WriteLine("这是Father");
}
}
class Son : Father//继承Father
{
public void Sons()
{
Console.WriteLine("这是Son");
}
}
示例
int x = 5;
int y = 6;
int z = x & y;
int z1 = x ^ y;
int z2 = x | y;
Console.WriteLine(" x:"+Convert.ToString(x ,2).PadLeft(32,'0')); //"00000000000000000000000000000101"
Console.WriteLine(" y:"+Convert.ToString(y ,2).PadLeft(32,'0')); //"00000000000000000000000000000110"
Console.WriteLine(" z:"+Convert.ToString(z ,2).PadLeft(32,'0')); //"00000000000000000000000000000100"
//是非是
//是是非
//是非非
Console.WriteLine(" x:"+Convert.ToString(x ,2).PadLeft(32,'0')); //"00000000000000000000000000000101"
Console.WriteLine(" y:"+Convert.ToString(y ,2).PadLeft(32,'0')); //"00000000000000000000000000000110"
Console.WriteLine("z1:"+Convert.ToString(z1,2).PadLeft(32,'0')); //"00000000000000000000000000000011"
//是非是
//是是非
//非是是
Console.WriteLine(" x:"+Convert.ToString(x ,2).PadLeft(32,'0')); //"00000000000000000000000000000101"
Console.WriteLine(" y:"+Convert.ToString(y ,2).PadLeft(32,'0')); //"00000000000000000000000000000110"
Console.WriteLine("z2:"+Convert.ToString(z2,2).PadLeft(32,'0')); //"00000000000000000000000000000111"
//是非是
//是是非
//是是是
&&需要注意的是它的短路规则
int x = 5;
int y = 4;
int z = 3;
if (z > y && z++ > 3)//z>y是false所以不通过,并且因为条件与存在短路规格所以
//当z>y为false时z++>3就不会再运行了
//&&需要两边条件都成立,当一边条件不成立,那么另一半也就没有再运行的必要了,所以这个时候z还是等于3并没有自增
{
Console.WriteLine("Hello.第一次Z的值是"+z);
}
else if(x > y && z++ > 3)//x>y为true继续z++>3,因为z++是先使用值再自增,所以这里是false,但z++运行了所以z现在的值是4
{
Console.WriteLine("Hello.第二次Z的值是" + z);
}
else if(x > y && z++ > 3)//x>y为true,z++>3为true(因为上面虽然没有通过,但z在比较了之后自增了所以这里z=4>3),运行完之后z再一次自增变为5
{
Console.WriteLine("Hello.第三次Z的值是" + z);//输出z的值5
}
else
{
Console.WriteLine("Hello.第四次Z的值是" + z);
}
||也有类似的短路规则
int x = 5;
int y = 4;
int z = 3;
if (x > y || z++ > 3)//x>y为true因为是条件或所以后面的条件会被编译器短路,也就是说z++>3不会运行z也就还是3
{
if (z > y || z++ > 3)//z>y为false编译器继续运行z++>3因为上面z++>3没有运行所以z现在还是等于3而且后置自增是在值使用之后再加一,所以这里z++>为false,之后代买z++再自增,此时z值为四
{
Console.WriteLine("地一轮Z值为"+z);
}else if (z > y || z++ > 3)//z>y为false,z++>3为true z++之后再自增值为五
{
Console.WriteLine("第二轮Z值为"+z);//输出z,其值为5
}
}
我们知道再c#当中int值是没有null的,
int studentA = null //这是错误的
但现实中我们却需要int有时候值是null
比如说一个同学的成绩,他还没交作业,这个时候,我们给他什么分都是错误的,因为他还没考,我们也不能给他0分,万一人家等下交上来之后分很高呢?这个时候我们就需要使用到Nullable关键字了
Nullable<int> studentA = null;//这个时候int值可以使用null值了
int? studentB = null;//也可以这样写,它们的意义是一样的
当我需要批量更改int值为null的时候,我们就可以使用??Null值合并操作符了
int? studentB = null;//也可以这样写,它们的意义是一样的
studentB=studentB??2//
条件操作符是所有操作符中唯一一个可以操控三个操作数的操作符,本质上是if else分支的一个简写
示例,我们先看一个if else的代码
int x = 80;
string str = string.Empty;
if (x >= 60)
{
str = "及格";
}
else
{
str = "不及格";
}
Console.WriteLine(str);
从上面可以看出if sles占了很多空间,这个时候我们可以使用?:来简写这个语句,使代码看起来更简洁
int x = 80;
string str = string.Empty;
str = x > 60 ? "及格" : "不及格";
Console.WriteLine(str);
//而且我们甚至可以更简洁一点,这是这种的一般用不上
int x = 80;
string str = x > 60 ? "及格" : "不及格";
Console.WriteLine(str);
这个实在是没啥好写的就写一个例子吧,其他的和这个使用方法一样
int x = 5;
x = x + 1;
int y = 5;
y += 1;
Console.WriteLine(x);//输出6
Console.WriteLine(y);//输出6
说白了就是将赋值操作符和运算操作符绑一起了
y+=1;就相当于y=y+1;
有一点需要注意的就是赋值操作符的运算顺序是从右至左的
int x = 3;
int y = 4;
int z = 5;
int a = x += y *= z;
Console.WriteLine(a);//输出的值是二十三
有了上面的这些知识之后,我们就可以为我们的函数编写更加复杂的逻辑了真开心哎,嘤嘤嘤