Toteutin seuraavan harjoituksen:
Toteuta a hakutaulukko, jossa on toimintoja, kuten
find(struct table*, const char*)
,insert(struct table*, const char*,int)
jaremove(struct table*, const char*)
. Taulukon esitys voi ollastruct
-parin tai matriisiparin taulukko (const char*[]
jaint*
); valitset, valitse myös palautustyypit toiminnoillesi.
#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(); }
Kommentoi parannuksia. Onko olemassa parempi tapa tehdä tämä?
Minulla on myös yksi erityinen kysymys: sopivatko assert
-tapani käyttämiseen?
Kommentit
Vastaa
Käyttämällä struct
Henkilökohtaisesti luulen, että teit oikean valinnan käyttämällä struct
, jossa on char*
ja int
kuin kaksi char*
ja int
. Pair
on käsitteellinen tietoyksikkö sovelluksessasi, joten on järkevää koota ne yhteen. Jos sinulla olisi 2 erillistä taulukkoa, heidän olisi helppo päästä pois synkronoinnista ja vaikea selvittää miksi niin tapahtui. Hyvää työtä!
Mieluummin const
ja toiminnot makroille
Olet määrittänyt matriisin koon makrolla. Tämä asettaa olet epäedullisessa asemassa ohjelmoinnin aikana. Olet poistanut arvon tyyppitiedot. Tekemällä sen:
const int ARR_SIZE = 10;
saat tyyppiturvallisuuden. Muokkaa: Se ”sa C ++ – ism, joka ei tee” ei toimi C. Mutta loput seuraavassa kappaleessa annetuista neuvoista ovat oikein, sikäli kuin tiedän.
Parametreja käyttävillä makroilla on vaarana, että niitä käytetään odottamattomilla tavoilla ja aiheuttaen vaikeita virheenkorjausongelmia. Onneksi et ole tehnyt niin täällä. Mutta yleensä, jos huomaat etsivän makroa, kysy itseltäsi, olisiko sinulle parempi, jos sinulla olisi vakio vai funktio. Olet melkein aina (olettaen, että voit käyttää vakiota halutulla tavalla).
Virheet
Koodissasi on joitain virheitä. Kohdassa make_pair()
et tarkista jos malloc()
onnistui. Jos se epäonnistuu, muistia ei kohdenneta ja pair
osoittaa kohtaan NULL
. Kun yrität määrittää pair->val
tai pair->word
, kaatut.
Jos olet korjannut tämän, table_insert()
käyttää make_pair()
-tulosta tarkistamatta, onko se ensin ”s NULL
. Tämä voitti” Älä kaadu heti, koska määrität vain taulukon osoittimen ryhmässä tbl->pairs[tbl->sz]
arvon pair
. Mitä tapahtuu myöhemmin, kun yrität etsiä tai tulostaa tai lisätä toisen kohteen, saat kaatumisen, kun iteroit kyseisen taulukon merkinnän ja yrität tehdä mitään sen kanssa.
Voit estää näitä virheitä tekemättä dynaamisesti allokoimalla taulukon merkinnät. Tee matriisista yksinkertaisesti Pair
-rakenteiden matriisi eikä osoitin heille.
Nimeäminen
Monet nimesi ovat todella hyviä . Pair
ja Table
ovat kunnollisia, luettavia nimiä tälle tehtävälle. make_pair()
, table_insert()
jne. ovat informatiivisia. Joitakin voitaisiin kuitenkin parantaa. tbl
ei säästä paljon kirjoittamalla table
. Käytä vain koko sanaa. Sama sz
vs. size
kanssa. i
on hyväksyttävä silmukamuuttujan nimellä, mutta olisi vielä parempi, jos se olisi kuvailevampi. Esimerkiksi entry_index
tai pair_index
. Sen tulisi kuvata mitä iteroit.
Kommentit
- kun vaihdan määritelmän arvoksi
const int ARR_SIZE = 10;
se antaa minulle virheenstruct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; };
, että ARR_SIZE ei ole const. Joten miten sitä käytetään tässä tapauksessa sitten? - Anteeksi – että ’ sa C ++ – ism. Luulin, että tämä oli C ++ -kysymys. Minun huono. Päivitän vastaukseni ’.
- I ’ olen vankka kuvailevien muuttujien nimien kannattaja, mutta tämän ohjelman silmukoiden yksinkertaisuudella luulen, että muuttujan nimi
i
toimii hienosti. - Jos
malloc()
epäonnistuu, määrityspair->val
taipair->word
saattaa kaatua jos olet onnekas . Se voi vain jatkua ja antaa vääriä tuloksia, tai se voi tehdä jotain aivan odottamatonta. Se ’ on määrittelemättömän käyttäytymisen ilo!
Vastaa
-
Älä suorita
malloc
-tulosta.malloc
palauttaa arvonvoid *
, ja C: ssä on kelvollinen muuntaavoid *
mihin tahansa osoittimeen. Jos heität estämään varoituksen kokonaisluvun osoittimen muuntamisesta, se tarkoittaa, että kääntäjällä ei olemalloc
prototyyppiä, ja oletuksena palauttaaint
(se on muinainen C-käytäntö). Jos nytint
ja osoitin ovat erikokoisia, malloc-palautusarvo katkaistaan kaikilla ikävillä seurauksilla. -
Ei ole suositeltavaa
sizeof(type)
. Pidä mieluumminsizeof(expression)
. Harkitse tapauksessasistruct Pair * pair = malloc(sizeof(*pair));
-
table_insert
lisää sokeasti täyteen taulukkoon. Sen pitäisi testatatbl->sz < ARR_SIZE
ja palauta virheilmoitus, jos se ei ole. -
Varsinainen lisäys
struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position
pitäisi olla oikeastaan yksi rivi:
tbl->pairs[tbl->sz] = make_pair(word, val);
kommentit
- miksi mallocin näyttäminen ei ole hyvä idea? C ++: ssa se saattaisi jopa kääntäjävirheen?
- @Sa ndro4912 Kysymys on merkitty tunnisteella C. C ++ on eri kieli, erityisesti tässä suhteessa. Jos C-kääntäjä valittaa, se tarkoittaa, että
malloc
-prototyyppi puuttuu, mikä voi johtaa ikäviin ongelmiin. - kyllä tiedän olevan c. ihmettelin vain, miksi tässä ei ole hyvä käytäntö ilmoittaa tyyppimuutokset näyttelijöillä
- @ Sandro4912 Katso muokkaus.
- @ Sandro4912
pair = malloc(sizeof *pair)
on helpompi koodata oikein ensimmäisellä kerralla, helpompi tarkistaa ja ylläpitää.
Vastaa
Ota kaikki käyttöön kääntäjän varoitukset
Kun kääntäjä on hyvin käytössä, for (int i = 0; i < tbl->sz; ++i)
olisi pitänyt varoittaa i
ja tbl->sz
sekä alue. Säästä aikaa ja ota kaikki varoitukset käyttöön ja käytä for (size_t i = 0; i < tbl->sz; ++i)
.
Yleensä koodi on sekava, kun int,size_t
käytetään melkein vaihdettavissa . Arkistoitin uudelleen ja käytän vain size_t
.
Pienen ja syvän kopion sekakäyttö
make_pair(const char* word, int val)
jakaa ja muodostaa kokonaan uuden struct Pair
(syvä kopio), mutta ei kopioi sitä, mihin word
osoittaa.
Ehkä
// pair->word = word; pair->word = strdup(word);
Käytä const
search_word()
ei muokkaa *tbl
, joten käytä 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)
Nimeäminen
Koodi käyttää const char* word;
, mutta se ei ole ”sana”, vaan merkkijono , jota käytetään kohdassa strcmp()
.
—– Lisäykset
Sopimusrikkomus?
Mikään vaatimuksissa ”Ota käyttöön hakutaulukko, kuten …” – toiminnot osoittavat, että const char*
on osoitin merkkijonolle . Joten kutsumalla strcmp()
on kyseenalainen, ellei ilmoittamattomia vaatimuksia ole. char *
-koodina voisi käyttää yksinkertaista vertailua
// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word)
assert()
sopivatko väitteeni käyttötarkoitukset?
Jos char *
lisättävän / haettavan osoittimen ei ole määritelty olevan merkkijono , assert(word);
ei ole oikea, koska word == NULL
ei tiedetä olevan virheellinen.
assert(return_val)
ryhmässä table_find(struct Table* tbl, const char* word, int* return_val)
on OK, mutta suunnittelen uudelleen sallimalla return_val == NULL
if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; }
*
tyyppi on muuttujan nimen vieressä (esim.const char *word
). C-tyypin syntaksin tarkoituksena on jäljitellä käyttöä, jossa esimerkiksi ’ d tyyppi*word
käyttääksesiconst char
-elementti. Huomaa myös, että tämä tekee selvemmäksi, minkä tyyppinenb
on ryhmässäint *a, b;
.