操作符概念
- 操作符(Operator)也译为“运算符”
- 操作符是用来操作数据的,被操作符操作的数据称为操作数(Operand)
注:数值类型提升
操作符的本质
- 操作符的本质是函数(即算法)的“简记法”
1)假如没有发明“+”,只有Add函数,算式3+4+5将可以写成Add(Add(3,4),5)
2)假如没有发明“×”,只有Mul函数,算式3+4×5将只能写成Add(3,Mul(4,5)),注意优先级 - 操作符不能脱离与它关联的数据类型
1)可以说操作符就是与固定数据类型相关联的一套基本算法的简记法
int a = 5;
int b = 3;
int c = a / b;
Console.WriteLine(c); //输出结果为1
double a = 5.0;
double b = 3.0;
double c = a / b;
Console.WriteLine(c); //输出结果为1.75
2)示例:为自定义数据类型创建操作符
优先级与运算顺序
- 操作符的优先级
1)可以使用圆括号提高被括起来表达式的优先级
2)只有圆括号可以嵌套
3)不像数学里有方括号和花括号,在C#语言中“[]”和“{}”有专门的用途 - 同优先级操作符的运算顺序
1)除了带有赋值功能的操作符,同优先级操作符都是由左向右进行运算
2)带有赋值功能的操作符的运算符的运算顺序是由右向左
3)与数学运算不同,计算机语言的同优级级运算没有“结合律”
3+4+5只能理解为Add(Add(3,4),5)不能理解为Add(3,Add(4,5))
各类操作符的示例
- 基本操作符
基本运算符是组成基本表达式的运算符,即不能再被拆分的表达式。
1)成员访问操作符:x . y
功能:访问外层名称空间中的子集名称空间;访问名称空间中的类型;访问类型的静态成员;访问对象的成员。
举例:
System.IO //访问外层名称空间中的子集名称空间
System.IO.File //访问IO名称空间中的File类型
System.IO.File.Create("D:\\HelloWorld.txt");
//访问File类型的静态成员Create方法
Form myForm = new Form();
myForm.Text = "Hello, World!" //访问myForm对象的Text属性
myForm.ShowDialog(); //访问myForm对象的ShowDialog方法
2)方法调用操作符:f ( x )
f:function的缩写,方法的名称;x:调用的参数。
注:一般,在方法名的后面加一对 () 来实现方法的调用。但是,在委托类型里面不需要加 () ,它是让间接地完成方法的调用。例子如下,
Calculator c = new Calculator();
Action myAction = new Action(c.PrintHello); //委托myAction来管理PrintHello方法
myAction(); //调用被它管理方法
3)元素访问操作符:a [ x ]
功能:访问集合中的元素。
举例:
(1)访问数组中的元素,[ 整数 ]
初始化数组
int[] myintArray = new int[10]; //数组长度为10
int[] myintArray = new int[] {1, 2, 3, 4}; //通过花括号直接给出数组的元素
int[] myintArray = new int[10] {1, 2, 3, 4}; 不能这样写,系统不会默认添0
访问数组中的元素
myArray[3] //访问最后一个元素
等价于myArray.Length-1
注:花括号{}被称为初始化器
(2)访问字典中的元素,[ 不一定是整数 ]
类名< > :泛型类,它本身不是一个完整的类,需要和其他类型组合在一起形成完整的类。例如,Dictionary<string, Student>,其中string作为索引的类型,Student作为值的类型。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Dictionary<string, Student> stuDic = new Dictionary<string, Student>(); //泛型类:类名<>,其中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;
}
}
4)后置的自增和自减:x ++,x –
x++
等价于x = x + 1
,x--
等价于x = x - 1
举例:
int x = 100;
int y = x++; 等价于 int y = x; x = x + 1;
5)typeof操作符
功能:查看类型的内部结构(Metadata)
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);
6)default 操作符
(1)default 的操作对象是值类型
结构体类型
int x = default(int); //结果:x = 0
double y = default(double); //结果:y = 0
枚举类型(注意)
enum Level
//枚举类型在声明时编译器会将其与整数对应起来
//第一个对应0,依次递增加1
//因此,Level level = default(Level)默认将整数0对应的内容返回
{
Low, //0
Mid, //1
High //2
}
enum Level
//倘若枚举类型在声明时采用显示赋值
//则 Level level = default(Level)返回的是整数0对应的内容
//如果任何元素都没有赋值0,则default(Level)返回整数0
{
Low = 1,
Mid = 0,
High = 2
}
(2)default 的操作对象是引用类型
Form myForm = default(Form);
Console.WriteLine(myForm == null); // 结果:ture
7)new
(1)关键字 var :声明隐式类型变量,根据所赋的值来判断变量的类型
var x = 100; //计算机根据100,判断出x的类型是整型,并且后续给x赋值必须为整型
int x = 100; //显示变量,直接给出了变量的类型
(2)new 操作符
需要new操作符
**类类型
new Form();
//创建Form类型的实例,通过()调用实例构造器
//获得实例的内存地址
Form myForm = new Form(){Text = "Hello"};
//通过{}调用实例的初始化器
new Form{} {Text = "Hello"}.ShowDialog();
不需要new操作符(C#把new隐藏起来了)“语法糖衣”
**基本类型
int x = 100;
string name = "Tim";
数组类型
int[] myArray = new int[10];
int myArray = {1,2,3,4}; //使用了“语法糖衣”
**为匿名类型创建对象
var person = new{Name = "Tim", Age = 34};
//new操作符为匿名类型创建对象,用var关键字创建隐式变量来引用该实例
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
Console.WriteLine(person.GetType().Name); //查看匿名类型的名称
注:new操作符功能强大不能乱用,在编写大型程序时,为了避免new操作符造成的紧耦合,可以使用一种依赖注入的设计模式。
(3)new 修饰符
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student(); //new操作符
stu.Report();
CsStudent CsStu = new CsStudent(); //new操作符
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 a CS student.");
}
}
}
8)checked 和 unchecked
功能:checked 检查一个值在内存中是否有溢出;unchecked 不用检查是否溢出
**操作符用法
static void Main(string[] args)
{
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
try
{
uint y = checked(x + 1); //检查是否有溢出,如果有,则跳入catch语句
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("There's overflow!");
}
}
**上下文用法
static void Main(string[] args)
{
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 ex)
{
Console.WriteLine("There's overflow!");
}
}
9)delegate
功能:声明委托的数据类型。可以用作操作符(已经过时)或者修饰符
**通过 += 操作符将事件挂接到事件处理器上
public MainWindow()
{
InitializeComponent();
this.myButton.Click += MyButton_Click; //事件通过+=操作符挂接到事件处理器上
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
this.myTextBox.Text = "Hello, World!";
}
**通过delegate操作符声明匿名方法(已经过时)
public MainWindow()
{
InitializeComponent();
this.myButton.Click += delegate (object sender, RoutedEventArgs e)
//用delegate操作符声明匿名方法已经过时
{
this.myTextBox.Text = "Hello, World!";
};
}
**用 => 代替 delegate 操作符(目前常用的)
public MainWindow()
{
InitializeComponent();
this.myButton.Click += (sender, e) =>
//编译器可以自动推断数据类型
//“=>”表示将sender和e这两个变量代入到下面的函数体内
{
this.myTextBox.Text = "Hello, World!";
};
}
10)sizeof
功能:获取对象在内存中所占字节数
注意:默认情况下,sizeof 只能用于获取基本数据类型(C#关键字里面除了string和operator的数据类型,因为这两个不是结构体数据类型)的实例在内存中所占的字节数;在非默认情况下,sizeof 可以去获取自定义结构体的实例在内存中占的字节数,但是需要把它放在不安全的上下文中。
**默认情况下
int x =sizeof(double);
int y = sizeof(decimal);
//decimal(16字节)能够获取比double(8字节)更加精确的数据,一般用在金融方面
**非默认情况下
namespace OperatorsExample
{
class Program
{
static void Main(string[] args)
{
unsafe //存放在不安全的上下文中
{
int x = sizeof(Student);
Console.WriteLine(x);
}
}
}
struct Student
{
int ID;
long Score;
}
}
11)-> 操作符(只能放在不安全的上下文中使用)
namespace OperatorsExample
{
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;
}
}
- 一元操作符(单目操作符)
在操作符的后面跟一个操作数构成表达式
1)& x,* x(两者都需要不安全的上下文)
&:取地址操作符; *:取引用符
unsafe
{
Student stu;
stu.ID = 1;
stu.Score = 99;
Student* pStu = &stu; //用&获取stu的地址
pStu->ID = 2;
(*pStu).Score = 100; //.的优先级更高,所以需要()来改变运算顺序,*是取内容
}
2)+,-,~ 操作符
**与数学运算符一致的功能
int x = 100;
int y = -(-x);
**与数学运算符不一致的功能
对正数取相反数时,在数学中y = -x
,而计算机语言,二进制的正数求相反数按位取反 + 1
3)! 操作符
功能:只能用来操作 bool 类型的值(true、false)
class Program
{
static void Main(string[] args)
{
Student stu = new Student(null);
Console.WriteLine(stu.Name);
}
}
class Student
{
public string Name;
public Student(string initName)
{
if (!string.IsNullOrEmpty(initName)) //!操作符在现实工作中的应用
{
this.Name = initName;
}
else
{
throw new ArgumentException("initName cannot be null or empty.");
};
}
}
4)++ x,– x
功能:在单独使用时,++x (–x)和 x++(x–) 得到的结果相同;但是,如果有赋值操作符时
int x = 100;
int y = x++; //结果:x=101,y=100 先赋值后自增
int y = ++x; //结果:x=101,y=101 先自增后赋值
5)( T ) x
功能:强制类型转换
(1)隐式(implicit)类型转换
参考《CSharp Language Specification》的 6.1节隐式转换
- 不可丢失精度的转换
int x = int.MaxValue;
long y = x;
Console.WriteLine(y);
- 子类向父类的转换
Human h = t;
即完成了由子类向父类的转换。
虽然,h 引用了 Teacher 的实例,但是在访问该实例时,所用的是 Human 类型的变量 h,而 Human 类型变量里面只有 Eat 和 Think 方法,所以在引用时只有这两个方法。
class Program
{
static void Main(string[] args)
{
Teacher t = new Teacher();
Human h = t; //子类向父类转换
Animal a = h; //子类向父类转换
a.Eat();
}
}
class Animal
{
public void Eat()
{
Console.WriteLine("Eating……");
}
}
class Human : Animal
{
public void Think()
{
Console.WriteLine("Who am I ?");
}
}
class Teacher : Human
{
public void Teach()
{
Console.WriteLine("I teach programming.");
}
}
- 装箱
链接: link.
(2)显式(explicit)类型转换
参考《CSharp Language Specification》的 6.2节显式转换
- 有可能丢失精度(甚至发生错误)的转换,即cast(铸造)
Console.WriteLine(ushort.MaxValue);
uint x = 65536;
ushort y = (ushort)x; //强制类型转换
Console.WriteLine(y); // y = 0
注意:值的大小和正负
- 拆箱
- 使用 Convert 类
方案1:使用 Convert 类的 Tostring 方法
方案2:直接调用数值数据的实例方法Tostring
private void btn_Click(object sender, RoutedEventArgs e)
{
double x = System.Convert.ToDouble(tb1.Text);
double y = System.Convert.ToDouble(tb2.Text);
double result = x + y;
this.tb3.Text = System.Convert.ToString(result); //方案1:使用Convert类的Tostring方法
this.tb3.Text = result.ToString(); //方案2:直接调用数值数据的实例方法Tostring
}
- ToString方法与各数据类型的Parse/TryParse方法
double x = double.Parse(this.tb1.Text); //Parse()只能解析格式正确的字符串数据类型
double y = double.Parse(this.tb2.Text);
TryParse() 方法的返回类型是布尔类型(转换成功:true,转换失败:false),该方法通过输出参数将解析好的值返回。
(3)自定义类型转换操作符
- 示例
显式类型转换:
class Program
{
static void Main(string[] args)
{
Stone stone = new Stone();
stone.Age = 5000;
Monkey wukongSun = (Monkey)stone; //1. 隐式类型转换:把 (Monkey) 删掉
Console.WriteLine(wukongSun.Age);
}
}
class Stone
{
public int Age;
public static explicit operator Monkey(Stone stone)
//2. 隐式类型转换:把 explicit 改成 implicit
{
Monkey m = new Monkey();
m.Age = stone.Age / 500;
return m;
}
}
class Monkey
{
public int Age;
}
- 算数运算操作符(*,/,%,+,-)
操作符针对不同的数据类型有不同的运算规则。
1)取余操作符:%
浮点类型取余
double x = 3.5;
double y = 3;
Console.WriteLine(x % y); //取余结果:0.5
-
位移操作符(<<,>>)
数据在内存中二进制的结构向左或向右进行一定位数的平移。
注1:在不产生溢出的情况下,左移即是给数据乘 2,右移即是给数据除 2。
注2:左移,无论是正数还是负数补进来的均是 0;右移,正数最高位补进来的是 0,负数最高位补进来的是 1。 -
关系(< > <= >=)和类型检测(is as)
所有关系运算符得到的结果均是bool类型。
1)is
功能:检验某个对象是否是某个类型的对象,输出结果为 bool 类型
class Program
{
static void Main(string[] args)
{
Teacher t = new Teacher();
var result1 = t is Human; // is 操作符
Car car = new Car();
var result2 = car is Object; // 结果:Ture,因为所有类都派生于Object类
Console.WriteLine(result1.GetType().FullName);
Console.WriteLine(result1);
}
}
class Animal
{
public void Eat()
{
Console.WriteLine("Eating……");
}
}
class Human : Animal
{
public void Think()
{
Console.WriteLine("Who am I ?");
}
}
class Teacher : Human
{
public void Teach()
{
Console.WriteLine("I teach programming.");
}
}
class Car
{
public void Run()
{
Console.WriteLine("Running...");
}
}
2)as
object o = new Teacher();
Teacher t = o as Teacher;
//判断 o 和 Teacher 是否一样。如果一样,则将对象 o 和它的地址交给 t,否则返回 null
if(t!=null)
{
t.Teach();
}
- 逻辑运算符
1)按位求与 x & y
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);
Console.WriteLine(strY);
Console.WriteLine(strZ);
2)按位或: x | y
int x = 7;
int y = 28;
int z = x | y;
3)按位异或:x ^ y
- 条件运算符
操作对象是bool类型的值,得到的结果仍是bool类型
1)条件与:&&(并且)
操作符左右两边都是ture时,得到的结果才是ture,否则为false。
2)条件或:|| (或)
只要操作符一边是ture,得到的结果就是ture。
注:尽量避开条件与和条件或的短路逻辑!!!
- null合并操作符(??)
可空数据类型(在数据未写入之前占据一定大小的空位)
Nullable<int> x = null; //或者 int? x = null
x = 100;
Console.WriteLine(x);
Console.WriteLine(x.HasValue); //HasValue属性,判断x内是否有值,有ture,无false
其中,Nullable<int>
被C#吸收为int?
int? x = null;
int y = x ?? 1; //如果x是null,则用1来代替
- 条件操作符(?:)
int x = 80;
string str = (x>=60) ? "Pass" : "Failed";
- 赋值和lambda表达式