Go'nun temel veri tipleri şunlardır: işaretli tam sayılar (int8, int16, int32, int64, int), işaretsiz tam sayılar (uint8 ile uint64 arası, uint, uintptr), kayan noktalı sayılar (float32, float64), karmaşık sayılar (complex64, complex128), bool ve string. byte, uint8'in; rune ise int32'nin takma adıdır. Farklı sayısal tipler bir ifadede karıştırılamaz; dönüşüm her zaman T(x) biçiminde açıkça yazılır.

Bu yazıda her tip ailesinin aralığını, taşma ve bölme davranışını, kayan noktalı sayıların hassasiyet sınırlarını, karmaşık sayıları, bool'un katılığını, byte ile rune'un string ile ilişkisini ve dönüşüm kurallarını spesifikasyona dayanarak, Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Serinin diğer yazıları için Go öğrenme rehberi sayfasına bakabilirsiniz.

Tam sayı tipleri ve boyutları

Tam sayı tipleri ve boyutları: int8, int16, int32 (rune), int64

Spesifikasyon, mimariden bağımsız tam sayı tiplerinin bit genişliğini ve aralığını kesin olarak tanımlar. n bitlik bir tam sayının değeri ikiye tümleyen (two's complement) aritmetiğiyle temsil edilir. Bunların yanında boyutu uygulamaya bağlı üç tip vardır: uint 32 ya da 64 bittir, int onunla aynı boyuttadır, uintptr ise bir işaretçi değerinin bitlerini tutacak kadar büyük işaretsiz bir tam sayıdır.

TipBitAralık
int88-128 ile 127
int1616-32768 ile 32767
int32 (rune)32-2147483648 ile 2147483647
int6464-9223372036854775808 ile 9223372036854775807
uint8 (byte)80 ile 255
uint16160 ile 65535
uint32320 ile 4294967295
uint64640 ile 18446744073709551615
int, uint32 veya 64platforma bağlı

FAQ'ya göre derleyiciler 32 bit makinelerde 32 bit, 64 bit makinelerde 64 bit int kullanır. Önemli olan nokta şudur: int ile int64, aynı makinede aynı boyutta olsalar bile farklı tiplerdir. Spesifikasyon, taşınabilirlik sorunlarını önlemek için takma adlar dışındaki tüm sayısal tiplerin birbirinden ayrı olduğunu belirtir. Bir değerin kaç bit tuttuğu sizin için önemliyse boyutu açık bir tip kullanın; indis ve sayaç gibi genel işler için int yeterlidir. math paketi sınır değerleri sabit olarak sunar, strconv.IntSize ise int'in bu platformdaki bit sayısını verir.

go
package main

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	fmt.Println(math.MinInt8, math.MaxInt8)    // -128 127
	fmt.Println(math.MaxUint8, math.MaxUint16) // 255 65535
	fmt.Println(math.MaxInt32)                 // 2147483647
	fmt.Println(math.MaxInt64)                 // 9223372036854775807
	fmt.Println(strconv.IntSize)               // bu makinede 64

	sayi := 1_000_000
	ikili := 0b1010
	sekizli := 0o755
	onaltili := 0xFF
	fmt.Println(sayi, ikili, sekizli, onaltili) // 1000000 10 493 255
}

Tam sayı literal'leri onluk, ikilik (0b), sekizlik (0o ya da başta 0) ve onaltılık (0x) yazılabilir. Okunabilirlik için rakamların arasına alt çizgi koyabilirsiniz: 1_000_000. Spesifikasyonun dil sürümleri bölümüne göre 0b ve 0o önekleri ile alt çizgiyle gruplama Go 1.13'te geldi. Alt çizgi yalnızca ardışık rakamlar arasında ya da önekten sonra yer alabilir; 42_ ve 4__2 geçersizdir, _42 ise sayı değil bir tanımlayıcıdır.

Taşma ve tam sayı bölmesi

