TD C1 (2425v1)

Objectifs :
- apprendre quelques notions nouvelles
- s'entraîner à écrire sur papier un programme C syntaxiquement correct


I. Les types de base (les parties en orange sont données juste pour information)

Les types fournis par C sont proches des types primitifs Java (historiquement, c'est plutôt l'inverse). Mais les différences sont suffisantes pour nous poser problème si on ne les connaît pas.

1) int est bien le type entier (signé), mais alors qu'il est toujours sur 32 bits en Java, son empreinte mémoire peut varier d'une machine à l'autre en C.
De même pour long. Par contre byte et short devraient bien occuper respectivement 8 et 16 bits.

2) float et double sont comme en Java (les fonctions mathématiques calculent aussi en double, donc les réels simple précision float sont peu utilisés).

3) char (pour stocker un seul caractère) n'occupe que 8 bits et est signé (-128 à +127), alors qu'il en occupe 16 en Java et n'est pas signé (0 à 65535).

4) Le type booléen n'existe pas en standard, même si des versions récentes fournissent le bool du C++.
On a coutume d'utiliser le type int (32 fois trop coûteux !) en prenant comme conventions :
- à la lecture, =0 vaut FAUX, et ≠0 vaut VRAI (dans les if et les while, par exemple)
- à l'écriture, 0 pour FAUX, et 1 pour VRAI

5) Il est possible d'utiliser des types plus exotiques tels que long int (équivalent de long), long long (dépendant de la machine), long double (sur 80 bits).

6) Il est également possible d'ajouter unsigned devant un type pour obtenir un type non signé.
Exemple : int correspond à 'entier relatif' (allant pour n bits de -2n-1 à +2n-1-1), alors que unsigned int correspond à 'entier naturel' (allant pour n bits de 0 à 2n-1).

II. La définition de type

Si vous trouvez fastidieux de répéter unsigned int à chaque fois que vous avez besoin d'une variable du type entier naturel, il est possible de définir un nouveau type par l'instruction :
typedef unsigned int  Naturel;
Ensuite, il suffira d'écrire Naturel n; pour déclarer une variable de ce type.

Cette possibilité bien plus générale
typedef definition_de_type  nouveau_nom ;
nous servira énormément lors du prochain TP.

Exception à la règle ci-dessus : pour définir un type tableau par exemple d'entiers, il faut écrire : typedef int TableauI[]; (donc avec les crochets à la fin), puis pour l'utiliser : TableauI vTab; par exemple.

III. Manipulations de tableaux

Lors du TP C1, vous avez remarqué qu'on ne pouvait pas obtenir le nombre de cases d'un tableau (sauf dans le bloc dans lequel il est créé).
Cela explique pourquoi on est presque toujours obligé d'ajouter un paramètre supplémentaire aux fonctions de manipulation des tableaux pour leur passer sa taille.

Attention ! Ne pas confondre le nombre de cases, par exemple 10, et le nombre d'éléments utiles dans le tableau, par exemple 7. Dans ce cas, les 3 dernières valeurs seraient ignorées.

1) Écrire un programme C comportant une fonction vmin retournant la valeur minimale d'un tableau de réels (passé en paramètre, ainsi que le nombre de valeurs à examiner) et un main comportant :
- la déclaration/initialisation d'un tableau de réels (d'au moins 7 valeurs)
- l'affichage du résultat de vmin avec en 2ème paramètre la taille du tableau (préalablement calculée)
- l'affichage du résultat de vmin avec en 2ème paramètre 3 ou 4 (de telle sorte que le résultat soit différent)

2) Écrire un programme C comportant une fonction imin (n'appelant pas la fonction vmin) retournant l'indice du minimum d'un tableau de réels (passé en paramètre, ainsi que le nombre de valeurs à examiner) et un main comportant :
- la déclaration/initialisation d'un tableau de réels (d'au moins 7 valeurs, avec au moins 2 fois la valeur minimale)
- l'affichage du résultat de imin avec en 2ème paramètre la taille du tableau (préalablement calculée)
Contrainte : Si la valeur minimale apparaît plusieurs fois dans le tableau, imin doit retourner le plus grand indice, tout en n'affectant pas la variable contenant la valeur minimale courante si celle-ci ne change pas !

3) Ceux qui n'ont pas terminé le TP C1 doivent le faire maintenant.
Pour les autres, écrire un programme C comportant une procédure affiche2 qui devra afficher dans l'ordre croissant des valeurs provenant de 2 tableaux de réels (de taille différente) triés par ordre croissant. affiche2 aura donc 4 paramètres : 2 x (un tableau + son nombre d'éléments).
Contrainte : Définir le type tableau et l'utiliser aux 4 endroits où il peut être utilisé.
Conseils : Comme cet algorithme n'est pas simple, il est conseillé (dans la boucle) de séparer la partie où l'on choisit dans quel tableau on va prendre la prochaine valeur, de la partie où l'on prend la valeur et fait évoluer les indices. D'autre part, la boucle 'Faire ... Tantque' semble bien adaptée ici :
do { instructions } while (condition); 
Exemple pour tester : { -5.0, -5.0, -4.44, -2.2, 1.1, 3.0, 6.6, 7.77 } et { -11.1, -3.0, -2.2, 5.5, 8.8, 9.9 }

IV. Indices pour ce TD

III.3) Voici un algorithme possible pour résoudre ce problème :
FAIRE
  prévoir un indice pour chaque tableau
  SI aucun tableau n'est terminé
    ALORS mémoriser dans quel tableau se trouve la prochaine valeur à afficher
    SINON mémoriser dans quel tableau il reste au moins une valeur
  mémoriser la valeur du bon tableau et faire avancer son indice
  afficher la valeur mémorisée
TANTQUE au moins l'un des deux tableaux n'est pas terminé


Rappels :

double vTab[] =  
 { 1.1, -2.2, 3.0, -4.44, -5.0, 6.6, 7.77 };
déclare/crée/initialise un tableau constant (un peu comme en Java, mais remarquez la place des crochets).

En C, il n'y a pas d'attribut length comme en Java ; on doit donc presque toujours passer la taille du tableau en paramètre supplémentaire de la fonction que l'on est en train d'écrire.

Seul cas où l'on peut calculer la taille d'un tableau : dans le même bloc, juste après la déclaration ci-dessus, on peut utiliser la formule : int vNbEle =    
       sizeof(monTableau)/sizeof(double);

pour un tableau de réels, car
sizeof retourne le nombre d'octets qu'occupe son paramètre.        
Si on ajoute une valeur lors de la déclaration/initialisation,
vNbEle sera automatiquement mis à jour.      
Attention ! La formule ci-dessus n'est pas valable si vous recevez un tableau en paramètre, puisque le tableau aura été créé ailleurs.

Le fonctionnement de printf mérite d'être détaillé. En effet, pour afficher autre chose que du texte, il faut spécifier dans son premier paramètre (qui est forcément une chaîne de caractères) un format d'affichage, par exemple %d pour un nombre entier. Exemple :             
printf( "rationnel=%d/%d\n", vN, vD ); pour afficher les entiers vN et vD.       
Pour un nombre réel, le format est
%lf.     
On peut inclure autant de formats
%qqch dans la chaîne de caractères en premier paramètre de printf à condition qu'il y ait ensuite le bon nombre de variables (séparées par des virgules) et dans le bon ordre !    
Donc, 
printf a un nombre variable de paramètres, le premier étant toujours une chaîne de caractères (n+1 paramètres s'il y a n caractères % dans le format en premier paramètre).
Bon à savoir : Il est même possible de spécifier pour chaque variable réelle combien de chiffres on veut afficher après la virgule ; il suffit d'ajouter
.2 entre % et lf pour imposer seulement 2 chiffres après la virgule par exemple.
Attention, en C :
  • Il ne faut mettre de droit d'accès (public, private) nulle part, ni de final avant la déclaration de chaque paramètre.
  • A part ça, la syntaxe non Objet est la même qu'en Java, sauf que l'ordre de déclaration des fonctions a de l'importance si elles s'appellent entre elles. Les fonctions doivent donc forcément être définies AVANT le main pour pouvoir être appelées dans/par le main.
  • Par ailleurs, le main doit retourner un code d'erreur sous la forme d'un entier, ou 0 si tout s'est bien passé.
  • Pour éviter de réinventer la roue si on a besoin de la valeur absolue par exemple, il faut utiliser la fonction mathématique fabs qui retourne la valeur absolue de son paramètre réel. Mais la fonction fabs n'étant pas déclarée dans stdio.h, mais dans math.h, comme l'ensemble des fonctions mathématiques usuelles.
  • Pour déclarer les tableaux, il faut écrire double pTab[] et non double[] pTab

La ligne #include <stdio.h> n'est pas une instruction C, mais une directive à l'intention du pré-processeur.

Le pré-processeur est un programme qui est lancé par la commande gcc juste avant de lancer le compilateur C proprement dit, c'est-à-dire que le compilateur ne compile pas directement notre fichier prog.c, mais un fichier prog.i, résultat de la transformation de prog.c par le pré-processeur.

En quoi consistent principalement ces transformations :
1) inclure des fichiers
.h (qui contiennent des déclarations) tels que stdio.h, math.h, ou bien d'autres           
2) remplacer toutes les occurrences d'un mot par une suite de caractères que l'on choisit :         
    -
#define TAILLE 10   permet de définir une constante
    Attention ! 
#define TAILLE 10;  (voyez-vous la différence ?) provoquerait une erreur de compilation dans une instruction telle que int max = TAILLE / 2;
    -
#define TAB { 11, -22, 33 }   permet de définir un tableau dont on aurait besoin plusieurs fois dans la suite du programme
    Attention !  Cette ligne ne déclare aucun tableau pour le compilateur ...