C#语言程序设计 4章.ppt

大学C语言程序设计-李继武 彭德林-课件PPT

收藏

资源目录
跳过导航链接。
压缩包内文档预览:(预览前20页/共211页)
预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图 预览图
编号:21836185    类型:共享资源    大小:6.11MB    格式:ZIP    上传时间:2019-09-06 上传人:QQ24****1780 IP属地:浙江
25
积分
关 键 词:
大学C语言程序设计-李继武 彭德林-课件PPT 大学 语言程序设计 李继武 彭德林 课件 ppt
资源描述:
大学C语言程序设计-李继武 彭德林-课件PPT,大学C语言程序设计-李继武,彭德林-课件PPT,大学,语言程序设计,李继武,彭德林,课件,ppt
内容简介:
第4章 C#面向对象高级编程,前面介绍了面向对象程序设计的基本概念和应用。但是面向对象还包括很多其他重要的概念。本章将深入分析面向对象编程的概念,并详细说明利用工具进行面向对象程序设计的方法。,4.1 类的继承与多态,4.1.1 继承 1、概述 现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征,也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。 为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。 注意:C#中,派生类只能从一个类中继承。这是因为,在C+中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类,这往往会带来许多问题,从而抵消了这种灵活性带来的优势。,C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。,C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。 程序清单: using System; namespace ConsoleApplication1 class Vehicle /定义汽车类 int wheels; /公有成员:轮子个数 protected float weight; /保护成员:重量 public Vehicle(); public Vehicle(int w, float g) wheels=w; weight=g;, public void Speak() Console.WriteLine(“the w vehicle is speaking!“); class Car: Vehicle /定义轿车类:从汽车类中继承 int passengers;/私有成员:乘客数 public Car(int w, float g, int p) : base(w,g) wheels=w; weight=g; passengers=p; ,Vehicle作为基类,体现了“汽车”这个实体具有的公共性质:汽车都有轮子和重量。 Car类继承了Vehicle的这些性质,并且添加了自身的特性:可以搭载乘客。,C#中的继承符合下列规则: (1)继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object类作为所有类的基类。 (2)派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去己经继承的成员的定义。,(3)构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。,(4)派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖己继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。 (5)类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。,2、覆盖,我们上面提到,类的成员声明中,可以声明与继承而来的成员同名的成员。这时我们称派生类的成员覆盖(hide)了基类的成员。这种情况下,编译器不会报告错误,但会给出一个警告。对派生类的成员使用new关键字,可以关闭这个警告。 前面汽车类的例子中,类Car继承了Vehicle的Speak()方法。我们可以给Car类也声明一个Speak()方法,覆盖Vehicle中的Speak,见下面的代码。 程序清单:,using System; namespace ConsoleApplication1, class Vehicle/定义汽车类 public int wheels; /公有成员:轮子个数 protected float weight;/保护成员:重量 public Vehicle(); public Vehicle(int w, float g) ,wheels=w; weight=g;, public void Speak() Console.WriteLine(“the w vehicle is speaking!“); class Car:Vehicle /定义轿车类 int passengers;/私有成员:乘客数 public Car(int w, float g, int p) wheels=w; weight=g;,passengers=p;, new public void Speak() Console.WriteLine(“Di-di!“); 注意:如果在成员声明中加上了new关键字修饰,而该成员事实上并没有覆盖继承的成员,编译器将会给出警告。在个成员声明同时使用new和override则编译器会报告错误。,3、base保留字,base关键字主要是为派生类调用基类成员提供一个简写的方法。我们先看一个例子程序代码: using System; namespace ConsoleApplication1 class A public void F() /F的具体执行代码 ,public int thisint nIndex, get set class B public void G() int x=base0; base.F(); ,类B从类A中继承,B的方法G中调用了A的方法F和索引指示器。方法F在进行编译时等价于:,public void G() int x=(A (this)0; (A (this).F(); 使用base关键字对基类成员的访问格式为: base .标识符 base 表达式列表,4.1.2 多态,在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。, C#中的多态,“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。 在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性: (1)编译时的多态性 编译时的多态性是通过重载来实现的。我们在前面介绍了方法重载,都实现了编译时的多态性。 对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信急决定实现何种操作。,(2)运行时的多态性,运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。 编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。, 虚方法,当类中的方法声明前加上了virtual修饰符,我们称之为虚方法,反之为非虚。使用了virtual修饰符后,不允许再有static, abstract,或override修饰符。,对于非虚的方法,无论被其所在类的实例调用,还是被这个类的派生类的实例调用,方法的执行方式不变。而对于虚方法,它的执行方式可以被派生类改变,这种改变是通过方法的重载来实现的。,案例:虚方法与非虚方法的调用 目标:说明了虚方法与非虚方法的区别 步骤: 1、启动VS.NET,新建一个控制台应用程序,名称填写为“DifferentiateTest”,位置设置为“c:CSharpSampleschp4”。,2、在代码设计窗口中编辑Class1.cs。其中的代码编写如下:,using System; namespace DifferentiateTest class A public void F()Console.WriteLine(“A.F“); public virtual void G()Console.WriteLine(“A.G“); class B: A new public void F()Console.WriteLine(“B.F“); public override void G()Console.WriteLine(“B.G“); ,class Test, static void Main() B b=new B(); A a=b; a.F(); b.F(); a.G(); b.G(); ,3、按Ctrl + F5编译并运行该程序,效果如图5-2所示。,图 4-1 程序运行结果,例子中,A类提供了两个方法:非虚的F和虚方法G。类B则提供了一个新的非虚的方法F,从而覆盖了继承的F;类B同时还重载了继承的方法G。 在本例中,方法a.G()实际调用了B.G,而不是A.G。这是因为编译时值为A,但运行时值为B,所以B完成了对方法的实际调用。, 在派生类中对虚方法进行重载,先让我们回顾一下普通的方法重载。普通的方法重载指的是:类中两个以上的方法(包括隐藏的继承而来的方法),取的名字相同,只要使用的参数类型或者参数个数不同,编译器便知道在何种情况下应该调用哪个方法。,而对基类虚方法的重载是函数重载的另一种特殊形式。在派生类中重新定义此虚函数时,要求的是方法名称、返回值类型、参数表中的参数个数、类型、顺序都必须与基类中的虚函数完全一致。在派生类中声明对虚方法的重载,要求在声明中加上override关键字,而且不能有new, static或virtual修饰符。,4.1.3 抽象和密封,1、抽象类 有时候,基类并不与具体的事物相联系,而是只表达一种抽象的概念,用以为它的派生类提供一个公共的界面。为此,C#中引入了抽象类(abstract class)的概念。,注意:C+程序员在这里最容易犯错误。C+中没有对抽象类进行直接声明的方法,而认为只要在类中定义了纯虚函数,这个类就是一个抽象类。纯虚函数的概念比较晦涩,直观上不容易为人们接受和掌握,因此C#抛弃了这一概念。,抽象类使用abstract修饰符,对抽象类的使用有以下几点规定: (1)抽象类只能作为其它类的基类,它不能直接被实例化,而且对抽象类不能使用new操作符。抽象类如果含有抽象的变量或值,则它们要么是null类型,要么包含了对非抽象类的实例的引用。 (2)抽象类允许包含抽象成员,虽然这不是必须的。,(3)抽象类不能同时又是密封的。,如果一个非抽象类从抽象类中派生,则其必须通过重载来实现所有继承而来的抽象成员。请看下面的示例: using System; namespace ConsoleApplication1 abstract class A public abstract void F(); abstract class B: A public void G() ,class C: B public override void FD /F的具体实现代码 ,抽象类A提供了一个抽象方法F。类B从抽象类A中继承,并且又提供了一个方法G;因为B中并没有包含对F的实现,所以B也必须是抽象类。类C从类B中继承,类中重载了抽象方法F,并且提供了对F的具体实现,则类C允许是非抽象的。,2、抽象方法,由于抽象类本身表达的是抽象的概念,因此类中的许多方法并不一定要有具体的实现,而只是留出一个接日来作为派生类重载的界面。举一个简单的例子,“图形”这个类是抽象的,它的成员方法“计算图形面积”也就没有实际的意义。面积只对“图形”的派生类比如“圆”、“一角形”这些非抽象的概念才有效,那么我们就可以把基类“图形”的成员方法“计算面积”声明为抽象的,具体的实现交给派生类通过重载来实现。 一个方法声明中如果加上abstract修饰符,我们称该方法为抽象方法(abstract method )。 如果一个方法被声明也是抽象的,那么该方法默认也是一个虚方法。事实上,抽象方法是一个新的虚方法,它不提供具体的方法实现代码。我们知道,非虚的派生类要求通过重载为继承的虚方法提供自己的实现,而抽象方法则不包含具体的实现内容,所以方法声明的执行体中只有一个分号“;”。,只能在抽象类中声明抽象方法。对抽象方法,不能再使用static或virtual修饰符,而且方法不能有任何可执行代码,哪怕只是一对大括号中间加一个一个分号“;”都不允许出现,只需要给出方法的原型就可以了。,还要注意,抽象方法在派生类中不能使用base关键字来进行访问。例如,下面的代码在编译时会发生错误: using System; namespace ConsoleApplication1 class A public abstract void F(); class B: A ,public override void F() base.F();/错误,base.F是抽象方法 ,我们还可以利用抽象方法来重载基类的虚方法,这时基类中虚方法的执行代码就被“拦截”了。下面的例子说明了这一点: using System; namespace ConsoleApplication1 class A ,public virtual void F(), Console.WriteLine(“A.F“); abstract class B: A public abstract override void F(); class C: B public override void F() Console.WriteLine(“C.F“); ,类A声明了一个虚方法F,派生类B使用抽象方法重载了F,这样B的派生类C就可以重载F并提供自己的实现。,3、密封类 想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞大,类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类己经没有再被继承的必要。C#提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。 密封类在声明中使用sealed修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提不出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。,在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类,而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类,如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual不再生效。,让我们看下面的例子: using System; namespace ConsoleApplication1 abstract class A public abstract void F(); sealed class B: A ,public override void F() /F的具体实现代码 ,如果我们尝试写下面的代码: class C: B C#会指出这个错误,告诉你B是一个密封类,不能试图从B中派生任何类。 4、密封方法 我们己经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealed method)的概念,以防止在方法所在类的派生类中对该方法的重载。,对方法可以使用sealed修饰符,这时我们称该方法是一个密封方法。,不是类的每个成员方法都可以作为密封方法,密封方法必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed修饰符总是和override修饰符同时使用。请看例子代码。 程序清单: using System; namespace ConsoleApplication1 class A public virtual void F() ,Console.WriteLine(“A.F“);, public virtual void G() Console.WriteLine(“A.G“); class B: A sealed override public void F() Console.WriteLine(“B.F“); override public void G() Console.WriteLine(“B.G“); class C: B ,override public void G() Console.WriteLine(“C.G“); ,类B对基类A中的两个虚方法均进行了重载,其中F方法使用了Sealed修饰符,成为一个密封方法。G方法不是密封方法,所以在B的派生类C中,可以重载方法G,但不能重载方法F。,4.2 操作符重载,4.2.1 问题的提出 在面向对象的程序设计中,自己定义一个类,就等于创建了一个新类型。类的实例和变量一样,可以作为参数传递,也可以作为返回类型。 在前几章中,我们介绍了系统定义的许多操作符。比如对于两个整型变量,使用算术操作符可以简便地进行算术运算: class A public int x; public int y; public int Plus() return x+y; ,再比如,我们希望将属于不同类的两个实例的数据内容相加:,class B public int x; class Test public int z; public static void Main() A a=new A(); B b=new B(); z=a.x+b.x; ,使用a.x+b.x这种写法不够简洁,也不够直观。更为严重的问题是,如果类的成员在声明时使用的不是public修饰符的话,这种访问就是非法的。 我们知道,在C#中,所有数据要么属于某个类,要么属于某个类的实例,充分体现了面向对象的思想。因此,为了表达上的方便,人们希望可以重新给己定义的操作符赋予新的含义,在特定的类的实例上进行新的解释。这就需要通过操作符重载来解决。,4.2.2 使用成员方法重载操作符,C#中,操作符重载总是在类中进行声明,并且通过调用类的成员方法来实现。 操作符重载声明的格式为:,type operator operator-game (formal-param-list) C#中,下列操作符都是可以重载的: + - ! + - true false * / % & | = != = =,但也有一些操作符是不允许进行重载的,如: =,&,|,?:,new, typeof , sizeof, is 一元操作符重载 顾名思义,一元操作符重载时操作符只作用于一个对象,此时参数表为空,当前对象作为操作符的单操作数。,下面我们举一个角色类游戏中经常遇到的例子。扮演的角色具有内力、体力、经验值、剩余体力、剩余内力五个属性,每当经验值达到一定程度时,角色便会升级,体力、内力上升,剩余体力和内力补满。“升级”我们使用重载操作符“+”来实现。,案例:游戏中“升级”问题 目标:掌握一元操作符重载的基本方法 步骤: 1、启动VS.NET,新建一个控制台应用程序,名称填写为“PlayerTest”,位置设置为“c:CSharpSampleschp4”。 2、在代码设计窗口中编辑Class1.cs。其中的代码编写如下:,using System; namespace PlayerTest, class Player public int neili; public int tili; public int jingyan; public int neili_r; public int tili_r; public Player() neili=10; tili=50; jingyan=0; neili_r=50; tili_r=50; public static Player operator +(Player p) ,p.neili=p.neili+50; p.tili=p.tili+100; p.neili_r=p.neili; p.tili_r=p.tili; return p; ,public void Show() Console.WriteLine(“Tili:0 “,tili); Console.WriteLine(“Jingyan: 0“,jingyan); Console.WriteLine(“Neili0“,neili); Console.WriteLine(“Tili_full: 0“,tili_r); Console.WriteLine(“Neili_full: 0“,neili_r); ,class Test public static void Main() Player man=new Player(); man.Show(); man+; Console.WriteLine(“Now upgrading:“); man. Show(); ,3、按Ctrl + F5编译并运行该程序,效果如图4-2所示。,二元操作符重载 大多数情况下我们使用二元操作符重载。这时参数表中有一个参数,当前对象作为该操作符的左操作数,参数作为操作符的右操作数。,图 4-2 程序运行结果,下面我们给出二元操作符重载的一个简单例子。 案例:笛卡儿坐标相加问题 目标:掌握二元操作符重载的基本方法 步骤:,1、启动VS.NET,新建一个控制台应用程序,名称填写为“DKRTest”,位置设置为“c:CSharpSampleschp4”。 2、在代码设计窗口中编辑Class1.cs。其中的代码编写如下: using System; namespace DKRTest class DKR public int x,y,z; public DKR(int vx,int vy, int vz) ,x=vx; y=vy; z=vz; ,public static DKR operator +(DKR d1,DKR d2) DKR dkr=new DKR(0,0,0); dkr.x=d1.x+d2.x; dkr.y=d1.y+d2.y; dkr.z=d1.z+d2.z; return dkr; class Test public static void Main() ,DKR d1=new DKR(3,2,1); DKR d2=new DKR(0,6,5); DKR d3=d1+d2; Console.WriteLine(“The 3d location of d3 is: 0, 1, 2“,d3.x,d3.y,d3.z); ,3、按Ctrl + F5编译并运行该程序,效果如图4-3所示,图 4-3 程序运行结果,4.3 类型转换,转换是使一种类型的表达式可以被视为另一种类型。转换可以是隐式或显式,这将确定是否需要显式地强制转换。例如,从int类型到long类型的转换是隐式的,因此int类型的表达式可隐式地按long类型处理。从long类型到int类型的反向转换是显式的,因此需要显式地强制转换。 int a = 123; long b = a; / 从 int 类型到 long 类型的转换 int c = (int) b; / 从 long 类型到 int 类型的反向转换,4.3.1 隐式类型转换,下列转换属于隐式转换: 标识转换 隐式数值转换 隐式枚举转换 隐式常数表达式转换 用户定义的隐式转换 隐式转换可以在各种情况下发生,包括函数成员调用、强制转换表达式以及赋值。 1、标识转换是在同一类型(可为任何类型)内进行转换。这种转换的存在,仅仅是为了使已具有所需类型的实体可被认为是可转换的(转换为该类型)。 2、隐式数值转换为:,从sbyte到short、int、long、float、double或decimal。 从byte到short、ushort、int、uint、long、ulong、float double或decimal。 从short到int、long、float、double或decimal。 从ushort到int、uint、long、ulong、float、double或decimal。 从int到long、float、double或decimal。 从uint到long、ulong、float、double或decimal。 从long到float、double或decimal。 从ulong到float、double或decimal。 从char到ushort、int、uint、long、ulong、float、double或decimal。 从float到double。,从int、uint、long或ulong到float以及从long或ulong到double的转换可能导致精度损失,但决不会影响到它的数量级。其他的隐式数值转换决不会丢失任何信息。,不存在向char类型的隐式转换,因此其它整型的值不会自动转换为char类型。 3、隐式枚举转换允许将十进制整数0转换为任何枚举类型。 4、隐式常数表达式转换允许进行以下转换:,int类型的常数表达式可以转换为sbyte、byte、short、ushort、uint或ulong类型(前提是常数表达式的值在目标类型的范围内)。 long类型的常数表达式可以转换为ulong类型(前提是常数表达式的值不为负)。 5、用户定义的隐式转换由以下三部分组成:先是一个标准的隐式转换(可选);然后是执行用户定义的隐式转换运算符,最后是另一个标准的隐式转换(可选)。计算用户定义的转换的精确规则的说明。,从S类型到T类型的用户定义的隐式转换按下面这样处理:,1)查找类型集D,将从该类型集考虑用户定义的转换运算符。此集合由S(如果S是类或构造)、S的基类(如果S是类)和T(如果T是类或结构)组成。 2)查找适用的用户定义转换运算符集合U。集合U由用户定义的隐式转换运算符组成,这些运算符是在D中的类或结构内声明的,用于从包含S的类型转换为被T包含的类型。如果U为空,则转换未定义并且发生编译时错误。 3)在U中查找运算符的最精确的源类型SX: 如果U中的所有运算符都从S转换,则SX为S。 否则,SX在U中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到这样的被包含程度最大的类型,则转换是不明确的,并且发生编译时错误。,4)在U中查找运算符的最精确的目标类型TX:,如果U中的所有运算符都转换为T,则TX为T。 否则,TX在U中运算符的合并目标类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编译时错误。 5)如果U中正好含有一个从SX转换到TX的用户定义转换运算符,则这就是最精确的转换运算符。如果不存在此类运算符,或者如果存在多个此类运算符,则转换是不明确的,并且发生编译时错误。否则,将应用用户定义的转换: 如果S不是SX,则先执行一个从S到SX的标准隐式转换。 调用最精确的用户定义转换运算符,以从SX转换到TX。 如果TX不是T,则再执行一个TX到T的标准隐式转换。,4.3.2显式类型转换,下列转换属于显式转换: 显式数值转换 显式枚举转换 用户定义的显式转换 显式转换可在强制转换表达式中出现。 显式转换集包括所有隐式转换。这意味着允许使用冗余的强制转换表达式。 不是隐式转换的显式转换是这样的一类转换:它们不能保证总是成功,知道有可能丢失信息,变换前后的类型显著不同,以至值得使用显式表示法。 1、显式数值转换是指从一个数值类型到另一个数值转换的转换,此转换不能用已知的隐式数值转换实现,它包括:,从sbyte到byte、ushort、uint、ulong或char。 从byte到sbyte 和char。,从short到sbyte、byte、ushort、uint、ulong或char。 从ushort到sbyte、byte、short或char。 从int到sbyte、byte、short、ushort、uint、ulong或char。 从uint到sbyte、byte、short、ushort、int或char。 从long到sbyte、byte、short、ushort、int、uint、ulong或char。 从ulong到sbyte、byte、short、ushort、int、uint、long或char。 从char到sbyte、byte或short。 从float到sbyte、byte、short、ushort、int、uint、long、ulong、char或decimal。 从double到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或decimal。 从decimal到sbyte、byte、short、ushort、int、uint、long、ulong、char、float或double。,由于显式转换包括所有隐式和显式数值转换,因此总是可以使用强制转换表达式从任何数值类型转换为任何其他的数值类型。,显式数值转换有可能丢失信息或导致引发异常。显式数值转换按下面所述处理: 对于从一个整型到另一个整型的转换,处理取决于该转换发生时的溢出检查上下文: 1)在checked上下文中,如果源操作数的值在目标类型的范围内,转换就会成功,但如果源操作数的值在目标类型的范围外,则会引发System.OverflowException。 2)在unchecked上下文中,转换总是会成功并按下面所述进行。 如果源类型大于目标类型,则截断源值(截去源值中容不下的最高有效位)。然后将结果视为目标类型的值。 如果源类型小于目标类型,则源值或按符号扩展或按零扩展,以使它的大小与目标类型相同。如果源类型是有符号的,则使用按符号扩展;如果源类型是无符号的,则使用按零扩展。然后将结果视为目标类型的值。,如果源类型的大小与目标类型相同,则源值被视为目标类型的值。,对于从decimal到整型的转换,源值向零舍入到最接近的整数值,该整数值成为转换的结果。如果转换得到的整数值不在目标类型的范围内,则会引发System.OverflowException。 对于从float或double到整型的转换,处理取决于发生该转换时的溢出检查上下文: 1)在checked上下文中,如下所示进行转换: 如果操作数的值是NaN或无穷大,则引发System.OverflowException。 否则,源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内,则该值就是转换的结果。 否则,引发System.OverflowException。,2)在unchecked上下文中,转换总是会成功并按下面这样继续。,如果操作数的值是NaN或infinite,则转换的结果是目标类型的一个未经指定的值。 否则,源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内,则该值就是转换的结果。 否则,转换的结果是目标类型的一个未经指定的值。 对于从double到float的转换,double值舍入到最接近的float值。如果double值过小,无法表示为float值,则结果变成正零或负零。如果double值过大,无法表示为float 值,则结果变成正无穷大或负无穷大。如果double 值为 NaN,则结果仍然是NaN。 对于从float或double到decimal的转换,源值转换为用decimal形式来表示,并且在需要时,将它在第28位小数位数上舍入到最接近的数字。如果源值过小,无法表示为decimal,则结果变成零。如果源值为 NaN、无穷大或者太大而无法表示为decimal值,则将引发System.OverflowException。,对于从decimal到float或double的转换,decimal值舍入到最接近的double或float值。虽然这种转换可能会损失精度,但决不会导致引发异常。,2、显式枚举转换为: 从sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double或decimal 到任何枚举类型。 从任何枚举类型到sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double或decimal。 从任何枚举类型到任何其他枚举类型。 两种类型之间的显式枚举转换是通过将任何参与的枚举类型都按该枚举类型的基础类型处理,然后在结果类型之间执行隐式或显式数值转换。例如,给定具有int基础类型的枚举类型E,从E到byte的转换按从int到byte的显式数值转换处理,而从byte到E的转换按从byte到int的隐式数值转换处理。,3、用户定义的显式转换由以下三个部分组成:先是一个标准的显式转换(可选),然后是执行用户定义的隐式或显式转换运算符,最后是另一个标准的显式转换(可选)。,从S类型到T类型的用户定义的显式转换按下面这样处理: 1)查找类型集D,将从该类型集考虑用户定义的转换运算符。该类型集由S(如果S为类或结构)、S的基类(如果S为类)、T(如果T为类或结构)和T的基类(如果T为类)组成。 2)查找适用的用户定义转换运算符集合U。集合U由用户定义的隐式或显式转换运算符组成,这些运算符是在D中的类或结构内声明的,用于从包含S或被S包含的类型转换为包含T或被T包含的类型。如果U为空,则转换未定义并且发生编译时错误。 3)在U中查找运算符的最精确的源类型SX:,如果U中的所有运算符都从S转换,则SX为S。 否则,如果U中的所有运算符都从包含S的类型转换,则SX在这些运算符的合并源类型集中是被包含程度最大的类型。如果找不到最直接包含的类型,则转换是不明确的,并且发生编译时错误。 否则,SX在U中运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编译时错误。,4)在U中查找运算符的最精确的目标类型TX: 如果U中的所有运算符都转换为T,则TX为T。 否则,如果U中的所有运算符都转换为被T包含的类型,则TX在这些运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型,则转换是不明确的,并且发生编译时错误。,否则,TX在U中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到这样的被包含程度最大的类型,则转换是不明确的,并且发生编译时错误。,5)如果U中正好含有一个从SX转换到TX的用户定义转换运算符,则这就是最精确的转换运算符。如果不存在此类运算符,或者如果存在多个此类运算符,则转换是不明确的,并且发生编译时错误。否则,将应用用户定义的转换: 如果S不是SX,则先执行一个从S到SX的标准显式转换。 调用最精确的用户定义转换运算符,以从SX转换到TX。 如果TX不是T,则再执行一个从TX到T的标准显式转换。,标准显式转换包括所有的标准隐式转换以及一个显式转换的子集,该子集是由那些与已知的标准隐式转换反向的转换组成的。换言之,如果存在一个从A类型到B类型的标准隐式转换,则一定存在与其对应的两个标准显式转换(一个是从A类型到B类型,另一个是从B类型到A类型)。,4.3.3类的引用转换 1、隐式引用转换为: 从任何引用类型到object。 从任何类类型S到任何类类型T(前提是S是从T派生的)。 从任何类类型S到任何接口类型T(前提是S实现了T)。 从任何接口类型S到任何接口类型T(前提是S是从T派生的)。 从元素类型为SE的数组类型S到元素类型为TE的数组类型T(前提是以下所列的条件均为真):,1)S和T只是元素类型不同。换言之,S和T具有相同的维数。 2)SE和TE都是引用类型。 3)存在从SE到TE的隐式引用转换。,从任何数组类型到System.Array。 从任何委托类型到System.Delegate。 从null类型到任何引用类型。 隐式引用转换是指一类引用类型之间的转换,这种转换总是可以成功,因此不需要在运行时进行任何检查。 引用转换无论是隐式的还是显式的,都不会更改所转换的对象的引用标识。换言之,虽然引用转换可能改变该引用的类型,但决不会更改所引用对象的类型或值。,2、显式引用转换为:,从object到任何其他引用类型。 从任何类类型S到任何类类型T(前提是S为T的基类)。 从任何类类型S到任何接口类型T(前提是S未密封并且S不实现T)。 从任何接口类型S到任何类类型T(前提是T未密封或T实现S)。 从任何接口类型S到任何接口类型T(前提是S不是从T派生的)。 从元素类型为SE的数组类型S到元素类型为TE的数组类型T(前提是以下所列条件均为真): 1)S和T只是元素类型不同。换言之,S和T具有相同的维数。 2)SE和TE都是引用类型。 3)存在从SE到TE的显式引用转换。,从System.Array以及它实现的接口到任何数组类型。 从System.Delegate以及它实现的接口到任何委托类型。,显式引用转换是那些需要运行时检查以确保它们正确的引用类型之间的转换。 为了使显式引用转换在运行时成功,源操作数的值必须为null,或源操作数所引用的对象的实际类型必须是一个可通过隐式引用转换转换为目标类型的类型。如果显式引用转换失败,则将引发System.InvalidCastException。 引用转换无论是隐式的还是显式的,都不会更改被转换的对象的引用标识。换言之,虽然引用转换可能更改引用的类型,但决不会更改所引用对象的类型或值。,4.3.4装箱与拆箱,装箱转换允许将值类型隐式转换为引用类型。将值类型的一个值装箱包括以下操作:分配一个对象实例,然后将值类型的值复制到该实例中。 装箱转换允许将“值类型”隐式转换为“引用类型”。存在下列装箱转换: 从任何“值类型”(包括任何“枚举类型”)到类型object。 从任何“值类型”(包括任何“枚举类型”)到类型System.ValueType。 从任何“值类型”到“值类型”实现的任何“接口类型”。 从任何“枚举类型”到System.Enum类型。 将“值类型”的值装箱的操作包括:分配一个对象实例并将“值类型”的值复制到该实例中。,最能说明“值类型”的值的实际装箱过程的办法是,设想有一个为该类型设置的装箱类。对任何“值类型”的T而言,装箱类的行为可用下列声明来描述:,sealed class T_Box: System.ValueType T value; public T_Box(T t) value = t; ,T类型值v的装箱过程现在包括执行表达式new T_Box(v)和将结果实例作为object类型的值返回。因此,下面的语句:,int i = 123; object box = i; 在概念上相当于: int i = 123; object box = new int_Box(i); 实际上,像上面这样的T_Box和int_Box并不存在,并且装了箱的值的动态类型也不会真的属于一个类类型。相反,T类型的装了箱的值属于动态类型T,若用is运算符来检查动态类型的话,也仅能引用类型T。例如,,int i = 123; object box = i; if (box is int) Console.Write(“Box contains an int“); ,将在控制台上输出字符串“Box contains an int”。 装箱转换隐含着复制一份欲被装箱的值。这不同于从引用类型到 object 类型的转换,在后一种转换中,转换后的值继续引用同一实例,只是将它当作派生程度较小的object类型而已。例如,设有下列的声明: struct Point public int x, y; public Point(int x, int y) this.x = x; this.y = y; ,则下面的语句: Point p = new Point(10, 10); object box = p; p.x = 20; Console.Write(Point)box).x);,将在控制台上输出值10,因为将p赋值给box是一个隐式装箱操作,它将复制p的值。如果将Point声明为class,由于p和box将会引用同一个实例,因此输出值为20。 拆箱也称为取消装箱转换。取消装箱转换允许将引用类型显式转换为值类型。一个取消装箱操作包括以下两个步骤:首先检查对象实例是否为给定值类型的一个装了箱的值,然后将该值从实例中复制出来。,取消装箱转换允许将引用类型显式转换为值类型。存在以下取消装箱转换:,从类型 object 到任何值类型(包括任何枚举类型)。 从类型 System.ValueType 到任何值类型(包括任何枚举类型)。 从任何接口类型到实现了该接口类型的任何值类型。 从 System.Enum 类型到任何枚举类型。 取消装箱操作包括以下两个步骤:首先检查该对象实例是否是某个给定的值类型的装了箱的值,然后将值从实例中复制出来。,参照前面关于装箱类的描述,从对象box到值类型T的取消装箱转换相当于执行表达式 (T_Box)box).value。因此,下面的语句:,object box = 123; int i = (int)box; 在概念上相当于: object box = new int_Box(123); int i = (int_Box)box).value; 为使到给定值类型的取消装箱转换在运行时取得成功,源操作数的值必须是对某个对象的引用,而该对象先前是通过将该值类型的某个值装箱而创建的。如果源操作数为null,则将引发System.NullReferenceException。如果源操作数是对不兼容对象的引用,则将引发System.InvalidCastException。,4.4 结构和接口,4.4.1 结构 结构与类有很多相似之处:结构可以实现接口,并且可以具有与类相同的成员类型。然而,结构在几个重要方面不同于类:结构为值类型而不是引用类型,并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。细心的程序员有时可以通过聪明地使用结构来增强性能。 声明一个结构,它有
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
提示  人人文库网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
关于本文
本文标题:大学C语言程序设计-李继武 彭德林-课件PPT
链接地址:https://www.renrendoc.com/p-21836185.html

官方联系方式

2:不支持迅雷下载,请使用浏览器下载   
3:不支持QQ浏览器下载,请用其他浏览器   
4:下载后的文档和图纸-无水印   
5:文档经过压缩,下载后原文更清晰   
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

网站客服QQ:2881952447     

copyright@ 2020-2025  renrendoc.com 人人文库版权所有   联系电话:400-852-1180

备案号:蜀ICP备2022000484号-2       经营许可证: 川B2-20220663       公网安备川公网安备: 51019002004831号

本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知人人文库网,我们立即给予删除!