Pointers - İşaretleyiciler
GO programlama dilinde her değişken hafızada bir yer tutar bu değişken için sistem hafızasında geçici ve benzersiz bir atama yapılır. Bir değere ilk atanan benzersiz hafıza ifadesine erişmek için o değere ait pointer ifadesini bilmemiz gerekir.
Örnek üzerinde inceleyelim :
func BasicPointers() {
var a int = 55 // Bir tamsayı değişkeni bildir
var p *int // Bir tamsayıya işaret eden pointer bildir
p = &a // 'a'nın adresini 'p'ye ata
fmt.Println("a'nın değeri:", a) // 'a'nın değerini yazdır
fmt.Println("a'nın adresi:", &a) // 'a'nın adresini yazdır
fmt.Println("p'nin değeri:", p) // 'p'nin değerini (a'nın adresi) yazdır
fmt.Println("p'deki değer:", *p) // 'p'yi dereference ederek 'a'nın değerini al
*p = 33 // 'p'nin işaret ettiği adresteki değeri değiştir
fmt.Println("a'nın yeni değeri:", a) // 'a'nın değiştirilmiş değerini yazdır
}
DIKKAT:
pointerifadelerindedeclarationveinitializationkavramları için iki farklı sembol yer alır:
&ifadesi :initializationifadesidir. Yanipointerifadesine ait adresi başlatmak için kullanılır.- '*' ifadesi :
declarationifadesidir. Bir ifade önüne bu konulduğunda bu ifadeninpointerolarak kullanılacılağını belirtir.
Yukarıdaki fonksiyonu inceleyeliyelim. Öncelikle a isimli int tipinde 55 değerinde bir değişken tanımladık ve GO bu değişken için bir hafıza birimi atadı. Daha sonra p isimli bir pointer ifadesi tanımladık ve bu ifadenin int türünde bir pointer ifadesi olduğunu var p *int biçiminde yazdık. Yani p ifadesi bir hafıza adresi görüntüleyecektir. Fakat mevcut tanımlı bir hafıza ataması olmadığından dolayı p = &a ifadesiyle a değişkenin hafıza değerini p isimli pointer'a atadık. Kodumuzu çalıştırdığımızda ekrana aşağıdaki gibi bir çıktı gelecektir.
a'nın değeri: 55
a'nın adresi: 0xc0000a6010
p'nin değeri: 0xc0000a6010
p'deki değer: 55
a'nın yeni değeri: 33
Bu ifadeleri eşleştirelim :
fmt.Println("a'nın değeri:", a)
//a'nın değeri: 55
fmt.Println("a'nın adresi:", &a)
// a'nın adresi: 0xc0000a6010
fmt.Println("p'nin değeri:", p)
// p'nin değeri: 0xc0000a6010
fmt.Println("p'deki değer:", *p)
// p'deki değer: 55
Yukarıdaki kod bloğunu incelediğimizde &a ifadesi 0xc0000a6010 gibi eşsiz bir hafıza konumunu görüntüler. Bu ifade sistemde a değişkeni için ayrılan hafıza bloğunun adresidir. a ifadesine 55 değerini atamıştık.
Hatırlarsanız ilk işlemde :
p = &a // 'a'nın adresini 'p'ye ata
şeklinde a'nın hafıza adresini p değişkenine atamıştık. Bu yüzden p değişkeni çağrıldığında hafıza adresi görüntüleyecektir.
fmt.Println("p'nin değeri:", p)
// p'nin değeri: 0xc0000a6010
Eğer p isimli pointer ifadesinin önüne asteriks koyarak *p şeklinde yazarsak, gerçekleşen işlem şöyle olacaktır. p değişkeni GO 'daki adrese gider, bu adresteki atanan değeri alır ve ekrana yazdırır . Bizim işlemimizde p değerine a ya ait adres atanmıştı. p isimli pointer ifadesi o adrese gider ve o adresteki 55 atammış değerini bize döndürür.
fmt.Println("p'deki değer:", *p)
// p'deki değer: 55
Fonksiyonlarda Pointer İfadeleri
Fonksiyonlarla pointer ifadeleri kullanılırken hafıza değer atamaları noktasında sıkça karşımıza çıkacak ifadeler mevcuttur. Fonksiyon argümanlarına pointer atamaları ya da fonksiyon çıktılarına pointer atamaları ile karşılacağız. Yazım biçimi aşağıdaki gibidir.
// func funcname(variable *datatype){}
func FonksiyonAdı(mypointer *string){}
Şimdi bir örnekle bu atamayı açıklayalım. İki adet fonksiyon oluşturuyoruz ve bu fonksiyonları main() isimli fonksiyonuzda çağıracağız.
func PointerDegerDegistirici() {
a := 58
fmt.Println("Fonksiyon çağrılmadan önceki a değeri =", a)
fmt.Println("DegerDegistir isimli Fonksiyon çağrıldı.")
DegerDegistir(&a)
fmt.Println("Fonksiyon çağrıldıktan sonraki a değeri =", a)
}
func DegerDegistir(num *int) {
*num = 99
}
Yukarıdaki örnekte DegerDegistir(num *int) isimli fonksiyonumuz a değişkenine ait adrese yeni bir değer atamaktadır. a değeri PointerDegerDegistirici() isimli fonksiyonumuzda 58 olarak belirlenmişti. &a ile bu değer atamasının hafızadaki konumu bulunur. Bu değeri DegerDegistir(&a) biçiminde ikinci fonksiyonumuza verdiğimizde buradaki değer ifadesi num *int formatına çevrilir. Yani hafızadaki 58 değeri * işareti ile işleme tabi tutulacak bir hale getirilir. *num = 99 ifadesiyle bulunduğu adresteki değerine 99 değeri atanır. Böylece sistem hafızasında a ifadesinin yeni değeri artık 99 olmuştur.
package main
import "fmt"
func main(){
PointerDegerDegistirici()
}
func PointerDegerDegistirici() {
a := 58
fmt.Println("Fonksiyon çağrılmadan önceki a değeri =", a)
fmt.Println("DegerDegistir isimli Fonksiyon çağrıldı.")
DegerDegistir(&a)
fmt.Println("Fonksiyon çağrıldıktan sonraki a değeri =", a)
}
func DegerDegistir(num *int) {
*num = 99
}
Bu ifademizi çağırdığımızda aşağıdaki çıktıyı elde edeceğiz:
Fonksiyon çağrılmadan önceki a değeri = 58
DegerDegistir isimli Fonksiyon çağrıldı.
Fonksiyon çağrıldıktan sonraki a değeri = 99
struct ifadelerinde pointer Kullanimi
struct ifadelerinde pointer kullanımı genellikle struct ifadesine ait bir özelliğe harici bir yapıyla erişip değiştirmek amacıyla kullanılır.
package main
import (
"fmt"
)
type Sahis struct {
isim string
yas int
}
func main() {
kisi1 := Sahis{"Cemil", 35}
fmt.Println("Fonksiyon çağrısından önce, kisi1 =", kisi1)
DuzenleSahis(&kisi1)
fmt.Println("Fonksiyon çağrısından sonra, kisi1 =", kisi1)
}
func DuzenleSahis(kisipointer *Sahis) {
kisipointer.yas = 32 // Kişinin yaşını değiştir
kisipointer.isim = "Esra"
}
Yukarıdaki örnekte Sahis isimli isim ve yas özellikleri olan bir struct yapısı kurulmuştur. kisi1 isimli bir struct nesnesi oluşturduk. Bu şahsın isim niteliği "Cemil" ve yas niteliği 35 olarak tanımlandı. Simdi bu sahsin
isim ve yas ozelliklerini pointer yardımıyla degistirelim.
DuzenleSahis(&kisi1)
Bu ifade kisi1 isimli öğeye ait adres değerine erişim sağlar. Bu yapı bir struct yapısı olduğundan dolayı kisi1.isim ve kisi1.yas niteliklerine erisim sağlar. Aşağıdaki fonksiyon aracılığıyla kişi1 nesnesine ait isim ve yas niteliklerini değiştirelim.
func DuzenleSahis(kisipointer *Sahis) {
kisipointer.yas = 32 // Kişinin yaşını değiştir
kisipointer.isim = "Esra"
}
Kodumuzu calistirdiğimizda aşağıdaki ekran çıktısını elde edeceğiz.
Fonksiyon çağrısından önce, kisi1 = {Cemil 35}
Fonksiyon çağrısından sonra, kisi1 = {Esra 32}
struct niteliklerini teker teker değiştirebileceğimiz gibi hepsini birden de örnekte olduğu gibi değiştirdik.
new ve make ifadelerinde pointer
nil ifadeler oluşturmaya yarayan new ve make gibi ifadeler de sistemde hafızada yer kaplar ve pointer ifadeler kullanılarak bu ifadelere de atamalar yapılabilir.
package main
import (
"fmt"
)
func main() {
sayi := new(int) // Bir tamsayı için bellek tahsis et ve ona bir pointer döndür
fmt.Println("sayi'nin adresi:", sayi) // adres : new(int) isimli degiskenin sayi icin ayırdığı adres
fmt.Println("sayi'ya tahsis edilen adresi:", &sayi) // DIKKAT : new(int) Adresine tahsis edilen adres
fmt.Println("sayi'daki mevcut değer:", *sayi) // Varsayılan değer (0)
*sayi = 100 // sayiya 100 degerini ata
fmt.Println("sayi'daki yeni değer:", *sayi) // atanmis degeri goruntule
fmt.Println("sayi'nin ilk adresi : ", sayi) // sayinin ilk adresi
}
Yukarıdaki örnekte sayi := new(int) ifadesi ile hafızada int tipinde değerler alabilen yer ayrılmış bir sayi degiskeni olusturduk.
Ekran çıktısı aşağıdaki gibi olacaktır.
sayi'nin adresi: 0xc000114010
sayi'ya tahsis edilen adresi: 0xc000116018
sayi'daki mevcut değer: 0
sayi'daki yeni değer: 100
sayi'nin ilk adresi : 0xc000114010
şimdi pointer ifadelerimizi inceleyelim. Öncelikli new ifadesi yeni oluşturulan degisken için bir pointer adresi tahsis eder. Bu pointer adresi, sayi isimli değişkenin adresinden farklıdır. 0xc000114010 adresi pointer icin tahsis edilen adrestir. 0xc000116018 adresi ise bu değişkene değişken olduğundan dolayı tahsis edilen hafıza adresidir. Yani iki farklı hafıza değeri olmasının sebebi, sayi isimli değişken için hafızada bir değer ve sayi değişkeninin hafızada tahsis ettiği pointer ifadesi için başka bir değer oluşmuştur. pointer ifademize *sayi = 100 şeklinde yaptığımız atamadan dolayı sayi ifademizin yeni değeri 100 olmuştur.