Abilitare JavaScript per vedere questo sito.

Generalità

Costruttori classe

Inizializzazione membri classe

Distruttori classe

Metodi classe

Ereditarietà e polimorfismo

Membri protetti e privati classe

Sovraccarico operatori

Accessi alle proprietà

Le classi di script sono dichiarate globalmente e forniscono un modo semplice per raggruppare proprietà e metodi in unità logiche. La sintassi delle classi è simile a quella di C++ e Java.

Generalità

Con le classi lo script writer può dichiarare nuovi tipi di dati che contengono gruppi di proprietà e metodi per manipolarli.

Le classi di script sono tipi di riferimento, il che significa che è possibile mantenere più riferimenti o handle per la stessa istanza di oggetto. Le classi utilizzano una gestione automatica della memoria, per cui le istanze degli oggetti vengono distrutte solo quando l'ultimo riferimento all'istanza viene cancellato.

Una classe può implementare metodi specifici per sovraccaricare gli operatori. Questo può semplificare il modo in cui le istanze dell'oggetto vengono utilizzate nelle espressioni, in modo che non sia necessario nominare esplicitamente ogni funzione, ad esempio il metodo opAdd si traduce nell'operatore +.

Un'altra caratteristica utile è la possibilità di implementare gli accessi alle proprietà, che possono essere utilizzati sia per fornire proprietà virtuali, cioè che sembrano proprietà ma in realtà non lo sono, sia per implementare routine specifiche che devono essere eseguite ogni volta che si accede a una proprietà.

Una classe di script può anche ereditare da altre classi e implementare interfacce.

Costruttori classe

I costruttori di classe sono metodi specifici che verranno utilizzati per creare nuove istanze della classe. Non è obbligatorio per una classe dichiarare i costruttori, ma farlo può rendere più facile l'uso della classe, poiché non sarà necessario istanziare la classe e poi impostare manualmente le proprietà.

I costruttori sono dichiarati senza un tipo di ritorno e devono avere lo stesso nome della classe stessa. È possibile implementare più costruttori con diversi elenchi di parametri per diverse forme di inizializzazione.

Il costruttore di copia è un costruttore specifico che il compilatore può utilizzare per creare codice più performante quando è necessario creare copie di un oggetto. Senza il costruttore di copia, il compilatore sarà costretto a istanziare prima la copia usando il costruttore predefinito e poi a copiare gli attributi con il metodo opAssign.

Un costruttore che accetta un singolo argomento può essere usato nelle conversioni di tipo. Per impostazione predefinita, il compilatore può utilizzarli per eseguire la conversione in modo implicito, se necessario. Se non lo si desidera, è possibile vietarlo aggiungendo il decoratore esplicito dopo il costruttore.

Un costruttore non può chiamare un altro costruttore. Se si desidera condividere le implementazioni nei costruttori, si deve utilizzare un metodo specifico per questo.

Il modo in cui i membri devono essere inizializzati può anche essere definito direttamente nella dichiarazione dei membri. In questo caso, l'espressione di inizializzazione verrà automaticamente compilata nel costruttore, senza che sia necessario scrivere nuovamente l'inizializzazione.

Costruttori generati automaticamente

In alcuni casi, il compilatore genera automaticamente un costruttore predefinito e un costruttore di copia.

Il costruttore predefinito viene generato automaticamente se non viene dichiarato esplicitamente un altro costruttore. Questo costruttore richiama semplicemente il costruttore predefinito per tutti i membri dell'oggetto e imposta tutti gli handle su null, a meno che i membri non abbiano inizializzazioni esplicite, nel qual caso queste vengono eseguite. Qualsiasi errore di compilazione nell'inizializzazione dei membri sarà segnalato come di consueto.

Il costruttore di copia viene generato automaticamente se non viene dichiarato esplicitamente un costruttore di copia. Questo costruttore cercherà di eseguire un costrutto di copia per ogni membro, oppure, se non è disponibile alcun costruttore di copia, eseguirà prima un costrutto predefinito seguito da un'assegnazione. Se viene riscontrato un errore di compilazione, ad esempio se un membro non può essere copiato, il costruttore di copia non verrà generato e il messaggio di errore verrà ignorato.

Se i costruttori generati automaticamente non sono desiderati, è possibile escluderli esplicitamente segnalandoli come cancellati.

