Jag implementerade följande övning:
Implementera en uppslagstabell med operationer som
find(struct table*, const char*)
,insert(struct table*, const char*,int)
ochremove(struct table*, const char*)
. Representationen av tabellen kan vara en matris av ettstruct
-par eller ett par matriser (const char*[]
ochint*
); 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
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 istruct 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, tilldelaspair->val
ellerpair->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
returnerarvoid *
, och i C är det giltigt att konverteravoid *
till valfri pekare. Om du kastar för att undertrycka en varning om heltal till pekarkonvertering betyder det att en kompilator inte har enmalloc
prototyp, och standardinställningen är att returnera enint
(det är en gammal C-konvention). Omint
och pekaren är av olika storlek, skulle malloc-returvärdet trunkeras med alla otäcka konsekvenser. -
Det rekommenderas inte att
sizeof(type)
. Föredrarsizeof(expression)
. Övervägstruct Pair * pair = malloc(sizeof(*pair));
-
table_insert
infogas blint i en fullständig tabell. Det bör testas förtbl->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; }
*
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 enconst char
-element. Observera också att detta gör det tydligare vilken typb
är iint *a, b;
.