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 xmain 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ë xchangeValue, 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:

  1. 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.
  2. 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.
  3. Ju po krijoni strukturën tuaj të të dhënave, p.sh. rafte, radhë.
  4. 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:

  1. "Adresa e kujtesës"
  2. "Tregues të varur, të zbrazët, të pavlefshëm dhe të egër"
  3. "Treguesit në Golang"

Botuar fillimisht në https://innv8.ke.