Inizializzazione membri classe

L'ordine in cui i membri della classe vengono inizializzati durante la costruzione di un oggetto diventa importante quando si utilizza l'ereditarietà o quando si definisce l'inizializzazione dei membri direttamente nella dichiarazione. Se si accede a un membro prima che sia stato inizializzato, lo script può causare un'eccezione di accesso a null handle, che interromperà l'esecuzione dello script.

Per una classe semplice, l'ordine in cui i membri vengono inizializzati è lo stesso in cui sono stati dichiarati. Se nella dichiarazione dei membri vengono fornite inizializzazioni esplicite, questi membri vengono inizializzati per ultimi.

Quando si usa l'ereditarietà, i membri della classe derivata senza inizializzazione esplicita saranno inizializzati prima di quelli della classe base, mentre i membri con inizializzazione esplicita saranno inizializzati dopo quelli della classe base.

Questo ordine di inizializzazione è stato scelto per evitare la maggior parte dei problemi legati all'accesso ai membri prima che siano stati inizializzati.

Tutti i membri sono inizializzati immediatamente all'inizio del costruttore definito, in modo che il resto del codice nel costruttore possa accedere ai membri senza problemi. Fa eccezione il caso in cui il costruttore inizializzi esplicitamente una classe base chiamando super(); in questo caso i membri con inizializzazione esplicita rimarranno non inizializzati fino a quando la classe base non sarà stata completamente costruita.

Si deve fare attenzione ai casi in cui un costruttore o l'inizializzazione di un membro chiamano i metodi della classe. Poiché i metodi della classe possono essere sovrascritti dalle classi derivate, è possibile che una classe base acceda involontariamente a un membro della classe derivata prima che sia stato inizializzato.

Distruttori classe

Normalmente non è necessario implementare il distruttore di classe, poiché lo script avanzato libererà per default tutte le risorse che l'oggetto detiene quando viene distrutto. Tuttavia, ci possono essere situazioni in cui è necessario eseguire una routine di pulizia più esplicita come parte della distruzione dell'oggetto.

Il distruttore viene dichiarato in modo simile al costruttore, tranne per il fatto che deve essere preceduto dal simbolo ~ (noto anche come operatore bitwise not).

Si noti che lo script avanzato utilizza la gestione automatica della memoria con la garbage collection, quindi potrebbe non essere sempre facile prevedere quando il distruttore viene eseguito. Lo script avanzato chiamerà inoltre il distruttore una sola volta, anche se l'oggetto viene resuscitato aggiungendo un riferimento ad esso durante l'esecuzione del distruttore.

Non è possibile invocare direttamente il distruttore. Se si desidera invocare direttamente la pulizia, è necessario implementare un metodo pubblico per questo.

Metodi classe

I metodi di classe sono implementati allo stesso modo delle funzioni globali, con l'aggiunta che il metodo di classe può accedere alle proprietà dell'istanza di classe direttamente o tramite la parola chiave "this", nel caso in cui una variabile locale abbia lo stesso nome.

Metodi const

Le classi aggiungono un nuovo tipo di sovraccarico di funzione, ovvero il sovraccarico const. Quando si accede a un metodo di una classe tramite un riferimento o un handle di sola lettura, è possibile invocare solo i metodi contrassegnati come costanti. Quando il riferimento o l'handle è scrivibile, possono essere invocati entrambi i tipi, con la preferenza per la versione non const nel caso in cui entrambi corrispondano.

Ereditarietà e polimorfismo

Lo script avanzato supporta l'ereditarietà singola, in cui una classe derivata eredita le proprietà e i metodi della sua classe base. L'ereditarietà multipla non è supportata, ma il polimorfismo è supportato dall'implementazione di interfacce e il riutilizzo del codice è garantito dall'inclusione di classi mixin.

Tutti i metodi della classe sono virtuali, quindi non è necessario specificarlo manualmente. Quando una classe derivata sovrascrive un'implementazione, può estendere l'implementazione originale chiamando specificamente il metodo della classe base utilizzando l'operatore di risoluzione dell'ambito. Quando si implementa il costruttore di una classe derivata, il costruttore della classe base viene richiamato utilizzando la parola chiave super. Se nessuno dei costruttori della classe base viene chiamato manualmente, il compilatore inserirà automaticamente una chiamata al costruttore predefinito all'inizio. Il distruttore della classe base sarà sempre chiamato dopo quello della classe derivata, quindi non è necessario farlo manualmente.

Una classe derivata da un'altra può essere implicitamente lanciata alla classe base. Lo stesso vale per le interfacce implementate da una classe. L'altra direzione richiede un cast esplicito, poiché non è noto in fase di compilazione se il cast è valido.

Controllo extra con final, abstract e override

Una classe può essere contrassegnata come "finale" per impedirne l'ereditarietà. Si tratta di una funzione opzionale, utilizzata soprattutto nei progetti più grandi, dove ci sono molte classi e può essere difficile controllare manualmente l'uso corretto di tutte le classi. È anche possibile contrassegnare come "finali" singoli metodi di una classe, nel qual caso è ancora possibile ereditare dalla classe, ma il metodo finale non può essere sovrascritto.

Un'altra parola chiave che può essere usata per contrassegnare una classe è "abstract". Le classi astratte non possono essere istanziate, ma possono essere derivate. Le classi astratte sono usate più frequentemente quando si vuole creare una famiglia di classi derivando da una classe base comune, ma non si vuole che la classe base sia istanziata da sola. Attualmente non è possibile contrassegnare i metodi come astratti, quindi tutti i metodi devono avere un'implementazione anche per le classi astratte.

Quando si deriva una classe, è possibile indicare al compilatore che un metodo è destinato a sovrascrivere un metodo della classe base ereditata. Quando questo viene fatto e non c'è un metodo corrispondente nella classe base, il compilatore emette un errore, perché sa che qualcosa non è stato implementato nel modo previsto. Questo è particolarmente utile per individuare errori in progetti di grandi dimensioni, dove una classe base può essere modificata, ma le classi derivate sono state dimenticate.

Membri protetti e privati classe

I membri della classe possono essere dichiarati come protetti o privati, per controllare da dove si può accedere ad essi. I membri protetti non possono essere accessibili dall'esterno della classe. I membri privati, inoltre, non sono accessibili alle classi derivate.

Questo può essere utile nei programmi di grandi dimensioni, quando si vogliono evitare errori di programmazione dovuti all'uso inappropriato di proprietà o metodi.

Sovraccarico operatori

È possibile definire cosa deve essere fatto quando un operatore viene utilizzato con una classe di script. Sebbene non sia necessario nella maggior parte degli script, può essere utile per migliorare la leggibilità del codice.

Questo si chiama sovraccarico dell'operatore e si ottiene implementando metodi specifici della classe. Il compilatore riconoscerà e utilizzerà questi

metodi quando compila espressioni che coinvolgono gli operatori sovraccaricati e la classe di script.

Operatori unari prefissi

op

opfunc

-

opNeg

~

opCom

++

opPreInc

--

opPreDec

Quando l'espressione op a viene compilata, il compilatore la riscrive come a.opfunc() e la compila al suo posto.

Operatori unari postfissi

op

opfunc

++

opPostInc

--

opPostDec

Quando l'espressione a op viene compilata, il compilatore la riscrive come a.opfunc() e la compila al suo posto.

Operatori di confronto

op

opfunc

==

opEquals

!=

opEquals

<

opCmp

<=

opCmp

>

opCmp

>=

opCmp

is

opEquals

!is

opEquals

L'espressione a == b viene riscritta come a.opEquals(b) e b.opEquals(a) e viene utilizzata la corrispondenza migliore. Il metodo != viene trattato in modo simile, tranne che per il fatto che il risultato viene negato. Il metodo opEquals deve essere implementato in modo da restituire un bool per essere considerato dal compilatore.

Gli operatori di confronto vengono riscritti come a.opCmp(b) op 0 e 0 op b.opCmp(a) e viene utilizzata la migliore corrispondenza. Il metodo opCmp deve essere implementato per restituire un int per essere considerato dal compilatore. Se l'argomento del metodo deve essere considerato più grande dell'oggetto, il metodo deve restituire un valore negativo. Se si suppone che siano uguali, il valore di ritorno deve essere 0.

Se viene effettuato un controllo di uguaglianza e il metodo opEquals non è disponibile, il compilatore cerca invece il metodo opCmp. Quindi, se il metodo opCmp è disponibile, non è necessario implementare il metodo opEquals, se non per motivi di ottimizzazione.

L'operatore di identità, is, si aspetta che opEquals prenda un handle, @, in modo che gli indirizzi possano essere confrontati per poter restituire se si tratta dello stesso oggetto, al contrario di due oggetti diversi che hanno lo stesso valore.

Operatori di assegnazione

op

opfunc

=

opAssign

+=

opAddAssign

-=

opSubAssign

*=

opMulAssign

/=

opDivAssign

%=

opModAssign

**=

opPowAssign

&=

opAndAssign

|=

opOrAssign

^=

opXorAssign

<<=

opShlAssign

>>=

opShrAssign

>>>=

opUShrAssign

Le espressioni di assegnazione a op b vengono riscritte come a.opfunc(b) e poi viene utilizzato il metodo di corrispondenza migliore. Un operatore di assegnazione può essere implementato, ad esempio, in questo modo:

Operatore di assegnazione generato automaticamente

Il compilatore genera automaticamente un opAssign per copiare il contenuto di un'istanza dello stesso tipo, nel caso in cui non sia dichiarato esplicitamente un metodo opAssign con un singolo parametro. L'opAssign generato copierà semplicemente ogni membro.

Se l'opAssign generato automaticamente non è desiderato, è possibile escluderlo esplicitamente segnalandolo come cancellato.

Operatori binari

op

opfunc

opfunc_r

+

opAdd

opAdd_r

-

opSub

opSub_r

*

opMul

opMul_r

/

opDiv

opDiv_r

%

opMod

opMod_r

**

opPow

opPow_r

&

opAnd

opAnd_r

|

opOr

opOr_r

^

opXor

opXor_r

<<

opShl

opShl_r

>>

opShr

opShr_r

>>>

opUShr

opUShr_r

Le espressioni con operatori binari a op b verranno riscritte come a.opfunc(b) e b.opfunc_r(a) e verrà utilizzata la migliore corrispondenza.

Operatori indice

op

opfunc

[]

opIndex

Quando l'espressione a[i] viene compilata, il compilatore la riscrive come a.opIndex(i) e la compila al suo posto. Sono supportati anche argomenti multipli tra le parentesi.

L'operatore indice può essere formato in modo simile agli accessi alle proprietà. L'accessore get dovrebbe essere chiamato get_opIndex e avere un parametro per l'indicizzazione. L'accessore set dovrebbe chiamarsi set_opIndex e avere due parametri, il primo per l'indicizzazione e il secondo per il nuovo valore.

Quando l'espressione a[i] viene utilizzata per recuperare il valore, il compilatore la riscrive come a.get_opIndex(i). Quando l'espressione viene usata per impostare il valore, il compilatore la riscriverà come a.set_opIndex(i, expr).

Operatore functor

op

opfunc

()

opCall

Quando viene compilata l'espressione expr(arglist) e expr valuta un oggetto, il compilatore la riscrive come expr.opCall(arglist) e la compila al suo posto.

Operatori di conversione di tipo

op

opfunc

type(expr)

constructor, opConv, opImplConv

cast<type>(expr)

opCast, opImplCast

Quando viene compilata l'espressione type(expr) e type non ha un costruttore di conversione che accetta un argomento con il tipo dell'espressione, il compilatore cercherà di riscriverla come expr.opConv(). Il compilatore sceglierà quindi l'opConv che restituisce il tipo desiderato.

Per le conversioni implicite, il compilatore cercherà un costruttore di conversione del tipo di destinazione che accetti un argomento corrispondente e non sia contrassegnato come esplicito. Se non ne trova uno, proverà a chiamare l'opImplConv sul tipo sorgente che restituisce il tipo di destinazione.

Questo dovrebbe essere usato solo per le conversioni di valori e non per i cast di riferimenti. Cioè, ci si aspetta che i metodi restituiscano una nuova istanza del valore con il nuovo tipo.

Nota: durante la compilazione delle espressioni booleane nelle condizioni, il compilatore non utilizzerà il bool opImplConv sui tipi di riferimento, anche se il metodo della classe è implementato. Questo perché è ambiguo se sia l'handle a essere verificato o l'oggetto vero e proprio.

