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.
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 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 ij karakteri gibi bazı karakterler bu tablolarda yer bulamamışlardır.
Unicode
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, 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
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:
0,97, 1,30
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:
97, 196,158
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 |
---|---|---|
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ğinden 8 bitlik char
türüne sığmaz. Öte yandan, dchar
4 bayttan oluştuğundan her Unicode karakterini tutabilir.
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 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 ismieuro
'dur ve programda değeri şöyle yazılabilir:wchar para_sembolü = '\€';
Diğer isimli karakterleri D'nin isimli karakterler listesinde bulabilirsiniz.
- Karakterleri tamsayı Unicode değerleriyle belirtmek:
char a = 97; wchar Ğ = 286;
- 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
veU
karakterlerinin farklı olduklarına dikkat edin). Bu yazımda karakterin kodunun on altı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, aşağıdaki 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ü 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, çı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");
Çı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ığı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:
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 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 üretirtoUpper
: Büyük harfini üretir
Aşağıdaki program bütün bu işlevleri kullanmaktadır:
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ığı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:
import std.stdio; import std.uni; void main() { writeln("i'nin büyüğü: ", toUpper('i')); writeln("I'nın küçüğü: ", toLower('I')); }
İstenmeyen çıktısı:
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.
Girişten karakter okumadaki sorunlar
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:
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ı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:
Lütfen bir harf girin: ğ
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:
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 uç birime 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: ğ
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.
D'nin Unicode desteği
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.D'nin
char
,wchar
, vedchar
türleri sırasıyla UTF-8, UTF-16, ve UTF-32 kod birimlerini ifade ederler. - 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.D'de kod noktalarını tam olarak destekleyen tür
dchar
'dır.char
vewchar
ise yalnızca kod birimi türü olarak kullanılmaya elverişlidirler. - 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. - Tek başına
Özet
- 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.