Go'da tam sayı taşması çalışma zamanı hatası üretmez. Spesifikasyona göre işaretsiz tiplerde +, -, * ve << işlemleri 2 üzeri n modunda (n tipin bit genişliğidir) hesaplanır, yani üst bitler atılır ve değer başa sarar; programlar bu davranışa güvenebilir. İşaretli tiplerde de taşma yasaldır ve sonuç ikiye tümleyen temsiline göre belirlidir; derleyici taşmanın hiç olmayacağını varsayarak optimizasyon yapamaz, örneğin x < x + 1 ifadesinin her zaman doğru olduğunu varsayamaz.

Tam sayı bölmesi sıfıra doğru kırpılır: 7/2 3, -7/2 -3 verir. Kalan operatörü % bölünenin işaretini taşır, bu yüzden -7 % 3 sonucu -1'dir. Sabit bir bölen sıfır olamaz (derleme hatası); çalışma zamanında sıfıra tam sayı bölmesi panic üretir. Ondalıklı sonuç istiyorsanız işlenenleri önce float64'e çevirmeniz gerekir.

go
package main

import "fmt"

func main() {
	var u uint8 = 255
	u++
	fmt.Println(u) // 0

	var i int8 = 127
	i++
	fmt.Println(i) // -128

	fmt.Println(7/2, -7/2, 7%3, -7%3) // 3 -3 1 -1
	fmt.Println(7.0 / 2)              // 3.5

	x := 7
	y := 2
	fmt.Println(float64(x) / float64(y)) // 3.5
}

Kayan noktalı sayılar: float32 ve float64

float32 ve float64, IEEE 754 standardındaki 32 ve 64 bitlik sayıları temsil eder. FAQ'ya göre Go'da boyutsuz bir float tipi bilinçli olarak yoktur, çünkü programcının hassasiyetin farkında olması istenir; tipsiz kayan noktalı sabitlerin varsayılan tipi float64'tür. Bu yüzden x := 3.0 bir float64 değişkeni oluşturur; float32 istiyorsanız tipi açıkça yazarsınız: var f float32 = 3.0 ya da f := float32(3.0).

Kayan noktalı sayılar ikili tabanda saklandığı için 0.1 gibi ondalık değerler tam temsil edilemez. Aşağıdaki örnekte iki float64 değişkenin toplamı 0.3'e eşit çıkmıyor. Aynı karşılaştırma sabitlerle yapıldığında ise sonuç doğru, çünkü sabit ifadeler derleme zamanında keyfi hassasiyetle tam hesaplanır. float32'nin hassasiyeti daha da sınırlıdır: 16777216 değerine 1 eklemek sonucu değiştirmez. Değişkenlerle sıfıra bölme panic üretmez; sonuç IEEE 754'e göre sonsuz ya da NaN olur.

go
package main

import (
	"fmt"
	"math"
)

func main() {
	a := 0.1
	b := 0.2
	fmt.Println(a + b)          // 0.30000000000000004
	fmt.Println(a+b == 0.3)     // false
	fmt.Println(0.1+0.2 == 0.3) // true (sabit ifade, tam hesaplanır)

	var f32 float32 = 16777216
	fmt.Println(f32+1 == f32) // true

	sifir := 0.0
	fmt.Println(1/sifir, -1/sifir, math.IsNaN(sifir/sifir)) // +Inf -Inf true

	g, h := 3.99, -3.99
	fmt.Println(int(g), int(h))     // 3 -3
	fmt.Println(int(math.Round(g))) // 4
}

Para gibi tam doğruluk gereken hesaplarda kayan noktalı sayılarla eşitlik karşılaştırması yapmayın; tutarları en küçük birim cinsinden tam sayı olarak tutmak ya da math/big paketini kullanmak daha güvenli yaklaşımlardır. Kayan noktadan tam sayıya dönüşümün davranışını aşağıdaki dönüşüm bölümünde ayrıca ele alıyoruz.

Karmaşık sayılar: complex64 ve complex128

Karmaşık sayılar: complex64 ve complex128

