Uppslagstabell med fast array

Jag implementerade följande övning:

Implementera en uppslagstabell med operationer som find(struct table*, const char*), insert(struct table*, const char*,int) och remove(struct table*, const char*). Representationen av tabellen kan vara en matris av ett struct -par eller ett par matriser (const char*[] och int*); du väljer, välj också returtyper för dina funktioner.

#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(); } 

Du kan gärna kommentera eventuella förbättringar. Finns det ett bättre sätt att göra detta?

Jag har också en specifik fråga: är min användning av assert lämplig?

Kommentarer

  • Som en stilistisk nitpick rekommenderar jag ’ d * i en pekare typ intill variabelnamnet (t.ex. const char *word). Syntaxen av C-typ utformades för att efterlikna användningen, där du till exempel ’ d typ *word för att komma åt en const char -element. Observera också att detta gör det tydligare vilken typ b är i int *a, b;.

Svara

Med struct

Personligen tror jag att du har gjort rätt val genom att använda en struct med en char* och int i stället för två matriser med char* respektive int. Pair är en konceptuell dataenhet i din app, så det är vettigt att sätta ihop dem. Om du hade två separata matriser, skulle det vara lätt för dem att komma ur synk och svårt att felsöka varför det hände. Trevligt arbete!

Föredrar const och fungerar framför makron

Du har definierat arraystorleken med ett makro. Detta sätter du har en nackdel under programmeringen. Du har tagit bort typinformationen för värdet. Genom att göra det:

const int ARR_SIZE = 10; 

får du typsäkerhet. Redigera: Den ”sa C ++ – ism som inte” t fungerar i C. My bad! Men resten av råden i nästa stycke är korrekta så vitt jag vet.

Med makron som tar parametrar riskerar du att de används på oväntade sätt och orsakar problem att felsöka. Lyckligtvis har du inte gjort det här. Men i allmänhet, om du hittar dig själv efter ett makro, fråga dig själv om du skulle ha det bättre med en konstant eller en funktion. Du kommer nästan alltid att vara (förutsatt att du kan använda konstanten på önskat sätt).

Fel

Det finns några fel i din kod. I make_pair() kontrollerar du inte för att se om malloc() lyckades. Om det misslyckas tilldelas inget minne och pair pekar på NULL. När du försöker tilldela pair->val eller pair->word kommer du att krascha.

Om du fixade det, table_insert() använder resultatet av make_pair() utan att kontrollera om det ”s NULL först. Detta vann” t krascha omedelbart, eftersom du bara tilldelar arraypekaren i tbl->pairs[tbl->sz] för att ha värdet pair. Vad som händer är senare när du försöker söka eller skriva ut eller infoga ett annat objekt, du kommer att krascha när du upprepar den posten i tabellen och försöker göra någonting med det.

Du kan göra dessa fel inte möjliga genom att inte dynamiskt allokera matrisposterna. Gör helt enkelt matrisen till en array med Pair -strängar snarare än en pekare till dem.

Namngivning

Många av dina namn är riktigt bra . Pair och Table är anständiga, läsbara namn för den här uppgiften. make_pair(), table_insert(), etc. är informativa. Vissa kan dock förbättras. tbl sparar inte mycket för att skriva över table. Använd bara hela ordet. Samma med sz vs. size. i är acceptabelt som loop-variabelnamn, men det vore ännu bättre om det var mer beskrivande. Till exempel entry_index eller pair_index. Det ska beskriva vad du itererar över.

Kommentarer

  • när jag ändrar definitionen till const int ARR_SIZE = 10; det ger mig ett fel i struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; att ARR_SIZE inte är const. Så hur ska jag använda det i det här fallet då?
  • Tyvärr – att ’ sa C ++ – ism. Jag tyckte att det här var en C ++ -fråga. Min dåliga. Jag ’ uppdaterar mitt svar.
  • Jag ’ är en stark anhängare av beskrivande variabelnamn, men med enkelhetens öglor i detta program tror jag att variabelnamnet i fungerar bra.
  • Om malloc() misslyckas, tilldelas pair->val eller pair->word kanske kraschar, om du har tur . Det kan bara fortsätta och ge fel resultat, eller så kan det göra något helt oväntat. Att ’ är glädjen av odefinierat beteende!

Svar

  • Kasta inte resultatet av malloc.

    malloc returnerar void *, och i C är det giltigt att konvertera void * till valfri pekare. Om du kastar för att undertrycka en varning om heltal till pekarkonvertering betyder det att en kompilator inte har en malloc prototyp, och standardinställningen är att returnera en int (det är en gammal C-konvention). Om int och pekaren är av olika storlek, skulle malloc-returvärdet trunkeras med alla otäcka konsekvenser.

  • Det rekommenderas inte att sizeof(type). Föredrar sizeof(expression). Överväg

     struct Pair * pair = malloc(sizeof(*pair)); 
  • table_insert infogas blint i en fullständig tabell. Det bör testas för tbl->sz < ARR_SIZE och returnera en felindikering om den inte är det.

  • Den faktiska infogningen

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

    borde egentligen vara en enda rad:

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

Kommentarer

  • varför är det inte en bra idé att casta malloc? I c ++ skulle det till och med få ett kompileringsfel?
  • @Sa ndro4912 Frågan är taggad C. C ++ är ett annat språk, särskilt i detta avseende. Nu om C-kompilatorn klagar betyder det att prototypen malloc saknas, vilket kan leda till otäcka problem.
  • ja jag vet är c. Jag undrade bara varför det här inte är en bra praxis att ange typändringarna med en rollbesättning
  • @ Sandro4912 Se redigera.
  • @ Sandro4912 pair = malloc(sizeof *pair) är lättare att koda korrekt första gången, lättare att granska och underhålla.

Svar

Aktivera alla kompilatorvarningar

Med en väl aktiverad kompilator borde for (int i = 0; i < tbl->sz; ++i) ha varnat för att blanda tecken mellan i och tbl->sz samt intervall. Spara tid och aktivera alla varningar och använd for (size_t i = 0; i < tbl->sz; ++i).

I allmänhet är koden snygg när den använder int,size_t nästan omväxlande . Jag ”skulle omarkitekt och endast använda size_t.

Blandad användning av grunt och djupt exemplar

make_pair(const char* word, int val) tilldelar och bildar en helt ny struct Pair (djup kopia), men kopierar ändå inte det word pekar på.

Kanske

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

Använd const

search_word() ändrar inte *tbl, så använd 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) 

Namngivning

Kod använder const char* word; men det är inte ett ”ord” utan en sträng som används i strcmp().

—– Tillägg

Överträdelse av kontrakt?

Ingenting i kraven ”Implementera en uppslagstabell med operationer som …” indikerar att const char* är en pekare till en sträng . Så att ringa strcmp() kan ifrågasättas såvida det inte finns några icke-angivna krav. Som char * kan kod använda en enkel jämförelse

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

Användning av assert()

är min användning av påståenden lämplig?

Om char * pekaren för att lägga till / sökning är inte angiven som en sträng , assert(word); är inte korrekt eftersom word == NULL är inte känt som ogiltigt.

assert(return_val) i table_find(struct Table* tbl, const char* word, int* return_val) är OK, men ändå skulle jag utforma om så att return_val == NULL

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

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *