Kurzusok
/
Go
Go
/
Feladatok
/
Választás napja
Választás napja

Választás napja

Tanulófeladat

Bevezetés

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:

  1. Ha nagy mennyiségű adatunk van, nagyon nem hatékony másolatokat készíteni ahhoz, hogy függvények között adjuk tovább. Ha ehelyett annak a memóriacímét adjuk át, ahol az adat tárolva van, jelentősen csökkenthetjük programjaink erőforrásigényét.
  2. Ha mutatókat adunk át a függvények között, közvetlenül hozzáférhetünk az adat egyetlen példányához, és módosíthatjuk is, ami azt jelenti, hogy az egyik függvény által végrehajtott változtatások azonnal láthatóvá válnak a program többi része számára, amikor a függvény véget ér.

Változók és memória

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.

Mutatók

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.

Mutató megszerzése egy változóra

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'

Az érték elérése mutatón keresztül (dereferálás)

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

Mutatók struktúrákra

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 már eleve mutatók

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.

Utasítások

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.

1. Hozz létre egy szavazatszámlálót

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

2. Kérd le a szavazatok számát egy számlálóból

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

3. Növeld egy számláló szavazatainak számát

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

4. Hozd létre a választási eredményeket

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
}

5. Hirdesd ki az eredményeket

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)

6. Szavazatok újraszámlálása

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
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
Go Exercism

Készen állsz elkezdeni a(z) Választás napja feladatot?

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.