Go, karmaşık sayıları yerleşik tip olarak destekler. complex128'in gerçel ve sanal kısımları float64, complex64'ünkiler float32'dir. Yerleşik complex fonksiyonu iki kısımdan karmaşık sayı oluşturur, real ve imag kısımları geri verir. 2i gibi sanal literal'ler doğrudan yazılabilir. Mutlak değer gibi işlemler math/cmplx paketindedir.

go
package main

import (
	"fmt"
	"math/cmplx"
)

func main() {
	z := complex(3, 4)
	fmt.Println(z)                // (3+4i)
	fmt.Println(real(z), imag(z)) // 3 4
	fmt.Println(cmplx.Abs(z))     // 5
	fmt.Printf("%T\n", z)         // complex128
	w := 2i
	fmt.Println(w * w) // (-4+0i)
}

bool: yalnızca true ve false

bool tipi yalnızca önceden tanımlı true ve false sabitlerini tutar. Go'da sayılar ya da nil, bool yerine kullanılamaz; C veya JavaScript'teki gibi sıfır olmayan sayının doğru sayılması yoktur. if koşulunun mutlaka bool bir ifade olması gerekir. Mantıksal operatörler &&, || ve !'dir ve kısa devre değerlendirmesi yapar.

go
package main

import "fmt"

func main() {
	n := 1
	if n {
		fmt.Println("doğru")
	}
}
text
# command-line-arguments
./main.go:7:5: non-boolean condition in if statement

Doğru yazım if n != 0 biçimindedir. Bu katılık, yanlışlıkla yapılan atama ve karşılaştırma karışıklıklarını derleme aşamasında yakalar.

string, byte ve rune

Spesifikasyona göre bir string değeri bir bayt dizisidir ve değiştirilemez; oluşturulduktan sonra içeriği değiştirilemez. len(s) karakter sayısını değil bayt sayısını verir ve s[i] ifadesi i'inci baytı döndürür. Go kaynak kodu UTF-8 olduğu için string literal'leri de UTF-8 kodludur. Türkçedeki ö, ş, ğ gibi harfler UTF-8'de iki bayt tutar; bu yüzden aşağıdaki iki harfli metnin uzunluğu 3 çıkar.

byte tek bir baytı, rune ise tek bir Unicode kod noktasını temsil eder. Tek tırnaklı 'ş' bir rune sabitidir ve değeri harfin Unicode numarasıdır (351). Bir string'i []byte'a çevirmek baytlarını, []rune'a çevirmek kod noktalarını verir. Ters tırnaklı ham string'lerde (raw string) ters eğik çizginin özel anlamı yoktur; Windows yolları ya da düzenli ifadeler için kullanışlıdır.

go
package main

import "fmt"

func main() {
	s := "Gö"
	fmt.Println(len(s))         // 3
	fmt.Println(s[1])           // 195
	fmt.Println([]byte(s))      // [71 195 182]
	fmt.Println([]rune(s))      // [71 246]
	fmt.Println(len([]rune(s))) // 2

	var b byte = 'A'
	var r rune = 'ş'
	fmt.Println(b, r)                       // 65 351
	fmt.Printf("%c %c %T %T\n", b, r, b, r) // A ş uint8 int32
	fmt.Println(string(r))                  // ş

	ham := `C:\yol\n`
	fmt.Println(ham, len(ham)) // C:\yol\n 8
}

String'leri karakter karakter dolaşmak, strings ve unicode/utf8 paketleri ve Türkçe büyük-küçük harf dönüşümünün tuzakları string, rune, byte ve UTF-8 yazısının konusu; burada yalnızca tip düzeyindeki ilişkiyi gösteriyoruz.

Tip dönüşümü: T(x)

Tip dönüşümü: T(x): Tam sayıdan tam sayıya, Kayan noktadan tam sayıya, Tam sayı ya da kayan noktadan kayan noktaya, Sabitler

