欢迎来到三一办公! | 帮助中心 三一办公31ppt.com(应用文档模板下载平台)
三一办公
全部分类
  • 办公文档>
  • PPT模板>
  • 建筑/施工/环境>
  • 毕业设计>
  • 工程图纸>
  • 教育教学>
  • 素材源码>
  • 生活休闲>
  • 临时分类>
  • ImageVerifierCode 换一换
    首页 三一办公 > 资源分类 > PPT文档下载  

    Delphi课件第6章.ppt

    • 资源ID:5386334       资源大小:1.21MB        全文页数:48页
    • 资源格式: PPT        下载积分:10金币
    快捷下载 游客一键下载
    会员登录下载
    三方登录下载: 微信开放平台登录 QQ登录  
    下载资源需要10金币
    邮箱/手机:
    温馨提示:
    用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)
    支付方式: 支付宝    微信支付   
    验证码:   换一换

    加入VIP免费专享
     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    Delphi课件第6章.ppt

    第6章 Delphi中类的应用,内容提要,面向对象的相关概念 类的定义 类的成员 类的特性 共同祖先Tobject简介 Delphi中的VCL 自定义类,6.1 面向对象的相关概念,1.对象 对象是现实世界中一类具有某些共同特性的事物的抽象。对象是构成系统的元素,是组成问题域的事物。这里问题域指应用系统所要解决和问题。小到一个数据,大到整个系统都是对象。对象不仅仅是物理对象,如写字台、手机等,还可以是某一类概念实体,如操系统中进程、室内照明的等级等都是对象。,2.消息 消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了。例如,单击鼠标,改变窗口尺寸,按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型及其他信息。例如,对于单击鼠标所产生的消息来说,其记录类型为TMsg,同时消息记录中还包含了单击鼠标时的坐标信息。对象进行处理及相互之间的联系,都只能通过消息传递来实现,发送消息的对象叫发送者,接受消息的对象叫接收者。发送者可以同时向各个对象传递消息;接受者可同时接受多个对象发来的消息。,3.类 类定义的是对象的类型,是对一组性质相同的对象的描述.它用于描述对象的所有性质,包括外部特性和内部实现。通过消息及相应的处理能力的描述,定义对象的外部特性;通过内部状态和处理能力的实现来描述定义对象的内部实现。在程序运行时,类被作为样板建立对象。对象和类的关系,就如同前面介绍的变量和类型的关系。4继承 所谓继承就是一个新的类类型,不必什么都重新定义,只需要继承一个已有的类型再加上自己的成员就构成一个新的类类型。继承是一个对象可以获得另一个对象特性的机制。类是一种层次结构,类的上层可以有子类。当某个类定义了某个特征后,所有在它下面的类都不得包启了该特征。类此子类直接继承其父类的全部描述,这叫传递性。类可以有多个父类,这叫多重继承,如果只能有一个父类,叫简单继承。Delphi中的类是简单继承。,6.2 类的定义,从程序设计的角度讲,类是一种数据类型,是一种特殊的数据类型,不过类定义了一种由字段、属性和方法3部分构成的数据结构。声明一个类的过程,也就是创建字段(Fields)、属性(Property)、方法(Method)的过程。字段:是类的内部数据变量。属性:是类提供给外部使用的数据变量。方法:是类中定义的函数和过程。三者统称为类的成员。类成员字段、属性与方法在类中有不同的作用。下面首先学习如何定义类。,6.2.1 类的定义,类类型声明的一般形式:Type 类名=Class(父类名)类成员 End;(1)类名可以是任何合法的标识符,不过Delphi有一个约定,使用大写字母T作为前缀来标识类类型。(2)Class是保留字,它表明声明的类型是一个类类型。(3)Class后的括号内为父类名,表明当前声明的类派生于父类名指定的类。(4)Class后的括号是可选的,如果不指明其父类则表示新声明的类直接从Dephi的Tobject类继承而来。,6.2.2 类的实例化,类的实例化就是利用类的方法创建对象的过程。方法:(1)首先声明对象变量,形式如下:Var对象变量名:类名:(2)创建对象变量,分两种情况。如果类是直接由Tobject继承而来,就用如下形式创建对象:对象变量名:=类名.Create;如果类重载或覆盖了Tobject的构造方法(Create),则创建对象的语句形式如下:对象变量名:=类名.构造方法(参数表);例如,如下代码就是Taverage中创建一个对象的过程:Var average:Taverage;/声明对象变量 average:=Taverage,Create;/创建对象实例,6.2.3 类运算符,1.类型判断运算符 is is 运算符用来检测一个类是否与另一个类兼容,即左操作符是否是右操作规程符的同类或子类,其语法形式如下:对象变量 is 类名 如果返回值为True,那么对象变量是类或其派生类的一个实例。如果对象为nil,返回值为false。在Windows程序设计中,经常使用is运算符判断组件的类型,例如:For I:=0 to ComonemtCount-1 do If Componentsi is Tedit then Tedit(ComponentsI).Text:=;以上代码,逐一检测窗体中的组件是否为编辑框,如果某组件是编辑框,则清空该编辑框。,2.类型强制转换运算符as as运算符是用来进行强制类型转换的,其语法形式如下:对象变量 as 类名 as在进行类型强制转换时首先测试,然后进行转换,若转换不成功,则引起异常ElnvalidCast。例如,Componentsi as Tedit 这条语句相当于以下语句:If Components i is Tedit then Tedit(Components i)Else Raise ElnvalidCast.Create;,6.2.4 类指针,类引用即是指向类的指针,而不是指向对象的指针,定义类引用类型和类引用变量的语句格式如下:Type类引用类型=class of 类名 Var 类引用变量名:类引用类型;例如,在Taverage中定义 Type Taverage Ref=Class of Taverage;var aRef:Taverage Ref;,6.3 类的成员,类成员由字段、属性和方法组成。,6.3.1 类的字段,类的字段也称为数据域,用来存储一个实例(对象)的信息,基本上可看成是一个变量,它可以是一组Delphi支持的类型变量的集合。,6.3.2 类的属性,类的属性用来描述类的实例(对象)的特征,它是访问对象数据的接口。属性声明中含有访问指令符(Read,Write),访问指令符用来决定属性的读写方法。属性控制如何使用属性过程设置或返回一个值。,由以上描述可知,属性在类中的功能主要有两个:一个是设置属性值,一个是返回属性值,与这两个功能对应的有两个存取程序,这两个存取程序分别用Read和Write关键字来定义,Read块用于获取属性的值,而Write块用于设置属性的值。也可以忽略一个块来创建只读属性或只写属性。不过属性至少要包含一个块才是有效的。定义属性用到关键字Property,属性的一般定义形式:Property属性名:属性类型Read字段或方法Write字段或方法 Default默认值;,6.3.2 类的属性,6.3.3 类的方法,类的方法就是在类中定义的一个过程或函数。类的方法需要先声明后实现。方法的声明在类的声明处进行,且只包含过程或函数的首部,而方法的实现要在单元的实现部分完成。Delphi中有以下几种方法:一般方法、构造方法、析构方法及类方法。,1.一般方法 此类方法在类内定义,在单元内实现,既可以是过程也可以是函数。(1)定义一般方法的语句格式如下:Type 类名=class(父类名)保护方式关键字(Public、Private等)Procedure方法名(参数表);Function方法名(参数表):返回值类型;.End;,(2)实现一个方法的语句格式如下:Procedure类名方法名(参数表);或者Function 类名方法名(参数表):返回值类型;常量、变量等定义 Begin 执行语句;End;(3)调用一个方法的语句格式如下:对象变量方法名(实际参数);注:一般方法的实现方法名前面要加上类名的限定。,2.构造方法 构造方法是一种特殊的方法,用来创建类的对象并对其进行初始化。在声明类的对象后,并没有创建该对象,只是定义了指向该类类型的一个指针,对象的创建和初始化工作是由类的构造方法来完成。在定义构造方法时,使用保留字constructor,名称通常为create。(1)定义构造方法的语句格式如下:Constructor 构造方法名(参数表);(2)实现构造方法的语法如下:Constructor类名.构造方法名(参数表);(3)调用构造方法的语句格式如下:对象变量名:=类名.构造方法名(参数表);,3.析构方法析构方法用来释放类的对象,并且释放对象中的其他数据结构。在定义析构函数时,使用保留字Destructor,函数名通常为Destroy。(1)定义析构函数的语句格式为:Destructor析构方法名(参数表);(2)实现析构方法时按照如下语法:Destructor类名.析构方法名(参数表);(3)调用析构方法的语句格式如下:对象变量名.析构方法(参数表);析构方法不是必须的,只有在构造方法中分配了内存、使用了资源、打开了文件或数据库,才需要析构方法做善后处理工作。,4.类方法Object pascal中还有一种称为类方法的特殊方法,类方法跟构造有些相似,其相似之处在于它们都能由类来引用,而不必先创建一个对象实例,也就是说类方法不依赖于任何类的具体实例。一般方法只能被类的实例调用,而类方法既可以被对象实例调用,也可以被类本身引用。类方法只是表明这个方法在逻辑上与这个类有联系。类方法可以是过程,也可以是函数,类方法在类结构中定义,与一般方法的区别是在关键Procedure或之前加一个Class关键字。例如:Type TClass=Class Class Function GetClassName:String;End;上例中,声明了一个类方法,它是一个返回类型为字符串的函数。,在程序中,可以直接由类来引用类方法,例如:Var MyString:String;MyString:=TClass.GetClassName;(1)定义类方法的语句格式如下:Class Procdure 类名.类方法名(参数表);或者 Class Function 类名.类方法名(参数表);(2)调用类方法的格式如下:类名.类方法名(参数表);或者 对象变量名.类方法名(参数表);,6.3.4 方法的类型,一个类中的方法,可以在声明时使用不同的指示字指定为静态的、动态的、虚拟的、抽象的、消息处理程序的方法。这些指示字是Static,Dynamic,Virtual,Message,Abstract,如果不加方法指示字,系统默认为静态方法(Static)。1.静态方法 在声明方法时如果没有使用指示字,该方法即为静态方法。当一个静态方法被调用时,方法名前的对象变量的类名决定调用的是哪个类的方法。例如以下代码中的Fly方法即为静态方法。Type TPlane=class Procedure Fly;End;Tjet=class(Tplane)Procedure Fly;End;,2.虚拟方法 虚拟方法比静态方法更灵活、更复杂。虚拟方法的地址不是在编译时确定的,而是在程序运行期间根据调用这个虚拟方法的对象实例来确定的,这种方法又称为滞后联编。虚拟方法通过指示字Virtual来声明,声明的一般形式如下:procedure 方法名(参数表);Virtual;function 方法名(参数表):返回值类型;Virtual;Constructor 方法名(参数表);Virtual;Destructor 方法名(参数表);Virtual;,【例6.4】在Tplane(父类)中声明虚拟方法Fly,在子类Tcopter和Tjet中覆盖父类中的同名方法Fly。TypeTplane=classprocedure fly;virtual/声明虚拟方法end;然后,从Tplane派生出两个子类,Tcopter和Tjet:Tcopter=class(Tplane)privatefModal:String;publicprocedure fly;override;/覆盖父类中的方法end;Tjet=class(Tplane)privatefModal:String;publicprocedure fly;override;/覆盖父类中的方法end;,调用虚拟方法:var plane:Tplane:beign plane:=Tcopter.Create;plane.Fly;/调用Tcopter类的Fly方法 plane.destroy;palne:=Tjet.create;plane.Fly;/调用Tjet类的Fly方法 plane.Destroy;end;,3.动态方法 动态方法的使用与虚拟方法相同,只是在内部实现上,虚拟方法速度较快,但存储空间耗用大,而动态方法的存储空间耗用少,但降低了速度。动态方法通过指示字Dynamic来声明,一般形式如下:procedure 方法名(参数表);Dynamic;function 方法名(参数表):返回值类型;Dynamic;Constructor 方法名(参数表);Dynamic;Destructor 方法名(参数表);Dynamic;如下代码声明了一个动态方法fly:TypeTplane=classprocedure fly;Dynamic;/声明动态方法end;,4.抽象方法所谓抽象方法,首先必须是虚拟的或动态的,其次在它所在类中只能声明而不能定义,只能在派生类中定义它。因此定义一个抽象方法,只是定义它的接口,而不是定义底层的操作。声明抽象方法使用abstract指示字,例如:typeTdesign=class(Object)Procedure Draw:Virtual;abstract;end;,5.消息处理程序的方法在方法的调用约定之后加上一个message,就可以定义一个消息处理程序方法,消息处理程序方法主要用于相应并处理某个特定的事件。声明消息处理方法使用message指示字,例如:type Tbox=class(Tcustomcontrol)Private Procedure WMChar(Var Message:TWMchar);message WMCHAR;end;,6.3.5 覆盖与重载,由以上学习可体会到:虚拟方法就是允许被其子类重新定义的成员函数。而子类重新定义父类虚拟方法的做法,称为“覆盖”,或者称为“重写”。重载,是指允许存在多个同名函数,而这些函数的参数表不同。其实,重载的概念并不属于“面向对象编程”,重载的实现是编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。,6.4 类的特性,类有三大特性:封装性继承性多态性,6.4.1 类的封装性,类是由封装在一起的数据和方法构成的。所谓封装指的是一个类中的有些成员对其他类来说是不可能直接访问的,这些成员只能由类本身的方法或属性来访问。Object Pascal中通过控制类成员的可见性来实现类成员的封装。一个类中,成员的可见性通过5个保留字来控制,这5个保留字是:private、public、protected、published和automated。1.Private保留字Private表示一个类的成员是这个类所私有的,在声明 这个类的单元或程序之外是不可见的。也就是说,在包含这个类的单元中,可以对定义为私有的字段和方法进行访问,而对于其他类包括它的派生类,Private部分声明的成员都是可见的,这就是面向对象编程中的数据保护机制。,2.Public 保留字Public表示一个类的成员是公有的,在声明这个类的单元或程序之外是可见的,这意味着在程序的任何地方都可以直接访问这些成员3.Protected 保留字Protected表示一个类的成员是受保护的,受保护的成员只能被当前类和当前类的子类所访问。在Protected部分声明的成员通常是方法,这样在派生类中访问这些方法,而不需要知道这些方法实现的细节。,4.Published 保留字Published声明的成员是发行类型的成员。从成员的可见性来说,发行成员是最高的。公有成员和发行成员都是公共的,能够被其他类的实例引用,也就是在运行期间可以随意访问。两者的区别在于公有成员在运行期间是可以访问的,而发行成员在设计期间和运行期间都可以被访问。5.Automated 保留字Automated与Public基本相同,惟一的区别在于声明为Automated的的成员会产生自动化类型信息。自动化类型信息是自动化服务器所要求的。因此,只有定义或应用自动化对象时才需要使用Automated成员类型。,6.4.2 类的继承性,类类型具有可继承性。所谓继承就是一个新的类类型,不必什么都重新定义,只需要继承一个已有的类型再加上自己的成员就构成一个新的类类型。被继承的类称为基类,继承下来的类称为派生类,基类的成员自动成为派生类的成员。类的继承具有传递性,例如假设T3继承了T2,而T2又是继承了T1,可以认为T3也继承T1。在Delphi中,所有的类都是从一个共同的类TObject继承下来的。,6.4.2 类的继承性,在Delphi中继承性很容易实现,只需在定义子类时指出它希望继承的父类即可。例如,在建立新窗体时,Delphi会自动创建Tform1类,代码如下:type TForm1=class(TForm)private Private declarations public Public declarations end;这表明,Tform1是Tform的子类,我们可以在不添加任何组件的情况下改变窗体的属性,如Caption、Width、Height等,这些属性都是从Tform类继承来的。,6.4.3 类的多态性,相同的函数调用被不同的对象接受时,会导致完全不同的行为,这种现象称为多态性。利用多态性,程序中只需进行一般形式的函数调用,函数的实现细节留给接受函数调用的对象。多态性是问题求解的面向对象方法的一种关键性能。在Object Pascal中,多态性是通过虚拟方法或动态方法实现的。参见:例6-5 例题中的关键问题是,当程序调用Fly时,究竟调用的是哪个Fly,是基类的Fly还是派生类的Fly 呢?,6.5 共同祖先Tobject简介,TObject是所有子类的祖先类。如果不指定父类的话,Delphi便指定父类是TObject。TObject声明了一些方法,这些方法可以被继承到子类中,有些还可以被覆盖。它的方法分为以下三类:(1)构造和析构方法。每个对象都有一个构造方法Create用来初始化对象。对象可以调用NewInstance分配内存,然后用将内存清0。每个对象还有一个析构方法Destroy,当对象建立后,这个函数只被调用来释放对象。(2)类方法。使用类方法ClassInfo、ClassName、ClassParent、ClassType、InheritsFrom及InstanceSize,可以得到关于该类或它的实例的有用信息。还可以得到块内属性和方法的实时类型信息:FieldAddress、MethodAddress及MethodName。(3)消息处理。使用Dispatch和DefaultHandler调用每个对象的内置消息处理方法。,6.6 Delphi中的VCL,Delphi中所有的构件继承于TObject,TObject是所有子类的祖先,它支持多态性,并且包含构造方法Create和析构方法Destroy。,6.6.1 VCL的层次结构,VCL的层次结构如图6-2所示。(详情请见P190),6.6.2 自定义组件,Delphi的构件使用和构件制作采用同样的工作环境和相似的编程方法,只要弄清基本原理,制作构件无需学习多少新东西。制作构件的基本过程可以概括为:编写构件单元(unit)。这包含构件声明和构件实现代码。按照与普通Delphi单元同样的方法编译和调试构件单元。创建构件注册单元。其中用uses语句连接构件单元,并用register过程完成构件的注册。编写构件联机帮助信息,并编译成标准Windows帮助文件。全部工作完成后,生成构件单元二进制文件(dcu)、构件注册源文件(pas)和帮助信息文件(hlp)及附加的关键词文件(kwf)。用户拿到这些文件后,就可以安装使用了。,1.确定一个父类 制作构件第一件事就是先择Delphi对象类型作为父对象,以派生新的对象。子对象可以继承父对象的全部非Private部件,但不能摆脱不需要的部件。因此,所选的对象应尽可能多的包含对象所需的属性、时间和方法,但不应包含子对象不需要的东西。2.添加属性 在构件中,属性和方法往往可以相互替代。对构件用户来说,属性比方法更直观简便。因此,只要可能,应尽量以属性取代方法。属性类型包括简单类型、枚举类型、集合类型、对象类型和数组类型。其定义方法如下:typeprivateflayers:integer;内部存储用的变量function getlayers:integer;用来读属性值的方法procedure setlayers(alayers:integer);用来写属性值的方法publishedproperty layers:integer read getlayers write setlayers default 1;end;,创建一个新组件的过程,3.事件与事件处理过程 创建构件时,事件也被当做属性来处理,区别仅在于事件必须定义为过程类型,使其成为一个隐蔽指针,指向某个潜在的过程。当构件用户为事件指定处理子程序后,事件便成为指向该子程序的指针。事件的定义方式如下:typeprivateFonclick:Tnotifyevent;声明事件变量以保存过程指针publishedproperty Onclick:Tnotifyevent read Fonclick write Fonclick;end;此例正是Delphi标准组件中Click事件的定义方式。,创建一个新组件的过程,4方法处理 方法处理在创建构件时和使用构件时没有多大区别,但有些问题仍需要注意。首先要注意的是,构件通常是在事件处理过程中调用,而构件作者又无法预测用户将在什么环境下如何调用构件。因此,构件中的方法应尽量避免占用系统资源,避免使Windows停止对用户操作的反应。创建构件时应随时意识到,此构件不仅可以直接调用,而且可用来创建别的构件。即使是对用户隐蔽的方法也应具有完整的功能和清晰的接口。除了属性读写方法之外,内部方法一般应声明为Protected虚方法,以便被派生对象继承和重载。属性读写方法则应采用Private声明严密保护。派生对象如果需要读写父对象的属性值,应该访问属性本身,没有必要直接访问其读写方法。,创建一个新组件的过程,5.构件测试 制作构件的核心工作是编写构件单元,构件单元的设计方式与一般Delphi单元没有什么不同,只是构件单元 中不能包含窗体。测试时,需先建立一个窗体单元,然后进行以下操作:(1)把被测构件单元名称加入窗体单元的Uses语句中,并在Public部分声明被测构件的对象实例。(2)在窗体单元的FormCreate子程序中调用被测构件的Create方法,以构造构件实例,其Ownet参数设置为Self,即窗体本身。然后给Parent属性赋值,并适当设置其他属性值。Parent是容纳构件的父对象,如果是窗体本身,应设置为Self。(3)运行包含测试窗体的工程,找出构件程序中的错误。,创建一个新组件的过程,6.注册构件 注册构件用的程序代码可以放在构件单元中,但在Delphi下注册构件时要求提供包含注册代码的源程序文件,因此,比较好的方式是把构件核心代码编译成.dcu文件或.dll动态链接库,在注册源文件中只放注册代码和外围程序。7.提供联机帮助 Delphi的帮助信息与Windows一般帮助信息结构基本上相同,其编写方法可参见有关资料。但Delphi包含一个特殊的帮助搜索引擎,能跨越多个帮助文件搜索关键词。因此,在构件帮助文件中不仅要有普通k型关键词脚注,还要包含Delphi所用的B型关键词脚注。,创建一个新组件的过程,6.7 自定义类,在Delphi中定义一个类要用到关键字Class。例如,下面的一小段代码就定义一个名字为Employee的类:TypeTEmployee=Class End;在对类命名时,微软推荐使用语言的命名规则。根据这种命名规则,就意味着类名的第一个字母必须大写,并且后面的并发连接词的第一个字母均为大写。,6.7 自定义类,一般自定义类的语法结构如下:Type TClassName=Class(TObject)public public fields public methods protected protected fields protected methods private private fields private methodsend;参见:例6-6,程序错误与处理异常处理机制异常类,发现程序错误语法错误(syntax error)又称编译错误,编写的语句不符合语法规范。运行错误(run-time error)程序虽然通过了编译,但执行时却发生了无效操作,即运行错误。逻辑错误(logic error)已通过编译,也没出现运行错误,但运行结果与期望值不一样,则程序出现了逻辑错误。启用调式器选择Tools|Debugger Option菜单项,在出现的对话框中选中复选框Integerated Debugger即可设置编译环境,程序错误与处理,异常概述异常(Exception)是一种特殊的事件,通俗地讲就是错误。异常响应异常保护异常保护处理程序可保证代码执行以后分配的资源能被释放,将欲保护的代码放于tryfinally块中。Try 语句序列1;Finally语句序列2;End;,异常处理机制,编写异常处理程序try语句序列1;excepton 标识符:异常类 do 语句1;on 标识符:异常类 do 语句2;else语句序列;end;,Exception类运行时间库异常外部错误异常类EExternal类型匹配异常类EInvalidCast 类型转换异常类EConvertError I/O异常类EInOutError 堆异常类EHeapException 对象异常类流异常类EStreamError 打印异常类EPrinter 图形异常类EInvalidGraphic和EInvalidGraphicOperation 字符串序列异常EStringListError和EListError 组件异常类通用组件异常类 专用组件异常类,异常类,

    注意事项

    本文(Delphi课件第6章.ppt)为本站会员(sccc)主动上传,三一办公仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知三一办公(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    备案号:宁ICP备20000045号-2

    经营许可证:宁B2-20210002

    宁公网安备 64010402000987号

    三一办公
    收起
    展开