Slice'y i tablice to jedna z najczęściej używanych struktur danych w Go. Mimo podobnej składni, różnice w ich budowie i zachowaniu mogą prowadzić do błędów związanych z wydajnością, pamięcią i semantyką.
Historia zagadnienia:
Go od samego początku wybrał wyraźny model zarządzania pamięcią, w którym tablice to sekwencje elementów o stałym rozmiarze, a slice'y to dynamiczny widok na tablicę. Takie rozdzielenie pozwala kontrolować koszt operacji i zachowanie kodu.
Problem:
Główna trudność polega na zamieszaniu między kopiowaniem tablicy (semantyka wartości) a "referencyjnością" slice'a. Błędy często pojawiają się podczas przekazywania tych typów do funkcji i zmiany wartości, prowadząc do nieoczekiwanych skutków ubocznych.
Rozwiązanie:
Tablice są zawsze kopiowane przy przekazywaniu przez wartość: funkcja otrzymuje kopię całej zawartości. Slice to mała struktura (nagłówek), która zawiera wskaźnik do tablicy, długość i pojemność. Zmiany wewnątrz slice'a są widoczne na zewnątrz, jeśli zmieni się zawartość tablicy (ale nie, jeśli sam slice jest przekierowany na nową tablicę wewnątrz funkcji).
Przykład kodu:
func updateArray(arr [3]int) { arr[0] = 10 } func updateSlice(slc []int) { slc[0] = 10 } func main() { a := [3]int{1,2,3} b := []int{1,2,3} updateArray(a) updateSlice(b) fmt.Println(a) // [1 2 3] fmt.Println(b) // [10 2 3] }
Kluczowe cechy:
Co się stanie, jeśli zmienisz długość slice'a wewnątrz funkcji? Czy wpłynie to na pierwotny slice?
Nie, zmiana długości slice'a (np. za pomocą slc = slc[:2]) wewnątrz funkcji wpłynie tylko na lokalną kopię nagłówka. Oryginalny slice pozostanie niezmieniony.
Czy operator append zwraca zmodyfikowany slice w tej samej pamięci?
Nie koniecznie. Jeśli pojemność jest niewystarczająca, zostaje utworzona nowa tablica, a wskaźnik do nowej tablicy jest zwracany. Stara tablica pozostanie niezmieniona.
Przykład kodu:
s := []int{1,2,3} s2 := append(s, 4, 5, 6) // s2 może być w nowej pamięci
Czy można przypisać tablicę do slice'a lub odwrotnie?
Nie. []int i [5]int to różne typy. Aby przekazać tablicę jako slice, należy skorzystać z konwersji arr[:]. Odwrócenie nie jest możliwe.
Młodszy programista zaimplementował funkcję aktualizacji tablicy, przekazując tablicę do funkcji z oczekiwaniem, że zmiany będą stosowane do oryginalnej tablicy. Zmiany nie "zapisanych".
Zalety:
Wady:
Funkcja przyjmowała slice i wyraźnie zwracała zmodyfikowaną kopię, zwiększając przewidywalność efektu. Wszystkie zmiany były świadome, dane nie "wyciekały" i nie były zmieniane w sposób niejawny.
Zalety:
Wady: