Blog

Go Programlama Diline Giriş

Go Nedir?

gopher

Go, Google mühendisleri tarafından 2007 yılında geliştirilmeye başlanan, kendi tabirleri ile basit, güvenilir ve verimli uygulamalar geliştirmeyi kolaylaştıran açık kaynaklı bir programlama dilidir.

Go dilinin en önemli özelliklerinden bazıları;

  • Kolay öğrenme ve kodlama ( öğrenme aşamasında kullanılabilecek çok başarılı da bir playgroundu var ),
  • Yükse hızda derleme,
  • Yüksek performans,
  • Güçlü dokümantasyon altyapısı,
    • Bu dokümantasyonların arasında “Nasıl daha iyi Go kodu yazabiliriz” ‘i anlattıkları “Effective Go” sayfası da mutlaka okunmalı.
  • Çöp toplayıcı ( Garbage collector ) desteği,
  • Açık kaynak,
  • Rahat kod okunabilirliği,
  • Yorum satırları ile proje dokümantasyonu yaratılabiliyor olması,
  • Çoklu kullanım ( Multi-threading ) ve Eşzamanlılık ( Concurrency )

Büyüklü küçüklü birçok firma ya da ekip tarafından yazılmış olan ve “Neden Go” sorusuna cevap veren makalelere bu bağlantı üzerinden erişebilirsiniz.

Go Kurulumu

Go dilinin kurulumu da yine kendine yakışır derecede kolay yapılabiliyor. Downloads sayfasından kullanılan işletim sistemine uygun olan yükleyiciyi indirilebilir. Kurulum adımları için bu bağlantı ziyaret edilebilirsiniz.

IDE ve editör eklentileri için de bu sayfadaki işlemler yapılarak kısa bir süre içerisinde tüm işlemleri tamamlayabilir ve kodlamaya hazır hale gelebilirsiniz. Ben geliştirme için vscode kullanıyorum ve eklenti olarak da şu eklentiyi kullanıyorum, tavsiye ederim.

Merhaba Dünya!

Kurulum ve ide/editör işlemleri tamamlandıysa artık kodlama için hazırız! “go version” komutu terminalde çalıştırılarak, kurulumun durumu kontrol edilebilir. Her şey yolunda ise aşağıdakine benzer bir yanıt görüntüleniyor olmalı.

go_version

$GOROOT ve $GOPATH değişkenlerinin doğruluğunu teyit etmek için de “go env” komutu kullanılabilir.

go_env

Ortam değişkenlerimiz de tamamsa artık başlayalım. "main.go" isimli bir dosya yarattıktan sonra içine aşağıdaki kodları yazıyoruz.


package main

import "fmt"

func main() {
    fmt.Println("Merhaba Dünya!")
}

"Her Go programı paketlerden oluşur. Programlar main paketinde çalışmaya başlar.”


Yandaki örnekte de, istediğimiz herhangi bir veriyi konsola yazdırmamızı sağlayan “fmt” paketini import ettik ve Println fonksiyonu ile ekrana metnimizi yazdıracağız.

“go run main.go” komutunu kullanarak kodu çalıştırabiliriz. Ya da “go build main.go” komutu ile exe yarabilir ve “./main” komutu ile de çalıştırabiliriz.

go_run_main_go
go_build_main_go

Değişkenler ve Sabitler

Go dili, hızlı bir derleme sağlayabilmek için, ya değişkenlerin tanımlanırken tip belirtilmesini ya da en azından gelecek verinin tipinin ne olduğunu kendisi kestirebilmek ( Bu konuya yazının ilerleyen kısımlarında değineceğiz ) ister ve değişkene, değişkenin kendi tipi dışında tipte bir veri atmaz. Bu yüzden kaliteli bir uygulama geliştirmek için, değişken tanımlama işlemlerine özen göstererek belleği en efektif biçimde kullanımını sağlamak gerekmektedir.

Değişken ve sabit tanımlama ( Variables & Constants )

Go dilinde 2 farklı şekilde değişken tanımlanabilir. Bunlardan ilki "var" kelimesinin ardından değişken ismi ve tipi şeklinde olan, aşağıdaki örneklerdeki yöntemdir;

// Sabit
const sabit_deger string = "MDP Group"

// Değişken
// Başlangıç değeri olmadan tanımlama
var ad   string
var yas  uint
var kilo float32

// Başlangıç değeri ile tanımlama
var soyad string = "Okyay"
var yas uint = 31

// Tek bir "var" kelimesi ile tanımlama
var (
  sehir, ulke string 
  yil uint
)

var takim, cinsiyet string

var ad, yas, sehir = "Ahmet Buğra", 31 , 58

Yukarıda tip belirterek yapılan değişken tanımlamalarına birtakım örnekler verdik. Şimdi bir de tip vermeden fakat derleme esnasında tipin tahmin edilebileceği şekilde tanımlama yapacağız;

// Ayrı ayrı
ad := "Ahmet Buğra"
yas := 31

// Birlikte
ad, yas := "Ahmet Buğra", 31

Bu tür kullanımlar yapılırken, ya başlangıç değeri atanmalı ya da bir fonksiyondan dönen tipi belli bir değişkeni karşılayacak şekilde bir kullanım yapılmalıdır. ( Fonksiyonlar kısmında bu konuyu göreceğiz. )

Tip Dönüşümü ( Type Casting )

Tipler arası dönüşüm işlemi için, yeni tipi yazdıktan sonra parantez içerisinde değişkeni yazmamız yeterli;

var degisken1 int = 10
var degisken2 uint = uint(degisken1)

Diziler ( Arrays )

Go dilinde dizi tanımlarken dizinin boyutu ve tipi mutlaka belirtilmelidir.

// var degisken_adi [dizi_boyutu] degisken_tipi

var dizi [10] int

// Başlangıç değeri ile dizi tanımlama
dizi2 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
dizi3 := [3]string{"ahmet", "bugra", "okyay"}

// Çok boyutlu dizi tanımlama
var cok_boyutlu [5][5] int

Dilimler ( Slices )

Slice, diziler üzerinde yapılan bir soyutlama işlemidir. Sabit boyutlu diziler tanımlamak yerine, belirsiz boyutlu diziler gibi kullanabilmemize olanak sağlar.

Aynı zamanda bir dizinin belirli index aralıkları belirtilerek referanslı bir benzerini yaratabilmemizi de sağlar.

// Referans ile slice yaratma
renkler := [6]string{"Kırmızı", "Beyaz", "Mavi", "Sarı", "Mor", "Siyah"}  

var dilim []string = renkler[1:5]

Boş bir slice yaratırken de boyut verebiliriz fakat boyuttan daha fazla sayıda yeni eleman ekleyebiliriz. Boş bir slice yaratabilmek için make() fonksiyonunu kullanıyoruz.

// Boş bir slice yaratma
dilim2 := make([]int, 2)
dilim[0] = 10
dilim[1] = 20

dilim = append(dilim, 30)
dilim = append(dilim, 40, 50, 60, 70, 80, 90, 100, 110)

fmt.Printf("%#v:", dilim)

cap() fonksiyonu ile slice'ın kaç eleman alabileceğini, len() ile slice'ın boyutunu öğrenebilirsiniz.

Maps

Go, benzersiz anahtarları değerlere eşleyen map adlı bir başka önemli veri türü sağlar. Anahtar, sonradan bir değer almak için kullandığınız nesnedir. Bir anahtar ve bir değer verildiğinde, değeri bir harita nesnesinde saklayabilirsiniz. Şimdi bir örnek ile detaylandıralım;

var baskentler map[string]string // hem anahtarı hem değeri string tanımladık
baskentler = make(map[string]string)

// Map'e değer atıyoruz
baskentler["Fransa"] = "Paris"
baskentler["Italya"] = "Roma"
baskentler["Japonya"] = "Tokyo"
baskentler["Turkiye"] = "Ankara"

// Map'ten değer alıyoruz
fmt.Println("Türkiye'nin başkenti", baskentler["Turkiye"], " şehridir.")

baskent, durum := baskentler["Almanya"]

if durum {
	fmt.Println("Almanya'nın başkenti : ", baskent)
} else {
	fmt.Println("Başkent map içerisinde bulunmuyor!")
}

Yapılar ( Structures )

Diziler, yalnızca belirlenen tek bir türden değişkenler tutmamızı sağlar. Ancak yapılar, farklı farklı tiplerde verileri bir arada tutmamıza olanak sağlar.

type Kitap struct {
  ad       string
  yazar    string
  konu     string
  kitap_id int
}

var Kitap1 Kitap
var Kitap2 Kitap
 
// Kitap1 verileri girilir
Kitap1.ad = "Go Dili"
Kitap1.yazar = "Ahmet Buğra Okyay"
Kitap1.konu = "Go Dili Eğitimleri"
Kitap1.kitap_id = 235834

Kitap2.ad = "Düşmüş Melekler"
Kitap2.yazar = "Richard K. Morgan"
Kitap2.konu = "Bilim Kurgu"
Kitap2.kitap_id = 340001

İşaretçiler ( Pointers )

İşaretçiler, bir değişkenin bellekteki adresini tutan değişkenlerdir. Bir değişkenin bellekteki adresine erişmek için değişkenin önüne "&" işareti eklenir.

var degisken int = 10
fmt.Printf("Değişkenin adresi : %x\n", &a)
// Bu komut sonrası konsolda aşağıdaki gibi bir çıktı olacaktır
// Değişkenin adresi : c0000a00d0

İşaretçi değişken kullanarak, bir değişkenin değerine aşağıdaki gibi erişilir;

var degisken int = 20
var isaretci_degisken *int

isaretci_degisken = &degisken // Değişken adresi pointer değişkene aktarılır

fmt.Printf("Değişkenin adresi: %x\n", &degisken)

fmt.Printf("İşaretçi değişkenin değeri: %x\n", isaretci_degisken)

fmt.Printf("Adresteki değer: %d\n", *isaretci_degisken)

Ayrıca bir değişkene, bellekteki adresi kullanılarak da değer ataması yapılabilir;

a := 5
c := &a

// Değişkenin bellekteki adresi
fmt.Println(c)

// Bu adresteki değişkenin değeri
fmt.Println(*c)

a = 10

fmt.Println(*c)

*c = 20

fmt.Println(a)

// Bu kodun konsol çıktısı aşağıdakine benzer olacaktır
// 0xc00000e0d0
// 5
// 10
// 20

Döngüler

Go dilinde, tüm döngü yöntemlerinden ihtiyacımız olan hangisi ise sadece "for" kullanarak ihtiyacımızı giderebiliyoruz. Bu sebeple "while", "foreach" vs. gibi bazı özel döngü ifadeleri bulunmuyor.

For döngüsünün söz dizimi aşağıdaki gibidir;

for [koşul |  ( başlangıç; koşul; artış) | Aralık] {
   islem(s);
}

Yapıyı kısaca açıklayalım;

  • Koşul doğru olduğu sürece döngü dönmeye devam eder.
  • Başlangıç değeri, her bir turda artış değeri kadar değişir.
  • Eğer bir aralık değeri varsa, döngü aralıktaki eleman kadar döndürülür.
for_flow_diagram

Döngü kullanım örnekleri;

// En temel ve bilinen for döngü yapısı
for a := 0; a < 10; a++ {
  fmt.Printf("a : %d\n", a)
}

// While yapısına benzer, koşul tabanlı döngü yapısı
var a int
var b int = 10

for a < b {
  a++
  fmt.Printf("a : %d\n", a)
}

// Foreach yapısına benzer, her eleman için dönen döngü yapısı
sayilar:= [6]int{1, 2, 3, 5} 

for i,x:= range numbers {
  fmt.Printf("x = %d , index = %d\n", x,i)
}

Yukarıdaki örneklerin sonunda yer alan, foreach benzeri yapıların index değerine ihtiyaç her zaman bulunmayabilir. Bu benzer durumda fonksiyonlar kısmında da anlatıldığı üzre - indexi döndüren değişken "_" değeri ile karşılanır. Örnek olarak;

sayilar:= [6]int{1, 2, 3, 5} 

// Her turda, indexi gösteren bir değere ihtiyacımız yok ise "_" kullanılır
for _,x:= range numbers {
  fmt.Printf("x = %d\n", x)
}

Çoğu dilde olduğu gibi Go dilinde de,

  • Çağrıldığı yerde döngüyü kıran "break",
  • Çağrıldığında, döngü içerisinde kendisinden sonra gelen kodları atlayıp, döngüyü bir sonraki tura ( çevrime ) geçiren "continue",
  • Çağrıldığında, kodu belirlenen yere atlatak "goto" komutları mevcuttur.
// Continue Örneği
var a int = 10

for a < 20 {
  if a == 15 {
    a = a + 1;
    continue;
  }
  fmt.Printf("a: %d\n", a);
  a++;     
}  

// Break Örneği
var a int = 10

for a < 20 {
  fmt.Printf("a: %d\n", a);
  a++;
  if a > 15 {
    break;
  }
}

// Goto Örneği 1
fmt.Println(1)
goto End
fmt.Println(2)
End:
fmt.Println(3)

// Goto Örneği 2
var a int = 10

LOOP: for a < 20 {
  if a == 15 {
    a = a + 1
    goto LOOP
  }
  fmt.Printf("value of a: %d\n", a)
  a++     
}  

Goto yapısının akışı şu şekildedir;

go_goto

Son olarak iç içe döngü yapısına da ufak bir örnek verelim;

// Asal sayı bulan for örneği
for i := 2; i < 100; i++ {
  for j := 2; j <= (i/j); j++ {
    if(i%j==0) {
      break;
    }
  }
  if(j > (i/j)) {
    fmt.Printf("%d asal sayıdır\n", i);
  }
}  

Fonksiyonlar

Belirli bir görevi yerine getirmek amacıyla bir araya getirilmiş kod topluluklarına fonksiyon diyoruz. Her Go programında da main() işlevini gören en az bir fonksiyon bulunur.

Fonksiyonlar parametreler alarak ya da almadan ve parametre döndürerek ya da döndürmeden işlevlerini yapabilirler. Ancak, eğer parametre alan ve döndüren bir fonksiyon yazıyor iseniz, mutlaka her bir parametrenin tipini belirtmeniz gerekmektedir.

Örnek bir fonksiyon gövdesi şu şekildedir;

func fonksiyon_adi( [parametre listesi] ) [dönüş türleri]
{
   fonksiyon gövdesi
}
  • Parametre listesi isimli alana her bir değişken ismi ve tipiyle tanımlanmalıdır.
  • Parametre listesi n adet olabilir. Örneğin, int tipinde ve sayısı belli olmayacak şekilde parametre alabileceğini bilen fonksiyonlar yazabilirsiniz.
  • Dönüş türleri kısmına ise, fonksiyonun yanıt olarak döndüreceği verinin tipi belirtilmelidir.
// Belirli bir sayıda giriş parametresi olan ve bir dönüş değeri olan fonksiyon
func topla(num1, num2 int) int {
  return num1 + num2
}

fmt.Println(topla(1, 2))

// Parametre almaksızın birden fazla değer döndüren fonksiyon
func noParam() (int, int, int) {
  return 1, 2, 3
}

Go dilinde, bir değişken tanımlandı ise mutlaka kullanılmalıdır. Aynı zamanda bir fonksiyon, birden fazla değer döndürüyor ise, tüm değerler birer değişkenle karşılanmalıdır.

Fonksiyondan dönen değerlerden herhangi bir tanesine ihtiyaç yoksa, ihtiyaç olmayan parametreler "_" işareti ile karşılanmalıdır.

func noParam() (int, int, int) {
  return 1, 2, 3
}

_, iki, _ := noParam()
fmt.Println(iki)

Yukarıdaki fonksiyon, çağrıldığında 1,2,3 değerlerini döndürüyor fakat biz sadece 2'ye ihtiyaç duyuyorsak 1 ve 3 'ü "_" ile karşılamalıyız.

Sayısı belirli olmayan şekilde parametre alan fonksiyonları da yine bir örnekle açıklayalım;

func toplam(n ...int) int {
  sonuc := 0

  for _, v := range n {
    t += v
  }
  return t
}

Birden fazla giriş parametresi ve birden fazla dönüş parametresi olan bir örnek yapalım;

func Bol(bolunen, bolen float32) (sonuc float32, err error) {
  if bolen == 0 {
    return 0, errors.New("Bölen 0 olamaz")
  }

  sonuc = bolunen / bolen
  return sonuc, nil
}

Özyineleme ( Recursion )

Bir fonksiyonun, kendi kendisini çağırması suretiyle çalıştırılması işlemine özyineleme diyoruz.

func recursion() {
  recursion()
}

func main() {
  recursion()
}

Bu konuyu herhangi bir programlama dilinde en kolay açıklayabilecek yöntem faktöriyel hesabıdır. Biz de bir faktöriyel hesaplama fonksiyonu ile örnekleyelim;

func factorial(i int)int {
  if(i <= 1) {
    return 1
  }
  return i * factorial(i - 1)
}

func main() { 
  var i int = 15
  fmt.Printf("%d sayısının faktöriyeli %d sayısıdır", i, factorial(i))
}

Go dilinde, eğer bir fonksiyon başka bir paket içerisinden çağrılarak kullanılacaksa yani yazıldığı yerden export edilmesi gerekiyor ise, fonksiyon ismi büyük harfle başlamalıdır!


Detaylı bir eğitim için Türkçe Go Turu’nu ve Go Dokümantasyonu ’nu ziyaret edebilirsiniz. Sorularınız için bağlantıya tıklayarak bize ulaşabilirsiniz.


Benzer
Bloglar

Mailiniz başarıyla gönderilmiştir en kısa sürede sizinle iletişime geçilecektir.

Mesajınız ulaştırılamadı! Lütfen daha sonra tekrar deneyin.