Jeg implementerede følgende øvelse:
Implementere en opslagstabel med operationer som
find(struct table*, const char*)
,insert(struct table*, const char*,int)
ogremove(struct table*, const char*)
. Repræsentationen af tabellen kan være en matrix af etstruct
-par eller et par arrays (const char*[]
ogint*
); du vælger, vælg også returtyper til dine 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 er velkommen til at kommentere eventuelle forbedringer. Er der en bedre måde at gøre dette på?
Jeg har også et specifikt spørgsmål: er mine anvendelser af assert
passende?
Kommentarer
Svar
Brug af struct
Personligt tror jeg, du har taget det rigtige valg ved at bruge en struct
med en char*
og int
i stedet for to arrays af char*
og int
. Pair
er en konceptuel dataenhed i din app, så det giver mening at sætte dem sammen. Hvis du havde 2 separate arrays, ville det være let for dem at komme ud af synkronisering og svært at debugge, hvorfor det skete. Dejligt arbejde!
Foretrækker const
og fungerer frem for makroer
Du har defineret arraystørrelsen ved hjælp af en makro. Dette sætter dig dårligt under programmeringen. Du har fjernet typeoplysningerne for værdien. Ved at gøre det:
const int ARR_SIZE = 10;
får du typesikkerhed. Rediger: At “sa C ++ – ism, der ikke gør” t arbejde i C. Min dårlige! Men resten af rådene i næste afsnit er korrekte, så vidt jeg ved.
Med makroer, der tager parametre, risikerer du, at de bruges på uventede måder og forårsager problemer med fejlfinding. Heldigvis har du ikke gjort det her. Men generelt, hvis du finder ud af at nå ud til en makro, så spørg dig selv, om du ville have det bedre med en konstant eller en funktion. Du vil næsten altid være (forudsat at du kan bruge konstanten på den ønskede måde).
Fejl
Der er nogle fejl i din kode. I make_pair()
skal du ikke tjekke for at se hvis malloc()
lykkedes. Hvis det fejler, tildeles ingen hukommelse, og pair
peger på NULL
. Når du prøver at tildele pair->val
eller pair->word
, vil du gå ned.
Hvis du fik ordnet det, table_insert()
bruger resultatet af make_pair()
uden at kontrollere, om det “s NULL
først. Dette vandt” t går ned med det samme, fordi du bare tildeler matrixmarkøren i tbl->pairs[tbl->sz]
for at have værdien pair
. Hvad der sker er senere, når du forsøger at søge eller udskrive eller indsætte et andet emne, du får et nedbrud, når du gentager denne post i tabellen og prøver at gøre noget med det.
Du kan muligvis gøre disse fejl umulige ved ikke at dynamisk tildeling af matrixindgange. Lav simpelthen arrayet til et array med Pair
struct snarere end en markør til dem.
Navngivning
Mange af dine navne er rigtig gode . Pair
og Table
er anstændige, læsbare navne til denne opgave. make_pair()
, table_insert()
osv. er informative. Nogle kunne dog forbedres. tbl
sparer dig ikke meget for at skrive table
. Brug bare hele ordet. Samme med sz
vs. size
. i
er acceptabelt som et loop-variabelnavn, men det ville være endnu bedre, hvis det var mere beskrivende. For eksempel entry_index
eller pair_index
. Den skal beskrive, hvad du gentager.
Kommentarer
- når jeg ændrer definitionen til
const int ARR_SIZE = 10;
det giver mig en fejl istruct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; };
at ARR_SIZE ikke er const, så hvordan skal jeg bruge det i dette tilfælde? - Undskyld – at ‘ sa C ++ – ism. Jeg troede, det var et C ++ spørgsmål. Min dårlige. Jeg ‘ opdaterer mit svar.
- Jeg ‘ er en stærk tilhænger af beskrivende variabelnavne, men med enkelheden af sløjferne i dette program synes jeg variabelnavnet
i
fungerer fint. - Hvis
malloc()
mislykkes, tildelespair->val
ellerpair->word
kan gå ned, hvis du er heldig . Det kan bare fortsætte og give forkerte resultater, eller det kan gøre noget helt uventet. At ‘ er glæden ved udefineret adfærd!
Svar
-
Kast ikke resultatet af
malloc
.malloc
returnerervoid *
, og i C er det gyldigt at konverterevoid *
til enhver markør. Hvis du kaster for at undertrykke en advarsel om heltal til pointerkonvertering, betyder det, at en compiler ikke har enmalloc
prototype, og som standard er dens returnering af enint
(det er en gammel C-konvention). Hvisint
og markøren nu er af forskellig størrelse, ville malloc-returværdien blive afkortet med alle grimme konsekvenser. -
Det anbefales ikke at
sizeof(type)
. Foretrækkersizeof(expression)
. Overvej i dit tilfældestruct Pair * pair = malloc(sizeof(*pair));
-
table_insert
indsættes blindt i en fuld tabel. Den skal teste fortbl->sz < ARR_SIZE
, og returner en fejlindikation, hvis den ikke er det. -
Den aktuelle indsættelse
struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position
skal egentlig være en enkelt linje:
tbl->pairs[tbl->sz] = make_pair(word, val);
Kommentarer
- hvorfor er det ikke en god idé at caste malloc? I c ++ ville det endda få en compiler-fejl?
- @Sa ndro4912 Spørgsmålet er mærket C. C ++ er et andet sprog, især i denne henseende. Hvis C-compileren nu klager, betyder det, at
malloc
prototypen mangler, hvilket kan føre til grimme problemer. - ja jeg ved er c. Jeg blev bare spekuleret på, hvorfor det her ikke er en god praksis at angive typen ændringer med en rollebesætning
- @ Sandro4912 Se redigering.
- @ Sandro4912
pair = malloc(sizeof *pair)
er lettere at kode korrekt første gang, lettere at gennemgå og vedligeholde.
Svar
Aktivér alle advarsler om kompilator
Med en velaktiveret kompilator skulle for (int i = 0; i < tbl->sz; ++i)
have advaret om at blande tegn på i
og tbl->sz
samt rækkevidde. Spar tid, og aktiver alle advarsler, og brug for (size_t i = 0; i < tbl->sz; ++i)
.
Generelt er kode squishy ved at bruge int,size_t
næsten ombytteligt . Jeg ville kun re-arkitekt og bruge size_t
.
Blandet brug af lav og dyb kopi
make_pair(const char* word, int val)
tildeler og danner en helt ny struct Pair
(dyb kopi), men kopierer dog ikke, hvad word
peger på.
Måske
// pair->word = word; pair->word = strdup(word);
Brug const
search_word()
ændrer ikke *tbl
, så brug 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)
Navngivning
Kode bruger const char* word;
alligevel er det ikke et “ord”, men en streng som brugt i strcmp()
.
—– Tilføjelser
Overtrædelse af kontrakt?
Intet i kravene “Implementere en opslagstabel med operationer som …” indikerer at const char*
er en markør til en streng Så at kalde strcmp()
er tvivlsom, medmindre der ikke er angivet krav. Som en char *
kunne kode bruge en simpel sammenligning
// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word)
Brug af assert()
er mine anvendelser af påstand passende?
Hvis char *
markøren, der skal tilføjes / søges, er ikke angivet til at være en streng , assert(word);
er ikke korrekt, da word == NULL
vides ikke at være ugyldig.
assert(return_val)
i table_find(struct Table* tbl, const char* word, int* return_val)
er OK, men alligevel ville jeg redesigne return_val == NULL
if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; }
*
i en markør skriv ved siden af variabelnavnet (f.eks.const char *word
). Syntaks af C-typen er designet til at efterligne brugen, hvor du f.eks. ‘ d skriver*word
for at få adgang til enconst char
element. Bemærk også, at dette gør det mere klart, hvilken typeb
er iint *a, b;
.