Oefening: segmenten verkennen
We hebben matrices in de vorige sectie verkend en geleerd dat matrices de basis vormen voor segmenten en kaarten. Je begrijpt waarom het over een ogenblik gaat. Net als matrices is een segment een gegevenstype in Go dat een reeks elementen van hetzelfde type vertegenwoordigt. Maar hoe belangrijker het verschil met matrices is dat de grootte van een segment dynamisch is, niet vast.
Een segment is een gegevensstructuur boven op een matrix of een ander segment. We verwijzen naar de oorspronkelijke matrix of het oorspronkelijke segment als de onderliggende matrix. Met een segment hebt u toegang tot de hele onderliggende matrix of alleen een subsequententie van elementen.
Een segment heeft slechts drie onderdelen:
- Aanwijzer naar het eerste bereikbare element van de onderliggende matrix. Dit element is niet noodzakelijkerwijs het eerste element van de matrix,
array[0]
. - De lengte van het segment. Het aantal elementen in het segment.
- Capaciteit van het segment. Het aantal elementen tussen het begin van het segment en het einde van de onderliggende matrix.
De volgende afbeelding geeft aan wat een segment is:
U ziet dat het segment slechts een subset van de onderliggende matrix is. Laten we eens kijken hoe u de voorgaande afbeelding in code kunt weergeven.
Een segment declareren en initialiseren
Als u een segment wilt declareren, doet u dit op dezelfde manier als u een matrix declareert. De volgende code vertegenwoordigt bijvoorbeeld wat u in de segmentafbeelding hebt gezien:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
fmt.Println(months)
fmt.Println("Length:", len(months))
fmt.Println("Capacity:", cap(months))
}
Wanneer u de code uitvoert, ziet u de volgende uitvoer:
[January February March April May June July August September October November December]
Length: 12
Capacity: 12
U ziet dat een segment op dit moment niet te veel verschilt van een matrix. U declareert ze op dezelfde manier. Als u de informatie uit een segment wilt ophalen, kunt u de ingebouwde functies len()
en cap()
. We blijven deze functies gebruiken om te bevestigen dat een segment een subsequence van elementen uit een onderliggende matrix kan hebben.
Items segmenteren
Go biedt ondersteuning voor de slice-operator s[i:p]
, waarbij:
s
vertegenwoordigt de matrix.i
vertegenwoordigt de aanwijzer naar het eerste element van de onderliggende matrix (of een ander segment) dat moet worden toegevoegd aan het nieuwe segment. De variabelei
komt overeen met het element op de indexlocatiei
in de matrix,array[i]
. Houd er rekening mee dat dit element niet noodzakelijkerwijs het eerste element van de onderliggende matrix is,array[0]
.p
vertegenwoordigt het aantal elementen in de onderliggende matrix dat moet worden gebruikt bij het maken van het nieuwe segment en ook de positie van het element. De variabelep
komt overeen met het laatste element in de onderliggende matrix die in het nieuwe segment kan worden gebruikt. Het element op positiep
in de onderliggende matrix wordt gevonden op de locatiearray[i+1]
. U ziet dat dit element niet noodzakelijkerwijs het laatste element van de onderliggende matrix is,array[len(array)-1]
.
Daarom kan een segment alleen verwijzen naar een subset van elementen.
Stel dat u vier variabelen elk kwartaal van het jaar wilt weergeven en dat u een segment months
van 12 elementen hebt. In de volgende afbeelding ziet u hoe u segmenteert months
in vier nieuwe quarter
segmenten:
Als u wilt weergeven in code wat u in de vorige afbeelding hebt gezien, kunt u de volgende code gebruiken:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
quarter1 := months[0:3]
quarter2 := months[3:6]
quarter3 := months[6:9]
quarter4 := months[9:12]
fmt.Println(quarter1, len(quarter1), cap(quarter1))
fmt.Println(quarter2, len(quarter2), cap(quarter2))
fmt.Println(quarter3, len(quarter3), cap(quarter3))
fmt.Println(quarter4, len(quarter4), cap(quarter4))
}
Wanneer u de code uitvoert, krijgt u de volgende uitvoer:
[January February March] 3 12
[April May June] 3 9
[July August September] 3 6
[October November December] 3 3
U ziet hoe de lengte van de segmenten hetzelfde is, maar de capaciteit verschilt. Laten we het quarter2
segment verkennen. Wanneer u dit segment declareert, zegt u dat u wilt dat het segment begint op positie drie en het laatste element zich op positienummer zes bevindt. De lengte van het segment is drie elementen, maar de capaciteit is negen omdat de onderliggende matrix meer elementen of posities heeft, maar niet zichtbaar is voor het segment. Als u bijvoorbeeld iets probeert fmt.Println(quarter2[3])
af te drukken, krijgt u de volgende fout: panic: runtime error: index out of range [3] with length 3
De capaciteit van een segment geeft alleen aan hoeveel u een segment kunt uitbreiden. Daarom kunt u een uitgebreid segment maken van quarter2
, zoals in dit voorbeeld:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
quarter2 := months[3:6]
quarter2Extended := quarter2[:4]
fmt.Println(quarter2, len(quarter2), cap(quarter2))
fmt.Println(quarter2Extended, len(quarter2Extended), cap(quarter2Extended))
}
Wanneer u de voorgaande code uitvoert, krijgt u de volgende uitvoer:
[April May June] 3 9
[April May June July] 4 9
U ziet dat wanneer u de quarter2Extended
variabele declareert, u de initiële positie ([:4]
) niet hoeft op te geven. Als u dat doet, gaat Go ervan uit dat u de eerste positie van het segment wilt. U kunt hetzelfde doen voor de laatste positie ([1:]
). In Go wordt ervan uitgegaan dat u wilt verwijzen naar alle elementen tot de laatste positie van een segment (len()-1
).
Items toevoegen
We hebben gezien hoe segmenten werken en hoe ze vergelijkbaar zijn met matrices. Laten we nu eens ontdekken hoe ze verschillen van matrices. Het eerste verschil is dat de grootte van een segment niet vast is, het is dynamisch. Nadat u een segment hebt gemaakt, kunt u er meer elementen aan toevoegen en wordt het segment uitgebreid. U ziet op een moment wat er met de onderliggende matrix gebeurt.
Als u een element aan een segment wilt toevoegen, biedt Go de append(slice, element)
ingebouwde functie. U geeft het segment door om te wijzigen en het element toe te voegen als waarden aan de functie. De append
functie retourneert vervolgens een nieuw segment dat u opslaat in een variabele. Dit kan dezelfde variabele zijn voor het segment dat u wijzigt.
Laten we eens kijken hoe het toevoegproces eruitziet in code:
package main
import "fmt"
func main() {
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("%d\tcap=%d\t%v\n", i, cap(numbers), numbers)
}
}
Wanneer u de voorgaande code uitvoert, ziet u de volgende uitvoer:
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
Deze uitvoer is interessant. Vooral voor wat de aanroep van de cap()
functie retourneert. Alles ziet er normaal uit tot de derde iteratie, waarbij de capaciteit verandert in 4 en er slechts drie elementen in het segment zijn. In de vijfde iteratie varieert de capaciteit opnieuw tot 8 en in de negende tot en met 16.
Ziet u een patroon van de capaciteitsuitvoer? Wanneer een segment niet voldoende capaciteit heeft om meer elementen te bewaren, verdubbelt Go de capaciteit. Er wordt een nieuwe onderliggende matrix gemaakt met de nieuwe capaciteit. U hoeft niets te doen voor deze toename van de capaciteit. Go doet dit automatisch. Je moet voorzichtig zijn. Op een bepaald moment heeft een segment mogelijk veel meer capaciteit dan nodig is en u krijgt geheugen te verspillen.
Items verwijderen
Misschien vraagt u zich af, hoe zit het met het verwijderen van elementen? Go heeft geen ingebouwde functie om elementen uit een segment te verwijderen. U kunt de segmentoperator s[i:p]
gebruiken die we eerder hebben besproken om een nieuw segment te maken met alleen de elementen die u nodig hebt.
Met de volgende code wordt bijvoorbeeld een element uit een segment verwijderd:
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
remove := 2
if remove < len(letters) {
fmt.Println("Before", letters, "Remove ", letters[remove])
letters = append(letters[:remove], letters[remove+1:]...)
fmt.Println("After", letters)
}
}
Wanneer u de voorgaande code uitvoert, krijgt u de volgende uitvoer:
Before [A B C D E] Remove C
After [A B D E]
Met deze code wordt een element uit een segment verwijderd. Het element wordt vervangen door het volgende element in het segment of geen als u het laatste element verwijdert.
Een andere benadering is het maken van een nieuwe kopie van het segment. In de volgende sectie leert u hoe u kopieën van segmenten maakt.
Kopieën van segmenten maken
Go heeft een ingebouwde copy(dst, src []Type)
functie om kopieën van een segment te maken. U verzendt het doelsegment en het bronsegment. U kunt bijvoorbeeld een kopie van een segment maken zoals in dit voorbeeld:
slice2 := make([]string, 3)
copy(slice2, letters[1:4])
Waarom wilt u kopieën maken? Wanneer u een element van een segment wijzigt, wijzigt u ook de onderliggende matrix. Alle andere segmenten die naar dezelfde onderliggende matrix verwijzen, worden beïnvloed. Laten we dit proces in code bekijken en vervolgens herstellen we dit door een kopie van een segment te maken.
Gebruik de volgende code om te controleren of een segment verwijst naar een matrix en elke wijziging die u in een segment aanbrengt, van invloed is op de onderliggende matrix.
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
fmt.Println("Before", letters)
slice1 := letters[0:2]
slice2 := letters[1:4]
slice1[1] = "Z"
fmt.Println("After", letters)
fmt.Println("Slice2", slice2)
}
Wanneer u de voorgaande code uitvoert, ziet u de volgende uitvoer:
Before [A B C D E]
After [A Z C D E]
Slice2 [Z C D]
U ziet hoe de wijziging die we hebben aangebracht, van invloed is op slice1
de letters
matrix en slice2
. U kunt in de uitvoer zien dat de letter B is vervangen door Z en dat dit van invloed is op iedereen die naar de letters
matrix verwijst.
U kunt dit probleem oplossen door een segmentkopie te maken, die onder de schermen een nieuwe onderliggende matrix maakt. U kunt de volgende code gebruiken:
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
fmt.Println("Before", letters)
slice1 := letters[0:2]
slice2 := make([]string, 3)
copy(slice2, letters[1:4])
slice1[1] = "Z"
fmt.Println("After", letters)
fmt.Println("Slice2", slice2)
}
Wanneer u de voorgaande code uitvoert, ziet u de volgende uitvoer:
Before [A B C D E]
After [A Z C D E]
Slice2 [B C D]
U ziet hoe de wijziging in slice1
de onderliggende matrix is beïnvloed, maar niet van invloed is op de nieuwe slice2
.