D.ershane D Programlama Dili Dersleri

adres: [address], değişkenin (veya nesnenin) bellekte bulunduğu yer
atama: [assign], değişkene yeni bir değer vermek
çöp toplayıcı: [garbage collector], işi biten nesneleri sonlandıran düzenek
değer türü: [value type], değer taşıyan tür
değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
kopyalama: [copy construct], nesneyi başka bir nesnenin kopyası olarak kurmak
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ğişken
referans türü: [reference type], başka bir nesneye erişim sağlayan tür
sınıf: [class], kendi üzerinde kullanılan işlevleri de tanımlayan veri yapısı
sonlandırma: [destruct], nesneyi kullanımdan kaldırırken gereken işlemleri yapmak
varsayılan: [default], özellikle belirtilmediğinde kullanılan
yapı: [struct], başka verileri bir araya getiren veri yapısı
yaşam süreci: [object lifetime], bir değişkenin veya nesnenin tanımlanmasından işinin bitmesine kadar geçen süre
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Yaşam Süreçleri ve Temel İşlemler

Çok yakında yapı ve sınıfları anlatmaya başlayacağım.

Yapıların kullanıcı türlerinin temeli olduklarını göreceğiz. Onlar sayesinde temel türleri ve başka yapıları bir araya getirerek yeni türler oluşturabileceğiz.

Daha sonra, D'nin nesneye dayalı programlama olanaklarının temelini oluşturan sınıfları tanıyacağız. Sınıflar, başka türleri bir araya getirmenin yanında bu türlerle ilgili özel işlemleri de belirlememizi sağlayacaklar.

O konulara geçmeden önce, şimdiye kadar hiç üzerinde durmadan kullandığımız bazı temel kavramları ve temel işlemleri açıklamam gerekiyor. Bu kavramlar ileride yapı ve sınıf tasarımları sırasında yararlı olacak.

Şimdiye kadar kavramları temsil eden veri yapılarına değişken adını verdik. Bir kaç noktada da yapı ve sınıf türünden olan değişkenlere özel olarak nesne dedik. Ben bu derste bunların hepsine birden genel olarak değişken diyeceğim. Herhangi bir türden olan herhangi bir veri yapısı, en azından bu ders kapsamında değişken adını alacak.

Bu derste yalnızca şimdiye kadar gördüğümüz temel türleri, dizileri, ve eşleme tablolarını kullanacağım; siz bu kavramların bütün türler için geçerli olduklarını aklınızda tutun.

Değişkenlerin yaşam süreçleri

Bir değişkenin tanımlanması ile başlayan ve geçerliliğinin bitmesine kadar geçen süreye o değişkenin yaşam süreci denir.

Geçerliliğin bitmesi kavramını isim alanı dersinde değişkenin tanımlandığı kapsamdan çıkılması olarak tanımlamıştım.

O konuyu hatırlamak için şu örneğe bakalım:

void hızDenemesi()
{
    int hız;                      // tek bir değişken ...

    for (int i = 0; i != 10; ++i) {
        hız = 100 + i;            // ... 10 farklı değer alır
        // ...
    }
}                                 // yaşamı burada sonlanır

O koddaki hız değişkeninin yaşam süreci hızDenemesi işlevinden çıkıldığında sona erer. Orada 100 ile 109 arasında 10 değişik değer alan tek bir değişken vardır.

Aşağıdaki kodda ise durum yaşam süreçleri açısından çok farklıdır:

void hızDenemesi()
{
    for (int i = 0; i != 10; ++i) {
        int hız = 100 + i;        // 10 farklı değişken vardır
        // ...
    }                             // yaşamları burada sonlanır
}

O kodda her birisi tek bir değer alan 10 farklı değişken vardır: döngünün her tekrarında hız isminde yeni bir değişken yaşamaya başlar; yaşamı, döngünün kapama parantezinde sona erer.

Parametrelerin yaşam süreçleri

İşlev parametreleri dersinde tanıdığımız parametre türlerine bir de yaşam süreçleri açısından bakmakta yarar var:

in: Parametrenin yaşamı işleve girildiği an başlar ve işlevden çıkıldığı an sona erer. Parametrenin değeri, işlev çağrılırken kullanılan değerin bir kopyasıdır.

ref: Parametre aslında işlev çağrıldığında kullanılan değişkenin takma ismidir. Parametrenin asıl değişkenin yaşam süreci üzerinde etkisi yoktur.

out: Parametre aslında işlev çağrıldığında kullanılan değişkenin takma ismidir. ref'ten farklı olarak, işleve girildiğinde asıl değişkene önce otomatik olarak türünün .init değeri atanır, ve bu değer daha sonra işlev içinde değiştirilebilir.

lazy: Parametre tembel olarak işletildiğinden, yaşamı kullanıldığı an başlar ve yine o an biter.

Bu dört parametre türünü kullanan ve yaşam süreçlerini açıklayan bir örnek:

void main()
{
    int main_in;      // değeri işleve kopyalanır

    int main_ref;     // işleve kendisi olarak ve kendi
                      // değeriyle gönderilir

    int main_out;     // işleve kendisi olarak gönderilir;
                      // işleve girildiği an değeri sıfırlanır

    işlev(main_in, main_ref, main_out, birHesap());
}

void işlev(
    in int p_in,      // yaşamı main_in'in kopyası olarak
                      // işleve girilirken başlar ve işlevden
                      // çıkılırken sonlanır

    ref int p_ref,    // main_ref'in takma ismidir

    out int p_out,    // main_out'un takma ismidir; ref'ten
                      // farklı olarak, işleve girildiğinde
                      // değeri önce int.init olarak atanır

    lazy int p_lazy)  // yaşamı işlev içinde kullanıldığı an
                      // başlar ve eğer kullanımı bitmişse
                      // hemen o an sonlanır; değeri için,
                      // her kullanıldığı an 'birHesap' işlevi
                      // çağrılır
{
    // ...
}

int birHesap()
{
    // ...
}
Temel işlemler

Hangi türden olursa olsun, bir değişkenin yaşamı boyunca etkili olan üç temel işlem vardır:

Değişkenlerin yaşam süreçleri, kurma işlemiyle başlar ve sonlandırma işlemiyle sona erer. Bu süreç boyunca değişkene yeni değerler atanabilir.

Kurma

Her değişken, kullanılmadan önce kurulmak zorundadır. Burada "kurma" sözcüğünü "hazırlamak, inşa etmek" anlamlarında kullanıyorum. Kurma iki alt adımdan oluşur:

  1. Yer ayrılması: Değişkenin yaşayacağı yer belirlenir.
  2. İlk değerinin verilmesi: O adrese ilk değeri yerleştirilir.

Her değişken bilgisayarın belleğinde kendisine ayrılan bir yerde yaşar. Derleyicinin istediğimiz işleri yaptırmak için mikro işlemcinin anladığı dilde kodlar ürettiğini biliyorsunuz. Derleyicinin ürettiği kodların bir bölümünün görevi, tanımlanan değişkenler için bellekten yer ayırmaktır.

Örneğin tamsayı bir kavramı temsil eden şöyle bir değişken

    int hız = 123;

bellekte bir int'in büyüklüğü kadar yer tutar. Belleği soldan sağa doğru bir şerit halinde devam ediyormuş gibi gösterirsek, o değişkenin bellekte şu şekilde yaşadığını düşünebiliriz:

   --+-----+-----+-----+--
     |     | 123 |     |
   --+-----+-----+-----+--

Her değişkenin bellekte bulunduğu yere o değişkenin adresi denir. Bir anlamda o değişken o adreste yaşamaktadır. Programda bir değişkenin değerini değiştirdiğimizde, o değişken için bellekte ayrılan yere, değişkenin yeni değeri yerleştirilir:

    ++hız;

Aynı adresteki değer bir artar:

   --+-----+-----+-----+--
     |     | 124 |     |
   --+-----+-----+-----+--

Kurma, değişkenin yaşamı başladığı anda gerçekleştirilir; çünkü değişkeni kullanıma hazırlayan işlemleri içerir. Onun herhangi bir şekilde kullanılabilmesi için kurulmuş olması önemlidir.

Değişkenler üç farklı şekilde kurulabilirler:

Hiçbir değer kullanılmadan kurulduğunda değişkenin değeri o türün varsayılan değeridir. Varsayılan değer, her türün .init niteliğidir:

    int hız;

O durumda hız'ın değeri int.init'tir; yani 0. Varsayılan değerle kurulan bir değişkenin daha sonradan başka değerler alacağını düşünebiliriz.

    File dosya;

Dosyalar dersinde gördüğümüz std.stdio.File türünden olan yukarıdaki dosya nesnesi ise işletim sisteminin hiçbir dosyasına bağlı olmayan bir File yapısı nesnesidir. Onun da işletim sisteminin hangi dosyasına erişmek için kullanılacağının daha sonradan belirleneceğini düşünebiliriz; varsayılan şekilde kurulmuş olduğu için henüz kullanılamaz.

Değişken bazen başka bir değişkenin değeri kopyalanarak kurulur:

    int hız = başkaHız;

O durumda hız'ın değeri başkaHız'ın değerinden kopyalanır ve hız yaşamına o değerle başlar. Sınıf değişkenlerinde ise durum farklıdır:

    auto sınıfDeğişkeni = başkaSınıfDeğişkeni;

sınıfDeğişkeni de yaşamına başkaSınıfDeğişkeni'nin kopyası olarak başlar.

Aralarındaki önemli ayrım; hız ile başkaHız'ın birbirlerinden farklı iki değer olmaları, öte yandan sınıfDeğişkeni ile başkaSınıfDeğişkeni'nin aynı nesneye erişim sağlamalarıdır. Bu önemli nokta, değer türleri ile referans türleri arasındaki farktan ileri gelir. Bu konuyu bir sonraki derste anlatacağım.

Son olarak, değişkenler belirli değerlerle veya özel şekillerde kurulabilirler:

   int hız = birHesabınSonucu();

hız'ın ilk değeri, programın çalışması sırasındaki bir hesabın değeri olarak belirlenir.

   auto sınıfDeğişkeni = new BirSınıf;

sınıfDeğişkeni, yaşamına new ile kurulan nesneye erişim sağlayacak şekilde başlar.

Sonlandırma

Değişkenin yaşamının sona ermesi sırasında yapılan işlemlere sonlandırma denir. Kurma gibi, sonlandırma da iki adımdan oluşur:

  1. son işlemler: değişkenin yapması gereken son işlemler işletilir
  2. belleğin geri verilmesi: değişkenin yaşadığı yer geri verilir

Temel türlerin çoğunda, sonlandırma sırasında özel işlemler gerekmez. Örneğin int türünden bir değişkenin bellekte yaşamakta olduğu yere sıfır gibi özel bir değer atanmaz. Program o adresin artık boş olduğunun hesabını tutar ve orayı daha sonra başka değişkenler için kullanır.

Öte yandan, bazı türlerden olan değişkenlerin yaşamlarının sonlanması sırasında özel işlemler gerekebilir. Örneğin bir File nesnesi, eğer varsa, ara belleğinde tutmakta olduğu karakterleri diske yazmak zorundadır. Ek olarak, dosyayla işinin bittiğini işletim sistemine bildirmek için de dosyayı kapatmak zorundadır. Bu işlemler, File'ın sonlandırma işlemleridir.

Dizilerde durum biraz daha üst düzeydedir: bütün dizi sonlanırken, o dizinin sahip olduğu bütün elemanlar da sonlanırlar. Eğer dizinin elemanları temel türlerdense, özel bir sonlanma işlemi gerekmez. Ama eğer dizinin elemanları sonlanma gerektiren bir yapı veya sınıf türündense, o türün sonlandırma işlemleri her eleman için uygulanır.

Sonlandırma, eşleme tablolarında da dizilerdeki gibidir. Ek olarak, eşleme tablosunun sahip olduğu indeks değişkenleri de sonlandırılırlar. Eğer indeks türü olarak bir yapı veya sınıf türü kullanılmışsa, her indeks nesnesi için o türün gerektirdiği sonlandırma işlemleri uygulanır.

Çöp toplayıcı: D, çöp toplayıcılı bir dildir. Bu tür dillerde sonlandırma işlemleri programcı tarafından açıkça yapılmak zorunda değildir. Yaşamı sona eren bir değişkenin sonlandırılması otomatik olarak çöp toplayıcı denen düzenek tarafından halledilir.

Değişkenler iki şekilde sonlandırılabilirler:

Bir değişkenin bunlardan hangi şekilde sonlandırılacağı öncelikle kendi türüne bağlıdır.

Temel türlerin hemen sonlandırıldıklarını düşünebilirsiniz çünkü zaten sonlandırma için özel işlemleri yoktur. Bazı türlerin değişkenlerinin son işlemleri ise çöp toplayıcı tarafından daha sonraki bir zamanda işletilebilir.

Atama

Bir değişkenin yaşamı boyunca karşılaştığı diğer önemli işlem atamadır.

Temel türlerde atama işlemi yalnızca değişkenin değerinin değiştirilmesi olarak görülebilir. Yukarıdaki bellek gösteriminde olduğu gibi, değişken örneğin 123 olan bir değer yerine artık 124 değerine sahip olabilir.

Daha genel olarak aslında atama işlemi de iki adımdan oluşur:

  1. eski değerin sonlandırılması: eğer varsa, sonlandırma işlemleri ya hemen, ya da çöp toplayıcı tarafından daha sonra yapılır
  2. yeni değerin verilmesi: eski değerin yerine yeni değer atanır

Bu iki adım, sonlandırma işlemleri olmayan temel türlerde önemli değildir. Ama sonlandırma işlemleri bulunan türlerde atamanın böyle iki adımdan oluştuğunu akılda tutmakta yarar vardır: atama aslında bir sonlandırma ve bir yeni değer verme işlemidir.

Bundan sonraki derste türlerin nasıl değer türleri ve referans türleri diye ikiye ayrıldıklarını anlatacağım.