Go örtük sayısal dönüşüm yapmaz. FAQ'nun açıkladığı gerekçe, C'deki otomatik dönüşümlerin getirdiği kolaylığın yarattığı karışıklığa değmemesidir: bir ifadenin işaretsiz olup olmadığı, ne kadar büyük olduğu ve taşıp taşmadığı belirsizleşir. Bu yüzden int ile int64'ü toplamak bile açık bir dönüşüm gerektirir:

go
package main

import "fmt"

func main() {
	var a int = 10
	var b int64 = 20
	fmt.Println(a + b)
}
text
# command-line-arguments
./main.go:8:14: invalid operation: a + b (mismatched types int and int64)

Dönüşüm T(x) biçiminde yazılır: int64(a) + b ya da a + int(b). Spesifikasyon sayısal dönüşümlerin kurallarını kesin olarak tanımlar ve bunlar sessizce veri kaybedebilir:

  • Tam sayıdan tam sayıya: değer önce işaretliyse işaret genişletmesi, işaretsizse sıfır genişletmesiyle büyütülür, sonra hedef tipin boyutuna kırpılır. Sonuç her zaman geçerli bir değerdir ve taşma bildirilmez; 300 değerindeki bir int64, int8'e çevrildiğinde 44 olur.
  • Kayan noktadan tam sayıya: kesirli kısım atılır, yani sıfıra doğru kırpılır. 3.99 değeri 3, -3.99 değeri -3 olur. Yuvarlama istiyorsanız önce math.Round kullanın.
  • Tam sayı ya da kayan noktadan kayan noktaya: sonuç hedef tipin hassasiyetine yuvarlanır.
  • Sabitler: bir sabit, ancak hedef tipte temsil edilebiliyorsa dönüştürülebilir. Bu kontrol derleme zamanında yapılır.
go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := uint16(0x10F0)
	fmt.Printf("%#x\n", uint32(int8(v))) // 0xfffffff0

	var buyuk int64 = 300
	fmt.Println(int8(buyuk)) // 44

	n := 65
	fmt.Println(strconv.Itoa(n)) // 65
	fmt.Println(string(rune(n))) // A

	aktif := true
	fmt.Println(!aktif, aktif && false, aktif || false) // false false true
}

Sabit dönüşümünün derleme zamanındaki katılığı bazen şaşırtır: değişkenle yazıldığında sorunsuz kırpılan int(g), sabitle yazıldığında derlenmez, çünkü 3.99 bir int olarak temsil edilemez:

text
# command-line-arguments
./main.go:6:18: cannot convert 3.99 (untyped float constant) to type int

Mini örnek: not ortalaması

Tam sayı ile kayan noktalı sayı arasındaki ayrım, en basit hesaplarda bile kendini gösterir. Aşağıdaki fonksiyon bir not listesinin ortalamasını hesaplıyor. Notlar ve toplam int, ama ortalamanın kesirli olması gerekiyor; bu yüzden bölmeden önce hem toplam hem eleman sayısı float64'e çevriliyor. Boş liste için sıfıra bölmeyi önlemek amacıyla erken dönüş yapılıyor. main içindeki son üç satır ise aynı hesabın üç farklı yazımını karşılaştırıyor.

go
package main

import "fmt"

func ortalama(notlar []int) float64 {
	if len(notlar) == 0 {
		return 0
	}
	toplam := 0
	for _, n := range notlar {
		toplam += n
	}
	return float64(toplam) / float64(len(notlar))
}

func main() {
	notlar := []int{70, 85, 90}
	fmt.Println(ortalama(notlar))          // 81.66666666666667
	fmt.Printf("%.2f\n", ortalama(notlar)) // 81.67
	fmt.Println(70 + 85 + 90/3)            // 185 (öncelik hatası)
	fmt.Println((70 + 85 + 90) / 3)        // 81 (tam sayı bölmesi)
	fmt.Println(float64(70+85+90) / 3)     // 81.66666666666667
}

İlk karşılaştırmada parantez unutulduğu için yalnızca 90 sayısı 3'e bölündü; çarpma ve bölmenin toplamadan önce yapılması bir tip sorunu değil, operatör önceliği sorunudur. İkinci satırda parantez doğru ama tüm işlenenler tipsiz tam sayı sabiti olduğu için tam sayı bölmesi yapıldı ve sonuç 81'e kırpıldı. Üçüncü satırda toplam önce float64'e çevrildiği için bölme kayan noktalı yapıldı. %.2f biçimi ise yalnızca gösterimi yuvarlar, değerin kendisini değiştirmez; biçimlendirme fiilleri fmt ile Printf biçimlendirme yazısında.

Sık yapılan hatalar

1. Sayıyı string(n) ile metne çevirmeye çalışmak. Spesifikasyon, tarihsel nedenlerle bir tam sayının string'e dönüştürülmesine izin verir, ama sonuç rakamlar değil o numaralı Unicode karakteridir: string(65) metni "A" olur. Spesifikasyon bu dönüşümün ileride dilden kaldırılabileceğini not eder; go vet de bu kullanımı yakalar. Program derlenir ve çalışır ama vet şu uyarıyı verir:

text
./main.go:7:14: conversion from int to string yields a string of one rune, not a string of digits

Sayıyı rakamlara çevirmek için strconv.Itoa ya da strconv.FormatInt kullanın; ayrıntılar strconv ile sayı ve string dönüşümü yazısında. Bilerek karakter üretiyorsanız string(rune(n)) yazarak niyetinizi açık edin.

2. Taşmanın panic üreteceğini sanmak. Tam sayı taşması sessizce başa sarar. Sayaçlarda ve boyut hesaplarında yeterince geniş bir tip seçin; dış kaynaktan gelen sayıları dönüştürmeden önce aralığını kontrol edin.

3. Kayan noktalı sayıları == ile karşılaştırmak. 0.1 + 0.2 örneğindeki gibi küçük yuvarlama farkları eşitliği bozar. Ya bir tolerans değeriyle karşılaştırın ya da tam sayı aritmetiğine geçin.

4. len(s) değerini harf sayısı sanmak. Türkçe karakter içeren metinlerde bayt sayısı harf sayısından büyüktür. Harf sayısı için utf8.RuneCountInString ya da len([]rune(s)) kullanılır.

5. int ile int64'ü aynı tip sanmak. 64 bit bir makinede ikisi aynı boyutta olsa da farklı tiplerdir; birbirine atanamaz ve aynı ifadede karıştırılamaz.

Sık Sorulan Sorular

Hangi tam sayı tipini kullanmalıyım?

Özel bir neden yoksa int kullanın; yerleşik len ve cap da int döndürür. Dosya biçimi, ağ protokolü ya da veritabanı alanı gibi bit genişliğinin önemli olduğu yerlerde int32, uint64 gibi boyutu açık tipleri tercih edin.

byte ile uint8 arasında fark var mı?

Hayır. byte, uint8'in takma adıdır ve ikisi aynı tiptir; rune ile int32 de öyle. Takma adlar yalnızca niyeti belirtir: byte ham veri baytını, rune bir Unicode kod noktasını anlatır.

Go neden otomatik sayı dönüşümü yapmıyor?

FAQ'ya göre örtük dönüşümler bir ifadenin işaretini, boyutunu ve taşma davranışını belirsizleştirir ve derleyiciyi karmaşıklaştırır. Go, açık dönüşüm yazma maliyetini taşınabilir ve anlaşılır kod için kabul eder; tipsiz sabitler bu katılığı büyük ölçüde yumuşatır.

Sıfıra bölme her zaman panic mi üretir?

Çalışma zamanında tam sayıyı sıfıra bölmek panic üretir; sabit bir bölen sıfırsa kod hiç derlenmez. Kayan noktalı sayılarda ise davranış IEEE 754'e bırakılmıştır; bu örneklerde sonuç +Inf, -Inf ya da NaN oldu.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Go öğrenme rehberi · Go değişkenler ve sabitler · Go string, rune, byte ve UTF-8 · Go strconv · Go fmt ve Printf · Go if ve switch