自定义结构体,并作为map/set/unordered_map/unordered_set的key值
一、代码背景:
- 为了更好的开发空间图形方面的程序,因此需要自定义个图形化常用的单位——坐标点。传统的方位点一般通过二维或者三维数组来展示和计算,这让一些缺乏空间维度思维的程序员来说,加重了对内部数据过多关注而带来的开销。因此我们通过定义点对象,来封装空间的数据,这样,程序员能更直观的理解和感受空间维度和各种几何计算。
- 因此,该篇文章抛砖引玉,先搭基建,然后在通过这个基建,来扩展空间几何的应用开发。
二、定义Point结构体:
- 首先需要一个点的结构体或者类(该篇暂定结构体);而结构体中需要有一下基本函数和变量;
// point.h
#include <string>
using namespace std;
struct Point
{
// 默认构造函数
Point() : x(0), y(0), z(0){}
Point(double x, double y, double z) : x(x), y(y), z(z) { is_valid = true; } // 此处设置为了更好的判断点的有效性
// 拷贝构造函数
Point(const Point& other)
{
x = other.x;
y = other.y;
z = other.z;
is_valid = other.is_valid;
}
// 赋值函数
Point&& operator= (const Point& other)
{
Point pt;
pt.x = other.x;
pt.y = other.y;
pt.z = other.z;
is_valid = other.is_valid;
return move(pt);
}
// 重载操作符+
Point&& operator+(const Point& other)
{
Point pt;
pt.x = this->x + other.x;
pt.y = this->y + other.y;
pt.z = this->z + other.z;
return move(pt);
}
// 重载操作符-
Point&& operator-(const Point& other)
{
Point pt;
pt.x = this->x - other.x;
pt.y = this->y - other.y;
pt.z = this->z - other.z;
return move(pt);
}
// 重载操作符< 用于后续的比较
bool operator<(const Point& other)
{
if (x == other.x)
{
if (y == other.y) return z < other.z;
else return y < other.y;
}
else return x < other.x;
}
// 此处为了后续更好的打印展示出来,因此将坐标点转化为字符串
string GetPointByStr()
{
return ("(" + to_string(x) + ", " + to_string(y) + ", " + to_string(z) + ")");
}
double x;
double y;
double z;
bool is_valid = false;
};
三、使用unordered_map进行装载Point和每个点所对应的信息,如该点有多少平方(double),有几棵树(int),是什么位置(string)……
此处暂时表示一个点所占区域大小,即<Point, double>
// main.cpp
#include <unordered_map>
#include <iostream>
#include <string>
#include "point.h"
using namespace std;
int main()
{
Point a = Point(1, 2, 3);
Point b = Point(2, 32, 13);
Point c = Point(-3, 43, 22);
// 校验重载操作符< 的实现情况
if (a < b)
{
cout << "a < b" << endl;
}
else
{
cout << "a > b" << endl;
}
unordered_map<Point, double> map;
map.insert(pair<Point, double>(a, 12.34));
map.emplace(pair<Point, double>(b, 9.01));
map.emplace(pair<Point, double>(c, 1.94));
for (auto iter : map)
{
Point pt = iter.first;
cout << "The loacl : " << pt.GetPointByStr() << ", and its area has " << iter.second << "平方米." << endl;
}
}
四、结果
显而易见的失败,而且是生成时的问题。当然这句话的意思大概就是,你的hash_compare函数不可用,更直观的意思就是,你这个key值是你自定义的对象,c++函数库找不到该对象的hash_function,因此需要你自己去给你自定义的对象,构造一个hash_function。
因此知道问题所在的我们,又感觉行了,直接网上找一堆方案就开干;
the first one:
既然没有这个function,那么我们给它造个
// 定义Point的hash表, 并且把他所有坐标拼接在一起作为他的hash串
static size_t PointHash(const Point& self) noexcept
{
static std::hash<std::string> hash_str;
return hash_str(to_string(self.x) + to_string(self.y) + to_string(self.z));
}
// 并且在Point中增加 一些compare有关的函数
stuct Point
{
...... // 其他函数与上面一致
// 注意 这个const 必须要加入,不然会导致operator()实现不了
// 养成好习惯,不改变数据的函数体 后面加个const来确保不会对数据更改
bool operator==(const Point& other) const
{
return (x == other.x) && (y == other.y) && (z == other.z);
}
bool operator()(const Point& a, const Point& b) const
{
return a == b;
}
...... // 变量与上面一致
}
然后主函数仅修改unordered_map的声明;
....... // 头文件信息与上 一致
int main()
{
Point a = Point(1, 2, 3);
Point b = Point(2, 32, 13);
Point c = Point(-3, 43, 22);
if (a < b)
{
cout << "a < b" << endl;
}
else
{
cout << "a > b" << endl;
}
unordered_map<Point, double, decltype(&PointHash)> map;
map.emplace(pair<Point, double>(a, 12.34));
map.emplace(pair<Point, double>(b, 9.01));
map.emplace(pair<Point, double>(c, 1.94));
for (const auto& iter : map)
{
Point pt = iter.first;
cout << "The loacl : " << pt.GetPointByStr() << ", and its area has " << iter.second << "平方米." << endl;
}
}
好,完事具备,直接跑起来:
看起来生成没问题,然而结果又又又有问题了,请看VCR:
显然,这里表示非法占用空间了, 0X00000这块空间,显然是我们不能用的,因此表明我们在初始化时有对象没有申请空间,那我们就会想了,我们又没有用到指针,就只是一个结构体,按理来说系统会自动申请和分配的,那到底哪里出问题了呢?
点开下面的debug数据,看到map中hash_function居然是 0X00000,。。。垂死病中惊坐起,小丑竟是我自己;显然这个hash_function没有分配空间,也就是未初始化。
然后找到问题所在,那我们重新找个方案——将我们的hash_function封装在一个类中
class PointHasher {//hash函数,得到hash码
public:
size_t operator()(const Point& pt)const {
return hash<string>()(to_string(pt.x) + to_string(pt.y) + to_string(pt.z));
}
};
主函数:
int main()
{
Point a = Point(1, 2, 3);
Point b = Point(2, 32, 13);
Point c = Point(-3, 43, 22);
//unordered_map<Point, double, decltype(&PointHash)> map;
unordered_map<Point, double, PointHasher> map;
map.emplace(pair<Point, double>(a, 12.34));
map.emplace(pair<Point, double>(b, 9.01));
map.emplace(pair<Point, double>(c, 1.94));
for (const auto& iter : map)
{
Point pt = iter.first;
cout << "The loacl : " << pt.GetPointByStr() << ", and its area has " << iter.second << "平方米." << endl;
}
}
最终,执行成功,我们可以看到,map的数据就变成正常的了
控制台:
五、总结
- 单独自定义一个对象或者结构体很简单,但是实现它的强扩展性,还需要更进一步的研习c++标准库源码,重载各种各样的函数和组件。
- 遗留一些问题,后续更深入了解后,再做补充——如
- 1、为什么声明一个静态hash_function,装载到unordered_map中,会造成未初始化的问题,而封装到一个类中就没有这样的问题呢?
- 2、是类和静态函数的底层问题导致,还是hash底层问题导致的?
- 也欢迎各位大佬指点12。
TOBECONTINUE。。。