环境搭建
- 实验说明:
- 下载源代码:
git clone https://github.com/cmu-db/bustub.git |
- 安装依赖
可以在本地安装,那么只需要执行 sudo ./build_support/packages.sh
即可(要求是 ubuntu
系统);同时我们也注意到,提供了 Dockerfile
文件,因此,我们可以直接使用 docker
来配置环境,执行下面几条命令,然后就能在 vscode
中使用了。
docker build . -t bustub |
进入容器(或者在本地安装完依赖)后,编译代码:
mkdir build |
- 项目说明
这个实验要求我们实现一个并发
trie
树,主要实现插入、删除和查找这三个算法。可以参考leetcode
的trie
树(前缀树)的题。数据在trie
树中以key-value
键值对形式存储,键是非空可变长度的字符串。例如,考虑在trie
中插入kv
对("ab", 1)
和("ac", "val")
,树的结构如下:
C++ 前置知识
这个 project 主要目的就是测试 C++ 水平,所以语言和代码贴的比较多。
explicit TrieNode(char key_char) : key_char_(key_char) {} |
explicit
这个关键字用来说明这个类的构造不支持隐式类型转换。举个例子吧:
class Person
{
public:
int age;
std::string name;
explicit Person(const std::string &name_) : name(name_) {}
Person(int age_) : age(age_) {}
};
Person a = 22; // 隐式构造,存在这样的构造函数
// Person b = std::string("ceyewan"); wrong,明确了不能使用隐式构造
Person b("ceyewan"); // 只能这样构造
std::cout << a.age << "\n" << b.name << std::endl;同样,这样初始化不仅代码风格简单,而且可以避免性能浪费,如果不这样的话,
“ceyewan“
(const char*
类型)会调用string
的构造函数得到name_
,然后再次调用string
的构造函数得到name
。
TrieNode(TrieNode &&other_trie_node) noexcept { |
noexcept
简单的理解成阻止抛出异常。这里,我们使用了右值引用,因为
children_
中存储的是unique_ptr
不能使用拷贝构造,只能使用移动构造。move
函数的作用就是将参数强制转换为右值,用于移动。
bool IsEndNode() const { return is_end_; } |
const
表示类中的这个函数不能修改类,可以理解为只读属性。我们可以重载函数,当const Entity
就会调用有const
的,普通的Entity
对象调用没用const
的。如果我们想在const
的方法中修改类的某个成员,需要把这个类成员标记为mutable
。优点在于安全,保证
const Entity
就一定是const
的。
std::unique_ptr<TrieNode> *InsertChildNode(char key_char, std::unique_ptr<TrieNode> &&child) { |
std::unique_ptr<TrieNode>
表示TrieNode
类型的智能独占指针,所以返回的时候只能返回指向这个指针的指针,如果返回它,那么指针本身就被销毁了。
children_[key_char] = std::move(child);
使用move
函数来进行移动构造,child
作为一个右值引用,执行完这个语句就消亡了。独占指针,只有一份!!!
TrieNodeWithValue(TrieNode &&trieNode, T value) : TrieNode(std::forward<TrieNode>(trieNode)) { |
TrieNode(std::forward<TrieNode>(trieNode))
是TrieNode
的移动构造函数,上面写了。
std::forward<type>()
和move
的差不多,但是有区别,简单来说,遇到右值就转发右值并启动移动语义,遇到左值就转发左值启动复制语义。
Insert 方法
bool Insert(const std::string &key, T value) { |
遍历 key
寻找路径,如果缺少了就插入就可。到了最后一个节点,如果是 EndNode
说明重复插入了,直接返回。否则,删除原来的 node
,插入新的 node
,插入的 node
实际上是 TrieNodeWithValue<T>
类型。
Remove 方法
bool RemoveHelper(std::unique_ptr<TrieNode> *curr, size_t i, const std::string &key, bool *success) { |
按照意思就是我们要递归的删除节点,既然需要递归,我们就要一个 helper
函数,当然了,直接写个栈充当函数栈也行。success
用来说明删除是否成功,而 remove_helper
函数的返回值用来告诉 parent
这个 child
是否可以 remove
。
GetValue 方法
template <typename T> |