Go'da biçimlendirilmiş çıktı fmt paketiyle üretilir. Printf ailesi bir biçim dizesi alır ve dizenin içindeki fiiller (verb) her argümanın nasıl yazılacağını belirler: %v varsayılan biçim, %+v alan adlarıyla struct, %#v Go sözdizimi, %T tip, %d tamsayı, %s metin, %q tırnaklı metin, %x onaltılık gösterim. Genişlik ve hassasiyet fiilin önüne yazılır; %8.2f en az 8 karakterlik alana iki ondalık basamakla sayı yazar.

Bu yazıda dört yazdırma ailesini, fiil tablosunu, bayrakları, genişlik ve hassasiyet kurallarını, Stringer ile error arayüzlerinin çıktıyı nasıl değiştirdiğini, hatalı biçim dizelerinin ürettiği %! işaretlerini ve go vet aracının bu hataları derlemeden önce nasıl yakaladığını anlatıyoruz. Tüm örnekler Go 1.27.3 ile çalıştırıldı ve çıktılar programların gerçek çıktısıdır. Dile yeni başladıysanız sırayı görmek için Go öğrenme rehberine göz atabilirsiniz.

Dört yazdırma ailesi: Print, Sprint, Fprint ve Append

Dört yazdırma ailesi: Print, Sprint, Fprint ve Append: Print, Println, Printf, Sprint, Sprintln, Sprintf, Fprint, Fprintln, Fprintf, Append, Appendln, Appendf

fmt paketindeki yazdırma fonksiyonları çıktının gittiği yere göre dört aileye ayrılır. Adın başı hedefi, sonu ise biçimlendirme biçimini söyler. Print, Println ve Printf standart çıktıya (os.Stdout) yazar. Sprint ailesi sonucu string olarak döndürür. Fprint ailesi herhangi bir io.Writer değerine yazar; bu bir dosya, ağ bağlantısı, HTTP yanıtı ya da strings.Builder olabilir. Append ailesi ise sonucu var olan bir bayt dilimine ekler ve genişlemiş dilimi döndürür.

AileHedefDönüş değeri
Print, Println, Printfos.Stdoutyazılan bayt sayısı ve hata
Sprint, Sprintln, Sprintfyeni stringstring
Fprint, Fprintln, Fprintfverilen io.Writeryazılan bayt sayısı ve hata
Append, Appendln, Appendfverilen []bytegenişlemiş []byte

Adın sonu da üç davranıştan birini seçer. Print biçimi olmayan sürümdür: her argümanı varsayılan biçimiyle yazar ve iki argümanın arasına yalnızca ikisi de string değilse boşluk koyar. Println her argümanın arasına her zaman boşluk koyar ve sona satır sonu ekler. Printf ise biçim dizesini izler ve kendiliğinden satır sonu eklemez. Bu küçük fark, ilk günlerde en çok şaşırtan davranışlardan biridir:

go
package main

import "fmt"

func main() {
	fmt.Print("a", "b", 1, 2, "c", "\n")
	fmt.Println("a", "b", 1, 2, "c")
	fmt.Print(3, 4, "\n")
	s := fmt.Sprint("x", 5, 6)
	fmt.Println(s)
	m := map[string]int{"elma": 3, "armut": 5, "ayva": 1}
	fmt.Println(m)
	var p *int
	fmt.Println(p, []string(nil), map[string]int(nil))
}

// Çıktı:
// ab1 2c
// a b 1 2 c
// 3 4
// x5 6
// map[armut:5 ayva:1 elma:3]
// <nil> [] map[]

İlk satırda "b" ile 1 arasında boşluk yok, çünkü taraflardan biri string; 1 ile 2 arasında ise boşluk var. Println aynı argümanları her zaman boşlukla ayırır. Haritalar yazdırılırken anahtarlar sıralanır; bu yüzden fmt çıktısı haritanın kendi gezinme sırasından farklı olarak her çalıştırmada aynıdır. Değeri nil olan işaretçi <nil>, nil dilim [], nil harita map[] olarak görünür; yani çıktıya bakarak nil dilim ile boş dilimi ayırt edemezsiniz. Bu ayrım önemliyse %#v kullanın.

Genel fiiller: %v, %+v, %#v ve %T

%v her tip için bir varsayılan biçim seçer: tamsayılar için %d, ondalıklı sayılar için %g, metin için %s, mantıksal değer için %t. Bileşik değerlerde bu kural öğelere tek tek uygulanır; struct {alan1 alan2}, dilim [öğe1 öğe2], harita map[anahtar:değer] şeklinde yazılır. Bu yapılara işaret eden bir işaretçi başına & alır. %+v struct alanlarının adlarını da yazar, %#v değeri Go kaynak koduna yapıştırabileceğiniz biçimde gösterir, %T ise değerin tipini verir.

go
package main

import "fmt"

type Urun struct {
	Ad    string
	Fiyat float64
	Stok  int
}

func main() {
	u := Urun{Ad: "Çay", Fiyat: 42.5, Stok: 3}

	fmt.Printf("%v\n", u)
	fmt.Printf("%+v\n", u)
	fmt.Printf("%#v\n", u)
	fmt.Printf("%T\n", u)

	fmt.Printf("%d %b %o %x %X\n", 255, 255, 255, 255, 255)
	fmt.Printf("%t %q %c %U\n", true, "şeker", 'ğ', 'ğ')
	fmt.Printf("%x\n", "Go")
	fmt.Printf("%%%d\n", 18)
}

// Çıktı:
// {Çay 42.5 3}
// {Ad:Çay Fiyat:42.5 Stok:3}
// main.Urun{Ad:"Çay", Fiyat:42.5, Stok:3}
// main.Urun
// 255 11111111 377 ff FF
// true "şeker" ğ U+011F
// 476f
// %18

Hata ayıklarken %+v çoğu zaman en kullanışlı seçenektir: hangi değerin hangi alana ait olduğunu görürsünüz. %#v tip adını ve string değerlerin tırnaklarını da gösterdiği için iki farklı tipin aynı göründüğü durumları ayırmaya yarar. %x bir string'e uygulandığında metnin her baytını iki onaltılık haneyle yazar; "Go" için sonuç 476f olur. Bir rune (Unicode kod noktası) için %c karakterin kendisini, %U ise U+011F biçimindeki kod noktasını verir. Biçim dizesine düz bir yüzde işareti koymak için %% yazarsınız; bu fiil argüman tüketmez.

FiilUygulandığı değerNe yazar
%vher tipvarsayılan biçim
%+vstructalan adlarıyla birlikte değer
%#vher tipGo sözdizimiyle değer
%Ther tipdeğerin tipi
%tbooltrue veya false
%d, %b, %o, %xtamsayı10, 2, 8 ve 16 tabanında sayı
%c, %Urunekarakter, U+XXXX kod noktası
%e, %f, %gondalıklı sayıbilimsel, ondalık, kısa gösterim
%sstring, []byteyorumlanmamış baytlar
%qstring, runeGo kurallarıyla kaçışlı, tırnaklı metin
%xstring, []byteher bayt için iki onaltılık hane
%pişaretçi, dilim0x ile başlayan adres
%werror (yalnızca Errorf)hatayı sarmalar, aksi halde %v gibi

Genişlik, hassasiyet ve bayraklar

Genişlik, hassasiyet ve bayraklar

Genişlik, fiilin hemen önüne yazılan ondalık sayıdır ve çoğu değer için yazılacak en az karakter sayısını belirtir; değer daha kısaysa soldan boşlukla doldurulur. Hassasiyet genişlikten sonra bir nokta ve sayıyla verilir. Ondalıklı sayılarda hassasiyet noktadan sonraki basamak sayısıdır; %g için ise en fazla anlamlı basamak sayısıdır. Belgeye göre %e ve %f için varsayılan hassasiyet 6'dır, %g ise değeri tek başına tanımlamaya yetecek en az basamağı kullanır. String ve bayt dilimlerinde hassasiyetin anlamı değişir: çıktının değil girdinin uzunluğunu sınırlar, yani metni keser.

Türkçe metinlerle çalışırken bilmeniz gereken bir ayrıntı var: genişlik ve hassasiyet C'deki gibi bayt ile değil rune ile ölçülür. "çiğ" UTF-8'de 5 bayt yer kaplasa da 3 rune olduğu için %-6s sonuna üç boşluk ekler. Bayt ve rune farkının ayrıntısı için string, rune ve byte yazısına bakabilirsiniz. Bayraklar ise şunlardır: - sola yaslar, + pozitif sayılarda da işaret yazar, boşluk bayrağı işaret yerine boşluk bırakır, 0 boşluk yerine sıfırla doldurur ve dolgu işaretin arkasına gelir, # alternatif biçimi seçer (%#x için 0x öneki gibi).

go
package main

import "fmt"

func main() {
	fmt.Printf("[%8.2f]\n", 3.14159)
	fmt.Printf("[%-8.2f]\n", 3.14159)
	fmt.Printf("[%08.2f]\n", -3.14159)
	fmt.Printf("[%5d] [%-5d] [%05d]\n", 42, 42, 42)
	fmt.Printf("[%+d] [% d]\n", 42, 42)

	// Genişlik ve hassasiyet rune ile ölçülür.
	fmt.Printf("[%-6s] [%.3s]\n", "çiğ", "İstanbul")

	// Genişliği argümandan almak
	fmt.Printf("[%*d]\n", 6, 7)

	fmt.Printf("%.3g %g %e\n", 12.345, 1e21, 123456.0)

	urunler := []struct {
		ad    string
		fiyat float64
	}{{"Simit", 15}, {"Peynir", 289.9}, {"Zeytin", 1240.75}}
	for _, u := range urunler {
		fmt.Printf("%-8s%10.2f TL\n", u.ad, u.fiyat)
	}
}

// Çıktı:
// [    3.14]
// [3.14    ]
// [-0003.14]
// [   42] [42   ] [00042]
// [+42] [ 42]
// [çiğ   ] [İst]
// [     7]
// 12.3 1e+21 1.234560e+05
// Simit        15.00 TL
// Peynir      289.90 TL
// Zeytin     1240.75 TL

Çıktıyı birlikte okuyalım. %08.2f negatif sayıda sıfırları eksi işaretinin arkasına koyar, sonuç -0003.14 olur. %.3s "İstanbul" metninden ilk üç rune'u alır; "İ" iki bayt olmasına rağmen tek karakter sayılır. Genişliğin yerine * yazarsanız değer bir önceki argümandan okunur ve bu argüman int olmalıdır. Son döngü, sabit genişlikli sütunlarla basit bir fiş tablosu üretir: ad sola yaslı 8 karakter, fiyat sağa yaslı 10 karakter. Burada %-8s rune saydığı için Türkçe karakter içeren ürün adlarında da sütunlar kaymaz.

Stringer, error ve biçimlendirme sırası

Bir değer belirli arayüzleri uyguluyorsa fmt onu yazdırmadan önce bu arayüzlerin metotlarını çağırır. Belgede sıra şöyle tanımlanır: önce reflect.Value kontrol edilir, ardından Formatter arayüzü gelir; %#v kullanılıyorsa GoStringer denenir. Fiil string için geçerli bir fiilse (%s %q %x %X) ya da %#v dışındaki %v ise iki kural daha devreye girer: değer error ise Error(), String() string metodu varsa o çağrılır. Sonuç ardından fiile göre biçimlendirilir. Bu kural, %.2f gibi sayısal bir fiilin String metodunu atlayıp alttaki sayıyı yazdırmasını açıklar:

go
package main

import (
	"errors"
	"fmt"
	"os"
	"strings"
)

type Sicaklik float64

func (s Sicaklik) String() string {
	return fmt.Sprintf("%.1f°C", float64(s))
}

var ErrBulunamadi = errors.New("kayıt bulunamadı")

func main() {
	s := Sicaklik(21.456)
	fmt.Println(s)
	fmt.Printf("%v | %s | %q | %.2f\n", s, s, s, s)

	metin := fmt.Sprintf("%s: %d adet", "kalem", 12)
	fmt.Println(metin, len(metin))

	var b strings.Builder
	fmt.Fprintf(&b, "%03d-%s", 7, "A")
	fmt.Println(b.String())

	fmt.Fprintln(os.Stderr, "bu satır standart hataya gider")

	err := fmt.Errorf("kullanıcı %d yüklenemedi: %w", 42, ErrBulunamadi)
	fmt.Println(err)
	fmt.Println(errors.Is(err, ErrBulunamadi))

	buf := []byte("sipariş: ")
	buf = fmt.Appendf(buf, "#%d", 1001)
	fmt.Println(string(buf))
}

// Çıktı:
// 21.5°C
// 21.5°C | 21.5°C | "21.5°C" | 21.46
// kalem: 12 adet 14
// 007-A
// bu satır standart hataya gider
// kullanıcı 42 yüklenemedi: kayıt bulunamadı
// true
// sipariş: #1001

String metodunun içinde float64(s) dönüşümü bilinçli olarak yapıldı. Aynı metodun içinde değeri dönüştürmeden %s ile yazdırmaya çalışırsanız fmt yeniden String metodunu çağırır ve fonksiyon kendini sonsuza kadar çağırır; bu hatayı aşağıdaki hatalar bölümünde göreceksiniz. Sprintf ile üretilen metnin uzunluğu bayt cinsindendir; örnekteki metin tamamen ASCII olduğu için 14 karakter ile 14 bayt aynıdır. Fprintf ise io.Writer uygulayan her değerle çalışır; bu sayede aynı biçimlendirme kodu dosyaya, ağ bağlantısına ya da belleğe yazabilir. Dosya ve io.Writer ayrıntıları için dosya okuma ve yazma yazısına bakın.

fmt.Errorf, biçimlendirilmiş metinden bir error üretir. Biçimde %w fiili varsa dönen hata, sarmalanan hatayı döndüren bir Unwrap metoduna sahip olur ve errors.Is zincirde arama yapabilir. Go 1.20 sürüm notları ile birden fazla %w desteği geldi; bu durumda Unwrap bir []error döndürür. Go 1.26 sürüm notları, biçimlendirme içermeyen fmt.Errorf("x") çağrısının artık daha az bellek ayırdığını ve genellikle errors.New("x") ile aynı ayırmayı yaptığını belirtir. Go 1.27 sürüm notları ise go vet printf denetiminin, error uygulayan bir E tipi için *E tipindeki işaretçiyi %w ile sarmalayan çağrıları raporladığını söyler, çünkü bu kullanım tip kontrollerinde yanlış sonuçlara yol açabilir. Hata sarmalamanın bütün ayrıntıları hata yönetimi yazısında anlatılıyor.

Açık argüman indeksleri ve biçim hataları

Varsayılan olarak her fiil sıradaki argümanı kullanır. Fiilin hemen önüne [n] yazarsanız bunun yerine 1'den başlayarak sayılan n'inci argüman biçimlendirilir. İndeks sonraki fiilleri de etkiler: %[1]x yazıldıktan sonra gelen fiil 2. argümanla devam eder. Bu özellik aynı değeri farklı biçimlerde tekrar yazmak ya da çeviri metinlerinde sözcük sırasını değiştirmek için kullanışlıdır.

fmt hatalı bir biçim dizesi yüzünden panik üretmez; sorunu çıktının içine %! ile başlayan bir açıklama olarak yazar. Yanlış tip veya bilinmeyen fiil için %!fiil(tip=değer), fazla argüman için %!(EXTRA tip=değer), eksik argüman için %!fiil(MISSING) görürsünüz. Aşağıdaki programın ikinci ve üçüncü satırının çıktısına dikkat edin:

go
package main

import "fmt"

func main() {
	fmt.Printf("%d\n", "merhaba")
	fmt.Printf("toplam: %d\n", 1, 2)
	fmt.Printf("ad: %s yaş: %d\n", "Ece")
	fmt.Printf("%[2]s %[1]s\n", "dünya", "merhaba")
	fmt.Printf("%d %d %#[1]x %#x\n", 16, 17)
}

// Çıktı:
// %!d(string=merhaba)
// toplam: 1
// %!(EXTRA int=2)ad: Ece yaş: %!d(MISSING)
// merhaba dünya
// 16 17 0x10 0x11

Fazla argüman açıklaması biçim dizesinin sonuna, yani satır sonu karakterinden sonra eklenir; bu yüzden bir sonraki satırın başında görünür ve sorunun kaynağını yanlış satırda aramanıza yol açabilir. Bu tür hataları çalıştırmadan yakalamanın yolu go vet komutudur. Aynı dosya için Go 1.27.3 şunu raporlar:

text
main.go:6:14: fmt.Printf format %d has arg "merhaba" of wrong type string
main.go:7:2: fmt.Printf call needs 1 arg but has 2 args
main.go:8:27: fmt.Printf format %d reads arg #2, but call has 1 arg

Bir String ya da Error metodu yazdırma sırasında panik üretirse fmt bunu da yakalar ve çıktıya %!s(PANIC=...) biçiminde bir açıklama yazar. Panik, nil bir alıcı üzerinde çağrılan Error, String veya GoString metodundan geliyorsa çıktı süslenmeden <nil> olur. go vet ve diğer araçlar fmt, vet ve doc araçları yazısında ayrıntılı anlatılıyor.

Okuma tarafı: Sscan ve Sscanf

Paketin tarama fonksiyonları yazdırmanın tersini yapar. Scan, Scanln ve Scanf standart girdiden, Fscan ailesi bir io.Reader değerinden, Sscan ailesi bir string'den okur. Scan türevleri satır sonunu boşluk gibi kabul eder, Scanln türevleri satır sonunda durur, Scanf türevleri ise bir biçim dizesine göre ayrıştırır. Taranacak argümanların hepsi temel tiplere işaretçi olmalıdır. Biçim belirtilmeden ya da %v ile tamsayı okunurken 0x, 0o, 0b önekleri ve basamak ayırıcı alt çizgiler kabul edilir. Okunan argüman sayısı verilenden azsa hata döner:

go
package main

import "fmt"

func main() {
	var plaka int
	var sehir string
	n, err := fmt.Sscanf("34 İstanbul", "%d %s", &plaka, &sehir)
	fmt.Println(n, err, plaka, sehir)

	var a, b int
	n, err = fmt.Sscan("0x1F 1_000", &a, &b)
	fmt.Println(n, err, a, b)

	n, err = fmt.Sscan("7", &a, &b)
	fmt.Println(n, err)
}

// Çıktı:
// 2 <nil> 34 İstanbul
// 2 <nil> 31 1000
// 1 EOF

Kullanıcıdan ya da dosyadan satır satır metin okurken tarama fonksiyonları yerine bufio.Scanner ile satırı alıp strconv ile dönüştürmek genellikle daha öngörülebilir sonuç verir. Belge de Fscan türevlerinin girdiden bir rune fazla okuyabileceğini ve döngü içinde kullanıldığında veri atlanabileceğini not eder.

