Dizgiler
"merhaba" gibi metin parçalarının dizgi olduklarını zaten öğrenmiş ve şimdiye kadarki kodlarda çok yerde kullanmıştık. Dizgileri anlamaya yarayan iki olanağı da bundan önceki üç bölümde gördük: diziler ve karakterler.
Dizgiler o iki olanağın bileşiminden başka bir şey değildir: elemanlarının türü karakter olan dizilere dizgi denir. Örneğin char[]
bir dizgi türüdür. Ancak, Karakterler bölümünde gördüğümüz gibi, D'de üç değişik karakter türü olduğu için, üç değişik dizgi türünden ve bunların bazen şaşırtıcı olabilecek etkileşimlerinden söz etmek gerekir.
readf
yerine readln
ve strip
Konsoldan satır okuma ile ilgili bazı karışıklıklara burada değinmek istiyorum.
Dizgiler karakter dizileri oldukları için satır sonu anlamına gelen '\n'
gibi kontrol karakterlerini de barındırabilirler. O yüzden, girdiğimiz bilgilerin sonunda bastığımız Enter tuşunu temsil eden kodlar da okunurlar ve dizginin parçası haline gelirler.
Dahası, girişten kaç karakter okunmak istendiği de bilinmediği için readf
giriş tükenene kadar gelen bütün karakterleri dizginin içine okur.
Bunun sonucunda da şimdiye kadar kullanmaya alıştığımız readf
istediğimiz gibi işlemez:
import std.stdio; void main() { char[] isim; write("İsminiz nedir? "); readf(" %s", &isim); writeln("Çok memnun oldum ", isim, "!"); }
Yazılan isimden sonra basılan Enter girişi sonlandırmaz, ve readf
dizgiye eklemek için karakter beklemeye devam eder:
İsminiz nedir? Mert ← Enter'a basıldığı halde giriş sonlanmaz ← (bir kere daha basıldığını varsayalım)
Konsolda girişi sonlandırmak için Linux ortamlarında Ctrl-D'ye, Windows ortamlarında da Ctrl-Z'ye basılır. Girişi o şekilde sonlandırdığınızda Enter'lar nedeniyle oluşan satır sonu kodlarının bile dizginin parçası haline geldiklerini görürsünüz:
Çok memnun oldum Mert ← isimden sonra satır sonu karakteri var ! ← (bir tane daha)
İsimden hemen sonra yazdırılmak istenen ünlem işareti satır sonu kodlarından sonra belirmiştir.
Bu yüzden readf
çoğu durumda girişten dizgi okumaya uygun değildir. Onun yerine ismi "satır oku" anlamındaki "read line"dan türemiş olan readln
kullanılabilir.
readln
'ın kullanımı readf
'ten farklıdır; " %s"
düzen dizgisini ve &
işlecini gerektirmez:
import std.stdio; void main() { char[] isim; write("İsminiz nedir? "); readln(isim); writeln("Çok memnun oldum ", isim, "!"); }
Buna rağmen satır sonunu belirleyen kodu o da barındırır:
İsminiz nedir? Mert
Çok memnun oldum Mert
! ← isimden sonra yine "satır sonu" var
Dizgilerin sonundaki satır sonu kodları ve bütün boşluk karakterleri std.string
modülünde tanımlanmış olan strip
işlevi ile silinebilir:
import std.stdio; import std.string; void main() { char[] isim; write("İsminiz nedir? "); readln(isim); isim = strip(isim); writeln("Çok memnun oldum ", isim, "!"); }
Yukarıdaki strip
ifadesi isim
'in sonundaki satır sonu kodlarının silinmiş halini döndürür. O halinin tekrar isim
'e atanması da isim
'i değiştirmiş olur:
İsminiz nedir? Mert
Çok memnun oldum Mert! ← "satır sonu" kodlarından arınmış olarak
readln
ve strip
zincirleme biçimde daha kısa olarak da yazılabilirler:
string isim = strip(readln());
O yazımı string
türünü tanıttıktan sonra kullanmaya başlayacağım.
Dizgiden veri okumak için formattedRead
Girişten veya herhangi başka bir kaynaktan edinilmiş olan bir dizginin içeriği std.format
modülündeki formattedRead()
ile okunabilir. Bu işlevin ilk parametresi veriyi içeren dizgidir. Sonraki parametreler ise aynı readf
'teki anlamdadır:
import std.stdio; import std.string; import std.format; void main() { write("İsminizi ve yaşınızı aralarında boşluk" ~ " karakteriyle girin: "); string satır = strip(readln()); string isim; int yaş; formattedRead(satır, " %s %s", isim, yaş); writeln("İsminiz ", isim, ", yaşınız ", yaş, '.'); }
İsminizi ve yaşınızı aralarında boşluk karakteriyle girin: Mert 30 İsminiz Mert, yaşınız 30.
Aslında, hem readf
hem de formattedRead
başarıyla okuyup dönüştürdükleri veri adedini döndürürler. Dizginin geçerli düzende olup olmadığı bu değer beklenen adet ile karşılaştırılarak belirlenir. Örneğin, yukarıdaki formattedRead
çağrısı string
türündeki isimden ve int
türündeki yaştan oluşan iki adet veri beklediğinden, dizginin geçerliliği aşağıdaki gibi denetlenebilir:
uint adet = formattedRead(satır, " %s %s", isim, yaş); if (adet != 2) { writeln("Hata: Geçersiz satır."); } else { writeln("İsminiz ", isim, ", yaşınız ", yaş, '.'); }
Girilen veri isim
ve yaş
değişkenlerine dönüştürülemiyorsa program hata verir:
İsminizi ve yaşınızı aralarında boşluk karakteriyle girin: Mert
Hata: Geçersiz satır.
Tek tırnak değil, çift tırnak
Tek tırnakların karakter sabiti tanımlarken kullanıldıklarını görmüştük. Dizgi sabitleri için ise çift tırnaklar kullanılır: 'a'
karakterdir, "a"
tek karakterli bir dizgidir.
string
, wstring
, ve dstring
değişmezdirler
D'de üç karakter türüne karşılık gelen üç farklı karakter dizisi türü vardır: char[]
, wchar[]
, ve dchar[]
.
Bu üç dizi türünün değişmez olanlarını göstermek için üç tane de takma isim vardır: string
, wstring
, ve dstring
. Bu takma isimler kullanılarak tanımlanan dizgilerin karakterleri değişmezdirler. Bir örnek olarak, bir wchar[]
değişkeninin karakterleri değişebilir ama bir wstring
değişkeninin karakterleri değişemez. (D'nin değişmezlik kavramının ayrıntılarını daha sonraki bölümlerde göreceğiz.)
Örneğin bir string
'in baş harfini büyütmeye çalışan şu kodda bir derleme hatası vardır:
string değişmez = "merhaba"; değişmez[0] = 'M'; // ← derleme HATASI
Buna bakarak, değiştirilmesi istenen dizgilerin dizi yazımıyla yazılabileceklerini düşünebiliriz ama o da derlenemez. Sol tarafı dizi yazımıyla yazarsak:
char[] bir_dilim = "merhaba"; // ← derleme HATASI
O kod da derlenemez. Bunun iki nedeni vardır:
"merhaba"
gibi kodun içine hazır olarak yazılan dizgilerin türüstring
'dir ve bu yüzden değişmezdirler.- Türü
char[]
olan sol taraf, sağ tarafın bir dilimidir.
Bir önceki bölümden hatırlayacağınız gibi, sol taraf sağ tarafı gösteren bir dilim olarak algılanır. char[]
değişebilir ve string
değişmez olduğu için de burada bir uyumsuzluk oluşur: derleyici, değişebilen bir dilim ile değişmez bir diziye erişilmesine izin vermemektedir.
Bu durumda yapılması gereken, değişmez dizinin bir kopyasını almaktır. Bir önceki bölümde gördüğümüz .dup
niteliğini kullanarak:
import std.stdio; void main() { char[] dizgi = "merhaba".dup; dizgi[0] = 'M'; writeln(dizgi); }
Derlenir ve dizginin baş harfi değişir:
Merhaba
Benzer şekilde, örneğin string
gereken yerde de char[]
kullanılamaz. Değişebilen char[]
türünden, değiştirilemeyen string
türünü üretmek için de .idup
niteliğini kullanmak gerekebilir. s
'nin türü char[]
olduğunda aşağıdaki satır derlenemez:
string sonuç = s ~ '.'; // ← derleme HATASI
s
'nin türü char[]
olduğu için sağ taraftaki sonucun türü de char[]
'dır. Bütün o sonuç kullanılarak değişmez bir dizgi elde etmek için .idup
kullanılabilir:
string sonuç = (s ~ '.').idup;
Dizgilerin şaşırtıcı olabilen uzunlukları
Unicode karakterlerinin bazılarının birden fazla baytla gösterildiklerini ve Türk alfabesine özgü harflerin iki baytlık olduklarını görmüştük. Bu bazen şaşırtıcı olabilir:
writeln("aĞ".length);
"aĞ" dizgisi 2 harf içerdiği halde dizinin uzunluğu 3'tür:
3
Bunun nedeni, "merhaba" şeklinde yazılan hazır dizgilerin eleman türünün char
olmasıdır. char
da UTF-8 kod birimi olduğu için, o dizginin uzunluğu 3'tür (a için tek, Ğ için iki kod birimi).
Bunun görünür bir etkisi, iki baytlık bir harfi tek baytlık bir harfle değiştirmeye çalıştığımızda karşımıza çıkar:
char[] d = "aĞ".dup; writeln("Önce: ", d); d[1] = 't'; writeln("Sonra:", d);
Önce: aĞ
Sonra:at� ← YANLIŞ
O kodda dizginin 'Ğ' harfinin 't' harfi ile değiştirilmesi istenmiş, ancak 't' harfi tek bayttan oluştuğu için 'Ğ'yi oluşturan baytlardan ancak birincisinin yerine geçmiş ve ikinci bayt çıktıda belirsiz bir karaktere dönüşmüştür.
O yüzden, bazı başka programlama dillerinin normal karakter türü olan char
'ı D'de bu amaçla kullanamayız. (Aynı sakınca wchar
'da da vardır.) Unicode'un tanımladığı anlamda harflerle, imlerle, ve diğer simgelerle ilgilendiğimiz durumlarda dchar
türünü kullanmamız gerekir:
dchar[] d = "aĞ"d.dup; writeln("Önce: ", d); d[1] = 't'; writeln("Sonra:", d);
Önce: aĞ Sonra:at
Doğru çalışan kodda iki değişiklik yapıldığına dikkat edin:
- Dizginin türü
dchar[]
olarak belirlenmiştir. "aĞ"d
hazır dizgisinin sonundad
belirteci kullanılmıştır.
Buna rağmen, dchar[]
ve dstring
kullanımı karakterlerle ilgili bütün sorunları çözemez. Örneğin, kullanıcının girdiği "aĞ" dizgisinin uzunluğu 2 olmayabilir çünkü örneğin 'Ğ' tek Unicode karakteri olarak değil, 'G' ve sonrasında gelen birleştirici (combining) breve şapkası olarak kodlanmış olabilir. Unicode ile ilgili bu tür sorunlardan kaçınmanın en kolay yolu bir Unicode kütüphanesi kullanmaktır.
Hazır dizgiler
Hazır dizgilerin özellikle belirli bir karakter türünden olmasını sağlamak için sonlarına belirleyici karakterler eklenir:
import std.stdio; void main() { string s = "aĞ"c; // bu, "aĞ" ile aynı şeydir wstring w = "aĞ"w; dstring d = "aĞ"d; writeln(s.length); writeln(w.length); writeln(d.length); }
3 2 2
a ve Ğ harflerinin her ikisi de wchar
ve dchar
türlerinden tek bir elemana sığabildiklerinden son iki dizginin uzunlukları 2 olmaktadır.
Dizgi birleştirmek
Dizgiler aslında dizi olduklarından, dizi işlemleri onlar için de geçerlidir. İki dizgiyi birleştirmek için ~
işleci, bir dizginin sonuna başka dizgi eklemek için de ~=
işleci kullanılır:
import std.stdio; import std.string; void main() { write("İsminiz? "); string isim = strip(readln()); // Birleştirme örneği: string selam = "Merhaba " ~ isim; // Sonuna ekleme örneği: selam ~= "! Hoşgeldin..."; writeln(selam); }
İsminiz? Can Merhaba Can! Hoşgeldin...
Dizgileri karşılaştırmak
Not: Unicode bütün yazı sistemlerindeki harfleri tanımlasa da onların o yazı sistemlerinde nasıl sıralandıklarını belirlemez. Aşağıdaki işlevleri kullanırken bu konuda beklenmedik sonuçlarla karşılaşabilirsiniz.
Daha önce sayıların küçüklük büyüklük karşılaştırmalarında kullanılan <
, >=
, vs. işleçlerini görmüştük. Aynı işleçleri dizgilerle de kullanabiliriz. Bu işleçlerin küçüklük kavramı dizgilerde alfabetik sırada önce anlamındadır. Benzer şekilde, büyüklük de alfabetik sırada sonra demektir:
import std.stdio; import std.string; void main() { write(" Bir dizgi giriniz: "); string dizgi_1 = strip(readln()); write("Bir dizgi daha giriniz: "); string dizgi_2 = strip(readln()); if (dizgi_1 == dizgi_2) { writeln("İkisi aynı!"); } else { string önceki; string sonraki; if (dizgi_1 < dizgi_2) { önceki = dizgi_1; sonraki = dizgi_2; } else { önceki = dizgi_2; sonraki = dizgi_1; } writeln("Sıralamada önce '", önceki, "', sonra '", sonraki, "' gelir."); } }
Büyük küçük harfler farklıdır
Harflerin büyük ve küçük hallerinin farklı karakter kodlarına sahip olmaları onların birbirlerinden farklı oldukları gerçeğini de getirir. Örneğin 'A' ile 'a' farklı harflerdir.
Ek olarak, ASCII tablosundaki kodlarının bir yansıması olarak, büyük harflerin hepsi, sıralamada küçük harflerin hepsinden önce gelir. Örneğin büyük olduğu için 'B', sıralamada 'a'dan önce gelir. Aynı şekilde, "aT" dizgisi, 'T' harfi 'ç'den önce olduğu için "aç" dizgisinden önce sıralanır.
Bazen dizgileri harflerin küçük veya büyük olmalarına bakmaksızın karşılaştırmak isteriz. Böyle durumlarda yukarıda gösterilen aritmetik işleçler yerine, std.string.icmp
işlevi kullanılabilir.
std.string modülü
std.string
modülü dizgilerle ilgili işlevler içerir. Bu işlevlerin tam listesini kendi belgesinde bulabilirsiniz.
Oradaki işlevler arasından bir kaç tanesi:
indexOf
: Verilen karakteri bir dizgi içinde baştan sona doğru arar ve bulursa bulduğu yerin indeksini, bulamazsa -1 değerini döndürür. Seçime bağlı olarak bildirilebilen üçüncü parametre, küçük büyük harf ayrımı olmadan aranmasını sağlarlastIndexOf
:indexOf
'a benzer şekilde çalışır. Farkı, sondan başa doğru aramasıdırcountChars
: Birinci dizgi içinde ikinci dizgiden kaç tane bulunduğunu sayartoLower
: Verilen dizginin, bütün harfleri küçük olan eşdeğerini döndürürtoUpper
:toLower
'a benzer şekilde çalışır. Farkı, büyük harf kullanmasıdırstrip
: Dizginin başındaki ve sonundaki boşlukları silerinsert
: Dizginin içine başka dizgi yerleştirir
Dizgiler de aslında dizi olduklarından, diziler için yararlı işlevler içeren std.array
, std.algorithm
ve std.range
modüllerindeki işlevler de dizgilerle kullanılabilir.
Problemler
- std.string modülünün belgesini inceleyin.
~
işlecini de kullanan bir program yazın: Kullanıcı bütünüyle küçük harflerden oluşan ad ve soyad girsin; program önce bu iki kelimeyi aralarında boşluk olacak şekilde birleştirsin ve sonra baş harflerini büyütsün. Örneğin "ebru" ve "domates" girildiğinde programın çıktısı "Ebru Domates" olsun.- Kullanıcıdan bir satır alın. Satırın içindeki ilk 'a' harfinden, satırın içindeki son 'a' harfine kadar olan bölümü yazdırsın. Örneğin kullanıcı "balıkadam" dizgisini girdiğinde ekrana "alıkada" yazdırılsın.
Bu programda
indexOf
velastIndexOf
işlevlerini kullanarak iki değişik indeks bulmanız, ve bu indekslerle bir dilim oluşturmanız işe yarayabilir.indexOf
velastIndexOf
işlevlerinin dönüş türleriint
değil,ptrdiff_t
'dir. İlk 'a' harfini bulmak için şöyle bir satır kullanabilirsiniz:ptrdiff_t ilk_a = indexOf(satır, 'a');
Bir kaç bölüm sonra göreceğimiz
auto
anahtar sözcüğü ile daha da kısa olabilir:auto ilk_a = indexOf(satır, 'a');