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

atama: [assign], değişkene yeni bir değer vermek
çökme: [crash], programın hata ile sonlanması
değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
dinamik: [dynamic], çalışma zamanında değişebilen
dizi: [array], elemanları yan yana duran ve indeksle erişilen topluluk
eleman: [element], topluluktaki verilerin her biri
indeks: [index], topluluk elemanlarına erişmek için kullanılan bilgi
kalan: [modulus], bölme işleminin kalan değeri
tanım: [definition], bir ismin neyi ifade ettiğinin belirtilmesi
topluluk: [container], aynı türden birden fazla veriyi bir araya getiren veri yapısı
... bütün sözlük



İngilizce Kaynaklar


Diğer




Diziler

Bir önceki bölümün problemlerinden birisinde 5 tane değişken tanımlamış ve onlarla belirli işlemler yapmıştık: önce iki katlarını almıştık, sonra da beşe bölmüştük. O değişkenleri ayrı ayrı şöyle tanımlamıştık:

    double sayı_1;
    double sayı_2;
    double sayı_3;
    double sayı_4;
    double sayı_5;

Bu yöntem her duruma uygun değildir, çünkü değişken sayısı arttığında onları teker teker tanımlamak, içinden çıkılmaz bir hal alır. Bin tane sayıyla işlem yapmak gerektiğini düşünün... Bin tane değişkeni ayrı ayrı sayı_1, sayı_2, ..., sayı_1000 diye tanımlamak hemen hemen olanaksız bir iştir.

Dizilerin bir yararı böyle durumlarda ortaya çıkar: diziler bir seferde birden fazla değişken tanımlamaya yarayan olanaklardır. Birden fazla değişkeni bir araya getirmek için en çok kullanılan veri yapısı da dizidir.

Bu bölüm dizi olanaklarının yalnızca bir bölümünü kapsar. Diğer olanaklarını daha ilerideki Başka Dizi Olanakları bölümünde göreceğiz.

bir bölümde göreceğiz. )
Tanımlanması

Dizi tanımı değişken tanımına çok benzer. Tek farkı, dizide kaç değişken bulunacağının, yani bir seferde kaç değişken tanımlanmakta olduğunun, türün isminden sonraki köşeli parantezler içinde belirtilmesidir. Tek bir değişkenin tanımlanması ile bir dizinin tanımlanmasını şöyle karşılaştırabiliriz:

    int     tekDeğişken;
    int[10] onDeğişkenliDizi;

O iki tanımdan birincisi, şimdiye kadarki kodlarda gördüklerimiz gibi tek değişken tanımıdır; ikincisi ise 10 değişkenden oluşan bir dizidir.

Yukarıda sözü geçen problemdeki 5 ayrı değişkeni 5 elemanlı bir dizi halinde hep birden tanımlamak için şu söz dizimi kullanılır:

    double[5] sayılar;

Bu tanım, "double türünde 5 tane sayı" diye okunabilir. Daha sonra kod içinde kullanıldığında tek bir sayı değişkeni sanılmasın diye ismini de çoğul olarak seçtiğime dikkat edin.

Özetle; dizi tanımı, tür isminin yanına köşeli parantezler içinde yazılan dizi uzunluğundan ve bunları izleyen dizi isminden oluşur:

    tür_ismi[dizi_uzunluğu] dizi_ismi;

Tür ismi olarak temel türler kullanılabileceği gibi, programcının tanımladığı daha karmaşık türler de kullanılabilir (bunları daha sonra göreceğiz). Örnekler:

    // Bütün şehirlerdeki hava durumlarını tutan bir dizi
    // Burada örneğin
    //   false: "kapalı hava"
    //   true : "açık hava"
    // anlamında kullanılabilir
    bool[şehirAdedi] havaDurumları;

    // Yüz kutunun ağırlıklarını ayrı ayrı tutan bir dizi
    double[100] kutuAğırlıkları;

    // Bir okuldaki bütün öğrencilerin kayıtları
    ÖğrenciBilgisi[öğrenciAdedi] öğrenciKayıtları;
