2010-11-11 12 views
61

Ich versuche herauszufinden, warum der folgende Code nicht funktioniert, und ich gehe davon aus, dass es ein Problem mit char * als Schlüsseltyp ist, aber ich bin mir nicht sicher wie ich es lösen kann oder warum es auftritt. Alle anderen Funktionen, die ich verwende (im HL2 SDK), verwenden char*, so dass die Verwendung von std::string viele unnötige Komplikationen verursachen wird.Verwenden von char * als Schlüssel in std :: map

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; 
} 
+10

Manchmal tut das Richtige zuerst weh. Ändern Sie Ihren Code, um 'std: string' einmal zu verwenden und danach glücklich zu sein. –

+1

Welche Art von Komplikationen? Es gibt eine implizite Konvertierung von char * nach std :: string. – tenfour

+0

Sie dürfen 'char *' nicht als Kartenschlüssel verwenden. Siehe [meine Antwort] (http://stackoverflow.com/questions/4157687/using-char-as-a-key-in-stdmap/4157811#4157811) warum. – sbi

Antwort

108

Sie müssen der Karte einen Vergleichsfunktor geben, andernfalls vergleicht sie den char * -Zeiger nicht mit der Zeichenkette. Im Allgemeinen ist dies der Fall, wenn Sie möchten, dass Ihr Kartenschlüssel ein Zeiger ist.

dh.

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

map<char *, int, cmp_str> BlahBlah; 

BEARBEITEN: Abgesehen von meiner Bearbeitung, ist der Funktor einfacher zu bedienen.

+2

tatsächlich kann er einfach die '& std :: strcmp' als dritten Template-Parameter übergeben –

+20

Nein,' strcmp' gibt eine positive, Null oder negative Ganzzahl zurück. Der Map-Funktor muss andernfalls "Wahr" für "Kleiner als" und "Falsch" zurückgeben. – aschepler

+4

@Armen: Ich glaube nicht, dass es funktioniert, da der dritte Template-Parameter etwas wie 'f (a, b) = a b, 0 sonst) '. – kennytm

41

Sie können nicht char* verwenden, wenn Sie absolut 100% sicher sind, Sie die Karte mit den exakt gleichen Zeiger zuzugreifen gehen, keine Strings.

Beispiel:

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

Wenn Sie mit s1 Karte zugreifen müssen Sie einen anderen Standort erhalten, als es mit s2 erreichbar.

+3

Sehr gute Erklärung. –

8

Sie vergleichen mit einem char * mit einer Zeichenfolge. Sie sind nicht gleich. Eine char * ist ein Zeiger auf ein Zeichen. Letztendlich ist es ein ganzzahliger Typ, dessen Wert als gültige Adresse für eine char interpretiert wird.

Eine Zeichenfolge ist eine Zeichenfolge.

Der Container funktioniert ordnungsgemäß, aber als Container für Paare, in denen der Schlüssel char * ist und der Wert int ist.

+1

Es ist nicht erforderlich, dass ein Zeiger eine lange ganze Zahl ist. Es gibt Plattformen (wie win64, wenn Sie jemals davon gehört haben :-)), wo eine lange Ganzzahl kleiner als ein Zeiger ist, und ich glaube, es gibt auch obskurere Plattformen, bei denen Zeiger und ganze Zahlen in verschiedene Register geladen und anders behandelt werden andere Möglichkeiten. C++ erfordert nur, dass Zeiger in einen ganzzahligen Typ und zurück konvertiert werden können; Beachten Sie, dass dies nicht bedeutet, dass Sie eine ausreichend kleine ganze Zahl in einen Zeiger umwandeln könnten, nur die, die Sie aus der Konvertierung eines Zeigers erhalten haben. –

+0

@ChristopherCreutzig, danke für deinen Kommentar. Ich habe meine Antwort entsprechend bearbeitet. –

21

Zwei C-Style-Strings können gleiche Inhalte haben, aber unterschiedliche Adressen haben. Und das map vergleicht die Zeiger, nicht die Inhalte.

Die Kosten für die Konvertierung in std::map<std::string, int> sind möglicherweise nicht so viel wie Sie denken.

Aber wenn Sie wirklich als Mapkeys brauchen zu verwenden const char*, versuchen:

#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; 
+0

Danke für die Info. Basierend auf meiner Erfahrung empfehle ich die Umwandlung in std :: string. – user2867288

-5

Es ist kein Problem, so lange auf Tastentyp zu verwenden, wie es Vergleich unterstützt (<, >, ==) und Zuordnung.

Ein Punkt, der erwähnt werden sollte - berücksichtigen Sie, dass Sie eine Vorlage Klasse verwenden. Als Ergebnis erzeugt der Compiler zwei verschiedene Instanziierungen für char* und int*. Während die tatsächliche Code von beiden wird praktisch identisch sein.

Daher - ich würde in Betracht ziehen, eine void* als Schlüsseltyp zu verwenden, und dann Gießen wie erforderlich. Das ist meine Meinung.

+6

Der Schlüssel muss nur '<' unterstützen. Aber es muss das auf eine Weise umsetzen, die hilfreich ist. Es ist nicht mit Zeigern. Moderne Compiler falten identische Template-Instanzen. (Ich weiß ganz genau, dass VC das tut.) Ich würde 'void *' nie verwenden, es sei denn, die Messung zeigt, dass dies viele Probleme löst. __Abandoning Typ-Sicherheit sollte niemals vorzeitig durchgeführt werden. – sbi

8

Sie können es mit std::map<const char*, int>, darf aber nicht verwenden, nicht const Zeiger (beachten Sie den zusätzlichen const für den Schlüssel) erhalten arbeiten, weil Sie nicht diese Zeichenfolgen, während die Karte bezieht sich auf sie als Schlüssel ändern müssen. (Während eine Karte seine Schlüssel schützt, indem sie machen const, würde dies nur constify den Zeiger, nicht die Zeichenkette, die es verweist.)

Aber warum Sie std::map<std::string, int> nicht einfach verwenden? Es funktioniert ohne Kopfschmerzen aus der Box.

2

Wie die anderen sagen, sollten Sie in diesem Fall wahrscheinlich std :: string anstelle von char * verwenden, obwohl im Prinzip nichts mit einem Zeiger als Schlüssel falsch ist, wenn das wirklich erforderlich ist.

Ich denke, ein anderer Grund, warum dieser Code nicht funktioniert, ist, dass sobald Sie einen verfügbaren Eintrag in der Karte finden Sie versuchen, es wieder in die Karte mit dem gleichen Schlüssel (Char *). Da dieser Schlüssel in Ihrer Map bereits vorhanden ist, schlägt die Einfügung fehl. Der Standard für map :: insert() definiert dieses Verhalten ... wenn der Schlüsselwert existiert, schlägt die Einfügung fehl und der zugeordnete Wert bleibt unverändert. Dann wird es trotzdem gelöscht. Sie müssten es zuerst löschen und dann erneut einfügen.

Auch wenn Sie das char * in eine std :: string ändern, bleibt dieses Problem bestehen.

Ich weiß, dass dieser Thread ziemlich alt ist, und Sie haben es bereits behoben, aber ich habe niemanden gesehen, der diesen Punkt so macht, um zukünftigen Zuschauern zu antworten, die ich antworte.

0

Hatte eine harte Zeit mit dem char * als Kartenschlüssel, wenn ich versuche, Element in mehreren Quelldateien zu finden. Es funktioniert gut, wenn alle innerhalb der gleichen Quelldatei, in die die Elemente eingefügt werden, zugreifen/finden. Wenn ich jedoch versuche, mit find in einer anderen Datei auf das Element zuzugreifen, kann ich das Element, das sich definitiv in der Map befindet, nicht abrufen.

Es stellt sich heraus, der Grund ist wie Plabo wies darauf hin, die Zeiger (jede Kompilierungseinheit hat eine eigene Konstante char *) sind nicht identisch, wenn es in einer anderen cpp-Datei zugegriffen wird.

Verwandte Themen