Использование char * в качестве ключа в std :: map - PullRequest
72 голосов
/ 11 ноября 2010

Я пытаюсь выяснить, почему следующий код не работает, и я предполагаю, что это проблема с использованием 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;
}

Ответы [ 8 ]

123 голосов
/ 11 ноября 2010

Вам нужно задать функтор сравнения на карте, иначе он сравнивает указатель, а не строку с нулевым символом в конце, на которую он указывает.В общем, это так, когда вы хотите, чтобы ключ карты был указателем.

Например:

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

map<char *, int, cmp_str> BlahBlah;
44 голосов
/ 11 ноября 2010

Вы не можете использовать 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.

22 голосов
/ 11 ноября 2010

Две строки в стиле 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;
8 голосов
/ 11 ноября 2010

Вы можете заставить его работать с std::map<const char*, int>, но не должны использовать указатели, отличные от const (обратите внимание на добавленный const для ключа), потому что вы не должны изменять эти строки, пока карта ссылается на них как на ключи , (Хотя карта защищает свои ключи, делая их const, это будет содержать только указатель , а не строку, на которую она указывает.)

Но почему бы вам просто не использовать std::map<std::string, int>? Работает из коробки без головной боли.

8 голосов
/ 11 ноября 2010

Вы сравниваете использование char * с использованием строки. Они не одинаковы.

A char * - указатель на символ. В конечном счете, это целочисленный тип, значение которого интерпретируется как действительный адрес для char.

Строка - это строка.

Контейнер работает правильно, но как контейнер для пар, в которых ключ равен char *, а значение - int.

2 голосов
/ 16 апреля 2012

Как говорят другие, вам, вероятно, следует использовать std :: string вместо char * в этом случае, хотя в принципе нет ничего плохого с указателем в качестве ключа, если это действительно то, что нужно.

Я думаю, что еще одна причина, по которой этот код не работает, заключается в том, что как только вы находите доступную запись на карте, вы пытаетесь повторно вставить ее в карту с тем же ключом (символ *). Поскольку этот ключ уже существует на вашей карте, вставка не удастся. Стандарт для map :: insert () определяет это поведение ... если значение ключа существует, вставка завершается неудачно, а сопоставленное значение остается неизменным. Тогда это все равно будет удалено. Вам нужно сначала удалить его, а затем снова вставить.

Даже если вы измените char * на std :: string, эта проблема останется.

Я знаю, что эта ветка довольно старая, и вы уже все исправили, но я не видел, чтобы кто-нибудь высказывал эту точку зрения, поэтому ради будущих зрителей я отвечаю.

0 голосов
/ 07 января 2015

Трудно было использовать char * в качестве ключа карты, когда я пытаюсь найти элемент в нескольких исходных файлах.Это прекрасно работает, когда все доступ / поиск в одном и том же исходном файле, где элементы вставлены.Однако, когда я пытаюсь получить доступ к элементу, используя find в другом файле, я не могу получить элемент, который определенно находится внутри карты.

Оказывается, причина в том, что, как указано Plabo , указатели (каждый модуль компиляции имеет свой собственный постоянный символ *) НЕ одинаковы при обращении к нему в другом файле cpp.,

0 голосов
/ 11 ноября 2010

Нет проблем с использованием любого типа ключа, если он поддерживает сравнение (<, >, ==) и назначение.

Один момент, который следует упомянуть, - принять во внимание, чтовы используете шаблон класс.В результате компилятор сгенерирует два разных экземпляра для char* и int*.Принимая во внимание, что фактический код обоих будет практически идентичен.

Следовательно - я бы рассмотрел использование void* в качестве типа ключа, а затем приведение его при необходимости.Это моё мнение.

...