39

我很确定我已经在某个地方看到了这个问题(comp.lang.c++?谷歌似乎也没有在那里找到它)但是在这里快速搜索似乎没有找到它,所以它是:

如果键不存在,为什么 std::map operator[] 会创建一个对象?我不知道,但对我来说,如果你与大多数其他 operator[](如 std::vector)相比,这似乎违反直觉,如果你使用它,你必须确保索引存在。我想知道在 std::map 中实现这种行为的理由是什么。就像我说的那样,当使用无效键访问时,更像是向量中的索引和崩溃(我猜是未定义的行为)不是更直观吗?

看到答案后细化我的问题:

好的,到目前为止,我得到了很多答案,说它基本​​上很便宜,所以为什么不或类似的东西。我完全同意这一点,但为什么不为此使用专用函数(我认为其中一条评论说在 java 中没有 operator[] 并且该函数称为 put)?我的观点是为什么 map operator[] 不像矢量那样工作?如果我在向量的超出范围索引上使用 operator[],即使它很便宜,我也不希望它插入一个元素,因为这可能意味着我的代码中有错误。我的观点是为什么它与地图不一样。我的意思是,对我来说,在地图上使用 operator[] 意味着:我知道这个密钥已经存在(无论出于何种原因,我只是插入了它,我在某处有冗余,无论如何)。我认为这样会更直观。

那就是说使用 operator[] 执行当前行为有什么好处(仅为此,我同意应该存在具有当前行为的函数,而不是 operator[])?也许这样可以提供更清晰的代码?我不知道。

另一个答案是它已经以这种方式存在,所以为什么不保留它,但是,可能当他们(stl 之前的那些)选择以这种方式实现它时,他们发现它提供了优势或什么?所以我的问题基本上是:为什么选择以这种方式实现它,这意味着与其他 operator[] 有点缺乏一致性。它有什么好处?

谢谢

4

13 回答 13

26

因为operator[]返回对值本身的引用,所以指示问题的唯一方法是抛出异常(通常,STL 很少抛出异常)。

如果你不喜欢这种行为,你可以map::find改用。它返回一个迭代器而不是值。这允许它在未找到值时返回一个特殊的迭代器(它返回map::end),但还需要您取消对迭代器的引用以获取该值。

于 2009-10-28T19:36:27.723 回答
14

标准说 (23.3.1.2/1) operator[] 返回(*((insert(make_pair(x, T()))).first)).second。这就是原因。它返回参考T&。没有办法返回无效的引用。它返回参考,因为我猜它非常方便,不是吗?

于 2009-10-28T19:34:11.163 回答
7

回答你真正的问题:没有令人信服的解释为什么要这样做。“只是因为”。

由于std::map关联容器,因此在映射中没有明确的预定义键范围必须存在(或不存在)(与 完全不同的情况相反std::vector)。这意味着使用std::map,您需要非插入和插入查找功能。可以以非插入方式重载[]并提供插入功能。或者可以做另一种方式:[]作为插入运算符重载并提供非插入搜索的功能。因此,有时有人决定采用后一种方法。这就是它的全部。

如果他们反过来做,也许今天有人会在这里问你问题的相反版本。

于 2009-10-28T20:12:13.520 回答
4

它用于分配目的:


void test()
{
   std::map<std::string, int >myMap;
   myMap["hello"] = 5;
}
于 2009-10-28T19:34:50.080 回答
4

我认为这主要是因为在 map 的情况下(例如,与 vector 不同)它相当便宜且易于操作——您只需创建一个元素。在向量的情况下,他们可以扩展向量以使新的下标有效——但是如果您的新下标远远超出了已有的下标,那么将所有元素添加到该点可能会相当昂贵。当您扩展一个向量时,您通常还指定要添加的新元素的值(尽管通常使用默认值)。在这种情况下,将无法在现有元素和新元素之间的空间中指定元素的值。

通常如何使用地图也有根本的区别。对于向量,通常在添加到向量中的事物与与向量中已有的事物一起工作的事物之间有一个清晰的界限。使用地图时,情况就不那么正确了——更常见的情况是看到代码会操纵存在的项目,或者如果它不存在则添加新项目。每个 operator[] 的设计都反映了这一点。

于 2009-10-28T20:30:37.897 回答
3

map.insert(key, item); 确保键在映射中但不会覆盖现有值。

map.operator[key] = 项目;确保键在地图中并用项目覆盖任何现有值。

这两个操作都非常重要,足以保证一行代码。设计者可能选择了对 operator[] 更直观的操作,并为另一个创建了函数调用。

于 2009-12-18T15:26:41.980 回答
3

它允许使用 插入新元素operator[],如下所示:

std::map<std::string, int> m;
m["five"] = 5;

5分配给由返回的值m["five"],它是对新创建元素的引用。如果operator[]不插入新元素,则无法以这种方式工作。

于 2009-10-28T19:35:14.263 回答
1

这里的区别在于 map 存储“索引”,即存储在 map 中(在其底层 RB 树中)的值是 a std::pair,而不仅仅是“索引”值。总是map::find()会告诉您是否存在与给定密钥的配对。

于 2009-10-28T19:34:53.280 回答
1

答案是因为他们想要一个既方便又快速的实现。

向量的底层实现是一个数组。因此,如果数组中有 10 个条目并且您想要条目 5,则 T& vector::operator[](5) 函数只返回 headptr+5。如果您要求输入 5400,它将返回 headptr+5400。

地图的底层实现通常是一棵树。每个节点都是动态分配的,不像标准要求的向量是连续的。所以 nodeptr+5 没有任何意义,map["some string"] 并不意味着 rootptr+offset("some string")。

与使用地图查找一样,如果您想进行边界检查,向量具有 getAt()。在向量的情况下,边界检查对于那些不想要它的人来说被认为是不必要的成本。在地图的情况下,不返回引用的唯一方法是抛出异常,对于那些不想要它的人来说,这也被认为是不必要的成本。

于 2009-10-28T20:29:23.203 回答
0

考虑这样一个输入 - 3 个块,每个块 2 行,第一行是第二个中的元素数:

5
13 20 22 43 146
4
13 22 43 146
5
13 43 67 89 146

问题:计算所有三个块的第二行中存在的整数数。(对于此示例输入,输出应为 3,因为所有三个块的第二行中都存在 13、43 和 146)

看看这段代码有多好:

int main ()
{
    int n, curr;
    map<unsigned, unsigned char> myMap;
    for (int i = 0; i < 3; ++i)
    {
        cin >> n;
        for (int j = 0; j < n; ++j)
        {
            cin >> curr;
            myMap[curr]++;
        }

    }

    unsigned count = 0;
    for (auto it = myMap.begin(); it != myMap.end(); ++it)
    {
        if (it->second == 3)
            ++count;
    }

    cout << count <<endl;
    return 0;
}

根据标准operator[]返回参考(*((insert(make_pair(key, T()))).first)).second。这就是为什么我可以写:

myMap[curr]++;

curr如果该键不存在于地图中,它会插入一个带有键的元素并将值初始化为零。尽管元素在地图中或不在地图中,它也会增加值。

看看有多简单?这很好,不是吗?这是一个很好的例子,它真的很方便。

于 2013-05-07T18:39:27.930 回答
0

If you want to read an element with some key from an std::map,
but you are unsure whether it exists,
and in case it doesn't, you don't want to insert it by accident,
but rather want to get an exception thrown,
but you also don't want to manually check map.find(key) != map.end() everytime you read an element,

just use map::at(key) (C++11)

https://www.cplusplus.com/reference/map/map/at/

于 2022-01-02T03:07:30.217 回答
0

我知道这是一个老问题,但似乎没有人能很好地回答 IMO。到目前为止,我还没有看到任何提及这一点:

应避免未定义行为的可能性!如果除了UB之外还有任何合理的行为,那么我想我们应该这样做。

std::vector/array表现出带有错误operator[]索引的未定义行为,因为实际上没有合理的选择,因为这是您可以在 c/c++ 中执行的最快、最基本的事情之一,尝试检查任何内容都是错误的。检查是at()为了什么。

std::*associative_container*已经完成了查找索引元素将去哪里的工作,因此在那里创建一个并返回它是有意义的。这是非常有用的行为,替代品operator[]看起来不太干净,但即使创建和插入新项目不是您想要的,或者对您没有用处,它仍然比未定义的行为好得多。

我认为operator[]是使用关联容器的首选语法,为了可读性,对我来说这非常直观,并且与operator[]数组的概念完全匹配:返回对该位置的项目的引用,以使用或分配给.

如果我对“如果那里什么都没有怎么办”的直觉只是“未定义的行为”,那么我的情况绝对不会更糟,因为我会尽我所能避免这种情况,句号。

然后有一天我发现我可以插入一个带有operator[]......生活更好的项目。

于 2021-07-18T05:55:37.760 回答
-2

不可能避免创建对象,因为 operator[] 不知道如何使用它。

myMap["apple"] = "green";

或者

char const * cColor = myMyp["apple"];

我建议地图容器应该添加一个类似的功能

if( ! myMap.exist( "apple")) throw ...

它比阅读更简单更好

if( myMap.find( "apple") != myMap.end()) throw ...

于 2013-06-28T06:00:03.253 回答