使用char *作为std :: map中的键


81

我试图弄清楚为什么下面的代码不起作用,并且我假设使用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;
}

14
有时候,一开始做正确的事会很痛。更改您的代码以使用std:string一次,之后再开心一点。
比约恩·波莱克斯(BjörnPollex)2010年

1
什么样的并发症?从char *隐式转换为std :: string。
tenfour

1
您不得将其char*用作地图键。看到我的答案为什么。
2010年

这似乎是由于使用而引起的不必要的并发症std::string
Pedro d'Aquino

我不明白,为了使用二进制键,地图是否不需要知道某个键是否相等,而不是知道某个键的值“小于”另一个键?
CodeMinion

Answers:


140

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

例如:

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

map<char *, int, cmp_str> BlahBlah;

2
实际上,他可以通过传递&std::strcmp第三个模板参数
Armen Tsirunyan,2010年

23
否,strcmp返回正,零或负整数。映射函子需要小于返回true,否则返回false。
aschepler,2010年

4
@Armen:我认为它不起作用,因为第三个模板参数期望使用f(a,b) = a<b,而不是f(a,b) = (-1 if a<b, 1 if a>b, 0 else)
kennytm 2010年

28
哦,对不起,我不好,发帖前没想过。让评论留在那里,让我的祖先感到遗憾:)
Armen Tsirunyan 2010年

2
正如我测试过的,它必须在bool operator()(char const * a,char const * b)之后使用const,就像bool operator()(char const * a,char const * b)const {blabla
ethanjyx

45

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


5
除非您定义自己的比较器,否则如接受答案中所述。
卢卡斯·卡林斯基

23

两个C样式的字符串可以具有相等的内容,但位于不同的地址。然后map比较指针,而不是内容。

转换成的成本std::map<std::string, int>可能不如您想像的高。

但是,如果确实需要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。
user2867288

8

您可以使用std::map<const char*, int>,但不能使用非const指针(请注意const为键添加了),因为在映射将它们称为键时,您不能更改这些字符串。(虽然地图通过制作键来保护其键const,但这只会构成指针,而不是其指向的字符串。)

但是,为什么不简单地使用std::map<std::string, int>呢?它开箱即用,没有头痛。


8

您正在比较使用achar *和使用字符串。他们不一样。

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

字符串是字符串。

该容器可以正常工作,但可以用作键为achar *且值为a的对的容器int


1
不需要指针为长整数。有些平台(例如win64,如果您曾听说过:-),则长整数小于指针,并且我相信还有更多晦涩的平台,其中指针和整数被加载到不同的寄存器中,并且在其他方法。C ++仅要求指针可以转换为某种整数类型并可以转换为整数。请注意,这并不意味着您可以将任何足够小的整数都转换为指针,而只是将转换指针得到的整数转换为指针。
Christopher Creutzig 2012年

@ChristopherCreutzig,谢谢您的评论。我相应地编辑了答案。
Daniel Daranas 2012年

2

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

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

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

我知道这个主题已经很老了,您现在已经修复了所有问题,但是我没有看到任何人提出这一点,因此为了将来的观众,我正在回答。


0

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

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


-5

有没有使用任何密钥类型,只要它支持比较(的问题<>==)和分配。

应该提到的一点-考虑到您正在使用模板类。其结果是,编译器会产生两个不同的实例char*int*。两者的实际代码实际上是相同的。

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


8
他们关键只需要支持<。但是它需要以一种有用的方式来实现它。它没有指针。现代编译器将折叠相同的模板实例。(我肯定可以确定VC会这样做。)void*除非测量表明这样做可以解决很多问题,否则我永远不会使用。永远不要过早放弃类型安全性。
2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.