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:
- Kurma: Yaşamın başlangıcı.
- Sonlandırma: Yaşamın sonu.
- Atama: Değerin değişmesi.
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:
- Yer ayrılması: Değişkenin yaşayacağı yer belirlenir.
- İ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:
- varsayılan şekilde: biz değer vermezsek
- kopyalanarak: başka bir değişkenin değeriyle
- belirli bir değerle: bizim tarafımızdan belirlenen değerle
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:
- son işlemler: değişkenin yapması gereken son işlemler işletilir
- 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:
- hemen: sonlandırma işlemleri hemen işletilir
- sonra: çöp toplayıcı tarafından ilerideki bir zamanda
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:
- 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
- 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.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları