【问题标题】:Store struct in a map; Checking if a struct exist inside a map将结构存储在地图中;检查结构是否存在于地图中
【发布时间】:2012-05-05 19:02:11
【问题描述】:

Q#1) 下面的结构不想被复制并给出编译错误 - 为什么以及如何处理它?

#include <iostream>
#include <string>
#include <map>

using namespace std;

struct person
{
    person(string n)
        :name(n)
    {}

    string name;
};

int main()
{
    map<string, person> my_map;

    my_map["one"] = person("Tom");

    return 0;
}

Q#2) 我们可以通过省略结构构造函数“person(const string& n)”并一一分配结构值来避免上述问题:

#include <iostream>
#include <string>
#include <map>

using namespace std;

struct person
{
    string name;
};

int main()
{
    map<string, person> my_map;

    person p;
    p.name = "Tom";
    my_map["one"] = p;

    return 0;
}

所以,假设我这样做,在地图中存储了很多人之后,我想检查地图中是否存在特定的人。据我所知,正确的做法是:

if(my_map.find("one") == my_map.end()) { //it doesn't exist in my_map }  
else {//it exists}

但据我了解,这将逐个遍历整个地图,不是吗?如果是,那么可以这样做吗:

using namespace std;

struct person
{
    string name;
    string identifier; // <--
};

int main()
{
    map<string, person> my_map;

    person p;
    p.name = "Tom";
    p.identifier = "something"; // <--
    my_map["one"] = p;

    if(my_map["unknown"].identifier == "something") // <--
        cout << "Found" << endl;
    else 
        cout << "Not found" << endl;

    return 0;
}

通过这样做,我们避免了迭代,并且内存中的垃圾与我们的标识符匹配的可能性是......我猜是很小的,特别是如果我们使用一些哈希。 那么这样做可以(安全)吗?

【问题讨论】:

  • 搜索具有给定键的元素不会遍历整个地图,请参阅下面的答案。

标签: c++ search map constructor struct


【解决方案1】:

首先,既然你有构造函数,你需要提供一个默认构造函数。这是因为 C++ 标准库容器使用值语义。所以地图需要能够复制值、分配它们并默认构造它们。由于您提供了构造函数,因此编译器不会合成默认构造函数。这是一个什么都不做的默认构造函数:

person() {} // default constructs string, so no special aciton required.

特别是在std::map 的情况下,operator[] 在映射中不存在具有键的元素时返回对默认构造值的引用:

my_map["one"] = p; // creates *default* person, then *assigns* it the value of p.

其次,关于您关于搜索地图的问题,std::map,搜索具有对数复杂性,通常实现为自平衡二叉树。因此,当您搜索时,您不会遍历整个地图。而且由于通过operator[] 访问会在搜索到的键不存在时引入新元素,因此使用find() 的表单是执行此操作的规范方式。

既然你提到了散列,C++11 提供了std::unordered_map,而tr1boosthash_map。这些使用散列函数执行搜索是常数时间。是否值得使用取决于地图大小等因素。常数时间可能大于搜索小地图所用的对数时间。

注意: 如果您想使用您的结构作为键,或者想将其插入到标准库sets 之一,您还有更多要求:

ma​​ps:您需要为键提供strict weak ordering,通过类的小于运算符或自定义比较器仿函数。如果您要使用person 作为密钥,您将需要类似:

bool operator<(const person& rhs) const { return name < rhs.name; }

unordered_ 或散列映射:您必须通过 operator== 或仿函数为键提供 hash 函数和相等比较。 .

【讨论】:

  • 不,默认的复制和赋值运算符在这里可以正常工作。只需一个默认构造函数就足够了。
  • @juanchopanza 在这种情况下,默认构造函数是什么?我的意思是默认构造函数到底应该是什么样子?
  • 使用operator[] 可以在地图中插入新条目,如果只是为了检查是否存在,我认为这可能不是预期的。也许对此有更多解释
  • @Acute 我将它添加到答案中。当然,根据你的数据成员,你可能真的想做点什么。
  • @Acute 是明确的,严格的弱排序、相等比较、散列只有当你想使用你的类作为 key 时才需要,或者如果你想将它插入到set
【解决方案2】:

1) 由于以下表达式,您的第一个示例中的代码无法编译:

my_map["one"]

my_map["one"]"one" 构造一个std::string,并将其传递给std::map::operator[]map::operator[] 确保将值映射到提供的键(如果键尚未与值关联,则通过将键与默认构造的值关联)并返回对该值的引用。

这不会编译,因为person 没有默认构造函数(“默认构造函数”是不带参数的构造函数)。

有几种方法可以解决这个问题。

一种方法是您采用的方式 - 删除构造函数。它之所以有效,是因为如果您不提供任何构造函数,则会隐式定义默认构造函数。

另一种方法是为person显式定义一个默认构造函数:

struct person
{
    person():name(){} //or person()=default; if your compiler supports this
    person(string n)
        :name(n)
    {}
    string name;
};

另一种方法是根本不使用operator[],而是使用map::insert,如下:

auto pair(my_map.insert(std::make_pair(std::string("one"),person("Tom"))));
if (!pair.second) {
    *pair.first = person("Tom");
}

2)在地图中查找元素的正确方法是(如您所说)使用:

if(my_map.find("one") == my_map.end()) {/*it doesn't exist in my_map*/}
else {/*it exists*/}

不会检查地图中的每个元素 - 实际上它可能只检查 O(log(map.size())) 元素。

您的担心完全没有根据,这是在地图中找到元素的正确方法,但是您继续的方式表明对 operator[] 的作用存在严重误解。

您问“如果地图中不存在"unknown"my_map["unknown"].identifier == "something" 返回 true 的概率是多少?”。

这个问题的答案是没有任何机会返回 true,因为如果映射中不存在键 std::string("unknown") 的值,那么 operator[] 会将 std::string("unknown") 与默认构造的 person 相关联,所以identifier 将是一个空字符串。

【讨论】:

  • 非常感谢您的详细解释!现在我明白了 struct 构造函数和 operator[] 是如何工作的!
猜你喜欢
  • 2017-09-25
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多