【读书笔记】C#高级编程 第七章 运算符和类型强制转换
(一)运算符
| 类别 | 运算符 | 
| 算术运算符 | + - * / % | 
| 逻辑运算符 | & | ^ ~ && || ! | 
| 字符串连接运算符 | + | 
| 增量和减量运算符 | ++ -- | 
| 移位运算符 | << >> | 
| 比较运算符 | == != < > <= >= | 
| 赋值运算符 | = += -= *= /= %= &= |= ^= <<= >>= | 
| 成员访问运算符(用于对象和结构) | . | 
| 索引运算符(用于数组和索引器) | [] | 
| 类型转换运算符 | () | 
| 条件运算符(三元运算符) | ?: | 
| 委托连接和删除运算符 | + - | 
| 对象创建运算符 | new | 
| 类型信息运算符 | sizeof is typeof as | 
| 溢出异常控制运算符 | checked unchecked | 
| 间接寻址运算符 | [] | 
| 名称空间别名限定符 | :: | 
| 空合并运算符 | ?? | 
1、运算符的简化操作
x+=y等同于x=x+y,赋值运算符中(+= -= *= /= %= &= |= ^= <<= >>=)都是相同的模式。
当x++和++x单独一行时等同于x=x+1。当他们用于较长的表达式内部时,运算符放在前面(++x)会在计算表达式之前增加x。
int x = 0,y=0;
x++;
++y;
if (x==y)
{
    Console.WriteLine("单独一行++在前在后结果一样");
}
if (++x==2)
{
    Console.WriteLine("++在前提前计算");
}
if (y++==2)
{
    Console.WriteLine("++在后没有提前计算");
}
运行以上代码,结果如下:

(1)条件运算符
条件运算符(?:)也称单元运算符,是if...else的简化形式。它判断一个条件真假来返回对应的值(为真返回:之前的值,为假返回:之后的值)。
int x = 0; string str = x == 0 ? "x等于0" : "x不等于0"; Console.WriteLine(str);
运行以上代码,结果如下:

恰当的使用三元运算符,可以使程序非常简洁。
(2)checked运算符和unchecked运算符
C#提供checked运算符和unchecked运算符,来对一个代码块进行标记,是否执行溢出检查。
byte x = 255; x++; Console.WriteLine(x);
运行以上代码,结果如下:

这是因为,C#默认使用unchecked运算符不对溢出执行检查,这会导致数据丢失(byte不能超过255,溢出的位会丢失,所以结果为0)。
如果要防止溢出导致的数据丢失,可以使用checked运算符对代码块进行标记。
byte x = 255;
checked
{
    x++;
}
Console.WriteLine(x);
运行以上代码,结果如下:

程序会抛出异常,这个时候我们就能捕捉异常,来防止溢出丢失数据。
(3)is运算符
Is运算符检查对象是否与特定类型兼容(是该类型或者派生自该类型)。
int x = 0;
if (x is object)
{
    Console.WriteLine("x是object类型或派生自object的类型");
}
运行以上代码,结果如下:

(4)as运算符
as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功;如果不兼容,as运算符就会返回null。
object obj1 = "字符串";
object obj2 = 0;
string str1 = obj1 as string;
string str2 = obj2 as string;
Console.WriteLine("obj1 as string转换结果:" + str1);
Console.WriteLine("obj2 as string转换结果:" + str2);
运行以上代码,结果如下:

as运算符允许在一步中执行安全的类型转换,不需要先使用is运算符测试类型,再执行转换。
(5)sizeof运算符
使用sizeof运算符可以确定栈中值类型的长度(单位是字节):
Console.WriteLine(sizeof(int));//4
(6)typeof运算符
typeof运算符返回一个表示特定类型的System.Type对象。例如,typeof(string)返回表示System.String类型的Type类型。在使用反射技术动态地查找对象的信息时,这个运算符很有用。
(7)可空类型和运算符
如果在程序中使用可空类型,就必须考虑null值与各种运算符一起使用时的影响。
int? x = null; int? y = x + 1;//null
null值的可能性表示,不能随意合并表达式中的可空类型和非可空类型
(8)空合并运算符
空合并运算符(??)提供了一种快捷方式,可以在处理可空类型和引用类型时表示null可能的值。
例子:
int? x = null; int y; y = x ?? 0; Console.WriteLine(y);
运行以上代码,结果如下:

如果第一个操作数是null,则返回第二个操作数,反之则返回第一个操作数。
2、运算符的优先级
优先级由上到下依次递减
| 组 | 运算符 | 
| 初级运算符 | () . [] x++ x-- new typeof sizeof checked unchecked | 
| 一元运算符 | + - ! ~ ++x --x 和数据类型强制转换 | 
| 乘除运算符 | * / % | 
| 加减运算符 | + - | 
| 移位运算符 | << >> | 
| 关系运算符 | < > >= <= is as | 
| 比较运算符 | == != | 
| 按位AND运算符 | & | 
| 按位XOR运算符 | ^ | 
| 按位OR运算符 | | | 
| 布尔AND运算符 | && | 
| 布尔OR运算符 | || | 
| 条件运算符 | ?: | 
| 赋值运算符 | = += -= *= /= %= &= |= ^= <<== >>= >>>= | 
建议在复杂表达式中,使用圆括号来显式指定运算符的执行顺序,可以使代码更易于理解。
(二)类型的安全性
1、类型转换
(1)隐式转换
只要保证值不会发生任何变化,类型转换就可以自动(隐式)进行。当值的数量级不受影响,而精度可能受到影响时,也可以进行隐式转换,因为编译器认为这是可以接受的错误。
(2)显式转换
当无法保证值不会发生任何变化时,就需要使用显式转换,否则编译器会报错。
例子:
long类型无法隐式的转换为int,但我们可以显式地进行转换
long lon = 1; int i = (int)lon;
显式转换是一种比较危险的操作,因为有可能造成溢出,这样导致结果与正确值不同。在进行隐式转换时,应使用checked运算符进行检查。
2、装箱和拆箱
装箱用于描述一个值类型转换为引用类型。
int i = 100; object obj = i;
拆箱用于描述相反的过程,其中以前装箱的值类型强制转换回值类型。
int i = 100; object obj = i; int _i = (int)obj;
(三)比较对象的相等性
对象相等的机制有所不同,这取决于比较的是引用类型(类的实例)还是值类型(基本数据类型、结构或枚举的实例)。
1、比较引用类型的相等性
(1)ReferenceEquals()方法
ReferenceEquals()是一个静态方法,测试两个引用是否引用类的同一个实例,特别是两个引用是否包含内存中的相同地址。最为静态方法,它不可重写。
Person p1, p2;
p1 = new Person();
p2 = new Person();
bool b1 = ReferenceEquals(null, null);
bool b2 = ReferenceEquals(null, p1);
bool b3 = ReferenceEquals(p1, p2);
Console.WriteLine("ReferenceEquals(null, null) 的结果:{0}", b1);
Console.WriteLine("ReferenceEquals(null, p1) 的结果:{0}", b2);
Console.WriteLine("ReferenceEquals(p1, p2) 的结果:{0}", b3);
运行以上代码,结果如下:

