Adı üstende! Temel(primitive) veri tipleri aslında programlamanın değil ama, orta seviye bir dil için onu öğrenmenin temeli🎯 sayılabilir. Haliyle teorik binlerce satırlık bilgiyi herhangi bir yerden zaten bulabilirsiniz. "3-4 tane terim, ne var bunda!" diyerek Object Oriented kodlamaya geçebilirsiniz. Zamanında ben de dahil, çoğu yazılımcının bu hataya⚠️düştüğünü gördüğüm için böyle bir yazı karalamak istedim.

Değişkenler için, daha çocuk yaşta cebirdeki denklem ve eşitsizliklere giriş ile beraber başımıza musallat olan X'e karşılık gelir diyebiliriz. 😌 Yani aslında değişken kavramı yazılıma has değil; matematikle birlikte fizik, kimya vb. diğer sayısal bilimler için geçerli bir kavramdır. Örneğin fizikte yaptığınız deneyde ölçtüğünüz basınç, sıcaklık, hız, sürtünme gibi değerler birer değişkene karşılık gelebilir. Hani bağımlısı, bağımsızı olan; 2 bilinmeyenli denklemlerin meşhur bilinmeyeni, hayranı olduğum büyük üstat Ömer Hayyam'ın bize hediyesi olan X. Bilgisayarlarda ise ilk kullanıldığı zamanlarda kavram olarak da benzer bir kavram iken, günümüzde belleklerin farklı bölgelere ayrılması ile veriye daha hızlı erişim ve belleğinin etkin kullanımı amacı ile geliştirilmiştir. Ama ben teknik anlamından ziyade olası en basit şekliyle kafanızda canlandırmaya çalışacağım. Yoksa teknik olarak çok daha ayrıntılı bir konudur. Haliyle bu yazı kod yazmada acele edenler için değildir, evde denemeyiniz!🤪

Şimdi bir hesap makinesinde çeşitli işlemler yaptığımızı düşünelim. Elde ettiğiniz sayıyı başka işlemlerde kullanmak için hafızaya almak için M+ tuşuna basarsınız. En kaba tabiriyle değişken; hafızaya almak için bastığımız bu tuşun işlevini görür. Hesaplamaya devam edelim. Sonraki işlem de bir sonuç doğuracak; sonrasında bir tane, bir tane daha... Aslında hesap makinesi adı üstünde gerçek bir 'compute' aracıdır ama hoppala 'hesapsayar'ımız çuvalladı! Başka bir sayıyı hafızaya alamıyor. Şimdi biz ona tekrar veri gireceğiz ve onun bize bir 'bilgi' daha üretmesi yani hesapsayarımızın bilgi-sayara dönüşmesi gerekiyor. Tabi bu bilgi insan algısındaki bilgi ile aynı değil düşünürken basitliği elden bırakmayın. İşte yaptığımız işlemlerle birlikte aynı zamanda rastgele erişimli hafıza(ram) dediğimiz hadiseye ihtiyacımız doğdu. Programlama dillerinde hesap makinesindeki gibi tek değişkenlik bir sınır olması söz konusu olamaz. Bizler için bunun bir sınırı yoktur. 'Yoktur'dan kastım günümüzdeki ram kapasiteleri ile değişkenlerin ramde kapladıkları boyutlar göz önüne alınırsa neredeyse yoktur. Bizim M+ tuşumuzdan birçok sayıda bulunur. Ayrıca hesap makinesinde tek bir değeri tuttuğumuz için buna isim vermeye gerek olmasa da biz değişkeni hafızaya çıkartırken bir isim verir ve o değişkene ulaşmamız gerektiğinde bu isim üzerinden ulaşırız. Değişken isminin amacı bir nevi hesap makinesindeki MR tuşuna benzer.

Mesela bir içecek alacağız. İçeceği içebilmek için doldurduğumuz bardak bir nevi değişkene benzetilebilir. Ortada bir şekilde ihtiyacımız kadar olan içeceği alabilme problemi var. Bardağa koyup istediğimiz yere taşıma çözümünü getirmiş oluyoruz. İçtikten sonra yıkayıp bardağı kaldırırsınız, yani böyle farz ediyorum.😏İşte değişkenler de bu şekilde ihtiyaç duyulan veri kadar tanımlanır, ister tanımlandığında ister daha sonra içi doldurulur daha sonra bellek tıkanmalarını önlemek için işi bittikten sonra kaldırılmalıdır. Aslında donanıma müdahale etmediğiniz bir uygulamada artık bunları frameworkler arkada sessiz sedasız halledebiliyorlar. Yani modern diller sizin yerinize bulaşıklarınızı toplayıp ortalığı siler süpürür. Bunu kısmi olarak manuel hale getirebildiğiniz bir mekanizma da yine diller içinde kısmen bırakılmış durumdadır.

Aslında şu an çok kaba tabirlerle canlandırmaya çalışıyorum, yalnız introduction bölümünü bitirip nesne yönelime başlayan birisi bu benzetmemde kafa karışıklığı yaşayabilir. Eğer değişken konusuna henüz başladıysanız buraya tıklayarak devam edin, yok nesne yönelime yeni başladıysanız devam edebilirsiniz. İşin aslı; bardağın kendisi kendine has rengi, boyutları vb. özellikleri olan bir nesnedir. Beyaz renk, 5 cm. çapında, 10 cm. yüksekliğinde bir bardak olsun. Bu tanımlarda kullandığımız "beyaz renk, 5cm. çap, 10 cm. yükseklik" birer değişkene karşılık gelir. Bardağı bu şekilde ayrıştırırsanız temeline inmiş olursunuz. Yani temeli derken atomaltı da demedik, işte uygulamalarımızdaki modellemeler için ihtiyacımız olan kadar temeli. "Temel Veri Tipleri" denmesinin sebebi de budur. Nasıl ki matematikte rakamlar olmadan hiçbir işlem yapamazsak makine dili hariç yazılım dillerinde de temel veri tipleri olmadan bir iş yapamazsınız. Bu örneğim üzerinden gidersek, işler arka planda daha da ayrıntılı veya daha doğru bir ifade ile değişken tanımlama mekanizması çok daha özgür bir kullanım sağlar. Şöyle bir evde yaşadığınızı düşünün: tüm hammaddeleri ve bu hammaddeleri ihtiyacınız olduğunda işleyerek size gereken eşyayı istediğiniz fonksiyonellikte ortaya çıkaran makinelerin, o eşya ile işiniz bittiğinde yine aynı hammaddeye çeviren bir geri dönüşüm sisteminin olduğu bir ev. Değişkenler ile bardağın özelliklerini oluştur dediğinizde kullanıma hazırdır. üstelik isterseniz içeçeği de hazır veya rengi, boyutları size özel belirlenmiş bir şekilde. Bu özellikler bu satırda .NET'e özel torpil geçersem; eğer sınıf kapsamında kullanılırsa field, kontrollü bir şekilde değer girilmesi isteniyorsa aynen İngilizce karşılığında olduğu gibi property diye geçer ki şahsen kod okunurluğu bakımından C#'ı birçok dilden avantajlı hale getirdiğini düşünürüm. Bunu sağlayan OOP ilkelerinden encapsulation dediğimiz ilkedir. Şimdilik koda girmek istemem ama sadece küçücük bir örnek olması açısından...

    public class User {
        public string Name { get; set; }
    }

Yukardakine benzer bir söz dizimini kısmen Java'da da uygulayabiliriz ama bence attığımız taş ürküttüğümüz kurbağa örneğine benzer. Bunu elersek Java'da şu şekilde yazarız.

public class User {
    private String name;
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
}

Bu bardak mutfağın dışında başka bir yerde kullanılmasın gibi bir isteğimiz olursa da erişim belirleyici dediğimiz anahtar kelimeler devreye girer. Bu bardak ailede büyükler kullanabilsin ama çocuklar kullanamasın derseniz kalıtım ile bunu sağlayabilirsiniz. Ama aynı bardağı büyükler farklı, küçükler farklı hareket veya özelliklerde kullansın isterseniz polymorphism, su içen kişi içme esnasında farklı hareketler de yapmaya zorunlu olsun derseniz interface, içerken tek bir sözünüzle sizin istediğiniz sırada hareketleri yapsın gibi tuhaf bir isteğiniz olursa delegation kullanırsınız. Sizin istediğiniz hareketler dışında, içen kişi kendi hareketlerini belirlemek isterse tanımladığınız delegasyonu uygun olanını kullanarak bir fonksiyon(event) oluşturur veya kendi oluşturduğu delegasyon üzerinden fonksiyon oluşturup bunu yapabilir. Bir tık öteye gideyim derseniz, mesela yukarıda bahsettiğim kalıtım örneğini generic sınıflar ile çok daha esnek ve özgür bir mimari sağlayabilirsiniz. Eğer sadece değişkenin ne olduğunu öğrenmek istiyor idiyseniz, yukarıdaki uyarıma rağmen bu paragrafa devam ettiyseniz çoktan pişman olmuşsunuzdur. 😄Çünkü bu bahsettiklerim nesne yönelimli programlama ile ilgilidir. Bu konu böyle bir paragrafta bahsedilebilecek kadar basit değil tabi ama ben ana hatlarıyla girmek için bunları yazdım. Tabi OOP öğrendiğinizde dünya önünüze serilmiyor. Hatta asıl her şey o zaman başlıyor. Bununla OOP konseptleri üzerine mimariyi düzgün kurdurmak için SOLID, Design Patterns, Aspect Oriented Programming gibi bir türlü yetişemeyeceğiniz prensip, yaklaşım veya metodoloji daha birçok kavram var. Örneğin bu saydıklarım her biri binlerce satır ayrıntıyla anlatılabilecek kavramlar.

Konuya dönersek; her şeyden önce "Neden farklı veri tipleri ile çalışmaya ihtiyaç duyarız?" sorusuna bakalım. En sade cevabı; farklı veri tiplerinin bellekte kaplayacakları alan ve onlara yaptıracağımız işlemler farklıdır. Örneğin tamsayılar, metinsel ifadeler, mantıksal ifadeler ilk akla gelenlerdir. Tamsayılarla dört işlem yaptırabiliyorken, metinsel ifadeler ile bu mümkün değildir. O nedenle her programlama dilinde farklı işlemler için farklı veri tipleri bulunur. Bu şekilde hem ramde ayrılacak yerden israf etmemiş, hem de sadece o veri tipi ile yapılabilecek işlemlerin yapılmasına izin vermiş oluruz. Ayrıca bazı diller tip güvenli ya da tip denetimli dillerdir. Nedir bu tip güvenliği? Tip uyumu için tüm işlemlerin derleyici kontrolünden geçirileceği anlamına gelir. Kural dışı olan işlemler derlenmez ve derleme zamanında hata verirler. Böylece hataları önlemeye yardımcı olarak kodunuzun güvenilirliği artırılmış olur. Bu yüzden 'tipi belirlenmeyen bir değişken' gibi bir kavram, mesela C# için söz konusu değildir. Bir değişken bildirimi yapılırken o değişkenin veri tipi kesinlikle bildirilmek zorundadır. Sonrasında da o değişkene atanan veriler, belirtilen veri tipinin sınırları dışına çıkamaz. Bunun bir istisnası, bir değişkenin tuttuğu verinin başka bir tipe dönüştürülmesi(casting) işlemidir. Bu veri tipi tarafında sadece küçücük bir örnek ama bu disiplinin sağladığı (generic architecture, reflecting, refactoring) gibi pek çok avantaj vardır. Fakat örneğin web uygulamarında başka alternatifiniz olmayan JavaScript derleyicisi bu denetimi yapmaz. Yani bu yazıyı okurken tarayıcı konsolunu açıp aşağıdaki kod satırlarını yapıştırırsanız herhangi bir hata vermeden derlenip sonuç yazılacaktır.

var name = "John Doe";
var number = 'A' + 1;//HEX A1 = DECIMAL 161
var number2 = '2' + 1;//Output: 21
console.log(name);

name = 123;
console.log(name);
console.log(number);

name değişkenine 123 atanmasına rağmen hata vermeden derlenir. console.log() fonksiyonunu Console.WriteLine() metodu ile değiştirerek C# için alacağınız derleme hatasını görebilirsiniz. Bu denetim, bir eksik veya JavaScript'in dezavantajı değil; dilin yaklaşımı böyledir.

Kısacası değişken, ramde belirli tipten bir veriyi tutması için ayrılan adrestir. İhtiyaç duyulan değerleri ramde geçici olarak saklamak amacı ile kullanılırlar. Her değişkene, ramde o değişkenin tutacağı veri tipine karşılık gelecek kadar bir yer ayrılır. Bu yere "değişkenin adresi" denir. Adresi olduğu gibi her değişkenin bir adı da vardır. Bu ad kullanılarak; değişkene değer atanabilir, tekrar okunabilir veya değiştirilebilir. İstendiğinde bazı kısıtlamalar getirilebilmekle birlikte; bir değişkene erişebilmek demek bu 3 isteği yerine getirebilme yeteneklerine sahip olmaktır.

Değer Tipleri ve Referans Tipleri

Nesne yönelim yaklaşımında veri tipleri, değer tipi ve referans tipi olmak üzere iki genel kategoride toplanır. Bu iki tip arasındaki fark, bir değişkenin ne içerdiği ile ilgilidir. Değer tipinde bir değişken 10 ya da 10,05 gibi gerçek bir değer içerirken; referans tipi bir değişken, o değere yapılan referansı içerir. C diline aşinalığınız varsa pointer mantığının aynısıdır. Değer tipleri belleğin stack(yığın) bölümünde çalışırken referans tipleri heap(öbek) bölümünde çalışır. Basit kalmaya çalışıyorum, eğer biraz daha derine ineyim derseniz belleğin bu bölümlerini araştırabilirsiniz.

Stack vs Heap

Temel tiplerden bir referans tip oluştururuz ve bu tip çağrıldığında bir nevi paketlenip bütün olarak önümüze geldiği için, heap daha yavaş çalışır. Ama hız dışındaki asıl büyük fark; değer tipleri veri aktarımında kopyalama yaparken, referans tiplerden oluşturulan örneklerin tümü ram üzerinde tek bir adresi işaret eder. Bu yüzden birinde yapılan değişiklik tümünü etkileyecektir. Eğer bunu gerektiği gibi kavramayazsanız tehlikeli sularda geziyorsunuz demektir!

Stack vs Heap

Tablodaki değer aralıklarını bilmeniz gereksiz olsa da örnek veri tipleri olması açısından MSDN'den kopyalayıp üzerinde küçük düzenlemeler yaptığım tipler aşağıda.

Değer Tipleri
Veri Tipi .NET (CTS) Karşılığı Açıklama Aralık
sbyte System.Byte 8 bit işaretli tam sayı [-128, 127]
short System.Int16 16 bit işaretli tam sayı [-32.768, 32.767]
int System.Int32 32 bit işaretli tam sayı [-2.147.483.648, 2.147.483.647]
long System.Int64 64 bit işaretli tam sayı [-9.223.372.036.854.775.808, 9.223.372.036.854.775.807]
byte System.Byte 8 bit işaretsiz tam sayı [0, 255]
ushort System.UInt16 16 bit işaretsiz tam sayı [0, 65.535]
uint System.UInt32 32 bit işaretsiz tam sayı [0, 4.294.967.295]
ulong System.UInt64 64 bit işaretsiz tam sayı [0, 18.446.744.073.709.551.615]
float System.Single 32 bit tek kayan sayı(~6-9 basamak) ± 1,5 x 10-45, ± 3,4 x 1038~
double Sytem.Double 64 bit çift kayan sayı (~15-16 basamak) ± 5,0 x 10-324, ± 1,7 x 10308 ~
decimal System.Decimal 128 bit ondalıklı sayı (~28-29 basamak) ± 1,5 x 10-28, ± 7,9 x 1028~
bool System.Boolean 8 bit true ya da false
char System.Char 16 bit Bütün unicode karakterleri
Referans Tipleri
Veri Tipi .NET (CTS) Karşılığı Açıklama
object System.Object Bütün veri türlerinin türediği sınıf
string System.String Unicode karakterlerinden oluşan string