📄 chapter2.htm
字号:
destroyed when it is no longer needed. When an object disappears from a program it is
said to go out of scope. Whenever an object goes out of scope in a C program, the
program must execute its <a href="chapter1.htm#destructor">destructor</a>. Otherwise, the heap--the memory area from which
the space needed to hold additional objects--will become fully used and the program
cannot continue without error.
<p> The <a href="chapter1.htm#implementation_file">implementation file</a> is shown below. The two functions increment() and
query() are similar to the version above. In this case these functions have pointer
arguments, and instead of merely incrementing or returning count, the value of count
associated with the pointer <code>this</code> is operated on prior to the functions return.
<pre>
#include "counter1.h"
#include <stdlib.h> /* for the memory operations */
void increment(Counter *this)
{
this->count++;
}
int query(Counter *this)
{
return this->count;
}
Counter *counter_(void)
{
Counter *this;
if(this=(Counter *)(malloc(sizeof(Counter))==NULL))
error_handler()
this->count=0;
return this;
}
void counter__(Counter *this)
{
free(this);
}
</pre>
<p>
The <a href="chapter1.htm#constructor">constructor</a> takes no argument and returns pointer to a type Counter. Inside
the <a href="chapter1.htm#constructor">constructor</a>, a temporary pointer to a type Counter is created by the line of code
<pre>
Counter *this;
</pre>
<p>
The system call malloc() is used to get a memory block the size of Counter. malloc()
returns a pointer to the type void, so the return can be cast onto a type Counter * which is
stored in <code>this</code>. The pointer form for member access is used to initialize count, and <code>this</code>, the
pointer, is returned to the calling program. The pointer returned from malloc() is tested to
make certain that the operating system found enough memory to allocate another block to
the program. If there were not enough memory available, the malloc() function will return
a NULL which cannot be a valid pointer in C. The return value is tested to determine if it
is a NULL. In the event that it is, a function error_handler() is executed. For the
programs in this chapter, the error_handler() will be a stub routine that does nothing. This
important function will be revisited in Chapter 4 where an error_handler() approach will
be developed. The routine Counter creates a dynamic memory location which contains
Count, initializes the value of count in that memory area, and returns a pointer to <code>this</code>
location to the calling program. The variable <code>this</code> must always be defined in the
<a href="chapter1.htm#constructor">constructor</a>. In C++ there is a variable <code>this</code> that need not be defined in any <a href="chapter1.htm#method">method</a>. This
variable is always a pointer to the current object. We will use <code>this</code> as the same thing as
you will find in C++. Anytime that <code>this</code> is used as a variable, it will be a pointer to the
appropriate object.
<p>
The <a href="chapter1.htm#destructor">destructor</a>, counter__(), uses the standard memory release call free() to return
the memory space occupied by the specific instance of Counter to the operating system.
<pre>
#include "counter1.h"
#include <stdio.h>
main()
{
Counter *a=counter_();
Counter *b=counter_();
int i;
for(i=0;i<12;i++)
{
increment(a);
increment(b);
}
for(i=0;i<6;i++)
increment(a);
printf("a=%d and b=%d\n",query(a),query(b));
counter__(a);
counter__(b);
}
</pre>
<p> The performance of this version of Counter is demonstrated by the above program.
Two instances of a counter are created by the two lines of code
<pre>
Counter *a=counter_();
Counter *b=counter_();
</pre>
<p>
Each line of code says:
<p><i>
Create an instance of a pointer to the type Counter. Initialize the pointer with the
return from the <a href="chapter1.htm#constructor">constructor</a> function counter_(). These lines of code are
definition statements as well as initialization statements.
</i><p>
For demonstration purposes, the program increments both a and b twelve times and then
increments a an additional six times. The output from this program is
<pre>
a=18 and b=12
</pre>
<p>
We have succeeded in modifying our approach to the creation of objects in C to allow
multiple instances of a single object. Notice that the <a href="chapter1.htm#destructor">destructor</a> was used at the end of the
program to delete the instances of a and b that were created with the <a href="chapter1.htm#constructor">constructor</a>.
<p> The approach outlined above handily eliminates the problem of multiple
instantiations. The program can now have as many instances of any given class as is
needed to meet the program needs. The cost so far to achieve these ends is minimal.
There is one copy of each <a href="chapter1.htm#method">method</a>, the <a href="chapter1.htm#constructor">constructor</a> and the <a href="chapter1.htm#destructor">destructor</a> associated with each
program. In this case, each object will be two bytes long, and the <a href="chapter1.htm#constructor">constructor</a> will allocate
exactly two bytes for each instance of the object. The object oriented approach has cost
nothing with the exception of the additional function calls in the main program needed to
set-up the objects and access the object <a href="chapter1.htm#attribute">attribute</a>s. We have gained the ability to deal with
Counters as an object. The <a href="chapter1.htm#method">method</a> function calls above are really mechanisms to send and
receive messages to and from the object. Such accesses are uniform across the whole
program, and the code created to access the Counters is very simple and to the point.
This particular program is so simple that many would say that it is easier to write the code
to do the job in other ways. Perhaps true, but in no case would the straight procedural
approach be so obvious as to what the program is doing.
<p> If our program approach is to allow multiple instances of any object, it is necessary
to have arguments for all object <a href="chapter1.htm#method">method</a>s. The problem here is that the program has no
means of determining which object is sending a message to any specific <a href="chapter1.htm#method">method</a>.
Therefore, the object identification must be included as a part of the message that is
passed to the <a href="chapter1.htm#method">method</a>. We will see later that when more complicated functions like virtual
functions and <a href="chapter1.htm#inheritance">inheritance</a> are employed, even a single additional argument is not
sufficient to identify accurately the program destination when a message is sent to an
object.
<h2><a name="inheritance"><a href="chapter1.htm#inheritance">Inheritance</a></a></h2>
The current approach outlined above does not allow either <a href="chapter1.htm#inheritance">inheritance</a> or
<a href="chapter1.htm#polymorphism">polymorphism</a> as it stands. These features are so important that they must be considered
when creating an object oriented method of programming. Inheritance allows a new class
to access all of the <a href="chapter1.htm#attribute">attribute</a>s and <a href="chapter1.htm#method">method</a>s of another class as if they were its own. The
new class can extend the capability of the class being inherited from. Semantic problems
creep into our list of difficulties with OOP now. Inheritance is a poor description of what
we are doing at this point. Inheritance usually implies the passage of characteristics and
traits from parent to child. Often the classes are given the name parent and child class. As
it turns out, this naming is not truly descriptive of what happens in OOP <a href="chapter1.htm#inheritance">inheritance</a>. The
parent class gives all of its <a href="chapter1.htm#attribute">attribute</a>s and <a href="chapter1.htm#method">method</a>s to the child class. The parent class can
operate on its own, but the child class is forever dependent on the presence of the parent.
An unlikely <a href="chapter1.htm#parent">parent</a>-
<a href="chapter1.htm#child">child</a>
arrangement if ever there was one. Sometimes the parent class is
called the base class or the super class. In such cases, the child class is usually called the
derived class. These descriptions probably convey the actual happenings in <a href="chapter1.htm#inheritance">inheritance</a>
better than the parent-child concept. We will continue to use <a href="chapter1.htm#inheritance">inheritance</a> here to describe
what occurs when a new class takes all of the characteristics of another class, but
recognize that such an action is not <a href="chapter1.htm#inheritance">inheritance</a> in any real sense.
<p> Let us examine what we can do to implement OOP <a href="chapter1.htm#inheritance">inheritance</a> in the C language
without the OOP extensions found in C++. To create <a href="chapter1.htm#inheritance">inheritance</a>, we really need a
mechanism to alter the structure that defines a class in another part of the program. True
<a href="chapter1.htm#inheritance">inheritance</a> will allow the characteristics of the base structure to be incorporated into a
new structure in the process of derivation of the new structure.
<p> Once a structure has been defined by a program, it is not possible to change it.
When inheriting, it is possible to include another class structure as a member of a new
class structure. Such an approach will lead to extremely convoluted programs, and one of
the purposes of OOP is to create uniform, smooth interfaces between portions of a
program. The idea of using one class structure as a member of another class structure will
lead to at least multiple indirection on accesses to the object <a href="chapter1.htm#attribute">attribute</a>s and <a href="chapter1.htm#method">method</a>s.
Some accesses would require one level of indirection, and others would require more.
What a nightmare!
<p> An approach shall be developed that does require some indirection, but the
indirection levels needed by the programmer will be uniform. Therefore, the demands on
the programmer are somewhat more complicated than usual, but the benefits gained by
having the OOP significantly outweigh the need for additional programmer involvement.
In the following <a href="chapter1.htm#header">header file</a>, observe that the #define statement that defines COUNT
contains items that are the <a href="chapter1.htm#attribute">attribute</a>s as well as function prototypes for the class typedefed
as Count. The <a href="chapter1.htm#method">method</a> prototypes use pointers to functions rather than the normal
function name, and the arguments to these functions are pointers to the type struct cnt. It
is not possible to use a pointer to the type Count, because Count is an undefined type at
this point in the program. The class declaration is the structure cnt, and this structure is
typedefed as a type Count when it is created. The members of struct cnt are simply those
items defined as COUNT. As was done earlier, the <a href="chapter1.htm#constructor">constructor</a> and <a href="chapter1.htm#destructor">destructor</a> are
prototyped in the <a href="chapter1.htm#header">header file</a> named counter2.h.
<pre>
#ifndef COUNT_H
#define COUNT_H
#include "defines.h"
#define COUNT \
WORD count; \
void (*increment)(struct cnt *);\
WORD (*query)(struct cnt *);
typedef struct cnt
{
COUNT
}Count;
Count *count_(void);
void count__(Count *);
#endif
</pre>
<p>
The main difference between this header and the previous one is that the member
<a href="chapter1.htm#attribute">attribute</a>s and <a href="chapter1.htm#method">method</a>s that are a part of the class are each defined in the #define
statement. The reason for this approach is not evident right now, but we will see the
importance of making these several members of the structure a seaprately accessable entity
and not keeping their definitions locked inside of the structure which defines the class.
<p> The <a href="chapter1.htm#implementation_file">implementation file</a> is shown below. One of the first items that you will
observe is that the <a href="chapter1.htm#method">method</a> function implementations are declared to be static. A static
declaration for a function causes the function to have file scope. Therefore, if another
portion of the program needs an increment, there will be no name conflicts if the various
files are each compiled separately. Otherwise, the functions increment() and query() are
the same as those shown in the earlier counter1 version of the class.
<pre>
#include "counter2.h"
#include <stdlib.h> /* needed for the memory functions */
static void increment(Count *this)
{
this->count++;
}
static WORD query(Count *this)
{
return this->count;
}
Count *count_(void)
{
Count *this;
if((this=(Count *)malloc(sizeof(Count)))==NULL)
error_handler();
this->count=0;
this->increment=increment;
this->query=query;
return this;
}
void count__(Count *this)
{
free(this);
}
</pre>
<p> The <a href="chapter1.htm#constructor">constructor</a> count_() is significantly modified from the earlier version. Here,
the memory space for the object is allocated, and the <a href="chapter1.htm#attribute">attribute</a> count is initialized to zero.
In this case, the two <a href="chapter1.htm#method">method</a> members of the structure are initialized with pointers to the
functions increment() and query() defined earlier in the <a href="chapter1.htm#implementation_file">implementation file</a>. Therefore,
any access to these functions must take the form
<pre>
Count *a=count_();
.
.
.
(*a->increment)(a);
.
.
</pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -