Sử dụng char * làm khóa trong std :: map


81

Tôi đang cố gắng tìm ra lý do tại sao mã sau không hoạt động và tôi cho rằng đó là sự cố khi sử dụng char * làm loại khóa, tuy nhiên tôi không chắc mình có thể giải quyết nó như thế nào hoặc tại sao nó lại xảy ra. Tất cả các chức năng khác mà tôi sử dụng (trong SDK HL2) đều sử dụng char*nên việc sử dụng std::stringsẽ gây ra nhiều biến chứng không đáng có.

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
Đôi khi làm điều đúng lúc đầu sẽ gây tổn thương. Thay đổi mã của bạn để sử dụng std:stringmột lần và hạnh phúc sau đó.
Björn Pollex

1
những loại biến chứng? có sự chuyển đổi ngầm định từ char * sang std :: string.
10

1
Bạn không được sử dụng char*làm chìa khóa bản đồ. Xem câu trả lời của tôi tại sao.
sbi

Đây dường như là một biến chứng không đáng có do không sử dụng std::string.
Pedro d'Aquino

Tôi không hiểu, để sử dụng khóa nhị phân, Bản đồ có cần phải biết một Khóa là Bằng hay không thay vì biết rằng một khóa có giá trị 'nhỏ hơn' một khóa khác?
CodeMinion

Câu trả lời:


140

Bạn cần cung cấp một hàm so sánh cho bản đồ, nếu không nó sẽ so sánh con trỏ chứ không phải chuỗi kết thúc bằng null mà nó trỏ tới. Nói chung, đây là trường hợp bất cứ lúc nào bạn muốn khóa bản đồ của mình là một con trỏ.

Ví dụ:

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
thực sự anh ấy chỉ có thể vượt qua &std::strcmpnhư các tham số mẫu thứ ba
Armen Tsirunyan

23
Không, strcmptrả về số nguyên dương, 0 hoặc âm. Trình điều khiển bản đồ cần trả về true trên giá trị nhỏ hơn và ngược lại là false.
aschepler

4
@Armen: Tôi không nghĩ nó hoạt động, vì tham số mẫu thứ 3 mong đợi một cái gì đó giống như f(a,b) = a<b, không f(a,b) = (-1 if a<b, 1 if a>b, 0 else).
kennytm

28
oh, xin lỗi, xấu của tôi, không suy nghĩ trước khi đăng. Hãy để bình luận nghỉ ở đó và mang xấu hổ đến tổ tiên của tôi :)
Armen Tsirunyan

2
Như tôi đã thử nghiệm, nó phải hoạt động với một const sau toán tử bool () (char const * a, char const * b), như toán tử bool () (char const * a, char const * b) const {blabla
ethanjyx

45

Bạn không thể sử dụng char*trừ khi bạn hoàn toàn chắc chắn 100% rằng bạn sẽ truy cập bản đồ với các con trỏ giống hệt nhau , không phải chuỗi.

Thí dụ:

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

Nếu bạn truy cập bản đồ với s1bạn sẽ nhận được một vị trí khác với truy cập nó bằng s2.


5
Trừ khi bạn xác định bộ so sánh của riêng mình, như đã giải thích trong câu trả lời được chấp nhận.
Lukas Kalinski

23

Hai chuỗi kiểu C có thể có nội dung bằng nhau nhưng ở các địa chỉ khác nhau. Và đómap so sánh các con trỏ, không phải nội dung.

Chi phí chuyển đổi thành std::map<std::string, int> có thể không nhiều như bạn nghĩ.

Nhưng nếu bạn thực sự cần sử dụng const char*làm khóa bản đồ, hãy thử:

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

Cảm ơn bạn về thông tin. Dựa trên kinh nghiệm của tôi, tôi thực sự khuyên bạn nên chuyển đổi sang std :: string.
user2867288,

8

Bạn có thể làm cho nó hoạt động std::map<const char*, int>, nhưng không được sử dụng các constcon trỏ không phải là con trỏ (lưu ý rằng đã thêm constcho khóa), vì bạn không được thay đổi các chuỗi đó trong khi bản đồ gọi chúng là khóa. (Mặc dù bản đồ bảo vệ các khóa của nó bằng cách tạo ra chúng const, nhưng điều này sẽ chỉ xác nhận con trỏ chứ không phải chuỗi mà nó trỏ tới.)

Nhưng tại sao bạn không sử dụng đơn giản std::map<std::string, int>? Nó hoạt động ra khỏi hộp mà không đau đầu.


8

Bạn đang so sánh bằng cách sử dụng char * với sử dụng một chuỗi. Chúng không giống nhau.

A char *là một con trỏ tới một char. Cuối cùng, nó là một kiểu số nguyên có giá trị được hiểu là địa chỉ hợp lệ cho a char.

Một chuỗi là một chuỗi.

Vùng chứa hoạt động chính xác, nhưng là vùng chứa cho các cặp trong đó khóa là a char *và giá trị là a int.


1
Không có yêu cầu đối với một con trỏ là một số nguyên dài. Có những nền tảng (chẳng hạn như win64, nếu bạn đã từng nghe về nó :-)) trong đó một số nguyên dài nhỏ hơn một con trỏ và tôi tin rằng cũng có nhiều nền tảng khó hiểu hơn, nơi con trỏ và số nguyên được tải vào các thanh ghi khác nhau và được xử lý khác nhau trong những cách khác. C ++ chỉ yêu cầu con trỏ có thể chuyển đổi thành một số kiểu tích phân và quay lại; lưu ý điều này không ngụ ý rằng bạn có thể truyền bất kỳ số nguyên đủ nhỏ nào đến con trỏ, chỉ những số nguyên bạn nhận được từ việc chuyển đổi một con trỏ.
Christopher Creutzig

@ChristopherCreutzig, cảm ơn bạn đã nhận xét. Tôi đã chỉnh sửa câu trả lời của mình cho phù hợp.
Daniel Daranas

2

Như những người khác nói, bạn có thể nên sử dụng std :: string thay vì char * trong trường hợp này mặc dù không có gì sai về nguyên tắc với một con trỏ làm khóa nếu đó là thứ thực sự cần thiết.

Tôi nghĩ một lý do khác khiến mã này không hoạt động là vì khi bạn tìm thấy một mục có sẵn trong bản đồ, bạn cố gắng lắp lại nó vào bản đồ bằng cùng một khóa (ký tự *). Vì khóa đó đã tồn tại trong bản đồ của bạn nên việc chèn sẽ không thành công. Tiêu chuẩn cho map :: insert () xác định hành vi này ... nếu giá trị khóa tồn tại thì chèn không thành công và giá trị được ánh xạ vẫn không thay đổi. Sau đó, nó vẫn bị xóa. Trước tiên, bạn cần xóa nó rồi lắp lại.

Ngay cả khi bạn thay đổi char * thành chuỗi std :: thì vấn đề này sẽ vẫn còn.

Tôi biết chủ đề này đã khá cũ và bây giờ bạn đã sửa nó nhưng tôi không thấy ai nói ra vấn đề này nên vì lợi ích của những người xem trong tương lai, tôi đang trả lời.


0

Đã gặp khó khăn khi sử dụng char * làm khóa bản đồ khi tôi cố gắng tìm phần tử trong nhiều tệp nguồn. Nó hoạt động tốt khi tất cả việc truy cập / tìm kiếm trong cùng một tệp nguồn nơi các phần tử được chèn vào. Tuy nhiên, khi tôi cố gắng truy cập phần tử bằng cách sử dụng tìm kiếm trong một tệp khác, tôi không thể lấy phần tử chắc chắn nằm trong bản đồ.

Hóa ra lý do là như Plabo đã chỉ ra, các con trỏ (mỗi đơn vị biên dịch đều có hằng số char * của riêng nó) KHÔNG giống nhau chút nào khi nó được truy cập trong một tệp cpp khác.


-5

Không có vấn đề để sử dụng bất kỳ loại quan trọng miễn là nó hỗ trợ so sánh ( <, >,== ) và chuyển nhượng.

Một điểm cần được đề cập - hãy lưu ý rằng bạn đang sử dụng một lớp mẫu . Kết quả là trình biên dịch sẽ tạo ra hai cách khởi tạo khác nhau cho char*int*. Trong khi thực tế của cả hai sẽ hầu như giống hệt nhau.

Do đó - tôi sẽ cân nhắc việc sử dụng một void*loại khóa và sau đó truyền khi cần thiết. Đây là ý kiến ​​của tôi.


8
Họ chính chỉ cần hỗ trợ <. Nhưng nó cần phải thực hiện điều đó theo cách hữu ích. Nó không phải với con trỏ. Các trình biên dịch hiện đại sẽ gấp các phiên bản mẫu giống hệt nhau. (Tôi biết chắc chắn VC làm điều này.) Tôi sẽ không bao giờ sử dụng void*, trừ khi đo lường cho thấy điều này để giải quyết rất nhiều vấn đề. Việc từ bỏ kiểu an toàn không nên được thực hiện sớm.
sbi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.