前言:本文用于PowerBuilder程序员,PowerBuilder最初由Sybase发布,能快捷开发C/S程序或者多层应用系统。SAP以58亿美元现金收购Sybase,PowerBuilder 12.6是SAP收购Sybase后与2014发布的版本,也是最稳定的PB版本之一,本文基于12.6。这是第五部分:结构和对象。
5.结构和对象
5.1结构
5.1.1概述
结构是一个或多个变量(有时称为元素)的集合,程序员将这些变量放在一起,作为一个组存放在单个名称下。变量可以有任何数据类型,包括标准数据类型和对象数据类型以及其他结构。
5.1.2定义结构
当程序员在结构绘制器(Structure painter)或对象绘制器(object painter,如窗口、菜单或用户对象)中定义结构时,就是在创建一个结构的定义。要使用该结构,必须声明它。当声明它时,它的一个实例将自动创建。当它超出作用范围时,结构就会被摧毁。
5.1.3声明结构
如果在结构绘制器(Structure painter)中定义了名为str_emp_data的全局结构,那么可以在脚本或对象的实例变量中声明该结构的实例。如果在对象绘制器(object painter)中定义结构,那么只能在对象的实例变量和脚本中声明结构的实例。
下面这句声明了结构str_emp_data的两个实例:
str_emp_data str_emp1, str_emp2
5.1.4结构变量的引用
在脚本中,使用点符号引用结构的变量:
structurename.variable
下面这些语句为str_emp_data中的变量赋值:
str_emp1.emp_id = 100
str_emp1.emp_lname = "Jones"
str_emp1.emp_salary = 200
str_emp2.emp_id = 101
str_emp2.emp_salary = str_emp1.salary * 1.05
5.1.5用结构作为实例变量
如果结构被声明为对象的一部分,可以使用点符号限定结构名称:
objectname.structurename.variable
假设下面这句声明了窗口w_customer的一个实例变量:
str_cust_data str_cust1
在一个对象脚本中下面这句引用了变量str_cust_data。代词This是可选的,因为结构声明是对象的一部分:
This.str_cust1.name
在另外一个对象脚本中下面这句限定访问窗口下的结构的变量name。
w_customer.str_cust1.name
5.2对象
5.2.1什么是对象
在面向对象编程OOP中,对象是一个包含状态信息和相关方法的自包含模块。PowerBuilder中的大多数实体都是对象:可视对象(如窗口和窗口上的控件)、非可视对象(如事务和错误对象)以及程序员自己设计的用户对象。
对象类(object class, C++和Java中直接称类class)是对象的定义。程序员可以在适当的绘制器(painter)中创建对象的定义:窗口Window、菜单Menu、应用程序Application、结构Structure或用户对象User Object绘制器。在painter中,程序员可以添加控件作为对象的一部分,初始化对象的属性,定义其实例变量和函数,并为其事件和函数编写脚本。
对象实例是在执行应用程序期间创建的对象具象。程序员的代码在为对象分配内存时实例化对象,根据对象类中的定义来生成对象。
对象引用是对象实例的句柄。为了与对象交互,程序员需要它的对象引用,可以将对象引用赋值给适当类型的变量。
5.2.2系统对象与用户对象
PowerBuilder支持两类对象:由PowerBuilder定义的系统对象(也称为系统类)和在painter中定义的用户对象。
- 系统对象
PowerBuilder系统对象或类,继承自基类PowerObject。系统类是程序员定义的所有对象的祖先。要查看系统类层次结构,应在浏览器(Browser)中选择System选项卡,选择PowerObject,然后从弹出菜单中选择显示层次结构(Show Hierarchy)和全部展开(Expand All)。
- 用户对象
程序员可以在窗口、菜单、应用程序、结构和用户对象绘制器(painter)中创建用户对象类定义。所定义的对象是从一个系统类或另一个自己编写的类继承而来的。
一些绘制器(painter)使用很多类。在窗口(Window )和用户对象(User Object)绘制器中,主定义继承自窗口或用户对象类。程序员所使用的控件(control)也继承自该控件的系统类。
5.2.3用户对象
两种类型
用户对象有两种主要类型:visual 和class。
可视用户对象visual
可视用户对象(visual user object)是具有特定行为的可重用控件或控件集。有三种类型:标准standard、定制custom和外部external。
可视用户对象类型 | |
可视用户对象 | 描述 |
Standard | 继承自特定的可视控件(visual control)。可以设置属性并编写脚本,这样才可以使控件可用。 |
Custom | 继承自UserObject系统类。可以在用户对象中包含许多控件,并为它们的事件编写脚本。 |
External | 在DLL中定义的,显示可视控件的用户对象。控件不是PowerBuilder对象层次结构的一部分。DLL程序员提供了用于设置控制其呈现的样式位的信息。 |
类用户对象
类用户对象由属性、函数组成,有时还包括事件。它们没有可视组件。有两种类型——标准standard 和定制custom。
类用户对象类型 | |
类用户对象 | 描述 |
Standard | 从非可视的PowerBuilder对象继承其定义,例如Transaction或Error对象。可以添加实例变量和函数。 一些非可视对象拥有事件——要为这些事件编写脚本,就必须定义一个类用户对象。 |
Custom | 自己设计的对象,为其定义实例变量、事件和函数,以便于在对象中封装特定于应用的代码。 |
5.2.4实例化对象
类与实例
由于PowerBuilder对象类和实例的命名方式,很容易认为它们是相同的东西。例如,当程序员在Window 绘图器(painter)中定义一个窗口时,这是在定义一个对象类。
单实例
当程序员使用open函数的最简单格式打开一个窗口时,我们正在实例化一个对象实例。类定义和实例都有相同的名称。在应用程序中,w_main是一个w_main类型的全局变量:
Open(w_main)
当用这种方法打开窗口时,仅仅能打开对象的一个实例。
多实例
如果程序员想打开一个窗口类的多个实例,需要定义一个变量来保存每个对象引用:
w_main w_1, w_2
Open(w_1)
Open(w_2)
也可以通过在open函数中指定类来打开窗口:
window w_1, w_2
Open(w_1, "w_main")
Open(w_2, "w_main")
对于类用户对象,总是定义一个变量来保存对象引用,然后用CREATE语句实例化对象:
uo_emp_data uo_1, uo_2
uo_1 = CREATE uo_emp_data
uo_2 = CREATE uo_emp_data
一个对象可以有多个引用。可以将对象引用赋值给适当类型的变量,也可以将对象引用传递给另一个对象以便它可以更改或获取该对象的信息。
5.2.4父子(祖先和后代)
派生对象
在PowerBuilder中,对象类可以从另一个类继承。派生对象(继承对象或后代对象)具有祖先对象的所有实例变量、事件和函数。程序员可以通过添加更多的变量、事件和函数来扩充后代。如果更改了祖先,那么编辑后代后,后代也不会影响祖先的更改。
实例化
在实例化后代对象时,PowerBuilder也实例化其所有祖先类。程序员只有通过特别的方式才能访问这些祖先实例,例如使用范围操作符访问函数或事件脚本的祖先版本。
5.2.5垃圾回收
什么是垃圾回收
PowerBuilder垃圾回收机制自动检查内存中未引用和孤立的对象,并回收它找到的任何对象,从而处理好大多数内存泄漏。程序员可以使用垃圾回收来销毁对象,而不是使用destroy语句显式地销毁它们。这使程序员可以避免,在销毁另一个进程正在使用的对象或通过引用传递给已投递事件或函数的对象时,发生的运行时错误。
垃圾回收什么时候产生
垃圾回收产生于:
- 从对象中删除引用之时
对象的引用是值为该对象的任意变量。当变量超出作用域或被赋予一个不同的值时,PowerBuilder删除对该对象的引用,计算剩余的引用,如果没有引用,则销毁对象。
- 超过垃圾回收间隔之时
当PowerBuilder完成系统触发事件的执行时,如果超过设置的垃圾回收操作间隔,它将进行一次垃圾回收操作。间隔缺省值是0.5秒。垃圾回收操作删除不能引用的任何对象和类,包括那些包含循环引用的对象和类(否则会导致相互引用未引用对象)。
当程序员触发一个事件或函数并传递一个对象引用时,PowerBuilder会向对象添加一个内部引用,以防止在事件或函数触发到实际执行期间回收对象。执行事件或函数时将删除此引用。
垃圾回收例外
有几种对象不会被回收:
- 可视对象
在屏幕上可见的任何对象都不会被回收,因为当对象创建并显示在屏幕上时,会向该对象添加一个内部引用。当任何可视对象被关闭时,它将被显式地销毁。
- 计时对象(Timing objects)
当前正在运行的任何计时对象都不会被回收,因为计时对象的Start函数添加了一个内部引用,而Stop函数会删除引用。
- 共享对象
已注册的共享对象不会被回收,因为SharedObjectRegister函数添加了一个内部引用,而SharedObjectUnregister删除内部引用。
控制垃圾回收产生时机
在PowerBuilder中自动进行垃圾回收,但程序员可以使用函数GarbageCollect、GarbageCollectGetTimeLimit和GarbageCollectSetTimeLimit强制立即进行垃圾回收,或更改引用计数检查之间的间隔。通过将垃圾回收操作之间的间隔设置为一个非常大的数字,可以有效地延迟垃圾回收。
5.2.6行为看起来象结构的用户对象
在PowerBuilder中,非可视用户对象可以提供类似于结构的功能。它的实例变量形成了一个类似于结构变量的集合。在脚本中,使用点符号引用用户对象的实例变量,就像使用结构变量一样。
用户对象优势
用户对象可以包括函数和它自己的结构定义,并且它允许程序员从一个祖先类继承。这些在结构定义中都不可能实现。
内存分配的差异
内存分配对于用户对象和结构来说是不同的。对象变量是对对象的引用。声明变量并不会为对象分配内存。声明它之后,必须用CREATE语句来实例化它。用户对象的赋值也不同。
自动实例化的对象
如果程序员想要一个含有方法和继承的用户对象,但又想要类似结构的内存分配,可以定义一个自动实例化对象。
程序员不必创建和销毁自动实例化的对象。与结构类似,它们在声明时创建,在超出作用域时销毁。然而,由于自实例化对象的赋值行为类似于结构,因此对象的副本可能是一个缺陷。
要使自定义类用户对象自动实例化,要选择用户对象属性表上的Autoinstantiate复选框。
5.3对象和结构的赋值
在PowerBuilder中,对象的赋值不同于结构或自动实例化对象的赋值:
- 当程序员将一个结构赋值给另一个结构时,整个结构将被复制,因此该结构有两个副本。
- 当程序员将一个对象变量赋值给另一个对象变量时,对象引用会被复制,因此两个变量都指向同一个对象。对象只有一个副本。
5.3.1结构的赋值
声明一个结构变量就会创建一个该结构的实例:
str_emp_data str_emp1, str_emp2 // 两个实例
当将一个结构赋值给另一个结构时,整个结构会被复制,结构数据的第二个副本就存在了:
str_emp1 = str_emp2
赋值操作将整个结构从一个结构变量复制到另一个结构变量,因为每个变量都是结构str_emp_data的一个单独实例。
赋值的约束
如果结构具有不同的定义,则不能将一个结构分配给另一个结构,即使它们具有相同的变量定义集。
例如,下面这几句的赋值操作时不允许的:
str_emp str_person1
str_cust str_person2
str_person2 = str_person1 // 不允许
5.3.2对象的赋值
声明一个对象变量就意味着声明了一个对象引用:
uo_emp_data uo_emp1, uo_emp2 // 两个对象引用
使用CREATE语句创建对象的一个实例:
uo_emp1 = CREATE uo_emp_data
将一个对象变量赋值给另一个对象变量时,将复制对该对象实例的引用。该对象只存在一个副本:
uo_emp2 = uo_emp1 // 两个变量均指向同一个对象实例
原型对象和派生对象
原型对象(祖先对象)和派生对象(后代对象)之间的赋值以同样的方式发生,对象引用被复制到目标对象。
假设uo_emp_data是uo_emp_active和uo_emp_inactive的祖先用户对象(原型)。
祖先类型的变量声明如下:
uo_emp_data uo_emp1, uo_emp2
创建一个后代的实例,并将其引用存储在祖先变量中:
uo_emp1 = CREATE USING "uo_emp_active"
将uo_emp1赋值给uo_emp2使两个变量都指向一个对象,该对象是后代uo_emp_active的实例:
uo_emp2 = uo_emp1
5.3.3自动实例化的用户对象的赋值
声明一个自动实例化的用户对象会创建一个该对象的实例(就像一个结构)。对于带有Autoinstantiate设置的对象,不允许使用CREATE语句。在下面的例子中,uo_emp_data具有Autoinstantiate设置:
uo_emp_data uo_emp1, uo_emp2 // 两个对象实例
当将一个自实例化对象赋值给另一个自实例化对象时,整个对象被复制到第二个变量:
uo_emp1 = uo_emp2
一个自动实例化的用户对象永远不会有多个引用。
传递给函数
当将一个自动实例化的用户对象传递给一个函数时,它看起来就像一个结构:
- 通过值传递对象的副本。
- 通过引用传递一个指向对象变量的指针,就像任何标准数据类型一样。
- 以只读方式传递对象的副本,但该副本不能被修改。
赋值的约束
如果对象类型匹配或目标是非自动实例化的祖先,那么允许在自动实例化的用户对象之间赋值。
- 规则1
如果将一个自动实例化对象赋值给另一个对象,那么它们必须是相同类型的。
- 规则2
如果将自动实例化的后代对象赋值给一个祖先变量,那么该祖先不能具有自动实例化Autoinstantiate 设置。祖先变量将包含对其后代副本的引用。
- 规则3
如果将一个祖先对象赋值给一个后代变量,那么该祖先必须包含一个后代的实例或产生一个执行错误。
示例
为了说明上述赋值约束,假设有这些声明,Uo_emp_active和uo_emp_inactive是自实例化对象,它们是非自实例化的uo_emp_data的后代:
uo_emp_data uo_emp1 // 祖先
uo_emp_active uo_empa, uo_empb // 后代
uo_emp_inactive uo_empi // 另外一个后代
- 规则 1的实例对于上面声明的用户对象,将其一个实例赋值给另一个实例时,有些赋值是编译器不允许的:
uo_empb = uo_empa // 允许,类型相同
uo_empa = uo_empi // 不允许,类型不同
- 规则 2的实例下面这句,uo_emp1包含了后代对象uo_empa的副本。Uo_emp_data (uo_emp1的类型)不能被自动实例化。否则,赋值违反规则1。如果uo_emp1被自动实例化,则会发生编译器错误:
uo_emp1 = uo_empa
- 规则 3的实例
如果uo_emp1包含它的后代uo_empa的实例,那么下面这句的赋值操作才允许,如果在这句之前,上一句赋值(即规则2的实例语句:uo_emp1 = uo_empa)已经发生,它是允许的。
uo_empa = uo_emp1
如果它不包含目标后代类型的实例,则会发生执行错误。