2016-11-15 14 views
0

Für meine Hausaufgaben müsste ich ein Array von Strings dynamisch mit malloc erstellen. A Ich war nicht wirklich in der Lage, bereits verfügbare Lösungen zu verstehen, da es eine etwas andere Art hatte, es zu benutzen als ich meine.C - Ein String-Array dynamisch erstellen (malloc)

MEIN PROBLEM: Kurz gesagt: Ich mache ein textbasiertes Abenteuerspiel. Das Inventar sollte "beliebig groß sein", sagte mein Professor. Wenn die Person keine Gegenstände hat, dann sollte die Größe des Inventars ebenfalls Null sein, mit 1 Gegenstand sollte es nur 1 Gegenstand, usw.-etc.

Ich habe versucht, etwas zu arbeiten, aber ich bin nicht wirklich fähig zu verstehen, wie malloc tatsächlich funktioniert, also hier ist mein Code (der offensichtlich falsch ist, aber wie wäre es richtig zu tun, was ich tun soll ?):

char* inventory; 
int amount=0; 
inventory=(char*) malloc(sizeof(char)*amount); 
//NOW THERE SHOULD BE AN INVENTORY WITH SIZE ZERO SINCE AMOUNT=0 
//NOW I WANT TO GIVE THE PLAYER AN ITEM: 
amount++; 
inventory=(char*) malloc(sizeof(char)*(amount+1); 
inventory[0]="sword"; 
//I WANT TO WRITE OUT INVENTORY TO TEST IF IT WORKS: 
printf("%s", inventory[0]); 
//FREE THE BITS LOCKED WITH MALLOC: 
free(inventory); 

Professor lehrt uns, dass wir Menge + 1 wegen des letzten Zeichens einer Zeichenkette sein muss ‚\ 0‘ oder so etwas zu schreiben haben.

So, hier ist, wie ich verstehe, wie malloc Werke (aber vielleicht ist dies nicht, wie es funktioniert, und ich verstehe es falsch): Normalerweise ist dies, wie würden Sie ein Array von Strings zum Beispiel erstellen:

char strings[10][200]; 

Das würde bedeuten, dass Sie 10 Zeichenfolgen haben, jede kann 200 Zeichen lang sein. Wenn ich malloc wie in meinem Code verwende: Die Zahl 'Betrag' ist im Grunde die gleiche wie die Zahl 10 in meinem Beispiel, und die Größe von (char) ist im Grunde die gleiche wie die Zahl 200 in meinem Beispiel, oder? Wenn nicht, dann bin ich total verloren.

Noch funktioniert mein Code offensichtlich nicht, also würde ich wirklich einige Hilfe von Ihnen schätzen, mit dem Arbeiten von malloc C-Codes und einigen Erklärungen, wenn Sie die Zeit natürlich haben.

+0

'mit Arbeits malloc C codes'..nopes, keine Chance. :) –

+0

In C werden Strings unbekannter/dynamischer Länge durch 'char *' Zeiger auf ihr erstes Zeichen dargestellt. Dieser Zeiger zeigt auf den Anfang des Speichers, der von dieser Zeichenfolge zugewiesen wurde (+ trailing \ 0). Um eine Anzahl von dynamischen Strings zu verwalten, benötigen Sie eine Anzahl von Zeichen, z. ein Array dieser Zeiger. Ein Array von Zeigern ist wiederum etwas Speicherplatz, der durch einen Zeiger auf sein erstes Element repräsentiert wird. Wenn Sie also ein Array von Strings benötigen, könnte das Array als 'char **' deklariert werden, da es auf den ersten Zeiger ('char *') auf einen String zeigt. – JimmyB

+2

Wenn ich die genaue Frage google, sind die ersten 8 Ergebnisse alle Stackoverflow-Fragen zu diesem Thema. Wenn Sie bereits nach Lösungen gesucht haben und keine genaue Antwort gefunden haben, prüfen Sie diese Fragen, damit Sie das Thema verstehen (machen Sie ein Array von Strings dynamisch mit malloc) und entwickeln Sie Ihre eigene Lösung. –

Antwort

0

Es ist mir nicht klar, was Sie tun wollen, aber lassen Sie uns Ihren Code analysieren:

char* inventory; 
int amount=0; 
inventory=(char*) malloc(sizeof(char)*amount); 

Sie haben gerade Null Byte Speicher zugewiesen, da amount Null ist. Die Dokumentation sagt "Wenn Größe 0 ist, reserviert malloc ein Null-Länge-Element im Heap und gibt einen gültigen Zeiger auf das Element" zurück, so dass Sie einen gültigen Zeiger, aber keinen Speicher haben.

amount++; 
inventory=(char*) malloc(sizeof(char)*(amount+1); 

Sie haben nun 2 Byte Speicherplatz zugewiesen. Sie haben jedoch den Speicher verloren, den Sie gerade zugewiesen hatten, obwohl er null Byte ist, da Sie das Ergebnis des zweiten Aufrufs malloc der Variablen zugewiesen haben, die das Ergebnis des ersten Aufrufs zu malloc enthält. Sie sollten free d vorher haben.

inventory[0]="sword"; 

Dies ist ungültig, weil inventory ein char * ist, so ist inventory[0] ein einziges char. Ihr Compiler sollte Sie gewarnt haben, also schalten Sie Warnungen ein.

printf("%s", inventory[0]); 

Das Ergebnis dieser printf nicht definiert ist, weil Sie eine Zeichenfolge gedruckt werden wollen, aber Sie passieren nur ein einzelnes Zeichen.

free(inventory); 

Das ist gut, obwohl Sie die erste Zuweisung verloren haben (siehe oben).

Von dieser Analyse hoffe ich, dass Sie Ihren Code ändern können, um zu tun, was Sie wollen.

+0

" Sie haben einen Zeiger auf eine konstante Zeichenkette für Inventar [0] zugewiesen zwei Bytes groß "Nein, das ist absolut nicht was passiert. 'inventory [0]' * ist kein Zeiger *, es ist ein 'char'. Diese Zuordnung ist einfach ungültig und sollte eine Diagnose auslösen. Das gleiche gilt für die Verwendung von 'inventory [0] 'in einem printf –

+0

@ n.m., Ich sah meinen Fehler und behob ihn. Danke trotzdem. –

0

Sie scheinen ein Array von Strings (dh ein Array von Arrays von char s) zu wollen, aber Sie haben einen Zeiger auf eine char. In C sind Zeiger und Arrays oft austauschbar (obwohl jeder, der sagt, dass sie dasselbe sind, C nicht benutzt hat), können Sie sich das vorstellen, weil Sie mit einem Zeiger arithmetisch auf benachbarte Daten zugreifen können und Arrays nur gespeichert werden als benachbarte Daten. Sie benötigen also einen Zeiger auf Zeiger auf Zeichen oder char **.

Sie müssen genau darüber nachdenken, wie Ihre Daten im Speicher gespeichert werden. In diesem Fall benötigen Sie eine Inventarliste, die ein Array von Zeigern enthält (Zeiger haben eine feste Länge, das ist also in Ordnung). Jeder Zeiger ist ein Zeiger auf die tatsächliche Zeichenfolge, die dieses Element in der Bestandsliste enthält.

Zweitens, das Inventar selbst muss malloc 'd sein, aber wenn alle Zeichenfolgen, die Sie darin speichern möchten, zur Kompilierzeit festgelegt werden, müssen die Zeichenfolgen selbst nicht sein. Wenn das für Sie verwirrend ist, denken Sie daran - wenn Sie das Programm ausführen, muss es irgendwo in den Speicher geladen werden. So werden Zeichenfolgen, die in das Programm kompiliert wurden, irgendwo im Speicher existieren, und C wird Ihnen erlauben, einen Zeiger auf diese Zeichenfolge im Speicher zu nehmen, genau wie Sie einen Zeiger auf Variablen im Speicher nehmen können. Jedes Mal, wenn Sie Ihrem Inventar eine Zeichenfolge hinzufügen müssen, müssen Sie zuerst die Größe Ihres Inventars ändern, indem Sie dessen Größe erhöhen und ein neues Inventar mit der neuen Größe erstellen. Verwenden Sie dazu memcpy oder Ähnliches, um den vorhandenen Inhalt des Inventars zu kopieren die neue Kopie, und dann Aufruf free auf der alten Kopie (sehr wichtig, sonst erhalten Sie ein Speicherleck, die schwer zu erkennen sind, aber bedeuten, dass, wenn Ihr Programm lang genug dauert es schließlich Ihren gesamten RAM essen), bevor schließlich überschreiben Ihr Zeiger auf die alte Kopie mit dem Zeiger auf die neue Kopie.

Dann möchten Sie die Zeichenfolge in die Bestandsliste einfügen, indem Sie etwas wie inventory[amount-1] = "some string"; tun (natürlich könnte die Zeichenfolge auch in eine Funktion als Argument übergeben werden, in diesem Fall sollte es eine Variable vom Typ char * sein).

Wenn Ihre Zeichenfolgen nicht alle zur Kompilierzeit festgelegt sind (dh wenn Sie den Namen eines Benutzers in das Inventar eingeben möchten, das er in die Befehlszeile eingibt oder wenn Sie eine Nummer wie "Ding 1" haben möchten) "Ding 2" etc., das in einer Schleife oder etwas erzeugt wird, dann müssen Sie auch malloc Platz für die Zeichenfolge.

Es tut uns leid, wenn das verwirrend ist, aber das ist wirklich die Art von Dingen, die Sie verstehen müssen, um jede Art von Fähigkeiten in C zu beanspruchen. Dies ist eine sehr kurze Erklärung eines ziemlich komplexen Themas Wenn Sie es nicht verstehen, müssen Sie ernsthaft mehr Zeit mit grundlegenderen Trainingsmaterialien verbringen, um zu versuchen, es zu lernen. Ich werde dir deshalb nicht den Code geben (und auch, weil es länger dauern würde, als es mir wert ist zu schreiben). Sie müssen ein grundlegendes Verständnis davon haben, wie Zeiger funktionieren und wie sie mit Arrays in C zusammenhängen, und zumindest ein vages Verständnis davon haben, wie diese Art von Dingen im Speicher gespeichert wird (einschließlich des Unterschieds zwischen dem Stack und dem Heap) in der Lage sein, dieses Problem richtig zu lösen. Viel Glück!

+0

Ich möchte Ihnen für Ihre lange und erklärende Antwort danken.Ich werde mir jetzt Zeit nehmen, soviel ich brauche, um zu versuchen, es so gut wie möglich zu verstehen. Ich werde Ihnen ein Feedback geben, was ich damit erreichen konnte. Vielen Dank! Definitive Upvote von mir für diese Antwort! – Noxter

0

I kann ein paar Tipps geben:

  • Verwenden structs gruppieren Variablen zusammen (wie Arrays und Array-Länge)
  • Verwenden structs abstract data type zu erstellen (Beispiel: 'Inventar' Datentyp)
  • Erstellen Sie Funktionen für den allgemeinen Betrieb (Inventar initialisieren, Inventar drucken, Bestandskapazität erhöhen, Inventar hinzufügen, Inventar freigeben, ...)
  • Stören Sie (überhaupt) nicht mit String Slices, ropes und 2D-Arrays, es sei denn, absolut notwendig. Ein Array von Zeichenzeigern funktioniert in diesem Fall gut. (Denken Sie nicht, dass Sie dies implementieren wollten, aber ich werde es trotzdem erwähnen)
  • Sie könnten jedoch möglicherweise in Zukunft ein inventory_item struct erstellen, anstatt nur char-Zeiger zu speichern. Auf diese Weise können Sie weitere Artikelattribute wie Anzahl und Qualität hinzufügen. ("You have %d %s, it is %s", item.count, item.name, item.quality)

Refactoring Code:

struct inventory { 
    size_t num_items; // What you call `amount`. 
    char **items; // What you call `inventory`. Note that it's a pointer to a _character pointer_, as the other answers indicated. 
}; 

int main(void) { 
    struct inventory inv; 

    // Initialize the inventory. 
    // You might want to create a function for this. 
    // In C++, you would use constructors. 
    inv.num_items = 0; 
    inv.items = NULL; // Set `items` to NULL, see below at realloc 

    // Once again, you might want to create a function for this, 
    // since you'll probably end up doing this a lot 
    void *old; // Store the old inv.items; in case we fail to allocate a new one. 
    old = inv.items; 
    inv.items = realloc(inv.items, sizeof(*inv.items) * inv.num_items+1); 
    // Explaination: `ptr = realloc(ptr, size)` is the same as `new_ptr = malloc(size); memcpy(ptr, old_ptr, /* size of old_ptr */); free(old_ptr);` 
    // realloc works the same as malloc when it's first argument is NULL. 
    if (!inv.items) { // Failed to allocate memory. 
     free(old); // Deinitialize the inventory. A `goto fail;` would be useful here. 
     return 1; 
    } 
    // Maybe you'd want seperate functions for adding items and growing the array, then call the growth function here. 
    inv.items[inv.num_items] = "sword"; 
    inv.items++; 

    // Print the inventory 
    for (size_t i=0; i < inv.num_items; i++) { 
     printf("inventory[%lu] = %s\n", i, inv.items[i]); // %lu = unsigned long, `size_t` on my system. 
    } 

    // You might again want a function for this 
    free(inv.items); // Deinitialize the inventory 
    // Optional, but cleaner: 
    inv.items = NULL; 
    inv.num_items; 

    return 0; 
} 

Der Nachteil der obigen Ansatz ist, dass Sie nicht Stringliterale und zugewiesen diejenigen mischen können, weil Sie nicht wissen würde, was man ist zu free beim Deinitialisieren. (free in einem String-Literal ist undefiniertes Verhalten).

Sollte in Ordnung sein, wenn alle Ihre Elemente String-Literale sind. Auf diese Weise können Sie auch den Operator == für diese Strings verwenden. Beachten Sie jedoch, dass Sie, wenn Sie jemals den ==-Operator für Strings verwenden können, wahrscheinlich eine Enumeration definieren möchten.

Wie würde ich es schreiben (Wird nicht in der Tiefe erklären):

typedef struct inventory inv_t; 
typedef struct inventory_item inv_item_t; 

struct inventory { 
    size_t num_items; 
    inv_item_t items; 
}; 
struct inventory_item { 
    int type; 
}; 

// Convenience macro for grouping enum data 
#define GENERATE_ITEM_TYPES(XX) \ 
XX(ITEM_NONE, "<unknown item>") \ 
XX(ITEM_SWORD, "sword") 

enum inventory_type { 
#define XX(enumname, ignore) enumname, 
    GENERATE_ITEM_TYPES(XX) 
#undef XX 
    NUM_ITEMS, // Automatically equals 2 
}; 
static char *names[NUM_ITEMS] = { 
#define XX(ignore, strname) strname, 
    GENERATE_ITEM_TYPES(XX) 
#undef XX 
}; 

static int inv_init(inv_t *self); 
static int inv_fini(inv_t *self); 

static int inv_print(inv_t *self); 

static int inv_reserve(inv_t *self, size_t new_size); 

static int inv_add(inv_t *self, inv_type_t type); 

static inv_item_t inv_item_from_type(int type) { 
    inv_item_t item = { .type = type }; 
    return item; 
} 

int main(void) 
{ 
    inv_t inv; 

    if (inv_init(&inv) != 0) goto fail; 

    if (inv_add(&inv, inv_item_from_type(ITEM_SWORD)) != 0) goto fail; 
    if (inv_print(&inv) != 0) goto fail; 

    inv_fini(&inv); 

    return 0; 

fail: 
    inv_fini(&inv); 

    return 1; 
} 

static int inv_init(inv_t *self) 
{ 
    self->num_items = 0; 
    self->items = NULL; 

    return 0; 
} 
static int inv_fini(inv_t *self) 
{ 
    free(self->items); 
    self->items = NULL; 
    self->num_items; 

    return 0; 
} 

static int inv_print(inv_t *self) 
{ 
    for (size_t i=0; i < inv.num_items; i++) { 
     printf("inventory[%llu] = %s\n", (unsigned long long int)i, names[inv.items[i]]); // No format for size_t, so convert to unsigned long long and use format `%llu`. More portable. 
    } 
} 

static int inv_reserve(inv_t *self, size_t new_size) 
{ 
    void *old; 

    old = inv.items; 
    inv.items = realloc(inv.items, sizeof(*inv.items) * new_size); 
    if (!inv.items) goto fail; 

    return 0; 

fail: 
    free(old); 
    return 1; 
} 
static int inv_add(inv_t *self, char *item) 
{ 
    if (inv_reserve(self, inv.num_items + 1) != 0) return 1; 

    inv.items[inv.num_items++] = item; 

    return 0; 
}