• Parfois utile de passer une fonction comme paramètre d ’ une autre fonction
• Un pointeur de fonction correspond à l ’ adresse du début du code de la fonction
• Un pointeur sur une fonction de prototype
type fonction (type_1, ..., type_n);
est de type
type (*)(type_1, ..., type_n);
Pointeurs de fonctions
• Une fonction operateur_binaire prenant en paramètres deux entiers et une fonction de type int, qui prend elle- même 2 entiers en paramètres, sera définie par :
• int operateur_binaire (int, int, int(*f)(int, int))
• Sa déclaration est donnée par
• int operateur_binaire (int, int, int(*)(int, int));
• Pour appeler la fonction operateur_binaire, on utilisera
comme troisième paramètre l ’ identificateur de la fonction
utilisée.
• Par exemple, si somme est une fonction de prototype
• int somme(int, int);
• on appelle operateur_binaire pour la fonction somme par l ’ expression :
• operateur_binaire(a,b,somme);
• Remarque : on n ’ utilise pas la notation &somme comme
paramètre effectif de operateur_binaire
Pointeurs de fonctions
• Pour appeler la fonction passée en paramètre dans le
corps de la fonction operateur_binaire, on écrit (*f )( a, b).
• Exemple
int operateur_binaire (int a, int b, int (*f) (int, int)) {
return ( (*f ) (a,b) ) ;
}
complexes
int (*(*f [ ] ) ( ) ) ; /*???????????????????/*
• Algorithme
• Répéter
•
s ’ il existe * d ’ indirection alors le traiter et l ’ éliminer
•
sinon
• s’il existe [] de tableau alors le traiter et l’éliminer
• sinon
• s’il existe des parenthèses alors les éliminer
• jusqu ’ à être ramené à un identificateur
Pointeurs
• char * f ( ) ;
• * f ( ) est un char
• f ( ) un pointeur vers char
• f une fonction retournant un pointeur vers un char
• int * f [10] ;
• *f [10] est un entier
• f [10] un pointeur vers un entier
• f un tableau (de 10 éléments) de pointeurs vers un entier
• char (*f ) ( );
• (*f ) ( ) est un char
• (*f ) une fonction qui retourne un char
• *f une fonction qui retourne un char
• f un pointeur vers une fonction retournant un char
• char *( *f ) ( );
• *( *f ) ( ) est un char
• (*f ) ( ) un pointeur vers un char
• (*f ) une fonction retournant un pointeur vers un char
• *f une fonction retournant un pointeur vers un char
• f un pointeur vers une fonction retournant un pointeur vers un char
Pointeurs
• static char * c [ ] = { « MOINS », « PLUS », « EGAL »} ;
• * c [ ] est un char
• c [ ] pointeur vers char
• c tableau de pointeurs vers char
• TABLEAU DE FONCTIONS
• Ecrivez un programme calfon permettant d ’ obtenir à la console des valeurs des fonctions : sin, cos, exp, log.
• Exemple :
$ calfon sin 0.5 0.479426
$ calfon log10 23
log10 : fonction inconnue
Compilation séparée
• Modularité
•
Un programme doit être découpé en plusieurs fichiers, même de petites tailles (réutilisabilité, lisibilité, etc.). Chaque
composante logique (un module) regroupe les fonctions et types autour d'un même thème.
•
Les modules s'appellent les uns les autres. Si M1 utilise une
fonction de M2, il n'a pas besoin du code de la fonction mais
juste de sa signature, c'est à dire son nom, le nombre de
paramètres, leur ordre et leur type, et le type retourné.
•
Remarque : si le compilateur rencontre un appel à une fonction qu'il ne connaît pas (qui n'a pas été déclarée précédemment), il ne râle pas et considère par défaut que c'est une fonction de type int.
Il ne se mettra à râler que si
• il rencontre plus tard une définition contradictoire pour cette fonction
• on lui demande de faire avec cette fonction quelque chose qu'on ne peut pas faire avec un int
•
S'il ne trouve nulle part le code de cette fonction, c'est lors de
l'édition des liens qu'il râlera.
Compilation séparée
• Pour chaque module truc, on fera 2 fichiers
•
fichier entête truc.h.
L'interface. Contient la signature de toutes les fonctions exportées et la déclaration des types exportés.
•
fichier truc.c.
Le code de toutes les fonctions exportées, déclaration de variables locales, fonctions locales, types locaux. Et contient au début
l'inclusion du .h (pour vérifier qu'on raconte bien la même chose partout)
• Tout module ou programme principal qui a besoin des
fonctions du module truc, devra juste inclure le fichier
truc.h
• Exemple :
•
module tab de manipulation de tableaux
•
module tri qui offre plusieurs méthodes de tris de tableaux
•
un programme principal qui veut créer un tableau, le trier par le tri
bulle et l'afficher.
Compilation séparée
fichier tab.h
/* module de gestion de tableaux */
typedef int *TAB; // type TAB = un tableau
void lecture(TAB,int); // fonction de lecture d'un tableau de taille donnee
// ... commentaire sur ce que fait chaque fonction
void affichage(TAB,int);
TAB creation(int);
int acces(TAB,int);
#include « tab.h »
TAB creation(int taille) { malloc ...
return ...
}
void lecture(TAB t,int taille){
int i;
for (i=0;i<taille;i++) ...
}
...
Compilation séparée
fichier tri.h
/*module de tris de tableaux*/
// pour connaitre le type TAB
#include « tab.h »
void tri_bulle(TAB,int);
void tri_rapide(TAB,int);
fichier tri.c
#include « tri.h »
void tri_bulle(TAB t,int taille){
...
}
void tri_rapide tri_bulle(TAB t,int taille){
...
}
fichier main.c
#include « tab.h »
#include « tri.h » TAB montab;
int main( ){
montab=creation(10);
lecture(montab,10);
tri(montab,10);
affichage(montab,10);
}
Compilation séparée
• Comment relier tout ça ?
•
avec tab.c et tab.h, on forme un fichier objet tab.o
•
avec tri.c, tab.h et tri.h, on forme un fichier objet tri.o
•
avec main.c, tri.h et tab.h, on forme un fichier objet main.o
• On fait l'édition des liens des main.o tri.o et tab.o et on obtient un éxecutable.
• C'est à dire qu'il faut donner les ordres de compilation : gcc -c tab.c
gcc -c tri.c gcc -c main.c
gcc tab.o tri.o main.o -o prog
MonProg: main.o tab.o tri.o
<tab> gcc main.o tab.o tri.o -o MonProg
main.o : main.c tab.h tri.h
<tab> gcc -c main.c
tri.o : tri.c tri.h tab.h
<tab> gcc -c tri.c
tab.o : tab.c tab.h
<tab> gcc -c tab.c
Taper make pour l'exécuter.
Compilation séparée
• Intérêt :
•
indispensable quand il y a beaucoup de fichiers (plutôt que de retaper à chaque fois tous les ordres de compilation)
•
il ne fait que ce qui est nécessaire : il ne recompile quelque chose
que si la dernière compilation est plus ancienne que la dernière
modification d'un fichier (c'est pourquoi il est indispensable de
mettre tous les .h utilisés).
•
On peut déclarer des variables VAR=machins qu'on utilise
ensuite par $(VAR). La maintenance et la mise à jour sont alors plus faciles à gérer.
fichier Makefile CC = gcc
OBJ = main.o tab.o tri.o MonProg: $(OBJ)
$(CC) $(OBJ) -o MonProg main.o : main.c tab.h tri.h
tri.o : tri.c tri.h tab.h
tab.o : tab.c tab.h
Compilation séparée
• Utilisation de macros spéciales :
•
$@ nom de la cible à reconstruire
•
$* nom de la cible sans suffixe
•
$< nom de la dépendance à partir de laquelle on reconstruit la cible
•
$? liste des dépendances plus récentes que la cible
• On peut rajouter dans le makefile une ligne pour effacer les fichiers objets
clean :
rm –f *.o
Préprocesseur
• Qu ’ est-ce que c ’ est ?
•
Simple substitution de texte
•
Augmente la lisibilité du code source
•
Augmente la vitesse d ’ exécution du programme
•
Le preprocessing intervient avant la phase de compilation
• Directives
•
Une directive au préprocesseur commence toujours par #
•
#include, #define, #ifdef…
•
Les directives sont effectives sur tout le fichier.
•
Remplacement des directives de précompilation par du code en
C (compréhensible par le compilateur)
• Définies par la directive #define
• Deux formes:
•
Simple remplacement d ’ une chaîne de caractère.
•
Remplacement de la chaîne et substitution d ’ argument.
• Une macro peut être définie sur plusieurs lignes (retour
chariot)
Macro simple
#define IDENTIFIANT chaîne
• chaîne est une séquence de caractères
• Toute occurrence de IDENTIFIANT dans le texte sera remplacé par chaîne
• Exemple :
• #define PI !3.14159!
• Remplacera durant la phase de préprocessing toutes les
occurrences de la chaîne « PI » dans le fichier C par la chaîne
« 3.14159 ».
• Autre forme de déclaration de constantes
• On peut définir les variables de précompilation à l'appel de l'enchaîneur de passes
option –Dnom=valeur
• Le préprocesseur changera les occurrences de la
variable nom par la valeur valeur, comme si la
constante de précompilation nom était définie par
la directive #define nom valeur.
Macro avec arguments (Macro- instruction)
• Syntaxe
#define <chaîne1>(<liste paramètres>) <chaîne2>!
(pas d’espace entre l’identifiant et la parenthèse)!
!
• Exemple
• Macro qui incrémente une variable
• #define inc(x) x++!
• Toute occurrence de « inc(<variable>) » sera
respectivement remplacée par <variable>++ durant
la phase de précompilation (indépendamment de son
type)
• Code original:
#define MIN(X,Y) ( (X)<(Y) ? (X) : (Y) )
….
MIN(a,b);
• Code intermédiaire:
….
( (a) < (b) ? (a) : (b) )
Macros prédéfinies
• Dans stdio.h
• #define getchar() getc(stdin)
• #define putchar(c) putc(c,stdout)
#define SQR(X) X*X
…
SQR(a+1);
• Code intermédiaire:
…
a+1*a+1; //ce qui est égal à a+(1*a)+1
• Solution: parenthéser:
#define SQR(X) (X)*(X)
Les macros ne sont pas des fonctions
• Code original:
#define SWAP(X,Y) {int temp; temp=X; X=Y; Y=temp;}
void main(int){
int a=3, b=4;
SWAP(a,b);
}
• Code intermédiaire:
void main(void){
int a=3,b=4;
{int temp; temp=a; a=b; b=temp;}
}
• __FILE__ : nom du fichier source courant
• __LINE__ : numéro de ligne courant
• __DATE__ : date de compilation du fichier courant
• __TIME__ : heure de compilation du fichier courant printf(``%s a été compilé le %s\n``,
__FILE__,__DATE__);
Opérateurs de Macro
• # permet de convertir l ’ argument en chaîne de caractères
#define MSG(F) printf(#F)
…
MSG(test mess); //devient printf(« test mess »);
• ## permet de concaténer
#define ERR(X,Y) printf(X ## Y)
…
ERR(``err: ``,``une erreur``);
//devient printf(``err: une erreur``);
• Il n ’ est pas permis de redéfinir des macros, sauf si elles ont été dédéfinies par #undef au
préalable.
• #undef est ignoré si l ’ identifiant n ’ a pas été défini
• #undef peut dédéfinir des macro prédéfinies!!!!
Inclure des fichiers
• On inclut des fichiers textes par #include
• Le texte du fichier est alors recopié à cet endroit.
• #include < stdio.h > cherche dans le chemin standard le fichier stdio.h
• #include ``monFichier.h`` cherche le fichier
monFichier.h dans le répertoire courant.
• Écrire une macro qui calcule le maximum de deux
nombres et écrire un programme de test qui l'utilise
Solution
• #include <stdio.h>
• /* Definition de la macro */
• #define max(x,y) (((x) > (y)) ? (x) : (y))
• /* Le programme de test */
• int main ( ) {
• int n1, n2;
• /* Deux nombres a fournir par l'utilisateur */
• /* Demande et lecture des deux nombres */
• printf("Introduire n1 : ");
• scanf("%d", &n1);
• printf("Introduire n2 : ");
• scanf("%d", &n2);
• /* Calcul du maximum et affichage du resultat */
• printf("max(%d, %d) = %d\n", n1, n2, max(n1, n2));
• }
d'ajouter d'autres catalogues à sa recherche
•
option de compilation -Inom_du_catalogue. Cette option peut être utilisée plusieurs fois de manière à spécifier plusieurs catalogues de recherche.
• Les fichiers inclus peuvent contenir d'autres
inclusions de fichiers. Ce processus récursif est
parfois limité à quelques niveaux d'inclusion.
Inclure des fichiers
• Si le nom du fichier est entre guillemets, le préprocesseur cherche le fichier :
•
dans le catalogue courant,
•
puis dans les catalogues spécifiés par les options -I.
• Si le nom du fichier est entre < >, le préprocesseur cherche le fichier :
•
dans les catalogues spécifiés par les options -I,
•
puis dans le catalogue par défaut du compilateur.
#include <stdio.h>
typedef int *p_int;
#include ``prog1.h``
int main(void){
}