Topluluklar ve elemanlar

Aynı türden değişkenleri bir araya getiren veri yapılarına topluluk adı verilir. Bu tanıma uydukları için diziler de toplulukturlar. Örneğin Temmuz ayındaki günlük hava sıcaklıklarını tutmak için kullanılacak bir dizi 31 tane double değişkenini bir araya getirebilir ve double türünde elemanlardan oluşan bir topluluk oluşturur.

Topluluk değişkenlerinin her birisine eleman denir. Dizilerin barındırdıkları eleman adedine dizilerin uzunluğu denir. "Eleman adedi" ve "dizi uzunluğu" ifadelerinin ikisi de sık kullanılır.

Eleman erişimi

Problemdeki değişkenleri ayırt etmek için isimlerinin sonuna bir alt çizgi karakteri ve bir sıra numarası eklemiştik: sayı_1 gibi... Sayıları hep birden bir dizi halinde ve sayılar isminde tanımlayınca elemanlara farklı isimler verme şansımız kalmaz. Onun yerine, elemanlara dizinin erişim işleci olan [] ile ve bir sıra numarasıyla erişilir:

    sayılar[0]

O yazım, "sayıların 0 numaralı elemanı" diye okunabilir. Bu şekilde yazınca sayı_1 ifadesinin yerini sayılar[0] ifadesi almış olur.

Burada dikkat edilmesi gereken iki nokta vardır:

İndeks

Elemanlara erişirken kullanılan sıra numaralarına indeks, elemanlara erişme işine de indeksleme denir.

İndeks sabit bir değer olmak zorunda değildir; indeks olarak değişken değerleri de kullanılabilir. Bu olanak dizilerin kullanışlılığını büyük ölçüde arttırır. Örneğin aşağıdaki kodda hangi aydaki gün sayısının yazdırılacağını ayNumarası değişkeni belirlemektedir:

    writeln("Bu ay ", ayGünleri[ayNumarası], " gün çeker");

ayNumarası'nın 2 olduğu bir durumda yukarıdaki ifadede ayGünleri[2]'nin değeri, yani Mart ayındaki gün adedi yazdırılır. ayNumarası'nın başka bir değerinde de o aydaki gün sayısı yazdırılır.

Yasal olan indeksler, 0'dan dizinin uzunluğundan bir eksiğine kadar olan değerlerdir. Örneğin 3 elemanlı bir dizide yalnızca 0, 1, ve 2 indeksleri yasaldır. Bunun dışında indeks kullanıldığında program bir hata ile sonlanır.

Dizileri, elemanları yan yana duran bir topluluk olarak düşünebilirsiniz. Örneğin ayların günlerini tutan bir dizinin elemanları ve indeksleri şu şekilde gösterilebilir (Şubat'ın 28 gün çektiğini varsayarak):

 indeksler →     0    1    2    3    4    5    6    7    8    9   10   11
 elemanlar →  | 31 | 28 | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 |

Not: Yukarıdaki indeksleri yalnızca gösterim amacıyla kullandım; indeksler belleğe yazılmazlar.

İlk elemanın indeksi 0, ve Ocak ayındaki gün sayısı olan 31 değerine sahip; ikinci elemanın indeksi 1, ve Şubat ayındaki gün sayısı olan 28 değerine sahip; vs.

Sabit uzunluklu diziler ve dinamik diziler

Kaç eleman barındıracakları programın yazıldığı sırada bilinen dizilere sabit uzunluklu dizi; elemanlarının sayısı programın çalışması sırasında değişebilen dizilere dinamik dizi denir.

Yukarıda 5 sayı tanımlamak için kullandığımız sayılar dizisi ve 12 aydaki gün sayılarını tutmak için kullandığımız ayGünleri dizileri sabit uzunluklu dizilerdir; çünkü eleman sayıları baştan belirlenmiştir. O dizilerin uzunlukları programın çalışması sırasında değiştirilemez. Uzunluklarının değişmesi gerekse, bu ancak kaynak koddaki sabit olan değerin elle değiştirilmesi ve programın tekrar derlenmesi ile mümkündür.

Dinamik dizi tanımlamak sabit uzunluklu dizi tanımlamaktan daha kolaydır; dizinin uzunluğunu boş bırakmak diziyi dinamik yapmaya yeter:

    int[] dinamikDizi;

Böyle dizilerin uzunlukları programın çalışması sırasında gerektikçe arttırılabilir veya azaltılabilir.

Eleman adedini edinmek ve değiştirmek için .length

Türlerin olduğu gibi dizilerin de nitelikleri vardır. Burada yalnızca bir tanesini göreceğiz. .length dizideki eleman adedini bildirir:

    writeln("Dizide ", dizi.length, " tane eleman var");

Ek olarak, .length dinamik dizilerde dizinin uzunluğunu değiştirmeye de yarar:

    int[] dizi;            // boştur
    dizi.length = 5;       // uzunluğu 5 olur
Bir dizi örneği

Bu bilgiler ışığında 5 değişkenli probleme dönelim ve onu dizi kullanacak şekilde tekrar yazalım:

import std.stdio;

void main() {
    // Bu değişkeni döngüleri kaç kere tekrarladığımızı saymak
    // için kullanacağız
    int sayaç;

    // double türündeki beş elemandan oluşan sabit uzunluklu
    // bir dizi tanımlıyoruz
    double[5] sayılar;

    // Sayıları bir döngü içinde girişten alıyoruz
    while (sayaç < sayılar.length) {
        write("Sayı ", sayaç + 1, ": ");
        readf(" %s", &sayılar[sayaç]);
        ++sayaç;
    }

    writeln("İki katları:");
    sayaç = 0;
    while (sayaç < sayılar.length) {
        writeln(sayılar[sayaç] * 2);
        ++sayaç;
    }

    // Beşte birlerini hesaplayan döngü de bir önceki
    // döngünün benzeridir...
}

Gözlemler: Döngülerin kaç kere tekrarlanacaklarını sayaç belirliyor: döngüleri, o değişkenin değeri sayılar.length'ten küçük olduğu sürece tekrarlıyoruz. Sayacın değeri her tekrarda bir arttıkça, sayılar[sayaç] ifadesi de sırayla dizinin elemanlarını göstermiş oluyor: sayılar[0], sayılar[1], vs.

Bu programın yararını görmek için girişten 5 yerine örneğin 20 sayı alınacağını düşünün... Dizi kullanan bu programda tek bir yerde küçük bir değişiklik yapmak yeter: 5 değerini 20 olarak değiştirmek... Oysa dizi kullanmayan programda 15 tane daha değişken tanımlamak ve kullanıldıkları kod satırlarını 15 değişken için tekrarlamak gerekirdi.

Elemanları ilklemek

D'de her türde olduğu gibi dizi elemanları da otomatik olarak ilklenirler. Elemanlar için kullanılan ilk değer, elemanların türüne bağlıdır: int için 0, double için double.nan, vs.

Yukarıdaki programdaki sayılar dizisinin beş elemanı da dizi tanımlandığı zaman double.nan değerine sahiptir:

    double[5] sayılar;     // dizinin bütün elemanlarının
                           // ilk değeri double.nan olur

Elemanların bu ilk değerleri dizi kullanıldıkça değişebilir. Bunun örneklerini yukarıdaki programlarda gördük. Örneğin ayGünleri dizisinin 11 indeksli elemanına 31 değerini atadık:

    ayGünleri[11] = 31;

Daha sonra da girişten gelen değeri, sayılar isimli dizinin sayaç indeksli elemanının değeri olarak okuduk:

    readf(" %s", &sayılar[sayaç]);

Bazen elemanların değerleri, dizi kurulduğu anda bilinir. Öyle durumlarda dizi, atama söz dizimiyle ve elemanların ilk değerleri sağ tarafta belirtilerek tanımlanır. Kullanıcıdan ay numarasını alan ve o ayın kaç gün çektiğini yazan bir program düşünelim:

import std.stdio;

void main() {
    // Şubat'ın 28 gün çektiğini varsayıyoruz
    int[12] ayGünleri =
        [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

    write("Kaçıncı ay? ");
    int ayNumarası;
    readf(" %s", &ayNumarası);

    int indeks = ayNumarası - 1;
    writeln(ayNumarası, ". ay ",
            ayGünleri[indeks], " gün çeker");
}

O programda ayGünleri dizisinin elemanlarının dizinin tanımlandığı anda ilklendiklerini görüyorsunuz. Ayrıca, kullanıcıdan alınan ve değeri 1-12 aralığında olan ay numarasının indekse nasıl dönüştürüldüğüne dikkat edin. Böylece kullanıcının 1-12 aralığında verdiği numara, programda 0-11 aralığına dönüştürülmüş olur. Kullanıcı 1-12 aralığının dışında bir değer girdiğinde, program dizinin dışına erişildiğini bildiren bir hata ile sonlanır.

Dizileri ilklerken sağ tarafta tek bir eleman değeri de kullanılabilir. Bu durumda dizinin bütün elemanları o değeri alır:

    int[10] hepsiBir = 1;    // Bütün elemanları 1 olur
Temel dizi işlemleri

Diziler, bütün elemanlarını ilgilendiren bazı işlemlerde büyük kolaylık sağlarlar.

Sabit uzunluklu dizileri kopyalama

Atama işleci, sağdaki dizinin elemanlarının hepsini birden soldaki diziye kopyalar:

    int[5] kaynak = [ 10, 20, 30, 40, 50 ];
    int[5] hedef;

    hedef = kaynak;

Not: Atama işleminin anlamı dinamik dizilerde çok farklıdır; bunu ilerideki bir bölümde göreceğiz.

Dinamik dizilere eleman ekleme

~= işleci, dinamik dizinin sonuna yeni bir eleman veya yeni bir dizi ekler:

    int[] dizi;                // dizi boştur
    dizi ~= 7;                 // dizide tek eleman vardır
    dizi ~= 360;               // dizide iki eleman olur
    dizi ~= [ 30, 40 ];        // dizide dört eleman olur

Sabit uzunluklu dizilere eleman eklenemez:

    int[10] dizi;
    dizi ~= 7;                 // ← derleme HATASI
Dinamik diziden eleman çıkartma

Dizi elemanları std.algorithm modülündeki remove() işlevi ile çıkartılırlar. Aynı elemanlara erişim sağlayan birden fazla dilim olabildiğinden remove() dizideki eleman adedini değiştiremez. Tek yapabildiği, elemanları bir veya daha fazla konum sola kaydırmaktır. Bu yüzden, remove() işlevinin sonucunun tekrar aynı dizi değişkenine atanması gerekir.

remove()'un iki farklı kullanımı vardır:

  1. Çıkartılacak elemanın indeksini belirtmek. Örneğin, aşağıdaki kod indeksi 1 olan elemanı çıkartır.
    import std.stdio;
    import std.algorithm;
    
    void main() {
        int[] dizi = [ 10, 20, 30, 40 ];
        dizi = dizi.remove(1);                // Tekrar dizi'ye atanıyor
        writeln(dizi);
    }
    
    [10, 30, 40]
    
  2. Çıkartılacak elemanı ilerideki bir bölümde göreceğimiz bir isimsiz işlev ile belirtmek. Örneğin, aşağıdaki kod değerleri 42'ye eşit olan elemanları çıkartır.
    import std.stdio;
    import std.algorithm;
    
    void main() {
        int[] dizi = [ 10, 42, 20, 30, 42, 40 ];
        dizi = dizi.remove!(a => a == 42);    // Tekrar dizi'ye atanıyor
        writeln(dizi);
    }
    
    [10, 20, 30, 40]
    
Birleştirme

~ işleci iki diziyi uç uca birleştirerek yeni bir dizi oluşturur. Aynı işlecin atamalı olanı da vardır (~=) ve sağdaki diziyi soldaki dizinin sonuna ekler:

import std.stdio;

void main() {
    int[10] birinci = 1;
    int[10] ikinci = 2;
    int[] sonuç;

    sonuç = birinci ~ ikinci;
    writeln(sonuç.length);     // 20 yazar

    sonuç ~= birinci;
    writeln(sonuç.length);     // 30 yazar
}

Eğer sol tarafta sabit uzunluklu bir dizi varsa, dizinin uzunluğu değiştirilemeyeceği için ~= işleci kullanılamaz:

    int[20] sonuç;
    // ...
    sonuç ~= birinci;          // ← derleme HATASI

Atama işleminde de, sağ tarafın uzunluğu sol tarafa uymazsa program çöker:

    int[10] birinci = 1;
    int[10] ikinci = 2;
    int[21] sonuç;

    sonuç = birinci ~ ikinci;

O kod, programın "dizi kopyası sırasında uzunluklar aynı değil" anlamına gelen hata ile çökmesine neden olur:

object.Error@(0): Array lengths don't match for copy: 20 != 21
Elemanları sıralama

std.algorithm.sort işlevi elemanları küçükten büyüğe doğru sıralar. sort()'tan yararlanabilmek için std.algorithm modülünün eklenmesi gerekir. (İşlevleri daha sonraki bir bölümde göreceğiz.)

import std.stdio;
import std.algorithm;

void main() {
    int[] dizi = [ 4, 3, 1, 5, 2 ];
    sort(dizi);
    writeln(dizi);
}

Çıktısı:

[1, 2, 3, 4, 5]
Elemanları ters çevirmek

std.algorithm.reverse elemanların yerlerini aynı dizi içinde ters çevirir; ilk eleman sonuncu eleman olur, vs.:

import std.stdio;
import std.algorithm;

void main() {
    int[] dizi = [ 4, 3, 1, 5, 2 ];
    reverse(dizi);
    writeln(dizi);
}

Çıktısı:

[2, 5, 1, 3, 4]
Problemler
  1. Yazacağınız program önce kullanıcıdan kaç tane sayı girileceğini öğrensin ve girişten o kadar kesirli sayı alsın. Daha sonra bu sayıları önce küçükten büyüğe, sonra da büyükten küçüğe doğru sıralasın.

    Burada sort ve reverse işlevlerini kullanabilirsiniz.

  2. Başka bir program yazın: girişten aldığı sayıların önce tek olanlarını sırayla, sonra da çift olanlarını sırayla yazdırsın. Özel olarak -1 değeri girişi sonlandırmak için kullanılsın: bu değer geldiğinde artık girişten yeni sayı alınmasın.

    Örneğin girişten

    1 4 7 2 3 8 11 -1
    

    geldiğinde çıkışa şunları yazdırsın:

    1 3 7 11 2 4 8
    

    İpucu: Sayıları iki ayrı diziye yerleştirmek işinize yarayabilir. Girilen sayıların tek veya çift olduklarını da aritmetik işlemler sayfasında öğrendiğiniz % (kalan) işlecinin sonucuna bakarak anlayabilirsiniz.

  3. Bir arkadaşınız yazdığı bir programın doğru çalışmadığını söylüyor.

    Girişten beş tane sayı alan, bu sayıların karelerini bir diziye yerleştiren, ve sonunda da dizinin elemanlarını çıkışa yazdıran bir program yazmaya çalışmış ama programı doğru çalışmıyor.

    Bu programın hatalarını giderin ve beklendiği gibi çalışmasını sağlayın:

    import std.stdio;
    
    void main() {
        int[5] kareler;
    
        writeln("5 tane sayı giriniz");
    
        int i = 0;
        while (i <= 5) {
            int sayı;
            write(i + 1, ". sayı: ");
            readf(" %s", &sayı);
    
            kareler[i] = sayı * sayı;
            ++i;
        }
    
        writeln("=== Sayıların Kareleri ===");
        while (i <= kareler.length) {
            write(kareler[i], " ");
            ++i;
        }
    
        writeln();
    }