Partie 1 - Bases de C

Pour l’organisation des fichiers (dossier p1/) et les options de compilation, voir les consignes. Pour l’utilisation de valgrind (et de l’option -g), voir la page Outils.

p1e1 : pointeurs simples et pointeurs sur tableaux

Écrire une fonction division_et_reste qui effectue la division entière de deux entiers positifs a et b :

void division_et_reste(int a, int b, int *quotient, int *reste);

La fonction ne retourne rien, mais stocke le quotient et le reste dans deux variables passées par adresse.

Écrire une fonction min_et_max qui recherche, dans un tableau d’entiers de taille taille, le plus petit et le plus grand élément :

void min_et_max(const int *tab, int taille, int *min, int *max);

Les résultats seront renvoyés par deux variables passées par adresse.

Tester avec a = 17, b = 5 et le tableau {5, 9, 2, 7, 3} déclaré dans le main.

Résultat attendu :

17 / 5 = 3, reste = 2

Tableau : 5, 9, 2, 7, 3
        - min : 2
        - max : 9

p1e2 : pointeurs et structures

Définir un type Duration (typedef struct { ... } Duration;) contenant 3 entiers non signés (unsigned int) : heures, minutes, secondes.

Dans le main, déclarer et initialiser :

  • d1 à 0h150min500s (volontairement non normalisée, donnera 2h38min20s une fois normalisée) ;

  • d2 à 5h35min58s ;

  • d3 à 2h45min06s.

Implémenter :

  • void print_duration(const Duration *d), qui affiche la durée au format hh:mm:ss (ex. 05:35:58, 02:45:06). Utiliser %02u : au moins 2 chiffres, complétés par des 0 (une valeur plus grande, comme 150, est affichée en entier) ;

  • void normalize_duration(Duration *d), qui normalise la durée (ex. 75s → 1min15s, 70min130s → 1h12min10s). Les heures ne sont pas limitées à 24 ;

  • void add_duration(const Duration *d1, const Duration *d2, Duration *result), qui calcule result = d1 + d2, puis normalise result.

Tester ces fonctions avec d1, d2, d3 et une variable Duration sum pour stocker les résultats.

Résultat attendu :

d1 brut      : 00:150:500
d1 normalisé : 02:38:20
d2           : 05:35:58
d3           : 02:45:06
d1 + d2 = 08:14:18
d2 + d3 = 08:21:04
d1 + d3 = 05:23:26

p1e3 : création de tableau dynamique