Se si desidera un cast di riferimento, cioè un handle di tipo diverso per la stessa istanza di oggetto, è necessario implementare il metodo opCast. Il compilatore cercherà di riscrivere un'espressione cast<tipo>(expr) come expr.opCast() e sceglierà l'overload opCast che restituisce un handle del tipo desiderato. Anche in questo caso si può implementare l'opImplCast, se il cast di riferimento può essere eseguito implicitamente dal compilatore.

Un esempio in cui gli overload dell'operatore opCast/opImplCast sono utili è quando si estende un tipo senza ereditarlo direttamente.

Accessi alle proprietà

Nota: l'applicazione può disattivare facoltativamente il supporto per gli accessi alle proprietà, quindi è necessario verificare il manuale dell'applicazione per determinare se questo è supportato o meno.

Spesso, quando si lavora con le proprietà, è necessario assicurarsi che venga seguita una logica specifica quando si accede ad esse. Un esempio potrebbe essere quello di inviare sempre una notifica quando una proprietà viene modificata, oppure di calcolare il valore della proprietà a partire da altre proprietà. Implementando i metodi di accesso alle proprietà, questo può essere implementato dalla classe stessa, rendendo più semplice l'accesso alle proprietà.

In script avanzato gli accessi alle proprietà sono dichiarati con la seguente sintassi:

Dietro le quinte, il compilatore lo trasforma in due metodi con il nome della proprietà e i prefissi get_ e set_, e con il decoratore di funzioni 'property'. Il seguente genera il codice equivalente ed è perfettamente valido:

Se si implementano gli accessi alle proprietà scrivendo esplicitamente i due metodi, è necessario assicurarsi che il tipo di ritorno dell'accesso get e il tipo di parametro dell'accesso set corrispondano, altrimenti il compilatore non saprà quale sia il tipo corretto da utilizzare.

Per le interfacce, la prima alternativa è di solito il modo preferito di dichiarare gli accessi alle proprietà, in quanto è abbastanza breve e facile da leggere.

Si può anche omettere l'accessor get o set. Se si tralascia l'accessore set, la proprietà sarà di sola lettura. Se si tralascia l'accessore get, la proprietà sarà di sola scrittura.

Gli accessi alle proprietà possono essere implementati anche per le proprietà globali, che seguono le stesse regole, tranne per il fatto che le funzioni sono globali.

Una volta dichiarati gli accessi alle proprietà, è possibile accedervi come alle proprietà ordinarie e il compilatore espanderà automaticamente le espressioni alle chiamate di funzione appropriate, set_ o get_, a seconda di come la proprietà viene utilizzata nell'espressione.

Si noti che, poiché gli accessi alle proprietà sono in realtà una coppia di metodi piuttosto che un accesso diretto al valore, si applicano alcune restrizioni su come possono essere utilizzati nelle espressioni che ispezionano e mutano nella stessa operazione. Gli assegnamenti composti possono essere utilizzati sugli accessi alle proprietà se l'oggetto proprietario è di tipo reference, ma non se l'oggetto proprietario è di tipo value. Questo perché il compilatore deve essere in grado di garantire che l'oggetto rimanga in vita tra le due chiamate all'accessor get e all'accessor set.

Gli operatori di incremento e decremento non sono attualmente supportati.

In questi casi, l'espressione deve essere espansa in modo che le operazioni di lettura e scrittura vengano eseguite separatamente, ad esempio l'operatore increment deve essere riscritto come segue:

Accessi a proprietà indicizzati

Gli accessi a proprietà possono essere utilizzati per emulare una singola proprietà o un array di proprietà a cui si accede tramite l'operatore indice. Gli accessi a proprietà per l'accesso indicizzato funzionano come gli accessi a proprietà ordinari, tranne che per il fatto che accettano un argomento indice. L'accessore get deve prendere l'argomento indice come unico argomento, mentre l'accessore set deve prendere l'argomento indice come primo argomento e il nuovo valore come secondo argomento.

Le assegnazioni composte attualmente non funzionano per le proprietà indicizzate.

 

  

Keyboard Navigation

F7 for caret browsing
Hold ALT and press letter

This Info: ALT+q
Page Header: ALT+h
Topic Header: ALT+t
Topic Body: ALT+b
Contents: ALT+c
Search: ALT+s
Exit Menu/Up: ESC