A Go, akárcsak sok más nyelv, rendelkezik mutatókkal. Ha most ismerkedsz a mutatókkal, elsőre kicsit rejtélyesnek tűnhetnek, de ha egyszer megszokod őket, egészen egyszerűek. A Go kulcsfontosságú részét képezik, szóval szánj rá időt, hogy igazán megértsd őket.
Mielőtt belemennénk a részletekbe, érdemes megérteni, mire is használjuk a mutatókat. A mutatók segítségével memóriát oszthatunk meg a programunk más részeivel, és ez két fő okból hasznos:
Tegyük fel, hogy van egy közönséges egész típusú változónk, a:
var a int
Amikor deklarálunk egy változót, a Go-nak helyet kell keresnie a memóriában, ahol tárolhatja az értékét. Ezt nagyrészt elrejti előlünk a Go. Amikor le kell kérnünk az abban a memóriaszakaszban tárolt értéket, elég a változó nevével hivatkoznunk rá.
Például amikor a + 2-t írunk, valójában lekérjük az a változóhoz tartozó memóriában tárolt értéket, és hozzáadunk 2-t.
Hasonlóképpen, amikor meg kell változtatnunk az a memóriaszakaszában lévő értéket, a változó nevének használatával hajthatunk végre értékadást:
a = 3
Az a-hoz tartozó memóriaszakasz mostantól a 3 értéket tárolja.
Bár a változók segítségével a memóriában lévő értékekre hivatkozhatunk, néha hasznos tudni azt a memóriacímet, amelyre a változó mutat. A mutatók ezeknek az értékeknek a memóriacímeit tárolják. Úgy deklarálsz egy mutató típusú változót, hogy a mögöttes típus elé csillagot írsz:
var p *int // 'p' contains the memory address of an integer
Itt egy p nevű változót deklarálunk, amelynek típusa „mutató int-re” (*int).
Ez azt jelenti, hogy p egy egész szám memóriacímét fogja tárolni.
A mutatók zéróértéke nil, mert egy nil mutató nem tárol memóriacímet.
Ahhoz, hogy megtaláljuk egy változó értékének memóriacímét, használhatjuk a & operátort.
Ha például meg akarjuk keresni az a változó memóriacímét, és el akarjuk tárolni a p mutatóban, a következőt tehetjük:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
Amikor van egy mutatónk, néha tudni szeretnénk, milyen érték tárolódik azon a memóriacímen, amelyet a mutató képvisel.
Ezt a * operátorral tehetjük meg:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
var b int
b = *p // b == 2
A *p művelet lekéri a p-ben tárolt memóriacímen lévő értéket.
Ezt a műveletet gyakran „dereferálásnak” nevezik.
A dereferáló operátort arra is használhatjuk, hogy új értéket adjunk a mutató által hivatkozott memóriacímnek:
var a int // declare int variable 'a'
a = 2 // assign 'a' the value of 2
var pa *int
pa = &a // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2 // increment by 2 the value at memory address 'pa'
fmt.Println(a) // Output: 4
// 'a' will have the new value that was changed via the pointer!
Ha értéket adunk a *pa-nak, az megváltoztatja a pa által tárolt memóriacímen lévő értéket.
Mivel pa az a memóriacímét tárolja, a *pa-nak való értékadással valójában az a értékét változtatjuk meg!
Egy figyelmeztetés azonban: mindig ellenőrizd, hogy egy mutató nem nil-e, mielőtt dereferálod.
Ha egy nil mutatót dereferálsz, a program futásidőben összeomlik!
var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference
Eddig csak primitív értékekre mutató mutatókkal találkoztunk. Struktúrákhoz is létrehozhatunk mutatókat:
type Person struct {
Name string
Age int
}
var peter Person
peter = Person{Name: "Peter", Age: 22}
var p *Person
p = &peter
Úgy is csinálhattuk volna, hogy létrehozunk egy új Person-t, és rögtön eltárolunk rá egy mutatót:
var p *Person
p = &Person{Name: "Peter", Age: 22}
Amikor van egy mutatónk egy struktúrára, nem kell dereferálnunk a mutatót ahhoz, hogy hozzáférjünk az egyik mezőjéhez:
var p *Person
p = &Person{Name: "Peter", Age: 22}
fmt.Println(p.Name) // Output: "Peter"
// Go automatically dereferences 'p' to allow
// access to the 'Name' field
A szeletek és a térképek azért különleges típusok, mert a megvalósításukban már eleve szerepelnek mutatók. Ez azt jelenti, hogy a legtöbbször nincs szükség arra, hogy mutatót hozzunk létre ezekhez a típusokhoz, hogy megosszuk az értékeik memóriacímét. Képzeld el, hogy van egy függvényünk, amely megnöveli egy térképben lévő kulcs értékét:
func incrementPeterAge(m map[string]int) {
m["Peter"] += 1
}
Ha létrehozunk egy térképet, és meghívjuk ezt a függvényt, a függvény által a térképen végzett módosítások a függvény befejeződése után is megmaradnak. Ez ahhoz a viselkedéshez hasonlít, amit akkor kapnánk, ha mutatót használnánk, de figyeld meg, hogy ebben a példában nem használunk semmilyen hivatkozást vagy dereferálást, sem a mutatószintaxis egyetlen elemét sem:
ages := map[string]int{
"Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!
Ugyanez érvényes akkor is, amikor egy szeletben lévő meglévő elemet módosítunk.
Azonban az olyan műveletek, amelyek új szeletet adnak vissza, mint például az append, különleges esetet jelentenek, és nem feltétlenül módosítják a szeletet a függvényen kívül.
Ez abból adódik, ahogyan a szeletek belsőleg működnek, de ezzel ebben a feladatban nem foglalkozunk részletesen, mivel ez egy haladóbb téma.
Ha igazán kíváncsi vagy, többet olvashatsz erről a Go Blog: Az „append” mechanikája bejegyzésben.
A közeledben lévő helyi iskolában nagyon aktív a diákegyesület. A diákegyesületet egy elnök vezeti, és kétévente választást tartanak, hogy új elnököt válasszanak.
Az idei választáson úgy döntöttek, hogy új digitális rendszerre van szükség a szavazatok megszámlálásához. Az iskola a segítségedet kéri az új rendszer megépítésében.
Az új szavazási rendszer egyik első eleme egy szavazatszámláló. Ez a számláló segít nyomon követni, hogy egy adott jelölt hány szavazatot kapott.
Hozz létre egy NewVoteCounter függvényt, amely elfogadja egy jelölt kezdeti szavazatainak számát, és egy int-re mutató mutatót ad vissza, amely a megadott kezdeti szavazatszámmal inicializálódik.
var initialVotes int
initialVotes = 2
var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true
Most már van módod új számlálók létrehozására! De rájössz, hogy az új rendszernek arra is szüksége lesz, hogy le tudja kérni a szavazatok számát egy számlálóból.
Hozz létre egy VoteCount függvényt, amely argumentumként egy számlálót (*int) vár, és visszaadja a számlálóban lévő szavazatok számát.
Ha a számláló nil, feltételezd, hogy a számlálóhoz nem tartozik szavazat:
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
VoteCount(voteCounter)
// => 3
var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0
Végre eljött a szavazatszámlálás ideje! Most szükséged van egy módra, amellyel növelheted a számlálóban lévő szavazatok számát.
Hozz létre egy IncrementVoteCount függvényt, amely argumentumként egy számlálót (*int) és egy szavazatszámot vár, és ennyivel növeli a számlálót.
Feltételezheted, hogy az átadott mutató soha nem nil.
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
IncrementVoteCount(voteCounter, 2)
votes == 5 // true
*voteCounter == 5 // true
Most, hogy minden szavazatot megszámláltunk, ideje előkészíteni az eredményhirdetést az egész iskola számára. Ehhez rájössz, hogy nem elég, ha csupán szavazatszámlálókkal rendelkezel. Kell egy mód arra, hogy egy adott jelölthöz hozzárendeld a szavazatok számát.
Hozz létre egy NewElectionResult függvényt, amely megkapja egy jelölt nevét és a szavazatai számát, majd visszaad egy új választási eredményt.
var result *ElectionResult
result = NewElectionResult("Peter", 3)
result.Name == "Peter" // true
result.Votes == 3 // true
A választási eredmény struktúra már létre van hozva neked, és így van definiálva:
type ElectionResult struct {
// Name of the candidate
Name string
// Votes of votes the candidate had
Votes int
}
Itt az idő, hogy az iskola tudtára add az új elnököt!
Az új elnököt az iskola kicsi digitális üzenőtábláin hirdetik ki.
Az üzenetnek mutatnia kell az új elnök nevét és a kapott szavazatok számát a következő formátumban: <candidate_name> (<votes>).
Íme egy példa egy ilyen üzenetre: "Peter (51)".
Hozz létre egy DisplayResult függvényt, amely argumentumként egy *ElectionResult értéket kap, és visszaad egy stringet a megjelenítendő üzenettel.
var result *ElectionResult
result = &ElectionResult{
Name: "John",
Votes: 32,
}
DisplayResult(result)
// => John (32)
Hogy megbizonyosodjanak a végleges eredmények pontosságáról, újraszámlálták a szavazatokat. Az újraszámlálás során kiderült, hogy néhány jelölt szavazatainak száma eggyel eltért.
Hozz létre egy DecrementVotesOfCandidate függvényt, amely megkapja a végleges eredményeket és egy jelölt nevét, akinek csökkentened kell a szavazatai számát.
A végleges eredmények map[string]int formában vannak megadva, ahol a kulcsok a jelöltek nevei, az értékek pedig az összes szavazatuk.
var finalResults = map[string]int{
"Mary": 10,
"John": 51,
}
DecrementVotesOfCandidate(finalResults, "Mary")
finalResults["Mary"]
// => 9
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Go nyelvet 34 fogalom165 feladat segítségével, valódi emberi mentorálással, mindez ingyen.