Karakterler
Karakterler yazıları oluşturan en alt birimlerdir: harfler, rakamlar, noktalama işaretleri, boşluk karakteri, vs. Önceki dersteki dizilere ek olarak bu derste de karakterleri tanıyınca iki ders sonra anlatılacak olan dizgi yapısı çok kolay anlaşılacak.
Bilgisayar veri türleri temelde bitlerden oluştukları için, 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 veya 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 önce bu konunun kısa tarihine bakmamız gerekiyor.
Tarihçe
ASCII Tablosu
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):
109, 101, 114, 104, 97, 98, 97
Her bir değer bir harfe karşılık gelir. Örneğin iki 'a' harfi için iki tane 97 değeri kullanılmıştır. (Not: Bu baytların gerçekte hangi sırada durdukları platforma ve hatta baytların içinde bulundukları belgeye göre farklılıklar gösterir. Burada yalnızca karakterleri ifade etmek için kullanılan kod değerlerini görüyoruz.)
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şma çalışmaları sonucunda ISO/IEC 8859 standart karakter kodları çıkmış, 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 olduğu için, IBM'in kod tablolarının sorunları bu standartta da vardır. Hatta Felemenkçe'nin ij karakteri gibi bazı karakterler bu tablolarda yer bulamamışlardır.
Unicode
Bütün bu sorunları kökünden çözen standart, Unicode'dur. Unicode; dünya dillerindeki ve yazı sistemlerindeki harflerin, karakterlerin, ve yazım işaretlerinin 100 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 yaşamadan; 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 çok 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ğu için Ğ'nin en az 2 baytla gösterilmesi gerekir.
Not: Bayt değerlerinin nasıl hesaplandığı konumuz dışında kalıyor. Aşağıdaki örneklerde kullanıldığı için bu iki baytın değerlerinin 1 ve 30 olduklarını söylemekle yetineceğim.
Karakterlerin elektronik ortamda nasıl ifade edildiklerine karakter kodlaması denir. Yukarıda "merhaba"nın 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 kullanır; yani 4 bayt. "merhaba"nın UTF-32 kodlaması da ASCII kodlamasıyla aynıdır; farkı, her karakter için 4 bayt kullanılmasıdır. Bu yüzden UTF-32 kodlamasının şöyle olacağını düşünebilirsiniz (okumayı kolaylaştırmak için iki satır halinde gösteriyorum):
0, 0, 0, 109, 0, 0, 0, 101, 0, 0, 0, 114, 0, 0, 0, 104, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 97
Başka bir örnek olarak, ifade edilecek metnin örneğin "aĞ" olduğunu düşünürsek:
0, 0, 0, 97, 0, 0, 1, 30
Yani a'da 1, ve Ğ'de 2 tane anlamlı bayt olduğu için; 5 tane de sıfır bulunmaktadır. Bu sıfırları, her karaktere 4 bayt verebilmek için gereken doldurma baytları olarak düşünebiliriz. Not: Baytların sıraları farklı olabilir ve bazılarının gerçek değerleri farklıdır.
Dikkat ederseniz, bu kodlama her zaman için ASCII kodlamasının 4 katı yer harcamaktadı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ıldığı için bu kodlama, duruma göre çok savurgan olabilir.
Öte yandan, karakterlerin her birisinin tam olarak 4 bayt yer tutuyor olmasının getirdiği yararları da vardır.
UTF-16: Bu kodlama, Unicode karakterlerinin çoğunu 16 bitle, yani 2 baytla gösterir. İki bayt yaklaşık olarak 65 bin değer tutabildiği için, 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 4 bayt olarak kodlanır:
0, 97, 1, 30
Not: Baytların sıraları farklı olabilir ve bazılarının gerçek değerleri farklıdır.
Bu kodlama çoğu belgede UTF-32'den daha az yer tutar ama karakterine göre değişik sayıda bayt kullanıldığı için işlenmesi daha karmaşıktır.
UTF-8: Bu kodlama, karakterleri 1 veya daha fazla baytla ifade eder. Eğer karakter ASCII tablosundaki karakterlerden birisiyse, 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 içinde en az yer tutan kodlamadır. Başka bir yararı, ASCII tablosundaki kodlara aynen karşılık geldiği için, 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Ğ" için:
97, 1, 30
Not: Baytların sıraları farklı olabilir ve bazılarının gerçek değerleri farklıdır.
D'nin karakter türleri
D'de karakterleri ifade etmek için 3 değişik 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 |
|---|---|---|
| char | işaretsiz 8 bit UTF-8 karakter değeri | 0xFF |
| wchar | işaretsiz 16 bit UTF-16 karakter değeri | 0xFFFF |
| dchar | işaretsiz 32 bit UTF-32 karakter değeri | 0x0000FFFF |
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ği için 8 bitlik char türüne sığmaz. Öte yandan, dchar 4 bayttan oluştuğu için her Unicode karakterini tutabilir.
Buna rağmen D Unicode'un bazı az kullanılan olanaklarını desteklemez. Bunu biraz aşağıda anlatacağım.
Karakter sabitleri
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 ders 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ı için 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 (Şu anda çalıştığım Linux ortamında bu programın ismi Character Map (konsolda charmap).)
- Karakterlerin bazılarını standart kısa isimleriyle yazmak. Bunun söz dizimi
\&karakter_ismi;biçimindedir. Örneğin avro karakterinin ismieuro'dur ve programda değeri şöyle yazılabilir:
wchar para_sembolü = '\€';
Diğer isimli karakterleri D'nin isimli karakterler listesinde bulabilirsiniz.
char a = 97; wchar Ğ = 286;
\sekizli_düzende_kod veya \xonaltılı_düzende_kod söz dizimleriyle yazmak:
char soru_işareti_sekizli = '\77'; char soru_işareti_onaltılı = '\x3f';
wchar için \udört_haneli_kod söz dizimini, dchar için de \Usekiz_haneli_kod söz dizimini kullanabilirsiniz. Bu yazımda karakterin kodunun onaltılı sayı sisteminde (hexadecimal) yazılması gerekir:
wchar Ğ_w = '\u011e'; dchar Ğ_d = '\U0000011e';
Bu yöntemler karakterleri çift tırnak içinde bir arada yazdığınız durumlarda da geçerlidir. Örneğin şu iki satır aynı çıktıyı verirler:
writeln("Ağ fiyatı: 10.25€"); writeln("\x41\u011f fiyatı: 10.25\€");
Kontrol karakterleri
Bazı karakterler yalnızca metin düzeniyle ilgilidirler; kendilerine özgü bir 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 |
|---|---|---|
| \n | yeni satır | Yeni satıra geçirir |
| \r | satır başı | Satırın başına götürür |
| \t | sekme | Bir sonraki sekme noktasına kadar boşluk bırakır |
Örneğin \n karakterini kullanırsak, çıktıda otomatik olarak yeni satır açmadığını bildiğimiz write ile bile yeni satır açtırabiliriz. Yazdırılacak metnin içinde istediğimiz 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");
Çıktısı:
birinci satır ikinci satır üçüncü satır
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ığı için, 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 için başlarına bir ters bölü daha yazılır:
| Yazım | İsim | Açıklama |
|---|---|---|
| \' | tek tırnak | Tek tırnağın karakter olarak tanımlanmasına olanak verir: '\'' |
| \\ | ters bölü | Ters bölü karakterinin yazılmasına olanak verir: '\\' veya "\\" |
std.uni modülü
std.uni modülü Unicode karakterleriyle ilgili yardımcı işlevler içerir. Bu modüldeki işlevleri Ddili Wiki'nin std.uni sayfasında görebilirsiniz.
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: bir harf mi?isWhite: bir boşluk karakteri mi?
to ile başlayan işlevler verilen karakteri kullanarak yeni bir karakter üretirler:
toLower: küçük harfini üretirtoUpper: büyük harfini üretir
Bu işlevleri kullanan bir program:
import std.stdio; import std.uni; void main() { 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("\€ harf midir? ", isAlpha('\€')); 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('ı')); }
Çıktısı:
ğ 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ığı için ne yazık ki i'nin büyüğü I, I'nın küçüğü de i olur. Bu yüzden bu iki harf için özel dikkat göstermemiz gerekir. İşte bu sorunu gösteren bir program:
import std.stdio; import std.uni; void main() { writeln("i'nin büyüğü: ", toUpper('i')); writeln("I'nın küçüğü: ", toLower('I')); }
ve yanlış çıktısı:
i'nin büyüğü: I I'nın küçüğü: i
Her alfabe sorunludur
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.
trileri kütüphanesi
Alfabelerle ilgili bu gibi sorunlara çözüm getiren bir proje, Ddili Forum üyeleri tarafından geliştirilen trileri kütüphanesidir. trileri, bu yazının yazıldığı sırada İngiliz, Türk, Azeri, Türkmen, Kırım Tatar, Gagavuz, Kumuk, Hakas, Kürt, ve İrlanda alfabelerini desteklemekte ve bu alfabelerin küçük-büyük dönüşüm ve sıralama sorunlarını çözmektedir.
trileri kütüphanesini şu sayfada deneyebilirsiniz:
http://www.ddili.org/cgi-bin/trileri_deneme
Girişten karakter okumadaki sorunlar
D'nin Unicode konusunda getirdiği güç ve esneklik, girişten karakter okurken beklenmedik sonuçlar doğurabilir. Bu çelişki, karakter ile ne kastedildiğinin açık olmaması nedeniyledir. Daha ileriye gitmeden önce, bu sorunu yaşayan bir program göstermek istiyorum.
import std.stdio; void main() { char harf; write("Lütfen bir harf girin: "); readf(" %s", &harf); writeln("Okuduğum harf: ", harf); }
Yukarıdaki programı Unicode kodlaması kullanılmayan bir ortamda çalıştırırsanız, 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 konsol penceresinde olduğu gibi bir Unicode ortamında çalıştırırsanız, yazdırılan harfin sizin yazdığınızla aynı olmadığını görürsünüz. Örneğin UTF-8 kodlaması kullanan bir konsolda ASCII tablosunda bulunmayan bir harf girildiğinde:
Lütfen bir harf girin: ğ
Okuduğum harf: ← girilen harf görünmüyor
Bunun nedeni, UTF-8 kodlaması kullanan konsolun ASCII tablosunda bulunmayan ğ gibi harfleri birden fazla kod ile temsil etmesi, ve readf'in char okurken bu kodlardan yalnızca ilkini alıyor olmasıdır. O char asıl karakteri temsil etmeye yetmediği için de writeln'ın yazdırdığı eksik kodlanmış olan harf konsolda 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:
import std.stdio; void main() { char birinci_kod; char ikinci_kod; write("Lütfen bir harf girin: "); readf(" %s", &birinci_kod); readf(" %s", &ikinci_kod); writeln("Okuduğum harf: ", birinci_kod, ikinci_kod); }
Program girişten iki char okumakta ve onları aynı sırada çıkışa yazdırmaktadır. O char değerlerinin art arda konsola gönderilmiş olmaları, bu sefer harfin UTF-8 kodlamasını standart çıkış tarafında tamamlamakta ve karakter doğru olarak gösterilmektedir:
Lütfen bir harf girin: ğ
Okuduğum harf: ğ ← iki char'ın oluşturmuş olduğu harf
Bu sonuçlar standart giriş ve çıkışın char akımları olmalarından kaynaklanır. Karakterlerin iki ders sonra göreceğimiz dizgiler aracılığıyla aslında çok daha rahat okunduklarını göreceksiniz.
D'nin Unicode desteği
Unicode çok büyük ve karmaşık bir standarttır. D, Unicode'un tamamını değil ama 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.
- karakter (character): yazı sistemlerinde kullanılmak üzere Unicode'un tanımlamış olduğu bütün şekiller, imler, ve konuşma dilinde "karakter" dediğimiz her şey bu tanıma girer.
- tek başına ğ kod noktası olarak
- art arda gelen g ve ̆ kod noktaları olarak (g ve sonrasında gelen birleştirici (combining) breve şapkası)
D'nin char, wchar, ve dchar türleri sırasıyla UTF-8, UTF-16, ve UTF-32 kod birimlerini ifade ederler.
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.
D'de kod noktalarını tam olarak destekleyen tür dchar'dır. char ve wchar ise yalnızca kod birimi türü olarak kullanılmaya elverişlidirler.
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:
D, kod noktalarının bileşimlerinden oluşan karakter kavramını desteklemez. D'nin gözünde tek kod noktası olan ğ ile art arda gelen g ve ̆ karakterlerinin ilgileri yoktur.
Özet
- Unicode, dünya yazı sistemlerindeki bütün karakterleri destekler
charUTF-8 kodlaması içindir; karakterleri ifade etmeye genelde elverişli olmasa da ASCII tablosunu desteklerwcharUTF-16 kodlaması içindir; karakterleri ifade etmeye genelde elverişli olmasa da özel durumlarda birden fazla alfabe karakterini desteklerdcharUTF-32 kodlaması içindir; 32 bit olması nedeniyle bütün Unicode karakterlerini destekler ve kod noktası olarak kullanılabilir
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları