Bu yazıda C++ toplulukları içerisinde benim belki de en sık kullandığım topluluk olan std::map topluluğunu ve onun biraz daha farklı bir türü olan std::multimap'i tanıtacağım. Tam adı std::map olduğu halde, okunurluğu arttırmış olmak için yazının geri kalanında ad alanı belirtecini kullanmadan yalnızca 'map' yazacağım.
Bu toplulukları anlatırken, onların ögelerini tutmak için kullandıkları std::pair yapısını da tanıtmak gerekecek.
Bu yazıya devam etmeden önce, içerisindeki kavramlara temel oluşturduğunu düşündüğüm [1] ve [2] numaralı referanslardaki yazıları okumak isteyebilirsiniz.
C++ topluluklarından en basiti olduğu söylenebilecek olan std::vector ile ilgili iki yazıyı da [3] ve [4]'te bulabilirsiniz.
map, bir ilişkili dizi gerçeklemesidir. Bunun ne olduğuna geçmeden önce, sıradan C dizilerinin de aslında ilişkili dizilerin özel bir hali olarak düşünülebileceklerini göstermek istiyorum.
Bilindiği gibi, öge erişim işleci (operator[]), C dizileriyle birlikte kullanıldığında parametre olarak bir sıra numarası alır:
double dizi[] = { 1.23, 4.56 };
size_t sira = 0;
dizi[sira] = 7.89;
Doğal olarak, bu kullanıma bakarak operator[] işlecinin belli bir sıradaki ögeye erişmek için kullanıldığını düşünebiliriz. Yani, verilen bir sıra numarasına karşılık dizi içindeki bir ögeye erişiriz.
Ancak; bu davranışı, verilen tamsayı ile "ilişkili olan" nesneye erişmek olarak da ifade edebiliriz. Bunu yaptığımızda, işlece verilen tamsayının aslında bir anahtar olduğunu söyleyebiliriz: bir dizi nesne içerisinden, tamsayı türünden bir anahtarla ilişkili olan bir tanesine erişiriz.
Eğer bu anahtar kavramını tamsayılardan başka türlere de genişletirsek, ilişkili dizilerin kısa bir tanımına varmış oluruz: içindeki nesnelere belirli anahtarlara göre erişim sağlayan topluluk.
map ile kullanılan anahtar ve nesnelerin türleri, belli koşulları sağladıkları sürece herhangi türden olabilirler. Elimizde map ile gerçeklenmiş bir telefon rehberi olduğunu ve rehberdeki numaralara kişilerin adlarıyla erişildiğini düşünürsek, map'i örneğin şöyle kullanabiliriz:
TelefonNumarasi & numara = rehber["ali"];
Burada rehber'in bir dizi gibi kullanıldığına, ama erişim sırasında tamsayı yerine dizgi gönderildiğine dikkat edin.
map de aslında bir sınıf şablonu olduğu için, kullanılmadan önce anahtar ve nesnelerinin hangi türlerden olduklarının bildirilmesi gerekir. Bunu, map şablonunun iki zorunlu parametresi ile bildiririz: Birinci parametre anahtarın, ikinci parametre ise nesnelerin türünü belirler. Örneğin, yukarıdaki telefon rehberini anahtarlar 'string', ve içindeki nesneler 'TelefonNumarasi' türlerinden olacak şekilde şöyle tanımlarız:
map<string, TelefonNumarasi> rehber;
Bu telefon rehberini kullanan küçük bir programa geçiyorum. Bu program, map'in operator[] işlecinin davranışının ilginç bir yönünü de gösteriyor:
#include <map>
#include <iostream>
using namespace std;
class TelefonNumarasi
{
friend ostream & operator<< (ostream &,
TelefonNumarasi const &);
string alanKodu_;
string numara_;
public:
/*
Varsayilan parametre degerleri nedeniyle bu
kurucu, ayni zamanda bir varsayilan kurucudur
(default constructor)
*/
explicit TelefonNumarasi(string const & alanKodu = "-",
string const & numara = "-----")
:
alanKodu_(alanKodu),
numara_(numara)
{}
};
ostream & operator<< (ostream & cikis,
TelefonNumarasi const & numara)
{
return cikis << '(' << numara.alanKodu_ << ") "
<< numara.numara_;
}
int main()
{
/*
Toplulugun tanimi
*/
map<string, TelefonNumarasi> rehber;
/*
Icinin doldurulmasi
*/
rehber["ali"] = TelefonNumarasi("1", "23456");
rehber["veli"] = TelefonNumarasi("5", "67890");
cout << "Ali'nin numarasi : " << rehber["ali"] << '\n';
/*
Icinde bulunmayan bir nesnenin yazdirilmasi
*/
cout << "Kaya'nin numarasi: " << rehber["kaya"] << '\n';
cout << "Toplam telefon numarasi: " << rehber.size() << '\n';
}
TelefonNumarasi sınıfının [2] numaralı referansın 'topluluk ögesi olma koşulları' bölümünde anlatılanlara uygun olarak bir varsayılan kurucu sunduğuna dikkat edin.
Bu programda ilk önce "ali" ve "veli" adlı kayıtları topluluğa ekliyoruz. Daha sonra da önce toplulukta bulunan ("ali") ve bulunmayan ("kaya") iki kaydın telefon numaralarını çıkışa yazdırıyoruz.
Toplulukta bulunmayan kayıt için de bir telefon numarası yazdırıldığına ve bunun sonucunda da toplam öge sayısının üç olduğuna dikkat edin. Sonuçta toplulukta şu kayıtlar bulunmaktadır:
ali (1) 23456
kaya (-) -----
veli (5) 67890
Bunun nedeni, map::operator[] işlecinin aslında şu algoritmayı işletmesidir:
1) eğer anahtar toplulukta yoksa
{
topluluğa o anahtara karşılık bir nesne ekle
(nesneyi varsayılan kurucusuyla kur)
}
2) o nesneye erişim sağla
Bu garip davranışın bir nedeni, histogram türü uygulamaları kolaylaştırmaktır. Girişten gelen sözcüklerin kaç kere görüldüklerini sayan şu programın ne kadar kolaylıkla yazılabildiğine bakın:
#include <map>
#include <iostream>
using namespace std;
int main()
{
map<string, unsigned long> toplamlar;
string sozcuk;
while (cin >> sozcuk)
{
++toplamlar[sozcuk];
}
cout << "Alfabetik siradaki ilk sozcuk olan '"
<< toplamlar.begin()->first << "' sozcugunu "
<< toplamlar.begin()->second << " kere gordum\n";
}
Şimdilik toplamlar.begin() kullanımını bir kenara bırakın; map'in ögelerinin türlerini biraz sonra anlatacağım.
Burada göstermek istediğim şey, bu programın işinin (sözcüklerin girişte kaç kere görüldüklerinin sayılmasının) tek bir satırda yapılabilmesidir. ++toplamlar[sozcuk] satırını şöyle açıklayabilirim:
1) eğer 'sozcuk' toplulukta yoksa
{
topluluğa o sözcüğe karşılık gelen bir nesne ekle
Bu topluluktaki nesnelerin türleri 'unsigned long'
olduğu için, ve 'unsigned long' türünden nesnelerin
varsayılan kurucuları onlara 0 değerini atadığı için,
bu durumda 'sozcuk' anahtarına karşılık elimizde 0
değeri olacaktır.
}
2) o nesneye erişim sağla
3) o nesnenin değerini bir arttır
Yani o satır işletildiğinde, ilk defa görülen bir sözcüğün değeri 1 olur.
operator[] işlecinin garip kabul edilebilecek bu davranışı, bu tür bir programda kolaylık sağlar. Ancak, yanlış bir kullanımda topluluğa sessizce istenmeyen ögeler eklenmesine de neden olabilir.
Buraya kadar anlattıklarımın std::map topluluk şablonunu hemen kullanmaya başlamak için yeterli olduğuna inanıyorum. Kısaca:
İngilizce'de "çift" anlamına gelen pair, herhangi türden iki nesneyi bir araya getirmek amacıyla kullanılır. pair nesnelerinin her kullanımda neleri temsil ettikleri baştan bilinemeyeceği için; birinci ve ikinci ögelerine İngilizce karşılıkları olan, sırasıyla 'first' ve 'second' adları verilmiştir.
Tanımladığı tür adlarını ve şablon kurucusunu çıkartırsak, tanımı aşağıdaki programdaki Cift yapı şablonu kadar basittir. Her 'pair' nesnesi kurulurken ögelerinin türlerini de açıkça belirtmek zorunda kalmayalım diye 'make_pair' adlı bir işlev de sunulmuştur.
İşlev şablonları şablon parametrelerinin türlerini işlev parametrelerinden çıkarsayabildikleri için, her seferinde öge türlerini belirtmemek için 'make_pair'i kullanabiliriz.
Bu küçük programda 'make_pair'in Türkçe tanımını da CiftYap adıyla veriyorum:
#include <iostream>
#include <string>
using namespace std;
/*
Bu yapi cok sade; yalnizca iki tane ogesi var
*/
template <class Tur1, class Tur2>
struct Cift
{
Tur1 birinci;
Tur2 ikinci;
Cift(Tur1 const & b, Tur2 const & i)
:
birinci(b),
ikinci(i)
{}
};
/*
Sablon parametrelerinin turlerini
her seferinde bildirmek zorunda kalmayalim
diye, boyle bir islev tanimliyoruz.
*/
template <class Tur1, class Tur2>
Cift<Tur1, Tur2> CiftYap(Tur1 const & birinci,
Tur2 const & ikinci)
{
return Cift<Tur1, Tur2>(birinci, ikinci);
}
// Bu islevin konumuzla bir ilgisi yok :)
template <class Gun>
void yazdir(Gun const & gun)
{
cout << "sayiyla: " << gun.birinci
<< " yaziyla: " << gun.ikinci << '\n';
}
int main()
{
string const pazartesi("pazartesi");
string const sali("sali");
// CiftYap islevini kullanmadan:
Cift<int, string> gun(1, pazartesi);
yazdir(gun);
/*
CiftYap islevi kolaylik sagliyor. Eger
olmasaydi, soyle yazmak zorunda kalirdik:
gun = Cift<int, string>(2, sali);
*/
gun = CiftYap(2, sali);
yazdir(gun);
}
pair'in map ile olan ilişkisi; anahtar/nesne çiftlerinin map'in içerisinde pair nesneleri olarak tutulmalarıdır. İçindeki nesnelere map erişicileri ile erişildiğinde, döndürülen pair'in 'first' ögesi anahtarı, 'second' ögesi de nesneyi temsil eder. Yani, bir map'teki nesnelere erişirken hem anahtar hem de nesne, kullanıma hazır olarak elimizin altındadır.
Bunun kullanımını göstermek için telefon rehberi programını rehberdeki bütün kayıtları yazdıracak şekilde değiştireceğim. Bunu yaparken şablon kullanımının getirdiği karmaşık yazımdan kurtulmak için typedef'ler de kullanacağım:
typedef map<string, TelefonNumarasi> Rehber;
typedef Rehber::value_type RehberKaydi;
map::value_type, zaten map'in bizim için kolaylık olsun diye sunduğu bir typedef'tir. Yukarıdaki satır yerine açıkça
typedef pair<string, TelefonNumarasi> RehberKaydi;
satırını da kullanabilirdim. Ancak bu durumda, ileride rehberin türlerinde bir değişiklik yapmak gerektiğinde bu satırı da değiştirmem gerekirdi. Bu olası değişiklik zahmetinden kurtardığı ve hata yapma riskini azalttığı için value_type yöntemini kullanmak daha iyidir.
Tür adlarını typedef'le tanımladıktan sonra artık tek bir rehber kaydını bir çıkış akımına gönderen işlevi yazabilirim:
/*
Tek bir kaydi yazdiran bir islev
*/
ostream & operator<< (ostream & cikis,
RehberKaydi const & kayit)
{
return cikis << kayit.first << ": " << kayit.second;
}
Şimdi yapılması gereken son şey, bütün kayıtları baştan sona çıkışa göndermektir. Bu işi, işlev nesnelerini anlattığım önceki bir yazımda da yaptığım gibi, for döngülerini açıkça yazmak yerine 'copy' algoritmasını kullanarak yapacağım.[5] Eğer main'in sonuna
copy(rehber.begin(),
rehber.end(),
ostream_iterator<RehberKaydi>(cout, "\n"));
ifadesini eklerseniz, rehberdeki kayıtların çıkışa şu şekilde yazdırıldıklarını görürsünüz:
ali: (1) 23456
kaya: (-) -----
veli: (5) 67890
Yukarıdaki çıkışta kayıtların alfabetik sırada a'dan z'ye doğru belirmeleri tesadüf değildir. Standart, map'in nesnelere logaritmik karmaşıklıkta erişim sağlamasını şart koşar. Bunu sağlamak için map, içindeki nesneleri bir ağaç yapısında tutar.
Bu ağaçtaki nesnelerin hangi sırada sıralanacaklarını map'e kendimiz bildirebiliriz. Bu sıralama yönteminin yukarıdaki programda olduğu gibi, özellikle belirtilmediği durumlarda; map sıralamayı anahtar olarak kullanılan türe operator< işlecini uygulayarak oluşturur. Yani, bizim örneğimizde kullanılan sıralama işleci, operator<(string const &, string const &)'tır.[6]
O işleç de string'lerin alfabetik sıradaki önceliğine göre sonuç bildirdiği için, nesneler alfabetik sırada sıralanırlar.
Sıralama algoritmasını map'e üçüncü şablon parametresi ile bildirebiliriz. Örneğin kayıtları alfabetik olarak ters sırada sıralamak için map'i şöyle kullanabiliriz:
typedef greater<string> AlfabetikTers;
typedef map<string, TelefonNumarasi, AlfabetikTers> Rehber;
Burada dikkat edilmesi gereken bir nokta, bu Rehber türünün önceki kullandığımız türle uyumsuz hale gelmiş olmasıdır. Bunun nedeni; sınıf şablonlarının türlerini, şablon parametrelerinin belirlemesidir. Sonuçta, örneğin programda
map<string, TelefonNumarasi>
türünün beklendiği bir yerde
map<string, TelefonNumarasi, AlfabetikTers>
türünü kullanamayız; ikisi ayrı türlerdir.
İçindeki ağaç yapısının tutarlılığının korunabilmesi için, map'in nesnelerinin birbirlerinden kesin olarak ayırt edilebilmeleri gerekir. Bu yüzden; anahtar olarak bir kullanıcı türü kullandığımızda; nesneler arasında, karışıklığa yol açmayacak olan bir sıra ilişkisi tanımlamamız gerekir.
Bu sıra ilişkisini, anahtar olarak kullanılacak türün operator< işleci ile belirleriz. Bunun nasıl yapıldığını göstermek için, daha önceki örneklerde kullandığım rehber sınıfının anahtar ve nesne türlerinin yerlerini değiştirmek istiyorum. Bu durumda, tersine çalışan bir rehber elde etmiş olacağız: verilen bir telefon numarasına karşılık gelen isme erişeceğiz.
Yazıyı daha fazla uzatmamak için bu yeni programın bu bölümle ilgili olmayan satırlarını ya hiç göstermeyeceğim, ya da /* ... */ açıklamaları olarak vereceğim:
class TelefonNumarasi
{
/* ... */
public:
/* ... */
/*
Bu sinifin bir map anahtari olabilmesi icin
gereken operator< islecini tanimliyoruz.
Burada yapmamiz gereken, uzerinde calistigimiz
nesnenin bize verilen 'diger' bir nesneden hangi
durumda daha kucuk (yani siralamada daha once
gelen) oldugunu belirlemektir.
Burada tek yapmamiz gereken, nesnenin kucuk
oldugu durumda 'true' dondurmektir.
*/
bool operator< (TelefonNumarasi const & diger) const
{
/*
Tutarli oldugu surece baska herhangi bir
siralama iliskisi de kurabiliriz. Ben, dogal
oldugu icin once alan koduna, sonra da
numaranin kendisine bakacagim. Tanim soyle:
Eger alan kodu diger nesnenin alan kodundan
kucukse, bu nesne daha kucuktur.
Eger alan kodlari esitse ama numara digerinin
numarasindan kucukse, bu nesne kucuktur.
Bunlarin disindaki durumlarda bu nesnenin diger
nesneden daha kucuk oldugunu soyleyemeyiz. O
yuzden o durumlarda, donus turumuz 'false'
olacaktir.
*/
return ((alanKodu_ < diger.alanKodu_)
||
((alanKodu_ == diger.alanKodu_)
&&
(numara_ < diger.numara_)));
}
};
/*
Bu tanimda anahtar olarak TelefonNumarasi kullaniliyor:
*/
typedef map<TelefonNumarasi, string> TersRehber;
typedef TersRehber::const_iterator KayitErisici;
/*
Yukaridaki sinif ve tanimlari deneyen bir 'main'
soyle yazilabilir:
*/
int main()
{
// Rastgele numaralar
TelefonNumarasi const numara1("1", "11111");
TelefonNumarasi const numara2("5", "55555");
TelefonNumarasi const numara3("2", "22222");
// Rehberi dolduruyoruz
TersRehber rehber;
rehber[numara1] = "ali";
rehber[numara2] = "veli";
rehber[numara3] = "deli";
// Numaralardan birisine ait olan kisiyi ariyoruz
TelefonNumarasi const & arananNumara = numara1;
KayitErisici const kayit = rehber.find(arananNumara);
if (kayit == rehber.end())
{
cout << "Bulamadim! Halbuki daha yeni eklemistim (?)";
}
else
{
cout << arananNumara << " numarali telefon '"
<< kayit->second << "' adli kisiye ait";
}
cout << '\n';
}
Aramayı yaparken map'in öge işlevi olan 'find'ı (std::map::find) kullandım. Onun yerine serbest işlev olan 'find' algoritmasını (std::find) da kullanabilirdim. st::map::find'ı yeğlememin nedeni, onun map'in iç ağaç yapısından haberli olması ve bu yüzden işini daha çabuk yapabilmesidir. std::find ise aramayı baştan sona sıralı olarak yapar.
multimap; map gibi çalışan, ama aynı anahtara sahip birden fazla nesneyi içinde barındırabilen bir topluluktur. Bu özelliği yüzünden, operator[] işlecini sunamaz: Çünkü o işlecin döndürebileceği birden fazla nesne bulunabilir.
multimap'in find işlevi, anahtara uyan ilk nesneye erişim sağlar. Aynı anahtara sahip sonraki nesnelere erişmek için erişiciyi kendimiz ilerletebiliriz.
Bunun dışında, multimap'in lower_bound ve upper_bound işlevlerini kullanarak bir anahtara uyan bütün nesnelerin topluluk içinde hangi aralıkta bulunduğunu da öğrenebiliriz. lower_bound ve upper_bound'un ikisinin birden işlerini yapabilen equal_range de aynı amaçla kullanılabilir.
multimap'in bir örneğini, özel bazı sayıların ne olduklarını bilen bir topluluk üzerinde göstermek istiyorum. Bu örnekte hem 'find', hem de 'equal_range' yöntemlerini kullanacağım.
#include <map>
#include <string>
#include <iostream>
#include <iterator>
using namespace std;
/*
multimap de map gibi kullanilir. Birinci
sablon parametresi anahtarin, ikincisi de
nesnelerin turunu belirler.
*/
typedef multimap<int, string> SayiSozlugu;
typedef SayiSozlugu::const_iterator KayitErisici;
typedef SayiSozlugu::value_type Kayit;
/*
Bilginin ilk halini bu ornekte bir yapida tutuyorum.
*/
struct SayiBilgisi
{
int sayi;
char const * anlam;
};
/*
Bilgi bu ornekte bu sabit diziden geliyor ama
herhangi bir kaynaktan da geliyor olabilirdi.
*/
SayiBilgisi const bilinenSayilar[] =
{
{ 7, "haftadaki gun sayisi" },
{ 1, "bir" },
{ 60, "dakikadaki saniye sayisi" },
{ 10, "eldeki parmak sayisi" },
{ 60, "saatteki dakika sayisi" },
{ 7, "guzel bir asal sayi" },
{ 42, "herseyin yaniti" },
{ 11, "on arti bir" },
};
#define TOPLAM_OGE(x) (sizeof(x) / sizeof(*(x)))
/*
Sozlugu doldurma isinde yardimci olmasi icin
islev nesnelerinin bir ornegini kullanacagim.[5]
Bu sinifin yaptigi sey, kurulurken kendisine
verilen SayiSozlugu'nu aklinda tutmasi ve
islev gibi kullanilirken kendisine verilen
her bir nesneyi o sozluge eklemesidir.
*/
class SozlugeKaydeden
{
SayiSozlugu & sozluk_;
public:
explicit SozlugeKaydeden(SayiSozlugu & sozluk)
:
sozluk_(sozluk)
{}
void operator() (SayiBilgisi const & bilgi) const
{
/*
Yazida anlatmadigim halde burada kullandigim
'insert' islevi, map ve multimap'e oge eklemek
icin kullanilir.
map ve multimap'in ogelerinin turleri 'pair'
oldugundan, burada 'make_pair' islevinden
yararlanabiliyorum.
*/
sozluk_.insert(make_pair(bilgi.sayi, bilgi.anlam));
}
};
SayiSozlugu sozlukKur()
{
SayiSozlugu sonuc;
/*
Bu yazinin konusundan ayriliyor olsam da,
asagidaki satirda sirasiyla neler oldugunu
soylemek istiyorum:
Once adsiz gecici bir SozlugeKaydeden nesnesi
olusturuluyor. Bu yapilirken, kendisine 'sonuc'
adli topluluk bildiriliyor.
for_each, bilinenSayilar icindeki her bilgiyi
SozlugeKaydeden::operator() islevine gonderiyor.
O islev de aldigi bilgiyi kendisine bildirilen
sozluge ekliyor.
*/
for_each(bilinenSayilar,
bilinenSayilar + TOPLAM_OGE(bilinenSayilar),
SozlugeKaydeden(sonuc));
return sonuc;
}
void kayitYazdir(Kayit const & kayit)
{
cout << kayit.first << " \""
<< kayit.second << "\" anlamina gelir\n";
}
void neBileyim(int sayi)
{
cout << "Hic " << sayi << " diye bir sayi duymadim\n";
}
void bilgiVer_find_ile(SayiSozlugu const & sozluk, int sayi)
{
KayitErisici erisici = sozluk.find(sayi);
if (erisici == sozluk.end())
{
neBileyim(sayi);
}
else
{
/*
find islevini multimap ile kullanarak anahtara
uyan nesnelerden ilkine erisebiliriz.
Bu durumda, nesnelerin hep sirali olarak
tutulduklarini bildigimiz icin, anahtar ayni
oldugu surece erisiciyi ilerleterek butun
kayitlari gorebiliriz.
Bunu yaparken 'erisici'nin sozlugun sonunda
olmadigini oncelikle denetlememiz gerekir.
*/
for ( ; (erisici != sozluk.end())
&&
(erisici->first == sayi); ++erisici)
{
kayitYazdir(*erisici);
}
}
}
void bilgiVer_aralik_ile(SayiSozlugu const & sozluk, int sayi)
{
/*
equal_range, aradigimiz anahtara uyan nesnelerin
basini ve sonunu bir 'pair' icindeki iki erisici
halinde dondurur.
Bu donus degerinin 'first' ogesi araligin basini,
'second' ogesi de araligin sonunu belirler.
equal_range'in dondurdugu bu bir cift erisiciyi
ayri ayri 'lower_bound' ve 'upper_bound' islevlerini
cagirarak da edinebiliriz. Onlari bu yazida
anlatmaya gerek gormuyorum.
*/
pair<KayitErisici, KayitErisici> const aralik
= sozluk.equal_range(sayi);
if (aralik.first == sozluk.end())
{
neBileyim(sayi);
}
else
{
for (KayitErisici erisici = aralik.first;
erisici != aralik.second;
++erisici)
{
kayitYazdir(*erisici);
}
}
}
void bilgiVer(SayiSozlugu const & sozluk, int sayi)
{
cout << "\n--- find ile ---\n";
bilgiVer_find_ile(sozluk, sayi);
cout << "\n--- equal_range ile ---\n";
bilgiVer_aralik_ile(sozluk, sayi);
}
int main()
{
SayiSozlugu const sozluk(sozlukKur());
/*
Simdi 7 sayisinin ne anlama geldigini bu sozlukte
bulmaya calisacagim.
*/
bilgiVer(sozluk, 7);
/*
Bir tane de bilinmeyen sayi bulmaya calisalim:
*/
bilgiVer(sozluk, 2003);
}
Bu topluluklardan nesne çıkartmanın bir yolu, map::erase işlevini çıkartılacak nesnenin anahtarıyla çağırmaktır. Örneğin:
rehber.erase("kaya");
Standart topluluklardan nesne çıkartırken, bazı erişicilerin geçersiz hale gelebileceklerini akılda tutmak gerekir. Her standart topluluk, hangi işlemlerin hangi erişicileri hangi koşullarda geçersiz hale getirdiğini belgelemek zorundadır.
Örneğin; bir vector topluluğunun sonuna nesne eklemek, vector'ün bütün içeriğinin başka bir alana kopyalanmasını gerektirebildiği için, o vector içindeki nesneleri gösteren her bir erişici geçersiz hale gelebilir. Öte yandan, vector'ün ortasından bir nesne çıkartmak, yalnızca onu ve sonraki nesneleri gösteren erişicileri geçersiz hale getirir; ondan önceki nesneleri gösteren erişiciler etkilenmezler.
map ve multimap'ten nesne çıkartmak, o nesneyi gösteren erişiciyi geçersiz hale getirir. Diğer nesneleri gösteren erişiciler bu işlemden etkilenmezler.
Nesne çıkartmanın başka bir yolu da; bir nesne çıkartmak için o nesneyi gösteren tek bir erişici, birden fazla erişici çıkartmak için ise o nesneleri belirleyen bir çift erişici kullanmaktır.
Bunu gösteren bir program da şöyle yazılabilir:
#include <map>
#include <iostream>
#include <string>
#include <iterator>
using namespace std;
typedef multimap<string, string> Sozluk;
typedef Sozluk::value_type Kayit;
/*
Yine bu yazinin konusundan ayriliyorum. Ancak,
bir 'Kayit'i cikisa gonderen islevi neden
'std' ad alani icerisinde tanimlamak zorunda
kaldigimi anlatmak gerektigine inaniyorum.
Fazla ayrintiya girmeden sunlari soyleyebilirim:
Hemen asagidaki operator<< islecinin
cikarsanmasinda kullanilan butun parametreler
std ad alani icerisinde tanimlanmislardir:
Kayit'in taniminda kullanilan multimap ve string
turlerinin her ikisi de std ad alani icindedirler.
O yuzden, asagida kullanilan copy algoritmasi
kendi icerisinde dolayli yoldan
cout << nesne;
yaptiginda, uygun bir operator<< isleci
ancak std ad alani icinde aranir.
Derleme zamaninda yapilan bu aramanin basariya
ulasmasi icin, bu operator<< islecini std ad
alani icinde tanimlamak gerekir.
Normalde kendi turlerimizle calisirken boyle
bir ayrintiyla ugrasmak zorunda kalmayiz. Cunku
o zaman zaten operator<< islecini normal olarak
kendi turumuzu tanimladigimiz ad alani icinde
tanimlariz.
Yani, ben burada 'namespace std' diyorum ama
cogu durumda boyle bir seye gerek olmaz. :)
*/
namespace std
{
ostream & operator<< (ostream & cikis,
Kayit const & kayit)
{
return cikis << kayit.first << ": " << kayit.second;
}
}
void altiCiziliBaslik(string const & baslik)
{
cout << '\n' << baslik << '\n';
/*
fill_n, bir nesneyi bir hedefe belirli bir
sayida kopyalamak icin kullanilir. Burada,
'-' karakterini baslik.size() kere cout'a
gonderiyoruz. Her bir kopyanin arasina ise
"", yani hicbir sey koyuyoruz. :)
*/
fill_n(ostream_iterator<char>(cout, ""),
baslik.size(), '-');
cout << '\n';
}
template <class Topluluk>
void cikisaGonder(string const & baslik,
Topluluk const & topluluk)
{
typedef typename Topluluk::value_type NesneTuru;
altiCiziliBaslik(baslik);
copy(topluluk.begin(),
topluluk.end(),
ostream_iterator<NesneTuru>(cout, "\n"));
}
void kayitEkle(Sozluk & sozluk,
string const & sozcuk,
string const & karsilik)
{
sozluk.insert(make_pair(sozcuk, karsilik));
}
void doldur(Sozluk & sozluk)
{
kayitEkle(sozluk, "bin", "climb on");
kayitEkle(sozluk, "ac", "open");
kayitEkle(sozluk, "masa", "table");
kayitEkle(sozluk, "at", "throw");
kayitEkle(sozluk, "ac", "hungry");
kayitEkle(sozluk, "at", "horse");
kayitEkle(sozluk, "kapi", "door");
kayitEkle(sozluk, "bin", "thousand");
}
int main()
{
Sozluk sozluk;
doldur(sozluk);
cikisaGonder("Baslangicta", sozluk);
sozluk.erase("kapi");
cikisaGonder("'kapi' ciktiktan sonra", sozluk);
sozluk.erase(sozluk.lower_bound("at"),
sozluk.upper_bound("bin"));
cikisaGonder("'at'in basindan 'bin'in sonuna kadar"
" ciktiktan sonra", sozluk);
}
Bu yazıda, çok yararlı bulduğum map ve multimap topluluklarının kullanımlarını göstermeye çalıştım. Bunu yaparken bazı standart yapı ve algoritmaları da tanıttım.
[1] Türden bağımsız bir toplama algoritması: http://ceviz.net/index.php?case=article&id=170
[2] C++'ın türden bağımsız toplulukları (generic containers): http://ceviz.net/index.php?case=article&id=184
[3] std::vector temel işlemleri: http://ceviz.net/index.php?case=article&id=202
[4] std::vector ileri düzey işlemleri: http://ceviz.net/index.php?case=article&id=203
[5] İşlev Nesneleri ('Function Objects' veya 'Functors'): http://ceviz.net/index.php?case=article&id=215
[6] Aslında sıralama için kullanılan işlev, less<string>'dir. operator< işleci doğrudan kullanılmaz; onu less<string> çağırır.