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;
}
问#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;
}
通过这样做,我们避免了迭代,并且内存中的垃圾与我们的标识符匹配的可能性是......我想很小,特别是如果我们使用一些哈希值。那么这样做可以(安全)吗?
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
将是一个空字符串。
首先,由于您有一个构造函数,因此您需要提供一个默认构造函数。这是因为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
,tr1
和boost
hash_map
。这些使用哈希函数执行搜索是恒定时间。是否值得使用它取决于地图大小等因素。常量时间可能大于搜索小地图所花费的对数时间。
注:如果要将结构用作键,或者想将其插入到标准库sets
之一中,则有进一步的要求:
maps:您需要通过类的小于运算符或自定义比较器函子为键提供严格的弱排序。如果你使用person
作为密钥,你不需要这样的东西:
bool operator<(const person& rhs) const { return name < rhs.name; }
unordered_或哈希映射:您必须通过 operator==
或函子为密钥提供哈希函数和相等比较。