金三银四C++面试考点之哈希表(std::unordered_map)

772 阅读3分钟

一、常用函数及其用法

1.1 初始化

使用大括号进行初始化,也可以不进行初始化

std::unordered_map<std::string, int> phonebook = {{"John", 272727}, {"Kate", 323232}, {"Mike", 181818}};

1.2 insert 插入

通过 insert 函数向哈希表中插入三个键值对

 // 插入元素
phonebook.insert(std::make_pair("Alice", 123456));
phonebook.insert(std::make_pair("Bob", 987654));
phonebook.insert(std::make_pair("Charlie", 555555));

1.3 []访问元素值

通过[]运算符查找了"Alice"对应的值

// 查找元素
std::cout << "Alice's number is " << phonebook["Alice"] << std::endl;

1.4 erase删除

使用erase函数删除了"Bob"对应的键值对

// 删除元素
phonebook.erase("Bob");

1.5 .first.second 访问键值对

for (auto ... : ...) 遍历所有元素,以及使用 .first.second 对元素的键值对访问

for (auto const& entry: phonebook) {
        std::cout << entry.first << ": " << entry.second << std::endl;
    }

1.6 find 查找

通过 find 查找是否含有特定键值的元素。如果要查找的键不存在,则返回end()迭代器,可以用它和 hashtable 的 end() 函数比较来判断是否查找成功。

// 查找是否含有特定键值的元素
auto iter = phonebook.find("John"); 
if (iter != phonebook.end()) 
{ 
    std::cout << "The value of key John is " << iter->second << std::endl; 
} 
else 
{ 
    std::cout << "Key John not found" << std::endl; 
}

1.7 emplace 插入

emplace:用于在hashtable中插入一个新元素;

// 在phonebook中添加一个新元素
phonebook.emplace("Tom", "123456");

1.8 size 元素数量

size:用于获取hashtable中元素的数量;

// 获取phonebook中元素的数量
size_t count = phonebook.size();

1.9 empty 空判断

empty:用于判断hashtable是否为空;

// 判断phonebook是否为空
bool is_empty = phonebook.empty();

1.10 clear清空

clear:用于清空hashtable中所有的元素;

// 清空phonebook中所有的元素
phonebook.clear();

1.11 beginend 遍历

beginend:用于获取hashtable的迭代器,这里的begin和end函数返回的是迭代器,用于遍历容器中的元素

// 使用迭代器遍历phonebook中的所有元素
for (auto it = phonebook.begin(); it != phonebook.end(); ++it) 
{
    std::cout << it->first << ": " << it->second << std::endl;
}

二、区分

2.1 emplaceinsert 区别

emplaceinsert 都是用于向容器中插入元素的函数,但它们的行为有所不同。

insertemplace
接受一个元素并将其副本插入容器中函数通过使用参数包和完美转发的方式,构造一个元素并插入到 std::unordered_map 容器中
需要提供要插入的元素的副本需要提供要构造的元素的构造函数参数

使用 emplace 函数的优势在于可以避免额外的拷贝或移动操作,提高了插入元素的效率。

为了更好的理解我们看一下 emplace 函数的实现,以unordered_map为例:

template<class... _Valty>
std::pair<iterator, bool> emplace(_Valty&&... _Val)
{
    return _Insert_emplace(value_type(_STD forward<_Valty>(_Val)...));
}

template<class... _Valty>可变参数模板的定义,它使用了模板参数包(template parameter pack)的语法。在C++11之前,我们只能通过函数重载或者宏定义等方式来实现可变参数函数的编写。而C++11中引入了可变参数模板的概念,可以通过这种方式更加优雅地编写可变参数的函数或类模板。_Valty是模板参数包,表示可以有任意数量的类型参数。在模板的使用中,可以通过逗号分隔多个类型参数进行使用。其中,...表示参数包展开的语法,将参数包中的参数进行逐一展开。在函数或类模板的实现中,可以使用类似递归的方式将参数包中的每个参数进行处理。更多细节可以阅读C++11特性之可变参数模板这篇文章。

函数体内部调用了 _Insert_emplace函数,该函数返回一个 std::pair类型的值,其中第一个值是插入的位置,第二个值表示插入是否成功。

_Insert_emplace 函数实现了将参数包中的值插入到 std::set 容器中,并且返回插入结果的功能。其实现原理和 insert 函数比较类似,只不过 emplace 函数的参数是可变的,可以传递任意数量的参数,而 insert 函数的参数是固定的。

在函数的实现中,使用了 value_type 函数来获取容器中元素的类型,并且使用了 std::forward 函数将参数列表中的参数传递给 value_type 函数构造一个容器元素的实例。最后将该实例插入到 std::set 容器中,返回插入结果。

总的来说,这个函数是一个模板函数,可以接收任意数量和类型的参数,并且将这些参数插入到 std::set 容器中。该函数的使用方式和 insert 函数类似,但是由于可以传递任意数量的参数,因此在某些情况下可以更加方便。

例子

假设有一个自定义的结构体Person

struct Person
{
    std::string name;
    int age;
    Person(const std::string& name, int age) : name(name), age(age) {}
};

现在有一个unordered_map,以std::string为键,以Person为值:

std::unordered_map<std::string, Person> phonebook;

使用emplace函数往phonebook中添加一个元素,需要提供Person的构造函数参数:

phonebook.emplace("Alice", "Alice", 30);

或者写成

phonebook.emplace("Alice", Person("Alice", 30));

而使用insert函数往phonebook中添加一个元素,则需要提供Person的副本:

Person p("Bob", 40);
phonebook.insert({"Bob", p});

可以看到,使用emplace函数可以直接传递构造函数的参数,避免了创建临时对象和拷贝的开销,而使用insert函数则需要先创建一个副本再插入。

另外,emplace 函数返回一个指向新插入元素的迭代器,而 insert 函数返回一个表示插入操作是否成功的std::pair对象。