Domanda

Venendo da C++ a Java, l'ovvia domanda senza risposta è: perché Java non ha incluso l'overload degli operatori?

Non lo è Complex a, b, c; a = b + c; molto più semplice di Complex a, b, c; a = b.add(c);?

C'è una ragione nota per questo, argomenti validi a favore non consentendo il sovraccarico dell'operatore?Il motivo è arbitrario o è andato perduto nel tempo?

È stato utile?

Soluzione

Supponendo che tu voglia sovrascrivere il valore precedente dell'oggetto a cui fa riferimento a, allora dovrebbe essere richiamata una funzione membro.

Complex a, b, c;
// ...
a = b.add(c);

In C++, questa espressione indica al compilatore di creare tre (3) oggetti nello stack, eseguire l'addizione e copia il valore risultante dall'oggetto temporaneo nell'oggetto esistente a.

Tuttavia, in Giava, operator= non esegue la copia del valore per i tipi di riferimento e gli utenti possono solo creare nuovi tipi di riferimento, non tipi di valore.Quindi, per un tipo definito dall'utente denominato Complex, l'assegnazione significa copiare un riferimento a un valore esistente.

Considera invece:

b.set(1, 0); // initialize to real number '1'
a = b; 
b.set(2, 0);
assert( !a.equals(b) ); // this assertion will fail

In C++, copia il valore, quindi il confronto risulterà non uguale.A Giava, operator= esegue la copia di riferimento, quindi a E b ora si riferiscono allo stesso valore.Di conseguenza, il confronto produrrà "uguale", poiché l'oggetto verrà confrontato uguale a se stesso.

La differenza tra copie e riferimenti non fa altro che aumentare la confusione dell'overload degli operatori.Come menzionato da @Sebastian, Java e C# devono entrambi gestire separatamente il valore e l'uguaglianza di riferimento: operator+ probabilmente si occuperebbe di valori e oggetti, ma operator= è già implementato per gestire i riferimenti.

In C++, dovresti occuparti di un solo tipo di confronto alla volta, quindi può creare meno confusione.Ad esempio, su Complex, operator= E operator== stanno entrambi lavorando sui valori: rispettivamente copiando valori e confrontando valori.

Altri suggerimenti

Ci sono molti post che lamentano il sovraccarico dell'operatore.

Ho ritenuto doveroso chiarire il concetto di “sovraccarico operatore”, offrendo un punto di vista alternativo su questo concetto.

Offuscamento del codice?

Questo argomento è un errore.

L'offuscamento è possibile in tutte le lingue...

È facile offuscare il codice in C o Java tramite funzioni/metodi così come lo è in C++ tramite gli sovraccarichi degli operatori:

// C++
T operator + (const T & a, const T & b) // add ?
{
   T c ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

// Java
static T add (T a, T b) // add ?
{
   T c = new T() ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

/* C */
T add (T a, T b) /* add ? */
{
   T c ;
   c.value = a.value - b.value ; /* subtract !!! */
   return c ;
}

...Anche nelle interfacce standard di Java

Per un altro esempio, vediamo il Cloneable interfaccia in Giava:

Dovresti clonare l'oggetto che implementa questa interfaccia.Ma potresti mentire.E crea un oggetto diverso.In effetti, questa interfaccia è così debole che potresti restituire un altro tipo di oggetto, solo per il gusto di farlo:

class MySincereHandShake implements Cloneable
{
    public Object clone()
    {
       return new MyVengefulKickInYourHead() ;
    }
}

Come il Cloneable l'interfaccia può essere abusata/offuscata, dovrebbe essere vietata per gli stessi motivi per cui si suppone che lo sia il sovraccarico dell'operatore C++?

Potremmo sovraccaricare il toString() metodo di a MyComplexNumber class per far sì che restituisca l'ora del giorno stringata.Dovrebbe il toString() essere vietato anche il sovraccarico?Potremmo sabotare MyComplexNumber.equals per far sì che restituisca un valore casuale, modificare gli operandi...eccetera.eccetera.eccetera..

In Java, come in C++, o qualunque linguaggio, il programmatore deve rispettare un minimo di semantica quando scrive il codice.Ciò significa implementare a add funzione che aggiunge, e Cloneable metodo di implementazione che clona e a ++ operatore rispetto agli incrementi.

Cosa offusca comunque?

Ora che sappiamo che il codice può essere sabotato anche attraverso i metodi Java incontaminati, possiamo chiederci quale sia il reale utilizzo dell'overload degli operatori in C++?

Notazione chiara e naturale:metodi controsovraccarico dell'operatore?

Di seguito confronteremo, per casi diversi, lo "stesso" codice in Java e C++, per avere un'idea di quale tipo di stile di codifica sia più chiaro.

Confronti naturali:

// C++ comparison for built-ins and user-defined types
bool    isEqual          = A == B ;
bool    isNotEqual       = A != B ;
bool    isLesser         = A <  B ;
bool    isLesserOrEqual  = A <= B ;

// Java comparison for user-defined types
boolean isEqual          = A.equals(B) ;
boolean isNotEqual       = ! A.equals(B) ;
boolean isLesser         = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual  = A.comparesTo(B) <= 0 ;

Tieni presente che A e B potrebbero essere di qualsiasi tipo in C++, purché vengano forniti gli sovraccarichi dell'operatore.In Java, quando A e B non sono primitivi, il codice può diventare molto confuso, anche per oggetti di tipo primitivo (BigInteger, ecc.)...

Accessori naturali per array/contenitori e abbonamento:

// C++ container accessors, more natural
value        = myArray[25] ;         // subscript operator
value        = myVector[25] ;        // subscript operator
value        = myString[25] ;        // subscript operator
value        = myMap["25"] ;         // subscript operator
myArray[25]  = value ;               // subscript operator
myVector[25] = value ;               // subscript operator
myString[25] = value ;               // subscript operator
myMap["25"]  = value ;               // subscript operator

// Java container accessors, each one has its special notation
value        = myArray[25] ;         // subscript operator
value        = myVector.get(25) ;    // method get
value        = myString.charAt(25) ; // method charAt
value        = myMap.get("25") ;     // method get
myArray[25]  = value ;               // subscript operator
myVector.set(25, value) ;            // method set
myMap.put("25", value) ;             // method put

In Java, vediamo che affinché ogni contenitore faccia la stessa cosa (accedere al suo contenuto tramite un indice o un identificatore), abbiamo un modo diverso per farlo, il che crea confusione.

In C++ ogni contenitore utilizza lo stesso modo per accedere al proprio contenuto, grazie all'overloading degli operatori.

Manipolazione di tipi naturali avanzati

Gli esempi seguenti utilizzano a Matrix oggetto, reperito utilizzando i primi link trovati su Google per "Oggetto matrice Java" E "Oggetto matrice c++":

// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E =  A * (B / 2) ;
E += (A - B) * (C + D) ;
F =  E ;                  // deep copy of the matrix

// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ;            // deep copy of the matrix

E questo non si limita alle matrici.IL BigInteger E BigDecimal le classi di Java soffrono della stessa verbosità confusa, mentre i loro equivalenti in C++ sono chiari quanto i tipi incorporati.

Iteratori naturali:

// C++ Random Access iterators
++it ;                  // move to the next item
--it ;                  // move to the previous item
it += 5 ;               // move to the next 5th item (random access)
value = *it ;           // gets the value of the current item
*it = 3.1415 ;          // sets the value 3.1415 to the current item
(*it).foo() ;           // call method foo() of the current item

// Java ListIterator<E> "bi-directional" iterators
value = it.next() ;     // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ;        // sets the value 3.1415 to the current item

Funtori naturali:

// C++ Functors
myFunctorObject("Hello World", 42) ;

// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;

Concatenazione del testo:

// C++ stream handling (with the << operator)
                    stringStream   << "Hello " << 25 << " World" ;
                    fileStream     << "Hello " << 25 << " World" ;
                    outputStream   << "Hello " << 25 << " World" ;
                    networkStream  << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;

// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;

Ok, in Java puoi usare MyString = "Hello " + 25 + " World" ; pure...Ma aspetta un secondo:Questo è un sovraccarico dell'operatore, non è vero?Non è un tradimento???

:-D

Codice generico?

Gli stessi operandi di modifica del codice generico dovrebbero essere utilizzabili sia per built-in/primitive (che non hanno interfacce in Java), oggetti standard (che potrebbero non avere l'interfaccia giusta) e oggetti definiti dall'utente.

Ad esempio, calcolando il valore medio di due valori di tipo arbitrario:

// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
   return (p_lhs + p_rhs) / 2 ;
}

int     intValue     = getAverage(25, 42) ;
double  doubleValue  = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix  matrixValue  = getAverage(mA, mB) ; // mA, mB are Matrix

// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.

Discutere del sovraccarico dell'operatore

Ora che abbiamo visto confronti equi tra il codice C++ che utilizza l'overload degli operatori e lo stesso codice in Java, possiamo ora discutere il concetto di "overload degli operatori".

Il sovraccarico degli operatori esisteva da prima dei computer

Anche al di fuori dell’informatica, c’è un sovraccarico degli operatori:Ad esempio, in matematica, agli operatori piace +, -, *, eccetera.sono sovraccarichi.

Infatti, il significato di +, -, *, eccetera.cambia a seconda dei tipi di operandi (numerici, vettori, funzioni d'onda quantistiche, matrici, ecc.).

La maggior parte di noi, nell'ambito dei nostri corsi di scienze, ha imparato molteplici significati per gli operatori, a seconda dei tipi di operandi.Li abbiamo trovati confusi, loro?

L'overload degli operatori dipende dai suoi operandi

Questa è la parte più importante dell'overloading degli operatori:Come in matematica, o in fisica, l'operazione dipende dal tipo degli operandi.

Quindi, conosci il tipo dell'operando e conoscerai l'effetto dell'operazione.

Anche C e Java hanno un sovraccarico degli operatori (hardcoded).

In C, il comportamento reale di un operatore cambierà a seconda dei suoi operandi.Ad esempio, sommare due numeri interi è diverso rispetto a sommare due numeri doppi, o anche un numero intero e uno doppio.Esiste anche l'intero dominio aritmetico del puntatore (senza cast, puoi aggiungere a un puntatore un intero, ma non puoi aggiungere due puntatori...).

In Java non esiste l'aritmetica dei puntatori, ma qualcuno ha comunque trovato la concatenazione di stringhe senza il + operatore sarebbe abbastanza ridicolo da giustificare un'eccezione al credo "il sovraccarico dell'operatore è un male".

È solo che tu, come C (per ragioni storiche) o Java (per ragioni personali, vedere sotto), non è possibile fornirne uno proprio.

In C++, l'overloading degli operatori non è opzionale...

In C++, l'overloading degli operatori per i tipi incorporati non è possibile (e questa è una buona cosa), ma definito dall'utente i tipi possono avere definito dall'utente sovraccarichi dell'operatore.

Come già detto in precedenza, in C++, e al contrario di Java, i tipi utente non sono considerati cittadini di seconda classe del linguaggio, se paragonati ai tipi built-in.Quindi, se i tipi built-in hanno operatori, anche i tipi utente dovrebbero essere in grado di averli.

La verità è che, come il toString(), clone(), equals() i metodi sono per Java (cioè.quasi-standard), l'overload degli operatori C++ è talmente parte del C++ che diventa naturale come gli operatori C originali o i metodi Java menzionati prima.

In combinazione con la programmazione dei modelli, il sovraccarico degli operatori diventa un modello di progettazione ben noto.In effetti, non puoi andare molto lontano in STL senza utilizzare operatori sovraccarichi e operatori di sovraccarico per la tua classe.

...ma non se ne dovrebbe abusare

L'overloading degli operatori dovrebbe cercare di rispettare la semantica dell'operatore.Non sottrarre in a + operatore (come in "non sottrarre in a add function" o "restituisci schifezze in a clone metodo").

Il sovraccarico del cast può essere molto pericoloso perché può portare ad ambiguità.Quindi dovrebbero davvero essere riservati a casi ben definiti.Quanto a && E ||, non sovraccaricarli mai se non sai veramente cosa stai facendo, altrimenti perderai la valutazione di cortocircuito che gli operatori nativi && E || Godere.

COSÌ...OK...Allora perché non è possibile in Java?

Perché James Gosling lo ha detto:

Ho omesso l'overload dell'operatore come a scelta abbastanza personale perché avevo visto troppe persone abusarne in C++.

James Gosling.Fonte: http://www.gotw.ca/publications/c_family_interview.htm

Si prega di confrontare il testo di Gosling sopra con quello di Stroustrup di seguito:

Molte decisioni di progettazione C++ hanno le loro radici nella mia avversione nel forzare le persone a fare le cose in un modo particolare [...] Spesso, ero tentato di mettere fuori legge una funzionalità che personalmente non mi piaceva, mi astenevo dal farlo perché Non pensavo di avere il diritto di imporre le mie opinioni agli altri.

Bjarne Stroustrup.Fonte:La progettazione e l'evoluzione del C++ (1.3 Background generale)

Il sovraccarico degli operatori avvantaggerebbe Java?

Alcuni oggetti trarrebbero grandi vantaggi dall'overload degli operatori (tipi concreti o numerici, come BigDecimal, numeri complessi, matrici, contenitori, iteratori, comparatori, parser ecc.).

In C++ è possibile trarre vantaggio da questo vantaggio grazie all'umiltà di Stroustrup.In Java, sei semplicemente fregato a causa di Gosling scelta personale.

Potrebbe essere aggiunto a Java?

Le ragioni per non aggiungere ora l'overload degli operatori in Java potrebbero essere un mix di politiche interne, allergia verso questa funzionalità, sfiducia nei confronti degli sviluppatori (sapete, quelli sabotatori che sembrano perseguitare i team Java...), compatibilità con le precedenti JVM, tempo per scrivere una specifica corretta, ecc.

Quindi non trattenere il fiato aspettando questa funzionalità...

Ma lo fanno in C#!!!

Sì...

Anche se questa non è l'unica differenza tra le due lingue, questa non smette mai di divertirmi.

Apparentemente, le persone di C#, con il loro "ogni primitivo è a struct, e a struct deriva da Oggetto", ho capito bene al primo tentativo.

E lo fanno dentro altre lingue!!!

Nonostante tutto il FUD contro l'overload degli operatori definiti utilizzati, i seguenti linguaggi lo supportano: Scala, Dardo, Pitone, F#, C#, D, Algol68, Chiacchiere, Fantastico, Perl6,C++, Rubino, Haskell, MATLAB, Eiffel, Lua, Clojure, Fortran90, Veloce, Ada, Delfi 2005...

Così tante lingue, con così tante filosofie diverse (e talvolta opposte), eppure su questo punto sono tutte d'accordo.

Cibo per la mente...

James Gosling ha paragonato la progettazione di Java a quanto segue:

"C'è questo principio riguardo al trasloco, quando ti sposti da un appartamento all'altro.Un esperimento interessante è fare le valigie nel tuo appartamento e mettere tutto negli scatoloni, poi trasferirti nell'appartamento successivo e non disimballare nulla finché non ne hai bisogno.Quindi stai preparando il tuo primo pasto e stai tirando fuori qualcosa da una scatola.Poi, dopo circa un mese, lo usi per capire più o meno di quali cose nella tua vita hai veramente bisogno, e poi prendi il resto delle cose - dimentica quanto ti piace o quanto è bello - e lo butti semplicemente via.È incredibile come ciò semplifichi la tua vita e puoi utilizzare questo principio in tutti i tipi di problemi di progettazione:non fare le cose solo perché sono belle o solo perché sono interessanti."

Puoi leggere il contesto della citazione qui

Fondamentalmente l'overload degli operatori è ottimo per una classe che modella qualche tipo di punto, valuta o numero complesso.Ma dopo inizi a rimanere rapidamente a corto di esempi.

Un altro fattore è stato l'abuso della funzionalità in C++ da parte degli sviluppatori che sovraccaricano operatori come "&&", "||", gli operatori cast e ovviamente "new".La complessità risultante dalla combinazione di tutto ciò con il passaggio per valore e le eccezioni è ben trattata nel file C++ eccezionale libro.

Dai un'occhiata a Boost.Units: testo del collegamento

Fornisce un'analisi dimensionale senza spese generali attraverso il sovraccarico dell'operatore.Quanto più chiaro può diventare questo?

quantity<force>     F = 2.0*newton;
quantity<length>    dx = 2.0*meter;
quantity<energy>    E = F * dx;
std::cout << "Energy = " << E << endl;

in realtà produrrebbe "Energia = 4 J" che è corretto.

I progettisti Java hanno deciso che il sovraccarico degli operatori era più un problema che un vantaggio.Semplice come quella.

In un linguaggio in cui ogni variabile oggetto è in realtà un riferimento, l'overload degli operatori comporta il rischio aggiuntivo di essere abbastanza illogico, almeno per un programmatore C++.Confronta la situazione con l'overload dell'operatore == di C# e Object.Equals E Object.ReferenceEquals (o come si chiama).

Fantastico ha un sovraccarico dell'operatore e viene eseguito nella JVM.Se non ti dispiace il calo delle prestazioni (che diminuisce ogni giorno).È automatico in base ai nomi dei metodi.ad esempio, "+" chiama il metodo "più (argomento)".

Penso che questa possa essere stata una scelta progettuale consapevole per costringere gli sviluppatori a creare funzioni i cui nomi comunichino chiaramente le loro intenzioni.In C++ gli sviluppatori sovraccaricano gli operatori con funzionalità che spesso non hanno alcuna relazione con la natura comunemente accettata di un determinato operatore, rendendo quasi impossibile determinare cosa fa una parte di codice senza esaminare la definizione dell'operatore.

Beh, puoi davvero darti la zappa sui piedi con il sovraccarico dell'operatore.È come se con i puntatori si commettessero errori stupidi e quindi si è deciso di togliere le forbici.

Almeno penso che sia questo il motivo.Sono comunque dalla tua parte.:)

Dire che il sovraccarico dell'operatore porta a errori logici del tipo in cui l'operatore non corrisponde alla logica dell'operazione, è come non dire nulla.Lo stesso tipo di errore si verificherà se il nome della funzione non è appropriato per la logica operativa, quindi qual è la soluzione:eliminare la capacità di utilizzo delle funzioni!?Questa è una risposta comica: "Inappropriato per la logica operativa", ogni nome di parametro, ogni classe, funzione o qualsiasi altra cosa può essere logicamente inappropriata.Penso che questa opzione dovrebbe essere disponibile in un linguaggio di programmazione rispettabile, e quelli che pensano che non sia sicuro - ehi, no, entrambi dicono che devi usarlo.Prendiamo il C#.Hanno abbassato i puntatori ma ehi - c'è un'istruzione di "codice non sicuro" - programma come preferisci a tuo rischio e pericolo.

Alcune persone dicono che il sovraccarico degli operatori in Java porterebbe all'offuscamento.Queste persone si sono mai fermate a guardare del codice Java facendo alcuni calcoli di base come aumentare un valore finanziario di una percentuale utilizzando BigDecimal?....la verbosità di un simile esercizio diventa la sua stessa dimostrazione di offuscamento.Ironicamente, l'aggiunta dell'overloading degli operatori a Java ci consentirebbe di creare la nostra classe Valuta che renderebbe tale codice matematico elegante e semplice (meno confuso).

Tecnicamente, in ogni linguaggio di programmazione è presente un sovraccarico degli operatori che può gestire diversi tipi di numeri, ad es.numeri interi e reali.Spiegazione:Il termine sovraccarico significa che esistono semplicemente diverse implementazioni per una funzione.Nella maggior parte dei linguaggi di programmazione sono previste diverse implementazioni per l'operatore +, una per gli interi, una per i reali, questo è chiamato sovraccarico dell'operatore.

Ora, molte persone trovano strano che Java abbia l'overload degli operatori per l'operatore + per aggiungere stringhe insieme, e da un punto di vista matematico questo sarebbe davvero strano, ma visto dal punto di vista dello sviluppatore di un linguaggio di programmazione, non c'è niente di sbagliato nell'aggiungere l'overload degli operatori integrato per l'operatore + per altre classi, ad es.Corda.Tuttavia, la maggior parte delle persone concorda sul fatto che una volta aggiunto l'overloading integrato per + per String, in genere è una buona idea fornire questa funzionalità anche allo sviluppatore.

Sono completamente in disaccordo con l'errore secondo cui il sovraccarico dell'operatore offusca il codice, poiché questo è lasciato alla decisione dello sviluppatore.È ingenuo pensarlo e, ad essere onesti, sta invecchiando.

+1 per aggiungere l'overload degli operatori in Java 8.

Supponendo Java come linguaggio di implementazione, a, b e c sarebbero tutti riferimenti al tipo Complex con valori iniziali null.Supponendo anche che Complex sia immutabile come menzionato BigInteger e simili immutabili BigDecimal, penso che intendi quanto segue, poiché stai assegnando il riferimento al Complesso restituito dall'aggiunta di b e c e non confrontando questo riferimento con a.

Non è:

Complex a, b, c; a = b + c;

tanto più semplice di:

Complex a, b, c; a = b.add(c);

A volte sarebbe bello avere l'overload degli operatori, classi friends ed ereditarietà multipla.

Tuttavia continuo a pensare che sia stata una buona decisione.Se Java avesse avuto un sovraccarico degli operatori, non avremmo mai potuto essere sicuri del significato degli operatori senza esaminare il codice sorgente.Al momento non è necessario.E penso che anche il tuo esempio di utilizzo dei metodi invece del sovraccarico degli operatori sia abbastanza leggibile.Se vuoi rendere le cose più chiare puoi sempre aggiungere un commento sopra le affermazioni pelose.

// a = b + c
Complex a, b, c; a = b.add(c);

Questo non è un buon motivo per impedirlo, ma è pratico:

Le persone non sempre lo usano in modo responsabile.Guarda questo esempio dalla libreria Python scapy:

>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>

Ecco la spiegazione:

L'operatore / è stato utilizzato come operatore di composizione tra due strati.Quando lo fa, il livello inferiore può avere uno o più dei suoi campi di impostazione predefiniti sovraccarichi in base allo strato superiore.(Puoi ancora dare il valore che desideri).Una stringa può essere utilizzata come livello grezzo.

Alternative al supporto nativo dell'overloading degli operatori Java

Poiché Java non prevede l'overload degli operatori, ecco alcune alternative che puoi esaminare:

  1. Usa un'altra lingua.Entrambi Fantastico E Scala hanno l'overload degli operatori e sono basati su Java.
  2. Utilizzo java-oo, un plugin che abilita l'overload degli operatori in Java.Tieni presente che NON è indipendente dalla piattaforma.Inoltre, presenta molti problemi e non è compatibile con le ultime versioni di Java (ad es.Giava 10).(Origine StackOverflow originale)
  3. Utilizzo JNI, Java Native Interface o alternative.Ciò ti consente di scrivere metodi C o C++ (forse altri?) da utilizzare in Java.Ovviamente anche questo NON è indipendente dalla piattaforma.

Se qualcuno ne conosce altri, per favore commenta e lo aggiungerò a questo elenco.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top