C#基础操作符详解(下)
书接上文的基本操作符,下文介绍的是其他操作符:
4.2一元操作符:
只要有一个操作数跟在它后面就可以构成表达式,也叫单目操作符。
①&x和*x操作符(很少见有印象即可):
这两个操作符同样也需要在不安全的上下文中运行:&是取地址操作符。
简单错误:*pStu.错误:由于.为基本操作符优先级大于*所以是先进行pStu.的操作正确应该为:(*pStu).即加个括号。
②+、-、!、~四个一元操作符:
-运算符可造成内存溢出:
int a = int.MinValue;
int b =checked( -a);//原因在于int.MaxValue与int.MinValue绝对值不一样。
~求反操作符,对操作数转化为2进制,进行二进制的按位取反。
计算机取相反数的原理:先把该数转化为2进制再按位取反+1
!操作符只能用来操作布尔类型的数据,
③++x;--x运算符:单独使用时x++与++x没有区别。
4.3、强制类型转化操作符:(T)x,T表示某种数据类型。
类型转换:
①隐式(implicit)类型转换,常见的为以下三种情况:
*不丢失精度的转换:小字节数(存储空间)的数据类型向多字节(存储空间)的数据类型转换不会丢失精度,比如金鱼从鱼缸里放到游泳池里还是好好的。例如:
int x = int.MaxValue;
long y = x;
这就是在不丢失精度的情况下进行的隐式类型转换,具体哪些数据类型可向哪些无丢失精度类型转换见C#语言定义文档6.1.2:
隐式数值转换为:(原则为从小的向大的转)
l 从 sbyte 到 short、int、long、float、double 或 decimal。
l 从 byte 到 short、ushort、int、uint、long、ulong、float、double 或 decimal。
l 从 short 到 int、long、float、double 或 decimal。
l 从 ushort 到 int、uint、long、ulong、float、double 或 decimal。
l 从 int 到 long、float、double 或 decimal。
l 从 uint 到 long、ulong、float、double 或 decimal。
l 从 long 到 float、double 或 decimal。
l 从 ulong 到 float、double 或 decimal。
l 从 char 到 ushort、int、uint、long、ulong、float、double 或 decimal。
l 从 float 到 double。
从 int、uint、long 或 ulong 到 float 的转换以及从 long 或 ulong 到 double 的转换可能导致精度损失,但决不会影响数值大小。其他的隐式数值转换决不会丢失任何信息。
不存在向 char 类型的隐式转换,因此其他整型的值不会自动转换为 char 类型。
*子类向父类的转换;
例如定义三个互相继承的类:
class Animal { public void Eat() { Console.WriteLine("Eat"); } } class Human:Animal { public void Think() { Console.WriteLine("think"); } } class Teacher:Human { public void Teach() { Console.WriteLine("Teach"); }
在main函数中:
Teacher t = new Teacher();
Human h = t;//这种就是子类向父类隐式类型换
而h.只能访问Human类的成员,不能访问Teacher类的成员,因为引用变量只能访问它这个变量的类型它所具有的成员,注意是这个变量的类型是(Human)而不是这个变量引用的类型(Teacher)。(简单的比喻比如人的父类是猴子,但猴子的只能做猴子的行为而不能做人类的行为。)
*装箱;见上一节。
②显式(explicit)类型转换:为什么要有?因为这种转换有精度的丢失,编译器要你明明白白的写出来,让你知道有精度的丢失,也要转换数据类型。
*有可能丢失精度(甚至发生错误)的转换,即cast(铸造):在数据前面加个圆括号:(T)x,T里面为强转后的数据类型。
例如:
uint x = 65536;
ushort y = (ushort)x;//这样才能成立,因为ushoort的最大值比x小
具体见语言定义文档6.2.1;注意强制类型转换时由有符号类型向无符号类型转换,符号位拿来当数据了肯定会丢失精度。所以在对有符号类型进行强转时要格外注意。
*拆箱
*使用Convert类:
首先:如下情况:
string str1=Console.ReadLine();//ReadLine方法是读取一行输入
string str2 = Console.ReadLine();
Console.WriteLine( str1+str2);//
若输入:12 12则输出的是1212,原因为由于操作符与数据类型相关联,当+识别到左右两边的数据都是string类型时则把两个字符串连接起来,得到的就是1212的字符串而不是我们想要的值24。
正确做法为: int x = Convert.ToInt32(str1);
int y = Convert.ToInt32(str2);
这个例子就是非常常见的数据类型转换例子。
有的数据类型不能使用cast这种方式如int和string这两种数据类型差距太大。这时需要借助工具类(如Convert类)来进行类型转换。如: Convert.ToInt32(x);这个类相当于一个类型转换枢纽,几乎可以把当前数据类型转换成全部想要的数据类型。
*ToString方法与各类数据类型的Parse/TryParse方法:
有时候我们需要把其他数据如int转化为字符串类型数据这时候有两种方法可以选择:
第一种:调用Conver类的ToString静态方法: Convert.ToString(36种重载形式);详见”强制类型转换ToString”
第二种:调用数值类型数据的ToString实例(非静态)方法来完成转换: x.ToString(3个重载);ToString方法但就是把实例转化为一个字符串,创建一个Object对象:object o;o.会出现四个方法其中就有”ToString”说明所有的数据类型都有这个方法(一切数据类型都由object派生)
Parse(解析):这里的th1.Text和th2.Text都是字符串类型(string)
double x = double.Parse(th1.Text);
double y = double.Parse(th2.Text);
可以这样改写,小小的缺点:只能解析格式正确的数据如()里输入1ab格式不对就不能自动解析并转换为double类型。
这时可以使用TryParse方法: double x = double.TryParse();
具体涉及内容以后会讲。
③自定义数据类型转换操作符
示例;*操作符的本质就是方法的简记法
例如:创建了两个类:
class Stone
{
public int Age;
}
class Monkey
{
public int Age;
}
在main函数中进行类型转换:
Stone stone = new Stone();
Monkey m = (Monkey)stone;
显然不行编译器不知道给怎么转换,我们要告诉它,由于是stone进行类型转换所以在stone类里面添加具体语句:
class Stone
{
public int Age;
public static explicit operator Monkey(Stone stone)//可以理解为显式类型转换操作符就是一个目标类型的实例的构造器,但是这个构造器不是写在目标类型的类里面而是写在被转换的数据类型里面(而前面有四个操作符public static explicit operator ).
{
Monkey m = new Monkey();
m.Age = stone.Age / 500;//表示如何转换
return m;
}
}
这样编译器就知道要怎么转换了,继续回到main函数:
Stone stone = new Stone();
stone.Age = 5000;
Monkey m = (Monkey)stone;
Console.WriteLine(m.Age);
按照stone类里面定义(m.Age = stone.Age / 500;)的对stone对象进行转换,输出为:10;
该方式为显式转换,隐式只需要: public static implicit operator Money(Stone stone)
即把 explicit改成implicit然后类型转换时: Money m = stone;可以把stone对象的前缀(Money)省略。
4.4、算术运算符:详见语言定义文档7.8.1
务必留意”类型提升”:算术运算符都会产生,默认的类型提升如int型的数据与double的数据相乘为了不丢失精度,int型先隐式转换为double再进行计算。
“/“整数除法要注意除0异常,浮点数的除法没有该异常。
double a = double.PositiveInfinity;//正无穷大
double b = double.NegativeInfinity;//负无穷大
double x=(double)5/4;
结果为浮点型的1.25因为()类型转换操作符优先级大于算术运算符,所以先对5进行类型转换为double再进行除法,除法过程中运算符发现两边类型不同进行”类型提升”
有时候取余操作符%也会进行”类型提升”,也有浮点数取余。
4.5、位移操作符:<<(左移)、>>(右移)
表示:数据在内存当中的2进制的结构向左或者向右进行一定位数的平移:
int x = 7;
int y = x << 1;//x左移一位
string strx = Convert.ToString(x, 2).PadLeft(32, '0');
string stry = Convert.ToString(y, 2).PadLeft(32, '0');
Console.WriteLine(strx);
Console.WriteLine(stry);
输出显示为:00000000000000000000000000000111
00000000000000000000000000001110明显左移了一位
当没有溢出的情况下左移就是×2右移就是÷2.
移位过多会产生溢出由于默认是unchecked所以不会有异常。
*左移无论是正负数补进来的都是0,右移如果操作的是正数最高位补进来的是0,负数则补1;
4.6、关系操作符<,>,<=,>=;
所有关系操作符的运算结果都是布尔类型的,除了可以比较数值类型还可以比较字符类型。
char char1='a';
char char2='A';
ushort u1 = (ushort)char1;
ushort u2 = (ushort)char2;结果为u1=97;u2=65,;(对应Unicode码表)
还可以比较字符串,只能比是否相等。
string str1 = "ABc";
string str2 = "Abc";
Console.WriteLine(str1.ToLower()==str2.ToLower());都转换为小写输出结果为相等。
还可以调用: string.Compare();返回正值则第一个大于第二个,负值则小于,0则相等,比较方式为把两个字符串对齐依次比较字母对应的Unicode码。
4.7、类型检验操作符,is,as
is操作符结果是布尔类型,作用为检验某个对象是否为某个类(可以判断分类):
Teacher t=new Teacher();
Var result=t is Teacher;
Var result=t is Animal;//由于Teacher的父类为Animal所以这个判断也为true;反过来不行
as操作符的作用为:
Object o=new Teacher();
Teacher t=o as Teacher;即如果o对象是Teacher类则把o对象的地址交给Teacher类的引用变量t,否则把null值给t。
4.8、逻辑位与(&),逻辑位或(|),逻辑按位异或(^);
所谓位与就是按位求与,求与时把1当做真,0当做假,真&真为真,真&假为假,假&假为假。例如:
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);
输出结果为:
00000000000000000000000000000111
00000000000000000000000000011100
00000000000000000000000000000100
按位或(|),真|真为真,假|真为真,假|假为假。
按位异或(^),两位相同为真,不同为假。
4.9、条件与(&&),条件或(||):用来操作布尔类型的值,最后结果也是布尔类型的值。
条件与(&&):左右两边都是真,整体值才为真;条件或(||):左右两边有一个真整体即为真。
条件与(&)与条件或(|)具有短路效应: if (x>y&&a++>3)当条件较多时条件与只要发现左边的值为假则不用判断右边的值了直接返回false,条件或情况类似,如果左边为真则直接返回true。
*由于存在该效应我们写代码时要尽量避免。
4.10、null合并操作符(??):
int ? y=null;//相当于暂时给y赋null值以后可变
int c = y ?? 1;//判断y是否为null是就用1赋值给y。
4.11、条件操作符:?:(是唯一一个可以接收三个数的操作符):就是if..else分支的简写:
y = (x >= 60) ? 100 : 0;表示x如果>=60就把100赋值给y否则把0赋值给y,往往用圆括号把条件括起来提高可读性。
4.12、(优先级最低)赋值表达式与lambda表达式:=,+=,%=,-=,>>=,<<=,&=,^=,|=
表示先运算再赋值,lambda表达式(=>)后面会讲到。
不过都要注意数据类型,运算符会根据两边的数据类型来判断进行的是整数运算还是浮点数运算还是其他。*赋值运算符的运算顺序是从右到左。例如:
Int x=3;
Int y=4;
Int z=5;
Int a=x+=y+=Z;先算右边的y+=z即y=y+z;...