Next: Methods, Previous: Collections, Up: Top [Index]
The previous chapters described some of the methods that the Ctalk classes define. This chapter discusses in further detail how to define Classes and variables that belong to the classes, and discusses some of the ways that Ctalk initializes instances of a class.
Many of the methods used in this chapter are primitive methods.
The primitive method definitions are in the Object
class, and
any class can use them.
At a minimum, a class definition defines the name of the new class and
its superclass. The primitive method class
(Object
class) takes the name of a new class as its argument. The new class
is defined in the class library as a subclass of the receiver’s class.
This statement defines a class with the name WriteFileStream
as a
subclass of the receiver, FileStream.
FileStream class WriteFileStream;
Or, more generically,
superclassname class newclassname <docstring>;
This declaration is necessary for Ctalk to identify the file as a class.
The superclassname indicates where the class fits into the class
library, and that determines which methods and instance data the new
class inherits. The only class that does not have a
superclassname is Object,
the topmost class in the class
library. Object
class is loaded by default and doesn’t require
a class declaration.
The docstring element is optional; it’s a human readable description of the class. It’s simply text enclosed in double quotes between the classname and the final semicolon. Here’s the previous example with a possible documentation string added.
FileStream class WriteFileStream "Defines the methods and instance variables that write data to files. Also defines the class variables stdoutStream and stderrStream, which are the object representation of the standard output and standard error streams.";
It’s necessary that the superclasses of the new class already be defined. That statement may seem obvious, but the definitions of class and instance variables that the new class inherits must already be present. That can sometimes be tricky when defining basic classes.
Another reason is that, when you define an instance or class variable, the variable is a member of your new class, unless you specify that the variable belongs to another class. If your class can inherit class variables, instance variables, and methods from the class’s superclasses, then they get used by the new class also. Any new variables and methods only need to be referenced by the class you’re defining.
However, you can direct Ctalk to initialize other classes if your
class needs them, by using, for example, statements like
require,
as described below.
Note: As mentioned above, when dealing with a basic class like
Object
or Symbol
, a method can’t always depend on
a class being defined. During initialization, Ctalk programs
require
a number of basic classes: Object,
Symbol,
Integer,
and ArgumentList,
and their
required classes Magnitude
and Character.
If, for example, you define a method in class Symbol
to print
objects, you cannot use WriteFileStream
class, which needs
Symbol
and its methods already defined. That would mean
Symbol
class needs to look up WriteFileStream,
which
needs to look up Symbol,
which needs to look up
WriteFileStream
again, and so on.
Ctalk doesn’t necessarily stop you from writing programs with circular class references - its method prototyping mechanism can often compensate by checking definitions from different classes as needed, but in cases where Ctalk can’t resolve circular references, then it’s necessary to add methods or instance variables in subclasses.
The previous chapters have described some of the ways that you can work with instance data using C. Many of the Ctalk library’s API functions also check for the type and value instance data. The method API is described later on.
We mentioned already that all objects have a default instance variable
named value.
However, many classes define other instance
variables. Classes define instance variables with the
instanceVariable
method.
Here is an example of an instance variable definition.
MyNewClass instanceVariable textMessage String NULL;
This example declares that all instances of MyNewClass
will
have an instance variable named textMessage,
and that it is
an instance of class String,
and it has the initial value
NULL.
The second and third arguments of instanceVariable
are
optional. Here is the syntax of the instanceVariable
method.
classname instanceVariable varname [varclass|typecast_expr] [initialvalue]
Remember that instanceVariable
only defines variables.
The method does not actually create them. When you create an object
that is an instance of your class, the object gets its own copy of the
variable.
If the program declares a variable with a type cast instead of a
native class for the variable, Ctalk matches the type cast to a class.
If the type cast of the variable is a pointer to some data type that
is not commonly used in Ctalk or C programs, then Ctalk simply
translates the native type to Symbol
. This is because the
program needs to implement its own methods for handling the data, and
Symbol
is the most generic class that Ctalk can use for referring
to C data in memory.
On the other hand, the classVariable
method creates the single
copy of a class variable that the instances of a class (and most other
classes) can use. Creating a class variable is similar to creating an
instance variable.
MyNewClass classVariable systemError Integer 0;
For completeness, here is the classVariable
method’s syntax.
classname classVariable varname [varclass|typecast_expr] [initialvalue]
Remember that if you define a variable as a different class than its member class, then the variable responds to messages of its own class. In these cases, you need to take care that you are working with the variable, and not the class that it belongs to.
If the variable’s native type is declared with a type cast, then Ctalk tries to translate the type cast into a Ctalk class, as described in the previous section.
Although you can define an initial value for a class variable, in many
cases you need to wait until the program is run before setting the
actual value of a class variable. An example of this is
stdoutStream
(class WriteFileStream
).
In many cases, a constructor method (which in Ctalk are named
new
), can call a class initialization method.
Here is a hypothetical class with classInit
a method. This
example simply sets the the name of the system in a class variable
called hostName.
MySuperClass class MyClass; MyClass classVariable myClassVar String NULL; ... Other class and instance variable definitions. ... MyClass classMethod classInit (void) { OBJECT *classObject_object, *classInitVar_object, *myClassVar_object; char buf[MAXLABEL]; /* * Return if we've already initialize the class. */ if (self classInitDone) return NULL; /* * Retrieve the class object, because we'll need it below. */ classObject_object = __ctalkGetClass("MyClass"); /* * Look for myClassVar. * The third argument of __ctalkGetClassVariable instructs the * function not to print a warning if it doesn't find the * variable. If the variable is not present, then the class * is not completely initialized, so return. */ if ((myClassVar_object = __ctalkGetClassVariable (self, "myClassVar", FALSE)) == NULL) { return NULL; } ... Initialize myClassVar here. ... classInitVar = __ctalkCreateObjectInit ("classInitDone", "Integer", "Magnitude", GLOBAL_VAR, "1"); __ctalkAddClassVariable (classObject, "classInitDone", classInitVar); return NULL; } ... Other methods. ...
When writing classes, you need to remember that Ctalk initializes the class and its methods and data in the following order.
Remember that a class definition only requires a superclass. All of the other elements are optional.
It is possible, when initializing complex classes, to construct
objects before the class is completely defined. That’s why
classInit
above checks for the class variable myClassVar
and returns if it is not present.
However, Ctalk checks the class initialization every time it creates
an instance of MyClass,
so a constructor method for MyClass
might look like the following.
MyClass instanceMethod new (char *myObjectName) { MyClass super new myObjectName; __ctalkInstanceVarsFromClassObject (myObjectName); MyClass classInit; return myObjectName; }
The program then initializes the class variables when the class is
completely defined. If classInitDone
is TRUE,
then the
program won’t initialize the class again.
Note: Ctalk does not automatically create a copy of a class’s
instance variables when creating objects. Your program must call
__ctalkInstanceVarsFromClassObject
to create the instance
variables in the new object.
If you simply want to initialize a class without creating a new object, you can add a statement like this to your program.
MyClass classInit;
Another example of a method that performs class initialization is the
method initDigits
(class ClockDigit
) in the program
ltime.c.
The method is too long to list here, but you can find
ltime.c
in the Ctalk distribution’s demos
subdirectory.
The later chapters describe methods and library functions that perform class initialization in more detail.
require
KeywordCtalk programs initialize the basic classes before starting
to initialize the application’s class and method
definitions. However, if your definition needs a class that
Ctalk does not automatically include, you can initialize the
class and include its method and variable definitions before
defining your own class by using the keyword require.
require Boolean;
This can be useful if you write a class and Ctalk outputs a message like this one.
prefix34.c:13: Method raiseException," (class Exception) used before it is defined.
In that case, you might want to add ‘require Exception;’ at the beginning of the class.
If that doesn’t help, another tactic is to add a method to the new class that duplicates the function of the yet-to-be- defined method. Yet another tactic is to create a unique subclass that contains the method you need.
One issue is that the superclass of require's
operand (in the
example above, that’s Boolean's
superclass, Object
) is already
completely defined, or the definitions in the input file that contains
the require
statement can cause circular class references.
However, these errors occur during compilation. If you can write a
method that does what you want at run time, then that may be another
yet strategy that you can use. For example, if you write
an expression like this in a basic class like Symbol
, you’ll
get “Method used before definition” errors.
/* The reference to Key class requires methods that aren't defined yet. */ if ((arg is Symbol) || (arg is Key)) { ... }
Instead, Object
class contains the method isSubClassOf
, which
doesn’t require the compiler to determine the class of arg
beforehand, because the method evaluates the class of ‘arg’ at
run time.
if (arg isSubClassOf "Symbol") { ... }
It’s useful for Ctalk to limit additions the basic classes. You can add a method to an existing class by defining the method in a source file (which is evaluated after all of the basic classes are evaluated), but you can’t add another library file to an existing class. This requires more planning when adding subclasses and methods, but it also makes the compiler trimmer.
When compiling programs, Ctalk looks for class libraries in the following order.
-I
command line
option.
CLASSLIBDIRS
environment
variable.
Ctalk applications occasionally need to look up a class library when they are run. In that case, the program searches directories in the following order.
CLASSLIBDIRS
environment
variable.
The directory where the standard libraries are located is determined when Ctalk is built and installed. This directory defaults to /usr/local/include/ctalk on UNIX systems. For more details, refer to the entry for the __classLibraryPath() function in the language reference.
Ctalk expands directories to their fully qualified names, so if you build a program with a command like this:
$ ctcc -I . myprogram.c -o myprogram
Then Ctalk expands the ‘.’ to the full path of the current directory, and searches for class libraries in the current directory in addition to the directories it normally searches.
In addition, for each search directory, if a subdirectory named
ctalk
exists, then Ctalk searches that subdirectory also.
The classSearchPath
method, defined in Application
class,
returns a String
that contains the class search path. This
example prints the search path, with each directory separated by a
colon (‘:’).
int main () { Application new app; printf ("%s\n", app classSearchPath); }
The ‘--printlibdirs’ command line option prints the directories that Ctalk searches, one directory per line.
One consequence of all this convenient instance data and overloaded methods is that it is easy to confuse which method a message actually translates into. That is because the member class of an instance or class variable might not be the class of the variable’s own instance data.
Here is an example. The openOn
message in the first statement
translates to the openOn
method in the ANSITerminalPane
class, while the openOn
message in the second statement
translates to the openOn
method in the ReadFileStream
class.
ANSITerminalPane new mainWindow; mainWindow paneStream openOn "/dev/ttyS0"; /* Correct. */ mainWindow paneStream inputHandle openOn "/dev/ttyS0"; /* Incorrect. */
This is because inputHandle
in the second statement is actually
a ReadFileStream
, while the program actually wants to call is
the openOn
method from ANSITerminalStream
, which is the
effect of the first statement. The Stream
classes have many
classes in common. In most cases this makes things simpler, but not
always.
There are several ways to work around this issue. This first is to use stack walkbacks and exception handlers whenever a program opens a file. That allows you to see what methods the program’s statements actually translate into. See Debugging.
If you can’t avoid an incorrect message translation in a program, you
can write your own method that only one of your program’s classes
responds to. For example, instead of yet another openOn
method,
your program might define a method like, for example,
openOnInputData
.
Finally, if the program cannot determine in advance which class is going to receive a message, you can write a method that dispatches the receiver to a method of the correct class. Here is an example.
Stream instanceMethod openOnTerminal (String pathName) { Exception new e; if (self is ANSITerminalStream) { if (pathName isATty) { self openOn pathName; } else { e raiseException NOT_A_TTY_X, pathName; } } else { e raiseException INVALID_RECEIVER_X, pathName; } return self; }
Next: Methods, Previous: Collections, Up: Top [Index]