(2)虚拟的Equals()方法
Equals()方法是虚拟方法,所以可以在自己的类中重写,从而按值来比较对象。
(3)静态的Equals()方法
Equals()静态版本与其虚拟实例版本的作用相同,其区别是静态版本带有两个参数,并对它们进行相等性比较。
(4)比较运算符==
最好将比较运算符看作严格的值比较和严格的引用比较之间的中间选项。
2、比较值类型的相等性
在比较值类型的相等性时,采用与引用类型相同的规则:ReferenceEquals()用于比较引用,Equals()用于比较值,比较运算符可以看作一个中间选项。但最大的区别是值类型需要进行装箱,才能把它们转换为引用类型,进而才能对它们执行方法。在System.ValueType类中重载了实例方法Equals(),以便对值类型进行合适的相等性测试。
(四)运算符重载
如果要对自定义的类使用运算符,就必须告诉编译器相关的运算符在这个类的上下文中的含义,此时就需要使用运算符重载。
C#要求所有的运算符重载都声明为public和static,这表示它们与它们的类相关联而不是特定实例。重载运算符需要使用operator关键字。编译器处理运算符重载和处理方法重载是一样的。C#要求成对的重载比较运算符,且必须返回布尔值。
例子:
public static decimal operator +(Person lhs, Person rhs)
{
    return lhs.Money + rhs.Money;
}
可以重载的运算符
| 类别 | 运算符 | 限制 | 
| 算术二元运算符 | +、*、/、-、% | 无 | 
| 算术一元运算符 | +、-、++、-- | 无 | 
| 按位二元运算符 | &、|、^、<<、>> | 无 | 
| 按位一元运算符 | !、~、true、false | true和false运算符必须成对重载 | 
| 比较运算符 | ==、!=、>=、<、<=、> | 比较运算符必须成对重载 | 
| 赋值运算符 | +=、-=、*=、/=、>>=、<<=、%=、&=、|=、^= | 不能显式地重载这些运算符,在重写单个运算符(如+、-等)时,它们会被隐式地重写 | 
| 索引运算符 | [] | 不能直接重载索引运算符。索引器成员类型允许在类和结构上支持索引运算符。 | 
| 数据类型强制转换运算符 | () | 不能直接重载类型强制运算符。用户定义的类型强制转换允许定义定制的类型强制转换。 | 
(五)用户定义的类型强制转换
C#允许定义自己的数据类型(结构和类),这意味着需要某些工具支持在自定义的数据类型之间进行类型转换。方法是把类型强制转换运算符定义为相关类的成员运算符,类型强制装换运算符必须标记为隐式或显式,以说明希望如何使用它。我们应遵守与预定义的类型强制转换相同的规则,如果知道无论在元变量中存储什么值,类型强制转换总是安全的,就可以把它定义为隐式强制转换。然而,如果某些数值可能会出错,如丢失数据或抛出异常,就应把数据类型转换定义为显式强制转换。
例子:
显式关键字explicit,隐式关键字implicit
class Program
{
    static void Main(string[] args)
    {
        Water water = new Water(100);
        Ice ice = (Ice)water;//Water显示转换为Ice
        Water water2 = ice;//Ice隐式转换为Water
        Console.ReadKey();
    }
}
public class Water
{
    public int Volume { get; set; }
    public Water(int volume)
    {
        this.Volume = volume;
    }
    public static explicit operator Ice(Water lhs)
    {
        return new Ice(lhs.Volume+1);
    }
}
public class Ice
{
    public int Volume { get; set; }
    public Ice(int volume)
    {
        this.Volume = volume;
    }
    public static implicit operator Water(Ice lhs)
    {
        return new Water(lhs.Volume-1);
    }
}
定义不同结构或类的实例之间的类型强制转换时完全合法的,但有两个限制:
- 如果某个类派生自另一个类,就不能定义这两个类之间的类型强制转换(这些类型的类型转换已经存在)。
- 类型强制转换必须在源数据类型或目标数据类型的内部定义。
C#要求把类型强制转换的定义放置在源类(或结构)或目标类(或结构)的内部。它的副作用是不能定义两个类之间的类型强制转换,除非至少可以编辑其中一个类的源代码。这是因为,这样可以防止第三方把类型转换引入类中。
【读书笔记】C#高级编程 第七章 运算符和类型强制转换的更多相关文章
- 读书笔记 - js高级程序设计 - 第七章 函数表达式
		闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者 ... 
- C#高级编程笔记 2016年10月8日运算符和类型强制转换
		1.checked和unchecked 运算符 C#提供了checked 和uncheckde 运算符.如果把一个代码块标记为checked, CLR就会执行溢出检查,如果发生溢出,就抛出overfl ... 
- C#高级编程9-第7章 运算符和类型强制转换
		运算符和类型强制转换 1.运算符 运算符的简化操作 条件运算符: if-else的简化操作,也称三元运算符.如果条件为真,返回一个值,为假返回另外一个值. condition?true_value:f ... 
- C#高级编程 (第六版) 学习 第六章:运算符和类型强制转换
		第六章 运算符和类型强制转换 1,运算符 类别 运算符 算术运算符 + - * / % 逻辑运算符 & | ^ ~ && || ! 字符串连接运算符 + 增量和减量运算符 ++ ... 
- 【读书笔记】C#高级编程 第三章 对象和类型
		(一)类和结构 类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法. 类和结构的区别:内存中的存储方式.访问方式(类是存储在堆上的引用类型,结构是存储在栈的值类型)和它 ... 
- R in action读书笔记(7)-第七章:基本统计分析(下)
		7.3相关 相关系数可以用来描述定量变量之间的关系.相关系数的符号(±)表明关系的方向(正相关或负相关),其值的大小表示关系的强弱程度(完全不相关时为0,完全相关时为1).除了基础安装以外,我们还将使 ... 
- R in action读书笔记(6)-第七章:基本统计分析(中)
		7.2 频数表和列联表 > library(vcd) > head(Arthritis) ID Treatment Sex Age Improved 1 57 Treated Male 2 ... 
- R in action读书笔记(5)-第七章:基本统计分析
		7.1描述性统计分析 > vars<-c("mpg","hp","wt") > head(mtcars[vars]) ... 
- 读书笔记 - js高级程序设计 - 第十一章 DOM扩展
		对DOM的两个主要的扩展 Selectors API HTML5 Element Traversal 元素遍历规范 querySelector var body = document.query ... 
随机推荐
- QT与DoNet中单例模式的简单实现
			由于使用场景的不同,单例模式的写法也有所区别. 目前接触到的,大多数都是多线程,大量数据处理,还要考虑灵活性,对原有类结构改动最小等因素,所以写法更是多种多样. QT个人较常用的一种写法:(两个文件: ... 
- sed基本使用
			1. 删除由空格组成的空白行 sed '/^ *$/d' test.txt sed '/[ ][ ]/d' test.txt 2. 删除空白行 sed '/^[[:space:]]*$/d' test ... 
- RocketMQ 集群的搭建部署 以及rocketmq-console-ng仪表台的安装部署
			在 RocketMQ 主要的组件如下. NameServerNameServer 集群,Topic 的路由注册中心,为客户端根据 Topic 提供路由服务,从而引导客户端向 Broker 发送消息.N ... 
- react antd上拉加载与下拉刷新与虚拟列表使用
			创建项目 create-react-app antdReact 安装:antd-mobile.react-virtualized npm i antd-mobile -S npm i react-vi ... 
- CMP0065警告问题
			参考链接: https://cmake.org/cmake/help/latest/policy/CMP0065.html https://cmake-developers.cmake.narkive ... 
- redis 集群 slots are covered by nodes.
			原因数据数据损坏.需要修复 1.检测 redis-cli --cluster check 127.0.0.1:7000 2.检测结果 slots are covered by nodes3.进行修复 ... 
- day04 缓冲字符流__异常处理
			缓冲字符流 缓冲字符输入流:java.io.BufferedReader 是一个高级的字符流,特点是块读文本数据,并且可以按行读取字符串. package io; import java.io.*; ... 
- PHP memcache add replace set的区别和其他用法收集
			add replace set的区别 最近在面试时遇到一个问题 memcache 的add replace set的区别,故在此进行加强 add 是向服务器添加一个缓存的数据,当该键已存在会返回一个f ... 
- Vue 状态管理之vuex  &&  {mapState,mapGetters}
			1 # 一.理解vuex 2 1.概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读写),也是一种组件间通信的方式,且适用于任意组件间 ... 
- 【AGC】构建服务1-云函数示例
			 前言:上一次笔者给大家带来了AGC领域的远程配置服务的学习.这次我们再继续深化学习AGC的相关知识.在文章开始之前,再给读者简单介绍一下AGC,以免第一次来的读者不了解.所谓AGC就是AppGal ... 
