Am implementat următorul exercițiu:
Implementați un tabel de căutare cu operații precum
find(struct table*, const char*)
,insert(struct table*, const char*,int)
șiremove(struct table*, const char*)
. Reprezentarea tabelului ar putea fi o matrice a unei perechistruct
sau a unei perechi de tablouri (const char*[]
șiint*
); alegeți, alegeți și tipurile de returnare pentru funcțiile dvs.
#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(); }
Simțiți-vă liber să comentați orice îmbunătățiri. Există o modalitate mai bună de a face acest lucru?
Am și o întrebare specifică: sunt adecvate utilizările mele de assert
?
Comentarii
Răspuns
Utilizarea struct
Personal, cred că ați făcut alegerea corectă utilizând un struct
cu un char*
și int
în acesta, mai degrabă decât 2 tablouri de și respectiv int
. Pair
este o unitate conceptuală de date din aplicația dvs., deci are sens să le puneți împreună. Dacă ați avea 2 tablouri separate, le-ar fi ușor să iasă din sincronizare și să depaneze greu de ce s-a întâmplat asta. Bună treabă!
Prefer const
și funcțiile la macro-uri
Ați definit dimensiunea matricei folosind o macro. vă aflați în dezavantaj în timpul programării. Ați eliminat informațiile de tip pentru valoare. Făcându-l:
const int ARR_SIZE = 10;
veți obține siguranța tipului. Editați: Acel „sa C ++ – ism that doesn” Nu lucrez în C. Rău! Dar restul sfaturilor din paragraful următor sunt corecte din câte știu eu.
Cu macrocomenzile care iau parametri, riscați să fie utilizate în moduri neașteptate și să cauzeze probleme greu de depanat. Din fericire, nu ați făcut asta aici. Dar, în general, dacă vă aflați în căutarea unei macro-uri, întrebați-vă dacă ați fi mai bine cu o constantă sau o funcție. Aproape întotdeauna veți fi (presupunând că puteți utiliza constanta în modul dorit).
Erori
Există unele erori în codul dvs. În make_pair()
, nu verificați pentru a vedea dacă malloc()
a reușit. Dacă eșuează, nu este alocată nici o memorie și pair
indică spre NULL
. Când încercați să atribuiți pair->val
sau pair->word
vă veți bloca.
Dacă ați remediat acest lucru, table_insert()
folosește rezultatul make_pair()
fără a verifica dacă este primul „s NULL
. A câștigat” Nu se blochează imediat, deoarece „doar atribuiți indicatorul matricei în tbl->pairs[tbl->sz]
pentru a avea valoarea pair
. Ce se va întâmpla mai târziu când dacă încercați să căutați sau să imprimați sau să inserați un alt element, veți primi un blocaj atunci când iterați peste acea intrare din tabel și veți încerca să faceți orice cu el.
Ați putea face aceste erori imposibile dacă nu alocarea dinamică a intrărilor matricei. Pur și simplu faceți din matrice o serie de Pair
structuri, mai degrabă decât un indicator către ele.
Denumirea
Multe dintre numele dvs. sunt foarte bune . Pair
și Table
sunt nume decente, lizibile pentru această sarcină. make_pair()
, table_insert()
etc. sunt informative. Unele ar putea fi îmbunătățite, totuși. tbl
nu vă economisește prea mult tastând peste table
. Folosește doar cuvântul întreg. La fel cu sz
vs. size
. i
este acceptabil ca nume de variabilă de buclă, dar ar fi și mai bine dacă ar fi mai descriptiv. De exemplu, entry_index
sau pair_index
. Ar trebui să descrie ceea ce „reiterați.
Comentarii
- când schimb definiția în
const int ARR_SIZE = 10;
îmi dă o eroare înstruct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; };
că ARR_SIZE nu este const, deci cum să-l folosim în acest caz atunci? - Ne pare rău – că ‘ sa C ++ – ism. Am crezut că este o întrebare C ++. Rău. Mă ‘ îmi voi actualiza răspunsul.
- Eu ‘ sunt un susținător ferm al numelor de variabile descriptive, dar cu simplitatea buclelor din acest program cred că numele variabilei
i
funcționează foarte bine. - Dacă
malloc()
eșuează, atribuirea cătrepair->val
saupair->word
s-ar putea bloca, dacă aveți noroc . S-ar putea să continue și să dea rezultate greșite sau poate face ceva total neașteptat. Că ‘ este bucuria comportamentului nedefinit!
Răspuns
-
Nu aruncați rezultatul
malloc
.malloc
returneazăvoid *
, iar în C este valid să se converteascăvoid *
la orice pointer. Dacă trimiteți pentru a suprima un avertisment despre conversia numărului întreg la pointer, înseamnă că un compilator nu are un prototipmalloc
și implicit este pentru a returna unint
(este o convenție antică C). Acum, dacăint
și indicatorul sunt de dimensiuni diferite, valoarea de returnare malloc ar fi trunchiată, cu toate consecințele urâte. -
Nu este recomandat să
sizeof(type)
. Preferațisizeof(expression)
. În cazul dvs., luați în considerarestruct Pair * pair = malloc(sizeof(*pair));
-
table_insert
inserează orbește într-un tabel complet. Ar trebui să testeze pentrutbl->sz < ARR_SIZE
și returnează o indicație de eroare dacă nu este. -
Inserarea efectivă
struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position
ar trebui să fie într-adevăr o singură linie:
tbl->pairs[tbl->sz] = make_pair(word, val);
Comentarii
- de ce nu este o idee bună să aruncați malloc? În c ++ ar primi chiar o eroare de compilator?
- @Sa ndro4912 Întrebarea este etichetată C. C ++ este un limbaj diferit, în special în acest sens. Acum, dacă compilatorul C se plânge, înseamnă că lipsește prototipul
malloc
, ceea ce poate duce la probleme urâtoare. - da, știu că este c. M-am întrebat de ce aici nu este o practică bună să indic schimbările de tip cu o distribuție
- @ Sandro4912 Vezi editarea.
- @ Sandro4912
pair = malloc(sizeof *pair)
este mai ușor de codat corect prima dată, mai ușor de examinat și de întreținut.
Răspunde
Activează toate avertismente ale compilatorului
Cu un compilator bine activat, for (int i = 0; i < tbl->sz; ++i)
ar fi trebuit să avertizeze cu privire la amestecarea semnelor i
și tbl->sz
, precum și intervalul. Economisiți timp și activați toate avertismentele și utilizați for (size_t i = 0; i < tbl->sz; ++i)
.
În general, codul este șmecher când utilizați int,size_t
aproape interschimbabil . „Ar fi arhitectat și aș folosi numai size_t
.
Utilizarea mixtă a copiei superficiale și profunde
make_pair(const char* word, int val)
alocă și formează un struct Pair
complet nou (copie profundă), dar nu copiază la ce indică word
.
Poate
// pair->word = word; pair->word = strdup(word);
Utilizați const
search_word()
nu modifică *tbl
, deci utilizați 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)
Denumire
Cod folosește const char* word;
totuși nu este un „cuvânt”, ci un șir așa cum este utilizat în strcmp()
.
—– Adăugări
Încălcarea contractului?
Nimic din cerințele „Implementarea unui tabel de căutare cu operațiuni precum …” nu indică faptul că const char*
este un pointer către un șir . Prin urmare, apelarea strcmp()
este discutabil dacă nu există cerințe nedeclarate. Ca char *
, codul ar putea utiliza o comparație simplă
// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word)
Utilizarea assert()
sunt adecvate utilizările mele de afirmare?
Dacă indicatorul char *
de adăugat / căutat nu este specificat ca șir , assert(word);
nu este corect ca word == NULL
nu este cunoscut ca fiind nevalid.
assert(return_val)
din table_find(struct Table* tbl, const char* word, int* return_val)
este în regulă, totuși aș reproiecta pentru a permite return_val == NULL
if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; }
*
într-un pointer tastați să fie adiacent cu numele variabilei (de ex.const char *word
). Sintaxa de tip C a fost concepută pentru a imita utilizarea, de exemplu ‘ d tip*word
pentru a accesa unconst char
element. Rețineți, de asemenea, că acest lucru face mai clar ce tipb
este înint *a, b;
.