• delphi 基础之二 面向对象概念初步


    面向对象概念初步

    •类自动生成 快捷键:ctrl+shift+c

    1.类的定义

    类是用户创建的数据类型,包括状态、表达式和一些操作。有3个组成部分,即字段、方法和属性。字段是类的内部数据变量,方法就是类中定义的函数和过程,属性是类提供给外部使用的数据变量。

    类的定义分两步:首先在类(单元)的接口(interface)部分说明这个方法.然后在实现部分(implementation)部分编写方法的实现代码.

     定义:

    ******************************************************

    interface

    type

    类名=class(父类名)

     数据域说明;      //类内部使用变量/常量的声明;

    方法说明首部;

    end;

     implementation

    实现代码:

    procedure 类名.方法(参数);

     实现代码;

    end;

    ******************************************************

    2.创建对象及对象成员的引用

    创建对象分两步:

    (1)首先声明对象,语法格式为:

    Var

    对象名 : 类名 ;   //此时对象名还只是个指针,没分配到内存。

    (2)然后调用create 分配内存 语法格式为:

    对象名 :=<类名>.create;

    对象中数据域(C++中的数据成员)和方法的引用 语法如下:

      对象.数据域;

      对象.方法;

      释放对象

    对象.free;

    3.类的封装

    在Object Pascal中,通过保留字Private、Protected、Public、Published、Automated来实现类的封装,通常称为存取控制符。

    Private:私有成员不能被类以外的程序访问。

    Protected:Protected与Private 相似,所不同的是保护成员可以被该类的所有派生类访问,并且成为派生类的私有成员。

    Public:字段和方法公有,外部程序可以访问。

    Published:公共成员可以被外部程序访问,与Public区别在于Published成员可以在设计期间和运行期间可以访问,而Public成员只能在运行期间访问。

    Automated: Automated声明的方法和属性将生成OLE(对象链接与嵌入)自动化操作的类型信息

     类的的数据域(数据成员)

    字段:是类的内部数据变量

    方法:就是类中定义的函数和过程。

    属性:是类提供给外部使用的数据变量

    1)Fields  (字段)

    字段就像属于对象的一个变量,它可以是任何类型,包括类类型 (也就是说,字段可以存储对象的引用)。 字段通常具有private 属性。

    2)方法:

    1)方法的类型

    对象的方法能定义成静态( static )、虚拟( virtual )、动态( dynamic)或消息处理(message )。

    ①静态方法

       静态方法是方法的缺省类型,对它就像对通常的过程和函数那样调用。编译器知道这些方法的地址,所以调用一个静态方法时它能把运行信息静态地链接进可执行文件。 静态方法执行的速度最快,但它们却不能被覆盖来支持多态性。

    Type

          TAnimal = class(TObject)

          procedure Sound;

          procedure Sleep;

          ... ...

    end;
          ... ...

    TDog = class(TAnimal)

          procedure Sound;

          function Sleep:Integer;

          ... ...

    end;

    静态方法不能被重载,上例中,派生类的静态方法只是替换了基类的静态方法。

     

     ②虚拟方法(virtual

    使用保留字virtual将函数定义为虚拟的。编译器通过建立虚拟方法表(VMT)来查找在运行时的函数地址。

    Type

           CMyClass = Class

           function Method : String; virtual;

    end

    虚函数牺牲空间,提高效率。

    由于虚拟方法能被覆盖,在代码中调用一个指定的虚拟方法时编译器并不知道它的地址。因此,编译器通过建立虚拟方法表 ( V M T )来查找在运行时的函数地址。所有的虚拟方法在运行时通过V M T来调度,一个对象的V M T表中除了自己定义的虚拟方法外,还有它的祖先的所有的虚拟方法,因此虚拟方法比动态方法用的内存要多,但它执行得比较快。(速度优化)

    {虚拟方法还有一种特例,即抽象方法:例如

        procedure One;override;abstract;

    在One方法后面,不但有override关键字,还多了一个abstract关键字(意为抽象)。这种方法称为抽象方法(在C++中称为纯虚拟函数)。含有抽象方法的类称为抽象类。抽象方法的独特之处在于,它只有声明,而根本没有实现部分,如果你企图调用一个对象的抽象方法,你将得到一个异常。只有当这个类的派生类重载并实现了该方法之后,它才能够被调用。(在C++中,甚至根本就不能建立一个抽象类的实例。)

    既然如此,那么这种抽象方法又有什么用呢?

    抽象方法本身不能够做任何事情,必须在子类中被重载并实现,才能够完成有意义的工作。但抽象方法的存在,相当于为父类留下了一个接口,当程序将一个子类的对象赋予父类的变量时,父类的变量就可以调用这个方法,当然此时它运行的是相应的子类中重载该方法的代码。如果没有这个抽象方法,父类的变量就不能调用它,因为它不能调用一个只在子类中存在、而在父类中不存在的方法!

    ③动态方法 (dynamic

    使用保留字dynamic,与虚拟函数语法相同,使用结果相同,但编译器实现的机制不同。编译器通过建立动态方法表(DMT)来查找在运行时的函数地址。

    Type

           CMyClass = Class

           function Method : String; dynamic;

    end

    动态函数牺牲效率节省空间。

    动态方法跟虚拟方法基本相似,只是它们的调度系统不同。编译器为每一个动态方法指定一个独一无二的数字,用这个数字和动态方法的地址构造一个动态方法表 ( D M T )。不像V M T表,在D M T表中仅有它声明的动态方法,并且这个方法需要祖先的D M T表来访问它其余的动态方法。正因为这样,动态方法比虚拟方法用的内存要少,但执行起来较慢,因为有可能要到祖先对象的D M T中查找动态方法。(代码大小优化

          ④消息处理方法message

         在关键字message后面的值指明了这个方法要响应的消息。用消息处理方法来响应windows的消息,这样就不用直接来调用它。。声明方法时,通过包含message指示字创建,并在message后面跟一个介于+1~49151之间的整数常量,体指定消息的号码(ID)。一个message方法必须具有一个单一var参数的过程。例

    Typt

    Ttextbox=class(Tcustomcontrol)

    Private WMChar(var Message:TWMChar);message WM_CHAR;

    End;

    2)方法的覆盖

    在Object Pascal覆盖一个方法用来实现O O P的多态性概念。通过覆盖使一个方法在不同的派生类间表现出不同的行为。Object Pascal 中能被覆盖的函数必须是虚拟函数(virtual)或者是动态函数(dynamic)。为了覆盖一个方法,在派生类的声明中用override代替virtualdynamic 。如果不含override关键字,如果子类声明和父类相同的方法,则视为新建。

    Type

           CMyClass = Class

           procedure Method; virtual;

           end

           CMySubClass = Class(CMyClass)

           procedure Method; override;

    end

    3)方法的重载

    1.所有方法都支持重载在同一个类中,出现多个同名的方法的现象就是重载。有相同的方法名,不同的参数。用保留字overload关键字。

    Type           

    Ta=class
     
        procedure Method(a , b : Integer);
    overload;
      
       procedure Method(a , b : String);
    overload;    
     end

     

    2.若要重载的是虚方法则需要使用reintroduce指示字。

    type

      T1=class(Tobject)

         Procedure test(I:integer);virtual;

    End;

    T2=calss(T1)

    Procedure Test(S:string);reintroduce;oveload;;

    End;

    (4)方法的继承

    Inherited 关键字:

    在子类中可以执行父类中同名的方法。

    Type

           Tman = Class 

           procedure showinfo; virtual;

           end;

           TStudent = Class(Tman) 

           procedure showinfo; override;

           end;

    end

     

    procedure TStudent.showinfo ;

    begin

      inherited; //调用父类中的同名函数
    end;

    重载技术使得我们不但可以在派生类中添加基类没有的数据和方法,而且可以非常方便地继承基类中原有方法的代码,只需要简单地加入Inherited就可以了。如果你不加入Inherited语句,那么基类的相应方法将被新的方法覆盖掉。

    4)方法的构造函数和析构函数

    构造函数是用来创建和初始化新对象的,通常使用构造函数的参数进行初始化。构造函数使用保留字Constructor来定义。

    Type

           TClass = Class

           Name : String;

           Constructor Create(s : String);

    end;

     

    Constructor TClass.Create(s : String);

    begin

            Name = s;

    end;

    析构函数:

    当不需要一个对象时,在类的实例中调用类的析构函数来删除它。

    析构函数的关键字destructor

    destructor destroy

    destructor delete

    对象.Destroy;(最好使用 对象.free)

    3)属性

    普通属性

    我们在delphi的类中常常能看到这样的代码:

    propert property 属性名 类型名 read 字符串1 write 字符串2

    例如:

    property Left: Integer read FLeft write SetLeft;

    我们在private中看到申明:

    procedure SetLeft(Value: Integer);(方法)

    和如下代码实现:

    procedure TControl.SetLeft(Value: Integer);

    begin

      SetBounds(Value, FTop, FWidth, FHeight);

      Include(FScalingFlags, sfLeft);

    end;

    如果你写了如下代码改变left:control1.left:=23,那么程序调用了函数SetLeft(23),SetBounds是改变区域的函数,这里你就明白了它封装了的好处,每次你改变left时它就会根据新的left而改变区域的大小,这个函数同时也改变了Fleft的大小。

    这样外部就看起来只是通过赋值运算来改变了该属性的值。Read和write可以是变量,或者是函数,取决于你的设计。

    你当然可以这样写:

    propert property 属性名 类型名 read 变量1(函数) write 变量2(函数)。

    变量1和变量2可以是相同的。你也可以这样

    propert property 属性名 类型名 read 方法1 write 方法2。

    任你组合。但是有2点要注意:

    1.命名规则最好按习惯来,易于阅读。

    2. 如果是变量,那么类型要和属性的类型一致,如果是方法,那么入口参数要和属性的类型一致。

    声明部分:

     Type

    Tdog=calss

    Private

    Color:string;

    Function Getcolor(x:string);

    Procedure Setetcolor(x:string)

    Public

    Property x:string read Getcolor write setcoler;

    End;

    实现部分:

    Fenction Tdog.Getcolor :string;

    Begin

    Result:=color;

    End;

    Procedure Tdog.setcoler(value:string)

    Begin

    Color:=value;

    End;

    事件属性Tevent

    我们常常使用组件的事件属性,比方说click事件,可是我们很难从表面看出它是如何调用的呢,如何触发的呢。下面我来给你解答。

    我们在属性管理器object inspector中看到event页onclick右边对应了一个方法的名字。我们其实可以这样给一个组件的事件对应上一个出来方法。以一个form为例子Form1. OnMouseDown:=‘你的方法‘。注意方法的入口参数有讲究,这里是(Sender:TObject)

    我们还是一tcontrol为例子,我们找到这段代码:

    property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;跟上面讲的类似,不过这里有个特殊的类型,TNOtifyEvent,是个事件类型,我们找到它的申明:

    TMouseEvent = procedure(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer) of object;

    可以看到,它其实就是个函数,但是蓝色部分把入口参数限定了。那么我们通过赋值Form1. OnMouseDown:=‘你的方法‘,就对应了OnMouseDown的方法。然后我们只要写了一段拦截鼠标消息的函数,在里面直接或间接调用FonMouseDown,那么就把消息和处理函数对应上去了。这里它间接调用的层数比较多,讲起来比较费时间,涉及到Message类型,建议大家去看下李维的书。

    以下附上间接调用过程,其实还要很多消息发生时也间接调用了,就不一一举出来了:(

    procedure WMRButtonDblClk(var Message: TWMRButtonDblClk); message WM_RBUTTONDBLCLK;//拦截消息的函数

    procedure TControl.WMRButtonDblClk(var Message: TWMRButtonDblClk);

    begin

      inherited;

      DoMouseDown(Message, mbRight, [ssDouble]);

    end;

    procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton;

    Shift: TShiftState);

    procedure TControl.DoMouseDown(var Message: TWMMouse; Button: TMouseButton;

      Shift: TShiftState);

    begin

      if not (csNoStdEvents in ControlStyle) then

        with Message do

          if (Width > 32768) or (Height > 32768) then

            with CalcCursorPos do

              MouseDown(Button, KeysToShiftState(Keys) + Shift, X, Y)

          else

            MouseDown(Button, KeysToShiftState(Keys) + Shift, Message.XPos, Message.YPos);

    end;

     procedure MouseDown(Button: TMouseButton; Shift: TShiftState;

    X, Y: Integer); dynamic;

    procedure TControl.MouseDown(Button: TMouseButton;

      Shift: TShiftState; X, Y: Integer);

    begin

      if Assigned(FOnMouseDown) then FOnMouseDown(Self, Button, Shift, X, Y);

    end;

    好处:

    如果你多写自己的类,你会发现这样做是多么的方便, delphi你都只是调用contol1.text来访问,control1.text:=’某字符串’来修改它的值。

    而在处理消息方面,基类把onclick,onmousedown这样的属性申明为protected,如果你要使用,可以申明为published就可以出现在object inspector里面,然后方便的写处理方法,你也可以不公开,而在ctreate函数中给它赋值,而不用像java那样,写listener那么复杂。

    5.类的多态

     虚方法和动态方法通过覆盖和重载技术实现多态。

    6.类的继承

    7.类方法(前面所提静态方法不同,类方法就是通过类名就可以访问的方法)

    类方法是作用在类而不是对象上面的方法。类方法的定义必须以关键字class 开始,

    比如,

      type

         TFigure = class

         public

           class function Supports(Operation: string): Boolean; virtual;

           class procedure GetInfo(var Info: TFigureInfo); virtual;

           ...

         end;

    类方法的定义部分也必须以 class 开始,比如,

      class procedure TFigure.GetInfo(var Info: TFigureInfo);

      begin

         ...

      end;

    在类方法的定义部分,Self 表示调用方法的类

    类方法既可以通过类引用来调用,也可以使用对象,当使用后者时, Self 值等于对象所属的类。

    7.Self参数:

    Self是指所编的程序范围是在哪一个类中,Delphi中大都在窗体范围内编程,因此,Self即指窗体,假如在编写一个类或是一个组件,则Self指该类或该组件。我们在过程和函数的声明中可以看出Self是代表哪个组件,即Self代表"."号之前的组件.另外应注重,Self只能用在类方法中,而不能用在过程或函数中.

    Self指实例化对象本身,说得本质一些就是:self是当前正在执行本函数的那个对象的数据块的首地址

  • 相关阅读:
    PyCharm中的Console自动换行
    Jenkins 配置用户权限错误导致无法登录解决方案
    Jenkins进阶-用户权限管理(10)
    python3+Flask 链接MySQL 时,提示“No module named MYSQLdb”
    Mongo导出mongoexport和导入mongoimport介绍
    maven常用命令
    Git 的origin和master分析
    How to handle your webdriver exceptions
    Java中equals和==的区别
    Maven中-DskipTests和-Dmaven.test.skip=true的区别
  • 原文地址:https://www.cnblogs.com/bjxsky/p/4610249.html
Copyright © 2020-2023  润新知