D Programlama Dili - Karakterler
Kitap RSS Ekitap sürümleri bayt: [byte], 8 bitlik tür
karakter kodlaması: [character encoding], karakter kodlarının ifade edilme yöntemi
kod tablosu: [code page], 127'den büyük karakter değerlerinin bir dünya dili için tanımlanmaları
kontrol karakteri: [control character], yeni satır açan '\n', yatay sekme karakteri '\t', gibi özel karakterler
Karakterler yazıları oluşturan en alt birimlerdir: harfler, rakamlar, noktalama işaretleri, boşluk karakteri, vs. Önceki bölümdeki dizilere ek olarak bu bölümde de karakterleri tanıyınca iki bölüm sonra anlatılacak olan dizgi kavramını anlamak kolay olacak.
Bilgisayar veri türleri temelde bitlerden oluştuklarından, karakterler de bitlerin birleşimlerinden oluşan tamsayı değerler olarak ifade edilirler. Örneğin, küçük harf 'a'nın tamsayı değeri 97'dir ve '1' rakamının tamsayı değeri 49'dur. Bu değerler tamamen anlaşmalara bağlı olarak atanmışlardır ve kökleri ASCII kod tablosuna dayanır.
Karakterler bazı dillerde geleneksel olarak 256 farklı değer tutabilen char türüyle gösterilirler. Eğer char türünü başka dillerden tanıyorsanız onun her harfi barındıracak kadar büyük bir tür olmadığını biliyorsunuzdur. D'de üç farklı karakter türü bulunur. Buna açıklık getirmek için bu konunun tarihçesini gözden geçirelim.
Donanımın çok kısıtlı olduğu günlerde tasarlanan ilk ASCII tablosu 7 bitlik değerlerden oluşuyordu ve bu yüzden ancak 128 karakter değeri barındırabiliyordu. Bu değerler İngiliz alfabesini oluşturan 26 harfin küçük ve büyük olanlarını, rakamları, sık kullanılan noktalama işaretlerini, programların çıktılarını uç birimlerde gösterirken kullanılan kontrol karakterlerini, vs. ifade etmek için yeterliydi.
Örnek olarak, "merhaba" metnindeki karakterlerin ASCII kodları sırasıyla şöyledir (bu gösterimlerde okumayı kolaylaştırmak için bayt değerleri arasında virgül kullanıyorum):
Her bir değer bir harfe karşılık gelir. Örneğin, iki 'a' harfi için iki adet 97 değeri kullanılmıştır.
Donanımdaki gelişmeler doğrultusunda ASCII tablosundaki kodlar daha sonra 8 bite çıkartılarak 256 karakter destekleyen Genişletilmiş (Extended) ASCII tablosu tanımlanmıştır.
IBM Kod Tabloları
IBM firması ASCII tablosuna dayanan ve 128 ve daha büyük karakter değerlerini dünya dillerine ayıran bir dizi kod tablosu tanımladı. Bu kod tabloları sayesinde İngiliz alfabesinden başka alfabelerin de desteklenmeleri sağlanmış oldu. Örneğin, Türk alfabesine özgü karakterler IBM'in 857 numaralı kod tablosunda yer aldılar.
Her ne kadar ASCII'den çok daha yararlı olsalar da, kod tablolarının önemli sorunları vardır: Yazının doğru olarak görüntülenebilmesi için yazıldığı zaman hangi kod tablosunun kullanıldığının bilinmesi gerekir çünkü farklı kod tablolarındaki kodlar farklı karakterlere karşılık gelirler. Örneğin, 857 numaralı kod tablosunda 'Ğ' olan karakter 437 numaralı kod tablosu ile görüntülendiğinde 'ª' karakteri olarak belirir. Başka bir sorun, yazı içinde birden fazla dilin karakteri kullanıldığında kod tablolarının yetersiz kalmalarıdır. Ayrıca, 128'den fazla özel karakteri olan diller zaten 8 bitlik bir tabloda ifade edilemezler.
ISO/IEC 8859 Kod Tabloları
Uluslararası standartlaştırma çalışmaları sonucunda ISO/IEC 8859 standart karakter kodları tanımlanmış ve örneğin Türk alfabesinin özel harfleri 8859-9 tablosunda yer almışlardır. Yapısal olarak IBM'in tablolarının eşdeğeri olduklarından IBM'in kod tablolarının sorunları bu standartta da bulunur. Hatta, Felemenkçe'nin ĳ karakteri gibi bazı karakterler bu tablolarda yer bulamamışlardır.
Unicode standardı bu sorunları çözer. Unicode, dünya dillerindeki ve yazı sistemlerindeki harflerin, karakterlerin, ve yazım işaretlerinin yüz binden fazlasını tanımlar ve her birisine farklı bir kod verir. Böylece, Unicode'un tanımladığı kodları kullanan metinler bütün dünya karakterlerini hiçbir karışıklık ve kısıtlama olmadan bir arada bulundurabilirler.
Unicode kodlama çeşitleri
Unicode, her bir karaktere bir kod değeri verir. Örnek olarak, 'Ğ' harfinin Unicode'daki değeri 286'dır. Unicode'un desteklediği karakter sayısı o kadar fazla olunca, karakterleri ifade eden değerler de doğal olarak artık 8 bitle ifade edilemezler. Örneğin, kod değeri 255'ten büyük olduğundan 'Ğ'nin en az 2 baytla gösterilmesi gerekir.
Karakterlerin elektronik ortamda nasıl ifade edildiklerine karakter kodlaması denir. Yukarıda "merhaba" dizgisinin karakterlerinin ASCII kodlarıyla nasıl ifade edildiklerini görmüştük. Şimdi Unicode karakterlerinin standart kodlamalarından üçünü göreceğiz.
UTF-32: Bu kodlama her Unicode karakteri için 32 bit (4 bayt) kullanır. "merhaba"nın UTF-32 kodlaması da ASCII kodlamasıyla aynıdır. Tek fark, her karakter için 4 bayt kullanılmasıdır:
0,0,0,109, 0,0,0,101, 0,0,0,114, 0,0,0,104, 0,0,0,97,
Başka bir örnek olarak, ifade edilecek metnin örneğin "aĞ" olduğunu düşünürsek:
Not: Baytların sıraları farklı platformlarda farklı olabilir.
'a'da bir ve 'Ğ'de iki adet anlamlı bayt olduğundan toplam beş adet de sıfır bulunmaktadır. Bu sıfırlar her karaktere 4 bayt verebilmek için gereken doldurma baytları olarak düşünülebilir.
Dikkat ederseniz, bu kodlama her zaman için ASCII kodlamasının 4 katı yer tutmaktadır. Metin içindeki karakterlerin büyük bir bölümünün İngiliz alfabesindeki karakterlerden oluştuğu durumlarda, çoğu karakter için 3 tane de 0 kullanılacağından bu kodlama duruma göre fazla savurgan olabilir.
Öte yandan, karakterlerin her birisinin tam olarak 4 bayt yer tutuyor olmasının getirdiği yararlar da vardır. Örneğin, bir sonraki Unicode karakteri hiç hesap gerektirmeden her zaman için tam dört bayt ötededir.
UTF-16: Bu kodlama, Unicode karakterlerinin çoğunu 16 bitle (2 bayt) gösterir. İki bayt yaklaşık olarak 65 bin değer tutabildiğinden, yaklaşık yüz bin Unicode karakterinin geri kalan 35 bin kadarı için daha fazla bayt kullanmak gerekir.
Örnek olarak "aĞ" UTF-16'da aşağıdaki 4 bayt olarak kodlanır:
Not: Baytların sıraları farklı ortamlarda farklı olabilir.
Bu kodlama çoğu belgede UTF-32'den daha az yer tutar ama nadir kullanılan bazı karakterler için ikiden fazla bayt kullandığından işlenmesi daha karmaşıktır.
UTF-8: Bu kodlama, karakterleri en az 1 ve en fazla 4 baytla ifade eder. Eğer karakter ASCII tablosundaki karakterlerden biriyse, tek baytla ve aynen ASCII tablosundaki değeriyle ifade edilir. Bunların dışındaki karakterlerin bazıları 2, bazıları 3, diğerleri de 4 bayt olarak ifade edilirler. Türk alfabesinin İngiliz alfabesinde bulunmayan özel karakterleri 2 baytlık gruptadırlar.
Çoğu belge için UTF-8 bütün kodlamalar arasında en az yer tutan kodlamadır. Başka bir yararı, ASCII tablosundaki kodlara aynen karşılık geldiğinden, ASCII kodlanarak yazılmış ve İngiliz alfabesini kullanan belgeler de otomatik olarak UTF-8 düzenine uyarlar. Bu kodlamada hiç savurganlık yoktur; bütün karakterler gerçekten gereken sayıda baytla ifade edilirler. Örneğin, "aĞ"ın UTF-8 kodlaması aşağıdaki gibidir:
D'nin karakter türleri
D'de karakterleri ifade etmek için 3 farklı tür vardır. Bunlar yukarıda anlatılan Unicode kodlama yöntemlerine karşılık gelirler. Temel türlerin tanıtıldığı sayfada gösterildikleri gibi:
Tür Açıklama İlk Değeri
işaretsiz 8 bit UTF-8 karakter değeri
işaretsiz 16 bit UTF-16 karakter değeri
işaretsiz 32 bit UTF-32 karakter değeri
Başka bazı programlama dillerinden farklı olarak, D'de her karakter aynı uzunlukta olmayabilir. Örneğin, 'Ğ' harfi Unicode'da en az 2 baytla gösterilebildiğinden 8 bitlik char türüne sığmaz. Öte yandan, dchar 4 bayttan oluştuğundan her Unicode karakterini tutabilir.
Karakterleri program içinde tek olarak belirtmek gerektiğinde etraflarına tek tırnak işaretleri koyulur:
char  a_harfi = 'a';
wchar büyük_yumuşak_g = 'Ğ';
Karakter sabitleri için çift tırnak kullanılamaz çünkü o zaman iki bölüm sonra göreceğimiz dizgi sabiti anlamına gelir: 'a' karakter değeridir, "a" tek karakterli bir dizgidir.
Türk alfabesindeki bazı harflerin Unicode kodları 2 bayttan oluştuklarından char türündeki değişkenlere atanamazlar.
Karakterleri sabit olarak program içine yazmanın bir çok yolu vardır:
En doğal olarak, klavyeden doğrudan karakterin tuşuna basmak
Çalışma ortamındaki başka bir programdan veya bir metinden kopyalamak. Örneğin, bir internet sitesinden veya çalışma ortamında karakter seçmeye yarayan bir programdan kopyalanabilir (Linux ortamlarında bu programın ismi Character Map'tir (uç birimlerde charmap).)
Karakterlerin bazılarını standart kısa isimleriyle yazmak. Bunun söz dizimi \&karakter_ismi; biçimindedir. Örneğin, avro karakterinin ismi euro'dur ve programda değeri şöyle yazılabilir:
wchar para_sembolü = '\&amp;euro;';
Diğer isimli karakterleri D'nin isimli karakterler listesinde bulabilirsiniz.
Karakterleri tamsayı Unicode değerleriyle belirtmek:
ASCII tablosundaki karakterleri değerleriyle \sekizli_düzende_kod veya \xon_altılı_düzende_kod söz dizimleriyle yazmak:
char soru_işareti_sekizli = '\77';
char soru_işareti_on_altılı = '\x3f';
Karakterleri Unicode değerleriyle yazmak. wchar için \udört_haneli_kod söz dizimini, dchar için de \Usekiz_haneli_kod söz dizimini kullanabilirsiniz (u ve U karakterlerinin farklı olduklarına dikkat edin). Bu yazımda karakterin kodunun on altılı sayı sisteminde (hexadecimal) yazılması gerekir:
Bu yöntemler karakterleri çift tırnak içinde bir arada yazdığınız durumlarda da geçerlidir. Örneğin, aşağıdaki iki satır aynı çıktıyı verirler:
writeln("Ağ fiyatı: 10.25€");
writeln("\x41\u011f fiyatı: 10.25\&amp;euro;");
Bazı karakterler yalnızca metin düzeniyle ilgilidirler; kendilerine özgü görünümleri yoktur. Örneğin, uç birime yeni bir satıra geçileceğini bildiren yeni satır karakterinin gösterilecek bir şekli yoktur; yalnızca yeni bir satıra geçilmesini sağlar. Böyle karakterlere kontrol karakteri denir. Kontrol karakterleri \özel_harf söz dizimiyle ifade edilirler.
Yazım İsim Açıklama
Yeni satıra geçirir
Satırın başına götürür
Bir sonraki sekme noktasına kadar boşluk bırakır
Örneğin, çıktıda otomatik olarak yeni satır açmayan write bile \n karakterlerini yeni satır açmak için kullanır. Yazdırılacak metnin içinde istenen noktalara \n karakterleri yerleştirmek o noktalarda yeni satır açılmasını sağlar:
write("birinci satır\nikinci satır\nüçüncü satır\n");
Tek tırnak ve ters bölü
Tek tırnak karakterinin kendisini tek tırnaklar arasında yazamayız çünkü derleyici ikinci tırnağı gördüğünde tırnakları kapattığımızı düşünür: '''. İlk ikisi açma ve kapama tırnakları olarak algılanırlar, üçüncüsü de tek başına algılanır ve yazım hatasına neden olur.
Ters bölü karakteri de başka özel karakterleri ifade etmek için kullanıldığından, derleyici onu bir özel karakterin başlangıcı olarak algılar: '\'. Derleyici \' yazımını bir özel karakter olarak algılar ve baştaki tek tırnakla eşlemek için bir tane daha tek tırnak arar ve bulamaz.
Bu iki karakteri sabit olarak yazmak gerektiğinde başlarına bir ters bölü daha yazılır:
Tek tırnağın karakter olarak tanımlanmasına olanak verir: '\''
Ters bölü karakterinin yazılmasına olanak verir: '\\' veya "\\"
std.uni modülü Unicode karakterleriyle ilgili yardımcı işlevler içerir. Bu modüldeki işlevleri kendi belgesinde bulabilirsiniz.
is ile başlayan işlevler karakterle ilgili sorular cevaplarlar: cevap yanlışsa false, doğruysa true döndürürler. Bu işlevler mantıksal ifadelerde kullanışlıdırlar:
isLower: Küçük harf mi?
isUpper: Büyük harf mi?
isAlpha: Herhangi bir harf mi?
isWhite: Herhangi bir boşluk karakteri mi?
to ile başlayan işlevler verilen karakteri kullanarak yeni bir karakter üretirler:
toLower: Küçük harfini üretir
toUpper: Büyük harfini üretir
Aşağıdaki program bütün bu işlevleri kullanmaktadır:
writeln("ğ küçük müdür? ", isLower('ğ'));
writeln("Ş küçük müdür? ", isLower('Ş'));
writeln("İ büyük müdür? ", isUpper('İ'));
writeln("ç büyük müdür? ", isUpper('ç'));
writeln("z harf midir? ",       isAlpha('z'));
writeln("\&amp;euro; harf midir? ", isAlpha('\&amp;euro;'));
writeln("'yeni satır' boşluk mudur? ", isWhite('\n'));
writeln("alt çizgi boşluk mudur? ",    isWhite('_'));
writeln("Ğ'nin küçüğü: ", toLower('Ğ'));
writeln("İ'nin küçüğü: ", toLower('İ'));
writeln("ş'nin büyüğü: ", toUpper('ş'));
writeln("ı'nın büyüğü: ", toUpper('ı'));
ğ küçük müdür? true
Ş küçük müdür? false
İ büyük müdür? true
ç büyük müdür? false
z harf midir? true
€ harf midir? false
'yeni satır' boşluk mudur? true
alt çizgi boşluk mudur? false
Ğ'nin küçüğü: ğ
İ'nin küçüğü: i
ş'nin büyüğü: Ş
ı'nın büyüğü: I
Türk alfabesinin şanssız harfleri: ı ve i
'ı' ve 'i' harflerinin küçük ve büyük biçimleri Türk alfabesinde tutarlıdır: noktalıysa noktalı, noktasızsa noktasız. Oysa çoğu yabancı alfabede bu konuda bir tutarsızlık vardır: noktalı 'i'nin büyüğü noktasız 'I'dır.
Bilgisayar sistemlerinin temelleri İngiliz alfabesiyle başladığından 'i'nin büyüğü 'I, 'I'nın küçüğü ise 'i'dir. Bu yüzden bu iki harf için özel dikkat göstermek gerekir:
writeln("i'nin büyüğü: ", toUpper('i'));
writeln("I'nın küçüğü: ", toLower('I'));
i'nin büyüğü: I
I'nın küçüğü: i
Karakter kodları kullanılarak yapılan küçük-büyük dönüşümleri ve harf sıralamaları aslında bütün alfabeler için sorunludur.
Örneğin, 'I'nın küçüğünün 'i' olarak dönüştürülmesi Azeri ve Kelt alfabeleri için de yanlıştır.
Benzer sorunlar harflerin sıralanmalarında da bulunur. Örneğin, 'ğ' gibi Türk alfabesine özgü harfler 'z'den sonra sıralandıkları gibi, 'á' gibi aksanlı harfler İngiliz alfabesinde bile 'z'den sonra gelirler.
Unicode karakterleri girişten okunurken beklenmedik sonuçlarla karşılaşılabilir. Bunlar genellikle karakter ile ne kastedildiğinin açık olmamasındandır. Daha ileriye gitmeden önce bu sorunu gösteren bir programa bakalım:
readf(" %s", &amp;harf);
Yukarıdaki programı Unicode kodlaması kullanılmayan bir ortamda çalıştırdığınızda programın girişinden aldığı Türkçe harfleri belki de doğru olarak yazdırdığını görebilirsiniz.
Öte yandan, aynı programı çoğu Linux uç biriminde olduğu gibi bir Unicode ortamında çalıştırdığınızda, yazdırılan harfin sizin yazdığınızla aynı olmadığını görürsünüz. Örneğin, UTF-8 kodlaması kullanan bir uç birimde ASCII tablosunda bulunmayan bir harf girilmiş olsun:
Okuduğum harf:           ← girilen harf görünmüyor
Bunun nedeni, UTF-8 kodlaması kullanan uç birimin ASCII tablosunda bulunmayan 'ğ' gibi harfleri birden fazla kod ile temsil etmesi, ve readf'in char okurken bu kodlardan yalnızca birincisini alıyor olmasıdır. O char da asıl karakteri temsil etmeye yetmediğinden, writeln'ın yazdırdığı eksik kodlanmış olan harf uç birimde gösterilememektedir.
char olarak okunduğunda harfin kendisinin değil, onu oluşturan kodların okunmakta olduklarını harfi iki farklı char olarak okuyarak görebiliriz:
readf(" %s", &amp;birinci_kod);
readf(" %s", &amp;ikinci_kod);
Program girişten iki char okumakta ve onları aynı sırada çıkışa yazdırmaktadır. O char değerlerinin art arda uç birime gönderilmiş olmaları, bu sefer harfin UTF-8 kodlamasını standart çıkış tarafında tamamlamakta ve karakter doğru olarak gösterilmektedir:
Okuduğum harf: ğ
Bu sonuçlar standart giriş ve çıkışın char akımları olmalarından kaynaklanır. Karakterlerin iki bölüm sonra göreceğimiz dizgiler aracılığıyla aslında çok daha rahat okunduklarını göreceksiniz.
Unicode çok büyük ve karmaşık bir standarttır. D, Unicode'un oldukça kullanışlı bir alt kümesini destekler.
Unicode ile kodlanmış olan bir metin en aşağıdan en yukarıya doğru şu düzeylerden oluşur:
Kod birimi (code unit): UTF kodlamalarını oluşturan kod değerleridir. Unicode karakterleri, kodlamaya ve karakterin kendisine bağlı olarak bir veya daha fazla kod biriminden oluşabilirler. Örneğin, UTF-8 kodlamasında 'a' karakteri tek kod biriminden, 'ğ' karakteri ise iki kod biriminden oluşur.
Kod noktası (code point): Unicode'un tanımlamış olduğu her harf, im, vs. bir kod noktasıdır. Örneğin, 'a' ve 'ğ' iki farklı kod noktasıdır.
Bu kod noktaları kodlamaya bağlı olarak bir veya daha fazla kod birimi ile ifade edilirler. Yukarıda da değindiğim gibi, UTF-8 kodlamasında 'a' tek kod birimi ile, 'ğ' ise iki kod birimi ile ifade edilir. Öte yandan, her ikisi de UTF-16 ve UTF-32 kodlamalarında tek kod birimi ile ifade edilirler.
Karakter (character): yazı sistemlerinde kullanılmak üzere Unicode'un tanımlamış olduğu bütün şekiller, imler, ve konuşma dilinde "karakter" veya "harf" dediğimiz her şey bu tanıma girer.
Bu konuda Unicode'un getirdiği bir karışıklık, bazı karakterlerin birden fazla kod noktasından oluşabilmeleridir. Örneğin, 'ğ' harfini ifade etmenin iki yolu vardır:
Tek başına 'ğ' kod noktası olarak
Art arda gelen 'g' ve '˘' kod noktaları olarak ('g' ve sonrasında gelen birleştirici (combining) breve şapkası)
Farklı kod noktalarından oluştuklarından, tek kod noktası olan 'ğ' karakteri ile art arda gelen 'g' ve '˘' karakterlerinin ilgileri yoktur.
Unicode, dünya yazı sistemlerindeki bütün karakterleri destekler.
char UTF-8 kodlaması içindir; karakterleri ifade etmeye genelde elverişli olmasa da ASCII tablosunu destekler.
wchar UTF-16 kodlaması içindir; karakterleri ifade etmeye genelde elverişli olmasa da özel durumlarda birden fazla alfabe karakterini destekler.
dchar UTF-32 kodlaması içindir; 32 bit olması nedeniyle bütün Unicode karakterlerini destekler ve kod noktası olarak kullanılabilir.