我很确定我已经在某个地方看到了这个问题(comp.lang.c ++?Google似乎也没有在那里找到它)但是这里的快速搜索似乎没有找到它所以它是:< / p>
如果密钥不存在,为什么std :: map operator []会创建一个对象?我不知道,但对我而言,如果你与大多数其他运算符[](如std :: vector)进行比较,这似乎是违反直觉的,如果你使用它,你必须确保索引存在。我想知道在std :: map中实现这种行为的理由是什么。就像我说的那样,当使用无效密钥访问时,更像行动中的索引和崩溃(我猜是未定义的行为)会不会更直观?
看到答案后提炼我的问题:
好的,到目前为止,我得到了很多答案,说基本上它很便宜所以为什么不是或类似的东西。我完全同意这一点,但为什么不使用专用函数(我认为其中一条评论说在java中没有运算符[]并且该函数被称为put)?我的观点是为什么不映射operator []像vector一样工作?如果我在向量上的超出范围索引上使用operator [],我不希望它插入元素,即使它很便宜,因为这可能意味着我的代码中出现错误。我的观点是为什么地图不一样。我的意思是,对我来说,在地图上使用operator []意味着:我知道这个密钥已经存在(无论出于什么原因,我只是插入它,我在某处有冗余,无论如何)。我认为这样会更直观。
那说使用operator []执行当前行为的优点是什么(仅限于此,我同意具有当前行为的函数应该在那里,而不是operator [])?也许它以这种方式提供更清晰的代码?我不知道。
另一个答案是,它已经存在,所以为什么不保留它然后,可能当他们(stl之前的那些)选择实现它时,他们发现它提供了一个优势或什么?所以我的问题基本上是:为什么选择以这种方式实现它,这意味着与其他运算符[]有点缺乏一致性。它给了什么好处?
由于
答案 0 :(得分:21)
因为operator[]
返回对值本身的引用,所以指示问题的唯一方法是抛出异常(通常,STL很少抛出异常)。
如果您不喜欢此行为,可以改用map::find
。它返回一个迭代器而不是值。这允许它在找不到值时返回一个特殊的迭代器(它返回map::end
),但也要求你取消引用迭代器来获取值。
答案 1 :(得分:11)
标准说(23.3.1.2/1)运算符[]返回(*((insert(make_pair(x, T()))).first)).second
。这就是原因。它返回引用T&
。无法返回无效引用。它返回参考因为我觉得很方便,不是吗?
答案 2 :(得分:8)
回答你的真实问题:没有令人信服的解释为什么这样做。 “只是因为”。
由于std::map
是一个关联容器,因此地图中必须存在(或不存在)的键没有明确的预定义范围(与完全不同的情况相反) std::vector
)。这意味着使用std::map
,您需要非插入和插入查找功能。可以以非插入方式重载[]
并提供插入功能。或者可以采用相反的方式:重载[]
作为插入运算符,并为非插入搜索提供函数。所以,某人有时决定采用后一种方法。这就是它的全部内容。
如果他们反过来这样做,也许今天有人会问你问题的反面版本。
答案 3 :(得分:4)
用于分配目的:
void test()
{
std::map<std::string, int >myMap;
myMap["hello"] = 5;
}
答案 4 :(得分:4)
我认为这主要是因为在地图的情况下(例如,与矢量不同),它相当便宜且易于操作 - 您只需要创建一个元素。在向量的情况下,他们可以扩展向量以使新的下标有效 - 但是如果你的新下标远远超出已经存在的下标,那么将所有元素添加到该点可能相当昂贵。扩展矢量时,通常还要指定要添加的新元素的值(尽管通常使用默认值)。在这种情况下,无法在现有元素和新元素之间的空间中指定元素的值。
通常如何使用地图也存在根本区别。使用向量时,通常会在添加到向量的事物和与向量中已有的事物一起使用的事物之间进行清晰的描绘。有了一张地图,那就不那么真实了 - 如果有一个代码可以操作那里的项目,那么很多会更常见,或者如果它已经存在则添加一个新项目。每个运算符[]的设计反映了这一点。
答案 5 :(得分:3)
它允许使用operator[]
插入新元素,如下所示:
std::map<std::string, int> m;
m["five"] = 5;
5
被分配给m["five"]
返回的值,该值是对新创建的元素的引用。如果operator[]
不会插入新元素,则无法以这种方式工作。
答案 6 :(得分:3)
map.insert(key,item);确保密钥在地图中,但不会覆盖现有值。
map.operator [key] = item;确保键在地图中并用项目覆盖任何现有值。
这两项操作都非常重要,足以保证一行代码。设计人员可能会选择哪个操作对于operator []更直观,并为另一个创建了函数调用。
答案 7 :(得分:1)
这里的区别在于map存储“索引”,即存储在地图中的值(在其底层RB树中)是std::pair
,而不仅仅是“索引”值。
总是map::find()
会告诉您是否存在与给定密钥的配对。
答案 8 :(得分:1)
答案是因为他们想要一个既方便又快速的实现。
向量的底层实现是一个数组。因此,如果阵列中有10个条目,并且您想要输入5,那么T&amp; vector :: operator [](5)函数只返回headptr + 5。如果您要求输入5400,则返回headptr + 5400。
地图的底层实现通常是树。每个节点都是动态分配的,与标准要求连续的向量不同。所以nodeptr + 5并不意味着什么,map [“some string”]并不意味着rootptr + offset(“some string”)。
与map一样,如果你想要边界检查,vector会有getAt()。在向量的情况下,对于那些不想要它的人来说,边界检查被认为是不必要的成本。在地图的情况下,不返回引用的唯一方法是抛出异常,对于那些不想要它的人来说,这也被认为是不必要的代价。
答案 9 :(得分: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
的元素,如果地图中没有该键,则将该值初始化为零。并且它增加了值,尽管元素在地图中或没有。
看看有多简单?很好,不是吗?这是一个非常方便的好例子。
答案 10 :(得分:0)
我知道这是个老问题,但 IMO 似乎没有人回答得很好。到目前为止,我还没有看到任何提及:
要避免未定义行为的可能性!如果除了 UB 之外还有任何合理的行为,那么我想我们应该这样做。
std::vector/array
表现出带有错误 operator[]
索引的未定义行为,因为实际上没有合理的选择,因为这是您在 c/c++ 中可以做的最快、最基本的事情之一,而且它会尝试检查任何东西都是错误的。检查是 at()
的用途。
std::*associative_container*
已经完成了查找索引元素将去哪里的工作,因此在那里创建一个并返回它是有意义的。这是非常有用的行为,operator[]
的替代品看起来不那么干净,但即使创建和插入新项目不是您想要的,或者对您没有用,它仍然比未定义的行为。
我认为 operator[]
是使用关联容器的首选语法,为了可读性,对我来说这是非常直观的,并且与数组的 operator[]
概念完全匹配:返回一个引用该位置的项目,使用或分配给。
如果我对“如果什么都没有怎么办”的直觉只是“未定义的行为”,那么我绝对不会更糟,因为我会尽我所能避免这种情况,句号。
然后有一天我发现我可以插入一个带有 operator[]
的项目......生活会更好。
答案 11 :(得分:-2)
无法避免创建对象,因为operator []不知道如何使用它。
myMap["apple"] = "green";
或
char const * cColor = myMyp["apple"];
我建议map容器应该添加一个像
这样的函数 if( ! myMap.exist( "apple"))
throw ...
阅读比
更简单,更好 if( myMap.find( "apple") != myMap.end())
throw ...