📄 oop.htm
字号:
<kbd>JOURNAL</kbd>'s version were an extended version of
<kbd>MEDIUM</kbd>'s version, <kbd>[PARENT]</kbd> could be applied as well,
for example like this:</p>
<pre>:NONAME ( JOURNAL -- )
DUP [PARENT] SALE
DUP PRICE @ 100 MAX SWAP PRICE ! ; IS SALE</pre>
<p>This definition works as expected even though the grandparent and not
the parent of class <kbd>JOURNAL</kbd> defines the original version of
<kbd>SALE</kbd>. The parent <kbd>PAPER-MEDIUM</kbd> has inherited the
version from <kbd>JOURNAL</kbd>'s grandparent
<kbd>MEDIUM</kbd>. If for whatever reason you want to compile
a version that is different from that of the parent class, you can use
<kbd>[BIND]</kbd> instead of <kbd>[PARENT]</kbd>:</p>
<pre>[BIND] MEDIUM SALE</pre>
<p><kbd>[BIND]</kbd> allows you to specify the direct parent class as well
as any indirect parent class whose version of the virtual member word shall
be compiled with static binding. Specifying any other than direct or indirect
parent classes is possible, but will cause an ambiguous condition in most
cases. <kbd>[BIND]</kbd> is actually
a generalized version of <kbd>[PARENT]</kbd>. Note that the usage of
<kbd>[PARENT]</kbd> and <kbd>[BIND]</kbd> is not restricted to extending the
semantics of virtual members within a class hierarchy. You can even chose to
statically bind a virtual member in the definition of a non-member word like
<kbd>SELLOUT</kbd>. For example, If you want to display only the title and
the original price of a medium, you can statically bind <kbd>.</kbd> to
class <kbd>MEDIUM</kbd> in the definition of <kbd>SELLOUT</kbd>:</p>
<pre><u>: SELLOUT ( MEDIUM -- )</u>
<u> ." Save money now!" CR DUP [BIND] MEDIUM . CR DUP SALE</u>
<u> ." Now for only $" .PRICE ." !" ;</u> OK
<u>1999 BOOK1 SET-PRICE</u> OK
<u>BOOK1 SELLOUT</u> Save money now!
Starting Forth ($19.99)
Now for only $15.99! OK
<u>799 JOURNAL1 SET-PRICE</u> OK
<u>JOURNAL1 SELLOUT</u> Save money now!
Scientific American ($7.99)
Now for only $1.00! OK</pre>
<p>Members that have been compiled into the <kbd>PROTECTED</kbd> word list
of a class are passed on to child classes as well.
However, private members can only be directly referred to within the
parent class definition. Accessing private data members or private member words
within the child class definitions or outside of a class definition can only
happen indirectly through public or protected member words. That's because the
<kbd>PRIVATE</kbd> word list of a class is not passed on to its children. The
<kbd>PROTECTED</kbd> word list of a class, on the other hand, is passed on to
all its children. Each child class actually starts with the <kbd>PROTECTED</kbd>
word list of its parent class and extends it with its own protected members,
whereas a child's <kbd>PRIVATE</kbd> word list is initially empty. Given the
parent class definition</p>
<pre>DT OBJECT PROCREATES PARENT-CLASS
DT PARENT-CLASS PROCREATES CHILD-CLASS
CLASS PARENT-CLASS
BODY
NULL UNSIGNED MEMBER PUBLIC-DATA-MEMBER
:MEMBER PUBLIC-MEMBER-WORD ( PARENT-CLASS -- )
>THIS ;
ALSO PRIVATE DEFINITIONS
NULL UNSIGNED MEMBER PRIVATE-DATA-MEMBER
:MEMBER PRIVATE-MEMBER-WORD ( PARENT-CLASS -- )
>THIS ;
ALSO PROTECTED DEFINITIONS
NULL UNSIGNED MEMBER PROTECTED-DATA-MEMBER
:MEMBER PROTECTED-MEMBER-WORD ( PARENT-CLASS -- )
>THIS ;
ENDCLASS</pre>
<p>here's what happens in the child class definition:</p>
<pre><u>CLASS CHILD-CLASS BODY</u> OK
<u>ALSO PRIVATE WORDS</u> OK
<u>ALSO PROTECTED WORDS</u>
PROTECTED-MEMBER-WORD ( PARENT-CLASS -- )
PROTECTED-DATA-MEMBER ( PARENT-CLASS -- ADDRESS -> UNSIGNED ) OK</pre>
<p>Note that the assignment of a virtual member word does not affect the
word list in which the virtual member word had been originally defined with
<kbd>VIRTUAL</kbd>.</p>
<h2><kbd>NEW</kbd> and <kbd>DELETE</kbd></h2>
<p>By default, new structures and new objects are allocated in dynamic memory.
<kbd>NEW</kbd> is a state-smart immediate word that executes or compiles the
word <kbd>(NEW)</kbd> in order to allocate and initialize the new structure
or object. It determines the data type of the structure or object by parsing
the input stream for the name of the data type. If the data type is a structure,
<kbd>(NEW)</kbd> is passed a literal parameter of data type
<kbd>ADDRESS -> STRUCTURE</kbd> which is the address of a cell that contains the
size of the structure in address units. <kbd>STRUCTURE</kbd> is the actual data
type of the structure. The output parameter of <kbd>(NEW)</kbd> is the address
of the allocated memory as an item of the structure's data type.</p>
<pre>(NEW) ( ADDRESS -> STRUCTURE -- 2ND )</pre>
<p>If you prefer to do the memory allocation yourself, all
you have to do is pushing the address of the allocated memory onto the stack
before <kbd>NEW</kbd> gets executed. This works because <kbd>(NEW)</kbd> is
overloaded with two additional versions that expect addresses of data types
<kbd>ADDRESS</kbd> and <kbd>CADDRESS</kbd> on the stack:</p>
<pre>(NEW) ( ADDRESS ADDRESS -> STRUCTURE -- 3RD )
(NEW) ( CADDRESS ADDRESS -> STRUCTURE -- 3RD )</pre>
<p>Let's assume you want to statically allocate a structure in the data space
using <kbd>ALLOT</kbd>. Here's what you have to do:</p>
<pre>HERE DT structure SIZE-STRUCTURE ALLOT NEW structure</pre>
<p><kbd>structure</kbd> stands for the name of structure. Remember that
new structures are not being automatically initialized.</p>
<p>You can even chose to implement your own version of <kbd>(NEW)</kbd> for
allocating structures. For example, if you generally want to allocate structures with
<kbd>ALLOT</kbd> instead of with <kbd>ALLOCATE</kbd>, the following version of
<kbd>(NEW)</kbd> does the job. If you define a separate vocabulary for it, you
can even switch arbitrarily between dynamic and static allocation:</p>
<pre><u>VOCABULARY STATIC-ALLOCATION</u> OK
<u>GET-CURRENT ALSO STATIC-ALLOCATION DEFINITIONS</u> OK
<u>: (NEW) ( ADDRESS -> STRUCTURE -- 2ND )</u>
<u> SIZE-STRUCTURE HERE CAST STRUCTURE SWAP ALLOT ;</u> OK
<u>SET-CURRENT HERE .</u> 4756966 OK
<u>NEW RECTANGLE .S .</u> RECTANGLE 4756966 OK
<u>PREVIOUS</u> OK
<u>NEW RECTANGLE DUP .</u> 1525264 OK
<u>DELETE</u> OK</pre>
<p>A structure that is no longer required shall be deleted with
<kbd>DELETE</kbd>. <kbd>DELETE</kbd> simply frees the allocated dynamic
memory:</p>
<pre>: DELETE ( STRUCTURE -- )
CAST ADDRESS FREE THROW ;</pre>
<p>However, bear in mind that <kbd>DELETE</kbd> must not be applied to
structures that were not allocated in dynamic memory with the first version
of <kbd>(NEW)</kbd>.</p>
<p>Allocating and deleting objects is similar to allocating and deleting
structures. The main differences are that each object has a constructor that
needs to be executed immediately <em>after</em> the memory space for the
object has been allocated, and a destructor that is exectured immediately
<em>before</em> the memory space for the object is released. Furthermore, the
virtual member table pointer needs to be stored into the first cell of a new
object. Since <kbd>NEW</kbd> is used both for creating new structures and
new objects, corresponding versions of <kbd>(NEW)</kbd> are provided:</p>
<pre>: (NEW) ( ADDRESS -> OBJECT -- 2ND )
DUP SIZE-OBJECT ALLOCATE THROW
TUCK -> ADDRESS -> OBJECT ! CAST OBJECT ;
: (NEW) ( ADDRESS ADDRESS -> OBJECT -- 3RD )
OVER -> ADDRESS -> OBJECT ! CAST OBJECT ;
: (NEW) ( CDATA CONST -> OBJECT -- 3RD )
OVER -> ADDRESS -> OBJECT ! CAST OBJECT ;</pre>
<p>The input parameter <kbd>ADDRESS -> OBJECT</kbd> is the pointer to the
object's virtual member table, whose first cell contains the size of the
object in address units. The actual definitions of <kbd>(NEW)</kbd>
are slightly different than what is shown here, but this has no impact on
the semantics. Of course, you can also define your own versions of
<kbd>(NEW)</kbd> just as has been demonstrated for structures a few
paragraphs above.</p>
<p>Now, what about the constructors? Since the constructors
of a class always have the same name as the class, and <kbd>NEW</kbd> parses
the class name, <kbd>NEW</kbd> simply saves the input source specification
before parsing, and restores it after parsing. The result is that the
class name is evaluated immediately after <kbd>NEW</kbd>, executing or
compiling a constructor that matches the parameters on the stack. You can
define multiple constructors for different sets of parameters. For example,
the <kbd>STRING</kbd> class that was shown above has a constructor that
expects the address and count of a character string in addition to the
string object. However, a string can also be initialized with another object
of class <kbd>STRING</kbd>, or with just a character count. An example of an
extended version of the <kbd>STRING</kbd> class with additional constructors
will be shown later in this section.</p>
<p>Since <kbd>NEW</kbd> always evaluates the name of a constructor, it is
not possible to create an object of a class that has no constructor. However,
defining a class without a constructor can make sense, if the only purpose of
this class is to derive child classes from it. Another interesting technique is
to define the constructors of a class in the <kbd>PRIVATE</kbd> word list.
The result is that objects of this class can only be created by member words
of classes that have been declared friends of the class. Objects of classes
whose constructors were defined in the <kbd>PROTECTED</kbd> word list can
only be created by member words of child classes or friend classes.</p>
<p>In contrast to constructors, each class has only one destructor, because
destructors do not have parameters in addition to the object itself. Moreover,
all destructors are virtual members and share the common name
<kbd>DESTRUCTOR</kbd>. They have one input parameter of the class data type
that is not consumed. <kbd>DELETE</kbd> for objects is an immediate word
that evaluates first the destructor and then a word called
<kbd>(DELETE)</kbd> that releases the allocated memory space:</p>
<pre>: (DELETE) ( OBJECT -- )
CAST ADDRESS FREE THROW ;
: DELETE ( -- )
" DESTRUCTOR" EVALUATE POSTPONE (DELETE) ; IMMEDIATE</pre>
<p>If an object has not been allocated in the dynamic memory space using the
first version of <kbd>(NEW)</kbd>, you may not apply <kbd>DELETE</kbd> or
<kbd>(DELETE)</kbd> to it. If you want to destroy the object anyway, you have
to use its destructor alone.</p>
<p>Now what's the typical semantic of a destructor? The destructor of class
<kbd>OBJECT</kbd> does nothing:</p>
<pre>:NONAME ( OBJECT -- 1ST ) ; IS DESTRUCTOR</pre>
<p>But the destructor of the <kbd>STRING</kbd> class actually needs a
destructor that frees the dynamic memory space the constructor allocated for
the character array. Otherwise, each time an object of class <kbd>STRING</kbd>
is deleted, the available dynamic memory space shrinks by the the size of the
character array. Without a dedicated destructor the definition of the
<kbd>STRING</kbd> class is incomplete. It would just inherit the default
destructor from class <kbd>OBJECT</kbd>. Adding the destructor and some more
constructors, the definition of the <kbd>STRING</kbd> class now looks like
this:</p>
<pre>DT OBJECT PROCREATES STRING
CLASS STRING
BODY
ALSO PROTECTED DEFINITIONS
NULL UNSIGNED MEMBER LEN
NULL CADDRESS -> CHARACTER MEMBER BUF
:MEMBER INIT ( UNSIGNED STRING -- )
>THIS DUP LEN ! CALLOCATE THROW -> CHARACTER BUF ! ;
ALSO FORTH DEFINITIONS PREVIOUS
:MEMBER STRING ( UNSIGNED STRING -- 2ND )
>THIS INIT BUF @ LEN @ BLANK THIS ;
:MEMBER STRING ( CADDRESS -> CHARACTER UNSIGNED STRING -- 4 TH )
>THIS INIT BUF @ LEN @ MOVE THIS ;
:MEMBER STRING ( STRING STRING -- 2ND )
>THIS DUP LEN @ INIT BUF @ BUF @ LEN @ MOVE THIS ;
:MEMBER . ( STRING -- )
>THIS THIS
IF LEN @
IF BUF @ LEN @ TYPE
ELSE ." <empty>"
THEN
ELSE ." <null>"
THEN SPACE ;
:MEMBER LENGTH ( STRING -- UNSIGNED )
LEN @ ;
:NONAME ( STRING -- 1ST )
DUP BUF @ FREE THROW ; IS DESTRUCTOR
ENDCLASS</pre>
<p>Note that <kbd>INIT</kbd> has been made a protected member, because it is
supposed to be used only by the constructors of the <kbd>STRING</kbd> class
and by its children.</p>
<p>Finally, here's another interesting feature that can be applied to both
structures and objects. You can define dedicated versions of <kbd>(NEW)</kbd>
and <kbd>(DELETE)</kbd> that only apply to a specific object, or dedicated
versions of <kbd>(NEW)</kbd> and <kbd>DELETE</kbd> for specific structures.
All you have to do is overloading <kbd>(NEW)</kbd> and <kbd>(DELETE)</kbd>
for the specific data type. But what's this feature good for? One example
is if you want to allocate all objects of a certain class in the data space
instead of in dynamic memory, while the default allocation for all other
objects remains dynamic:</p>
<pre>: (NEW) ( ADDRESS -> SAMPLE-CLASS -- 2ND )
SIZE-OBJECT HERE CAST SAMPLE-CLASS SWAP ALLOT ;</pre>
<p>This definition of <kbd>(NEW)</kbd> overloads the default version and is
only invoked for the class <kbd>SAMPLE-CLASS</kbd> and its children. Since
the allocation is static, you don't need to overload <kbd>(DELETE)</kbd>. The
overloaded version of <kbd>(NEW)</kbd> can be either a member word or a global
definition.</p>
<p>As anoth
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -