CS106L HashMap 笔记
1|0Hash Map
1|1Milestone 1: Const-Correctness
首先打开main.cpp
,找到student_main()
函数,这个函数是用来测试里程碑 1 的。首先这个函数本身应该是没有 bug
的但是这个函数调用函数的参数应该是const
类型。
首先需要修改这些函数的定义。我修改了
这样的话,函数就无法正常编译,因为有些HashMap
有些成员函数也应该是const
所以要去hashmap.cpp
和hashmap.h
中重载一些函数。 我们逐个错误去去解决。
首先第一个错误是map.end()
报错,这里应该是返回一个常量的。找到函数的声明是
应当重载为
为什么不能重载为const_iterator end()
?因为C++不支持仅基于返回值类型重载。
考虑对于函数的重载,真的需要完整的实现?其实并不是,因为我们可以使用static_cast
和 const_cast
实现代码的复用。
先说static_cast
函数,本质上是显示类型转换,有些类似C语言的强制类型转换。主要的差别在运算过从中进行类型检查,以避免隐式类型转换和其他不安全的类型转换。
这两句话在这里的作用是一样的。
再说const_cast
的作用是解除对const
关键字的限制。如果你需要在某些情况下修改一个 const
变量的值,但编译器不允许直接修改,可以使用
const_cast
。换句话来说就是将 const T*
转换为 T*
,或 const T&
转换为 T&
。
好,接下来说一下我们重载的思路。我们首先调用iterator end()
函数,并对返回值做类型转换。
我们通过auto ptr = this
, 结合 CLion 的提示就可以知道this
的类型是const HashMap<K, M, H> *
, 这也就意味着如果
this -> end()
调用的函数只能是const_iterator end() const
,也就变成了递归调用。如何解决这个问题?我们可以
const_cast<HashMap<K, M, H> *>(this)
去掉const
关键字的限制,这样就可以正常调用iterator end()
所以最终这样就好了。
下一个需要重载的函数就是
重载为
函数实现就是
下一个需要重载的函数是
重载为
函数实现为
1|2Milestone 2: Special Member Functions and Move Semantics
去掉注释后,我发现原本可以正常编译的代码无法编译了。
这是因为我们没有一些成员函数定义为const
,我把size(),empty(),load_factor(),bucket_count(),contains()
的定义修改后就可以正常运行。
我们需要实现以下四个函数
拷贝构造函数
函数定义
函数实现
HashMap
有三个变量,分别是_size
内部元素个数,_hash_function
哈希函数,_buckets_array
桶数组。哈希函数不变很好理解。对于元素的值,我们不能直接拷贝桶数组,这样会直接指向原始的对象,并不会拷贝对象。因此我可以新建全部为nullptr
的桶数组,并把个数设为零,然后逐个把元素插入到新的HashMap
就好。
拷贝赋值函数
函数定义
函数实现
首先我们要判断左值和右值是否是一个对象,如果不是一个对象才需要进行赋值。
如果要赋值,则首先应该把左值都清空。然后应当把左值的哈希函数赋值为右值的哈希函数。
要注意此时左值和右值的桶数组大小可能是不同的,我们要注意把桶的大小对应上。
最后把右值的值逐个插入即可。
移动构造函数
函数定义
因为全部都是对象,所以其实我们直接初始话列表加std::move
就能解决
移动赋值函数
函数定义
函数实现
整体思路与移动构造函数很接近
1|3源码阅读
主要是阅读hashmap.h
和hashmap.cpp
。
首先HashMap
类用到了三种类型K,M,H
分别表示键、值、哈希函数。并且把pair<const K, M>
定义为value_type
。
成员变量
这个哈希表是拉链法实现的,所以定义一个这样的类
用来记录每个节点的值,和节点指向的下一个点。
然后定义了三个成员变量,_size, _hash_function,_buckets_array
。分别表示哈希表类元素的个数,哈希函数,头指针。
HashMap()
除了默认构造函数外,他实现了一个
这个函数指定了初始桶的大小,并且让桶内全部是nullptr
~HashMap()
只采用默认的析构函数。
size()
返回哈希表的大小。就是返回_size
empty()
判断哈希表是否为空,返回size() == 0
bucket_count()
桶的大小,返回_buckets_array.size()
find_node(const K &key)
首先定义了一个using node_pair = pair<node*,node*>
用来储存一堆点的指针。
这个函数是给定一个key
,找到这个key
对应的键值对n
,并返回n
的前驱和n
两个点的指针。如果找不到返回<nullprt,nullptr>
。
为什么还需要前驱?因为当指向删除操作时,需要把n
前驱的后继指向n
的后继。
如何实现?本质时先找到key
对应的index
,再遍历桶中index
的链。
contains(const K &key)
判断键是否存在于hashmap
中,返回find_node(key).second != nullptr
at(const K &key)
找到key
对应的值。
重载了两个版本
实现方法,用find_node
查找对应的键值对。
clear()
清空hashmap,遍历每一条链,然后不断的把链头指向后继,直到链头为nullptr
。
make_iterator(node *curr);
根据指针找到对应的迭代器。
这里讲一下迭代器如何实现的。
其实很简单,迭代器主要用三个成员变量
然后每次可以根据node->next
找到下一个点,如果下一个点是nullptr
说明当前的链变量完了,要遍历下一个链,如果所有的链都遍历完了,则hashmap遍历完了。
因此这个迭代器实际上只是Forward Iterator
。
所以我们只要根据值找到对应的桶数组和下标再遍历一下链找到key
就好了。
find(const K &key)
找到key
对应的键值对,并返回指向键值对的迭代器。重载了两个版本。
实现方法,用find_node
找到点,然后用make_iterator
返回迭代器。
insert(const value_type &value)
插入值,根据value.first
计算出哈希值,然后找到对应的桶数组把这个值插到末尾即可。最后++_size
。
erase(const K &key)
用find_node
找到点,然后把前驱后继接起来。
erase(const_iterator pos)
因为要返回删除后点,所有我们可以先向后走一步,再删除前驱节点。
begin()
遍历桶数组,找到第一个非空的链,返回链头的迭代器。
end()
返回nullptr
的迭代器。
__EOF__

本文链接:https://www.cnblogs.com/PHarr/p/18731344.html
关于博主:前OIer,SMUer
版权声明:CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2024-02-22 Codeforces Round 923 (Div. 3)