std::vector temel işlemleri

Bu yazıda, C++'ın standart topluluklarından (container) birisi olan ve C dizileri gibi çalıştığı için anlaşılması ve kullanımı en kolay olan vector'ün üzerinde duracağım. Umarım yazı içinde vector'ü hemen kullanmaya başlayacağınız kadar bilgi bulursunuz. Eksik kaldığını veya anlaşılmaz olduğunu düşündüğünüz yerleri C++ forumunda tartışmaya açarsanız yazıyı daha da yararlı hale getirmiş olursunuz.

Standart topluluklar ve ögelerine erişim sağlayan erişiciler (iterator) konusunda kısa bir hatırlatma için C++'ın türden bağımsız toplulukları yazısından yararlanabilirsiniz.

Burada amacım, vector topluluğunun en sık kullanılan olanaklarını göstermek olacak. Toplulukların arayüzlerinin tamamını görmek için, standart C++ kütüphanesindeki toplulukların ilk hallerini de sağlamış olan STL belgelerine bakabilirsiniz.

'vector' şablonunu kullanabilmek için, kaynak koda onun bildirilmiş olduğu <vector> başlığını eklemek gerekir. Ayrıca, 'vector' de diğer bütün standart adlar gibi, 'std' ad alanı içinde bildirilmiş olduğundan; vector'ü kullanmadan önce, en azından başlığın eklendiği satırdan sonraya bir

    using namespace std;

satırı ekleyebilirsiniz.

'vector'lere öge eklemenin en doğal yolu, nesneleri topluluğun sonuna eklemektir. Bu örnek, girişten gelen sayıların yalnızca çift olanlarıyla (2, 4, 6, vs.) ilgileniyor. Girişten sayıları okuyor, o sayıların ikiye tam olarak bölünenlerini saklıyor ve sonunda da o nesnelerle bazı işlemler yapıyor. 'vector'ün kullanımıyla ilgili başka bilgileri programın içine açıklamalar halinde yerleştirdim.

Programı deneyecekseniz ve çoğu durumda olduğu gibi standart girişin klavyeye bağlı olduğu bir şekilde çalıştıracaksanız; sayıları klavyeden girdikten sonra girişi sonlandırmak için UNIX türevi işletim sistemi kabuklarında Ctrl-D'ye, bir DOS penceresinde de Ctrl-Z'ye basabilirsiniz.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

/* 
   Kodun okunmasi ve bakimi kolay olsun diye,
   tamsayi turunden nesneler tutan vector topluluguna
   Sayilar adini takiyoruz.

   Boylece, program icinde tamsayilar toplulugunu
   kullandigimiz dort yerde vector<int> yazmak
   yerine, Sayilar yazabilecegiz.
*/
typedef vector<int> Sayilar;

/*
  Bu islev, standart giristen okudugu cift sayilari
  bir toplulugun sonuna ekliyor. Diger sayilar
  gozardi ediliyorlar.

  Burada konudan biraz ayrilacak ve bazilarinizin
  bir performans sorunu olarak gorebilecegi bir
  kullanima deginecegim.

  Dikkat ederseniz, olusturulan sayi toplulugu, bu
  islevden deger olarak donduruluyor. Bu durumda,
  sayi toplulugunun bir kopyasinin cikartildigini
  dusunebilirsiniz. Ancak, iyi nitelikli derleyiciler
  'named return value optimization' (NRVO)
  olarak adlandirilan bir yontem uygularlar ve donus
  degerini kopyalamak yerine, yalnizca sonucta o
  degerin atanacagi nesneyi olustururlar.

  Bunun sonucunda da, main icinde

    Sayilar ciftler = giristenCiftleriOku();

  satirinda yalnizca bir tane sayi toplulugu
  olusturulmus olur.

  Zaten bu tur performans kaygilarini programin
  calismasinda bir yavaslik oldugu zamanlara ve o
  yavasligin nedeni olan satirlara ayirmamiz gerekir.

  Ayrica hatirlarsaniz: Zamanindan once yapilan
  eniyilestirme her kotulugun anasidir. (Premature
  optimization is the mother of all evil.) :)

  Artik asil konumuza donebiliriz :)
*/
Sayilar giristenCiftleriOku()
{
    // vector toplulugunu bos olarak olusturuyoruz
    Sayilar ciftler;

    // Giristen gelen sayilari bu degiskene atayacagiz
    int sayi = 0;

    while (cin >> sayi)
    {
        /*
          Ikiye bolumunden kalani 'operator%' ile
          buluruz. Bu kalan sifir oldugunda sayi cifttir.
        */
        int const kalan = sayi % 2;

        if (kalan == 0)
        {
            /*
              'vector'un push_back islevi, toplulugun
              sonuna yeni bir nesne eklemek icin kullanilir
            */
            ciftler.push_back(sayi);
        }
    }

    return ciftler;
}

/*
  Iste hangi toplulukla calistigindan haberi olmayan
  bir kullanici islevi sablonu. Bu sablon, bir
  erisici gibi davranan her turle calisabilir.
*/
template <class Erisici>
void cikisaYazdir(Erisici simdiki, Erisici son)
{
    /*
      Bu algoritma, 'simdiki' ve 'son' parametreleriyle
      belirtilen araligin gercekte ne tur bir
      topluluk icinde bulundugundan habersiz oldugu icin,
      ekrana ancak boyle genel bir ifade yazabiliyor.

      Ornegin "'vector'deki nesneler" gibi ozel bir
      sey soyleyemiyor.
     */
    cout << "Araliktaki nesneler: ";

    while (simdiki != son)
    {
        cout << *simdiki << ' ';
        ++simdiki;
    }

    cout << '\n';
}

/*
  Bir islev sablonu daha.
*/
template <class Erisici>
double ortalama(Erisici simdiki, Erisici son)
{
    double sonuc = 0;
    size_t adet = 0;

    for ( ; simdiki != son; ++simdiki)
    {
        sonuc += *simdiki;
        ++adet;
    }

    return sonuc / adet;
}

/*
  Bu islev, vector topluluklarinin ogelerine
  rastgele erismek icin dizi erisim islecinin
  (operator[]) nasil kullanildigini gosteriyor.

  Bu kullanimda vector, bir C dizisi gibi calisir.
 */
void ucuncuOgeyiYazdir(Sayilar const & sayilar)
{
    cout << "Ucuncu sayi: " << sayilar[2] << '\n';
}

/*
  Sayilar turunun her bir ogesi bu ornekte 'int'
  oldugu icin, bu islevi 'transform' algoritmasi
  ile kullanabiliriz.

  'transform' algoritmasini daha asagida tanitiyorum.
 */
int karesi(int sayi)
{
    return sayi * sayi;
}

int main()
{
    cout << "Giristen tamsayilar okuyacak "
            "ve cift olanlarini saklayacagim...\n";
    
    Sayilar ciftler = giristenCiftleriOku();

    /*
      Butun standart topluluklar, barindirdiklari
      ogelere bastan sona erisim saglamak icin
      'begin' ve 'end' adli oge islevler sunarlar.

      Geleneksel olarak; 'begin' ilk ogeyi,
      'end' ise son ogeden bir sonrayi gosterir.
      'end'in hicbir zaman bir ogeye erismek
      icin kullanilmamasi gerekir; cunku 'end'in
      gosterdigi gecerli bir oge yoktur.
     */
    cikisaYazdir(ciftler.begin(), ciftler.end());

    cout << "Ortalama: "
         << ortalama(ciftler.begin(), ciftler.end()) << '\n';

    /*
      Standart topluluklarin geleneksel islevlerinden
      olan 'size', topluluktaki oge sayisini verir.
    */
    if (ciftler.size() >= 3)
    {
        ucuncuOgeyiYazdir(ciftler);
    }
    else
    {
        cout << "Bu toplulukta ucten daha az oge var\n";
    }

    /*
      Simdi de toplulugumuzu standart algoritmalardan
      birisiyle kullanalim.

      <algorithm> basliginda tanimlanan 'transform,' bir
      cift giris erisicisi, bir adet cikis erisicisi ve
      nesnelere uygulanacak bir islem (ornegin bir islev
      gostergesi) alir.
      
      Yaptigi is; giris erisicileriyle belirlenen nesnelerin
      her birisini, kendisine verilen islemden gecirdikten
      sonra cikisina kopyalamaktir.

      Bu tanim, sozluk anlami olan 'donustur'e de uyar:
      giristeki nesneler bir islemden gecirilerek
      donusturulurler ve cikisa gonderilirler.

      Buradaki kullanimda cikisin baslangicini belirleyen
      erisici ile girisin baslangicini belirleyen erisicinin
      ayni olduklarina dikkat edin (ciftler.begin()). Cikisi
      girisin aynisi olarak kullandigimiz icin; aslinda
      girisi, yani 'ciftler' toplulugunu donusturmus oluyoruz.
     */
    transform(ciftler.begin(),   // girisin basi
              ciftler.end(),     // girisin sonu
              ciftler.begin(),   // cikisin basi
              karesi);           // her ogeye uygulanacak islem

    cout << "Butun ogeleri kareleriyle degistirdikten sonra...\n";
    cikisaYazdir(ciftler.begin(), ciftler.end());

    return 0;
}

'vector'le ilgili olarak bu kadarını bilmek, ondan çok büyük ölçüde yararlanmak için yeterli olacaktır. Bu kadar bilgiyi kendi programlarınızda uygulayarak aynı C dizileri gibi çalışan ve çoğu programda onlardan hiç de yavaş olmayan topluluklar oluşturabilirsiniz. Üstelik C dizileri yerine vector topluluklarını kullandığınız zaman, bellek yönetimi ile de ilgilenmek zorunda kalmazsınız.

Bir sonraki yazıda vector'ün hem daha az kullanılan, hem de daha ileri düzey kabul edilebilecek olan kullanımlarına değineceğim. Orada vereceğim örneklerde, vector'ü 'int' dışındaki türlerle de kullanacağım.

Teşekkür:

Yazının taslakları üzerinde yaptıkları yararlı eleştirilerden dolayı Volkan Uzun'a ve Ersin Er'e teşekkür ederim. Daha önceden gözardı ettiğim bir çok önemli ayrıntıyı onların uyarılarından sonra ekledim.


Ali Çehreli - acehreli@yahoo.com