ChucK : Language Specification > Classes & Objects
| |
| |
Classes & ObjectsGeneral Overview of Topic
View sample code for classes & objects and the ChucK API Reference
introductionChuck implements an object system that borrows from both C++ and Java conventions. In our case this means:
For the sake of clarity we will define these terms:
learning about objects with .help()As of chuck-1.4.1.0, all objects (built-in and user-defined) can invoke the .help() function, which dynamically generates information regarding the underlying type of an object or a class—and outputs this information to the console. The information includes the inheritance chain of the type in question, its methods and data as well as those inherited from its parent (and its parent's parent, and so on). The .help() feature is designed to be a run-time teaching/learning tool and a quick reference while programming. It's fun, sort of! // call help() on it to learn about a class and its API // (the output is printed to the chuck console) SinOsc.help();Check out the help.ck example. built-in classesChucK has many classes defined within the language.
The above are some commonly used base classes in ChucK. working with objectsLet's begin with some examples. For these, let's assume Foo is a defined class. // create a Foo object; stored in reference variable bar Foo bar; The above code does two things:
Note that in contrast to Java, this statement both declares a reference variable and instantiates a instance of that class and assigns the reference to the variable. Also note that in contrast to C++, bar is a reference, and does not represent the object itself. To declare a reference variable that refers to nothing (also called a null reference): // create a null reference to a Foo object Foo @ bar; The above code only declare a reference and initializes it to null. (random note: the above statement may be read as "Foo at bar") We can assign a new instance to the reference variable: // assign new instance of Foo to bar new Foo @=> Foo @ bar; // (this statement is equivalent to 'Foo bar', above) The code above is exactly equivalent to Foo bar; as shown above. The new operator creates an instance of a class, in this case Foo. The @=> operator performs the reference assignment. (see here for more information on @=>) It is possible to make many references to same object: // make a Foo Foo bar; // reference assign to duh bar @=> Foo @ duh; // (now both bar and duh points to the same object) ChucK objects are reference counted and garbage collection takes place automatically. As stated above, a classes may contain data and behavior, in the form of member variables and member functions, respectively. Members are accessed by using 'dot notation' - reference.memberdata and reference.memberfunc(). To invoke a member function of an object (assuming class Foo has a member function called compute that takes two integers and returns an integer): // make a Foo Foo bar; // call compute(), store result in boo bar.compute( 1, 2 ) => int boo; writing a custom classChucK is an object-oriented language and it is possible to define new types and their behavior by authoring a new class. If a class has been defined (either in the same file or as a public class in a different file) then it can be instantiated similar to any other type. Unless declared public, class definitions are scoped to the shred and will not conflict with identically named classes in other running shreds. Classes encapsulate a set of behaviors and data. To define a new object type, the keyword class is used followed by the name of that class. // define class X class X { // insert code here } If a class is defined as public, it is integrated into the central namespace (instead of the local one), and can be instantiated from other programs that are subsequently compiled. There can be at most one public class per file. // define public class MissPopular public class MissPopular { // ... } // define non-public class Flarg class Flarg { // ... } // Both MissPopular and Flarg can be used in this file. // Only MissPopular can be used from another file. Poor Flarg. We define member data and methods to specify the data types and functionality required of the class. Members, or instance data and instance functions are associated with individual instances of a class, whereas static data and functions are only associated with the class (and shared by the instances). members (instance data + functions)Instance data and methods are associated with an object. // define class X class X { // declare instance variable 'm_foo' int m_foo; // another instance variable 'm_bar' float m_bar; // yet another, this time an object Event m_event; // function that returns value of m_foo fun int getFoo() { return m_foo; } // function to set the value of m_foo fun void setFoo( int value ) { value => m_foo; } // calculate something fun float calculate( float x, float y ) { // insert code } // print some stuff fun void print() { <<< m_foo, m_bar, m_event >>>; } } // instantiate an X X x; // set the Foo x.setFoo( 5 ); // print the Foo <<< x.getFoo() >>>; // call print x.print(); class constructors As of version 1.5.2.0, ChucK now supports constructors and destructors. Constructors can be invoked when declaring a variable or with `new`: // connecting UGens, with construtors SinOsc foo( 440 ) => Gain g( .5 ) => dac; // `new` and assignment new TriOsc( 440, 0.5 ) @=> Osc @ oscillator; // can combine constructors with arrays string arr( "foo" )[10]; Constructors can also be defined and overloaded in class definitions: class Foo { // a member variable 1 => int num; // constructor "default" fun Foo() { 2 => num; } // another constructor fun Foo( int x ) { x => num; } // yet another constructor fun Foo( int x, int y ) { x*y => num; } // alternate way of defining a constructor, using the @construct keyword fun @construct( int x, int y, int z ) { x*y*z => num; } // destructor fun @destruct() { <<< "bye:", this.num >>>; } } // constructor "default" Foo f1(); // another constructor Foo f2( 15 ); // yet another constructor new Foo( 8, 9 ) @=> Foo @ f3; // yet another constructor new Foo( 10, 11, 12 ) @=> Foo @ f4; // print <<< f1.num, f2.num, f3.num, f4.num >>>; static (data + functions)Static data and functions are associated with a class, and are shared by all instances of that class -- in fact,static elements can be accessed without an instance, by using the name of the class: Classname.element. // define class X class X { // static data static int our_data; // static function fun static int doThatThing() { // return the data return our_data; } } // do not need an instance to access our_data 2 => X.our_data; // print out <<< X.our_data >>>; // print <<< X.doThatThing() >>>; // create instances of X X x1; X x2; // print out their static data - should be same <<< x1.our_data, x2.our_data >>>; // change use one 5 => x1.our_data; // the other should be changed as well <<< x1.our_data, x2.our_data >>>; Update (chuck-1.5.4.3 or higher): full static variable instantiation is now supported for all primitive and Object types. See examples/class/static-init.ck. // a class public class Foo { // int 1 => static int S_INT; // float 2 => static float S_FLOAT; // dur 3::second => static dur S_DUR; // time now + 4::second => static time S_TIME; // vec3 @(5,6,7) => static vec3 S_VEC3; // array [8,9,10,11] @=> static int S_INT_ARRAY[]; // string static string S_STRING("12"); // ugen static SinOsc S_SINOSC(440); } // access and print static variables <<< Foo.S_INT >>>; <<< Foo.S_FLOAT >>>; <<< Foo.S_DUR / second >>>; <<< (Foo.S_TIME-now) / second >>>; <<< Foo.S_VEC3 >>>; for( auto i : Foo.S_INT_ARRAY ) <<< i >>>; <<< Foo.S_STRING >>>; <<< Foo.S_SINOSC.freq() >>>; inheritanceInheritance in object-oriented code allows the programmer to take an existing class and extend or alter its functionality. In doing so we can create a taxonomy of classes that all share a specific set of behaviors, while implementing those behaviors in different, yet well-defined, ways. We indicate that a new class inherits from another class using the extends keyword. The class from which we inherit is referred to as the parent class, and the inheriting class is the child class. The Child class receives all of the member data and functions from the parent class, although functions from the parent class may be overridden ( below ). Because the children contain the functionality of the parent class, references to instances of a child class may be assigned to a parent class reference type. For now, access modifiers (public, protected, private) are included but not fully implemented. Everything is public by default. // define class X class X { // define member function fun void doThatThing() { <<<"Hallo">>>; } // define another fun void hey() { <<<"Hey!!!">>>; } // data int the_data; } // define child class Y class Y extends X { // override doThatThing() fun void doThatThing() { <<<"No! Get away from me!">>>; } } // instantiate a Y Y y; // call doThatThing y.doThatThing(); // call hey() - should use X's hey(), since we didn't override y.hey(); // data is also inherited from X <<< y.the_data >>>; Inheritance provides us a way of efficiently sharing code between classes which perform similar roles. We can define a particular complex pattern of behavior, while changing the way that certain aspects of the behavior operate. // parent class defines some basic data and methods class Xfunc { int x; fun int doSomething( int a, int b ) { return 0; } } // child class, which overrides the doSomething function with an addition operation class Xadds extends Xfunc { fun int doSomething ( int a, int b ) { return a + b ; } } // child class, which overrides the doSomething function with a multiply operation class Xmuls extends Xfunc { fun int doSomething ( int a, int b ) { return a * b; } } // array of references to Xfunc Xfunc @ operators[2]; // instantiate two children and assign reference to the array new Xadds @=> operators[0]; new Xmuls @=> operators[1]; // loop over the Xfunc for( 0 => int i; i < operators.cap(); i++ ) { // doSomething, potentially different for each Xfunc <<< operators[i].doSomething( 4, 5 ) >>>; } because Xmuls and Xadds each redefine doSomething( int a, int b ) with their own code, we say that they have overridden the behavior of the parent class. They observe the same interface, but have potentially different implementation. This is known as polymorphism. | |
| |