Delphi ve Operator Overloading
"Operator Overloading"(operator aşırı yükleme), Delphi’ye Delphi.NET ile birlikte gelen class helper’lar gibi yeni özelliklerden birisidir. Bu teknik, class helper’lar gibi hem Win32 hem de .NET için kullanabilirsiniz.
Delphi’ye, belki de çok uzun zaman önce eklenmesi gereken bu özellik ile, herhangi bir sınıf veya record için toplama(+), çıkarma(-), çarpma(*) gibi operatörlere bazı özel anlamlar yükleyebiliyoruz. C++ programcılarının hiç de yabancı olmadıkları bu teknik ile kendimize özel veri tipleri tanımlayıp, bu tiplerin Integer, string gibi başka tipler ile etkileşimlerinin nasıl sonuç vereceğini belirleyebilirsiniz.
Giriş
Delphi programcılarının uzun zamandır en çok yakındıkları ve Delphi derleyicisi tasarımcılarının başını ağrıttıkları belli başlı konular vardır. Birisi şüphesiz parametrize tiplerdir(generics, c++ için STL). Highlander sürümü ile birlikte bu özellik de Delphi’ye kazandırılmış olacak. Diğerleri de nested tipler, class static tipler ve operator overloading olarak sıralanabilir. Parametrize tipler hariç diğerlerini hem Delphi, hem de Delphi.NET için şu anda kullanabiliyoruz.
Bu makalemizde göreceğimiz operator aşırı yükleme veya operator overloading, sınıf ya da record’ların kullanımını büyük bir ölçüde genişletmektedir. Delph.NET için hem record hem de class veri tipleri için operator overloading yapılabilirken, Delphi for Win32 için sadece record veri tiplerinde operator overloading yapılabilir. Ama bu kullanım bakımından o kadar da etki etmemektedir. Hatta çoğu durumda operator overloading için record kullanılmaktadır. Vereceğimiz örnekler sınıflar ile yapılmış olduğundan Delphi.NET ortamında denemelisiniz. Sınıflar ile örnek veriyorum, çünkü recordlar’dan farklı olarak yapılacak fazladan bir kaç işlem var. Mesela record’larda çoğu durumda Result çıkış değeri Record olduğundan, Create ile oluşturmanıza gerek yok.
Ayran’nın Suyu Fazla mı Olmuş, Ne?
Operator overloading’i anlatabilmek için böylesine uçuk sayılabilecek bir örnek seçtim. Belki ayran içtikçe bu konuyu hatırlarsınız ![]()
Şimdi 3 adet tipimiz olsun. TAyran, TYogurt ve TSu. TAyran sınıfı içinde hem TYogurt tipinde, hem de TSu tipinde iki adet özelliğimiz olsun. TTuz da olurdu ama şimdilik ayranımız tuzsuz olsun ![]()
TYogurt = class Miktar: Integer; end; TSu = class Miktar: Integer; end; TAyran = class Yogurt: TYogurt; Su: TSu; public constructor Create; end; constructor TAyran.Create; begin inherited; Yogurt := TYogurt.Create; Su := TSu.Create; end;
Şimdi diyelimki TAyran tipimize TYogurt ve TSu tiplerinde ekleme yapılabilsin. Ayrıca TYogurt ve TSu kendi aralarında toplanabilsin. Ayrıca bir de TAyran ile diğer tipler karşılaştırılmaya tabi tutulabilsin. Bu işlemleri siz, ayrı ayrı metodlar tanımlayarak ve bu metodları gerektiği yerde kullanarak gerçekleştirebilirisiniz. Ama operator overloading kullanarak aşağıdaki gibi işlemler yapabileceğiz:
var
Yogurt: TYogurt;
Su: TSu;
Ayran: TAyran;
begin
Yogurt := 15;
Su := 5;
Ayran := Yogurt + Su;
Yogurt := Yogurt - 5;
if Ayran > Yogurt then
ShowMessage('Ayrandaki yogurt miktarı daha fazla');
end;Nesne oluşturma kısımlarını konumuz dışında olduğu için atladım. Tabi ki bu işlemler, operator overloading olmadan hataya sebep olacaktır. Çünkü bir sınıf tipini kalkıp başka bir sınıf tipi ile topluyorsunuz, sonra bu sınıf tipine Integer ekliyorsunuz ve sonra da başka bir sınıf tipi ile karşılaştırmaya tabi tutuyorsunuz. Dediğim gibi bu işlemleri prosedür ve fonksiyonlarla da halledebilirsiniz. Ama bu işlemleri operator overloding üzerinden yaparsanız, kullanımda yukarıdaki gibi bir kolaylık sağlamış olacaksınız. Şimdi veri tiplerimiz ile yukarıdaki işlemleri gerçekleştirebilmek için gereken operator tanımlamalarını yapalım.
Add ve Subtract
Şimdi TYogurt nesnesi için aşağıdaki tanımlamayı yapalım. Ama ondan önce TYogurt içinde TAyran ve TSu sınıflarına referanslarımız olduğundan ikisi için forward tanımlaması gerekmekte. Çünkü TAyran ve TSu, TYogurt’dan sonra tanımlanmıştır. Her neyse bu zaten nesne programlama konusu. Şu an bizim konumuzla alakası fazla yok.
TAyran = class; //forward tanımlama
TSu = class; //forward tanımlama
TYogurt = class
Miktar: Integer;
class operator Add(Yogurt: TYogurt; Su: TSu): TAyran; //Ayran := Yogurt + Su
class operator Add(Yogurt1: TYogurt; Yogurt2: TYogurt): TYogurt; //Yogurt3 := Yogurt1 + Yogurt2
class operator Subtract(Yogurt1: TYogurt; Yogurt2: TYogurt): TYogurt; //Yogurt3 := Yogurt1 - Yogurt2
end;Gördüğünüz gibi üç adet satır ekledik. Bunlardan ilki "+" yani toplama operatorünün üzerine yazar. Bu class statik metod ile Add metodunun üzerine yazmak ile nesnemiz ile yapılacak olan her "+" toplama işleminde bu metod çağrılacaktır. Ama verdiğimiz kriterlere göre… Örneğimizde gördüğünüz gibi iki adet Add operatoru tanımlandı. Bunlardan ilki TYogurt ile TSu değerlerini topluyor ve sonucu TAyran olarak çıktı veriyor. İkincisi ise iki adet TYogurt nesnesin topluyor ve yine TYogurt cinsinden çıktı veriyor.
Buradaki altın kural, eğer iki parametreli operator metodu var ise, tanımlayacağınız operator metodundaki parametrelerden en az birisi işleme girecek olan sınıftan olmalı. İki parametre alan çoğu operator metodları için bu mantık vardır. Bu yüzden bir operator’e overload yapacaksanız, kara kara hangi sınıfta tanımlıyacağınızı düşünmenize gerek yok. Çünkü işleme girecek olan herhangi bir sınıf bu metodun bir parametresi olacağından, işleme girecek olan herhangi bir sınıf içinde bu overload işlemini yapabilirsiniz. Mesela ilk Add tanımlamasını TSu sınıfında da yapabilirdik. Çünkü TSu da işleme girmektedir. Ama artık gerek kalmadı. Çünkü:
Ayran := Yogurt + Su;
gibi bir ifade için bu Add metodu çağırılacaktır.
Tek parametreli operator metodlarında ise aslında mantık ile parametresinin ne olacağını bilebiliyorsunuz. Zaten yanlış bir tanımlama yaptığınızda derleyici sizi uyaracaktır. Operator metodlarının parametreleri dahil tam listesi için yardım dosyasına müracaat etmelisiniz.
En son satırdaki Subtract operator metodu ise "-" çıkarma işleminin görevini görmektedir. Burada gördüğünüz örnekte iki adet TYogurt nesnesi birbirinden çıkarılıp sonuç olarak TYogurt cinsinden çıktı veriliyor. Yani:
Yogurt3 := Yogurt1 - Yogurt2;
gibi.. Şimdi bu metodların kod kısımlarını da verelim.
class operator TYogurt.Add(Yogurt: TYogurt; Su: TSu): TAyran; begin Result := TAyran.Create; Result.Yogurt.Miktar := Yogurt.Miktar; Result.Su.Miktar := Su.Miktar; end; class operator TYogurt.Add(Yogurt1, Yogurt2: TYogurt): TYogurt; begin Result := TYogurt.Create; Result.Miktar := Yogurt1.Miktar + Yogurt2.Miktar; end; class operator TYogurt.Subtract(Yogurt1, Yogurt2: TYogurt): TYogurt; begin Result := TYogurt.Create; Result.Miktar := Yogurt1.Miktar - Yogurt2.Miktar; end;
Gördüğünüz gibi Result değerlerini her defasında Create ile oluşturduk. Çünkü Result bir nesnedir. Ve Result ile atanacak olan nesne birbirinden farklıdır. İşte record kullanmanın bir yararı da burada. Create ile ikide bir oluşturmanıza gerek yok. Record kullanmanın tek dez avantajı, forward tanımlama yapamamanız. Ama bu sorun, her bir record’u ayrı dosyalara alınarak üstesinden gelinebilir. Ben bununla kafanızı karıştırmamak için sınıf’ları ve forward tanımlamaları kullandım. Ayrıa record’un bir avantajı da sınıf gibi private, public, protected alanlarda prosedür ve fonksiyon oluşturabilmenizdir. Gerçi bu özellik operator overloading ile aynı zamanda Delphi’ye kazandırılmıştır. Her neyse…
Implicit ve Explicit
Şimdi diyelimki TYogurt tipindeki nesnemizi TAyran nesnesine dönüştüreceğiz. Yani TYogurt nesnesindeki miktarı, TAyran üzerine yazacaz. Bunun için TYogurt nesnesine şunu eklemeliyiz:
class operator Implicit(Yogurt: TYogurt): TAyran; //Ayran := Yogurt;
Implicit operator metodu hangi sınıf veya record içinde tanımlanmış ise o sınıf veya record’u metodun Result kısmında bulunan sınıfa dönüştürür. Örneğimiz için bu Ayran değişkene Yogurt’u atamak ile olur. Bu metodun kodları ise örneğimize göre şöyle olabilir:
class operator TYogurt.Implicit(Yogurt: TYogurt): TAyran; begin Result := TAyran.Create; Result.Yogurt.Miktar := Yogurt.Miktar; end;
Eğer sınıf için operator overloading kullanıyorsanız, burada ve diğer tanımlamalarda dikkat etmeniz gereken nokta parametre ile gelen nesneleri direk olarak Result içine atama yapmamanız. Çünkü bu taktirde sınıfın işaretçisini eşitlemiş olursunuz ve işler istemediğiniz noktalara gelebilir.
Explicit operatorü ise mantık olarak aynıdır. Sadece kullanımda fark vardır. Yani yukarıdaki "Ayran := Yogurt" işleminde, kullanıcının böyle kolay bir şekilde eşitlemesini istemeyebiliriz. Yani direk böyle bir eşitleme yaptığında hata yapmış olabileceğini düşündürtmek için bu kullanıma izin vermeyebiliriz. Ve sadece type casting ile tip dönüşümüne izin verebiliriz. İşte bunu bize sağlayan operator Explicit’dir.
"Ayran := Yogurt" eşitlemesini "Ayran := TAyran(Yogurt)" şeklinde yapabilmek için sadece yukarıda yazdığımız Implicit yazan yeri Explicit olarak değiştirin:
class operator TYogurt.Explicit(Yogurt: TYogurt): TAyran; begin Result := TAyran.Create; Result.Yogurt.Miktar := Yogurt.Miktar; end;
Kod aynı kod, ama sadece Implicit yerine Explicit var. Bunu yapmakla artık "Ayran := Yogurt" çevirimi derleyici tarafından hatalı olarak görülecek ve "Incompatible types" yani uyumsuz tipler gibi bir hata verecektir. Böylece kullanıcıyı "Ayran := TAyran(Yogurt)" işlemini yapmaya zorlamış oluyoruz.
Peki neden Implicit’i kaldırdık? Hem Implicit hem de Explicit operatorunu kullanamazmıydık. Kullanabiliridik. Ama Explicit çalışmazdı. Çünkü Implicit daima Explicit’den önceliklidir.
Karşılaştırma
Karşılaştırma işlemleri için bir çok operator vardır. Burada hepsine değinmemiz biraz zor. Sadece makalemizin başında verdiğimiz kodda kullandığımız ">" büyüktür karşılaştırma operatoru için overload işlemini yapalım. Bu sefer dilerseniz TAyran sınıfımızda böyle bir tanımlama yapalım:
TAyran = class
Yogurt: TYogurt;
Su: TSu;
public
constructor Create;
class operator GreaterThan(Ayran: TAyran; Yogurt: TYogurt): Boolean;
end;Karşılaştırma operatorlerini overload yaparken dönüş değerinin Boolean olmasına dikkat edin. Bu operator metodunu aşağıdaki gibi kodlayabiliriz:
class operator TAyran.GreaterThan(Ayran: TAyran; Yogurt: TYogurt): Boolean;
begin
if Ayran.Yogurt.Miktar > Yogurt.Miktar then
Result := True
else
Result := False;
end;Yada daha kısa ve şık bir birçimde şöyle de olabilir:
class operator TAyran.GreaterThan(Ayran: TAyran; Yogurt: TYogurt): Boolean; begin Result := Ayran.Yogurt.Miktar > Yogurt.Miktar; end;
İçini nasıl doldurmanız gerektiği size kalmış. Ama burada Yogurt için de büyüktür operatorünü overload yapabilirdik. Bu durumda
Ayran.Yogurt.Miktar > Yogurt.Miktar
yazmak yerine:
Ayran.Yogurt > Yogurt
yazabilirdik. Gerisi size kalmış.
Sonuç
Burada değinmediğimiz bir çok operatöre aşırı yükleme yapabilirsiniz. Bunların tam listesi için yardım dosyasında "Operator Overloading" ile ilgili kısma bakmanız yeterli.
Tabi burada verdiğimiz sınıflar için tek tek operator tanımlaması yaptığınızda ortalık biraz karışabiliyor. Bunların önüne geçmek için operator tanımlamalarını daha çok ata sınıflarda gerçekleştirmelisiniz. Böylece çocuk sınıflarda bu tanımlamaları bir daha yapmak zorunda kalmazsınız. Mesela örneğimizde TSu ve TYogurt sınıflarının her ikisinin de Miktar isminde bir özelliği mevcut. Bu iki sınıfı TSiviMadde gibi "Miktar" özelliği olan bir ata sınıftan türetebilirdiniz. Ve bu operator tanımlamalarını ata sınıf için yapıp, ayrı ayrı TSu ve TYogurt için yapmanıza gerek kalmazdı.
Yorumlarınız önemlidir, bekliyorum.



