Contents
Index
Summary
Citation
Doornik
Ox Language Tutorial
Contents:
- Introduction
- A first Ox program
- Running the first Ox program
- Linking multiple files
- Variables, types and scope
- Functions and function arguments
- The for and while loops
- The if statement
- Operations and matrix programming
- Arrays
- Object-oriented programming
- Style and Hungarian notation
- Optimizing for speed
Tables:
- Hungarian notation prefixes
- Hungarian notation, case sensitivity
Introduction
This chapter will give a brief overview of the important elements
of the Ox language.
A more formal description of the Ox syntax is in
the syntax chapter.
The next section will introduce the first Ox program,
showing the similarity between the syntax of Ox and that of the
C language. (the standard reference for C is Kernighan and Ritchie, 1988)
We shall see that a program always includes header files to define
the standard library functions, and that it must have a main
function, which is where program control starts. We shall also see
that the body of the function is enclosed in curly braces.
A first Ox program
Ox is an object-oriented matrix language with a syntax similar
to the C and C++ languages. This similarity is most clear in
syntax items such as loops, functions, arrays and classes.
A major difference is that Ox variables have no explicit type,
and that special support for matrices is available.
A comprehensive matrix function library is provided with Ox.
The advantages of object-oriented programming are that it
potentially improves the clarity and maintainability of the code,
as well as reducing coding effort through inheritance. Several
useful classes are provided with Ox.
An Ox program consists of one or more source code files. As a first
example consider the following small program
(the code is in \ox\samples\myfirst.ox):
#include // include the Ox standard library header
main() // function main is the starting point
{
decl m1, m2; // declare two variables, m1 and m2
m1 = unit(3); // assign to m1 a 3 x 3 identity matrix
m2 = <0,0,0;1,1,1>;//m2 is a 2 x 3 matrix, the first row
// consists of zeros, the second of ones
print("two matrices", m1, m2); // print the matrices
}
Running this first program will produce the following result:
two matrices
1.0000 0.00000 0.00000
0.00000 1.0000 0.00000
0.00000 0.00000 1.0000
0.00000 0.00000 0.00000
1.0000 1.0000 1.0000
The default extension of an Ox source code file is .ox,
so the file name of this
program could e.g. be myfirst.ox.
The next section explains how to run the Ox program on your
system. First we consider some aspects of the program.
-
The first line includes the oxstd.h file into the
source code (literally: the contents of the file are inserted at
that point). This file contains the function declarations of the standard
library, so that the function calls can be checked for number of arguments.
The file name is between < >, indicating that the header file
came with the Ox program.
-
The function main is the starting point, and each program
is only allowed one such function. Even though main
has no arguments, it still requires ().
-
Variables may be declared by using the decl statement,
and have no type until the program is actually run.
-
unit is a standard library function, which creates an identity
matrix; here it is called with argument 3.
The result is assigned to the variable m1.
The type of m1 has become matrix, and until a reassignment
is made (or it goes out of scope), m1 will keep its type and value.
-
<0,0,0;1,1,1> is a matrix constant. Elements are listed
by row, whereby rows are separated by a semicolon, and elements within
a row by a colon.
This value is stored in m2, which is now also
of type matrix.
-
print is a library function, which can print any type of
variable or constant to the standard output screen. It can take any
number of arguments. Here it has three: a string constant and
two variables (which both happen to be matrices).
One important aspect of the C, C++ and Ox languages emerges from the program:
array elements start at zero, so [0][0] is row 0, column 0, and
[1][2] is row 1, column 2. This convention is also
adopted the Ox language (but could be changed, see
under pragmas.
The advantage of Ox over C here is that we can directly work with
matrices, and do not have to worry about memory allocation and deallocation.
Running the first Ox program
Although you might not run Ox under MS-DOS, please read the next section
anyway, as much of the information in there is relevant for
other Ox versions.
MS-DOS compiler
The Ox compiler under MS-DOS is called oxl; starting it
without arguments lists the command line options.
Provided Ox has been properly installed, you can type:
oxl myfirst
to run the myfirst.ox program (the .ox extension is
automatically appended).
The output appears on screen. It can be redirected to the file
myfirst.out as follows:
oxl myfirst > myfirst.out
Dynamic link libraries (DLL) are not supported under MS-DOS.
Linux, Sun, HP compilers
The Ox compiler under Linux is called oxlinux.
The Ox compiler on the Sun (SunOS 4.1) is called oxsun, the
version for the Hewlett-Packard workstation oxhp.
Both have the same command line syntax as the MS-DOS compiler.
Unlike the MS-DOS compiler, they cannot display graphs on screen
(but graphs can be saved to a disk file and viewed
with GhostView). Dynamic link
libraries (DLL) are not yet supported
on the Sun. It is possible to statically relink Ox with
additional source code.
Windows command-line compiler
The Ox command-line compiler for Windows NT/Windows 95 is called oxlw.
It has the same command line syntax as the MS-DOS and Linux compilers.
Like the Linux compiler, it cannot display graphs on screen
(but can save graphs to disk). Dynamic link
libraries (DLL) are supported
under Windows.
Windows compiler (OxRun)
OxRun is a small Windows front end to Ox. It offers
the same services as the command-line compilers. The main dialog
items are:
- Filename:
- the Ox program to compile
- Link:
- the object files to link in e.g.
file1+file2+file3
- Include/link path:
- this field corresponds to the -I command
line switch in Oxl, e.g.: c:/ox/include
- Define:
- arguments for the -D command line switch
OxRun remembers previously run programs, and has a Browse button.
Most importantly, it activates GiveWin, and text and graphics
output from the Ox program will appear in GiveWin.
To abort a program run using OxRun, use end task on the task manager
(closing GiveWin or a GiveWin window will stop the output, but not the program).
OxRun and GiveWin are not part of the basic release of Ox.
Running programs with graphics
Several types of graphs are readily produced in Ox, such
as graphs over time of several variables, cross-plots,
histograms, correlograms, etc.
Although all graph saving will work on any system supported by Ox,
the result on screen will not always be identical.
A graph can be saved in various formats:
- encapsulated PostScript (.eps),
- PostScript (.ps), and
- GiveWin graphics file (.gwg).
When using GiveWin, graphs can also be saved in Windows Metafile
format (.wmf), and copied to the clipboard for pasting
into wordprocessors.
MS-DOS graphics
ShowDrawWindow switches the system to graphics mode
and shows the graph on screen.
Use CloseDrawWindow to switch back to text mode.
If you forget this, use the MS-DOS mode command to switch back, e.g.:
mode co80
Linux graphics
Oxlinux, etc. cannot display graphics,
but can save graphics.
Windows graphics from the command-line
Oxlw cannot display graphics, but can save graphics.
Windows graphics (OxRun and GiveWin)
Text and graphics
output from the Ox program will appear in GiveWin.
There, text and graphs can be edited further, or copied to the clipboard
for pasting into other programs.
Linking multiple files
The source code of larger projects will often be spread over several
source files. Usually the .ox file containing the main function
is only a few tens of lines. We have already seen that information
about other source files is passed on through included header files.
However, to run the entire program, the code
of those files needs to be linked
together as well. Ox offers various ways of doing this.
As an example, consider a mini-project consisting
of two files: a source code file and a header file. The third file
will contain the main function. All files in the examples
are in \ox\samples.
File 1: myfunc.ox
#include
// calls counter, initialize to 0
static decl iCalls = 0;
MyFunction(const ma)
{
++iCalls; // increment calls counter
print("MyFunction has been called ", iCalls,
" times and prints:", ma);
}
File 1: myfunc.h
MyFunction(const ma);
The header file myfunc.h declares the
MyFunction function,
so that it can be used in other Ox files.
Note that the declaration ends in a semicolon.
The source code file contains the definition of the function,
which is the actual code of the function. The header of the definition
does not end in a semicolon, but is followed by
the opening brace of the body
of the function. The iCalls variable is declared outside
any function, making it an external variable. Here we
also use the static type specifier, which restricts
the scope of the variable to the myfunc.ox file: iCalls
is invisible anywhere else (and other files may contain their
own iCalls variable). Without the static specifier,
the iCalls variable can be seen from any other source file,
provided it is declared in the header file.
Local variables, such as m1 and m2 in myfirst.ox
are called automatic. Their life starts when they are declared,
and finishes at the closing brace (matching the brace level of declaration).
Including the code into the main file
The first way of combining the mini project with the main
function is to #include the actual code. In that case
the myfunc.h header file is not needed:
File 3a: mymaina.ox
#include
#include "myfunc.ox"
main()
{
MyFunction("one");
}
The result will be just one code file, and mymaina.ox can be run,
e.g. as oxl mymaina.
Separate compilation and linkage
Ox source code files can be compiled into Ox object files. These files
have the .oxo extension, and are binary. The format is identical across
operating systems, but since they are binary, transfer from one
platform to another has to be done in binary mode.
File 3b: mymainb.ox
#include
#include "myfunc.h"
main()
{
MyFunction("one");
}
The second way of running the project is to first compile myfunc.ox
into myfunc.oxo. The next step is to compile mymainb.ox and
link the two together.
First compile myfunc.ox into an Ox object file using the -c switch:
oxl -c myfunc
This creates myfunc.oxo (the .oxo extension is automatically appended).
(Such files are binary, and cross-platform compatible.)
Remember that a new
myfunc.oxo needs to be created every time myfunc.ox changes.
Next run mymainb.ox, linking in myfunc.oxo:
oxl mymainb -lmyfunc
The -l switch specifies the files to link in; with an additional
myfunc2.oxo for example:
oxl mymainb -lmyfunc+myfunc2
Separate compilation, including link file
Finally, object files to be linked in can be included, comparable
to including source code files. For this, the link pragma
is used:
File 3c: mymainc.ox
#include
#include "myfunc.h"
#pragma link("myfunc.oxo")
main()
{
MyFunction("one");
}
When mymainc.ox is run, the Ox object file myfunc.oxo is
linked in at the specified place, and this file can be run, e.g. as
oxlw mymainc.ox.
Variables, types and scope
Variables are declared using the decl keyword. Unlike
C, variables are implicitly typed. This means that variables
do not have a type when they are declared, but get a type when
values are assigned to them. So a variable can change type during
its lifetime. The most important implicit types are int
for an integer value, double for a real number, string
for a text string and matrix for a matrix (two-dimensional array)
of real numbers.
The next Ox program illustrates implicit declaration and scope:
#include
main()
{
decl i, d, m, s;
i = 1; // assign integer to i --> i is of type int
d = 1.0; // assign real number to d --> d is double
s = "some text"; // assign string to s --> s is string
m = zeros(3,3); // assign to m a 3 x 3 matrix of zeros
// --> m is of type matrix
print("i=", i, " d=", d, " s=", s, "\nm=", m);
}
This prints (\n is the newline character):
i=1 d=1 s=some text
m=
0.00000 0.00000 0.00000
0.00000 0.00000 0.00000
0.00000 0.00000 0.00000
The scope of a variable refers to the parts of the program
which can see the variable. This could be different from its
lifetime: a variable can be `alive' but not `seen'. If a variable is
declared outside any function, its scope is the remainder of the source
file. It is possible to export such variables to other source files,
as we shall see shortly.
Variables declared inside a function have scope until the closing
brace of the level at which it is declared. The following example
illustrates:
#include
decl mX; // external variable
main()
{
decl i = 0; // local variable
{
decl i = 1, j = 0; // new i
mX = ones(3,3);
print("i=", i, " j=", j); // prints: i=1 j=0
} // brace end: local i and j cease to exist
print("\ni=", i); // revert to old i, prints: i=0
}
The variable mX (here we use Hungarian notation, see
Hungarian notation prefixes),
can be seen everywhere in the main
function. To make sure that it can never be seen in
other source files, prefix it with the word static. It is
good programming practice to use static in such cases, because
it is very useful to know that it is not used in any other files
(we may than rename it, e.g., without any unexpected side effects).
An example was given in myfunc.ox on page \pageref{myfunc.ox}.
It is also possible to share variables between various
source files, although there can be only one declaration
(physical allocation) of the
shared variable. The following modifications would do that for
the myfunc.ox
program:
(1) delete the static keyword from the declaration,
(2) add to myfunc.h the line:
extern decl iCalls;
Then any code which includes myfunc.h can reference or change
the iCalls variable.
Functions and function arguments
We have already used various functions from the standard
library (such as print, ones and zeros), and written
several new ones (MyFunction, various main functions).
Indeed, an Ox program is primarily a collection of functions.
It is important to know that all function arguments are passed by
value. This means that the function gets a copy which it can
change without changing the original. For example:
#include
func(mA)
{
mA = zeros(1,2);
print("ma in func()", mA);
}
main()
{
decl ma;
ma = ones(1,2);
print("ma before func()", ma);
func(ma);
print("ma after func()", ma);
}
which prints:
ma before func()
1.0000 1.0000
ma in func()
0.00000 0.00000
ma after func()
1.0000 1.0000
If the function argument is not changed by the function, it
is good programming style to prefix it with the const
keyword, as in:
func(const mA)
{
print("ma in func()", mA);
}
Then the compiler can generate much more efficient code,
especially for matrices and strings.
Of course it is possible to return changed values from the function.
If there is only one return value, this is most simply done by
using the return statement:
#include
func(const r, const c)
{
return rann(r, c); // return r x c matrix of random
} // numbers from standard normal
main()
{
print("return value from func():", func(1,2) );
}
Another way is to pass a pointer to the variable, rather
than the variable itself, as for example in:
#include
func(const pmA)
{
pmA[0] = zeros(1,2);
print("ma in func()", pmA[0]);
}
main()
{
decl ma;
ma = ones(1,2);
print("ma before func()", ma);
func(&ma);
print("ma after func()", ma);
}
which prints:
ma before func()
1.0000 1.0000
ma in func()
0.00000 0.00000
ma after func()
0.00000 0.00000
Now the change to ma is permanent. The argument
to the function was the address of ma, and
func received that address as a pointer. In C one could
reference what is pointed to as *pmA or pmA[0].
In Ox only the latter is possible; we modified what
that pointer referred to by assigning a value to pmA[0].
When func has finished, ma has been changed permanently.
Note that we gave the argument a const qualification. This was possible
because we did not change pmA itself, but what pmA pointed to.
The for and while loops
Since Ox is a matrix language, there is much less need for
loop statements than in C or \Cpp. Indeed, because Ox is compiled
and then interpreted, there is a speed penalty for using
loop statements when they are not necessary.
The for, while and do while loops have the same syntax as
in C. The for loop consists of three parts,
an initialization part, a termination check, and an incrementation
part. The while loops only have a termination check.
#include
main()
{
decl i, d;
for (i = 0; i < 5; ++i)
{
d = i * 0.01;
print(d, "\n");
}
}
which prints:
0
0.01
0.02
0.03
0.04
This could also be written, less elegantly, using while as follows:
#include
main()
{
decl i, d;
i = 0;
while (i < 5)
{
d = i * 0.01;
print(d, "\n");
++i;
}
}
It is not uncommon to have more than one loop counter
in the for statement, as
the following code snippet illustrates:
decl i, j;
for (i = 0, j = 10; i < 5 && j > 0; ++i, --j)
print(i * j, "\n");
The && is logical-and, whereas || is logical-or.
The ++i statement is called (prefix) incrementation, and means
`add one to i'. Similarly, --j subtracts one from j.
There is a difference between prefix and postfix incrementation
(decrementation). For example, the second line in
i = 3;
j = ++i;
means: add one to i, and assign the result
to j, which will get the value 4. But
i = 3;
j = i++;
means: leave the value of i on the stack for assignment,
then afterwards increment i. So j will get the value 3.
In the incrementation part of the for loop it does not matter
whether you use the prefix or postfix form.
The if statement
The if statement allows for conditional program flow.
In the following example we draw a uniform random number.
Such a random number is always between zero and one. The ranu
returns a matrix, unless we ask it to generate just one number.
Then it returns a double, as is the case here.
decl d = ranu(1,1);
if (d < 0.5)
print("less than 0.5\n");
else if (d < 0.75)
print("less than 0.75\n");
else
print("greater than 0.75\n");
Again, braces are used to group multiple statements together.
They should also be used when nesting if statements,
to avoid confusion about which else belongs to which
if.
decl d1 = ranu(1,1), d2 = ranu(1,1);
if (d1 < 0.5)
{ print("d1 is less than 0.5\n");
}
else
{ if (d2 < 0.75)
print("d1 >= 0.5 and d2 < 0.75\n");
else
print("d1 >= 0.5 and d2 <= 0.75\n");
}
The if part is executed if the expression evaluates to
a non-zero value (true). The else part otherwise, i.e. when
the expression evaluates to zero (false: either an integer 0, or
a double 0.0).
Some care is required when using matrices in if statements.
A matrix expression is a true statement if all elements are true
(non-zero). Even if only one element is zero, the matrix expression
is false, so
#include
main()
{
if (ones(2,2)) print("yes");
else print("no");
if (unit(2)) print("yes");
else print("no");
if (zeros(2,2)) print("yes");
else print("no");
}
prints: yesnono.
There are two forms of relational operators.
There is
< <= > >= == !=
meaning `less', `less than or equal', `greater',
`greater than or equal', `is equal' and `is not equal'. These always produce
the integer value 1 (true) or 0 (false). If any of the arguments is a matrix,
the result is only true if it is true for each element:
#include
main()
{
if (ones(2,2) == 1) print("yes"); // true for each
else print("no"); // element
if (unit(2) == 1) print("yes");//not true for each
else print("no"); // element
if (zeros(2,2) == 1) print("yes");//not true for each
else print("no"); // element
}
prints: yesnono.
The second form are the dot-relational operators
.< .<= .> .>= .== .!=
meaning `dot less', `dot less than or equal',
`dot greater',
`dot greater than or equal', `is dot equal' and `is not dot equal'.
If any of the arguments is a matrix,
the result is a matrix of zeros and ones, with each element indicating
the relevant result.
The any library function returns 1 (true) if
any element of the matrix is non-zero, so that
yesyesno will be printed by:
#include
main()
{
if (any(ones(2,2))) print("yes");
else print("no");
if (any(unit(2))) print("yes");
else print("no");
if (any(zeros(2,2))) print("yes");
else print("no");
}
To conclude: you can test whether all elements
of a matrix m are equal to one (say) by
writing: if (m == 1). To test whether any element
is equal to one: if (any(m .== 1)). The expression
if (m != 1), on the other hand, is only true if none
of the elements is equal to one. So, use if (!(m == 1))
to test whether it is true that not all elements are equal to one.
Operations and matrix programming
To a large extent, the same operators are available in Ox
as in C or C++. Some of the additional operators are
power (^), horizontal concatenation (~), vertical
concatenation (|) and the Kronecker product (**).
One important distinction is that the operators are also available
for matrices, so that, for example, two matrices can
be added up directly. For some operators, such as multiplication,
there is a distinction between the dot operators (e.g. .* is
element by element multiplication and * is matrix multiplication
if both arguments are matrices). Not available in Ox are the bitwise
operators, instead you need to use the library functions
binand and binor.
Because Ox is implicitly typed, the result type of the
expression will depend on the types of the variables
in the expression. When a mixture of types is involved, the result
is promoted upwards in the order integer, double, matrix.
So in an expression consisting if an integer and a double, the integer
will be promoted to a double. An expression of only integers yields
an integer. However, there are two important exceptions
to this rule:
- integer division is done in floating point and yields a double.
This is an important difference with C, where integer division
is truncated to an integer.
-
power expressions involving integers which yield a result too
large to be expressed as an integer give a double result.
To illustrate, we write the Fahrenheit to Celsius example of Kernighan and Ritchie (1988)
in Ox:
#include
const decl LOWER = 0;
const decl UPPER = 100;
const decl STEP = 20;
main()
{
decl fahr;
for (fahr = LOWER; fahr <= UPPER; fahr += STEP)
print("%3d", fahr, " ",
"%6.1f", (5.0/9.0) * (fahr-32), "\n");
}
which prints:
0 -17.8
20 -6.7
40 4.4
60 15.6
80 26.7
100 37.8
In C we have to write 5.0/9.0, because 5/9
evaluates to zero. In Ox both expressions would be evaluated
in floating point arithmetic.
In general we get more more efficient code
by vectorizing each program as much as possible:
#include
const decl LOWER = 0;
const decl UPPER = 100;
const decl STEP = 20;
main()
{
decl fahr;
fahr = range(LOWER, UPPER, STEP)';
print("%6.1f", fahr ~ (5.0/9.0) * (fahr-32) );
}
-
As in the first version of the program, we declare three constants
which define the Fahrenheit part of the table.
-
The range() function creates a 1 by n matrix
with the values LOWER, LOWER+STEP,
LOWER + 2STEP, ..., UPPER.
-
The transpose operator ' changes this into an n by 1
matrix.
-
The conversion to Celsius in the print statement works on the
matrix as a whole: multiplication of a matrix by a scalar is equivalent
to multiplication by the scalar of each element of the matrix.
-
The ~ operator concatenates the two column vectors into
an n by 2 matrix.
-
Finally, the print function is different from the
printf in C. In Ox each variable to print is simply specified
sequentially. It is possible, as done here with "%6.1f", to
insert formatting strings for the next variable.
The program prints a table similar to the earlier output:
0.0 -17.8
20.0 -6.7
40.0 4.4
60.0 15.6
80.0 26.7
100.0 37.8
Arrays
The Ox syntax allows for arrays, so you may use, for
example, an array of strings (often useful),
an array of matrices, or even an array of an array of matrices
(etc.). The following program gives an example.
#include
const decl MX_R = 2;
const decl MX_C = 2;
main()
{
decl i, asc, asr, m;
asr = new array[MX_R];
asc = new array[MX_C];
for (i = 0; i < MX_R; ++i)
asr[i] = sprint("col ", i);
for (i = 0; i < MX_R; ++i)
asc[i] = sprint("row ", i);
m = ranu(MX_R, MX_C);
print("%r", asr, "%c", asc, m);
}
which prints
row 0 row 1
col 0 0.020192 0.68617
col 1 0.15174 0.74598
-
The new operator declares a new object. That could be
a class object, as discussed in the next chapter, a matrix, a string,
or, as used here, an array. The argument in square brackets is the size
of the array. (When creating a matrix in this way, note that a matrix is
always two-dimensional, and needs two arguments,
as in: m = new matrix[2][2].)
-
The sprint functions return a string, which is stored in the
arrays.
-
In print(), we use "%r" followed by an array of strings
to specify row labels for the subsequent matrix. Columns labels
use "%c".
Object-oriented programming
In some of the literature of recent years it has been claimed that
object-oriented programming would solve all programming problems.
My claims here are more modest.
I see it as a useful addition, but believe thoughtless application
could actually result in less readable programs.
You may completely ignore the object-oriented features. However,
you will then not be able to use the preprogrammed classes
for data management and simulation. It is especially in the latter
task that I found a considerable reduction in the required
programming effort after writing the base class.
One of the drawbacks of C++, as compared with C, is that it is
a much larger and considerably more complicated language, with all the extra
overhead used for the object-oriented features. The standardization
of C++ is also as yet an unfinished process. Ox only implements a
subset of the C++ features. I tend to see that as a benefit
rather than a drawback.
The class is the main vehicle for object-oriented programming.
A class is nothing more than a group of variables (the data)
and functions (the actions) packaged together. This makes it a supercharged
struct (or record in Pascal terminology).
Inheritance allows for a new class
to add data and functions to the base class, or even redefine functionality
of the base class.
In Ox, all data members of the class are private (only visible to class
members), and all function members are public. Like C++, Ox
has the virtual keyword to define functions which can be
replaced by the derived class. Classes are used by dynamically
creating objects of that class. No static objects exist in Ox.
When an object is created, the constructor function is called,
when the object is deleted, the destructor function is called.
The encapsulation of data in classes removes the need for global
variables. This is an important advantage: a proliferation of global
variables makes programs very difficult to maintain. That is
also why it is important to make external variables which are
specific to one file static.
More information on object-oriented programming is
given in the classes section.
Style and Hungarian notation
The readability and maintainability of a program is considerably
enhanced when using a consistent style and notation, together with
proper indentation and documentation. Style is a personal matter; this
section describes the one I have adopted.
In my code, I always indent by four spaces at the next level of control
(i.e. after each opening brace), jumping back on the closing brace.
Table tut.1: Hungarian notation prefixes
prefix type example
i integer iX
c count of cX
f boolean (integer flag) fX
(b is also used)
d double dX
m matrix mX
v vector (1 by n matrix) vX
s string sX
a array
as array of strings asX
am array of matrix amX
p pointer (function argument) pX
m_ class member variable m_mX
g_ external variable with global scope g_mX
I have found Hungarian notation especially
useful (see e.g. Petzold, 1992, Ch. 1).
Hungarian notation involves the decoration of variable names.
There are two elements to Hungarian notation: prefixing of variable
names to indicate type
(Table tut.1), and using case to indicate scope
(Table tut.2, remember
that Ox is case sensitive).
Table tut.2: Hungarian notation, case sensitivity
function all lowercase
function (exported) first letter uppercase
static external variable type in lowercase, next letter uppercase
exported external variable as above, but prefixed with g_
function argument type in lowercase, next letter uppercase
local variables all lowercase
constants all uppercase
As an example consider:
#include
const decl MX_R = 2; /* a constant */
decl g_mX; /* exported matrix */
static decl iCount; /* static external variable */
static func1(const pdX) /* argument is pointer to double */
{
}
/* exported function */
Func2(const mX, const asX, const cT, const cX)
{
decl i, m;
}
Func2 expects a cT by cX matrix, and
corresponding array of cX variable names.
The c prefix is used for the number of elements in a matrix or string.
Note however, that it is not necessary in Ox to pass dimensions separately.
You can ask mX and asX what dimensions they have:
Func2(const mX, const asX)
{
decl i, m, ct, cx;
cx = columns(mX);
ct = rows(mX);
if (cx != sizeof(asX))
print("error: dimensions don't match");
}
Optimizing for speed
Ox is very fast: current benchmarks suggest that it is faster
than several other commonly used matrix language interpreters.
A program can never be fast enough though, and here are some
tips to achieve higher speed:
-
Use matrices as much as you can, avoiding loops and
matrix indexing.
-
Use the const argument qualifier when an argument is
not changed in a function: this allows for more efficient function
calling.
-
Use built-in functions where possible.
-
When optimizing a program with loops, it usually only pays
to optimize the inner most loop. One option is to move loop invariants
to a variable outside the loop.
-
Avoid using `hat' matrices, i.e. avoid using outer products
over large dimensions when not necessary.
-
If necessary, you can link in C or Fortran code, see
the appendix on Extending Ox in the written documentation.
-
Note that matrices are stored by row (the C and C++ default,
but transposed from the Fortran default), so it could
sometimes be faster to transpose matrices (i.e. have data variables
in rows instead of columns).
-
If necessary, you can link in C or Fortran code, see
the appendix on Extending Ox in the written documentation.
References
Kernighan, B.W. and Ritchie, D.M. (1988).
The C Programming Language 2nd Ed.
Englewood Cliffs, NJ: Prentice Hall.
Petzold, C. (1992).
Programming Windows 3.1.
Redmond: Microsoft Press.
Ox version 1.11. This file last changed 5-Aug-1996.