Próbuję dowiedzieć się, dlaczego poniższy kod nie działa i przypuszczam, że jest to problem z użyciem znaku * jako typu klucza, jednak nie jestem pewien, jak mogę go rozwiązać i dlaczego tak się dzieje. Wszystkie inne funkcje, których używam (w HL2 SDK) używam, char*
więc użycie std::string
spowoduje wiele niepotrzebnych komplikacji.
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
jednorazowy i ciesz się później.char*
jako klucza mapy. Zobacz moją odpowiedź dlaczego.std::string
.Odpowiedzi:
Musisz dać mapie funktor porównawczy, w przeciwnym razie porównuje on wskaźnik, a nie ciąg zakończony znakiem null, na który wskazuje. Generalnie dzieje się tak zawsze, gdy chcesz, aby klucz mapy był wskaźnikiem.
Na przykład:
struct cmp_str { bool operator()(char const *a, char const *b) const { return std::strcmp(a, b) < 0; } }; map<char *, int, cmp_str> BlahBlah;
źródło
&std::strcmp
jako trzeci parametr szablonustrcmp
zwraca dodatnią, zerową lub ujemną liczbę całkowitą. Funktor mapy musi zwracać wartość true w przypadku mniejszego niż i fałsz w przeciwnym razie.f(a,b) = a<b
, że nief(a,b) = (-1 if a<b, 1 if a>b, 0 else)
.Nie możesz używać,
char*
chyba że masz absolutną 100% pewność, że uzyskasz dostęp do mapy z dokładnie tymi samymi wskaźnikami , a nie ciągami znaków.Przykład:
char *s1; // pointing to a string "hello" stored memory location #12 char *s2; // pointing to a string "hello" stored memory location #20
Jeśli uzyskasz dostęp do mapy za pomocą
s1
, uzyskasz inną lokalizację niż dostęp do niej za pomocąs2
.źródło
Dwa łańcuchy w stylu C mogą mieć taką samą zawartość, ale znajdować się pod różnymi adresami. I to
map
porównuje wskaźniki, a nie zawartość.Koszt konwersji na
std::map<std::string, int>
może nie być taki, jak myślisz.Ale jeśli naprawdę potrzebujesz używać
const char*
jako kluczy mapy, spróbuj:#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;
źródło
Możesz sprawić, by działał z
std::map<const char*, int>
, ale nie możesz używaćconst
wskaźników innych niż wskaźniki (zwróć uwagę na dodaneconst
dla klucza), ponieważ nie możesz zmieniać tych ciągów, gdy mapa odnosi się do nich jako do kluczy. (Podczas gdy mapa chroni swoje klucze, tworząc jeconst
, spowoduje to tylko utworzenie wskaźnika , a nie ciąg, na który wskazuje).Ale dlaczego po prostu nie używasz
std::map<std::string, int>
? Działa po wyjęciu z pudełka bez bólów głowy.źródło
Porównujesz użycie a
char *
do użycia ciągu. One nie są takie same.A
char *
jest wskaźnikiem do znaku. Ostatecznie jest to typ całkowity, którego wartość jest interpretowana jako prawidłowy adres dlachar
.Ciąg to ciąg.
Kontener działa poprawnie, ale jako kontener dla par, w których klucz to a,
char *
a wartość toint
.źródło
Jak mówią inni, prawdopodobnie powinieneś użyć std :: string zamiast char * w tym przypadku, chociaż w zasadzie nie ma nic złego w stosowaniu wskaźnika jako klucza, jeśli jest to naprawdę wymagane.
Myślę, że innym powodem, dla którego ten kod nie działa, jest to, że po znalezieniu dostępnego wpisu na mapie próbujesz ponownie wstawić go do mapy za pomocą tego samego klucza (znak *). Ponieważ ten klucz już istnieje w twojej mapie, wstawianie nie powiedzie się. Standard dla map :: insert () definiuje to zachowanie ... jeśli istnieje wartość klucza, funkcja wstawiania kończy się niepowodzeniem i odwzorowana wartość pozostaje niezmieniona. Następnie i tak zostanie usunięty. Najpierw musisz go usunąć, a następnie włożyć ponownie.
Nawet jeśli zmienisz znak * na std :: string ten problem pozostanie.
Wiem, że ten wątek jest dość stary i już to wszystko naprawiłeś, ale nie widziałem nikogo, kto by to zauważył, więc dla dobra przyszłych widzów odpowiadam.
źródło
Trudno mi było używać znaku * jako klucza mapy, gdy próbowałem znaleźć element w wielu plikach źródłowych. Działa dobrze, gdy wszystkie uzyskiwanie dostępu / znajdowanie w tym samym pliku źródłowym, w którym wstawiane są elementy. Jednak gdy próbuję uzyskać dostęp do elementu za pomocą funkcji find w innym pliku, nie jestem w stanie pobrać elementu, który zdecydowanie znajduje się wewnątrz mapy.
Okazuje się, że powód jest taki, jak wskazał Plabo , wskaźniki (każda jednostka kompilacji ma swój własny stały znak *) NIE są w ogóle takie same, gdy uzyskuje się do niego dostęp w innym pliku cpp.
źródło
std::map<char*,int>
użyje wartości domyślnejstd::less<char*,int>
do porównaniachar*
kluczy, co spowoduje porównanie wskaźników. Ale możesz określić własną klasę porównania w następujący sposób: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;
Pamiętaj, że musisz się upewnić, że wskaźnik char * jest prawidłowy. Radziłbym użyć
std::map<std::string, int>
mimo wszystko.źródło
Nie ma problemu, aby użyć dowolnego klucza typ tak długo, jak obsługuje porównania (
<
,>
,==
) i przypisanie.Jedna kwestia, o której należy wspomnieć - weź pod uwagę, że używasz klasy szablonu . W rezultacie kompilator wygeneruje dwie różne instancje dla
char*
iint*
. Podczas gdy rzeczywisty kod obu będzie praktycznie identyczny.Dlatego - rozważyłbym użycie a
void*
jako typu klucza, a następnie rzutowanie w razie potrzeby. To jest moja opinia.źródło
<
. Ale musi to zaimplementować w pomocny sposób. Nie ma wskaźników. Nowoczesne kompilatory będą składać identyczne instancje szablonów. (Wiem na pewno, że VC to robi.) Nigdy bym tego nie używałvoid*
, chyba że pomiar wykazałby to, aby rozwiązać wiele problemów. Nigdy nie należy rezygnować z bezpieczeństwa typu przedwcześnie.