D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

akım: [stream], nesnelerin art arda erişildiği giriş çıkış birimi
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
dilim: [slice], başka bir dizinin bir bölümüne erişim sağlayan yapı
dizgi: [string], "merhaba" gibi bir karakter dizisi
dizi: [array], elemanları yan yana duran ve indeksle erişilen topluluk
işlev: [function], programdaki bir kaç adımı bir araya getiren program parçası
karakter: [character], 'a', '€', '\n', gibi en alt düzey metin parçası
parametre: [parameter], işleve işini yapması için verilen bilgi
Phobos: [Phobos], D dilinin standart kütüphanesi
takma isim: [alias], var olan bir olanağın başka bir ismi
... bütün sözlük



İngilizce Kaynaklar


Diğer




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:

  1. "merhaba" gibi kodun içine hazır olarak yazılan dizgilerin türü string'dir ve bu yüzden değişmezdirler.
  2. 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:

  1. Dizginin türü dchar[] olarak belirlenmiştir.
  2. "aĞ"d hazır dizgisinin sonunda d 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:

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
  1. std.string modülünün belgesini inceleyin.
  2. ~ 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.
  3. 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 ve lastIndexOf işlevlerini kullanarak iki değişik indeks bulmanız, ve bu indekslerle bir dilim oluşturmanız işe yarayabilir.

    indexOf ve lastIndexOf işlevlerinin dönüş türleri int 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');