Ders 04 / 18
Temel Tipler
Dört yazılmamışlık aynı çerçeveden geçiriliyor: tipsiz bir sabitin varsayılan tipi int oluyor, int8 sınırını aşan bir toplam -128'e sarıyor, çalışma zamanında toplanan iki float64 değeri 0.3'e eşit çıkmıyor, ve bir karakter değişmezinin tipi aslında int32 — rune yalnız bir takma ad. Sınırlayıcı ölçüm: örtük dönüşümün reddi sabitlerde geçerli değil.
İçindekiler
Önceki ders bir değişkenin tipini hep bir değerden çıkardı, ama tiplerin kendileri hiç
sorgulanmadı. Bu ders dört soruyu birden soruyor: bağlam vermeden yazılan bir sayının
varsayılan tipi ne, bir tam sayı sınırını aştığında ne oluyor, iki kayan noktalı değer
toplanıp karşılaştırıldığında beklediğimiz sonucu veriyor mu, ve rune dediğimiz tip aslında
nedir? Dördü de aynı üç sınıfla okunuyor — tanımlı, reddedildi, açıkta — ama bu dördü, kursun
merkezî on beş kalemlik çerçevesinin parçası değil; bu dersin kendi gözlemi.
Programlama Temelleri kursu (M01/K02) temel veri tiplerini, işleç önceliğini ve açık ile örtük dönüşümü Python’la kurdu. Bu kavramlar burada tekrar kurulmaz — farkı tek cümleyle yazmak yeter: orada tipler çalışma zamanında esnekti ve bir değer taştığında sonuç büyük tam sayıya genişliyordu; Go’da her tam sayı tipi sabit genişliktedir ve taşma genişlemeyle değil, sarmayla karşılanıyor. Bu ders, o genişliğin sınırında ne olduğunu ölçüyor.
Dört Yazılmamışlık, Tek Çerçeve
// temel.go — dort yazilmamislik: varsayilan tip, tasma, kayan nokta karsilastirmasi, rune
package main
import "fmt"
const Sayi = 5
func main() {
fmt.Printf("%-28s tip=%-8s deger=%v\n", "tipsiz sabitin varsayilani", fmt.Sprintf("%T", Sayi), Sayi)
var b int8 = 127
b++
fmt.Printf("%-28s tip=%-8s deger=%v\n", "tam sayi tasmasi", fmt.Sprintf("%T", b), b)
var x, y float64 = 0.1, 0.2
toplam := x + y
fmt.Printf("%-28s esit_mi=%-6v toplam=%v\n", "kayan nokta karsilastirmasi", toplam == 0.3, toplam)
c := 'A'
fmt.Printf("%-28s tip=%-8s deger=%v\n", "karakter degismezi", fmt.Sprintf("%T", c), c)
}
tipsiz sabitin varsayilani tip=int deger=5 tam sayi tasmasi tip=int8 deger=-128 kayan nokta karsilastirmasi esit_mi=false toplam=0.30000000000000004 karakter degismezi tip=int32 deger=65
DT10. Dört satır da aynı üç sınıfla (tanımlı, reddedildi, açıkta) okunuyor, ama bu on beş kalemlik çekirdeğin bir parçası değil — ortak çerçeveye eklenen yeni bir gözlem. Sıfır Değerler dersindeki 8/4/3 dağılımı bu dört satırı içermiyor.
Dört satırın üçü tanımlı, biri açıkta. Hiçbiri reddedilmiyor — dördü de geçerli Go kaynağıdır, dördü de derlenir ve çalışır; aralarındaki fark, sonucun kaynaktan mı, sabit bir kuraldan mı, yoksa çalışma zamanındaki temsilden mi geldiğidir. Sırayla okuyalım.
Tipsiz Sabitin Varsayılan Tipi
Sayi sabiti 5 değeriyle tanımlanırken hiçbir tip yazılmıyor — const Sayi = 5, const Sayi int = 5 değil. Go’da böyle bir sabit tipsizdir: kendi başına bir int, bir float64 ya
da başka bir tip değildir, yalnız bir sayı değeridir ve kullanıldığı yere göre biçimlenir. Sayi
fmt.Printfe bir interface{} argümanı olarak geçtiğinde, biçimlenecek somut bir tipe ihtiyaç
duyuluyor ve derleyici burada tipsiz sabitin varsayılan tipini devreye sokuyor: tam sayı
sabitleri için bu varsayılan inttir.
Bu, önceki derste ölçülen kısa bildirimden farklı bir mekanizma. Kısa bildirimde tip, atanan
değerin kendi tipinden çıkarılıyordu; burada değerin başlangıçta hiç tipi yok, tip yalnız
bir bağlam onu zorladığında beliriyor. Sayiyı bir float64 değişkenine atasaydık, aynı sabit
hiç dönüşüm yazmadan float64 olurdu — çünkü tipsiz bir sabit, hedefin izin verdiği her tipe
kendiliğinden uyar. Bu ayrım, dersin sınırlayıcı ölçümünde tekrar karşımıza çıkacak.
Varsayılan tip yalnız int ile sınırlı değil; her sabit türünün kendi varsayılanı var. Tırnak
içindeki bir sabit varsayılan olarak string, true/false varsayılan olarak bool, ondalık
yazılan bir sabit varsayılan olarak float64 alır. Bunların hiçbiri kaynakta yazılmıyor —
önceki derste kısa bildirimin bir değerden tip çıkardığını görmüştük; burada görülen,
değerin kendisinin de, hiçbir bağlam onu zorlamadığı sürece, kendi başına tipsiz kalmasıdır.
Tip, ancak bir yere yerleştirildiğinde ortaya çıkıyor.
Tam Sayı Taşması: Tanımlı Bir Sarma
int8 tipi 8 bit genişliğindedir ve işaretli olduğu için -128 ile 127 arasındaki değerleri
tutar. bye 127 verip bir arttırdığımızda, sonuç bu aralığın dışına taşıyor — ama program
çökmüyor, bir hata da üretmiyor. Sonuç -128 oluyor: sarma, aralığın üst ucundan alt ucuna
dönüyor.
DT11. Taşma burada kasıtlı üretiliyor. Go, tam sayı taşmasını bir hata saymıyor; ikiye tümleyen aritmetiğiyle tanımlı bir şekilde sarıyor. Bu, “yazılmayanın karşılığı” sorusuna üçüncü bir yanıt daha ekliyor: taşmayı önleyecek bir denetim yazılmadı, ama dil bu boşluğa rastgele bir değer değil, aritmetik olarak öngörülebilir bir değer koyuyor.
Sarmanın kuralı basit: int8in üst sınırını (127) bir aşan değer, alt sınırdan (-128) devam
eder. Bu, ikiye tümleyen gösterimin doğal bir sonucu — aynı bit örüntüsü, işaretli okunduğunda
negatif, işaretsiz okunduğunda pozitif bir sayıya karşılık gelir ve toplama işlemi bit
düzeyinde aynı kalır, yalnız yorumlanışı değişir. Derleyicinin burada hiçbir denetim eklememesi
bir ihmal değil — sarma davranışı, donanımın zaten yaptığı bir işlemi olduğu gibi görünür
kılıyor.
Sarmanın öngörülebilir olması, onu güvenli yapmıyor. -128 sonucu, bnin gerçek matematik
değerinin (128) hiçbir yaklaşık hâli değil, tamamen farklı bir sayı — üstelik işareti de
değişmiş. Bir hesaplama bu sınırı fark etmeden geçerse, sonucun yanlış olduğu hiçbir yerde
işaretlenmez; taşma, ilk derste ölçülen reddedilme ailesinden değil, ölçüm çekirdeğinin
tanımladığı tanımlı sınıfından. Dil bir şey koyuyor, ama koyduğu şey her zaman aritmetik
olarak beklenen şey değil.
Bu davranış, tam sayı tiplerinin genişliğinin neden kaynakta açıkça seçilmesi gerektiğini
gösteriyor. int8 yerine daha geniş bir tip (int32, int64) seçilseydi aynı toplama hiçbir
sarma üretmezdi; sarma, değerin kendisinden değil, seçilen genişlikten kaynaklanıyor. Bu da
tam sayı tipini seçmenin, önceki derste ölçülen kısa bildirimin dışında kalan bir karar
olduğunu gösteriyor — kısa bildirim b := int8(127) yazılmadıkça hiçbir zaman int8e varmaz,
varsayılan olarak her zaman inte düşer. int8 gibi dar bir tip yalnız var ile ya da açık
bir dönüşümle seçilebiliyor, ve bu seçim taşma sınırını da beraberinde getiriyor.
Kayan Nokta Karşılaştırması: Açıkta Kalan Sonuç
Üçüncü satır, x ve y adlı iki float64 değişkenini 0.1 ve 0.2 ile ilklendirip
topluyor ve toplamı 0.3e eşitliyor. Sonuç false. Toplamın kendisi de 0.3 değil,
0.30000000000000004.
DT12. Burada yazdırılan ondalık basamaklar bir ortama bağlı veri değildir — bu, IEEE 754
ikili kesirli gösteriminin belirlenimci sonucudur ve her koşumda aynı basamaklara varır.
0.1 ve 0.2 ikilik tabanda sonlu bir kesir olarak yazılamaz, tıpkı 1/3ün onluk tabanda
yazılamaması gibi; her ikisi de en yakın float64 değerine yuvarlanır ve toplamları, gerçek
matematiksel toplamın kendisi değil, o iki yuvarlanmış değerin toplamıdır. Bu yuvarlama Go’ya
özgü değil — float64 temsili dilin belirtimi değil, IEEE 754 standardının kendisi ve dil bu
temsili olduğu gibi devralıyor.
Bu satır açıkta sınıfına giriyor, çünkü dil burada hiçbir şey koymuyor ve derleyici de
hiçbir şeyi reddetmiyor. == işleci geçerli bir karşılaştırmadır, derlenir ve çalışır; yalnız
sonucun “matematiksel olarak beklenen” olup olmadığı çalışma zamanındaki temsile bağlı kalıyor.
Kod, ne yaptığını söylüyor — iki float64 değerini eşitliyor — ama okuyanın kafasındaki
“0.1 artı 0.2, 0.3 eder” beklentisiyle örtüşüp örtüşmeyeceğini önceden söylemiyor.
Burada ilginç bir ayrıntı var: Sayi sabitinde gördüğümüz tipsiz sabit mekanizması, bu satırı
da etkileyebilirdi. 0.1 + 0.2 == 0.3 ifadesi, üç tarafı da tipsiz sabit olarak yazılsaydı,
derleyici bu toplamı derleme zamanında, keyfi hassasiyetle hesaplardı ve sonuç gerçekten
true çıkardı — çünkü sabit aritmetiği ikili yuvarlamaya değil, tam kesir hesabına dayanıyor.
Açıkta kalan davranış, sabitlerin değil, çalışma zamanı değişkenlerinin özelliği: x ve
y birer değişken olduğu için toplamaları çalışma zamanında, önceden yuvarlanmış ikili
değerlerle yapılıyor.
Karakterin Tam Sayı Olması
Dördüncü satırda c := 'A' yazılıyor. Tek tırnak içindeki bir karakter, Go’da bir rune
değişmezidir ve kısa bildirimle tipi çıkarıldığında int32 çıkıyor — rune diye ayrı bir
tip görünmüyor, çünkü rune, dilin belirtiminde int32nin bir takma adından başka bir
şey değil. %T her ikisini de aynı adla, int32 diye yazdırıyor.
Değerin kendisi de bunu doğruluyor: çıktıda deger=65 yazıyor, A değil. fmt.Printfin
%v biçim doğrulayıcısı, bir değeri kendi tipine göre yazdırıyor ve int32nin varsayılan
yazımı ondalık bir sayıdır — karakteri görmek isteseydik %c yazmamız gerekirdi. Bu, “rune bir
tam sayıdır” iddiasının yalnız tip sisteminde değil, biçimlendirmede de göründüğü anlamına
geliyor: dil, bir runeu özel bir “karakter” kutusunda tutmuyor, sıradan bir sayı gibi
davranıyor ve okunabilir bir karakter olarak görünmesi tamamen biçimlendirme çağrısının
tercihi.
Bu dördüncü satır ilk satırla aynı aileden: ikisi de bir değişmezin varsayılan tipini
ölçüyor. Aradaki fark, ilkinde varsayılan tip intken, karakter değişmezinde varsayılan tipin
doğrudan runeun kendisi (int32) olması. 'A' yazıldığında dil, bunun bir kod noktası
olduğunu ve kod noktalarının tam sayı olarak temsil edildiğini biliyor; 65 yazmakla 'A'
yazmak arasındaki fark bu yüzden yalnız okunurlüktedir — ikisi de aynı tipte, aynı değerde
sonuçlanıyor.
Bu, rune sözcüğünün neden bu kursta hiç çevrilmediğini de açıklıyor: rune, Go’nun kendi
anahtar sözcüklerinden biri değil, standart kitaplığın tanımladığı bir tip takma adıdır —
tıpkı byteın uint8in takma adı olması gibi. Bir takma ad, kaynakta yazıldığında derleyici
tarafından hemen gerçek adına çözülüyor; çalışma zamanında rune diye ayrı bir tip
etiketi taşınmıyor, %T bunu doğruluyor. Bu ders boyunca “karakter” sözcüğünü ölçülmüş bir
şey için kullanmaktan kaçınıyoruz, çünkü tam olarak ölçtüğümüz şey, bir karakterin aslında bir
tam sayı olduğudur — kursun bileşik tipler konusundaki Dizgiler ve Rune’lar dersinde bu ayrım,
bir dizginin neden karakter değil bayt dizisi olduğuyla birlikte derinleşecek.
Sınırlayıcı Ölçüm: Örtük Dönüşümün Reddi Sabitlerde Geçerli Değil
İlk derste ölçülen dört retten biri, örtük tip dönüşümünün reddiydi: bir int32 değişkeni bir
int64 değişkenine dönüşüm yazılmadan atanamıyordu. Bu kural, tipsiz sabitler için
geçerli değil. Ölçmek için ilk dersteki derleyici çağrısına dönüyoruz.
// derleyici.go — derleyiciyi program icinden cagiran yardimci
package main
import (
"os"
"os/exec"
"path/filepath"
)
func derlenirMi(kaynak string) bool {
dizin, err := os.MkdirTemp("", "sinama")
if err != nil {
return false
}
defer os.RemoveAll(dizin)
os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
cmd := exec.Command("go", "build", "./...")
cmd.Dir = dizin
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
return cmd.Run() == nil
}
func sinif(ok bool) string {
if ok {
return "derlendi"
}
return "derlenmedi"
}
// sinirlayici.go — ortuk donusumun reddi sabitlerde gecerli degil
package main
import "fmt"
func main() {
sabitUyum := `package sinama
func F() { var hedef int64 = 5; _ = hedef }`
degiskenUyumsuzlugu := `package sinama
func F() { var kaynak int32 = 5; var hedef int64 = kaynak; _ = hedef }`
fmt.Printf("%-32s %s\n", "tipsiz sabit hedefe uyuyor", sinif(derlenirMi(sabitUyum)))
fmt.Printf("%-32s %s\n", "ayni degeri tasiyan degisken", sinif(derlenirMi(degiskenUyumsuzlugu)))
}
tipsiz sabit hedefe uyuyor derlendi ayni degeri tasiyan degisken derlenmedi
İki satır da aynı sayısal değeri (5) taşıyor ve aynı hedef tipe (int64) atanıyor. Birinci
satırda kaynak bir tipsiz sabittir — 5 yazımı hiçbir tipe bağlı değil, int64e doğrudan
sığar ve derleyici bunu bir dönüşüm değil bir ilk değer ataması sayıyor. İkinci satırda aynı
5 değeri önce bir int32 değişkenine atanıyor; bu noktadan sonra 5 artık tipsiz değil,
int32 tipinde somutlaşmış bir değerdir ve int64e geçişi ilk derste ölçülen örtük
dönüşüm reddine takılıyor.
Bu, “aynı değer” ile “aynı tip” arasındaki farkı gösteriyor. Derleyicinin ret kararı, taşınan sayısal değere değil, kaynağın tipine bakıyor. Bir tipsiz sabit, somutlaşana kadar hiçbir genişlik taşımaz; bir değişken ise tanımlandığı anda belirli bir genişliğe kilitlenir. İlk derste ölçülen kural bu yüzden yeniden yazılmalı: örtük dönüşüm, iki tipli değer arasında reddediliyor; tipsiz bir sabitin hedefe uyması dönüşüm değil, sabitin doğasının bir sonucu.
Bu sınır, dördü olsaydı diye bir soruyla da sınanabilir: 5 yerine 500 yazıp hedefi int8
yapsaydık ne olurdu? O durumda tipsiz sabit hedefe sığmadığı için derleme yine reddederdi
— ama bu, bir dönüşüm reddi değil, bir taşma reddidir: derleyici, bir sabitin değerini
derleme zamanında bildiği için, hedefe sığmayacağını önceden görüp kaynağı hiç derlemez. Bu,
ikinci bölümde ölçülen çalışma zamanı taşmasından (int8’in 127’yi aşması) tamamen farklı bir
mekanizma: orada taşma çalışırken sarıyordu, burada taşma derlenirken reddediliyor,
çünkü sabitin değeri kaynakta zaten yazılı ve derleyici onu hesaplamak için programı
çalıştırmasına gerek yok.
Dört ölçüm de aynı ayrımı farklı açılardan gösteriyor: bir sabitin tipi kaynakta yazılmadığı
sürece askıda kalır ve ancak bir bağlam onu somutlaştırdığında kesinleşir; bir değişkenin
tipi ise tanımlandığı anda kesinleşir ve o andan sonra genişliğiyle birlikte taşınır. İlk
satırdaki varsayılan tip, dördüncü satırdaki karakterin tam sayı oluşu ve sınırlayıcı ölçümdeki
sabit-değişken ayrımı — üçü de bu askıda kalma durumunun farklı görünümleri. Yalnız ikinci ve
üçüncü satır bu aileden ayrılıyor: onlar askıda kalmayla değil, zaten somutlaşmış bir tipin
sınırlarıyla (int8in genişliği, float64ün ikilik temsili) ilgili.
Özet
- Tipsiz bir tam sayı sabitinin bağlam vermeyen bir kullanımda varsayılan tipi
inttir. int8sınırını aşan bir toplama, hata üretmeden -128’e sarıyor — taşma tanımlı, ama aritmetik olarak beklenen değil.- Çalışma zamanında toplanan iki
float64değeri, matematiksel olarak eşit görünen bir sabitle karşılaştırıldığındafalseverebiliyor; bu sonuç açıkta kalıyor. rune,int32nin bir takma adıdır; bir karakter değişmezinin varsayılan tipi doğrudan budur.- Örtük tip dönüşümünün reddi yalnız tipli değerler arasında geçerli — tipsiz bir sabit, hedef tipe kendiliğinden uyuyor.
Sonraki Adım
Bu derste ölçülen dört yazılmamışlığın üçü tanımlı bir karşılık buldu, biri açıkta kaldı. Ama
hiçbiri, bir değişkenin hiç değer almadığı durumu sormadı — var b int8 yazıp hiç değer
vermeseydik, b ne olurdu? Sıradaki ders bu soruyu kursun merkezine taşıyor: on beş ayrı
yazılmamışlığı tek bir çerçevede topluyor ve sekizinin tanımlı, dördünün reddedilmiş, üçünün
açıkta olduğunu ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.