Në këtë tutorial do të shpjegoj se çfarë janë treguesit dhe si t'i përdorim ato. Do të përdor Go për demonstrim. Nëse nuk jeni të njohur me gjuhën, një vend i mirë për të filluar është të merrni "Tour of Go"
1. Çfarë janë pointerët?
Kur programojmë ne krijojmë variabla për të ruajtur të dhënat për programin tonë. Këto variabla marrin memorie (RAM) që caktohet nga sistemi operativ. Një tregues është adresa e kujtesës. Me fjalë të tjera, ai ruan adresën në vendndodhjen ku ruhet një variabël.
Merrni këtë shembull:
import "fmt" func main() { var x int x = 8 fmt.Println(x) // prints 8 }
Ne kemi deklaruar një variabël x
. Duhet të ruhet diku në memorie, për të marrë adresën:
package main import "fmt" func main() { var x int x = 8 fmt.Println(x) // prints 8 fmt.Println(&x) // prints the memory address e.g 0xc00001c138 }
Ju gjithashtu mund ta ruani adresën në adresën e saj. p.sh.
package main import "fmt" func main() { var x int x = 8 var xPtr *int xPtr = &x fmt.Println(x) // prints 8 fmt.Println(&x)// prints the memory address e.g 0xc00001c138 fmt.Println(xPtr)// prints the memory address e.g 0xc00001c138 }
Në këtë shembull, xPtr
ruan adresën e kujtesës së x
. Për shkak se x
është një numër i plotë, lloji i të dhënave të xPtr
është *int
. *
do të thotë se ndryshorja është një tregues.
Ne gjithashtu mund të marrim vlerën e x
nga treguesi. Kjo quhet de-referencimi i një treguesi.
package main import "fmt" func main() { var x int x = 8 var xPtr *int xPtr = &x fmt.Printf("x=%d\n", x) // x=8 fmt.Printf("&x=%d\n", &x) // &x=824633835832 fmt.Printf("xPtr=%d\n", xPtr) // xPtr=824633835832 fmt.Printf("*xPtr=%d\n", *xPtr) // *xPtr=8 }
Vini re se unë kam përdorur %d
për të formatuar adresën e memories, në këtë mënyrë mund të marrim adresën në bazën 10. Si parazgjedhje, Go e kthen adresën e memories në format heksadecimal.
Ne gjithashtu mund të modifikojmë vlerën e një ndryshoreje duke modifikuar atë që ruhet në adresën e memories. Ju duhet të shmangni modifikimin e variablave në këtë mënyrë sepse modifikon vlerën edhe nëse ndahet në të gjithë funksionet. Për ta demonstruar këtë, së pari le të mos përdorim tregues.
package main import "fmt" func main() { var x = 8 fmt.Printf("main.x=%d\n", x) changeValue(x) fmt.Printf("main.x=%d\n", x) } func changeValue(x int) { fmt.Printf("changeValue: received %d\n", x) x = x * 5 fmt.Printf("changeValue: changed to %d\n", x) } /* output main.x=8 changeValue: received 8 changeValue: changed to 40 main.x=8 */
Funksioni x
në main
nuk është i njëjti x
i përdorur në changeValue
sepse ato janë në shtrirje të ndryshme. Kur changeValue
merr një vlerë, në thelb e ruan atë në variablin e vet dhe e modifikon atë. Për ta vërtetuar këtë, le të printojmë adresat e kujtesës të të dy variablave x
.
package main import "fmt" func main() { var x = 8 fmt.Printf("main.x=%d, address=%d\n", x, &x) changeValue(x) fmt.Printf("main.x=%d\n", x) } func changeValue(x int) { fmt.Printf("changeValue: received %d, address=%d\n", x, &x) x = x * 5 fmt.Printf("changeValue: changed to %d\n", x) } /* output main.x=8, address=824633835832 changeValue: received 8, address=824633835864 changeValue: changed to 40 main.x=8 */
Tani, në vend që të kalojmë x
në changeValue
, le të kalojmë një tregues te x
.
package main import "fmt" func main() { var x = 8 fmt.Printf("main.x=%d, address=%d\n", x, &x) changeValue(&x) fmt.Printf("main.x=%d\n", x) } func changeValue(x *int) { fmt.Printf("changeValue: received %d, address=%d\n", *x, x) *x = *x * 5 fmt.Printf("changeValue: changed to %d\n", *x) } /* output main.x=8, address=824633835832 changeValue: received 8, address=824633835832 changeValue: changed to 40 main.x=40 */
Tani, disa gjëra të reja ndodhin.
Për shkak se tani po ia kalojmë treguesin funksionit changeValue
, kalojmë &x
në vend të x
sepse po kalojmë adresën.
x
është tani një tregues në funksionin changeValue
, prandaj, kur shtypim vlerën e ruajtur në x
, duhet ta çreferojmë atë si x
dhe kur shtypim adresën e memories, thjesht përdorim x
sepse ajo mban adresën.
Kur modifikojmë vlerën ne bëjmë:
*x = *x * 5
Në anën e djathtë, marrim vlerën e ruajtur në x
, në këtë rast 8 dhe e shumëzojmë me 5. Më pas, vlerën aktuale të ruajtur në x
e zëvendësojmë me rezultatin e shumëzimit.
Pas kësaj rreshti, të dyja main
dhe changeValue
shtypin vlerën e x
si 40. Kjo për shkak se changeValue
nuk krijoi vlerën e vet, ai modifikoi x
direkt sepse ka akses në adresën e tij.
Kjo është fuqia dhe rreziku i përdorimit të treguesve. Ato në thelb ju lejojnë të "ripërdorni" të njëjtën variabël. Megjithatë, kur e modifikoni kudo, ai do të modifikohet kudo tjetër.
2. Kur të përdoren treguesit.
Në një kuptim praktik, mos u tundoni të përdorni shumë tregues. Përdorni ato vetëm kur:
- Po përdorni burime të përbashkëta, p.sh. një lidhje me bazën e të dhënave, redis etj. Kjo siguron një rritje të performancës sepse mund të ndani të njëjtën lidhje në aplikacionin tuaj dhe të shmangni hapjen dhe mbylljen e lidhjeve për çdo transaksion.
- Ju po kaloni rreth një ndryshoreje të madhe, p.sh. një strukture dhe dëshironi të kurseni në kujtesë duke kaluar vetëm adresën dhe jo të gjithë strukturën.
- Ju po krijoni strukturën tuaj të të dhënave, p.sh. rafte, radhë.
- Gjatë shkrimit të metodave të strukturës që modifikojnë vlerën e shembullit të strukturës
package main import "fmt" type Person struct { Name string Age int } func (p Person) MethodWithoutPointer() { p.Age = 56 } func (p *Person) MethodWithPointer() { p.Age = 56 } func main() { var john = Person{ Name: "John Doe", Age: 32, } Print(john) john.MethodWithoutPointer() // john.Age is not modified because a new Person instance is created, therefore the scopes are different. Print(john) john.MethodWithPointer() // john.Age is modified because we are modified the value in john variable's memory address. Print(john) } func Print(person Person) { fmt.Printf("%s is %d years old\n", person.Name, person.Age) } /* output John Doe is 32 years old John Doe is 32 years old John Doe is 56 years old */
3. Kur duhet shmangur treguesit.
Rregulli i përgjithshëm i përgjithshëm është, përdorni tregues vetëm kur është e nevojshme ose kur ata do të përmirësojnë performancën. Shmangni ato ku do të modifikoni vlerën në funksione, thread ose gorutine të ndryshme.
Për shembull, kjo mund të sjellë rezultate të paqëllimshme.
package main import "fmt" type Person struct { Name string Age int } func main() { var john = Person{ Name: "John Doe", Age: 32, } SomeImportantFunction(john) SomeFunction(&john) SomeImportantFunction(john) } func SomeImportantFunction(person Person) { fmt.Printf("%s is %d years old\n", person.Name, person.Age) } func SomeFunction(person *Person) { person.Age++ } /* output John Doe is 32 years old John Doe is 33 years old */
Imagjinoni nëse SomeImportantFunction
do të ishte një funksion kritik dhe SomeFunction
thjesht do të rriste Age
për arsyet e veta, SomeImportantFunction
do të sillej keq në varësi të faktit nëse thirrej përpara apo pas SomeFunction
.
Prandaj, modifikoni treguesit vetëm kur dëshironi që ai të modifikohet kudo që është referuar në programin tuaj.
Referencat dhe burimet:
- "Adresa e kujtesës"
- "Tregues të varur, të zbrazët, të pavlefshëm dhe të egër"
- "Treguesit në Golang"
Botuar fillimisht në https://innv8.ke.