Super Squirrel ha scritto:E' imprescindibile e adesso ti spiego il perchè. Immagina di avere due variabili x=0 e y=1, e di volerne scambiare il contenuto. Nel C++ non esiste un comando base per scambiare il contenuto di due variabili, bisogna quindi procedere per assegnazioni successive. Ora con le sole variabili x e y le uniche assegnazioni possibili sono x=y (quindi x=y=1) e y=x (quindi x=y=0), ma in entrambi i casi perderemo uno dei valori contenuti nelle variabili. Risulta quindi indispensabile l'utilizzo di una terza variabile d'appoggio che conservi temporaneamente il valore contenuto in una delle due variabili e che eviti quindi la perdita di dati.
Immagina per esempio avere un bicchiere d'acqua e uno di aranciata e di volerne scambiare il contenuto. Risulta evidente in questo caso che l'utilizzo di un terzo contenitore diventa indispensabile.
Su due variabili è possibile fare molte più operazioni che assegnare loro un valore. Prendiamo per esempio due valori interi (con infinita precisione per il momento). Se facciamo le seguenti operazioni:
- Codice:
x <- x + y
y <- x - y
x <- x - y
Avremo che alla fine le due variabili sono state scambiate. Se chiamiamo infatti ogni nuova versione diversamente abbiamo che:
- Codice:
x1 = x + y
y1 = x1 - y
x2 = x1 - y1
Abbiamo che y1 vale x1 - y e quindi x + y - y = x. In modo simile, x2 è uguale a x1 - y1 e quindi a x + y - x = y. Alla fine di queste operazioni avremo quindi scambiato le due variabili. Lo stesso procedimento è possibile con ogni operazione binaria che possa essere invertita. In C++ l'operazione migliore per questa operazione è lo XOR bitwise (rappresentato con ^). Il tuo codice sarà quindi in questo caso:
- Codice:
x = x ^ y;
y = x ^ y;
x = x ^ y;
Il codice finale è però decisamente oscuro e in pratica più lento di quello che fa uso di una variabile intermedia. La ragione è principalmente che lo scambio può essere implementato come segue in un qualche pseudo-assembly:
- Codice:
MOV r0, [x] // sposto il valore in x nel registro r0
MOV r1, [y] // sposto il valore di y nel registro r1
MOV [x], r1 // sposto il valore in r1 in x
MOV [y], r0 // sposto il valore in r0 in y
Nel caso degli XOR invece il codice avrà la seguente forma:
- Codice:
MOV r0, [x] // sposto il valore in x nel registro r0
MOV r1, [y] // sposto il valore in y nel registro r1
XOR r0, r0, r1 // faccio lo XOR tra r0 e r1 e memorizzo il valore in r0
XOR r1, r0, r1 // faccio lo XOR tra r0 e r1 e memorizzo il valore in r1
XOR r0, r0, r1 // faccio lo XOR tra r0 e r1 e memorizzo il valore in r0
MOV [x], r0 // sposto il valore di r0 in x
MOV [y], r1 // sposto il valore di r1 in y
Quando si parla di C++ questo trucco è abbastanza inutile. Ha forse un senso in assembly per scambiare registri in situazioni in cui non ci sono altri registri liberi. Ma anche in questo caso fa più che altro parte del passato in cui il numero di registri era molto più limitato e comunque in situazioni molto più particolari. Credo sia poi utile per vedere come spesso sia possibile trovare soluzioni a problemi che sembravano irrisolvibili mettendo in dubbio le proprie convinzioni (che l'unica operazione utilizzabile per uno scambio fosse l'assegnazione di un valore ad esempio).