C#基础操作符详解(上)
本节内容:
1.操作符概览;
2.操作符的本质;
3.操作符与运算顺序
4.操作符详解。
1.操作符概览:
操作符(Operator)也译为”运算符”
操作符是用来操作数据的,被操作符操作的数据称为操作数(Operand)
表格从上往下优先级递减,同一行运算符的优先级一样一般按从左到右算,
“=”赋值操作符,是先运算右边的值再运算左边的值,所以是最后运算的。
2.操作符的本质
①操作符的本质是函数(即算法)的”简记法”
假如没有发明”+”只有Add函数,算式3+4+5将可以写成Add(Add(3,4),5)
假设没有发明”*”只有Mul函数,那么算式3+4*5将只能写成Add(3,Mul(4,5))
可见有操作符可读性更强。
②操作符不能脱离与它关联的数据类型(比如double数据类型的除法与int类型的除法相同数据结果不同)
可以说操作符就是与固定数据相关联的一套基本算法的简记法。
示例:为自定义的数据类型创建操作符。(格式为把方法名字改为”operator 想要定义的操作符”如:”operator +”)如下例子进一步说明了C#里面的操作符就是方法,也就是函数的一个简记法。
class Person { public string Name; //public static List<Person>GetMary(Person p1, Person p2)(一般方法自定义操作符之前) 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 child"; people.Add(child); } return people; } }
3.操作符与运算顺序
①操作符的优先级
可以使用圆括号提高被括起来的表达式的优先级。
圆括号可以嵌套。
不像数学里面有方括号和花括号,在C#语法中”[]”与”{}”有专门的用途。
②同优先级操作符的运算顺序
除了带有赋值功能的操作符,同优先级操作符都是有左到右进行运算,
带有赋值功能的操作符的运算顺序是由右到左(比如赋值运算符”=”),
与数学运算不同,计算机语言的同优先级运算没有”结合率”:
3+4+5只能理解为Add(Add(3,4),5)不能理解为Add(3,Add(4,5)。
- 操作符详解
4.1基本操作符
①(成员访问操作符)”.”操作符(上表中写为X.Y):四种功能;
*访问命名空间当中的子集命名空间;
*访问名称空间当中的类型;
*访问类型的静态成员(静态成员隶属于类本身故用类可以访问,而用类的对象不能访问类的静态成员);
*访问对象的成员(包括数据成员和方法);
②方法调用操作符”()”即方法后面跟着的那对圆括号(上表写为f(x))。
调用方法一定要加圆括号,但是:
Action myAction = new Action(c.PrintHello);//把PrintHello方法交给委托对象myAction管理
myAction();//这样在委托对象后面加个圆括号就相当于调用了被它管理的方法了,
这个时候PrintHello方法可以不带圆括号。
③元素访问操作符”[]”
int[] myIntArray = new int[13];//创建int数组的实例13个元素
int[] myIntArray2 = new int[]{1,2,3,4,5};//也可以在后面加花括号输入相应值,这对或括号叫做"初始化器",[]里不写数组大小会根据初始化器自动赋值...
myIntArray[0]=2;//访问的是第一个数组元素,访问数组元素,[]里写的是偏移量,从0开始。
Dictionary<string, Student> stuDic = new Dictionary<string, Student>();//一个类名后面,跟着一个尖括号表示这个类是泛型
//泛型是不完整的类如Dictionary<string, Student>在尖括号里要说明索引的类型(string)与值的类型(Student)(顺带一提Dictionary是一个字典类型)
*总结元素访问操作符”[]”里面放的是索引里面不一定是整数,如以下举例。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Dictionary<string, Student> stuDic = new Dictionary<string, Student>();//一个类名后面,跟着一个尖括号表示这个类是泛型 6 //泛型是不完整的类如Dictionary<string, Student>在尖括号里要说明索引的类型(string)与值的类型(Student)(顺带一提Dictionary是一个字典类型) 7 for (int i = 1; i < 100; i++) 8 { 9 Student stu = new Student(); 10 stu.Name = "s_" + i.ToString(); 11 stu.Score = 100+i; 12 stuDic.Add(stu.Name, stu);//把stu放进字典里面,所以为stu.Name,值为stu 13 } 14 Student number6 = stuDic["s_6"];//说明了[]里不一定是整数,而一定是索引 15 Console.WriteLine(number6.Score); 16 } 17 } 18 class Student 19 { 20 public string Name; 21 public int Score; 22 }
④x--与x++:叫做后置的加加和后置的减减:
Int x=100; int y=x++;结果为x=101;y=100;因为x++是先赋值再进行自增;
--x与++x:叫做前置的加加和前置的减减:先进行自增或自减后进行赋值。
⑤typeof()操作符和default()操作符
*typeof操作符的作用为查看变量的种类:
Type t = typeof(int);
Console.WriteLine(t.Namespace);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);
*Default操作符使操作数取默认值:数值型为0,引用型为null,
int x=default(int);//default操作的类型为结构体类型即数值类型时就返回内存块当中为0的值:
Console.WriteLine(x);
输出为0;
Form myForm = default(Form);//default操作数的类型为引用类型时就返回内存块当中为0的值即为null
Console.WriteLine(myForm==null);
输出为true;
当为枚举型enum时: Level level=default(Level);
Console.WriteLine(level);
enum Level
{
Mid,
Low,
High
}
结果为Mid,如果把Mid的位置和Low互换则结果为Low,这是因为当default操作符遇到枚举类型会把它当做数值型来处理,即第一个元素为0,后面的依次+1;
如果这样写:
enum Level
{
Mid=1,
Low=0,
High=2
}则返回值为Low。当用default获取枚举值的时候要小心,如果这样写:
enum Level
{
Mid=1,
Low=3,
High=2
}返回值为0,出错了,所以在设置枚举值时最好给元素一个0的整数值。
先说明:关键字var:帮助生成隐式类型变量:
int x;//显式变量,明确的告诉了编译器x属于什么数据类型;
var y;//隐式变量,告诉编译器y的类型暂时不知道,当我赋值的时候看着办
C#是强类型语言变量一旦确定数据类型就不可以变更。
⑥new操作符:
*帮助我们在内存当中创建一个类型的实例并且立刻调用这个实例的实例构造器(所谓的构造函数),并取得的实例地址....
new Form();//调用默认实例构造器
创建这个实例之后如果没有任何变量去引用它,访问它,过一会垃圾收集就把这个实例所占用的堆内存当做垃圾给收回来了。
*除了创建实例和调用实例构造器之外还能把new取得的实例地址通过赋值符号交给负责访问这个实例的变量。这样就在变量和实例之间构成了引用关系。有了这个引用关系之后就可以通过这个变量来访问实例。如: Form myForm=new Form();//调用默认实例构造器
myForm.Text = "Hello!";//通过变量来访问实例
*上面为主要功能,以下为附加功能:调用实例的初始化器:
Form myForm = new Form() {Text="Hello!" };在实例后面加花括号里面加属性的值。
可以初始化多个属性,中间逗号隔开。
还有:有的时候用实例只是一次性的没必要创建一个引用变量去初始化它,可以采用这时初始化器就发挥作用了:new Form(){Text=”Hello!”}.ShowDialog();只是由于没有引用变量引用(没有小孩牵着这个气球,气球一会就飞走了)所以一段时间后,垃圾回收器把它的堆内存回收。
a、错觉:要创建类的实例就一定要使用new操作符,错误的。如string Name = "Hello!";
String是一个类,创建实例时不用new操作符,这种方式叫做C#的”语法糖衣”,原因为为了统一使string与int的书写格式,而把string类的new操作符隐藏起来了,string可以用new但平常不这么用。类似的还有数组:
用new操作符:
int[] myArray = new int[10];//由于int的实例构造器有点特殊不用圆括号调用;
不用new操作符时:
int[] myArray = { 1,2,3,4};
b、new操作符特殊用法:为匿名类型创建实例,
Form myForm=new Form(){Text=”Hello!”};当为非匿名类型创建实例时new后面要加类型名,
当为匿名类型创建实例时:如:
Var person=new {Name=”Mr li”,Age=34};//new操作符后面不跟类型,直接用初始化器初始化实例,什么类型?让编译器根据初始化内容自行判断,不过该实例一定要有引用变量引用,不知道类型?用var隐式变量即可。那到底是什么类型呢?
Console.WriteLine(Person.GetType().Name);
输出为:<>f__AnonymousType0`2
“<>f__AnonymousType”为约定的前缀,0表示我在程序中创建的第一个,’2表示这个类型为泛型类,构成这个类型的时候你需要两个类型来构成它,哪两个类型呢?就是初始化器里面的一个是string,一个是int。这是在创建匿名类型时编译器自己识别的类型。
这里才真正体现出var类型(全部)功能的强大之处与重要性。因为如上一种情况就算你想写出它的类型也不知道叫什么名字。
*记住new操作符与var隐式变量组合的使用方法:是为匿名对象创建对象并且用隐式类型变量来引用这个实例。
c、new操作符有危险性(功能强大伴随的滥用风险)一旦在某个类里面(比如main函数隶属的Program类)用new操作符创建了某个类的实例(比如在main函数中创建Form类),那么这个类(Form)就与主类(Program)紧紧耦合在一起,Pragram类就紧紧依赖于Form类,一旦某个类(Form)出现问题,整个耦合体都无法正常运行。即new操作符会造成紧耦合。那怎么解决?在软件工程有项非常重要和实用的技术叫做”设计模式”,在”设计模式”当中有一种非常重要的模式叫做”依赖注入”(dependenty injection),该模式就是帮助我们把紧耦合变成相对松的耦合。有概念即可:new操作符有风险慎用,大型程序中为了避免有紧耦合的情况我们有一种叫做”依赖注入”的设计模式可以使用,实现不必关注。
*程序设计追求”高内聚低耦合”
d、new关键字的多用性(不是操作符而是关键字):如
class Student
{
public void Report()
{
Console.WriteLine("I'm a student");
}
}
class CsStudent:Student
{
new public void Report()//这叫子类对父类方法的隐藏,这里的new便不是操作符而是修饰符用来修饰new后面的方法的。(并不常见)
{
Console.WriteLine("I'm a Cstudent");
}
}
则 Student stu = new Student();
stu.Report();
CsStudent csStu = new CsStudent();
csStu.Report();时分别调用各自的Report()方法。
⑦checked()和unchecked()操作符:用来检查()内的值在内存中是否有溢出:(Overflow)
C#是强类型语言,任何一个变量它在内存里面都有数据类型,而数据类型有个非常重要的作用就是表示这种数据类型的实例在内存当中能够占多大的空间,一个值在内存空间所占的大小决定了这个值能够表达的范围,一旦超出这个范围这个值就产生了溢出。Checked就是告诉我们要去检出溢出,unchecked则告诉我们不用:
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
try
{
uint y = checked(x + 1);//检测x+1是否溢出,溢出后去catch捕获异常
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("There is overflow");
}
Unchecked()操作符表示不用检查,C#中默认该种方式。Checked也有其他用法:
Checked
{
try
{
uint y = checked(x + 1);//检测x+1是否溢出,溢出后去catch捕获异常
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("There is overflow");
}
}
直接判断整个语句块中所有语句是否有溢出。
⑧delegate操作符(关键字)最主要的作用为声明一种叫委托的数据类型,委托是C#非常重要的概念。本节主要讲其作为操作符的作用(非常稀有因为拉姆达表达式(Lambda Expressions)的出现就是来替代delegate当做操作符的场景):使用delegate生成匿名方法:
this.myButton.Click +=delegate (object sender, RoutedEventArgs e)//使用delegate声明了一个匿名方法
{
this.myTextBox.Text = "Hello World!";
};
程序原本应为:this.myButton.Click += myButton_Click;
void myButton_Click(object sender, RoutedEventArgs e)
{
this.myTextBox.Text = "Hello World!";
}
现在替代这用用法的拉姆达表达式:
this.myButton.Click += (sender, e)=>
{
this.myTextBox.Text = "Hello World!";
};
语法的演变可见C#语法越来越简洁,功能越来越强大。
⑨sizeof()操作符:
a、只能获取结构体类型在内存中所占字节数,默认情况下:sizeof只能去获取基本数据类型他们的实例在内存当中所占的字节数,基本数据类型:比如int、uint...说白了就是C#关键字里面那些除了string和object的数据类型:因为这两个为引用类。
b、在非默认的情况下可以使用sizeof去获取自定义的结构体类型的实例它在内存中占的字节数,但是需要把它放在不安全的上下文当中:
unsafe
{
int x=sizeof(Student);
}
Decimal数据类型精确度比double高占16个字节;
⑩最后一个”基本操作符”:”->”
*类(class)属于引用类型,结构体(struct)属于值类型。C#中有严格的规定像指针操作,取地址操作,用指针去访问成员的操作,只能用来操作结构体类型,不能用它们去操作引用体类型。(Class)
要运行不安全代码除了要把它放在unsafe{}里面,还要再项目->最后一项(相应项目属性)->生成->勾选”允许生成不安全代码”,所谓双重保险。
使用该操作符时要在unsafe情况下使用:
unsafe
{
Student stu;
stu.ID = 1;
stu.Score = 99;
Student*pStu=&stu;
pStu->Score = 100;
Console.WriteLine(stu.Score);
}