This page describes an extension of C called OOC, that is, Object-Oriented C. It is meant to be faster than Objective-C and simpler than C++.
Monotype text in bold is used for reserved words, and should not be used for new identifiers. Monotype text in italic is meant to be replaced by actual identifiers or other code.
Besides files of type .h and .c, the source code also resides in files of type .ooh and .ooc. The files .ooh contain the declarations and public bits of the program, while files .ooc hold the implementations and all the private things.
There's an additional pre-processor directive:
#import <filename>
#import "filename"
This directive is similar to #include, except it includes filename only once.
Functions, as well as class methods, can be overloaded. They can also have default argument values as in C++.
The simplest new type is probably the errors construct:
errors name
{
error-1,
error-2,
error-3,
...
}
error-1 and so on are any valid identifiers in C. These are used with the statements error() and iferror(), explained below. Here is an example of a list of errors:
errors FileError
{
ReadAfterEOF,
DiskFull,
BadHandle,
FileAlreadyOpen
}
Next, comes the protocols. This is simply a list of function declarations:
protocol name <generics>: super-list
{
function-declaration-1;
function-declaration-2;
function-declaration-3;
...
}
It can inherit from other protocols (the super-list), so that a class implementing it must implement all the functions in the super protocols along with the newly declared functions. Here are some examples:
protocol SimpleElementProtocol
{
void DecElement(void);
void IncElement(int amount);
}
protocol ArrayElementProtocol <Object O>: SimpleElementProtocol
{
O GetElement(int index);
O SetElement(int index, O element);
}
The final new type is the class. A class is a structure containing members which can be variables or functions.
class name <generics>: super-class, protocols-list (category-name)
{
member-declaration-1;
member-declaration-2;
member-declaration-3;
...
}
Variables must come first. A class may not have a super class, in which case it inherits from no class. Also, its protocols list may be empty. Variables may have the getter and setter modifiers, the first to allow:
myVar = myClass->x;
and the second to allow:
myClass->x = myVar;
outside the class (inside the class and its subclasses, the variable x is always accessible). Here is an example of a class declaration:
class Rectangle: Object
{
getter float x, y;
float w, h;
Rectangle* Init(float x, float y, float w, float h);
float GetArea(void);
}
This class has four variables (all floats) and two methods, one to initialize its contents and another to return an area. Also, its superclass is the class Object. The class declaration for Rectangle above goes in a .ooh header file, if we wish to make it public. Then, in a .ooc file comes the implementation of the declared function members, in this way:
Rectangle* Rectangle::Init(float x, float y, float w, float h)
{
self->x = x;
self->y = y;
self->w = w;
self->h = h;
return self;
}
float Rectangle::GetArea(void)
{
return w * h;
}
Now, when we wish to use the class Rectangle, we simply declare it and initialize it, like this:
Rectangle *myRect = Rectangle->Alloc()->Init(0.0, 0.0, 1.0, 0.5);
Log("The area is %i.", myRect->GetArea());
This creates an instance of class Rectangle by allocating space in the heap first, and then initializing the x, y, w, and h members with some appropriate values. Class instances must be pointers, that is, we cannot say:
Rectangle myRect; // This is wrong!
There is one primitive type that may sometimes be useful: id. It stands for any class and we can use it thus:
id *instance-name;
From then on, instance-name may refer to any class we desire. If the class has at least one member function that is not implemented anywhere, it is said to be abstract, and cannot be instantiated. We'll have to create a subclass that implements that function and then use that subclass. The class Rectangle above is a subclass of the class Object. Here is an example of a subclass of Rectangle called Lozenge:
class Lozenge: Rectangle
{
float side;
override Lozenge* Init(float x, float y, float w, float h);
float GetSide(void);
}
It has a new member variable, called side, it redefines the Init() member function (note the mandatory use of override when redefining a function with the same name and signature as the one in the super class), and it adds a new member function called GetSide() to obtain the length of its side (we could have used getter). The implementations could be like this:
Lozenge* Lozenge::Init(float x, float y, float w, float h)
{
super->Init(x, y, w, h);
side = sqrt(((w * w) + (h * h)) / 2.0);
return self;
}
float Lozenge::GetSide(void)
{
return side;
}
Additionally, note the use of
super
and also of
self
in these examples. Categories work in a way similar to Objective-C. They allow the addition of new member functions to existing classes without having to subclass those classes. Extensions cannot contain new member variables, only function declarations, like this:
class name (category-name)
{
function-declaration-1;
function-declaration-2;
function-declaration-3;
...
}
Here is an example:
class Rectangle (Perimeter)
{
float GetPerimeter(void);
}
This code is inserted into a file called RectanglePerimeter.ooh if we wish to make it public. Then in RectanglePerimeter.ooc we add:
float Rectangle::GetPerimeter(void)
{
return 2 * (w + h);
}
This extension of C supports the additional operator:
->>
which turns the expression:
a = myObj->>field;
into its equivalent form:
a = (*(*myObj)).field;
We have what other languages call the for-in loop:
iterate(variable-declaration, iterable-object)
{
statement-1;
statement-2;
statement-3;
...
}
Here's an example (assume StringAppend() and CharUpper() are functions defined somewhere in the program):
String s1 = "Example", s2;
iterate(char c, s1)
StringAppend(s2, CharUpper(c));
After running this program, s2 contains the string "EXAMPLE". Finally, we have the errors statements. To throw an error, we simply say:
error(type.name);
To catch errors say
iferror(type.name, ...)
{
statement-1;
statement-2;
statement-3;
...
}
iferror(type, ...)
{
statement-1;
statement-2;
statement-3;
...
}
...
purge
{
statement-1;
statement-2;
statement-3;
...
}
These iferror() statements usually come at the end of a function. Their arguments can be type.name to catch a single error, or simply type to catch a whole family of errors. These names are defined by the errors enumeration, as described above. Also, more than one error (or family) can be caught in each iferror() statement. Finally, the purge statements are executed after the code to handle the error is executed. (This is the throw-cath-finally mechanism of other languages.)
Copyright © 2014 Rui Cuco. All rights reserved.
All trademarks mentioned in these pages belong to their respective owners.