Nachschlagetabelle mit festem Array

Ich habe die folgende Übung implementiert:

Implementieren Sie a Nachschlagetabelle mit Operationen wie find(struct table*, const char*), insert(struct table*, const char*,int) und remove(struct table*, const char*). Die Darstellung der Tabelle kann ein Array eines struct -Paares oder eines Arrayspaares (const char*[] und ); Sie wählen auch Rückgabetypen für Ihre Funktionen.

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define ARR_SIZE 10 struct Pair { const char* word; int val; }; struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; struct Pair* make_pair(const char* word, int val) { assert(word); struct Pair* pair = (struct Pair*)malloc(sizeof(struct Pair)); pair->val = val; pair->word = word; return pair; } void table_empty(struct Table* tbl) { assert(tbl); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { free(tbl->pairs[i]); tbl->pairs[i] = NULL; } tbl->sz = 0; } int search_word(struct Table* tbl, const char* word) { assert(tbl); assert(word); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { //printf("%s %i\n", tbl->pairs[i]->word,i); if (strcmp(tbl->pairs[i]->word, word) == 0) { return i; } } return -1; // error } void table_insert(struct Table* tbl, const char* word, int val) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i != -1) { // replace val tbl->pairs[i]->val = val; } else { // add new pair struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position ++tbl->sz; } } int table_find(struct Table* tbl, const char* word, int* return_val) { assert(tbl); assert(word); assert(return_val); int i = search_word(tbl, word); if (i != -1) { *return_val = tbl->pairs[i]->val; return 0; } return -1; // error not found } int table_remove(struct Table* tbl, const char* word) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i == -1) return -1; free(tbl->pairs[i]); // free value at current pos tbl->pairs[i] = tbl->pairs[tbl->sz - 1]; // put address of last word at the pos of the current --tbl->sz; // "erase" last word return 0; } void table_print(struct Table* tbl) { assert(tbl); printf("\n"); printf("table size = %i\n", tbl->sz); for (int i = 0; i < tbl->sz; ++i) printf("%s %i\n", tbl->pairs[i]->word, tbl->pairs[i]->val); fflush(stdout); } void print_search_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); int val = 0; if (table_find(tbl, word, &val) == 0) printf("%s %i\n",word, val); else printf("%s not found in table\n", word); printf("\n"); fflush(stdout); } void print_remove_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); if (table_remove(tbl, word) == -1) printf("%s not deleted\n", word); else printf("%s deleted\n", word); printf("\n"); fflush(stdout); } int main() { struct Table table = { 0 }; int val = 0; table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); print_search_result(&table, "Hello"); print_search_result(&table, "Test"); print_search_result(&table, "keyword"); print_remove_result(&table, "Hello"); print_remove_result(&table, "Hello"); // double delete == false print_remove_result(&table, "What"); print_remove_result(&table, "going"); print_remove_result(&table, "is"); print_remove_result(&table, "on"); print_remove_result(&table, "Test"); table_print(&table); table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); table_empty(&table); table_print(&table); getchar(); } 

Sie können sich jederzeit zu Verbesserungen äußern. Gibt es einen besseren Weg, dies zu tun?

Ich habe auch eine spezielle Frage: Sind meine Verwendungen von assert angemessen?

Kommentare

  • Als stilistischen Trottel würde ich ‚ die * in einem Zeiger empfehlen Geben Sie neben dem Variablennamen ein (z. B. const char *word). Die Syntax vom Typ C wurde entwickelt, um die Verwendung nachzuahmen, wobei Sie beispielsweise ‚ d *word eingeben, um auf eine Element. Beachten Sie auch, dass dadurch klarer wird, welcher Typ b in int *a, b; ist.

Antwort

Verwenden von struct

Ich persönlich denke, Sie haben mit a die richtige Wahl getroffen struct mit einem char* und int anstelle von 2 Arrays von char* bzw. int. Die Pair ist eine konzeptionelle Dateneinheit in Ihrer App. Daher ist es sinnvoll, sie zusammenzustellen. Wenn Sie zwei separate Arrays hätten, wäre es für sie leicht, nicht mehr synchron zu sein und schwer zu debuggen, warum dies passiert ist. Gute Arbeit!

Bevorzugen Sie const und Funktionen gegenüber Makros

Sie haben die Arraygröße mit einem Makro definiert. Dies setzt Sie sind beim Programmieren im Nachteil. Sie haben die Typinformationen für den Wert entfernt. Wenn Sie Folgendes festlegen:

const int ARR_SIZE = 10; 

erhalten Sie Typensicherheit. Bearbeiten: Dieser „sa C ++ – Ismus, der dies nicht tut“ Ich arbeite nicht in C. Mein schlechtes! Der Rest der Ratschläge im nächsten Absatz ist meines Wissens korrekt.

Bei Makros, die Parameter verwenden, besteht die Gefahr, dass sie auf unerwartete Weise verwendet werden und schwer zu debuggende Probleme verursachen. Zum Glück haben Sie das hier nicht getan. Wenn Sie jedoch nach einem Makro greifen, fragen Sie sich im Allgemeinen, ob Sie mit einer Konstante oder einer Funktion besser dran wären. Sie werden es fast immer sein (vorausgesetzt, Sie können die Konstante verwenden auf die gewünschte Weise).

Fehler

Ihr Code enthält einige Fehler. In make_pair() überprüfen Sie nicht, ob dies angezeigt wird wenn malloc() erfolgreich war. Wenn dies fehlschlägt, wird kein Speicher zugewiesen und pair zeigt auf NULL. Wenn Sie versuchen, pair->val oder pair->word zuzuweisen, stürzen Sie ab.

Wenn Sie dies behoben haben, table_insert() verwendet das Ergebnis von make_pair(), ohne zu überprüfen, ob es zuerst NULL ist. t stürzt sofort ab, da Sie „nur den Array-Zeiger in tbl->pairs[tbl->sz] zuweisen, um den Wert pair zu erhalten. Was passiert, ist später, wenn Wenn Sie versuchen, ein anderes Element zu suchen, zu drucken oder einzufügen, kommt es zu einem Absturz, wenn Sie diesen Eintrag in der Tabelle durchlaufen und versuchen, etwas damit zu tun.

Sie könnten diese Fehler nicht möglich machen, indem Sie nicht Dynamisches Zuweisen der Array-Einträge. Machen Sie das Array einfach zu einem Array von Pair -Strukturen und nicht zu einem Zeiger auf diese.

Benennung

Viele Ihrer Namen sind wirklich gut . Pair und Table sind anständige, lesbare Namen für diese Aufgabe. make_pair(), table_insert() usw. sind informativ. Einige könnten jedoch verbessert werden. tbl erspart Ihnen nicht viel Eingabe über table. Verwenden Sie einfach das ganze Wort. Gleiches gilt für sz vs. size. i ist als Name einer Schleifenvariablen akzeptabel, aber es wäre noch besser, wenn es aussagekräftiger wäre. Beispiel: entry_index oder pair_index. Es sollte beschreiben, worüber Sie „iterieren“.

Kommentare

  • , wenn ich die Definition in const int ARR_SIZE = 10; Es gibt mir einen Fehler in struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; };, dass ARR_SIZE nicht const ist. Wie wird es dann in diesem Fall verwendet?
  • Entschuldigung – dass ‚ sa C ++ – ism. Ich dachte, dies sei eine C ++ – Frage. Meine schlechte. Ich ‚ werde meine Antwort aktualisieren.
  • Ich ‚ bin ein überzeugter Unterstützer beschreibender Variablennamen, aber aufgrund der Einfachheit der Schleifen in diesem Programm denke ich, dass der Variablenname i funktioniert einwandfrei.
  • Wenn malloc() fehlschlägt, wird pair->val oder pair->word könnte abstürzen, wenn Sie Glück haben . Es kann einfach weitergehen und falsche Ergebnisse liefern, oder es kann etwas völlig Unerwartetes bewirken. Das ‚ ist die Freude an undefiniertem Verhalten!

Antwort

  • Das Ergebnis von malloc wird nicht umgewandelt.

    malloc gibt void *, und in C ist es gültig, void * in einen beliebigen Zeiger zu konvertieren. Wenn Sie eine Warnung zur Konvertierung von Ganzzahlen in Zeiger unterdrücken, bedeutet dies, dass ein Compiler keinen malloc -Prototyp hat und standardmäßig einen (es ist eine alte C-Konvention). Wenn nun int und der Zeiger unterschiedlich groß sind, wird der Malloc-Rückgabewert abgeschnitten, mit allen bösen Konsequenzen.

  • Es wird nicht empfohlen, sizeof(type) zu verwenden. Bevorzugen Sie sizeof(expression). In Ihrem Fall sollten Sie

     struct Pair * pair = malloc(sizeof(*pair)); 

  • table_insert fügt blind in eine vollständige Tabelle ein. Es sollte auf tbl->sz < ARR_SIZE und geben eine Fehleranzeige zurück, wenn dies nicht der Fall ist.

  • Die tatsächliche Einfügung

     struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position 

    sollte eigentlich eine einzelne Zeile sein:

     tbl->pairs[tbl->sz] = make_pair(word, val); 

Kommentare

  • Warum ist es keine gute Idee, Malloc zu besetzen? In C ++ würde es sogar einen Compilerfehler geben?
  • @Sa ndro4912 Die Frage ist mit C gekennzeichnet. C ++ ist eine andere Sprache, insbesondere in dieser Hinsicht. Wenn sich der C-Compiler beschwert, bedeutet dies, dass der Prototyp malloc fehlt, was zu bösen Problemen führen kann.
  • Ja, ich weiß, ist c. Ich habe mich nur gefragt, warum es hier nicht empfehlenswert ist, die Typänderungen mit einer Besetzung anzugeben.
  • @ Sandro4912 Siehe Bearbeiten.
  • @ Sandro4912 pair = malloc(sizeof *pair) ist beim ersten Mal einfacher korrekt zu codieren, einfacher zu überprüfen und zu warten.

Antwort

Alle aktivieren Compiler-Warnungen

Bei einem gut aktivierten Compiler sollte for (int i = 0; i < tbl->sz; ++i) vor dem Mischen der Vorzeichen von i und tbl->sz sowie Bereich. Sparen Sie Zeit und aktivieren Sie alle Warnungen und verwenden Sie for (size_t i = 0; i < tbl->sz; ++i).

Im Allgemeinen ist der Code bei der Verwendung von int,size_t nahezu austauschbar . Ich würde nur size_t neu entwerfen und verwenden.

Gemischte Verwendung von flacher und tiefer Kopie

make_pair(const char* word, int val) weist eine ganz neue struct Pair (tiefe Kopie) zu und bildet sie, kopiert jedoch nicht, worauf word zeigt.

Vielleicht

// pair->word = word; pair->word = strdup(word); 

Verwenden Sie const

search_word() ändert *tbl nicht. Verwenden Sie daher const to convey that. Same for table_find () , table_print () , print_search_result () `.

// int search_word(struct Table* tbl, const char* word) int search_word(const struct Table* tbl, const char* word) 

Benennung

Code verwendet const char* word;, ist jedoch kein „Wort“, sondern eine Zeichenfolge , wie sie in strcmp() verwendet wird.

—– Ergänzungen

Vertragsverletzung?

Nichts in den Anforderungen „Implementierung einer Nachschlagetabelle mit Operationen wie …“ gibt an, dass const char* ist ein Zeiger auf eine Zeichenfolge . Rufen Sie also strcmp() ist fraglich, sofern keine nicht angegebenen Anforderungen vorliegen. Als char * könnte Code einen einfachen Vergleich verwenden.

// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word) 

Verwendung von assert()

Sind meine Verwendungszwecke angemessen?

Wenn Der Zeiger char * zum Hinzufügen / Suchen ist nicht als Zeichenfolge angegeben. assert(word); ist nicht als word == NULL ist nicht als ungültig bekannt.

Die assert(return_val) in table_find(struct Table* tbl, const char* word, int* return_val) ist in Ordnung, aber ich würde neu entwerfen, um return_val == NULL

if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; } 

zuzulassen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.