Yeni Veri Tipleri ve Operator Overloading

// 13 Haziran 2010 // Delphi

Bir önceki makalemizde Interface’ler ile ilgili bilgi vermiş ve Operator Overloading namı diğer operatör aşırı yükleme hususuna değineceğimizden bahis açmıştık. Bu makalemizde; Operator Overloading hakkında yazacağız ve .Net framework’te olduğu gibi çalışan yeni bir String ve DateTime veri türü oluşturacağız. Ancak öncelikle, aslında hepimizin bildiği operatör kavramına bir göz gezdirmekte fayda var sanırım.

Operatörler; belirli veri türleri arasında ilişki kuran, onları bir sonuca ulaştıran yapılardır. Hemen hemen hergün kullandığımız aritmetik işlemlerdeki toplama(+), çıkartma(-), çarpma(*) ve bölme(/) işlemlerini ifade ettiğimiz simgeler; yada mantıksal işlemlerde kullandığımız or(veya), and(ve), not(değil) vb. gibi simgesel ifadelere operatörler denilebilir. Bildiğiniz gibi; operatörler veri türleri üzerinde üstlerine düşen mantıksal yada matematiksel işlemleri yaparak bir sonucun üretilmesini sağlarlar. Operatörlerin bu vazifelerini, gerek günlük yaşantımızda; gerekse de programlama hayatımızda sıklıkla kullanırız. Programlama ortamlarında yaşadığımız tecrübeler gereği operatörlerimizi düzgün veritipleri ile kullanırız. Örneğin toplama operatörü; sağındaki ve solundaki verilerin toplanması amacına hizmet eder. Ancak; toplama operatörünün sol ve sağındaki değerlerin sayısal olması yada alfasayısal olması durumlarında toplamanın neticesi farklılık arzeder. Basit bir örnek verelim;

var
  iLeft, iRight, iTotal : Integer;
  sLeft, sRight, sTotal : String;
begin
  iLeft := 10;
  iRight := 20;
  iTotal := iLeft + iRight;

  sLeft := 'Operator ';
  sRight := 'Overloading';
  sTotal := sLeft + sRight;
end;

Yukarıda gördüğünüz basit örnekte, aynı operatör(+) farklı sonuçlara hizmet etmektedir. Birinci durumda sonucumuz “30″ olurken; ikinci durumda sonuç “Operator Overloading” olmaktadır. Hemen hemen hiçbirimiz; iki farklı veri türünü aynı operatöre vermeyiz. Örneğin hiçbirimiz; bir sayı ile bir string ifadeyi toplamayız. Ancak bazı durumlarda, bir veri türü ile bir başka veri türünü herhangi bir operatöre vererek işlemek isteyebiliriz. Bu gibi durumlarda, genellikle derleyicimiz bizleri uyaracak bir warning vermekle yetinir. Ancak bazı durumlarda yaptığımız işlemlerde beklediğimizin dışında sonuçlar gördüğümüz de olur. Örneğin;

var
  bLeft, bRight, bTotal : Byte;
  wLeft,wRight,wTotal : Word;
begin
  bLeft := High(Byte); // 255
  bRight:= High(Byte); // 255
  bTotal:= bLeft + bRight; // ??

  wLeft := High(Word); // 65.535
  wRight:= High(Word); // 65.535
  wTotal:= wLeft + wRight; // ??
end;


Yukarıdaki örneğimizde 3 adet Byte ve Word değişken tanımlanıyor ve bu değişkenlerden Left ve Right olanlara o değişken tipinin alabileceği maksimum değer atanıyor. Ardından bu değişkenler birbirleri ile toplanıyor. Bu durumda Byte değişkenlerin toplanmasından elde edilecek değerin; 255 + 255 = 510 olması ve Word değişkenlerin toplanmasından elde edilecek değerin de 65.535 + 65.535 = 131.070 olması bekleniyor. Ancak durum tamamen bunun dışında sonuçlanıyor. Byte değerlerin toplanmasından elde edilen sonucun 254 olduğunu ve Word değerlerin toplanmasından elde edilen sonucun ise; 65.534 olduğunu görüyoruz.

Eminim buna benzer sıkıntılara daha önceleri rastlamışsınızdır. Bu gibi sorunlar tespit edilmesi son derece güç sorunlar olduğu için bu hususa da değinmeden geçmek istemedim. Bu garip durumun aslında son derece mantıklı bir açıklaması var. Bildiğiniz gibi bir Byte veri türü; içinde en yüksek değer olarak 255 sayısını tutabilir. Byte türündeki bir değişkene 255 + 255 ‘lik bir verinin atanmaya çalışılması durumunda; 510 olması gereken veri kırpılarak atanır. Kırpılma işlemi; ilgili sonucun 255 + 1 ‘e göre mod alınmasından kalan değerdir. Byte ve Word değişkenlerimizdeki durum aşağıdaki gibi olur;

var
  bLeft, bRight, bTotal : Byte;
  wLeft, wRight, wTotal : Word;
begin
  bLeft := High(Byte); // 255
  bRight := High(Byte); // 255
  bTotal := bLeft + bRight; // İşte tam bu noktada; 510 mod (255 + 1) işlemi yapılıyor. Bu da 254 sonucunu verecektir.

  wLeft := High(Word); // 65.535
  wRight := High(Word); // 65.535
  wTotal := wLeft + wRight; // Bu noktada da; 131.070 mod (65.535 + 1) işlemi yapılıyor. Bu işlemin sonucu da 65.534 sonucunu verecektir.
end;

Gördüğünüz gibi; yaptığımız aritmetik işlemlerde artı operatörünün her iki tarafındaki veri türü ve sonucun atanacağı veri türüde birbirine eşit olmasına rağmen beklenen sonucun aksi bir durum ile karşılaştık. Buna benzer bir başka garip durumu daha sizlerle paylaşmak isterim. Aşağıdaki örneği lütfen dikkatlice inceleyiniz:

function Replicate(const Value : String; const Count : Cardinal) : String;
var
  Counter : Cardinal;
begin
  Result := Value;
  for Counter := 0 to Count - 1 do Result := Result + Value;
end;
..
..
..
var
  sRetValue : String;
begin
  sRetValue := Replicate('Merhaba ', 0);
  ShowMessage(sRetValue);
end;

Yukarıda örneğini verdiğimiz kodda, Replicate metodunun içindeki döngünün asla çalışmayacağını düşünerek(0′dan -1′e) metodumuzun sadece “Merhaba” döndürmesini bekleriz. Oysaki durum burada da farklıdır. Örneğimizi çalıştırıp denerseniz, kodun asla ShowMessage satırına inemediğini göreceksiniz. Tabii uzun bir sabrınız yok ise. Peki burada ne gibi bir gariplik olmuştur ?

Dikkat ederseniz eğer, Replicate metoduna geçilen Count parametresi Cardinal veri tipinden tanımlanmıştır. Ve bu veritipi, Low(Cardinal)..High(Cardinal) aralığında yani 0 ila 4.294.967.295 arasında değer alacaktır. Veri aralığından gözlemleyeceğiniz gibi, bu veri türü negatif değerler alamamaktadır. Oysaki Replicate(‘Merhaba’, 0) kullanımı sayıyı negatif aralığa sürüklemek isteyecektir(0-1). Bu gibi bir durumda; döngümüz sıfırdan 4.294.967.295 değerine kadar dönmeye çalışacaktır. Gördüğünüz gibi pozitif sayı aralığındaki değişkenlere negatif değer atama çabaları, veri tipinin alabileceği maximum değere set edilmesini sağlar.

Bu sebeple; veri tipleri üzerinde aritmatiksel yada mantıksal operatörleri kullanırken dikkatli olmakta fayda vardır. Takdir edersiniz ki; bu tip hataları tespit etmek son derece zordur. Bu sebeple kodlarımızı yazarken bu hususları göz önünde bulundurmak programlarımızın stabilitesi ve bizim akıl sağlığımız açısından son derece önemlidir.

Bahsedilen bu iki garip durum senaryosunun aslında Operator Overloading hususu ile direkt bir ilgisi yok. Ancak; bu konuyu anlatırken hatalara neden olabilecek bu dikkat edilesi hususu da ilginize sunmak istedim.

Çevresinde dolaştığımız ancak henüz kendisine temasta bulunmadığımız Operator Overloading konusunda da biraz konuşmanın zamanı geldi sanırım. Operatörleri aşırı yüklemek; kısaca derleyiciye ilgili işlemin sonuçlarını “ben yöneteceğim” demektir! Bu iddialı sözü, genellikle programlarımızı daha da basitleştirebilmek adına söyleriz. Sıklıkla başka bir kullanım amacına rastlanmaz. Ancak; biz makalemizde Operator Overloading’i Delphi’ye yeni tipler kazandırmak amacı ile kullanacağız.

.Net dillerinde; Operator Overloading hem sınıflara hemde struct denilen Delphi’de record’a karşılık gelen yapılara uygulanabilmektedir. Ancak; Delphi’de Garbage Collection gibi bir çöp toplama mekanizmasının var olmaması nedeni ile henüz bu özellik sadece record’lar ile sınırlıdır. Yeni veritürlerimizden NewString türüne girmeden evvel, string türlerle ilgili bazı örnekler vermeye çalışalım:

var
  sVal : String;
  iLen : Integer;
begin
  sVal := 'Merhaba ';
  sVal := sVal * 3; // Geçersiz

  iLen := sVal.Length; // Geçersiz
  sVal := sVal.Replace('a', 'x'); // Geçersiz
  sVal := -sVal; // Geçersiz
end;

Örneğimiz de; sVal string türünde bir değişken tanımlanmış ardından bu değişkene “Merhaba ” ifadesi atanmıştır. Ardından hepimize saçma görünebilecek bir işlem ile bir string değişken 3 ile çarpılmaya çalışılmıştır. Burada amaçlanan 3 adet “Merhaba ” string’inin yanyana getirilmesidir. Ancak derleyicimiz bize; “Incompatible types: string and Integer” gibi bir hata ile yanıt vermiştir.

Ardından, sVal değişkenimiz üzerinde Length isimli bir metodun çağrılması istenmiştir. Bu da geçersiz bir kullanımdır. Çünkü string veri türü; Delphi’de bir sınıf yada bir record değildir dolayısı ile metod çağrımlarını desteklemez. Buna benzer bir şekilde Replace metodu da çalışmayacaktır. Son kullanım tarzımızda sVal değişkenimizin içindeki değerin ters çevrilmesi(Reverse) arzulanmıştır. Ancak şu anki imkanlarımız ile bu da mümkün değildir.

Tüm bu işlemleri yapabilmek için elbetteki pek çok string metodu bulunmaktadır. Ancak biz bu metodları hatırımızda tutmak yerine .Net framework’teki mantalitede kullanabilmek istersek ne yapmalıyız ? İşte tam bu noktada Record’lar ve operator overloading konusuna girmiş bulunuyoruz. Daha fazla ilerlemeden önce; Delphi’de tanımlı olan operatörlerimizin neler olduğuna bir bakalım:

Operator Category Declaration Signature Symbol Mapping
Implicit Conversion Implicit(a : type) : resultType; implicit typecast
Explicit Conversion Explicit(a: type) : resultType; explicit typecast
Negative Unary Negative(a: type) : resultType; -
Positive Unary Positive(a: type): resultType; +
Inc Unary Inc(a: type) : resultType; Inc
Dec Unary Dec(a: type): resultType; Dec
LogicalNot Unary LogicalNot(a: type): resultType; not
BitwiseNot Unary BitwiseNot(a: type): resultType; not
Trunc Unary Trunc(a: type): resultType; Trunc
Round Unary Round(a: type): resultType; Round
Equal Comparison Equal(a: type; b: type) : Boolean; =
NotEqual Comparison NotEqual(a: type; b: type): Boolean; <>
GreaterThan Comparison GreaterThan(a: type; b: type) Boolean; >
GreaterThanOrEqual Comparison GreaterThanOrEqual(a: type; b: type): resultType; >=
LessThan Comparison LessThan(a: type; b: type): resultType; <
LessThanOrEqual Comparison LessThanOrEqual(a: type; b: type): resultType; <=
Add Binary Add(a: type; b: type): resultType; +
Subtract Binary Subtract(a: type; b: type) : resultType; -
Multiply Binary Multiply(a: type; b: type) : resultType; *
Divide Binary Divide(a: type; b: type) : resultType; /
IntDivide Binary IntDivide(a: type; b: type): resultType; div
Modulus Binary Modulus(a: type; b: type): resultType; mod
LeftShift Binary LeftShift(a: type; b: type): resultType; shl
RightShift Binary RightShift(a: type; b: type): resultType; shr
LogicalAnd Binary LogicalAnd(a: type; b: type): resultType; and
LogicalOr Binary LogicalOr(a: type; b: type): resultType; or
LogicalXor Binary LogicalXor(a: type; b: type): resultType; xor
BitwiseAnd Binary BitwiseAnd(a: type; b: type): resultType; and
BitwiseOr Binary BitwiseOr(a: type; b: type): resultType; or
BitwiseXor Binary BitwiseXor(a: type; b: type): resultType; xor

Gördüğünüz gibi pek çok tanımlı operatörümüz var. Bu operatörlerimizin tek tek ne olduğunu ifade etmeden önce neden yeni bir string tipi tanımlamak istediğimizi ve bize getirilerinin neler olabileceği hakkında tüm söylediklerimize ek olarak bir kaç kelam daha etmekte fayda olabilir.

.Net dillerinde; bir string değişken tanımlandığında o string türü ile ilgili işlem yapabilecek metodların hepsine nokta(.) ya basarak erişebilme gücüne sahip olursunuz. Böylece bir veri türü üzerinde ne gibi işlemler yapılabileceğini bir bakışta gözlemleyebilirsiniz. Aynı zamanda; o veri türü ile ilgili işlem yapan metodların isimlerini ezberlemek yada hangi unit’te tanımlandıklarını hatırlamak ve uses bloğunda referans vermek durumunda kalmazsınız. Ayrıca; operator overloading herhangi bir veri türü ile yapılması derleyici tarafından yasaklanmış işlemleri kodlamanıza da imkan sağlayacaktır(Bir string’in tersini almak için başına eksi koymak gibi).

Bu faydalar; operator overloading kavramını programlarımızda bir yardımcı unsur olarak kullanmamız için yeterli olabilecek sebeplerdendir. Tüm bu teorik verilerin ışığında artık kodlamaya geçmeye başlayabiliriz. Tahmin edebileceğiniz gibi, yeni veri türümüz string veri türü ile iletişim kurabilen, ek özellikler getirebilen bir record olacaktır.

type
  NewString = record
  end;

..
..
var
  Old : String;
  New : NewString;
begin
  Old := 'Merhaba';
  New := Old;
end;

Örneğimizde NewString isminde bir record tanımladık ve içinde herhangi bir metod tanımlamadık. Ardından Newstring türündeki değişkenimize string türündeki değişkenimizi atamaya çalıştık. Ancak; derleyici tam bu satırda bize, “Incompatible types: ‘NewString’ and ‘string’” hatası verecektir. Her iki tipin birbirine nasıl atanacağı bu noktada derleyici tarafından bilinmemektedir. Bu noktada yapacağımız şey; derleyiciye yeni tipimizin string veri türü ile atama sırasında ne gibi bir işlem yapması gerektiği hususunda bilgi vermektir. Kullanmamız gereken Implicit adındaki operatör metodudur. Implicit operatörü yeni tanımladığımız record’umuza atama yapılacağı sırada ne gibi bir kod çalıştırmamız gerektiğini söyleyecektir. Yeni kodumuz aşağıdaki gibi olacaktır.

type
  NewString = record
  private
    fData : String;
  public
    class operator Implicit(const Left : String) : NewString;
  end;

implementatiton

class operator NewString.Implicit(const Left : String) : NewString;
begin
  Result.fData := Left;
end;
..
..
var
  Old : String;
  New : NewString;
begin
  Old := 'Merhaba';
  New := Old;
end;

Gördüğünüz gibi NewString record’umuz içinde private bölümünde fData isminde ve string türünde bir değişken tanımladık. Ardından; NewString veri türümüze atama yapmak istediğimiz veri türü string olduğu için Implicit operatörünü string verileri alabilecek şekilde tanımladık. Bu aşamada; New := Old; ataması Implicit kodumuza dallanılmasına sebep olacaktır. Implicit operatörü bilinçsiz tür dönüşüm operatörü olarak da bilinir. New adlı ve NewString türlü değişkenimize string türündeki verilerin atanmaya çalışılması her zaman Implicit metodumuza başvurulmasına neden olacaktır. Ancak; New := 123; gibi bir atama yine derleyici tarafından bir hata ile neticelenecektir. Çünkü Integer veri türü ile atama uyumluluğuna sahip değiliz. Integer veri türü ile ilgili bir atamanında geçerli olması isteniyorsa; Integer veri türü içinde bir Implicit metodunun yazılması icap edecektir.

Explicit ise; bilinçli tür dönüşüm operatörüdür. Bazen veri türlerimizi başka bir veri türüne dönüştürmek için casting yaparız. Bu tarz durumlarda explicit operatöründe yazdığımız kodlar devreye girecektir. Örneğin; Old := String(New); türündeki bir kullanım explicit operatör çağrımına neden olacaktır. O halde explicit operatörümüzü de ekleyerek ilerlemeye devam edelim:

type
  NewString = record
  private
    fData : String;
  public
    class operator Implicit(const Left : String) : NewString;
    class operator Explicit(const Left : NewString) : String;
  end;

implementatiton

class operator NewString.Implicit(const Left : String) : NewString;
begin
  Result.fData := Left;
end;

class operator NewString.Explicit(const Left : NewString) : String;
begin
  Result := Left.fData;
end;

..
..
var
  Old : String;
  New : NewString;
begin
  Old := 'Merhaba';
  New := Old;

  Old := String(New);
end;

String ifadeler sözkonusu olduğunda sık kullandığımız operatörlerden birisi de +(Artı) operatörüdür. Bunun için Delphi’de kullanmamız gereken operatörün Add operatörü olduğunu tahmin edersiniz. Ancak; ilgili tablodan gördüğünüz gibi Add operatörünün kullanılması , Implicit ve Explicit’in kullanımlarından biraz daha farklıdır. Add metodu içine toplanması istenen iki veri türünü de parametre olarak beklemektedir. Biz bu durumda tüm olasılıklara uygun Add metodlarını yazmak durumunda kalırız. Arzu ederseniz onu da kodlayalım;

  NewString = record
  private
    fData : String;
  public
    class operator Implicit(const Left : String) : NewString;
    class operator Explicit(const Left : NewString) : String;

    class operator Add(const Left : String; const Right : NewString) : NewString; // 1.nci metod: New := 'Test' + New; gibi bir kullanım.
    class operator Add(const Left : NewString; const Right : String) : NewString; // 2.nci metod: New := New + 'Test'; gibi bir kullanım.
    class operator Add(const Left : NewString; const Right : NewString) : NewString; // 3.ncü metod: New := New + New; gibi bir kullanım.
  end;

implementatiton

class operator NewString.Implicit(const Left : String) : NewString;
begin
  Result.fData := Left;
end;

class operator NewString.Explicit(const Left : NewString) : String;
begin
  Result := Left.fData;
end;

class operator NewString.Add(const Left : String; const Right : NewString) : NewString;
begin
  Result.fData := Left + Right.fData;
end;

class operator NewString.Add(const Left : NewString; const Right : String) : NewString;
begin
  Result.fData := Left.fData + Right;
end;

class operator NewString.Add(const Left : NewString; const Right : NewString) : NewString;
begin
  Result.fData := Left.fData + Right.fData;
end;

..
..
var
  Old : String;
  New : NewString;
begin
  Old := 'Merhaba';
  New := Old;

  Old := String(New);

  New := 'Dünya ' + New; // "Dünya Merhaba" sonucunu elde edeceğiz. 1.nci metod çağrılacaktır.
  New := New + ' Dünya'; // "Merhaba Dünya" sonucunu elde edeceğiz. 2.nci metod çağrılacaktır.
  New := New + New;      // "MerhabaMerhaba" sonucunu elde edeceğiz. 3.ncü metod çağrılacaktır.
end;

Add operatöründe yada onun gibi birden fazla veri türü ile işlem yapan diğer operatör metodlarında yegane kural; geçilmesi gereken parametrelerden en az bir tanesinin NewString türünde olması gerektiğidir. Kısacası aşağıdaki gibi bir tanıma sahip olamayız:

type
  NewString = record
  ..
  ..
    class operator Add(const Left : String; const Right : String); // Her iki parametre türü de string olduğu için kabul edilmez.!
  end;

String veri türlerinde çıkartma işlemine pek sık rastlanmadığı için Subtract operatörü ile ilgilenmeyeceğiz ancak onun da Add operatöründen farklı olmadığını bilmenizi isterim. Başlarda verdiğimiz örneklerde bir string ifadeyi belli bir sayı ile çarparak o string’den birden fazla elde etmek ve string ifadenin başına eksi(-) operatörünü koyarak ilgili string ifadeyi ters çevirmeyi denemiş ve hata ile neticeleneceğini ifade etmiştik. Şimdi gelin, bu operatörleri de yeni veri türümüz olan NewString için kodlayalım. Kullanacağımız operatörler tahmin edeceğiniz gibi, Multiply ve Negative operatörleri olacak;

class operator NewString.Multiply(const Left : NewString; const Right : Integer) : NewString; // 1.nci metod
begin
  Result.fData := DupeString(Left.fData, Right);
end;

class operator NewString.Multiply(const Left : Integer; const Right : NewString) : NewString; // 2.nci metod
begin
  Result.fData := DupeString(Right.fData, Left);
end;

class operator NewString.Negative(const Left : NewString) : NewString;
begin
  Result.fData := ReverseString(Left.fData);
end;

..
..
var
  New : NewString;
begin
  New := 'Merhaba'; // Implicit çağrımına neden olur.
  New := New * 3; // "MerhabaMerhabaMerhaba" sonucunu döndürür. 1.nci metod çağrılacaktır.

  New := 'Dünya'; // Implicit çağrımına neden olur.
  New := 3 * New; // "DünyaDünyaDünya" sonucunu döndürür. 2.nci metod çağrılacaktır.

  New := 'Merhaba Dünya'; // Implicit çağrımına neden olur.
  New := -New; // Negative çağrımına neden olur. "aynüD abahreM" sonucunu döndürecektir.
end;

Bunların ardından olmazsa olmazlarımızdan Equal ve NotEqual operatör metodlarını kodlamamız gerekiyor. Bu metodların kodlanmaması NewString veri türümüz için karşılaştırma işlemleri yapamamamız anlamını taşıyacaktır.

class operator NewString.Equal(const Left: String; // 1.nci metod
  const Right: NewString): Boolean;
begin
  Result := Left = Right.fData;
end;

class operator NewString.Equal(const Left: NewString; const Right: String): Boolean; // 2.nci metod
begin
  Result := Left.fData = Right;
end;

class operator NewString.Equal(const Left, Right: NewString): Boolean; // 3.ncü metod
begin
  Result := Left.fData = Right.fData;
end;

class operator NewString.NotEqual(const Left, Right: NewString): Boolean; // 1.nci metod
begin
  Result := Left.fData <> Right.fData;
end;

class operator NewString.NotEqual(const Left: NewString; // 2.nci metod
  const Right: String): Boolean;
begin
  Result := Left.fData <> Right;
end;

class operator NewString.NotEqual(const Left: String; // 3.ncü metod
  const Right: NewString): Boolean;
begin
  Result := Left <> Right.fData;
end;
..
..
var
  New, New2 : NewString;
begin
  New := 'Merhaba';
  New2:= 'Merhaba';

  if 'Merhaba' = New then ShowMessage('Merhaba = New'); // 1.nci metod çağrılacaktır.
  if New = 'Merhaba' then ShowMessage('New = Merhaba'); // 2.nci metod çağrılacaktır.
  if New = New2 then ShowMessage('New = New2'); // 3.ncü metod çağrılacaktır.

  if New <> New2 then ShowMessage('New <> New2'); // 1.nci metod çağrılacaktır.
  if New <> 'Merhaba' then ShowMessage('New <> Merhaba'); // 2.nci metod çağrılacaktır.
  if 'Merhaba' <> New then ShowMessage('Merhaba <> New'); // 3.ncü metod çağrılacaktır.
end;

Diğer operatörlerimiz belki string veri türümüz için anlamlı olmayacaktır. Ancak, sizler kendi yazacağınız veri türlerinde ilgili operatör metodlarını da kullanabilirsiniz. Ben; sıklıkla eksikliğini hissettiğimiz String ve TDatetime veri türleri ile ilgili yeni tip tanımına gitmeyi faydalı buldum. Yazmış olduğum yeni tipleri indirebilmeniz için makalenin sonunda bir link vereceğim.

Makalemizi neticelendirmeden evvel yeni tiplerimiz ile neler yapabileceğimize dair küçük bir kaç örnek vermek faydalı olacaktır:

procedure Log(const Message : String);
begin
  form1.Memo1.Lines.Add(Message);
end;

var
  New : NewString;
  arr   : TList<Char>;
  arr2 : TList<String>;
  ch   : Char;
  str : String;

  Len,
  Index : Integer;
begin
  New := 'Merhaba Dünya';
  Log(New.ToString);

  arr := New.ToChararray;
  for ch in arr do Log(ch);

  arr := New.ToCharArray(5, 6);
  for ch in arr do Log(ch);
 
  if New.Contains('Merhaba') then Log('Merhaba bulundu');
  if New.EndsWith('Dünya') then Log('Dünya stringi ile bitiyor');
  if New.StartsWith('Merhaba') then Log('Merhaba stringi ile başlıyor');
  Index := New.IndexOf('Dünya');
  Len := New.Length;
  New := New.Trim;
  New := New.Replace('a', 'x');
  New := New.SubString(5, 5);
  New := New.ToUpper;
  New := New.ToLower;

  New := 'Merhaba,Dünya';
  arr2 := New.Split(',');
  for str in arr2 do Log(str); // Merhaba ve Dünya
end;

var
  NewDT : NewDateTime;
  dt : TDateTime;
begin
  NewDT := Now;
  NewDT := Date;
  NewDT := 'bugün';
  NewDT := 'yarın';
  NewDT := '+5y'; // Bugünün tarihinden 5 yıl sonrası.
  NewDT := Now + '-3y +5a -7g +6s +15dk +30sn'; // Bugünden 3 yıl 7 gün öncesinden 5 ay 6 saat 15 dakika 30 saniye sonrası.
  NewDT := Date + 5; // 5 gün sonrası.
  Log(NewDT.ToString);
  Log(InttoStr(NewDT.Day));
  Log(InttoStr(NewDT.Month));
  Log(InttoStr(NewDT.Year));
  Log(InttoStr(NewDT.Hour));
  Log(InttoStr(NewDT.Minute));
  Log(InttoStr(NewDT.Second));
  Log(InttoStr(NewDT.MilliSecond));

  NewDT.Year := NewDT.Year + 3;
  dt := NewDT.ToDateTime;
  Log(DateToStr(dt));

  Inc(NewDT);
  Dec(NewDT);

  NewDT := NewDT - 7;
  NewDT := NewDT - '3g 5s';
end;

NewDateTime ve Newstring isimli dosyaları da buradan indirebilirsiniz.

Sevgiler, saygılar..

“Yeni Veri Tipleri ve Operator Overloading” için 15 Yorum

  1. Olcay DAĞLI diyor ki:

    Eline emeğine sağlık hocam, bu seride diğer makalelerin gibi gayet güzel gidiyor, okudukça yazılım ummanında bir damla gibi hissediyorum kendimi ;)

  2. Veli BOZATLI diyor ki:

    Harukuladenin fevkinde ;)

  3. Zafer Çelenk diyor ki:

    Merhaba,
    Ben ilk olarak merakımdan dolayı uğraştığım C++ dilinde görmüştüm Operatör aşırıyüklemeyi. Önce tuhaf geldiğini söylemeliyim ancak bir kaç defa kullandıktan sonra gerçekten çok kullanışlı ve güçlü bir özellik olduğunu anlamakta geçikmedim tabi.

    Delphininde böyle güzel bir özelliği bünyesine katmış olması çok güzel ama bunun sadece record düzeyinde kalması kötü olmuş. Yazı için elinize sağlık.

    • Tuğrul HELVACI diyor ki:

      Yorumunuza teşekkürler. Aslında operatör aşırı yüklemenin sınıflarda pek bir avantaj sağlayacağını düşünmüyorum. Astarı yüzünden pahalıya gelebilir, eskilerin deyimi ile. Garbage Collection gibi otomatik bir hazıfa yönetimi mekanizması yokken uygulanabilirliği de hatalara açık. Dolayısı ile record’lar operatör aşırı yükleme için yeterli görünüyor gözüme.

  4. Zafer Çelenk diyor ki:

    MyString MyString::operator+(const Dizgi &dzg)
    {
    MyString gecici(”, boy + dzg.boy);

    strcpy(gecici.veri, veri);
    strcat(gecici.veri, dzg.veri);

    return gecici;
    }

    C++ dilinde gerçeklediğim bir string sınıfına ait artı (+) operatörünün aşırı yüklenmesi. Tabi ki yararları gibi zararlarınıda konuşabiliriz. Ama recordların yanında sınıflardada daha bir tatlı oluyor diye düşünüyorum.

    • Tuğrul HELVACI diyor ki:

      Sınıflarda bu tarz işlemlerin yapılabilmesi otomatik çöp toplayıcı olmadan Memory Leak’lere neden olur üstadım. Operatörler class metodlar olduğu için class instanceleri üzerinde çalışamazlar. Dolayısı ile result’ların yeni instance’lar oluşturması icap eder. Bu oluşan instance’ler bir Interface’i implemente etmişler ise ne ala, o zaman ilgili scope’dan çıktıklarında Free olurlar, ama interface implemente etmemişler ise o zaman kullanılmayan ve referansı kaybolmuş onlarca nesnemiz olur. Bu nesnelerin Free edilmemesi de uygulamanın kullandığı hafıza miktarında takdir edersin ki; artışlara neden olur.

      Ya çöp toplama gibi bir mekanizma olmalı, kullanılmayan nesneler belirli algoritmalar ile otomatikman temizlenmeli; yada bu özellik class verilerinde değil de record verilerinde uygulanmalı. İşte bu sebeplerden Delphi record verilerine uygulamayı tercih etmiş. Delphi’nin .Net’e verdiği destek zamanlarında operator overloading sınıflara da uygulanabiliyordu, ama bunun sırrı .Net’in içindeki çöp toplama mekanizması idi ;)

  5. Zafer Çelenk diyor ki:

    Üstad senin bilginin yanında bizim ki elbette yetersiz kalır ama anlamadığım bazı noktalarda beni aydınlatırsan çok memnun olurum.

    Yukarıdaki kod bir .NET kodu değil bildiğimiz C++ kodu ve bildiğimiz C++ dili operatör aşırı yüklemeyi destekler ve yine bildiğimiz C++ dilinde çöp toplayıcı yoktur, diye biliyorum. Ben C++ dilinde bu konuyu derinlemesine bilmiyorum ama bu şekilde bir çok kullanım gördüm. Ya o kodlar yanlış, ya siz konuyu yanlış anladınız yada ben başka türlü bakıyorum olaya, bir türlü anlayamadım.

    • Tuğrul HELVACI diyor ki:

      Estağfirullah.

      O halde öncelikle Memory Leak’ler hususunda bir kaç kelam etmekte fayda olabilir. Memory Leak’ler hepimizin bildiği gibi yönetemediğimiz hafıza taşmalarıdır. Örneğin bir nesneyi create ettikten sonra ona referans veren değişkeni nil’e eşitlemek, yada gösterdiği adresi değiştirmek artık hafızada ayrılmış olan bloğun asla serbest kalamaması anlamını taşır. Bir kaç örnek vermek faydalı olabilir sanırım;

      var
      b : TButton;
      begin
      b := TButton.Create(Self);
      ShowMessage(InttoStr(Integer(Pointer(b))));

      b := TButton.Create(Self);
      ShowMessage(InttoStr(Integer(Pointer(b))));
      end;

      Yukarıdaki kod örneğimizde 2 adet TButton nesnesi oluşturduk ve bu nesnelerin Owner’ını Self olarak ayarladık. Bu durumda; program kapatılırken ilgili Button nesneleri Free edilebilecektir. Ama uygulamamız yaşam döngüsünde iken iki nesnenin de Free edilebilmesi söz konusu değildir. İlk olarak oluşturulan Button nesnesi b değişkeni tarafından işaret ediliyor ancak hemen ardından b değişkeninin gösterdiği adres yeni bir button’un create edilmesi ve b değişkenine atanması yolu ile kaybediliyor. Bu sebeple birinci oluşturulan Button programımız çalışırken asla bir daha yok edilemez. İkinci button’un create edilmesi ve b değişkenine adresinin atanmasından sonra ise procedure’den çıkılıyor ve artık b değişkenine de erişilemiyor. Dolayısı ile ikinci button’da yok edilemiyor.

      Görüldüğü üzere Memory Leak’ler her daim oluşabilme ihtimalleri ile son derece dikkat edilesi programlama unsurlarıdır. Programımızın çalışması sırasında oluşturduğumuz nesnelere, daha sonra o nesneleri Free edebilmek amacı ile ulaşamıyorsak hangi nedenle yada hangi yöntemle olursa olsun bunun adı Memory Leak’tir.

      Şimdi gelelim Operator Overloading’in oluşturacağı memory leak’lere.. İster record’larda kullanın isterseniz de sınıflarda kullanın, tüm operator metodları class metodlardır. Ve class metodların içinden ilgili nesne instance’ına ve o sınıf içindeki static değişken ve metod tanımlarına erişemezsiniz. Bu gibi bir durumda, sınıflara uygulanacak operator overloading’de mecburen geriye yeni bir instance döndürmek durumunda kalırsınız. Örneğin;

      type
      TIntegerClass = class
      private
      fData : Integer;
      public
      class operator Add(a: TIntegerClass; b: Integer): TIntegerClass; // Delphi’de sınıflarda operator overloading olsa idi tanımı bu şekilde olurdu.
      end;

      class TIntegerClass.operator Add(a: TIntegerClass; b: Integer): TIntegerClass;
      begin
      // Bu kod bloğunda Self kullanamazsınız. Result bir sınıf türü olduğu için, mecburen bir instance’ını oluşturmak durumunda kalırdınız.
      Result := TIntegerClass.Create;
      Result.fData := a.fData + b;
      end;

      Şimdi gelelim kullanımına;

      var
      aIntClass : TIntegerClass;
      begin
      aIntClass := TIntegerClass.Create;
      aIntClass.fData := 100;

      aIntClass := aIntClass + 100; // İşte tam bu noktada aIntClass değişkeninin gösterdiği memory adresi bir başka yeri göstermeye başlar ve önceki nesne artık temizlenmek üzere erişelemez duruma geçer.(fDatası 100 olan nesne artık temizlenemeyecektir.)

      aIntClass := aIntClass + 150; // Burada da ikinci oluşturduğumuz yani fDatası 200 olan nesne artık erişelemez duruma gelir.
      end;

      Yukarıda gördüğümüz gibi, sınıflarda olabilecek operator overloading sürekli bir şekilde memory Leak’lerin artmasına neden olur. Buna engel olmanın tek yolu(tabii sınıflarda operator overloading olsa idi), sınıfların herhangi bir interface’i implemente etmesi yada derleyicinin GC gibi bir çöp toplayıcıya sahip olması olurdu. C++ dilini pek iyi bilmem, siz orada sınıflarda operator overloading var diyorsanız vardır. Bu konuda iddia sahibi olacak kadar bilgili değilim, lâkin programlamanın mantığı gereği; ya otomatik temizleme mekanizmasına sahip olmalı, interface implemente ediyor olmalı yada daha da garibi bir sınıf metodu içinden nesne verilerine ve metodlarına ulaşabiliyor olmalıdır.

      Umarım şimdi ortak bir noktada buluşabilmek adına yeterince açık izah edebilmişimdir.

  6. bilgisayar diyor ki:

    Merhaba,
    Sitenizdeki bütün konuları inceleme fırsatım olmadı ama bikaç konuyu inceledim ve sayenizde çok yararlı bilgiler öğrendim.Sitenizde bu güzel bilgileri bizimle paylaştığınız için size çok teşekkür ediyorum.Bundan sonra blogunuzun sürekli takipcisi olacağım…

  7. Nazif AVCI diyor ki:

    Merhaba, çok faydalı bilgileri çok güzel birşekilde anlatıyosunuz. Memory leak konusundada aydınlatıcı bir makale bekliyoruz.

  8. Tuğrul HELVACI diyor ki:

    Merhaba Nazif bey, fırsat bulabilirsem yazacaklarım arasına onu da dahil edebilirim. Ancak memory leak’ler hakkında ne bilmek istiyorsunuz, izah edebilirseniz o noktalara yoğunlaşabilirim makalemi yazarken.

  9. Nazif AVCI diyor ki:

    Memory leak çok sık duyduğum bir kelime ama bugüne kadar yazdığım programlarda hiç dikkat etmedim bu konuya. Nedir bu memory leak, önemi nedir, çok büyük sorunlara yol açar mı? Gibi sorular var kafamda…

  10. Nazif AVCI diyor ki:

    Memory leak ın işi biten hafıza bloğunun temizlenmemesi diye biliyorum fakat, delphi de memory leak a yol açacak örnekler verirseniz çok iyi olur.

Yorum Yazın