Écrire un programme C qui :

  • demande une taille de tableau (qui doit être > 0, sinon le programme se termine) ;

  • alloue dynamiquement un tableau d’entiers de cette taille (voir Gestion mémoire / Allocation dynamique) ;

  • remplit le tableau avec des valeurs aléatoires (voir Aléatoire) comprises dans l’intervalle [50, 100[ ;

  • affiche le contenu du tableau avec une fonction void print_table(const int *tab, int n).

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e3.c && valgrind ./a.out
... affichage de valgrind
Taille du tableau (>0) : 20
Contenu du tableau : 66 81 75 64 77 73 66 83 59 99 72 83 72 65 98 69 59 99 79 93
... affichage de valgrind sans fuites mémoire

p1e4 : modification de tableau dynamique

Reprendre le code précédent, puis modifier la taille du tableau (en gardant les données initiales) en fonction d’une nouvelle taille saisie par l’utilisateur, à l’aide de realloc. Si la nouvelle taille est plus grande, remplir les nouvelles cases avec des valeurs aléatoires. Une taille <= 0 termine le programme.

Afficher ensuite le nouveau tableau.

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e4.c && valgrind ./a.out
... affichage de valgrind
Taille du tableau (>0) : 20
Tableau initial : 57 75 71 71 99 91 73 73 81 87 65 76 94 73 78 80 51 56 67 79
Nouvelle taille (>0) : 10
Nouveau tableau : 57 75 71 71 99 91 73 73 81 87
... affichage de valgrind sans fuites mémoire
$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e4.c && valgrind ./a.out
... affichage de valgrind
Taille du tableau (>0) : 10
Tableau initial : 51 72 71 50 66 81 55 96 55 83
Nouvelle taille (>0) : 20
Nouveau tableau : 51 72 71 50 66 81 55 96 55 83 90 90 92 50 76 66 87 56 98 58
... affichage de valgrind sans fuites mémoire

p1e5 : tableau de chaînes de caractères

Écrire un programme C qui :

  • demande un nombre n de chaînes ;

  • alloue dynamiquement un tableau de n chaînes de caractères ;

  • le remplit avec des chaînes saisies dans le terminal : lire chaque chaîne dans un tampon de taille fixe (ex. char buf[256]), puis allouer strlen(buf) + 1 octets et y copier la chaîne ;

  • une fois les n chaînes enregistrées, parcourt le tableau, regarde si la chaîne "coucou" est présente et affiche l’indice de sa première occurrence ;

  • pour finir, affiche les chaînes du tableau.

Lire n avec fgets puis atoi : mélanger scanf et fgets laisse un \n dans le tampon d’entrée, que le fgets suivant lirait comme une ligne vide.

Fonctions utiles (inclure <string.h> et <stdlib.h>, ainsi que <stdbool.h> pour read_line) :

  • strlen(chaine) pour obtenir la longueur d’une chaîne ;

  • strcpy(destination, source) pour copier une chaîne ;

  • fgets(chaine, taille_max, flux), qui, contrairement à scanf, lit aussi les espaces. Dans notre cas, le flux d’entrée (flux) sera stdin ;

  • atoi(chaine) pour convertir une chaîne en entier ;

  • int strcmp(const char *s1, const char *s2) pour comparer deux chaînes (retourne 0 si elles sont égales).

Voici une fonction read_line qui lit une entrée utilisateur et retire le \n :

/**
* @brief Lit une ligne (y compris les espaces) dans buffer et retire le '\n'
*
* @param buffer tableau dans lequel écrire les données
* @param taille nombre maximal de caractères (taille du tableau)
* @return true si la lecture a réussi, false sinon (fin de l'entrée ou erreur)
*/
bool read_line(char *buffer, int taille) {
    if (fgets(buffer, taille, stdin) == NULL) {
        buffer[0] = '\0';
        return false;
    }
    buffer[strcspn(buffer, "\n")] = '\0';
    return true;
}

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e5.c && valgrind ./a.out
... affichage de valgrind
Nombre de chaînes : 3
Chaîne 1 : hello
Chaîne 2 : hey
Chaîne 3 : hej

À la recherche de coucou...
coucou n'est pas dans le tableau

Affichage final :
[0] hello
[1] hey
[2] hej
... affichage de valgrind sans fuites mémoire
$ valgrind ./a.out
... affichage de valgrind
Nombre de chaînes : 3
Chaîne 1 : hello
Chaîne 2 : coucou
Chaîne 3 : hej

À la recherche de coucou...
coucou est trouvé à l'indice 1

Affichage final :
[0] hello
[1] coucou
[2] hej
... affichage de valgrind sans fuites mémoire

p1e6 : création de matrice dynamique

Écrire un programme C qui :

  • demande à l’utilisateur un nombre de lignes rows et de colonnes cols (strictement positifs) ;

  • alloue dynamiquement une matrice (int **) d’entiers de taille rows × cols à l’aide d’une fonction int **alloc_matrix(size_t rows, size_t cols) ;

  • la remplit avec des valeurs aléatoires dans l’intervalle [20, 60[ ;

  • l’affiche avec une fonction void print_matrix(int **matrix, size_t rows, size_t cols), qui met une tabulation entre chaque élément et revient à la ligne à chaque nouvelle ligne ;

  • calcule avec deux fonctions int *sum_rows(int **matrix, size_t rows, size_t cols) et int *sum_columns(int **matrix, size_t rows, size_t cols) les sommes par ligne et par colonne : sum_rows renvoie un tableau de rows sommes (une par ligne), sum_columns un tableau de cols sommes (une par colonne), tous deux alloués dynamiquement. Afficher ces sommes dans le main ;

  • libère la matrice avec une fonction void free_matrix(int **matrix, size_t rows), sans oublier les tableaux renvoyés par sum_rows et sum_columns.

Vérifier qu’il n’y a pas de fuites mémoire (voir Analyse à l’exécution).

Résultat attendu :

$ gcc -std=c2x -Wall -Wextra -pedantic -g p1/p1e6.c && valgrind ./a.out
... affichage de valgrind
Nombre de lignes (>0) : 5
Nombre de colonnes (>0) : 3
Matrice 5x3 :
41      54      24
51      57      23
41      32      36
28      39      47
23      33      30
Somme des lignes :
119     131     109     114     86
Somme des colonnes :
184     215     160
... affichage de valgrind sans fuites mémoire