Table de consultation avec tableau fixe

Jai implémenté lexercice suivant:

Implémenter un table de consultation avec des opérations telles que find(struct table*, const char*), insert(struct table*, const char*,int) et remove(struct table*, const char*). La représentation de la table peut être un tableau dune paire struct ou une paire de tableaux (const char*[] et int*); vous choisissez, choisissez également les types de retour pour vos fonctions.

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

Nhésitez pas à commenter toute amélioration. Y a-t-il une meilleure façon de faire cela?

Jai également une question spécifique: mes utilisations de assert sont-elles appropriées?

Commentaires

  • En tant que pic stylistique, je ‘ recommande le * dans un pointeur le type doit être adjacent au nom de la variable (par exemple const char *word). La syntaxe de type C a été conçue pour imiter lutilisation, où, par exemple, vous ‘ d type *word pour accéder à un const char. Notez également que cela rend plus clair le type de b dans int *a, b;.

Réponse

En utilisant struct

Personnellement, je pense que vous avez fait le bon choix en utilisant un struct avec un char* et int au lieu de 2 tableaux de char* et int, respectivement. Pair est une unité conceptuelle de données dans votre application, il est donc logique de les regrouper. Si vous aviez 2 tableaux séparés, il serait facile pour eux de se désynchroniser et difficile de déboguer pourquoi cela sest produit. Beau travail!

Préférez const et fonctions aux macros

Vous avez défini la taille du tableau à laide dune macro. Cela met vous êtes désavantagé lors de la programmation. Vous avez supprimé les informations de type pour la valeur. En le faisant:

const int ARR_SIZE = 10; 

vous obtenez la sécurité de type. Edit: Cest « un C ++ – ism that doesn » t travailler en C. Mon mauvais! Mais le reste des conseils du paragraphe suivant est correct pour autant que je sache.

Avec les macros qui prennent des paramètres, vous courez le risque de les utiliser de manière inattendue et de causer des problèmes difficiles à déboguer. Heureusement, vous n’avez pas fait cela ici. Mais en général, si vous vous trouvez à la recherche d’une macro, demandez-vous si vous seriez mieux avec une constante ou une fonction. Vous le serez presque toujours (en supposant que vous puissiez utiliser la constante de la manière souhaitée).

Erreurs

Il y a des erreurs dans votre code. Dans make_pair(), vous ne cochez pas pour voir si malloc() a réussi. En cas déchec, aucune mémoire nest allouée et pair pointe vers NULL. Lorsque vous essayez dattribuer pair->val ou pair->word, vous plantez.

Si vous avez résolu ce problème, table_insert() utilise le résultat de make_pair() sans vérifier sil « est NULL en premier. Cela a gagné » t planter immédiatement, car vous « êtes simplement en train dassigner le pointeur de tableau dans tbl->pairs[tbl->sz] pour avoir la valeur de pair. Ce qui se passera est plus tard lorsque vous essayez de rechercher, dimprimer ou dinsérer un autre élément, vous obtiendrez un plantage lorsque vous parcourez cette entrée dans le tableau et que vous essayez de faire quoi que ce soit avec.

Vous pourriez rendre ces erreurs impossibles à ne pas allouer dynamiquement les entrées du tableau. Faites simplement du tableau un tableau de structures Pair plutôt quun pointeur vers elles.

Dénomination

Beaucoup de vos noms sont vraiment bons . Pair et Table sont des noms décents et lisibles pour cette tâche. make_pair(), table_insert(), etc. sont informatifs. Certains pourraient cependant être améliorés. tbl ne vous évite pas beaucoup de taper sur table. Utilisez simplement le mot entier. Idem avec sz vs size. i est acceptable comme nom de variable de boucle, mais ce serait encore mieux sil était plus descriptif. Par exemple, entry_index ou pair_index. Il doit décrire ce sur quoi vous « réitérez ».

