rattrapages en langage c - Philippe J. Giabbanelli

Transcription

rattrapages en langage c - Philippe J. Giabbanelli
Rattrapages en langage C, correction
Opérations fondamentales sur les tableaux
I.
⌂ Dans le main, créer un tableau d’entiers de taille 30, remplissez le avec des nombres aléatoires et
trouvez-en le maximum de trois façons différentes.
→ Si on doit tout faire dans le main, ‘trois façons différentes’ signifie trois écritures différentes pour les
boucles. On a for, while et do while.
Attention : il serait incorrect de dire que « la fonction main ‘prend’ un tableau, le rempli avec des valeurs
aléatoires, etc. etc. ». La fonction main prend toujours un nombre d’arguments (argument count) et la
valeur de ses arguments (argument values). Après, elle fait différentes choses…
int main(int argc, char **argv){
int tab[30], i, vMax;
/* On fait du C : on déclare toutes les variables, puis on les utilise */
srand(time(NULL));
/* Il faut initialiser l’aléatoire. Penser à faire #include <time.h> … */
vMax = tab[0];
/* On initialise le maximum à la première valeur du tableau */
for(i=0; i < 30; i+ +){
/* On peut tout faire en une passe ! */
tab[i]=rand();
/* Nombre aléatoire. Penser à faire #include <stdlib.h> …*/
if(vMax < tab[i]) vMax = tab[i] ;
/* On a trouvé un nouveau maximum, on remplace. */
}
return vMax;
}
Les deux autres écritures de la boucle, avec while et do while :
i = 0;
while(i < 30){
tab[i] = rand() ;
if(vMax < tab[i]) vMax = tab[i] ;
i++;
}
i = 0;
do{
tab[i] = rand() ;
if(vMax < tab[i]) vMax = tab[i];
i++;
}while(i < 30);
⌂ Créez une fonction vector_Random qui, étant donné un tableau, le remplisse de nombres aléatoires.
→ « Etant donné un tableau » ne signifie pas que le seul paramètre fourni à la fonction sera le tableau !
En effet, la taille du tableau est également un paramètre. Donc on a déjà vector_Random(int tab[], int n).
Un tableau est en réalité une adresse mémoire ‘et une certaine zone’ (sa taille). Quand on passe le tableau
à la fonction, ce qu’on passe est donc un pointeur sur une adresse mémoire : la fonction va modifier en
mémoire le tableau. Donc, en sortie de la fonction, les modifications seront toujours là. Il n’y a donc pas
besoin de renvoyer le tableau ou quoi que ce soit : on est allé le modifier.
Ceci n’est valable que si l’objet sur lequel on travaille est passé par un pointeur ! Par exemple la méthode
void add(int a){ a = a + 10 ;} va modifier localement la variable a, et après tout sera oublié.
En revanche, void add(int *a){ *a = *a + 10 ;} va modifier la variable a en mémoire !
La solution intermédiaire est int add(int a){ return a + 10 ;}, i.e. création de variables locales et retour.
void vector_Random(int tab[],int n){
int i, vMax;
/* Nos variables locales */
srand(time(NULL)); /* Toujours penser à initialiser l’aléatoire */
for(i=0; i < n; i++) /* Remplie le tableau avec des nombres aléatoires (pas de ‘façon’ aléatoire !) */
tab[i]=rand();
/* Pas besoin d’accolades si on a une seule instruction */
}
Philippe Giabbanelli
1
Mai 2006
⌂ Créer une fonction vector_Max qui, étant donné un tableau, en détermine le maximum.
→ Rien de nouveau ici.
int vector_Max(int tab[],int n){
int i, vMax;
vMax = tab[0];
for(i=0; i < n; i++)
if(vMax<tab[i])
vMax = tab[i];
return vMax;
}
⌂ Au démarrage du programme, il doit maintenant proposer d’essayer ces fonctions (oui / non).
→ Avant, on a eu besoin de faire #include <time.h> car on utilisait la fonction time et #include <stdlib.h> car on
utilisait la fonction rand. Il ne devait y avoir aucun autre #include : on ne demande que ce qui est nécessaire !
On veut maintenant d’autres fonctions : scanf, puts, printf. Il n’y a que deux fichiers dont il faut se rappeler :
- stdio, pour Input/Ouput (entrées-sorties). Défini entre autre scanf, puts, printf et le type FILE.
- stdlib, des utilitaires. Défini entre autre l’aléatoire (rand…) et la gestion mémoire (malloc, free…).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
/* Si on ne l’utilise pas en complet, on dit pourquoi on l’inclut… */
/* Par exemple : je met stdlib car j’utilise rand.
*/
/* pour utiliser time (initialisation de l’aléatoire)
*/
int main(int argc, char **argv){
char reponse ;
puts(“Voulez-vous tester vos fonctions ? (o/n) ”) ;
scanf(“%c”,&reponse) ;
if(reponse == ‘o’ || reponse == ‘O’){
puts(“Quelle taille voulez-vous ?”);
int n , *tab;
scanf(“%d”,&n) ;
tab = (int *)malloc(n*sizeof(int));
vector_Random(tab, n) ;
printf(“Le maximum du tableau de %d cases est %d \n ”, n, vector_Max(tab, n)) ;
free(tab) ;
/* Libérer la mémoire qu’on a alloué nous même. Ne pas oublier ! */
}else if(reponse == ‘n’ || reponse == ‘N’) puts(“Au revoir”) ;
else puts(“Ne pas toujours ecrire n’importe quoi…”) ;
return EXIT_SUCCESS ;
}
Rappelons le fonctionnement de scanf. On prend un certain type de données (c : character, d : digit,…) et
on le met à l’adresse demandée. En pratique, on veut stocker la réponse dans une variable : il faut donc
utiliser l’adresse mémoire de cette variable, que l’on demande en la précédant de &.
Il y a deux fonctions importantes d’affichage : printf est la plus complète, on peut y mettre des types
comme pour scanf, mais on ne doit pas oublier de passer à la ligne : \n. puts est une fonction qui affiche une
chaîne de caractères (put string) et fait automatiquement le retour à la ligne ; elle est plus rapide.
On demande la taille du tableau et l’utilisateur, et on va donc le créer dynamiquement, i.e. en demandant
nous même de réserver de la mémoire. On utilise malloc, en lui fournissant la quantité de mémoire à
réserver. On manipule des entiers (int), et on en manipule n : malloc(n*sizeof(int)). Comme malloc renvoie un
pointeur générique (void *), on spécifie ce qu’on a : un pointeur d’entiers, (int *)malloc(n*sizeof(int));.
Philippe Giabbanelli
2
Mai 2006
⌂ vector_Random2 ne devra pas utiliser le symbole [ ].
→ On a dit qu’un tableau est en réalité une adresse mémoire. La notation [ ] est équivalent à celle avec *.
tab[] est l’adresse du premier élément du tableau ; on peut le noter *tab, c’est un pointeur sur la 1ère case !
tab[i] signifie qu’on avance de i cases par rapport à l’adresse de tab ; cela équivaut donc à *(tab+i).
Notons que ce ‘+’ est de l’arithmétique des pointeurs, ce n’est pas une simple opération.
Le ‘+’ avance de i cases : il n’ajoute pas la valeur i à l’adresse. Autrement dit, si on a tableau t1 de petits
entiers (short) et un tableau t2 de réels (double), alors *(t1 + 10) et *(t2 + 10) ne se déplacent pas d’autant
d’octets (car la case à l’unité n’a pas la même taille), mais ils se déplacent d’autant de cases.
void vector_Random2(int *tab, int n){
int i;
i=0;
srand(time(NULL));
while(i < n){
*(tab+i)= rand();
i++;
}
}
⌂ vector_maxAbs donne le maximum en valeur absolue, à l’aide d’une macro pour la valeur absolue.
→ Une macro est une substitution textuelle : #define NOM equivalent
On ne fait pas ceci pour de la performance, c’est un simple exercice académique. Il est assez déconseillé
d’écrire ses fonctions sous formes de macro car les choses peuvent être évaluées plusieurs fois… On
utilise plutôt les macros pour définir des choses qui ont du sens. Par exemple, si on fait un jeu de rôle :
#define NORD
0
#define SUD
1
#define OUEST 2
#define EST
3
On facilite ainsi la lecture du code : il est plus parlant de voir NORD que 0…
Ce qu’on veut ici est légèrement plus compliqué : on veut une fonction. Faisons un exemple :
#define CARRE(x) x*x
#define ADD(x,y) x+y
On ne met pas de ; à la fin de la ligne, il n’y en a pas besoin car c’est une substitution textuelle.
Par exemple, si on avait b = CARRE(a); dans le code, alors on remplace avec b = a*a;
La valeur absolue de x est x s’il est positif, et –x s’il est négatif. C’est l’occasion d’utiliser l’opérateur ‘?’ :
#define ABS(x) (x>0) ? x : -x
/* syntaxe <expression> ? <si vrai> : <si faux> */
/* ndlr : toutes les directives au préprocesseur (commencent par #...) doivent être au début du fichier ! */
int vector_maxAbs(int tab[], int n){
int i, vMax;
vMax = ABS(tab[0]);
// si le max est une valeur absolue au départ, plus besoin de le vérifier
i=0;
while(i < n){
vMax = (vMax>ABS(tab[i])) ? vMax : ABS(tab[i]);
i++;
}
return vMax;
}
Philippe Giabbanelli
3
Mai 2006
⌂ La fonction vector_Sum doit calculer la somme du tableau ; implémentations itératives et récursives.
→ Récursif signifie que la fonction doit se rappeler elle-même. Dites-vous de façon simple que c’est
itératif lorsque la fonction ne se rappelle pas elle-même (en réalité, c’est un tout petit peu plus subtil…).
Pour faire la somme de façon itérative, c’est simple : on prend une boucle, une variable de résultat, on
somme tout ceci, puis on renvoie le résultat. L’itératif, c’est « comme on a toujours fait ».
int vector_Sum(int *tab,int n){
int somme, i;
somme =0;
for(i=0; i < n; i++)
somme = somme + tab[i];
return somme;
}
Quand on fait du récursif, c’est l’occasion de poser proprement ses équations de récurrence. Si on est
capable d’écrire la chose de façon mathématiquement saine, alors coder ce sera juste une petite traduction.
Notre fonction somme est une fonction à deux variables : un tableau T et un entier n.
n > 0 → Somme(T, n) = T[n-1] + Somme(T, n-1)
n = 0 → Somme(T, 0) = 0
La somme d’un tableau c’est son dernier élément, plus la somme du reste. Si on est arrivé au bout, alors
on renvoie le neutre pour l’addition : c’est 0. On pourra comparer avec la définition de la factorielle…
int vector_Sum_Rec(int *tab, int n){
if(n>0) return tab[n-1] + vector_Sum_Rec(tab,n-1);
return 0;
}
⌂ La fonction vector_Cut supprime toutes les occurrences de l’élément passé en paramètre.
→ On commence ici à aborder un peu d’algorithmique. Il faut donc toujours commencer par faire soimême un exemple. Après on abstrait, et en dernier on code… On ne commence jamais par coder !
Soit le tableau 3 2 1 4 5 6 7 4 1 2 3 2 4 2 3 et je veux supprimer toutes les occurrences de 3.
Je me donne un pointeur i sur le début du tableau et un pointeur j sur la fin du tableau.
3 2 1 4 5 6 7 4 1 2 3 2 4 2 3, i = 0, j = 14.
Je vois un 3 : il faut que je le supprime. Je le permute avec la fin, et je décrémente la fin.
Autrement dit : j’ai un élément à supprimer, je le met en bout de tableau, et je raccourci le tableau.
3 2 1 4 5 6 7 4 1 2 3 2 4 2 3, i = 1, j = 13. Je vois encore un 3. Je supprime à nouveau en permutant.
2 2 1 4 5 6 7 4 1 2 3 2 4 3 3, i = 10, j = 12. J’avance jusqu’à trouver quelque chose à supprimer…
2 2 1 4 5 6 7 4 1 2 3 2 4 3 3, i = 12, j = 12. Les pointeurs se rencontrent, tout a été traité.
Sachant que je commence à compter à partir de 0, cela signifie que ma taille est maintenant j + 1 (ou i+1).
int vector_Cut(int *tab, int k, int n){
int i,j;
i = 0; j = n-1;
do{
if(tab[i]==k){
int tmp; tmp = i; i = j; j = tmp;
j--;
}
else i++;
}while(i!=j);
return j+1;
}
Philippe
Giabbanelli
Exemple d’utilisation :
int n, *tab ;
n = 100 ;
*tab = (int *)malloc(n*sizeof(int)) ;
vector_random(tab, n);
/* tableau de 100 entiers remplis aléatoirement */
n = vector_Cut(tab, 32000, n);
free(tab);
4
Mai 2006