|
Gli operatori sono speciali metodi che permettono di eseguire, appunto, operazioni tra due valori mediante l'uso di un simbolo (ad esempio,
+ per la somma, - per la differenza, eccetera...). Quando facciamo i calcoli, comunemente usando i tipi base numerici del Framework, come
Int16 o Double, usiamo praticamente sempre degli operatori. Essi non sono nulla di "straordinario", nel senso che anche se non sembra, data
la loro particolare sintassi, sono pur sempre definiti all'interno delle varie classi come normali membri (statici). Gli operatori, come
i tipi base, del resto, non si sottraggono alla globale astrazione degli linguaggi orientati agli oggetti: tutto è sempre incasellato
al posto giusto in una qualche classe. Ma questo lo vedremo più avanti quando parlerò della Reflection.
Sorvolando su questa breve parentesi idilliaca, torniamo all'aspetto più concreto di questo capitolo. Anche il programmatore ha la
possibilità di definire nuovi operatori per i tipi che ha creato: ad esempio, può scrivere operatori che operino tra
strutture e tra classi. In genere, si preferisce adottare gli operatori nel caso delle strutture poiché, essendo tipi value, si
prestano meglio - come idea, più che altro - al fatto di subire operazioni tramite simboli. Venendo alla pratica, la sintassi
generale di un operatore è la seguente:
Shared Operator [Simbolo]([Parametri]) As [Tipo Restituito]
'...
Return [Risultato]
End Operator
Come si vede, la sintassi è molto simile a quella usata per dichiarare una funzione, ad eccezione della keyword e dell'identificatore.
Inoltre, per far sì che l'operatore sia non solo sintatticamente, ma anche semanticamente valido, devono essere soddisfatte
queste condizioni:
- L'operatore deve SEMPRE essere dichiarato come Shared, ossia statico. Infatti, l'operatore rientra nel dominio della classe in
sé e per sé, appartiene al tipo, e non ad un'istanza in particolare. Infatti, l'operatore può essere usato per
eseguire operazioni tra tutte le istanze possibili della classe. Anche se viene definito in una struttura, deve comunque essere Shared.
Infatti, sebbene il concetto di struttura si presti di meno a questa "visione" un po' assiomatica del concetto di istanza, è pur
sempre vero che possono esistere tante variabili diverse contenenti dati diversi, ma dello stesso tipo strutturato.
- L'operatore può specificare al massimo due parametri (si dice unario se ne specifica uno, e binario se due), e di questi
almeno uno DEVE essere dello stesso tipo in cui l'operatore è definito - tipicamente il primo dei due deve soddisfare
questa seconda condizione. Questo risulta abbastanza ovvio: se avessimo una struttura Frazione, come fra poco mostrerò, a cosa
servirebbe dichiararvi all'interno un operatore + definito tra due numeri interi? A parte il fatto che esiste già, è logico
aspettarsi che, dentro un nuovo tipo, si descrivano le istruzioni necessarie ad operare con quel nuovo tipo, o al massimo ad attuare
calcoli tra questo e i tipi già esistenti.
- Il simbolo che contraddistingue l'operatore deve essere scelto tra quelli disponibili, di cui qui riporto un elenco con annessa
descrizione della funzione che usualmente l'operatore ricopre:
- + (somma)
- - (differenza)
- * (prodotto)
- / (divisione)
- (divisione intera)
- ^ (potenza)
- & (concatenazione)
- = (uguaglianza)
- > (maggiore)
- < (minore)
- >= (maggiore o uguale)
- <= (minore o uguale)
- >> (shift destro dei bit)
- << (shift sinistro dei bit)
- And (intersezione logica)
- Or (unione logica)
- Not (negazione logica)
- Xor (aut logico)
- Mod (resto della divisione intera)
- Like (ricerca di un pattern: di solito il primo argomento indica dove cercare e il secondo cosa cercare)
- IsTrue (è vero)
- IsFalse (è falso)
- CType (conversione da un tipo ad un altro)
Sintatticamente parlando, nulla vieta di usare il simbolo And per fare una somma, ma sarebbe meglio attenersi alle normali norme di utilizzo
riportate.
Ed ecco un esempio:
Module Module1
Public Structure Fraction
'Numeratore e denominatore
Private _Numerator, _Denumerator As Int32
Public Property Numerator() As Int32
Get
Return _Numerator
End Get
Set(ByVal value As Int32)
_Numerator = value
End Set
End Property
Public Property Denumerator() As Int32
Get
Return _Denumerator
End Get
Set(ByVal value As Int32)
If value <> 0 Then
_Denumerator = value
Else
'Il denominatore non può mai essere 0
'Dovremmo lanciare un'eccezione, ma vedremo più
'avanti come si fa. Per ora lo impostiamo a uno
_Denumerator = 1
End If
End Set
End Property
'Costruttore con due parametri, che inizializza numeratore
'e denominatore
Sub New(ByVal N As Int32, ByVal D As Int32)
Me.Numerator = N
Me.Denumerator = D
End Sub
'Restituisce la Fraction sottoforma di stringa
Function Show() As String
Return Me.Numerator & " / " & Me.Denumerator
End Function
'Semplifica la Fraction
Sub Semplify()
Dim X As Int32
'Prende X come il valore meno alto in modulo
'e lo inserisce in X. X servirà per un
'calcolo spicciolo del massimo comune divisore
X = Math.Min(Math.Abs(Me.Numerator), Math.Abs(Me.Denumerator))
'Prima di iniziare, per evitare errori, controlla
'se numeratore e denominatore sono entrambi negativi:
'in questo caso li divide per -1
If (Me.Numerator < 0) And (Me.Denumerator < 0) Then
Me.Numerator /= -1
Me.Denumerator /= -1
End If
'E con un ciclo scova il valore più alto di X
'per cui sono divisibili sia numeratore che denominatore
'(massimo comune divisore) e li divide per quel numero.
'Continua a decrementare X finché non trova un
'valore per cui siano divisibili sia numeratore che
'denominatore: dato che era partito dall'alto, questo
'sarà indubbiamente il MCD
Do Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0))
X -= 1
Loop
'Divide numeratore e denominatore per l'MCD
Me.Numerator /= X
Me.Denumerator /= X
End Sub
'Somma due frazioni e restituisce la somma
Shared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction
Dim F3 As Fraction
'Se i denumeratori sono uguali, si limita a sommare
'i numeratori
If F1.Denumerator = F2.Denumerator Then
F3.Denumerator = F1.Denumerator
F3.Numerator = F1.Numerator + F2.Numerator
Else
'Altrimenti esegue tutta l'operazione
'x a x*b + a*y
'- + - = ---------
'y b y*b
F3.Denumerator = F1.Denumerator * F2.Denumerator
F3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator
End If
'Semplifica la Fraction
F3.Semplify()
Return F3
End Operator
'Sottrae due Fraction e restituisce la differenza
Shared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction
'Somma l'opposto del secondo membro
F2.Numerator = -F2.Numerator
Return F1 + F2
End Operator
'Moltiplica due frazioni e restituisce il prodotto
Shared Operator *(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction
'Inizializza F3 con il numeratore pari al prodotto
'dei numeratori e il denominatore pari al prodotto dei
'denominatori
Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _
F1.Denumerator * F2.Denumerator)
F3.Semplify()
Return F3
End Operator
'Divide due frazioni e restituisce il quoziente
Shared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction
'Inizializza F3 eseguendo l'operazione:
'a x a y
'- / - = - * -
'b y b x
Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _
F1.Denumerator * F2.Numerator)
F3.Semplify()
Return F3
End Operator
End Structure
Sub Main()
Dim A As New Fraction(8, 112)
Dim B As New Fraction(3, 15)
A.Semplify()
B.Semplify()
Console.WriteLine(A.Show())
Console.WriteLine(B.Show())
Dim C As Fraction = A + B
Console.WriteLine("A + B = " & C.Show())
Console.ReadKey()
End Sub
End Module
CType
CType è un particolare operatore che serve per convertire da un tipo di dato ad un altro. Non è ancora stato introdotto
nei precedenti capitoli, ma ne parlerò più ampiamente in uno dei successivi. Scrivo comunque un paragrafo a questo riguardo
per amor di completezza e utilità di consultazione.
Come è noto, CType può
eseguire conversioni da e verso tipi conosciuti: la sua sintassi, tuttavia, potrebbe sviare dalla corretta dichiarazione. Infatti, nonostante
CType accetti due parametri, la sua dichiarazione ne implica uno solo, ossia il tipo che si desidera convertire, in questo caso Fraction.
Il secondo parametro è implicitamente indicato dal tipo di ritorno: se scrivessimo "CType(ByVal F As Fraction) As Double", questa
istruzione genererebbe un CType in grado di convertire dal tipo Fraction al tipo Double nella maniera consueta in cui siamo abituati:
Dim F As Fraction
'...
Dim D As Double = CType(F, Double)
La dichiarazione di una conversione verso Double genera automaticamente anche l'operatore CDbl, che si può usare tranquillamente al posto
della versione completa di CType. Ora conviene porre l'accento sul come CType viene dichiarato: la sua sintassi non è speciale solo perchè
può essere confuso da unario a binario, ma anche perchè deve dichiarare sempre se una conversione è Widening (di
espansione, ossia senza perdita di dati) o Narrowing (di riduzione, con possibile perdita di dati). Per questo motivo si deve
specificare una delle suddette keyword tra Shared e Operator. Ad esempio: Fraction
rappresenta un numero razionale e, sebbene Double non rappresenti tutte le cifre di un possibile numero periodico, possiamo considerare
che nel passaggio verso i Double non ci sia perdita di dati nè di precisione in modo rilevante. Possiamo quindi definire la conversione
Widening:
Shared Widening Operator CType(ByVal F As Fraction) As Double
Return F.Numerator / F.Denumerator
End Operator
Invece, la conversione verso un numero intero implica non solo una perdita di precisione rilevante ma anche di dati, quindi la definiremo
Narrowing:
Shared Narrowing Operator CType(ByVal F As Fraction) As Int32
'Notare l'operatore di divisione intera (per maggiori
'informazioni sulla divisione intera, vedere capitolo A6)
Return F.Numerator F.Denumerator
End Operator
Operatori di confronto
Gli operatori di confronto godono anch'essi di una caratteristica particolare: devono sempre essere definiti in coppia, < con >,
= con <>, <= con >=. Non può infatti esistere un modo per verificare se una variabile è minore di un altra e non se è
maggiore. Se manca uno degli operatori complementari, il compilatore visualizzerà un messaggio di errore. Ovviamente, il tipo
restituito dagli operatori di confronto sarà sempre Boolean, poiché una condizione può essere solo o vera o falsa.
Shared Operator <(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
'Converte le frazioni in double e confronta questi valori
Return (CType(F1, Double) < CType(F2, Double))
End Operator
Shared Operator >(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
Return (CDbl(F1) > CDbl(F2))
End Operator
Shared Operator =(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
Return (CDbl(F1) = CDbl(F2))
End Operator
Shared Operator <>(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
'L'operatore "diverso" restituisce sempre un valore opposto
'all'operatore "uguale"
Return Not (F1 = F2)
End Operator
È da notare che le espressioni come (a=b) o (a-c>b) restituiscano un valore booleano. Possono anche essere usate nelle espressioni, ma è
sconsigliabile, in quanto il valore di True è spesse volte confuso: in VB.NET è -1, ma a runtime è 1, mentre negli altri linguaggi è
sempre 1. Queste espressioni possono tuttavia essere assegnate con sicurezza ad altri valori booleani:
'...
a = 10
b = 20
Console.WriteLine("a è maggiore di b: " & (a > b))
'A schermo compare: "a è maggiore di b: False"
|
|