& ve *
Bir pointer bir değerin bellek adresini tutar. &x size x'e bir pointer verir. *p pointer'ı izleyerek değere ulaşır.
Yıldızın iki rolü vardır. Bir tipte (*int) "int'e pointer" anlamına gelir. Bir ifadede (*p) "p'nin işaret ettiği değer" anlamına gelir. p'nin kendisini yazdırmak 0xc000012345 gibi, çalıştırmadan çalıştırmaya değişen bir adres gösterir.
Pointer'lar neden var: kopyalamak yerine paylaşmak
Go her şeyi değerle aktarır. Bir fonksiyon her argümanın bir kopyasını alır, bu yüzden çağıranın değişkenini değiştiremez. Bir pointer aktarmak bunun yerine adresi kopyalar ve iki taraf da aynı değere ulaşır.
Go'da C++ anlamında referanslar yoktur. Go'da "referansla aktarım", bir pointer'ı değerle aktarmak demektir.
Struct pointer'ları
Struct pointer'ları Go kodunda en yaygın pointer'lardır. Alan erişimi otomatik olarak dereference eder: p.Name, (*p).Name demektir.
Metotlar da aynı mantığı izler: bir pointer alıcı, metodun struct'ı değiştirmesine izin verir. Değer ve pointer alıcı arasındaki seçimi metotlar sayfası anlatıyor.
new
new(T) sıfırlanmış bir T ayırır ve adresini döndürür. Bir değişken tanımlayıp adresini almakla aynıdır.
p := new(int) // *int pointing at 0
var x int
q := &x // same thing, two lines
a := new(Account) // same as &Account{}
Pratikte struct'lar için &T{...} daha yaygındır, çünkü alanları aynı ifadede ayarlamanıza izin verir. new temel tiplere pointer'lar için kullanışlıdır. Onu, slice'ları, map'leri ve channel'ları oluşturup pointer olarak değil kullanıma hazır olarak döndüren make ile karıştırmayın.
Yerel bir değişkene pointer döndürmek güvenlidir
C'de yerel bir değişkenin adresini döndürmek bir hatadır. Go'da sıradan bir koddur.
Derleyicinin escape analizi, bir değerin stack'te mi yoksa heap'te mi yaşayacağına karar verir. Siz asla seçmezsiniz ve belleği asla serbest bırakmazsınız: ona hiçbir şey işaret etmediğinde çöp toplayıcı onu geri alır. Merak ediyorsanız go build -gcflags=-m bu kararları yazdırır.
nil pointer'lar
Her pointer tipinin sıfır değeri nil'dir. Nil'i dereference etmek panic'e yol açar.
Çıktı:
1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference
recover olmasaydı program bu mesajla ve panic: runtime error: invalid memory address or nil pointer dereference ile [signal SIGSEGV: segmentation violation ...] ile başlayan bir stack trace ile çökerdi. Olağan kaynaklar:
- hiç ayarlanmamış bir pointer değişkeni ya da struct alanı
- eksik bir anahtar için bir
map[string]*Tüzerindenildöndürenm[key] - bir hatayı yok saymak:
f, _ := os.Open(path)başarısızlıktaf'yi nil bırakır - nil bir pointer üzerinde çağrılan ve sonra bir alanı okuyan pointer alıcılı bir metot
Nil bir pointer üzerinde metot çağırmanın kendisi geçerlidir. Panic yalnızca metot alıcının alanlarına dokunduğunda olur.
Pointer ne zaman kullanılır
Pointer kullanın, eğer:
- bir fonksiyon ya da metot çağıranın değerini değiştirmek zorundaysa
- değer büyük bir struct'sa ve sık sık aktarılıyorsa
- tip kopyalanmamalıysa (bir
sync.Mutexiçeriyorsa ya da bir bağlantı gibi benzersiz bir kaynağı temsil ediyorsa) - "değer yok"u
nilile ifade etmeniz gerekiyorsa, örneğin bir JSON struct'ında isteğe bağlı bir alan (*int, "yok" ile0'ı ayırt eder)
Pointer kullanmayın, eğer:
- değer küçük ve salt okunursa (
time.Time, birPoint) - tip zaten referans benzeri bir değerse: slice'lar, map'ler, channel'lar, fonksiyonlar ve interface'lerin önüne nadiren
*gerekir. Bir*[]intya da*map[string]intneredeyse her zaman bir hatadır
Pointer'lar kendiliğinden daha hızlı değildir. Küçük bir struct'ı kopyalamak ucuzdur, bir pointer ise heap ayırmayı zorunlu kılıp çöp toplayıcıya iş ekleyebilir.
Pointer'lar ve döngü değişkenleri
Bir döngü değişkeninin adresini almak Go 1.22'den beri güvenlidir, çünkü her yinelemenin kendi değişkeni vardır:
Bu 0 1 2 yazdırır. Go 1.22'den önce (range 3 de o sürümde geldi) for i := 0; i < 3; i++ ile yazılmış aynı döngü 3 3 3 yazdırıyordu, çünkü üç pointer da tek bir değişkeni paylaşıyordu. for _, v := range items içindeki &v'nin slice'taki elemana değil, elemanın bir kopyasına işaret ettiğine dikkat edin. Elemanın kendisine pointer almak için &items[i] kullanın.
Sık yapılan hatalar
- Kontrol etmeden dereference etmek. Nil'in mümkün olduğu her yerde
if p == nilkontrolü yapın. - Bir range değerine pointer.
&vbir kopyaya işaret eder;&s[i]kullanın. - Slice'lara ve map'lere pointer'lar. Nadiren gerekir. Bunun yerine yeni slice'ı döndürün.
- Bir
appendboyunca&s[i]'yi tutmak.appendyeniden ayırırsa pointer hâlâ eski diziye işaret eder ve sonraki yazmalar görünür hiçbir yere gitmez.
Sıkça Sorulan Sorular
Go'da & ve * ne anlama gelir?
&x, x'in adresini alır ve *T tipinde bir pointer verir. *p pointer'ı dereference eder: işaret ettiği değeri okur ya da yazar. Bir tipte *T, "T'ye pointer" anlamına gelir.
Go'da new ile make arasındaki fark nedir?
new(T) sıfırlanmış bir T ayırır ve bir *T döndürür; her tip için çalışır. make yalnızca slice'lar, map'ler ve channel'lar için çalışır ve o tipin başlatılmış (sıfırlanmış değil, pointer değil) bir değerini döndürür: make(map[string]int) kullanıma hazırdır, new(map[string]int) ise nil bir map'e pointer'dır.
Go'da yerel bir değişkene pointer döndürebilir miyim?
Evet. C'nin aksine güvenlidir. Derleyicinin escape analizi değişkenin fonksiyondan uzun yaşadığını görür ve onu heap'te ayırır. func newInt() *int { x := 5; return &x } doğru bir Go kodudur.
Go'da "invalid memory address or nil pointer dereference" hatasına ne yol açar?
Nil bir pointer üzerinden okumak ya da yazmak: hiç atanmamış bir *T değişkeni, bir map[K]*V içinde eksik bir anahtarın aranması ya da kontrol etmediğiniz bir hatanın yanında nil döndüren bir fonksiyon. Nil kontrolü yapın ya da pointer'ı kullanmadan önce başlatın.
Go'da pointer aritmetiği var mı?
Hayır. Bir pointer'a ekleme yapamaz ya da onun üzerinden belleği indeksleyemezsiniz. unsafe paketi alt seviye kod için buna, hiçbir güvenlik garantisi olmadan izin verir. Normal Go kodu bitişik bellek için slice'ları kullanır.