Commentaires

  • lorsque je change la définition en const int ARR_SIZE = 10; cela me donne une erreur dans struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; que ARR_SIZE nest pas const, alors comment lutiliser dans ce cas alors?
  • Désolé – que ‘ en C ++ – ism. Je pensais que cétait une question C ++. Ma mauvaise. Je ‘ je mettrai à jour ma réponse.
  • Je ‘ je suis un fervent partisan des noms de variables descriptifs, mais avec la simplicité des boucles de ce programme, je pense que le nom de la variable i fonctionne très bien.
  • Si malloc() échoue, attribuer à pair->val ou pair->word pourrait planter, si vous avez de la chance . Cela pourrait simplement continuer et donner de mauvais résultats, ou cela pourrait faire quelque chose de totalement inattendu. Cest ‘ la joie du comportement indéfini!

Réponse

  • Ne convertissez pas le résultat de malloc.

    malloc renvoie void *, et en C, il est valide de convertir void * en nimporte quel pointeur. Si vous effectuez un cast pour supprimer un avertissement concernant la conversion dentier en pointeur, cela signifie quun compilateur na pas de prototype malloc et quil renvoie par défaut un int (cest une ancienne convention C). Maintenant, si int et le pointeur sont de taille différente, la valeur de retour malloc serait tronquée, avec toutes les conséquences désagréables.

  • Il nest pas recommandé de sizeof(type). Préférez sizeof(expression). Dans votre cas, envisagez

     struct Pair * pair = malloc(sizeof(*pair)); 
  • table_insert insère aveuglément dans un tableau complet. Il doit tester tbl->sz < ARR_SIZE et renvoie une indication derreur si ce nest pas le cas.

  • Linsertion réelle

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

    doit être en fait une seule ligne:

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

Commentaires

  • pourquoi nest-ce pas une bonne idée de lancer malloc? En C ++, il obtiendrait même une erreur de compilation?
  • @Sa ndro4912 La question est étiquetée C. C ++ est un langage différent, en particulier à cet égard. Maintenant, si le compilateur C se plaint, cela signifie que le prototype malloc est manquant, ce qui peut conduire à des problèmes désagréables.
  • oui je sais que c. Je me suis simplement demandé pourquoi ici ce nest pas une bonne pratique dindiquer les changements de type avec un cast
  • @ Sandro4912 Voir edit.
  • @ Sandro4912 pair = malloc(sizeof *pair) est plus facile à coder correctement la première fois, plus facile à réviser et à gérer.

Réponse

Tout activer avertissements du compilateur

Avec un compilateur bien activé, for (int i = 0; i < tbl->sz; ++i) aurait dû avertir du mélange de la signature de i et tbl->sz ainsi que range. Gagnez du temps et activez tous les avertissements et utilisez for (size_t i = 0; i < tbl->sz; ++i).

En général, le code est difficile à utiliser int,size_t presque de manière interchangeable . Je « réarchitecte et utilise size_t uniquement.

Utilisation mixte de copie superficielle et profonde

make_pair(const char* word, int val) alloue et forme un tout nouveau struct Pair (copie complète), mais ne copie pas ce que word pointe.

Peut-être

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

Utilisez const

search_word() ne modifie pas *tbl, utilisez donc 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) 

Dénomination

Code utilise const char* word; mais ce nest pas un « mot », mais une chaîne telle quutilisée dans strcmp().

—– Ajouts

Violation de contrat?

Rien dans les exigences « Implémenter une table de recherche avec des opérations telles que … » nindique que const char* est un pointeur vers une chaîne . Appelez donc strcmp() est discutable à moins dexigences non déclarées. En tant que char *, le code pourrait utiliser une simple comparaison

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

Utilisation de assert()

Mes utilisations dassert sont-elles appropriées?

Si le pointeur char * pour ajouter / rechercher nest pas spécifié comme étant une chaîne , assert(word); nest pas correct car word == NULL nest pas connu pour être invalide.

Le assert(return_val) dans table_find(struct Table* tbl, const char* word, int* return_val) est OK, mais je referais la conception pour autoriser return_val == NULL

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

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *