C++'de neden std:array sınıfını kullanmalıyız?

2 May 2021

Öncelikle merhaba, bugün daha önceden bilmediğim bir konuyu, biraz da kendime anlatmak amacı ile bir gönderi yazmaya karar verdim. Bilmeniz gereken bir konu, tüm bu yazının yalnızca C++11'den sonrası için çalışıyor oluşu.

Ben çoğu zaman C++ ile de yazsam, C ile de yazsam, basit C tipi dizi tanımlamasını kullanıyordum. Bir örneğini aşağıda bulabilirsiniz. Ancak bugün işlediğim C++ ile Qt Core kursunda hoca standart kütüphanedeki dizi (array) sınıfından da bahsetti. Bu konu ilgimi çekti ve neden bu sınıfı kullanmamızın basit tanıma göre daha avantajlı olduğunu düşünmeye başladım.

int basit_dizi[4] = {5, 7, 8, 40};
Basit bir integer tipli dizi tanımı. Dizinin boyutu 4, son elemanın indisi 3.

C++ Reference sitesinden ilk olarak sınıfın metotlarını inceledim. Çok önemli olduğunu düşündüğüm şu metotlar hoşuma gitti.

  • size $\to$ Dizinin boyutunu döndürüyor. Bu sayede bizi sizeof(basit_dizi)/sizeof(basit_dizi[0]) silsilesinden kurtarıyor.
  • empty $\to$ Dizinin eleman sayısının 0 olup olmadığını denetliyor. Aslında return !(sizeof(basit_dizi)/sizeof(basit_dizi[0])) ile eşdeğer.
  • front $\to$ Dizinin ilk elemanını veriyor. Bildiğiniz return basit_dizi[0].
  • back $\to$ Dizinin son elemanını veriyor. Bunu böyle yapmak ne kadar doğrudur bilmiyorum ancak böyle bir fonksiyon yazacak olsaydım C için, unu kullanırdım: return basit_dizi[sizeof(basit_dizi)/sizeof(basit_dizi[0]) - 1]. Bu sayede eleman sayısını sizeof ile çekmiş ve onun bir eksiğini istemiş olurdum.
  • fill $\to$ İşte bu gayet güzel. Bir döngü ile yazacağımız işi tek bir metota koymuşlar. Yüksek ihtimal bizim düşünemeyeceğimiz hataları da düşünmüşlerdir, elimize temiz bir metot vermişler.
  • swap $\to$ Bu metot ise bir dizi tipindeki değişken ile çağırıyor ve iki değişkenin tüm elemanlarını (yerleri sabit kalmak kaydı ile) değiştiriyor. Acaba değişkenlerin bellek adreslerini mi değiştiriyor diye bir kontrol ettim ancak öyle değilmiş. Yalnızca içeriği değiştiriyor.
#include <QCoreApplication>
#include <array>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    std::array<int, 6> dizi_havali = {125, 11, 98, 67, 2, -439};
    std::array<int, 6> dizi_pek_degil = {15, 14, 13, 12, 11, 10};

    for (int& x : dizi_havali) qInfo() << ' ' << &x;
    for (int& x : dizi_pek_degil) qInfo() << ' ' << &x;
    qInfo("---");

    dizi_havali.swap(dizi_pek_degil);

    for (int& x : dizi_havali) qInfo() << ' ' << &x;
    for (int& x : dizi_pek_degil) qInfo() << ' ' << &x;

    return a.exec();
}
swap() metotunun bellek adresi değiştirip değiştirmediğini kontrol ettiğim kod. Ee, tabi ki Qt Core'u sıkıştıracağız araya, o kadar öğreniyoruz dedik. ;)

Iterator Metotları

Iterator'ün (yoksa "'un" mu?) ne işe yaradığından öncelikle bahsetmek istiyorum. Bir veri tipi yarattığınızı varsayalım. Buna bir sınıf deyin, bir struct deyin, başka bir şey deyin fark etmez. Kendinize has bir içeriği olan bir veri tipi diyelim biz şimdilik. Bu veri tipinde de en baştan en sona kadar dolaşmak istiyorsunuz. Bunu nasıl yaparsınız?

Şimdi, ben eğer bu şekilde bir sıralı veri tipi hayal edersem aklıma henüz yeni öğrendiğim struct ile oluşturulmuş bağlantılı liste tipi geliyor. Bir bağlantılı liste içinde dolaşmak için baglantili_liste[indis] şeklinde bir çağırma yapamam. Bunun yerine bir döngü yazmalıyım ve bu döngü içinde sürekli bir sonraki elemana gitmeliyim. Bu döngüyü gidip bir fonksiyona yedirirsem, elimde çok basit bir iterator olmuş olur. Ha, bunu C++ iterator olarak görmez orası ayrı!

Anlatmak istediğim mantık şu, iteratorleri, bizler o veri tipini dolaşmak için kullanırırız. Çünkü çoğu zaman ilgili veri tipini dolaşmak indis artışı ileveri_tipi[indis] kadar kolay değildir. C++ ise bunu yapabilmemiz için bize çok güzel bir yol tanımış.  Sınıfların içine iterator metotları koyun," demiş, "ben sizin basit bir döngü ile dolaşmanızı sağlayacağım!". Bunu ise yukarıdaki örnek kodumda şu şekilde yaptığımı görebilirsiniz.

// elemanın_veri_tipi := Elemanın veri tipi, mesela int gibi.
// atanacak_eleman := Veri tipinde dolaşırken 
// elemanları atayacağımız değişkenin kendisi.
// veri_tipi_degiskeni := Veri tipini oluşturduğumuz
// değişkenin kendisi.
// expression() := Artık dolaşırken ne yapmak istiyorsanız o.

for (elemanın_veri_tipi atanacak_eleman : veri_tipi_degiskeni) expression();

Array sınıfını kullanırsanız size bu iterasyon olayları için tam olarak 8 metot veriliyor. Bu sekiz metottan ikisi ileriye doğru gezinme için, ikisi geriye doğru gezinme için ve geri kalan dördü ise "değiştirmeden (const ile)" gezinme için.

Peki, soruyorum size, gerekli mi tüm bu metotlar? Öncelikle kontrol edelim, bu sınıfın header dosyasının satır sayısı 386.

Bu sınıfı özellikle gömülü için bir kod yazarken ben açıkçası kullanmam. 386 satırlık kod yerine bir-iki satır daha fazla yazarım, çok daha azında, basit bir dizi kullanarak hallederim. Zaten denilebilir, "C++ ile gömülü mü olur?" diye. Ancak masaüstü yazılımları için kullanabilirim, sonuçta disk alanı kısıtım yok.

Başlıkta neden kullanmalıyız yazıyor, ancak ben bu yazıyı yazarken, kullanmamaya karar verdim. En azından yazı boyunca iteratorleri öğrendik ve sınıfı tanıdık. Güzel oldu.

Herkese iyi çalışmalar dilerim.


Published in cpplang::tr