A GNU Octave programozási nyelv

Objektum-orientált programozás

Az Octave lehetőséget ad osztályok definiálására, valamint operátorok és függvények túlterhelésére. Az osztályok segítségével enkapszuláció valósítható meg, kivédhető az, hogy az osztályhoz tartozó adatokat véletlenül változtassák meg.

A nyelvi eszközök leírása során példaként egy polinomot reprezentáló osztály fog szerepelni. A

a0 + a1 * x + ... + an * x^n

polinomot a

a = [a0, a1, ..., an];

vektorral fogjuk reprezentálni.

Osztályok létrehozása

Minden osztálynak külön könyvtárat kell létrehozni, melynek neve az osztály neve, előtte a @ szimbólummal. Ebbe a könyvtárba kell helyezni az osztály függvényeit. Köztük a konstruktort is, melynek segítségével létrehozhatunk egy példányt az osztályból. Például esetünkben a @polynomial/polynomial.m fájl tartalma lehetne:

function p = polynomial (a) if (nargin == 0) p.poly = [0]; p = class (p, "polynomial"); elseif (nargin == 1) if (strcmp (class (a), "polynomial")) p = a; elseif (isvector (a) && isreal (a)) p.poly = a(:).'; p = class (p, "polynomial"); else error ("polynomial: expecting real vector"); endif else print_usage (); endif endfunction

Ahogy a példa mutatja, a konstruktor visszatérési értéke a class függvény eredménye kell, hogy legyen. Ennek a függvénynek két paramétere van: az első az adattagokkal inicializált struktúra, a második pedig az osztály neve. A konstruktor hívására egy példa:

p = polynomial ([1, 0, 1]);

Az argumentumként függvényt váró beépített függvények ugyanúgy alkalmazhatóak az így létrehozott osztályokhoz tartozó függvényekre is. Például a help @polynomial/display parancs kiírja a polynomial osztály display függvényéhez tartozó dokumentációt, a type @polynomial/display parancs a függvény kódját, a dbstop @polynomial/display pedig a függvény első végrehajtható sorához beszúr egy töréspontot a debugger számára.

Annak ellenőrzése, hogy egy változó egy bizonyos osztály példánya-e, az isobject és isa függvények használhatóak:

p = polynomial ([1, 0, 1]); isobject (p) isa (p, "polynomial")

További hasznos általános, osztályokkal kapcsolatos függvények:

methods (obj)
Az obj példány függvényeinek neveit adja vissza.

methods ("classname")
A classname nevű osztály függvényeit adja vissza.

Továbbá alkalmazhatóak rá az adatstruktúrákra vonatkozó függvények is, mint például az adattagok neveit visszaadó fieldnames (s).

Osztályok módosítása

Ha szeretnénk az osztály adattagjait lekérdezhetővé és beállíthatóvá tenni, ahhoz definiálnunk kell bizonyos függvényeket. Ezek közül a legegyszerűbb a display függvény. Ezt hívja meg az Octave, amikor az osztály egy példányát ki kell írni, mert például egy pontosvesszővel nem lezárt kifejezés eredménye. Ha ez a függvény nincs definiálva, akkor ebben az esetben semmi nem kerül a képernyőre.

A polinom osztály display függvénye lehetne például a következő:

function display (p) a = p.poly; first = true; fprintf ("%s =", inputname (1)); for i = 1 : length (a); if (a(i) != 0) if (first) first = false; elseif (a(i) > 0) fprintf (" +"); endif if (a(i) < 0) fprintf (" -"); endif if (i == 1) fprintf (" %g", abs (a(i))); elseif (abs(a(i)) != 1) fprintf (" %g *", abs (a(i))); endif if (i > 1) fprintf (" X"); endif if (i > 2) fprintf (" ^ %d", i - 1); endif endif endfor if (first) fprintf (" 0"); endif fprintf ("\n"); endfunction

A függvényt érdemes a fprintf ("%s =", inputname (1)); sorral kezdeni, hogy konzisztens legyen a beépített típusokkal, melyek szintén kiírják a változó nevét az objektum tartalma előtt.

Szintén konzisztencia miatt érdemes definiálni a get és set függvényeket. A get függvény egy vagy két argumentumot vár. Egy argumentum esetén egy, az argumentumként kapott objektum adattagjait tartalmazó adatstruktúrát ad vissza, két argumentum esetén a második argumentum által megnevezett adattagját adja vissza az első argumentumként kapott objektumnak. Példa:

function s = get (p, f) if (nargin == 1) s.poly = p.poly; elseif (nargin == 2) if (ischar (f)) switch (f) case "poly" s = p.poly; otherwise error ("get: invalid property %s", f); endswitch else error ("get: expecting the property to be a string"); endif else print_usage (); endif endfunction

Hasonlóan, a set függvény a módosítandó objektumot várja, majd párosával egy beállítandó adattag nevét és az adattag új értékét. Példa:

function s = set (p, varargin) s = p; if (length (varargin) < 2 || rem (length (varargin), 2) != 0) error ("set: expecting property/value pairs"); endif while (length (varargin) > 1) prop = varargin{1}; val = varargin{2}; varargin(1:2) = []; if (ischar (prop) && strcmp (prop, "poly")) if (isvector (val) && isreal (val)) s.poly = val(:).'; else error ("set: expecting the value to be a real vector"); endif else error ("set: invalid property of polynomial class"); endif endwhile endfunction

Mivel Octave-ban nincs lehetőség referencia szerinti paraméterátadásra, így a set függvény a módosított objektumot adja vissza, és a következő módon kell meghívni:

p = set (p, "a", [1, 0, 0, 0, 1]);

Saját osztály egy példányát ugyanúgy el lehet menteni .m fájlba a save és load függvények segítségével, mint a beépített típusokat. Ha az osztálynak van saveobj(a) függvénye, akkor ez végrehajtódik mentés előtt, pl. eltávolíthatóak felesleges, származtatott vagy gyorsítótárazott értékeket tartalmazó adattagok. Ennek párja a loadobj(a), amely az fájl betöltése után hívódik meg, és itt elvégezhető pl. a mentéskor kitörölt adattagok visszahelyezése. Példák:

function b = saveobj (a) b = a; if (isempty (b.field)) b.field = initfield (b); endif endfunction function b = loadobj (a) b = a; b.addmissingfield = addfield (b); endfunction

Túlterhelés

Minden Octave függvény túlterhelhető, hogy hívásakor egy osztályspecifikus eljárás fusson le. Mivel klasszikus OO értelemben vett tagfüggvények nincsenek, így helyette ezt a túlterhelési mechanizmust használhatjuk.

Például a plot függvényt túlterhelhetjük, hogy utána a felhasználó által írt osztályok tartalmát is ki tudjuk rajzolni vele:

function h = plot (p, varargin) n = 128; rmax = max (abs (roots (p.poly))); x = [0 : (n - 1)] / (n - 1) * 2.2 * rmax - 1.1 * rmax; if (nargout > 0) h = plot (x, p(x), varargin{:}); else plot (x, p(x), varargin{:}); endif endfunction

A függvény ezután így is hívható:

p = polynomial ([1, 0, 1]); plot(p);

Külön érdekesség, hogy a típuskonverziót magvalósító függvények (pl.: double(a)) is túlterhelhetőek.

Lehetőség van az operátorok túlterhelésére is. Minden operátorhoz tartozik egy függvénynév. Ha az osztálynak van ilyen nevű függvénye, akkor az a függvény hívódik meg az operátor alkalmazásakor. Az operátorok és a hozzájuk tartozó függvények:

OperátorFüggvényLeírás
a + bplus (a, b)Összeadás
a - bminus (a, b)Kivonás
+ auplus (a)Unáris plusz
- auminus (a)Unáris mínusz
a .* btimes (a, b)Elemenkénti szorzás
a * bmtimes (a, b)Mátrixszorzás
a ./ brdivide (a, b)Elemenkénti jobbra osztás
a / bmrdivide (a, b)Jobbra mátrixosztás
a .\ bldivide (a, b)Elemenkénti valra osztás
a \ bmldivide (a, b)Balra mátrixosztás
a .^ bpower (a, b)Elemenkénti hatványozás
a ^ bmpower (a, b)Mátrixhatványozás
a < blt (a, b)Kisebb
a <= ble (a, b)Kisebb vagy egyenlő
a > bgt (a, b)Nagyobb
a >= bge (a, b)Nagyobb vagy egyenlő
a == beq (a, b)Egyenlő
a != bne (a, b)Nem egyenlő
a & band (a, b)Logikai és
a | bor (a, b)Logikai vagy
! bnot (a)Logikai nem
a’ctranspose (a)Adjungálás
a.’transpose (a)Transzponálás
a : bcolon (a, b)Két argumentumú intervallum operátor
a : b : ccolon (a, b, c)Három argumentumú intervallum operátor
[a, b]horzcat (a, b)Vízszintes konkatenáció
[a; b]vertcat (a, b)Függőleges összefűzés
a(s_1, …, s_n)subsref (a, s)Indexelés
a(s_1, …, s_n) = bsubsasgn (a, s, b)Indexelt értékadás
b (a)subsindex (a)0 kezdetű indexszé alakítás
displaydisplay (a)Parancssori megjelenítés

A polinom osztály mtimes függvénye például nézhetne ki így:

function y = mtimes (a, b) y = polynomial (conv (double (a), double (b))); endfunction

Kérdés még, hogy ha egy függvény vagy operátor argumentumai különböző osztályba tartoznak, akkor melyikhez tartozó függvény vagy operátor hívódjon meg. Ennek meghatározására a superiorto és inferiorto beépített függvények szolgálnak. Ezeket csak az osztály konstruktorában hívhatóak meg, és azt határozhatjuk meg velük, hogy ha egy függvény vagy operátor argumentumai között az adott osztály és a class osztály egy-egy példánya is szerepel, akkor az adott osztály megfelelő függvénye (superiorto ("class")) vagy a másik osztály megfelelő függvénye (inferiorto ("class")) hívódjon meg.

Például ha a polynomial osztály konstruktorába beszúrjuk a superiorto ("double"); sort, azzal jelezzük, hogy a

2 * polynomial ([1, 0, 1]);

Hívás hatására a polynomial osztály mtimes függvénye hívódjon meg.

A példa szépséghibája, hogy ez az alapértelmezett viselkedés is, mivel a felhasználó által definiált típusok alapértelmezett módon magasabb preferenciával rendelkeznek, mint a beépített típusok.

Ha az argumentumok típusai között nincs preferencia-sorrend, akkor az előrébb szereplő változó típusához tartozó függvény hívódik meg.

Öröklődés

Az Octave az osztályok öröklését is lehetővé teszi. Ehhez az osztály konstruktorában létre kell hozni az ősosztály egy példányát, majd ezt átadni harmadik paraméterként a class függvénynek. Többszörös öröklődés is támogatott, ehhez tetszőleges mennyiségű további hasonló paraméter adható meg a class függvénynek.

Ekkor az öröklő osztály extra adattagokként tárolja az ősosztályok egy-egy példányát. Az ősosztály függvényei azonban használhatóak, ha nincs felüldefiniálva, akkor az adattagon hívódik meg a hozzá tartozó függvény. Továbbá az isa függvény igazat ad vissza, ha azt kérdezzük le, hogy az öröklő osztály egy példánya az ősosztály egy példánya-e.