【问题标题】:Using char* as a key in std::map使用 char* 作为 std::map 中的键
【发布时间】:2011-05-08 15:30:12
【问题描述】:

我试图弄清楚为什么以下代码不起作用,并且我假设这是使用 char* 作为键类型的问题,但是我不确定如何解决它或它为什么会发生。我使用的所有其他函数(在 HL2 SDK 中)都使用 char*,所以使用 std::string 会导致很多不必要的复杂化。

std::map<char*, int> g_PlayerNames;

int PlayerManager::CreateFakePlayer()
{
    FakePlayer *player = new FakePlayer();
    int index = g_FakePlayers.AddToTail(player);

    bool foundName = false;

    // Iterate through Player Names and find an Unused one
    for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
    {
        if(it->second == NAME_AVAILABLE)
        {
            // We found an Available Name. Mark as Unavailable and move it to the end of the list
            foundName = true;
            g_FakePlayers.Element(index)->name = it->first;

            g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
            g_PlayerNames.erase(it); // Remove name since we added it to the end of the list

            break;
        }
    }

    // If we can't find a usable name, just user 'player'
    if(!foundName)
    {
        g_FakePlayers.Element(index)->name = "player";
    }

    g_FakePlayers.Element(index)->connectTime = time(NULL);
    g_FakePlayers.Element(index)->score = 0;

    return index;
}

【问题讨论】:

  • 有时做正确的事一开始会很痛苦。将您的代码更改为使用std:string 一次,然后就开心了。
  • 什么样的并发症?存在从 char* 到 std::string 的隐式转换。
  • 您不能使用char* 作为映射键。请参阅my answer 为什么。
  • 这似乎是由于not使用std::string造成的不必要的并发症。
  • 我不明白,为了使用二进制键,Map 是否不需要知道键是否相等而不是知道键的值“小于”另一个?

标签: c++ map stdmap


【解决方案1】:

您需要为映射提供一个比较函子,否则它会比较指针,而不是它指向的以空字符结尾的字符串。通常,只要您希望地图键成为指针,就会出现这种情况。

例如:

struct cmp_str
{
   bool operator()(char const *a, char const *b) const
   {
      return std::strcmp(a, b) < 0;
   }
};

map<char *, int, cmp_str> BlahBlah;

【讨论】:

  • 其实他可以把&amp;std::strcmp作为第三个模板参数传入
  • 否,strcmp 返回正整数、零整数或负整数。映射函子需要在小于时返回 true,否则返回 false。
  • @Armen:我认为它行不通,因为第三个模板参数需要f(a,b) = a&lt;b,而不是f(a,b) = (-1 if a&lt;b, 1 if a&gt;b, 0 else)
  • 哦,对不起,我的错,发帖前没想到。让评论留在那里,给我的祖先带来耻辱:)
  • 正如我测试的那样,它必须在 bool operator()(char const *a, char const *b) 之后使用 const,例如 bool operator()(char const *a, char const *b ) const{ blabla
【解决方案2】:

您不能使用char*,除非您绝对 100% 确定您将使用完全相同的指针而不是字符串来访问地图。

例子:

char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20

如果您使用s1 访问地图,您将获得与使用s2 访问地图不同的位置。

【讨论】:

  • 除非您定义自己的比较器,如已接受的答案中所述。
【解决方案3】:

两个 C 风格的字符串可以有相同的内容但位于不同的地址。 map 比较的是指针,而不是内容。

转换为std::map&lt;std::string, int&gt; 的成本可能没有您想象的那么高。

但如果您确实需要使用const char* 作为映射键,请尝试:

#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
    bool operator() (const char* str1, const char* str2) const
    { return std::strcmp(str1, str2) < 0; }
};

typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;

【讨论】:

  • 感谢您的信息。根据我的经验,我强烈建议转换为 std::string。
【解决方案4】:

您可以使用std::map&lt;const char*, int&gt;,但不能使用非const 指针(注意为键添加的const),因为当映射将它们称为键时,您不能更改这些字符串. (虽然地图通过将其设为const 来保护其密钥,但这只会构成指针,而不是它指向的字符串。)

但是你为什么不直接使用std::map&lt;std::string, int&gt;呢?它开箱即用,不会让人头疼。

【讨论】:

    【解决方案5】:

    您将使用char * 与使用字符串进行比较。它们不一样。

    char * 是指向 char 的指针。最终,它是一个整数类型,其值被解释为char 的有效地址。

    字符串就是字符串。

    容器可以正常工作,但作为键为 char * 且值为 int 的对的容器。

    【讨论】:

    • 指针不需要是长整数。有一些平台(比如win64,如果你听说过它:-))长整数小于指针,而且我相信还有更多模糊的平台,指针和整数被加载到不同的寄存器中并在不同的寄存器中进行不同的处理其他方法。 C++ 只要求指针可以转换为某种整数类型并返回;请注意,这并不意味着您可以将任何足够小的整数转换为指针,只有从转换指针获得的整数。
    • @ChristopherCreutzig,感谢您的评论。我相应地编辑了我的答案。
    【解决方案6】:

    正如其他人所说,在这种情况下,您可能应该使用 std::string 而不是 char*,尽管原则上如果确实需要指针作为键并没有错。

    我认为此代码不起作用的另一个原因是,一旦您在地图中找到可用条目,您就会尝试使用相同的键(char*)将其重新插入到地图中。由于该键已存在于您的地图中,因此插入将失败。 map::insert() 的标准定义了这种行为......如果键值存在,则插入失败并且映射值保持不变。然后它无论如何都会被删除。您需要先将其删除,然后重新插入。

    即使您将 char* 更改为 std::string,此问题仍然存在。

    我知道这个帖子已经很老了,你现在已经修复了它,但我没有看到有人提出这一点,所以为了未来的观众,我正在回答。

    【讨论】:

      【解决方案7】:

      当我尝试在多个源文件中查找元素时,很难使用 char* 作为映射键。当在插入元素的同一源文件中进行所有访问/查找时,它工作正常。但是,当我尝试在另一个文件中使用 find 访问元素时,我无法获取绝对在地图内的元素。

      事实证明,原因正如Plabo 指出的那样,指针(每个编译单元都有自己的常量 char*)在另一个 cpp 文件中访问时根本不一样。

      【讨论】:

        【解决方案8】:

        std::map&lt;char*,int&gt; 将使用默认的std::less&lt;char*,int&gt; 来比较char* 键,这将进行指针比较。但是你可以像这样指定你自己的 Compare 类:

        class StringPtrCmp {
            public:
                StringPtrCmp() {}
        
            bool operator()(const char *str1, const char *str2) const   {
                if (str1 == str2)
                    return false; // same pointer so "not less"
                else
                    return (strcmp(str1, str2) < 0); //string compare: str1<str2 ?
            }
        };
        
        std::map<char*, YourType, StringPtrCmp> myMap;
        

        请记住,您必须确保 char* 指针有效。 我建议还是使用std::map&lt;std::string, int&gt;

        【讨论】:

          【解决方案9】:

          只要支持比较(&lt;&gt;==)和赋值,使用任何键类型都没有问题。

          应该提到的一点 - 考虑到您正在使用 模板 类。结果编译器将为char*int* 生成两个不同的实例。而两者的实际代码几乎相同。

          因此 - 我会考虑使用 void* 作为键类型,然后根据需要进行强制转换。 这是我的看法。

          【讨论】:

          • 他们的key只需要支持&lt;。但它需要以一种有帮助的方式实现它。它没有指针。现代编译器将折叠相同的模板实例。 (我确信 VC 会这样做。)我永远不会使用void*,除非测量表明这可以解决很多问题。 永远不要过早地放弃类型安全。
          猜你喜欢
          • 1970-01-01
          • 2014-08-06
          • 2011-06-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-09
          相关资源
          最近更新 更多