Sık yapılan hatalar

Sık yapılan hatalar: Printf ile yazdırıp sona \n koymayı unutmak. Printf satır sonu eklemez..., Println içine biçim dizesi yazmak. fmt.Println("değer, Bir String metodunun içinde...
  • Printf ile yazdırıp sona \n koymayı unutmak. Printf satır sonu eklemez; çıktılar aynı satırda birleşir.
  • Println içine biçim dizesi yazmak. fmt.Println("değer: %d", x) çıktıya değer: %d 5 yazar; go vet bunu fmt.Println call has possible Printf formatting directive %d diye bildirir.
  • Bir String metodunun içinde alıcıyı dönüştürmeden %s veya %v ile yazdırmak. Sonuç sonsuz özyineleme ve yığın taşmasıdır.
  • Genişliğin bayt saydığını varsaymak. Go'da genişlik bayt değil rune sayar; hizalamayı bayt hesabıyla yapmaya çalışırsanız Türkçe karakterli satırlar kayar.
  • %w fiilini fmt.Errorf dışında kullanmak ya da error olmayan bir değerle vermek. Belgeye göre bu geçersizdir; diğer fonksiyonlarda %w yalnızca %v gibi davranır.
  • Struct'ı işaretçi olarak yazdırınca çıktının başındaki & işaretini adres sanmak. Adresi görmek için %p gerekir.

Özyineleme hatası derleyiciden geçer ve ancak çalışırken fatal error: stack overflow ile çöker. Aşağıdaki Etiket tipi için go vet sorunu önceden gösterir:

go
package main

import "fmt"

type Etiket string

func (e Etiket) String() string {
	return fmt.Sprintf("<%s>", e) // hatalı: string(e) yazılmalı
}

func main() {
	fmt.Println(Etiket("go"))
}
text
main.go:8:23: fmt.Sprintf format %s with arg e causes recursive (command-line-arguments.Etiket).String method call

Sık Sorulan Sorular

%v ile %+v arasındaki fark nedir?

İkisi de değeri varsayılan biçimiyle yazar. Fark yalnızca struct'larda ortaya çıkar: %v alan değerlerini {Çay 42.5 3} şeklinde yan yana koyar, %+v ise {Ad:Çay Fiyat:42.5 Stok:3} şeklinde alan adlarını da ekler. %#v bunlara tip adını ve tırnakları da katar.

Printf mi Sprintf mi kullanmalıyım?

Metni hemen ekrana yazacaksanız Printf, bir değişkene atayıp sonra kullanacaksanız Sprintf, bir dosyaya veya ağ bağlantısına yazacaksanız Fprintf kullanın. Sonucu var olan bir bayt dilimine eklemek istiyorsanız Appendf ailesi metni o dilimin sonuna yazar ve genişlemiş dilimi döndürür.

Ondalık sayıyı iki basamakla nasıl yazdırırım?

fmt.Printf("%.2f", x) noktadan sonra iki basamak yazar ve yuvarlar. Sonucu string olarak istiyorsanız fmt.Sprintf("%.2f", x) ya da strconv.FormatFloat(x, 'f', 2, 64) kullanabilirsiniz. fmt ondalık ayırıcı olarak nokta kullanır (belgedeki örnek: 123.456); Türkçe gösterim için virgül gerekiyorsa sonucu ayrıca dönüştürmeniz gerekir.

Uygulama loglarında fmt yeterli mi?

Küçük araçlarda fmt.Fprintln(os.Stderr, ...) yeterli olabilir. Zaman damgası, seviye ve anahtar-değer alanları gereken servislerde standart kütüphanedeki log/slog paketi daha uygundur; ayrıntılar slog ile yapılandırılmış loglama yazısında var.

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 · strconv ile sayı ve string dönüşümü · string, rune ve byte · Go hata yönetimi · Go interface · gofmt, go vet ve go doc