#include #include #include /** * Pour cet exercice, il vous est fortement consille de prendre un papier et un stylo. * Je vous conseille aussi fortement de brancher votre cerveau, de prendre du temps, * la complexite de cet exercice est nettement plus elevee que pour les exo precedents. * Ceci ne regarde que vous, faites le serieusement, si vous y arrivez, * vous etes pret pour le partiel ! * * Note: pour s'en sortir, relisez vos cours d'algo sur les listes. **/ #define MAX_CONTACT 200 #define MAX_STRING 64 #define MAX_TEL 12 enum genre { ORGANISATION, HOMME, FEMME }; struct contact { int id; enum genre sexe; char lastname[MAX_STRING + 1]; // + 1 pour le zero terminal char firstname[MAX_STRING + 1]; char tel[MAX_TEL + 1]; // FIXME remarque c'est un poil plus grand qu'un numero francais, // et ce sont des chars et non des nombres pour stocker les caracteres tels que +, * ou # char addr[MAX_STRING + 1]; // TODO rajouter la ville, le code postal et autres. }; struct lst_contact { struct contact contact; struct lst_contact* next; }; struct book { int next_id; struct lst_contact *used, *free; struct lst_contact pool[MAX_CONTACT]; }; int member(int n, char* tab[], char* s) { for (int i = 0; i < n; i++) { if (!strcasecmp(s, tab[i])) // On a pas vu la fonction en cours, c'est comme `strcmp` // a la casse près (majuscule/minuscule) return i; } return -1; } void scan_string(int size, char* buffer) { /* * ou pourrait utiliser `scanf("%s", ...)` mais il ne tolere pas franchement les espaces * ce qui n'est pas super pratique pour les addresses. * * du coup, on voudrait utiliser `gets`, cependant la doc (`man gets`) dit : * the gets() function cannot be used securely. [...] * it is strongly suggested that the fgets() function be used in all cases. * * vous pouvez donc utiliser/retenir l'appel suivant comme un idiome ! */ // buffer[size] = 0; // On rajoute le zero terminal a la fin (juste au cas ou) // fgets(buffer, size, stdin); // // Dans certain cas (cf. la doc) fgets rajoute un saut de ligne // // On enleve donc ce saut de ligne // int i = strrchr(buffer, '\n'); // if (i >= 0) // buffer[i] = 0; /* * Un autre solution probablement plus claire, consiste a reecrire (f)gets. */ int i = 0; // compteur de caracteres int c; // pour stocker le prochain caractere while ((c = getchar()) > 0 && c != '\n' && i < size) // Tant qu'on a des caracteres a lire // et que ce n'est pas entree // et qu'on a pas depase la taille max buffer[i++] = c; buffer[i] = 0; // et on rajoute le zero terminal // On pourrait s'arreter la, cependant si l'utilisateur tape trop de // caracteres ils vont trainer et parasiter la prochaine lecture. Pour etre // complet et correct il faut donc les manger. while (i < size && (c = getchar()) > 0) ; // On ne fait rien } enum genre scan_genre() { char tmp[13]; // Taille de organisation + 1 char* genres[] = { "ORGANISATION", "HOMME", "FEMME" }; int g = ORGANISATION; // On stocke un element d'union dans un entier ... et ca marche. printf("Genre (organisation, homme, femme): "); scan_string(12, tmp); // La longeur d'organisation g = member(3, genres, tmp); if (g == -1) // si le genre n'est pas trouve, on utilisera organisation g = ORGANISATION; // ceci illustre encore qu'une enum n'est rien de plus qu'un nombre return g; // Remarquer que l'entier est automatiquement converti dans l'union ... et ca marche. } struct contact scan_contact() { struct contact c; c.sexe = scan_genre(); printf("Nom: "); scan_string(MAX_STRING, c.lastname); if (c.sexe != ORGANISATION) { printf("prenom: "); scan_string(MAX_STRING, c.firstname); } printf("Adresse: "); scan_string(MAX_STRING, c.addr); printf("tel: "); scan_string(MAX_TEL, c.tel); return c; } void print_contact_name(struct contact* c) { char* genres[] = { "M.", "Mme" }; if (c->sexe == ORGANISATION) printf("%s", c->lastname); else printf("%s %s %s", genres[c->sexe - 1], c->firstname, c->lastname); } void print_contact_short(struct contact* c) { print_contact_name(c); printf(": %s\n", c->tel); } void print_contact(struct contact* c) { print_contact_name(c); printf("\n%s\ntel: %s\n", c->addr, c->tel); } void print_address_book(struct book* ab) { struct lst_contact* c = ab->used; while (c) { // c != NULL printf("[%3d] ", c->contact.id); print_contact_short(&c->contact); c = c->next; } } void init_address_book(struct book* ab) { ab->next_id = 0; ab->used = NULL; ab->free = ab->pool; // ab->pool == &(ab->pool + 0) == &(ab->pool[0]) ab->pool[MAX_CONTACT - 1].next = NULL; for (int i = 1; i < MAX_CONTACT; i++) ab->pool[i - 1].next = &ab->pool[i]; } struct lst_contact* get_free_slot(struct book* ab) { struct lst_contact* res = ab->free; // le premier contact libre est celui directement pointe par la case `free` if (res != NULL) // S'il existe un slot disponible, le sortir de la liste ab->free = res->next; // c'est a dire, prendre le suivant return res; } void add_used_slot(struct book* ab, struct lst_contact* item) { item->next = ab->used; // rajouter le nouveau devant l'ancien premier ab->used = item; // maintenant le nouveau premier est le courant. } // Peut on remplacer ce contact par un pointeur ? Si oui le faire. // Vous poser la question pour toutes les fonctions (precedentes ou suivantes): // "Peut on enlever/rajouter un pointeur." void add_contact(struct contact c, struct book* ab) { struct lst_contact* n = get_free_slot(ab); if (n == NULL) { printf("plus de place pour stocker votre contact.\n"); return; // eventuellement exit(EXIT_FAILURE); } n->contact = c; n->contact.id = ab->next_id++; // Set Id of current contact and increment next id add_used_slot(ab, n); } struct contact* find_contact_by_name(char* name, struct book* ab) { struct lst_contact* c = ab->used; while (c) { // i.e., c != NULL if (!strcasecmp(c->contact.lastname, name)) return &c->contact; c = c->next; } return NULL; } void print_contact_by_name(char* name, struct book* ab) { struct contact* c = find_contact_by_name(name, ab); if (c == NULL) printf("Contact '%s' inexistant\n", name); else print_contact(c); } struct contact* find_contact_by_id(int id, struct book* ab) { struct lst_contact* c = ab->used; while (c) { // i.e., c != NULL if (c->contact.id == id) return &c->contact; c = c->next; } return NULL; } // La question suivante est pour vous, vous pouvez ecrire la fonction `start_with` // qui compare les `n` premiers caracteres ... ou lire la doc de `strcmp` (et reflechir qques secondes). /** * Cette fonction retire le contact qui a l'id `id` du carnet d'adresse. */ void remove_contact(int id, struct book* ab) { // Cette fonction est relativement compliqué ... en effet pour 'retirer' un // element d'une liste simplement chainee il faut connaitre le maillon // precedent ! Comme si cela ne suiffisait pas, ATTENTION au cas particulier // du premier maillon. // // En question subsidiaire, comment pourriez vous separer cette fonction en // `sous-fonctions` plus utiles. Comment unifier ces sous-fonctions avec // celles pour `ajouter` un contact. struct lst_contact* prev = NULL; struct lst_contact* item = ab->used; while (item) { // i.e., item != NULL if (item->contact.id == id) break; // Une fois n'est pas coutume on sort de la boucle de facon un poil premature prev = item; item = item->next; } if (item == NULL) return; // Le contact n'est pas trouve if (prev == NULL) { // c'est le cas particulier du premier maillon ab->used = item->next; } else { // sinon le maillon precedent a comme suivant, le maillon suivant du maillon trouve // (ca parait complique a lire, mais dessinez le !). prev->next = item->next; } // Maintenant on racroche le maillon aux contacts libres. item->next = ab->free; ab->free = item; } // J'arrete la correction ici puisqu'il n'y a plus rien de vraiment complique. // Ceci etant dit, j'insiste, faites les, histoire de verifier que vous avez // compris. // // // Rajouter des entrees au pool, represente une ``petite'' difficulte. // Globalement l'idee est de remplacer dans la structures le tableau par un // ptr. Cependant, rappellez vous, un ptr et un tableau c'est pareil pour les // types ... mais pas pour la memoire ! Il vous faudra donc ``passer'' un // tableau vide lors de l'initialisation. Pour le faire de facon automatique, // la seule solution c'est `malloc` et `free` (et le debut des ennuis). La // strategie la plus courrament utilise est la suivante, quand le nb max // d'entree est atteint, on alloue un tableau du double de la taille du tableau // courrant. Puis on recopie les l'ancien dans le nouveau (`memcpy` fait // parfaitement l'affaire). On ne reduit jamais la taille du tableau ! Ceci // peut paraitre etrange, l'idee sous-jacente est la suivante : si on a eu un // jour besoin de ces elements, on en aura probablement besoin a nouveau. // (Pour les plus braves, ou les cas ou cela est necessaire, on peut quand meme // faire une fonction 'compact' qui reduit la taille du tableau exactement au // nombre d'entree utiles.) /** * Ce main est completement stupide mais permet de tester vite fait. * Ameliorez le ! */ int main(int argc, char* argv[]) { struct book ab; init_address_book(&ab); for (int i = 0; i < 3; i++) { struct contact c = scan_contact(); add_contact(c, &ab); } print_address_book(&ab); print_contact_by_name("foo", &ab); remove_contact(1, &ab); print_address_book(&ab); remove_contact(2, &ab); print_address_book(&ab); remove_contact(0, &ab); print_address_book(&ab); }