D.ershane D Programlama Dili Dersleri

akım: [stream], nesnelerin art arda erişildiği giriş çıkış birimi
BOM: [BOM, byte order mark], dosyanın en başına yazılan Unicode kodlama belirteci
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
enum: [enum], isimli sabit değer olanağı
kapsam: [scope], küme parantezleriyle belirlenen bir alan
klasör: [directory], dosyaları barındıran dosya sistemi yapısı (dizin)
kontrol karakteri: [control character], yeni satır açan '\n', yatay sekme karakteri '\t', gibi özel karakterler
kurma: [construct], yapı veya sınıf nesnesini kullanılabilir duruma getirmek
nesne: [object], belirli bir sınıf veya yapı türünden olan değer
sınıf: [class], kendi üzerinde kullanılan işlevleri de tanımlayan veri yapısı
standart çıkış: [standard output], program çıktısının normalde gönderildiği akım
standart giriş: [standard input], program girişinin normalde okunduğu akım
üye işlev: [member function], yapı veya sınıfın özel işlemleri
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Dosyalar

Ne kadar güçlü olsalar da; önceki bölümde gördüğümüz >, <, ve | her duruma uygun değildir. Çünkü her program işini yalnızca standart giriş ve çıkışla etkileşerek yapamaz.

Örneğin öğrenci kayıtları ile ilgilenen bir program, standart çıkışını kullanıcıya bir komut menüsü göstermek için kullanıyor olabilir. Standart girişini de kullanıcıdan komut almak için kullandığını düşünürsek, böyle bir programın kayıtlarını tuttuğu öğrenci bilgilerini yazmak için bir dosyaya ihtiyacı olacaktır.

Bu bölümde işletim sisteminin klasörlerde barındırdığı dosyalara yazmayı ve dosyalardan okumayı öğreneceğiz.

Temel kavramlar

Bu dersin yazıldığı tarihlerde (Eylül 2009) Phobos kütüphanesinde dosyalarla ilgili iki farklı olanak bulunuyor: std.stdio modülündeki File yapısı ve std.stream modülündeki File sınıfı. Henüz ne yapıları gösterdim, ne de sınıfları. O yüzden, bu iki File'ın kurulmaları sırasındaki söz dizimleri konusunda bazı kabuller yapmanızı bekleyeceğim.

İki olanaktan birisinin ötekisine göre kesin bir üstünlüğü bulunmuyor. Ben sonunda seçimi size bırakacağım. Örneklere geçmeden önce, her ikisi için de geçerli olan genel kavramları tanıtacağım.

Karşı taraf

Bu bölümdeki bilgilerle oluşturulan dosyaların her ortamda rahatça okunabileceklerini düşünmeyin. Benzer şekilde, başka ortamlarda oluşturulmuş dosyaların da bu bölümdeki bilgilerle açılabileceklerini beklemeyin.

Bir dosya oluşturup içine bilgiler yazmak, o dosyanın başka bir ortamda açılıp okunması için yeterli olmayabilir. Dosyayı yazan tarafla okuyan tarafın belirli konularda anlaşmış olmaları gerekir. Örneğin dosyaya char[] olarak yazılmış olan bir bilginin wchar[] olarak okunması yanlış sonuç doğurur.

İki ders sonra Unicode dosyaların en başlarına yazılan BOM belirteçlerini öğreneceksiniz. O belirteçler de bu bölümdeki File olanaklarını yanıltacaklardır.

Bu bölümde göreceğiniz File olanaklarını, şu durumlarda kullanışlı olacaklarını düşünerek öğrenin:

Dosya erişim hakları

İşletim sistemi, dosyaları programlara çeşitli erişim haklarıyla sunar. Erişim hakları hem performans, hem de dosya sağlığı açısından önemlidir.

Konu dosyadan okumak olunca; aynı dosyadan okumak isteyen birden fazla programa aynı anda okuma izni verilmesi, programlar birbirlerini beklemeyecekleri için hız kazancı sağlar. Öte yandan, konu dosyaya yazmak olunca; dosyanın içeriğinin tutarlılığı açısından dosyaya belirli bir anda ancak tek bir programın yazmasına izin verilmelidir; yoksa iki programın birbirlerinden habersiz olarak yazmaları sonucunda dosyanın içeriği tutarsız hale gelebilir.

D dosyaları, erişim haklarından temelde iki tanesi ile ilgilidir: okuma ve yazma erişimi.

Dosya açmak

Programın standart giriş ve çıkış akımları olan din ve dout, program başladığında zaten açılmış ve kullanıma hazır olarak gelirler; onları kullanmaya başlamadan önce özel bir işlem yapmamız gerekmez.

Dosyaların ise diskteki isimleri ve istenen erişim hakları bildirilerek program tarafından açılmaları gerekir.

Dosya kapatmak

Açılan dosyaların mutlaka kapatılmaları da gerekir; ancak, File nesneleri kendileri sonlanırken dosyalarını da kapattıkları için, normalde bu işin programda açıkça yapılması gerekmez. Dosya, File nesnesinin içinde bulunduğu kapsamdan çıkılırken kendiliğinden kapatılır. Örnek:

if (bir_koşul) {

    // dosya burada oluşturulmuş ve kullanılmış olsun
    // ...

} // ← dosya bu kapsamdan çıkılırken otomatik olarak kapatılır

Bazen, aynı dosyanın aynı kapsam içinde ama değişik erişim haklarıyla açılması gerekebilir. Örneğin başlangıçta yazma erişimi ile açılarak içine bilgi yazılmış olan bir dosya; yine aynı kapsam içinde, ama bu sefer okuma erişimi ile açılabilir. Böyle bir durumda, dosyayı önce kapatmak, sonra yeni erişim hakkıyla tekrar açmak gerekir.

Dosyaya yazmak ve dosyadan okumak

Dosyalar da karakter akımları oldukları için, alışık olduğunuz writeln ve readf gibi işlevleri onlarla da kullanabilirsiniz.

Dosya sonu: eof()

Bir dosyadan okurken dosyanın sonuna gelinip gelinmediği, "dosya sonu" anlamına gelen "end of file"ın kısaltması olan eof() üye işleviyle denetlenir. Bu işlev, dosya sonuna gelindiğinde true döndürür.

Klasör işlemleri için std.file modülü

Klasör işlemleri ile ilgili olan std.file modülünün Ddili Wiki'deki sayfasında işinize yarayacak işlevler bulabilirsiniz. Örneğin exists, belirtilen isimde bir dosyanın klasörde zaten bulunup bulunmadığını bildirir:

    if (exists(dosya_ismi)) {
        // dosya mevcut
    } else {
        // dosya mevcut değil
    }

std.stdio.File yapısı

Bu yapının iyi yanları; erişim belirteçlerinin C dilindekilerle aynı olması, ve yapının şimdiye kadar kullandığımız std.stdio modülünde zaten var olmasıdır.

Kötü tarafı, BOM belirteci kullanan Unicode kodlamalarıyla doğru olarak çalışamamasıdır. Bu yapıyı ya bütünüyle ASCII dosyalarla kullanabilirsiniz, ya da BOM içermeyen UTF-8 dosyalarıyla.

Erişim belirteçleri C'nin standart fopen işlevinin kullandıkları ile aynıdır:

 Belirteç  Anlamı
rokuma erişimi
dosya, başından okunacak şekilde hazırlanır
r+okuma ve yazma erişimi
dosya, başından okunacak ve başına yazılacak şekilde hazırlanır
wyazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içi boşaltılır
w+okuma ve yazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içi boşaltılır
asonuna yazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içeriği korunur ve sonuna yazılacak şekilde hazırlanır
a+okuma ve sonuna yazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içeriği korunur; başından okunacak ve sonuna yazılacak şekilde hazırlanır

Yukarıdaki erişim haklarının "rb" gibi sonuna 'b' karakteri gelenleri de vardır. O durumda, dosyaya yazma ve dosyadan okuma işlemleri sırasında karakter dönüşümleri yapılmaz. Bazı özel kodların değişik ortamlardaki alt düzey gösterimleriyle ilgili olan bu konuyu bu derste anlatmayacağım.

Bu yapının belgesini std.stdio wiki sayfasında bulabilirsiniz.

std.stdio.File ile yazma örneği
import std.stdio;

void main()
{
    File dosya = File("ogrenci_bilgisi", "w");

    dosya.writeln("İsim  : ", "Zafer");
    dosya.writeln("Numara: ", 123);
    dosya.writeln("Sınıf : ", "1A");
}

Dizgiler dersinden hatırlayacağınız gibi, "ogrenci_bilgisi" gibi bir dizginin türü string'dir ve değişmezdir. Yani File, dosya ismini ve erişim hakkını string türü olarak kabul eder. Bu yüzden, yine dizgiler bölümünden hatırlayacağınız gibi, File'ı örneğin char[] türünde bir dizgi ile kuramazsınız; kurmanız gerektiğinde o dizginin .idup niteliğini çağırmanız gerekir. Bu bilgiyi problemlerden birisinde hatırlamanız gerekecek.

Yukarıdaki program, çalıştırıldığı klasör içinde ismi ogrenci_bilgisi olan bir dosya oluşturur.

Not: Dosya ismi olarak dosya sisteminin izin verdiği her karakteri kullanabilirsiniz. Ben bu derslerde dosya isimlerinde yalnızca ASCII harfler kullanacağım.

std.stdio.File ile okuma örneği
import std.stdio;

void main()
{
    File dosya = File("ogrenci_bilgisi", "r");

    while (!dosya.eof()) {
        string satır = dosya.readln();
        writeln("Okuduğum satır -> |", satır);
    }
}

Yukarıdaki program, çalıştırıldığı klasör içindeki ogrenci_bilgisi isimli dosyanın içindeki satırları başından sonuna kadar okur ve standart çıkışa yazdırır.


std.stream.File sınıfı

Bu sınıfın iyi yanı, kullanımının din'in ve dout'un kullanımlarından hiçbir farkının bulunmamasıdır. Bu tür dosyaları kullanırken dosyadan yalnızca satır satır değil, değişken değişken de okuyabilirsiniz.

Bu sınıfı da BOM belirteciyle başlayan Unicode dosyalarıyla doğrudan kullanamazsınız. Ancak, iki ders sonra anlatılacağı gibi, File nesneniz ile yeni bir EndianStream nesnesi oluşturursanız, o yeni nesneyle BOM'lu dosyaları doğru olarak okuyabilirsiniz.

Erişim belirteçleri, daha sonra öğreneceğimiz enum değerler olarak tanımlanmıştır:

 Belirteç  Anlamı
FileMode.Inokuma erişimi
dosya, başından okunacak şekilde hazırlanır
FileMode.Outyazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: başına yazılacak şekilde hazırlanır
FileMode.OutNewyazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içi boşaltılır
 FileMode.Append okuma ve sonuna yazma erişimi
dosya yoksa: boş olarak oluşturulur
dosya zaten varsa: içeriği korunur; başından okunacak ve sonuna yazılacak şekilde hazırlanır

Bu sınıfın belgesini std.stream wiki sayfasında bulabilirsiniz.

std.stream.File ile yazma örneği
import std.stream;

void main()
{
    File dosya = new File("ogrenci_bilgisi", FileMode.OutNew);

    dosya.writefln("İsim  : ", "Zafer");
    dosya.writefln("Numara: ", 123);
    dosya.writefln("Sınıf : ", "1A");
}

std.stdio modülündeki File'dan iki farkı olduğuna dikkat edin: Sınıf nesnesi olduğu için new anahtar sözcüğü ile oluşturulur, ve üye işlevin ismi writeln değil, writefln'dir.

std.stream.File ile okuma örneği

File'ın "müsait" anlamına gelen available niteliği, dosyada hâlâ okunacak karakter kalıp kalmadığını, yani okumaya müsait olup olmadığını bildirir.

import std.cstream;
import std.stream;

void main()
{
    File dosya = new File("ogrenci_bilgisi", FileMode.In);

    while (dosya.available) {
        char[] satır = dosya.readLine();
        dout.writefln("Okuduğum satır -> |", satır);
    }
}

std.stdio ile std.stream karışıklığı

Ben bu programda dout.writefln kullandım. Onun yerine daha önceki programlarda gördüğümüz writeln'ı kullanmak isteseydik, writeln'ın içinde bulunduğu std.stdio modülünü de eklememiz gerekirdi. Ne yazık ki öyle yapıldığında bir derleme hatası oluşur:

import std.stdio;    // ← bir File bu modülden gelir
import std.stream;   // ← bir File da bu modülden...

void main()
{
    File dosya = new File("ogrenci_bilgisi", FileMode.In);
 // ^^^^ Derleme HATASI: hangi File olduğu belli değil!

Derleme hatasının nedeni, bizim kısaca File dediğimiz olanağın std.stdio.File ve std.stream.File'dan hangisi olduğunun açık olmamasıdır; derleyici buna kendisi karar veremez. Hem std.stdio modülünü eklemek, hem de std.stream.File sınıfını kullanmak gerektiğinde türün ismini tam olarak yazmamız gerekir:

import std.stdio;
import std.stream;

void main()
{
    std.stream.File dosya =
        new std.stream.File("ogrenci_bilgisi", FileMode.In);

    while (dosya.available) {
        char[] satır = dosya.readLine();
        writeln("Okuduğum satır -> |", satır);
    }
}

Bu programda satırları çıkışa yazdırmak için std.cstream modülündeki dout.writefln değil, std.stdio modülündeki writeln kullanılmıştır.

File türünün ismini uzun olarak yazmak hem daha çok iş gerektirir, hem de hatalı yazma olasılığını arttırır. Bir sonraki derste göreceğiniz auto anahtar sözcüğü ile tür isimlerini yazmanın çok daha kısa olduğunu göreceksiniz.

Problemler
  1. Yazacağınız program kullanıcıdan bir dosya ismi alsın, o dosyanın içindeki boş olmayan bütün satırları, dosyanın ismine .bak eklenmiş başka bir dosyaya yazsın. Örneğin, verilen dosyanın ismi deneme.txt ise, boş olmayan satırlarını deneme.txt.bak dosyasına yazsın.
  2. İşinize yarayacak bilgiler:

    • std.stream modülünün belgelerinde görüldüğü gibi, File'ın kurucu işlevi, dosya ismini string türünde alır. Eğer elinizde değişebilen türde bir dizgi varsa, örneğin bir char[] varsa, dosya ismi olarak onun .idup ile alınmış bir kopyasını kullanmanız gerekecektir
    • Satırın boş olup olmadığını .length niteliğinin değerine bakarak anlayabilirsiniz
  3. Öğrenci bilgileri tutulan bir dosya olduğunu düşünelim. Dosyanın satırlarında önce öğrencinin adı, soyadı, ondan sonra da iki tane ders notu bulunuyor olsun (bu dosya düzeninde öğrencilerin orta isimleri bulunmasın):
  4. Demir Çelikçi 80 95
    Çiçek Böcekçi 97 90
    

    Ben örnek olarak iki öğrenci yazdım. Sizin programınız dosyada belirsiz sayıda öğrenci olduğunda da doğru çalışsın: öğrenci kayıtlarını dosyadan satır satır okusun ve öğrencilerin yalnızca ön isimlerini ve ders ortalamalarını, virgülden sonrasını da gösterecek şekilde yazdırsın:

    Demir: 87.5
    Çiçek: 93.5
    
... çözümler