
Här följer del 2 av artikeln om STL tree containers. I del 1 diskuterade jag gemensamma aspekter hos tree containers, samt användning av std::set
. I denna del 2, går jag igenom hur du använder std::map
.
Internt är map tämligen likt set, med en avgörande skillnad: eftersom map parar ihop en nyckel med ett värde, så innehåller varje tree-node ett objekt av typen std::pair<Key, Val>
. Det här är viktigt att tänka på vid iteration eller när man tankar över element från en map till en annan container.
#include <print>
#include <map>
#include <vector>
#include <string>
using namespace std::string_literals;
using std::string;
int main() {
auto names = std::map<string, int>{
{"Anna"s, 10}, {"Berit"s, 20}, {"Carin"s, 30}
};
std::print("explicit : [");
for (std::pair<string const, int>& p : names) std::print("({}: {}) ", p.first, p.second);
std::println("]");
std::print("implicit : [");
for (auto const& p : names) std::print("({}: {}) ", p.first, p.second);
std::println("]");
std::print("simple : [");
for (auto const& [name, count] : names) std::print("({}: {}) ", name, count);
std::println("]");
std::println("convenient: {}", names);
auto v = std::vector< std::pair<string const, int> >{names.begin(), names.end()};
std::println("vector : {}", v);
}
explicit : [(Anna: 10) (Berit: 20) (Carin: 30) ]
implicit : [(Anna: 10) (Berit: 20) (Carin: 30) ]
simple : [(Anna: 10) (Berit: 20) (Carin: 30) ]
convenient: {"Anna": 10, "Berit": 20, "Carin": 30}
vector : [("Anna", 10), ("Berit", 20), ("Carin", 30)]
Åtkomst av element
Precis som för std::set, så har std::map metoderna contains()
, count()
och find()
, med likartad funktion.
auto names = std::map<string, int>{
{"Anna"s, 10}, {"Berit"s, 20}, {"Carin"s, 30}
};
std::println("-- using contains() --");
if (names.contains("Carin")) std::println("snames contains 'Carin'");
if (not names.contains("Nisse")) std::println("names contains not 'Nisse'");
std::println("-- using count() (pre c++20) --");
if (names.count("Anna") > 0) std::println("snames contains 'Anna'");
if (names.count("Frida") == 0) std::println("names contains not 'Frida'");
std::println("-- using find() --");
if (auto it = names.find("Berit"s); it != names.end())
std::println("found: {}", *it);
if (auto it = names.find("Kurt"s); it == names.end())
std::println("not found: {}", "Kurt"s);
-- using contains() --
snames contains 'Carin'
names contains not 'Nisse'
-- using count() (pre c++20) --
snames contains 'Anna'
names contains not 'Frida'
-- using find() --
found: ("Berit", 20)
not found: Kurt
Emellertid, är det index operatorn som är mest användbar. Den returnerar referensen till värdet för en viss nyckel, eller om nyckel saknas så skapas ett nytt par där värdet initieras med typens default-constructor.
Alternativt, så finns metoden at()
som också returner en referens till värdet, men till skillnad mot index operatorn, så kastas en exception om nyckeln inte finns i containern. Här kommer ett belysande kodexempel.
auto names = std::map<string, int>{
{"Anna"s, 10}, {"Berit"s, 20}, {"Carin"s, 30}
};
std::println("-- using operator[] --");
names["Berit"s] = 50;
names["Carin"s] *= 5;
names["Anna"s]++;
auto _ = names["Doris"s];
std::println("names: {}", names);
std::println("-- using method at() --");
names.at("Anna"s) += 10;
try {
auto _ = names.at("Eva"s);
} catch (std::exception const& x) {
std::println("ERR: {}", x.what());
}
std::println("names: {}", names);
-- using operator[] --
names: {"Anna": 11, "Berit": 50, "Carin": 150, "Doris": 0}
-- using method at() --
ERR: map::at
names: {"Anna": 21, "Berit": 50, "Carin": 150, "Doris": 0}
Insättning av element
Precis som för set, så finns metoderna insert()
och emplace()
, om det inte det räcker med index operatorn. Först visar jag ett exempel med insert(). Notera att det du sätter in är ett object av typen std::pair
.
auto names = std::map<string, int>{};
names.insert( {"Berit"s, 20} );
names.insert( std::pair{"Carin"s, 30} );
names.insert( std::make_pair("Anna"s, 10) );
auto v = std::vector< std::pair<string const, int> >{
{"Frida"s, 60}, {"Doris"s, 40}, {"Eva"s, 50}
};
names.insert(v.begin(), v.end());
std::println("names: {}", names);
// names: {"Anna": 10, "Berit": 20, "Carin": 30, "Doris": 40, "Eva": 50, "Frida": 60}
Användning av metoden emplace() är emellertid av mer begränsat värde, såvida inte den behövs av strikt prestandamässiga skäl. Här visar jag två anrop. Det första tar nyckel plus ett färdigt objekt (Person), medan det andra tar konstruktor-argumenten via ett tämligen krångligt och pratsamt sätt. Första argumentet till emplace är en så kallad class tag, och signalerar till emplace att packa upp de följande två tuple argumenten och skapa objekten på plats inuti containern. Personligen tycker jag man klarar sig alldeles utmärkt med att bara använda index operator för både insättning och åtkomst.
auto persons = std::map<string, Person>{};
persons.emplace("anna"s, Person{"Anna Conda"s, 42, 165.F});
persons.emplace(
std::piecewise_construct,
std::forward_as_tuple("per"s),
std::forward_as_tuple("Per Silja"s, 52U, 175.F)
);
for (auto&& [key, p] : persons) std::println("{:4}: {}, {}, {}",
key, p.name, p.age, p.height);
// anna: Anna Conda, 42, 165
// per : Per Silja, 52, 175