Next: File input and output, Previous: Hello, Up: Top [Index]
Ctalk is designed to work with C. There are a number of classes that correspond directly to the basic C data types.
In Ctalk, you can use instances of the classes that are shown in the table below interchangeably with their C equivalents.
Ctalk Class C Type ----------- ------ Array char **, int **, ... Character char Float float, double Integer int, long int LongInteger long long int String char *
There are a few places that you cannot use objects, like function
parameter declarations or struct
members. You’ll also see a
lot of examples that translate data between objects and C variables.
The manner in which Ctalk translates between the two is a powerful
feature of the language.
Integer,
Character,
LongInteger,
and Float
ClassesLike most object oriented languages, when you declare an
object, you’re actually sending a message to a constructor
method. As we mentioned earlier, the methods that create
objects are called constructors. And as with many object
oriented languages, you create an object with the message
new.
Integer new myInt;
Objects of class Integer
work exactly the same as C variables
of type int.
You can use expressions like the following, as
though you were programming in C.
Integer new myInt; myInt = 2; printf ("%d\n", myInt + myInt);
With several exceptions, you can use all of the operators that C
integers recognize. Prefix and postfix operators, like +,
-,
*,
&,
and so on, also work as methods.
Integer
class also defines some methods as the equivalent of
these operators, like invert
for !
and bitComp
for ~
.
This example shows how you would use the methods in C statements.
int i; /* C variable. */ Integer new myInt; /* Ctalk Object. */ i = 2; myInt = 2; /* These two statements are equivalent. */ printf ("%d\n", !i); printf ("%d\n", myInt invert); /* These two statements are also equivalent. */ printf ("%d\n", ~i); printf ("%d\n", myInt bitComp);
The same is also true for instances of the Character,
LongInteger,
and Float
classes.
For instance, you can add and subtract two characters. This is sometimes useful for conversions, as in the following examples.
Character new receiver; Character new operand; receiver = 'A'; operand = ' '; printf ("%c\n", receiver + operand);
The result is the lower case ‘a’. This has the same effect as:
Character new receiver; receiver = 'A'; printf ("%c\n", receiver toLower);
Or even:
printf ("%c\n", 'A' toLower);
To convert a character to uppercase, you can use either -
or
toUpper.
Both are implemented by the Character
class.
Character new receiver; Character new operand; receiver = 'a'; operand = ' '; /* These two statements produce the same result. */ printf ("%c\n", receiver - operand); printf ("%c\n", receiver toUpper);
For completeness, we should note that you can also achieve the same
effect with the bit operators &
, |
, and ^,
although
their application can be a bit more involved. If you’re not certain how
the operators would work, enter the ASCII value of a character in a
programmer’s calculator and convert it to base 2. Then notice the
effect of changing different bits.
For now, we’ll just show an example of changing a letter’s case - from lower to upper case, or vice versa.
We do this by XOR’ing the fifth bit of the character value. (Note
that 2^5
is 32,
the ASCII value of a space (‘ ’)
character.)
Here is a simple example of the operation.
Character new upperCase; Character new lowerCase; Integer new toggleBit; upperCase = 'Z'; lowerCase = 'z'; toggleBit = 100000b; printf ("%c\n", upperCase ^ toggleBit); printf ("%c\n", lowerCase ^ toggleBit);
String
ClassAs the introduction mentioned, Ctalk overloads many operators. This
is especially obvious of class String.
This code shows how String
class overloads some math operators.
String new s1; String new s2; String new helloString; /* C's strcpy () would also work here. */ s1 = "Hello, "; s2 = "world!"; /* And strcat () could perform the same task here. */ helloString = s1 + s2; printf ("The value of \"helloString\" is \"%s\".\n", helloString); s1 = "s1"; s2 = "s2"; /* Test whether String objects are equal without strcmp (). */ if (s1 == s2) { printf ("The strings s1 and s2 are equal.\n"); } else { printf ("The strings s1 and s2 are not equal.\n"); }
Briefly, the String
instance methods =,
==,
and
+
function similarly to the C
library functions strcpy(3), strcmp(3), and
strcat(3), respectively.
But these operators can also be overloaded. For example, the +
method behaves like C’s strcat() function when the argument is
also a String
object. If the argument is a Integer
,
then the +
method increments the reference to the receiver String
object. This is discussed further below. See String_pointer_math.
Like C, however, instances of String
and Character
are
not interchangeable. If you want to use an instance of class
Character
as a string, you must use the method asString
(class Magnitude
) to return a String
version of the
Character
object.
Here is a brief example. The two printf
statements produce
the same output.
Character new myChar; myChar = 'a'; printf ("%c\n", myChar); printf ("%s\n", myChar asString);
String
class implements several convenience methods. The
split
method, which we described in the previous chapter, splits
a string along a separator character and puts each substring token in
an array.
Here is the statement from the example in the previous chapter. See ctpath.c.
nItems = path split separator, paths;
If the receiver, path,
contains a string like
‘/home/users/joe’, and separator, a Character
contains
‘/’, then split
places the individual names in the
Array,
paths.
The operation looks something like this.
path ---> "/home/users/joe" / | \ / | \ split / | \ v v v -------------------------------- paths ---> |paths at 0|paths at 1|paths at 2| | "home" | "users" | "joe" | --------------------------------
The expression, paths at 0,
is equivalent to the paths[0]
element of a C array, and so on, for the other elements of the array.
Another convenience method, subString,
returns the part of the
receiver string beginning at the first argument. The second argument
is the length of the substring. Here is an example.
myString = "This is a string."; mySubString = myString subString 5, 2;
Ctalk has two methods that allow you to search strings:
search
and quickSearch
.
The search
method allows you to search for text
patterns within strings. It recognizes a few
metacharacters: ‘.’, ‘^’, ‘$’, and ‘*’.
The quickSearch
searches for exact text only, but it
uses a much faster search algorithm.
Both methods take as their arguments the text pattern you
want to search for, and an Array
object, where the
methods return the positions in the receiver where the first
character of the pattern occurs. The first character of the
receiver is offset 0
. The last element of the
offsets array is -1
, which indicates that
search
found no further matches. The methods return an
Integer
that contains the number of matches. This
example shows how a program might search a String
object with quickSearch
.
int main () { String new string; String new pattern; Array new offsets; Integer new nMatches; pattern = "He"; string = "Hello, world! Hello, world, Hello, world!"; nMatches = string quickSearch pattern, offsets; printf ("nMatches: %d\n", nMatches); offsets map { printf ("%d\n", self); } }
When run, the program should produce output similar to this.
nMatches: 3 0 14 28 -1
Here is an example of using search
to find a pattern
in a String
object.
int main () { String new string; String new pattern; Array new offsets; Integer new nMatches; pattern = "l*o"; string = "Hello, world! Hello, world, Hello, world!"; nMatches = string search pattern, offsets; printf ("nMatches: %d\n", nMatches); offsets map { printf ("%d\n", self); } }
When run, the program should produce results like this.
nMatches: 6 2 8 16 22 30 36 -1
The search
method matches as much of a pattern as
possible. It’s also important to remember that a ‘*’
metacharacter matches zero or more occurrences of the
character it follows. So the pattern, "l*o,"
matches both "llo,"
and, "o."
However, Ctalk’s pattern matching does not allow the text that matches a pattern to overlap. So a pattern like ‘.:’ matches the character before each pair of colons in the string ‘item1::item2::item3::item4’, while the pattern ‘:.’ matches only the first colon of each pair of colons.
Strings
As the the section above mentioned, Ctalk overloads math operators for
String
and its subclasses. These operators are +
,
-
, ++
, --
, +=
, -=
, and *
. For
String
objects (and subclasses of String
if the subclass
doesn’t also overload the methods) these methods behave the same as
the equivalent C operators - that is, they increment (or decrement)
the value of the receiver String
by one or more characters.
Here is an example that demonstrates a few of the methods.
int main () { String new s; String new t; Integer new halfLength; Integer new i; s = "Hello, world!"; i = 0; halfLength = 7; t = s; printf ("%s\n", s); while (i++ <= halfLength) printf ("%s\n", t++); while (t-- != NULL) printf ("%s\n", t); }
When run, this program produces the following output.
Hello, world! Hello, world! ello, world! llo, world! lo, world! o, world! , world! world! world! world! world! , world! o, world! lo, world! llo, world! ello, world! Hello, world! (null)
Note that String
and Integer
objects get iterated
separately. There’s a slight semantic difference from C because Ctalk
doesn’t overload subscripts - a Ctalk program would use t at i
to refer to the i’th character in a String
.
As you might expect, though, the expression *str
returns the
first element of str
as a Character
object.
Array
ClassAn Array
in Ctalk is similar to arrays in every other
programming language. Ctalk arrays are collections of objects,
indexed sequentially from index 0
to the last element in the
Array.
To add an element to an Array
object, use the method
atPut.
Array new myArray; String new myString; myString = "Hello, world!"; myArray atPut 0, myString;
The atPut
method has two arguments, the index of the array
element, and the object you want to store there.
To retrieve an Array
element, use the method at.
The
at
method takes one argument, the index of the array element.
Using the previous example, this statement retrieves the first element
of myArray
and prints it.
printf ("%s\n", myArray at 0);
You do not have to predeclare an array to a specific size.
Objects of Array
class can hold as many objects as necessary.
If you want to know how many elements an Array
object contains,
use the method size,
which returns the number of elements as an
Integer
object.
printf ("myArray has %d elements.\n", myArray size);
If you add an object to an array at an index that already contains an
object, atPut
replaces the array element with the new object.
Array new myArray; myArray atPut 0, "My"; myArray atPut 1, "name"; myArray atPut 2, "is"; myArray atPut 3, "Bill"; WriteFileStream classInit; stdoutStream printOn "%s %s %s %s.\n", myArray at 0, myArray at 1, myArray at 2, myArray at 3; myArray atPut 3, "Joe"; stdoutStream printOn "%s %s %s %s.\n", myArray at 0, myArray at 1, myArray at 2, myArray at 3;
The WriteFileStream classInit
statement initializes the
program’s standard output. You need to include it (also run by the
WriteFileStream
method new
) before printing to the
console with Ctalk methods. Later chapters describe file input and
output in greater detail.
Generally, if you want to repeat an operation on all members of an
array, you can use C’s for
, while
, or do
loops.
If you want to iterate over a class using Ctalk, many classes
implement the method map.
The map
method can take the name of another method as its
argument. map
then calls the method with each element of the array
as its receiver.
Here is a more complete implementation of the example above, written
using map.
Array instanceMethod printArrayElement (void) { printf ("%s ", self); return NULL; } int main () { Array new myArray; myArray atPut 0, "My"; myArray atPut 1, "name"; myArray atPut 2, "is"; myArray atPut 3, "Bill"; myArray map printArrayElement; printf ("\n"); myArray atPut 3, "Joe"; myArray map printArrayElement; printf ("\n"); }
In the printArrayElement
method, self
refers to each
successive element of myArray
(in main
.) Each element
of myArray
is an instance of class String.
However, you should note that Array
elements, like elements of
any other collection, can be objects from any class, and C does not
necessarily handle classes that correspond to complex data so easily.
In this case, it is relatively easy for the program to determine how
to deal with a String
object. Mapping over other collection
types, AssociativeArray
s, requires that methods examine
receivers of unknown classes. See Collections.
If Array
elements are instances of complex classes, or the
elements represent complex C data types, then the method may need to
determine the class of its receiver, which is discussed in the
following chapters, and the method may need to translate objects from
one class to another. See Variable Promotion and Type Conversion.
The map
method can also use a block of code as its argument, as
in this example.
int main () { List new l; String new sPrefix; String new s; sPrefix = "This element is "; l push "l1"; l push "l2"; l map { s = sPrefix + self; printf ("%s\n", s); } exit(0); }
You should note that the block has a different scope than the function
or method where it occurs. Objects declared in the method are also
visible within the block, but not vice versa. Also, self
, when
it occurs inside a code block in this manner, refers to each
successive element of the receiver collection (each member of
List
l in the example above), and super
, is
overloaded so that, when used as a receiver, refers to the receiver of
the method that contains the block of code; i.e., the self
of
the method that contains the block.
While you can use a break
statement to exit
that map
block, you cannot (at least currently), return
from the method from within the block. Otherwise, control structures
within a block work just as you would expect. Here is an example from
Object
class.
Object instanceMethod libraryPath (void) { Array new searchDirs; Application new docApp; String new s; String new libraryPathName; String new receiverName; FileStream new f; returnObjectClass String; s = docApp classSearchPath; s split ':', searchDirs; receiverName = self value; searchDirs map { libraryPathName = self + "/" + receiverName; if (f exists libraryPathName) break; } return libraryPathName; }
Once again, self
within the argument block refers to each
successive member of the searchDirs
array.
You can use self
in argument blocks even if a block occurs
in a C function. Otherwise, either Ctalk or the compiler, depending
on the context, will not be able to find the receiver that self
should refer to, and Ctalk or the compiler, or both, will issue
warning or error messages.
If the program tries to use super
within a C function, Ctalk
prints a warning and uses the receiver of the entire code block
itself-generally, that’s the receiver of a map
method.
Many collection classes declare their own versions of map
.
All of them use the __ctalkInlineMethod ()
library function
to actually perform the in-line call. Programs that implement inline
calls elsewhere can (and should) use this function. Refer to the
__ctalkInlineMethod ()
description in the Ctalk Language
Reference for the gritty details.
It’s worth noting here that map
with an argument block does not
allow certain constructs. You can nest argument blocks, but they
cannot be called recursively. In addition, if the first argument to
map
is a separate method, some classes, like List
, allow
you to provide further arguments to the target
method. See MapArguments.
Occasionally, references to self
within code blocks
can be ambiguous. This mostly happens when self
is
used as an argument to a method inside the block. Consider
the following example, which uses mapInstanceVariables
(defined in class Object
).
myObject mapInstanceVariables { if (self name != "value") { s_element = self formatInstanceVariable self asSymbol; /* Here, "self" refers to s_element. */ s = s + s_element; } }
Here, the statement
s_element = self localFormatInstanceVariable self asSymbol;
can cause trouble, because self
refers to
s_element
, not the receiver of the block. The
program can find the formatInstanceVariable
method
(defined in ObjectInspector
class) due to the way
that mapInstanceVariables
defines receivers, but the
‘self asSymbol’ refers to s_element
(the
receiver of =
), instead of referring to the receiver
of the block.
To make this construct clear, the block can appear, somewhat artificially, as the following.
rcvrCopy copy self; /* Here, "self" is the method's receiver. */ myObject mapInstanceVariables { if (self name != "value") { /* Here, "self" is the block's receiver. */ selfVarSymbol = self; /* Assigning to a Symbol creates a reference. */ s_element = rcvrCopy formatInstanceVariable selfVarSymbol; s = s + s_element; } }
However, it’s also useful to use super
in the same manner.
So the example above gets slightly abbreviated.
myObject mapInstanceVariables { if (self name != "value") { /* Here, "self" is the block's receiver. */ selfVarSymbol = self; /* Assigning to a Symbol creates a reference. */ s_element = super formatInstanceVariable selfVarSymbol; s = s + s_element; } }
Use of the mapInstanceVariable
method is discussed
further in the following sections. See The -> Method.
Array
objects, like other subclasses of Collection
, can
use overloaded math operators to loop through the collection’s elements.
These methods include --
and ++
. Here’s a brief example.
int main (int argc, char **argv) { Array new a; Key new k; a atPut 0, "value0"; a atPut 1, "value1"; a atPut 2, "value2"; a atPut 3, "value3"; k = *a; while (++k) printf ("%s\n", *k); }
When run the program produces output like this.
value0 value1 value2 value3
There’s a more complete discussion of how math operators work with
Collection
objects and Collection's
subclasses further
on. See CollectionMathOperators.
The result of evaluating one message can be the receiver of another message, as in the following example.
int main () { ReadFileStream new infileStream; Array new pathDirs; Integer new nDirs; SystemErrnoException new e; /* Substitute the path of the file here. */ infileStream openOn "/path/name/here"; if (e pending) e handle; nDirs = infileStream streamPath split '/', pathDirs; printf ("%i\n", nDirs); printf ("%s\n", pathDirs at 0); printf ("%s\n", pathDirs at 1); printf ("%s\n", pathDirs at 2); }
The receiver of the method split,
in the statement
nDirs = infileStream streamPath split '/', pathDirs;
is the result of sending the message streamPath
to
infileStream.
The result is the path name of
infileStream,
a String.
The result of one message must be a member of the same class as the receiver of the next message, or a member of a subclass if the message refers to a method.
Note the use of the SystemErrnoException
object, e.
This class is used for recording system errors from methods that
interact with the operating system. The later sections describe
exception handling in more detail.
The Ctalk Language Reference describes the class and instance variables of each class, and the return classes of methods.
If you enclose a simple expression in parentheses, then Ctalk can treat the complete expression as a receiver. This expression provides an example.
("Hello, " + "world!") length;
This expression returns the result 13
, because the
expression "Hello, " + "world!"
returns a
String
object containing the result, the complete
String
, "Hello, world!"
, the receiver
of the length
message.
C variables and objects work equivalently in receiver expressions. So the following example,
String new s1; String new s2; s1 = "Hello, "; s2 = "world!"; (s1 + s2) length;
is equivalent to the example above, as is this example:
char s1[MAXLABEL]; char s2[MAXLABEL] strcpy (s1, "Hello, "); strcpy (s2, "world!); (s1 + s2) length;
Method parameters and self
also work normally within
simple receiver expressions. The following example does
exactly what you would expect it to do.
String instanceMethod catLengthArg (String sArg) { printf ("%d\n", (self + sArg) length); } int main () { String new s; String new sArg; s = "Hello, "; sArg = "world!"; s catLengthArg sArg; }
You can overload common C math operators in just about any case where you have an expression as a receiver. Operator precedence in Ctalk works a little differently than C, however, because non-operator methods bind more tightly than math operators; that is, they have a higher precedence.
So an expression like the following doesn’t do what you expect.
("Hello, " + "world ") + ("again" + "!") length;
That is because the length
message has a higher
precedence that +
, so length
evaluates only
the subexpression, ("again" + "!")
, which then
generates an invalid operand exception when the second
+
is evaluated, due to the fact that the
length
method returns an Integer
, while the
receiver of the second +
message is a String
.
To get length
to evaluate the complete expression,
enclose the expression in parentheses.
(("Hello, " + "world ") + ("again" + "!")) length;
Here’s another example of an expresison where it’s necessary to add parentheses.
(*itemPtr) org x = self resources integerAt "scrollWidth" + self resources integerAt "leftMargin";
This expression is ambiguous because the ‘+’ operator in the
argument list is overloaded in both String
and Integer
classes. So if Ctalk is to interpret the argument list, it
might consider the first integerAt
token to use the rest of the
expression as its single argument, in which case Ctalk would interpret
the argument expression like this, and try to perform an increment of
the "scrollWidth" token, which isn’t (as yet) supported, and would
(probably) make no sense, because "scrollWidth" is a key in the
AssociativeArray,
"resources".
(*itemPtr) org x = self resources integerAt ("scrollWidth" + self resources integerAt "leftMargin");
Instead, what we really want is to interpret the complete argument expression to the ‘=’ operator as this.
(*itemPtr) org x = (self resources integerAt "scrollWidth") + (self resources integerAt "leftMargin");
This causes Ctalk to first evaluate each operand of the ‘+’
operator, which results in Integer
operands, so Ctalk evaluates
the entire expression as an integer addition, and it uses the
Integer: +
method, and returns the addition of the two
Integer
values that are stored in the "resources"
AssociativeArray
.
This section could also be titled When to use become, copy, or =.
Because objects can be much more complex than C data types, variable promotion and type conversion become much more complex operations in object oriented languages than in C. This will become apparent later on.
For now, because we’re discussing basic object classes, it should be
sufficient to look at the methods of class Magnitude
for our examples, which is the superclass of classes like
Character,
String
(a subclass of
Character
), Integer,
and LongInteger.
You need to use the Magnitude
methods asCharacter,
asInteger,
and asLongInteger
to perform type conversions
explicitly. The Ctalk Language Reference describes the type
conversion methods for each class.
When making assignments, you can use =
for receivers and
arguments that a function or method declares explicitly, or with
constant receivers and objects. The classes Integer,
Character,
and String
each implement a =
method,
so a program would use =
with objects that are instances of
these classes, or with constants like 1,
a,
or
"Hello, world!"
In other cases, if a statement needs to make an assignment
and the statement contains receiver or instance variables,
or the expression contains an operand that the program
cannot determine until run time, or if the receiver or
operand are of completely different classes, then
become
(class Object
) can in most cases perform
the type conversion and assignment. This is often the case
with classes that represent complex data, like files or
windows.
The become
method takes into account situations where the type
of a receiver or local object may change during the course of a
program’s execution.
Warning: However, the method become
alters or even deletes the original object. That
means you should not use it with objects that are passed as
Symbol
values, or other expressions where an
application uses an object outside of it original scope.
Here’s a partial example, from the attachTo
method in
X11CanvasPane
. The become
method mutates the
instance variables, paneBuffer
and
paneBackingStore
into X11Bitmap
objects.
Because the front end does not know for certain that the
objects have been mutated (because there’s nothing
syntactically special about a method like become
that
indicates its function). So when the front end encounters
the initialize
method (which is defined in class
X11Bitmap
), the language may only be able to assume
that paneBuffer
and paneBackingStore
are still
Symbol
objects, which do not have an initialize
method. So the front end generates a warning and informs
you that the statement’s evaluation is going to wait until
run time.
X11CanvasPane instanceMethod attachTo (Object parentPane) { X11Bitmap new xPaneBuffer; X11Bitmap new xPaneBackingStore; self super attachTo parentPane; self paneBuffer become xPaneBuffer; self paneBackingStore become xPaneBackingStore; if (parentPane containerMode == "full") { self viewWidth = parentPane size x; self viewHeight = parentPane size y; self size x = parentPane size x; self size y = parentPane size y; /* * These two statements generate warnings, and * Ctalk waits until run time to evaluate them. */ self paneBuffer initialize self size x, self size y, self depth; self paneBackingStore initialize self size x, self size y, self depth; } else { fprintf (stderr, "attachTo (class X11CanvasPane) : undefined containerMode.\n"); } return NULL; }
If you want better statement checking, then you can try
performing whatever operations you need first (in this
example, those are the statements that contain the
initialize
method), and then mutating the
objects with become
second.
X11Bitmap new xPaneBuffer; X11Bitmap new xPaneBackingStore; ... xPaneBuffer initialize self size x, self size y, self depth; xPaneBackingStore initialize self size x, self size y, self depth; ... self paneBuffer become xPaneBuffer; self paneBackingStore become xPaneBackingStore;
Classes like X11PaneDispatcher
, X11CanvasPane
,
and X11TextPane
use a lot of object references.
There are some factors that you need to watch out for when
working with Symbol
objects that refer to other
objects.
We use X11Bitmap
objects because they allow us to
maintain graphics features, like colors and fonts, within
the X11Bitmap
object, which considerably simplifies
the classes of higher level objects of the Pane
subclasses. It also helps guarantee that we need to set
graphics features only at the beginning of the program, or
once again only when the program requests that the window
display at different font or color, or some other graphics
operation.
There’s more discussion about graphics further on, and the next sections talk more about object references.
When a statement needs to duplicate objects of the same class, then it
should use the copy
(class Object
) method.
Remember, though, that the receiver of the copy
message is the target of the copy operation. The
copy
method completely replaces the receiver with the
source object that is copy
’s argument. That means the
original receiver object is no longer available.
If a class contains data that requires a special protocol to duplicate objects, then it can implement its own versions of these methods, or use any other way it needs to assign objects.
The Symbol
class gives programs the ability to use
the same object anywhere in a program, and makes it much
easier to maintain multiple references to objects. Symbol
objects accomplish this by maintaining, instead of a unique
sub-object, a reference to another, original object.
From here to the end of the tutorial, you’ll see a lot of information about object references. If the information seems to be repetitive, it’s because references are essential to writing complex programs and add a level of complexity to the language that you need to be aware of.
Passing objects by reference allows programs to modify objects just about anywhere. Then, in many cases, the application can use an object without knowing beforehand exactly what class the object is, or what data the object contains, until an expression is actually evaluated. It’s something that you need to be aware of when writing complex programs.
The =
method of class Symbol
behaves differently depending
on its context. It can either assign an object to a Symbol's
label,
or it can duplicate the symbol’s reference, depending on whether the
Symbol
object is the receiver a method like *
.
The *
method always provides access to the object referred to
by the receiver. In this sense, *
acts very much like C’s
*
operator. It’s also convenient to refer to Symbols
in
this context as pointers, following the C terminology.
The following example might make this a little clearer.
int main () { Symbol new sym1; Symbol new sym2; Symbol new sym3; Integer new i; i = 2; sym3 = sym1; /* Save the original value of sym1. */ sym1 = i; printf ("%d\n", sym1); *sym2 = i; /* Pointer context - refer to the object using '*'. */ printf ("%d\n", *sym2); sym1 = sym3; /* Restore sym1 to the original object. */ i = 4; *sym1 = i; /* Pointer context again. */ printf ("%d\n", *sym1); }
The *
method is also a convenient shorthand for the
setValue
and getValue
methods, which can fit into places
where a prefix operator might be confusing or just plain syntactically
incorrect, and which have a higher operator precedence (remember that
method labels bind more tightly than operators). This somewhat
hypothetical example shows one case where the precedence of an
operator can cause unpredictable behavior.
TreeNode new head; TreeNode new tChild; Symbol new tSib; tSib = TreeNode basicNew "Sibling Node"; head makeSibling tSib; .... /* Do some stuff */ head siblings map { *tSib = self; tChild = TreeNode basicNew "Child Node"; ... /* Do some more stuff. */ tSib getValue addChild tChild; /* This expression evaluates correctly. */ *tSib addChild tChild; /* This expression doesn't evaluate left-to-right as you might expect, because "addChild" has a higher precedence than "*". */ (*tSib) addChild tChild; /* Adding parentheses causes the "*" operator to be evaluated first, so this expression evaluates left-to-right. */ }
In some cases, object references tell Ctalk to wait until the application actually runs before trying to evaluate an expression. Consider another somewhat hypothetical example.
If we declare an instance variable like this:
X11Pane instanceVariable container Object NULL;
And then, further down, the program contains an expression like this:
self container deleteAndClose;
Ctalk cannot generate the correct code, because the value of
container
is actually an Object
, and it can’t
determine beforehand from the Object
class library
where to find the deleteAndClose
method. We might
assume in this case that container
is also a
X11Pane,
but using class membership to bind receivers
and methods raises all sorts of havoc; for example, when
performing math operations with instance variables. And in
some cases, the application needs to take care that the
method’s class–here, the X11Pane
class defines the
deleteAndClose
method–actually has been evaluated in order
to avoid an undefined forward reference.
Instead, a class might declare an instance variable like this.
X11Pane instanceVariable container Symbol NULL;
Noting that a method like getValue
(defined in class
Symbol
) returns Any
class of object, Ctalk then
knows that it should wait until the program is run before
trying to evaluate the expression. So expressions like this
example, from the subPaneResize
method above, isn’t
actually evaluated until the program runs.
__subPaneRef getValue displayText;
In some cases, if you try to use an instance variable message with an object reference, Ctalk generates a warning that the instance variable message is ambiguous.
For example, the x
and y
messages in the
printf(3) arguments are ambiguous.
int main () { Point new p; Symbol new s; p x = 125; p y = 175; s = p; /* Ambiguous messsages, "x," and, "y." */ printf ("%d %d\n", s getValue x value, s getValue y value); }
In these cases, Ctalk generates a warning message, so when
building this program, the output looks like this (assuming
the input file is named example.c
).
$ ctcc example.c -o example /usr/local/bin/ctalk example.c -o example.i && /usr/bin/gcc example.i -o example -lctalk -L/usr/local/gcc-current/lib -L/usr/local/lib -L/usr/lib -L/usr/local/lib -L/usr/lib example.c:10: Ambiguous operand: Warning: Message, "x," follows method, "getValue," which returns "Any" class object. example.c:10: Ambiguous operand: Warning: Message, "y," follows method, "getValue," which returns "Any" class object.
When evaluating references, Ctalk can generally figure out that
successive calls to getValue
(or *
) mean the same
reference, but a program shouldn’t depend on this behavior, especially
when there are instance variable messages or other method messages in
the expression.
The following examples might help explain this.
Symbol new mySym; Integer new myInt; myInt = 1; mySym = myInt; printf ("%d", mySym getValue value); /* Correct. */ mySym = myInt asSymbol; printf ("%d", mySym getValue value); /* Still correct. */ printf ("%d", mySym getValue getValue value); /* Not necessarily correct. */
In the third example, Ctalk can still figure the expression
out because the receiver, mySym
is the same for both
getValue
messages. To repeat, programs shouldn’t depend
on this being the case for complex expressions.
These characteristics also apply to the subclasses of Symbol;
for example, the Key
class. Key
objects are mainly used
as the indices of Collection
objects and its subclasses.
Keys
help simplify the task of looping through
Collection
members. They also help more specialized subclasses
extend the basic Collection
object. There’s more discussion of
how to use Keys
and references to them below.
See Collections.
The previous section described how to assign object references in relatively simple expressions. However, in C it is often necessary to provide multiple levels of indirection. Declarations like ‘char **argv’ and ‘char *myvar[512]’ occur frequently in C programs.
If objects need to use multiple levels of referencing and
dereferencing, We’ve already mentioned Symbol
class’s
getValue
method, and its synonym, *
. Ctalk also
provides several other methods for this purpose: addressOf
(defined in Object
class) and deref
(defined in
Symbol
class).
These two methods make it possible to construct expressions like those in the following example.
int main () { Integer new i; Symbol new s; i = 1; s = i addressOf; printf ("%d\n", i); printf ("%d\n", s deref); }
When run, the output of the two printf statements is identical.
The following examples also describe shorthands for the
methods. addressOf
is functionally equivalent to the C
‘&’ unary operator, and deref
is functionally
equivalent to the unary ‘*’ operator.
If a program needs to use multiple levels of indirection, it
can use the addresOf
and deref
messages multiple
times, as in this example.
int main () { Point new p; Symbol new s; p x = 1; s = p x addressOf addressOf; printf ("%d\n", p x); printf ("%d\n", s deref deref); }
Or even,
int main () { Point new p; Symbol new s; p x = 1; s = p x addressOf addressOf addressOf; printf ("%d\n", p x); printf ("%d\n", s deref deref deref); }
In each case, the output of the printf statements is the same.
The main difference between the addressOf
and
deref
expressions, compared with =
and
getValue
, is that =
only converts a symbol value
once, while addressOf
always returns a Symbol
object that points to the receiver.
In practice, it works out that deref
allows statements
with a little more precision than a series of getValue
statements would allow.
->
Method and C Object MembersCtalk also overloads the C language’s dereference
(->
) operator so you can obtain the value of any
object’s C members by referring to the C name that Ctalk
uses internally.
The member names of a C OBJECT
typedef are:
__o_name __o_classname __o_class __o_superclassname __o_superclass __o_p_obj __o_value instance_methods class_methods instancevars classvars scope nrefs next prev
Note: The use of __o_classname
and __o_superclassname
are being phased out. You should use the CLASSNAME
and
SUPERCLASSNAME
macros instead.
If the ->
operator is a method, then Ctalk recognizes both
the old and new names. However, if ->
is used with
an OBJECT *
as a C operator, then it is necessary to
use the CLASSNAME and SUPERCLASSNAME macros.
Ctalk uses some special semantics with the ->
method.
First of all, when given a C name as an argument, Ctalk
returns a Symbol
, String
or Integer
object with a reference to the original object whenever
necessary.
That means, where an object contains a reference to its
class object, for example, the ->
method returns a
reference to the actual object, so you can work with the
actual object representation of the C value.
This can be a bit tricky in practice, because you need to
make sure that a program knows what class of object to expect
in return - a String,
Integer,
or
Symbol
.
Often Ctalk can tell by the context of a statement what class of object to expect, as in this example.
int main () { Integer new i; Integer new mbrInteger; String new mbrString; Symbol new mbrSymbol; mbrString = i -> __o_name; printf ("name: %s\n", mbrString); mbrString = i -> __o_classname; printf ("classname: %s\n", mbrString); mbrSymbol = i -> __o_class; printf ("class: %p\n", mbrSymbol); mbrString = i -> __o_superclassname; printf ("superclassname: %s\n", mbrString); mbrSymbol = i -> __o_superclass; printf ("superclass: %p\n", mbrSymbol); mbrSymbol = i -> __o_p_obj; printf ("parent: %p\n", mbrSymbol); mbrString = i -> __o_value; printf ("value: %s\n", mbrString); mbrInteger = i -> scope; printf ("scope: %d\n", mbrInteger); mbrInteger = i -> nrefs; printf ("references: %d\n", mbrInteger); mbrSymbol = i -> instancevars; printf ("instancevars: %p\n", mbrSymbol); mbrSymbol = i -> classvars; printf ("classvars: %p\n", mbrSymbol); mbrInteger = i -> instance_methods; printf ("instance methods: %p\n", mbrInteger); mbrInteger = i -> class_methods; printf ("class methods: %p\n", mbrInteger); mbrSymbol = i -> prev; printf ("prev: %p\n", mbrSymbol); mbrSymbol = i -> next; printf ("next: %p\n", mbrSymbol); }
That’s because the =
method of each receiver -
mbrString
, mbrInteger
, and mbrSymbol
-
perform the necessary type conversions.
However, if you use the ->
method inline, as in this
statement:
stdoutStream printOn "name: %s\n", self -> __o_name;
You need to make sure that the method printOn
(from
WriteFileStream
class in this example) knows what
class the argument self -> __o_name
is going to
return. For arguments that are members of common classes
like String
and Integer
objects, this normally
isn’t a problem.
Symbol objects, however, can be trickier, because they are
references. So to get the actual C pointer to an
OBJECT
type, a program needs to use a statement like
the following.
stdoutStream printOn "superclass: %p\n", self -> __o_superclass asSymbol;
This can lead to some very convoluted statements. To
retrieve the value
instance variable of an object
reference, you would need a statement or two like these examples.
__objectRef getValue -> instancevars getValue value;
Or even more confusingly,
refValue become __objectRef getValue -> instancevars getValue value;
As mentioned in the last section, assigning an object to a
Symbol
or using the asSymbol
method have the
effect of dereferencing an object, similar to referencing
and dereferencing objects in C with the ‘*’, ‘&’,
‘->’, and ‘.’ operators. The effective use of these
C operators takes practice, and the same is true of Ctalk.
So you can have a series of statements like the following example.
Symbol new varValue; ... varValue = self -> instancevars; myString printOn "value: %s (%s)\n", varValue getValue value -> __o_value, varValue getValue value -> __o_classname;
But you can also reference and then dereference an object multiple times.
Symbol new varValue; ... varValue = self -> instancevars asSymbol; myString printOn "value: %s (%s)\n", varValue getValue getValue value -> __o_value, varValue getValue getValue value -> __o_classname;
There’s another, more serious issue when parsing instance variables. That is, if Ctalk needs to make a copy of an instance variable, it cannot also copy the links to the object’s other instance variables. To work reliably, Ctalk would need to copy the entire set of objects. That means, normally, you would not be able to loop through a set of instance variables to examine their contents.
Instead, you would need to create statements like these:
__objectRef getValue -> instancevars -> next getValue value; __objectRef getValue -> instancevars -> next -> next getValue value; ...
And this is probably not what you want, and Ctalk does not
provide special handling for ->
arguments, because
->
, as much as possible, needs to work like any other
method.
Instead, Ctalk provides a method, mapInstanceVariables
,
defined in Object
class, that performs this work for
you. (There is also a mapClassVariables
method,
described below.) It works similarly to the map
methods in
List
and other collection classes, except that it
works specifically on instance variable lists. It’s also
designed to handle object references differently.
For example, this program looks rather simple.
int main () { Symbol new sym; Point new p; p x = 100; p y = 150; sym = p; sym getValue mapInstanceVariables { if (self -> __o_name != "value") { printf ("name: %s\n", self -> __o_name); printf ("value: %d\n", self value -> __o_value asInteger); } } }
Except that there is a slight pitfall - sym
is a Symbol
,
that is a reference to a Point
object, p
, and Ctalk
can’t determine the actual class of the block’s receiver until the
program is run. (The =
method in the statement sym = p;
doesn’t have very many special semantics, either.)
However, mapInstanceVariables
can take this factor
into account, and it will check for possible receiver
classes and if necessary issue a warning.
The output of the example above looks something like this.
Warning: From mapInstanceVariables (class Object): Warning: The argument block thinks the receiver's class is, "Symbol," Warning: when it is actually, "Point." name: x value: 100 name: y value: 150
If you want to avoid the warning, you can rewrite the program like this.
int main () { Point new p; p x = 100; p y = 150; p mapInstanceVariables { if (self -> __o_name != "value") { printf ("name: %s\n", self -> __o_name); printf ("value: %d\n", self value -> __o_value asInteger); } } }
You should also note that ->
returns the value of an object as a
String
object. That means if the program needs to perform any
numeric conversions, it should first morph the result into an
Integer
, Float
, or other class, using asInteger
(defined in Magnitude
and String
classes), and so on.
Here is an example of how you might manage some simple
numeric conversions, with class variable receivers provided
by the mapClassVariables
method, defined in
Object
class. When working with objects, methods
like printOn
and writeFormat
(defined in
String
and WriteFileStream
classes) are better
at formatting the values of objects than simply using
printf(3).
int main () { WriteFileStream new w; String new s; String new s_element; Symbol new varRef; s = ""; w mapClassVariables { varRef = self; s_element printOn "%s (%s)\n", varRef getValue -> __o_name, varRef getValue -> __o_classname; s = s + s_element; s_element printOn "%d (%s)\n", varRef getValue -> __o_value asInteger, varRef getValue -> __o_classname; s = s + s_element; } stdoutStream writeFormat "%s\n", s; }
Here the use of writeFormat
is mainly for
illustration. It is likely that writeFormat
will be
deprecated in future releases.
There will certainly be times when you need to work with
object references in C. and there are a number of Ctalk
library API functions that can help you translate between
object values (which are represented internally as formatted
char *
’s), and C variables, and vice versa. These
functions and others appear in many of the examples in this
this document and are described in more detail in the
Ctalk Language Reference.
void *__ctalkGenericPtrFromStr (char *); void __ctalkObjValPtr (OBJECT *, void *); void *__ctalkStrToPtr (char *); char __ctalk_to_c_char (OBJECT *); double __ctalk_to_c_double (OBJECT *); int __ctalk_to_c_int (OBJECT *); char *__ctalk_to_c_char_ptr (OBJECT *); void *__ctalk_to_c_ptr (OBJECT *); long long int __ctalk_to_c_longlong (OBJECT *); OBJECT *obj_ref_str (char *);
We should mention explicitly one other point about methods and messages, and it is related to some of the examples in the previous sections. Ctalk objects also respond to messages that name instance variables.
All Ctalk objects contain the instance variable, value,
which
is inherited from class Object.
Because of this, if you use an
object without a message, Ctalk uses the value
message by
default.
In this simple example, the following two statements are equivalent.
printf ("%d\n", myInt); printf ("%d\n", myInt value);
With more complex objects, for example like those in the Stream
and Pane
subclasses, statements that contain instance variable
messages can quickly become complex. The later chapters and code
snippets in The Ctalk Language Reference describe how to use
instance and class variable messages with complex objects.
The example from the previous section is typical.
Point new p; p x = 100; p y = 150; ...
Here x
and y
are not methods, but instance
variables defined in Point
class. These messages
often work like method messages in expressions, except that
you can also assign values to them, by giving the name of
the instance variable and the object it belongs to.
The rules for using a default value
method have a few exceptions.
They occur mainly when Ctalk translates between objects and C. That’s
partly because C normally assigns objects by value, while Ctalk works
with object references. This manual discusses object references here
and in more detail later on, but here we’ll mention the exceptions so
that you’re aware of them.
As we’ll get to, objects are represented internally as an OBJECT
*
C type, and that’s how programs access them using C.
So we can assign an object to a C variable with a set of statements like the following.
Object new myObj; /* A Ctalk object. */ OBJECT *myObj_alias; /* A C reference to an object. */ myObj_alias = myObj;
Then you can access the object using the myObj_alias
variable.
In this case, Ctalk doesn’t try to cast the object - it’s already an
OBJECT *
. That is, Ctalk doesn’t consider that there’s an
implicit value
message following the object. (Though you can, of
course, add a value
message to the statement if you actually do
want the value.)
The same is true of using printf ()
to print an object. If
Ctalk sees that you want to print a pointer, and there’s no message
following the object, then Ctalk does just that. These statements
simply print the address of an object.
printf ("%p\n", myObject); printf ("%#x\n", myObject);
The second printf ()
statement might generate a warning with
some compilers, but as long as pointers are the same size as
ints
, the statement should work fine.
The other case where the complete object is used on its own is in the
case of the self
keyword. Ctalk doesn’t consider that it has a
default value
message following it. So the examples above are
true for self
also.
OBJECT *self_alias; /* A C reference to an object. */ self_alias = self; printf ("%p\n", self); printf ("%#x\n", (unsigned int)self_alias); /* The cast prevents a warning from the compiler. */
Finally, if there’s a C statement that you don’t want Ctalk to
evaluate as a value assignment, you can always use the eval
keyword to so that Ctalk interprets the statement verbatim.
myObj_alias = myObj; /* These two statements are equivalent. */ myObj_alias = eval myObj;
No object oriented programming manual would be complete without some discussion of the way objects refer to themselves. The semantics of objects might seem obvious after a bit, but they cause Ctalk to use another set of rules when referring to classes themselves.
For example, if a program contains the statements,
myString = "Key"; /* Select the Key class. */ printf ("%s\n", myString superclassName);
The program will display, ‘Character,’ which is the superclass
of String
class, and not Symbol
, which is the superclass
of Key
class.
For these cases, the method classObject
(in Object
class), provides the actual class object when you give it a class
name.
With the use of classObject
, the code sample would become
this.
myString = "Key"; myClassObj = myString classObject; printf ("%s\n", myClassObj superclassName);
As a general rule, if you’re working with elements of an object, it’s safer to work on the actual object, and not another object that simply contains a classes’ name.
However, if you’re trying to find information that’s external to an
object, for example, its file path in the class library, it’s safe to
use the name of a class expressed as the value of a String
object.
The difference is not always clear-cut, and it might take some
checking of what kind of receiver the method expects and what its
function is in order to figure out how a class argument’s semantics
are going to work. Here’s a fragment from the classdoc
program that provides a small illustration of this. Refer to the
manual page, classdoc(1) for more information about
classdoc.
Application new classDocApp; String new classArg; Object new classObj; classObj = classArg classObject; /* classArg contains the class name supplied by the user. */ printf ("%s\n", classObj superclassName); printf ("%s\n", classArg libraryPath); printf ("%s\n", classDocApp classDocString classObj);
Next: File input and output, Previous: Hello, Up: Top [Index]