İçeriğe geç
academia.sh

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 sabittir5 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.
  • int8 sı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 float64 değeri, matematiksel olarak eşit görünen bir sabitle karşılaştırıldığında false verebiliyor; 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat