[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
5.8 Structures
Users may also define their own data types as structures, along with
user-defined operators, much as in C++. By default, structure members
are public
(may be read and modified anywhere in the code), but may be
optionally declared restricted
(readable anywhere but writeable
only inside the structure where they are defined) or private
(readable and writable only inside the structure). In a structure definition,
the keyword this
can be used as an expression to refer to the enclosing
structure. Any code at the
top-level scope within the structure is executed on initialization.
Variables hold references to structures. That is, in the example:
struct T { int x; } T foo=new T; T bar=foo; bar.x=5;
The variable foo
holds a reference to an instance of the structure
T
. When bar
is assigned the value of foo
, it too
now holds a reference to the same instance as foo
does. The assignment
bar.x=5
changes the value of the field x
in that instance, so
that foo.x
will also be equal to 5
.
The expression new T
creates a new instance of the structure T
and
returns a reference to that instance. In creating the new instance, any code in
the body of the record definition is executed. For example:
int Tcount=0; struct T { int x; ++Tcount; } T foo=new T;
Here, the expression new T
will produce a new instance of the class, but
will also cause Tcount
to be incremented, so that it keeps track of the
number of instances produced.
The expression null
can be cast to any structure type to yield a null
reference, a reference that does not actually refer to any instance of the
structure. Trying to use a field of a null reference will cause an error.
The function bool alias(T,T)
checks to see if two structure references
refer to the same instance of the structure (or both to null
). For
example, in the example code at the start of the section, alias(foo,bar)
would return true, but alias(foo,new T)
would return false, as new
T
creates a new instance of the structure T
. The boolean operators
==
and !=
are by default equivalent to alias
and
!alias
respectively, but may be overwritten for a particular type
(for example, to do a deep comparison).
After the definition of a structure T
, a variable of type T
is
initialized to a new instance (new T
) by default. During the definition
of the structure, however, variables of type T
are initialized to
null
by default. This special behaviour is to avoid infinite
recursion of creating new instances in code such as
struct tree { int value; tree left; tree right; }
Here is a simple example that illustrates the use of structures:
struct S { real a=1; real f(real a) {return a+this.a;} } S s; // Initializes s with new S; write(s.f(2)); // Outputs 3 S operator + (S s1, S s2) { S result; result.a=s1.a+s2.a; return result; } write((s+s).f(0)); // Outputs 2
It is often convenient to have functions that construct new instances of a
structure. Say we have a Person
structure:
struct Person { string firstname; string lastname; } Person joe=new Person; joe.firstname="Joe"; joe.lastname="Jones";
Creating a new Person is a chore; it takes three lines to create a new instance and to initialize its fields (that's still considerably less effort than creating a new person in real life, though).
We can reduce the work by defining a constructor function
Person(string,string)
:
struct Person { string firstname; string lastname; static Person Person(string firstname, string lastname) { Person p=new Person; p.firstname=firstname; p.lastname=lastname; return p; } } Person joe=Person.Person("Joe", "Jones");
While it is now easier than before to create a new instance, we still
have to refer to the constructor by the qualified name
Person.Person
. If we add the line
from Person unravel Person;
immediately after the structure definition, then the constructor can be used
without qualification: Person joe=Person("Joe", "Jones");
.
The constructor is now easy to use, but it is quite a hassle to define. If you write a lot of constructors, you will find that you are repeating a lot of code in each of them. Fortunately, your friendly neighbourhood Asymptote developers have devised a way to automate much of the process.
If, in the body of a structure, Asymptote encounters the definition of
a function of the form void operator init(args)
, it implicitly
defines a constructor function of the arguments args
that
uses the void operator init
function to initialize a
new instance of the structure.
That is, it essentially defines the following constructor (assuming the
structure is called Foo
):
static Foo Foo(args) { Foo instance=new Foo; instance.operator init(args); return instance; } |
This constructor is also implicitly copied to the enclosing scope after the end
of the structure definition, so that it can used subsequently without qualifying
it by the structure name. Our Person
example can thus be implemented as:
struct Person { string firstname; string lastname; void operator init(string firstname, string lastname) { this.firstname=firstname; this.lastname=lastname; } } Person joe=Person("Joe", "Jones");
The use of operator init
to implicitly define constructors should not be
confused with its use to define default values for variables
(see section Variable initializers). Indeed, in the
first case, the return type of the operator init
must be void
while in the second, it must be the (non-void
) type of the variable.
The function cputime()
returns a structure cputime
with cumulative CPU times
broken down into the fields parent.user
, parent.system
,
child.user
, and child.system
. For convenience, the
incremental fields change.user
and change.system
indicate
the change in the corresponding total parent and child CPU
times since the last call to cputime()
. The function
void write(file file=stdout, string s="", cputime c, string format=cputimeformat, suffix suffix=none);
displays the incremental user cputime followed by “u”, the incremental system cputime followed by “s”, the total user cputime followed by “U”, and the total system cputime followed by “S”.
Much like in C++, casting (see section Casts) provides for an elegant implementation of structure inheritance, including virtual functions:
struct parent { real x=1; void virtual(int) {write (0);} void f() {virtual(1);} } void write(parent p) {write(p.x);} struct child { parent parent; real y=2; void virtual(int x) {write (x);} parent.virtual=virtual; void f()=parent.f; } parent operator cast(child child) {return child.parent;} parent p; child c; write(c); // Outputs 1; p.f(); // Outputs 0; c.f(); // Outputs 1; write(c.parent.x); // Outputs 1; write(c.y); // Outputs 2;
For further examples of structures, see Legend
and picture
in
the Asymptote
base module plain
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |