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.