Programmation C/Pointeurs
Dans cette section, nous allons présenter un mécanisme permettant de manipuler les adresses, les pointeurs. Un pointeur a pour valeur l'adresse d'un objet C d'un type donné (un pointeur est typé). Ainsi, un pointeur contenant l'adresse d'un entier sera de type pointeur vers entier.
Usage et déclaration
modifierL'opérateur & permet de connaitre l'adresse d'une variable, on dira aussi la référence. Toute déclaration de variable occupe un certain espace dans la mémoire de l'ordinateur. La référence permet de savoir où cet emplacement se trouve. En simplifiant à l'extrême, on peut considérer la mémoire d'un ordinateur comme une gigantesque table d'octets. Quand on déclare une variable de type int
, elle sera allouée à un certain emplacement (ou dit autrement : un indice, une adresse ou une référence) dans cette table. Un pointeur permet simplement de stocker une référence, il peut donc être vu comme un nombre allant de 0 à la quantité maximale de mémoire dont dispose votre ordinateur (moins un, pour être exact).
Un pointeur occupera habituellement toujours la même taille (occupera la même place en mémoire), quel que soit l'objet se trouvant à cet emplacement. Il s'agit en général de la plus grande taille directement gérable par le processeur : sur une architecture 32bits, elle sera de 4 octets, sur une architecture 64bits, 8 octets, etc. Le type du pointeur ne sert qu'à renseigner comment sont organisées les données suivant l'adresse référencée par le pointeur. Ce code, par exemple, affiche la référence d'une variable au format hexadécimal :
int i;
printf("%p\n", &i);
Pouvoir récupérer l'adresse n'a d'intérêt que si on peut manipuler l'objet pointé. Pour cela, il est nécessaire de pouvoir déclarer des pointeurs, ou dit autrement un objet pouvant contenir des références. Pour cela on utilise l'étoile (*
) entre le type et le nom de la variable pour indiquer qu'il s'agit d'un pointeur :
T * pointeur, * pointeur2, /* ..., */ * pointeurN;
Déclare les variables pointeur, pointeur2, ..., pointeurN de type pointeur vers le type T. À noter la bizarrerie du langage à vouloir associer l'étoile à la variable et non au type, qui oblige à répéter l'étoile pour chaque variable.
/* Ce code contient une déclaration volontairement confuse */
int * pointeur, variable;
Cet exemple de code déclare un pointeur sur un entier de type int
et une variable de type int
. Dans un vrai programme, il est rarement possible d'utiliser des noms aussi triviaux, aussi il est recommandé de séparer la déclaration des variables de celles des pointeurs (ou d'utiliser l'instruction typedef
, qui, elle, permet d'associer l'étoile au type), la lisibilité du programme sera légèrement améliorée.
Il est essentiel de bien comprendre ce qui a été déclaré dans ces exemples. Chaque pointeur peut contenir une référence sur un emplacement de la mémoire (un indice dans notre fameuse table). On peut obtenir une référence (ou un indice) avec l'opérateur &
(ou allouer une référence soi-même avec des fonctions dédiées, c.f la section suivante). Cet opérateur transforme donc une variable de type T
en un pointeur de type T *
. Insistons sur le terme variable, car évidemment des expressions telles que '&23435
' ou '&(2 * a / 3.)
' n'ont aucun sens, dans la mesure où les constantes et expressions du langage n'occupent aucun emplacement susceptible d'intéresser votre programme.
Ce code, par exemple, affiche la référence d'une variable dans un format défini par l'implémentation (qui peut être hexadécimal, ou une combinaison "segment:offset", par exemple) :
int i;
printf("%p\n", &i);
Il ne faut pas oublier que, comme toutes les variables locales en C, un pointeur est à l'origine non initialisé. Une bonne attitude de programmation est de s'assurer que lorsqu'il ne pointe pas vers un objet valide, sa valeur est mise à zéro (ou NULL
, qui est déclaré entre autre dans <stdio.h>
).
L'arithmétique des pointeurs
modifierL'arithmétique associée aux pointeurs est sans doute ce qui a valu au C sa réputation « d'assembleur plus compliqué et plus lent que l'assembleur ». On peut très vite construire des expressions incompréhensibles avec les opérateurs disponibles. Dans la mesure du possible, il est conseillé de se limiter à des expressions simples, quitte à les décomposer, car la plupart des compilateurs savent très bien optimiser un code C.
Déréférencement
modifierLe déréférencement ou indirection est l'opération la plus simple sur les pointeurs. Comme son nom l'indique, il s'agit de l'opération réciproque au référencement (&
). L'opérateur associé est l'étoile (*
), qui est aussi utilisé pour déclarer un type pointeur. Cet opérateur permet donc de transformer un pointeur de type T *, en un objet de type T, les opérations affectant l'objet pointé :
int variable = 10;
int * pointeur = &variable;
*pointeur = 10; /* la valeur de variable est = à 10 */
Ici, pointeur
contient une adresse valide, celle de variable
; son déréférencement est donc possible. Par contre, si pointeur
était une variable locale non initialisée, son déréférencement provoquerait à coup sûr un arrêt brutal de votre programme.
Vous obtiendrez le même résultat, si pointeur
est initialisé à NULL
. Cette adresse est invalide et toute tentative de déréférencement se soldera par un arrêt du programme.
Arithmétique de base
modifierL'arithmétique des pointeurs s'apparente à celle des entiers, mais il est important de comprendre la distinction entre ces deux concepts.
Les opérations arithmétiques permises avec les pointeurs sont :
Addition / soustraction d'une valeur entière à un pointeur (on avance / recule d'un nombre de cases mémoires égal à la taille du type T) : le résultat est donc un pointeur, de même type que le pointeur de départ.
Il faut bien faire attention avec ce genre d'opération à ne pas sortir du bloc mémoire, car le C n'effectuera aucun test pour vous. Considérez l'exemple suivant :
/* Parcours les éléments d'un tableau */
int tableau[N];
int * p;
for (p = tableau; p < &tableau[N]; p ++)
{
/* ... */
}
Normalement un tableau de N cases permet d'être itéré sur les indices allant de 0 à N - 1, inclusivement. L'expression &tableau[N]
fait référence la case mémoire non allouée immédiatement après le plus grand indice, donc potentiellement source de problème. Toutefois, par exception pour le premier indice après le plus grand, C garantit que le résultat de l'expression soit bien défini. Bien sûr, il ne faut pas déréférencer ce pointeur.
À noter qu'à l'issue de la boucle, p
pointera sur la N+1ème case du tableau, donc hors de l'espace alloué. Le C autorise tout à fait ce genre de pratique, il faut juste faire attention à ne pas déréférencer le pointeur à cet endroit.
Soustraction de deux pointeurs de même type (combien d'objet de type T y a t-il entre les deux pointeurs) : le résultat est donc un entier, de type ptrdiff_t
.
int autre_tableau[3];
int tableau[10];
int * p = &tableau[5]; /* p pointe sur le 6e élément du tableau */
int * q = &tableau[3]; /* q pointe sur le 4e élément du tableau */
ptrdiff_t diff1 = p - q; /* diff1 vaut 2 */
ptrdiff_t diff2 = q - p; /* diff2 vaut -2 */
q = &autre_tableau[2];
ptrdiff_t diff3 = p - q; /* Erreur ! */
Dans cet exemple, les deux premières soustractions sont définies, car p
et q
pointent sur des éléments du même tableau. La troisième soustraction est indéfinie, car on utilise des adresses d'éléments de tableaux différents.
Notons que l'opérateur []
s'applique toujours à une opérande de type entier et une autre de type pointeur. Lorsqu'on écrit tableau[i]
, il y a en fait une conversion de tableau à pointeur avec l'application de l'opérateur []
. On peut donc bien sûr utiliser l'opérateur []
avec un pointeur pour opérande :
int a;
int b;
int * p = &a; /* On peut accéder à la valeur de 'a' via 'p[0]' ou '*p' */
/* p[1] est indéfini - n'espérez pas accéder à la valeur de b depuis l'adresse de a */
Arithmétique avec effet de bord
modifierC'est sans doute ce qui a donné des sueurs froides à des générations de programmeurs découvrant le C : un usage « optimisé » de la priorité des opérateurs, le tout imbriqué dans des expressions à rallonge. Par exemple 'while( *d++ = *s++ );
', pour copier une chaine de caractères.
En fait, en décomposant l'instruction, c'est nettement plus simple qu'il ne parait. Par exemple :
int i;
int * entier;
/* ... */
i = *entier++; /* i = *(entier++); */
Dans ce cas de figure, l'opérateur d'incrémentation ayant priorité sur celui de déréférencement, c'est celui-ci qui sera appliqué en premier. Comme il est postfixé, l'opérateur ne prendra effet qu'à la fin de l'expression (donc de l'affectation). La variable i sera donc tout simplement affectée de la valeur pointée par entier et après cela le pointeur sera incrémenté. Voici les différents effets suivant les combinaisons de ces deux opérateurs :
i = *++entier; /* Incrémente d'abord le pointeur, puis déréférence la nouvelle adresse pointée */
i = ++*entier; /* Incrémente la valeur pointée par "entier", puis affecte le résultat à "i" */
i = (*entier)++; /* Affecte la valeur pointée par "entier" et incrémente cette valeur */
On peut évidemment complexifier les expressions à outrance, mais privilégier la compacité au détriment de la clarté et de la simplicité dans un hypothétique espoir d'optimisation est une erreur de débutant à éviter.
Le pointeur void *
modifier
Ce pointeur est un cas particulier. Il permet de pointer sur un type quelconque. Il est notamment utilisé dans la fonction malloc():
void * malloc(int n);
En pratique, il faut penser à transformer ce pointeur pour qu'il devienne utilisable, même si certains compilateurs acceptent de l'utiliser directement:
// Allocation avec conversion
int * p; // Pointeur p sur le type int
p = (int *) malloc(sizeof(int) * 10); // Allocation de 10 int, soit 20 octets
*p = 4; // Modification
// Allocation sans conversion
int * p; // Pointeur p sur le type int
p = malloc(sizeof(int) * 10); // Allocation de 10 int, soit 20 octets
*p = 4; // Modification
Tableaux dynamiques
modifierUn des intérêts des pointeurs et de l'allocation dynamique est de permettre de décider de la taille d'une variable au moment de l'exécution, comme par exemple pour les tableaux. Ainsi pour allouer un tableau de n entiers (n étant connu à l'exécution), on déclare une variable de type pointeur sur entier à laquelle on alloue une zone mémoire correspondant à n entiers :
int * alloue_tableau(int n, size_t taille)
{
return malloc(n * taille);
}
/* Ailleurs dans le programme */
int * tableau = alloue_tableau(256, sizeof *tableau);
if (tableau != NULL)
{
/* opérations sur le tableau */
/* ... */
free( tableau );
}
Cet exemple alloue un tableau de 256 cases. Bien que la variable soit un pointeur, il est dans ce cas permis d'accéder aux cases de 0 à 255, soit entre les adresses &tableau[0]
et &tableau[255]
, incluses.
Tableaux dynamiques à deux dimensions
modifierTout comme on pouvait allouer des tableaux statiques à plusieurs dimensions, on peut allouer des tableaux dynamiques à plusieurs dimensions. Pour ce faire, on commence là-aussi par déclarer un pointeurs approprié : un pointeur sur des pointeurs (etc.) sur des types. Pour déclarer un tableau dynamique d'entiers à deux dimensions :
int ** matrice;
L'allocation d'un tel objet va se dérouler en plusieurs étapes (une par étoile), on alloue d'abord l'espace pour un tableau de pointeurs vers entier. Ensuite, on alloue pour chacun de ces tableaux l'espace pour un tableau d'entiers. Si on veut une matrice 4x5 :
#define LIGNES 4
#define COLONNES 5
int i;
int ** matrice = malloc(sizeof *matrice * LIGNES);
for (i = 0; i < LIGNES; i++)
{
matrice[i] = malloc(sizeof **matrice * COLONNES);
}
Il ne faut jamais oublier de libérer la mémoire allouée précédemment. Ainsi, à tout appel de malloc
doit correspondre un appel de free
Pour libérer l'espace alloué ci-dessus, on procède de manière inverse, en commençant par libérer chacune des lignes du tableau, puis le tableau lui même :
for(i = 0; i < LIGNES; i++)
{
free(matrice[i]);
}
free(matrice);
Tableaux dynamiques à trois dimensions
modifierVoici maintenant un exemple d'une fonction créant un tableau à trois dimensions avec surtout, et c'est très important, les tests des valeurs de retour des fonctions malloc
:
int *** malloc_3d(int nb_tableau, int lignes, int colonnes)
{
int i;
int j;
int***t = malloc(sizeof(*t) * nb_tableau);
/* première dimension */
if (t==NULL)
{
printf ("Impossible d'initialiser avec malloc\n" );
exit (-1);
}
for (i=0;i< nb_tableau;i++) {
t[i] = malloc(sizeof(**t) * lignes);
/* deuxième dimension */
if (t[i]==NULL) {
printf ("Impossible d'initialiser avec malloc\n" );
exit (-1);
}
for (j=0;j<lignes;j++) {
/* troisième dimension */
t[i][j] = malloc(sizeof(***t) * colonnes);
if (t[i][j]==NULL)
{
printf ("Impossible d'initialiser avec malloc\n" );
exit (-1);
}
}
}
return t;
}
Utilisation des pointeurs sur des tableaux particuliers
modifierIl est possible avec un pointeur de lire/parcourir les éléments d'une structure. Chaque éléments d'une structure utilise un espace qui permet de calculer des déplacements.
Cependant cette utilisation est dangereuse, comme dans l'exemple ci-dessous qui ne fonctionne que sur les processeurs où la taille d'un pointeur égale celle d'un entier de type int
, ce qui n'est généralement pas le cas sur les processeurs 64 bits.
#include <stdio.h>
#include <string.h>
struct s
{
int a;
int b;
char *s;
}s;
int main(void)
{
struct s st={1,1,"salut"};
void *p=&st;
printf("%s\n", (char *)((void **)p)[2]);
/* <!> si sizeof(int) < sizeof(void*) le pointeur pointe après la structure ! */
/*<==OU==>*/
p += 2*sizeof(int);
printf("%s\n", (char *)((void **)p)[0]);
p -= 2*sizeof(int);
memset(p,-1, 2*sizeof(int));
printf("%i,%i\n", st.a, st.b);
memset((int *)p,0, 2*sizeof(int));
printf("%i,%i\n", st.a, st.b);
((int *)p)[0] = 1;
((int *)p)[1] = 0;
printf("%i,%i\n", st.a, st.b);
printf("%s\n", st.s);
return 0;
}
Utilisation des pointeurs pour passer des paramètres par adresse
modifierToutes les variables en C, à l'exception des tableaux, sont passés par valeurs aux paramètres des fonctions. C'est à dire qu'une copie est effectuée sur la pile d'appel. Si bien que toutes les modifications de la variable effectuées dans la fonction seront perdues une fois de retour à l'appelant. Or, il y a des cas où l'on aimerait bien pouvoir modifier une variable passée en paramètre et que ces modifications perdurent dans la fonction appelante. C'est un des usages des paramètres par adresse : permettre la modification d'une variable de l'appelant, comme dans l'exemple suivant :
#include <stdio.h>
/* Ce code échange le contenu de deux variables */
void echange(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int a = 5;
int b = 2;
printf("a = %d, b = %d.\n", a, b);
/* On passe à 'echange' les adresses de a et b. */
echange(&a, &b);
printf("a = %d, b = %d.\n", a, b);
echange(&a, &b);
printf("a = %d, b = %d.\n", a, b);
return 0;
}
Ce passage par adresse est extrêmement répandu pour optimiser la quantité de données qui doit transiter sur la pile d'appel (qui est, sur beaucoup de systèmes, de taille fixe). En fait, même si la variable ne doit pas être modifiée, on utilise quand même un passage par adresse, juste pour éviter la copie implicite des variables autres que les tableaux. Ceci est particulièrement intéressant avec les structures, puisque celles-ci ont tendance à être assez imposantes, et cela ne nuit pas trop la lisibilité du programme.
Tableaux dynamiques et passage de tableaux comme arguments d'une fonction
modifierComme on vient de le voir, l'intérêt principal d'une allocation dynamique est de pouvoir lancer son programme sans connaître la taille du tableau qu'on utilisera; celle-ci sera établie en cours de fonctionnement. Cependant, un autre intérêt du tableau dynamique est de pouvoir être passé comme argument d'une fonction pour des tableaux à plusieurs dimensions.
En reprenant la fonction malloc_3d()
vue précédemment, on peut écrire :
int fonction_3d (int ***tab);
int main(int argc, char **argv)
{
/* Tableau dynamique créé avec des malloc() */
int ***tab = malloc_3d(2, 10, 10);
fonction_3d (tab);
return EXIT_SUCCESS;
}
/* fonction recevant le tableau en 3d */
void fonction_3d (int ***tab)
{
/* On peut utiliser ici la notation tab[i][j][k] en veillant à
ce que i, j, k ne sortent pas des bornes du tableau -> dans
cet exemple tab[3][9][9] est illégal alors que tab[1][3][7]
peut être utilisé.
On ne pourra pas savoir si on sort du tableau !
*/
}
Pointeurs vers fonctions
modifierLes pointeurs vers les fonctions sont un peu spéciaux, parce qu'ils n'ont pas d'arithmétique associée (car une telle arithmétique n'aurait pas beaucoup de sens). Les opérations permises avec les pointeurs sur fonctions sont en fait relativement limitées :
type_retour (*pointeur_fonction)(liste_paramètres);
Déclare pointeur_fonction
, un pointeur vers une fonction prenant liste_paramètres
comme paramètres et renvoyant type_retour
. Le parenthésage est ici obligatoire, sans quoi l'étoile se rattacherait au type de retour. Pour faire pointer un pointeur vers une fonction, on utilise une affectation « normale » :
pointeur_fonction = &fonction;
/* Qui est en fait équivalent à : */
pointeur_fonction = fonction;
Où fonction
est compatible avec le pointeur (mêmes paramètres et valeur de retour). Une fois que le pointeur pointe vers une fonction, on peut appeler cette fonction :
(*pointeur_fonction)(paramètres);
/* Ou plus simplement, mais moins logique syntaxiquement */
pointeur_fonction(paramètres);
Exemple simple d'utilisation de pointeur de fonction avec retour
modifierOn fait une comparaison de deux entiers (5 et 4) via un pointeur sur une fonction de comparaison.
#include <stdio.h>
#include <stdlib.h>
int comparaison_plus_grand_que(int a, int b)
{
return a > b ;
}
int main(int argc, char * argv[])
{
int taille = 10;
int (*p_comparaison)(int,int); // pointeur de fonction
int a = 5, b = 4;
p_comparaison = comparaison_plus_grand_que;
if((*p_comparaison)(a,b)) // appel de la fonction via le pointeur
printf("%d est plus grand que %d\n", a ,b);
else
printf("%d est plus petit que %d\n", a ,b);
return EXIT_SUCCESS;
}
Pointeurs de fonctions
modifierExemple sans pointeur |
---|
On écrit une fonction graphique. Cette fonction dessine f. //INITIALISATION : f est une FORME GRAPHIQUE f := "_/-\_" //. //DECLARATION DE LA FONCTION dessine_f : fonction dessine_f() dessine f fin de fonction Cela donne le résultat : _/-\_ Avec la fonction f qui est fixe, dessine_f() ne sait dessiner que la forme "_/-\_". Si on remplace la valeur intrinsèque de f par un pointeur de fonction, la fonction pourra dessiner n'importe quelle forme. |
Même exemple en utilisant un pointeur de fonction |
---|
En C, le nom d'une fonction est un pointeur. On peut l'utiliser comme argument :
//INITIALISATION : g, h sont des FORMES GRAPHIQUES g := "/-\/-\" h := "--\/--" //. //DECLARATION DE LA FONCTION dessine_f : fonction dessine_f(P_f) dessine P_f() fin de fonction //. dessine_f(g) donnera :"/-\/-\" dessine_f(h) donnera :"--\/--" Voir Exemple graphique (avec Gnuplot) ci-dessous. |
Exemple numérique
modifier- Testé sous Code Block (Windows,Linux).
- Passer deux pointeurs de fonctions à une fonction.
Code source
modifier- Ici on passe les deux fonctions f et g à la fonction f1_o_f2().
- La même fonction peut calculer gof, fog et fof...
- On peut remarquer que les pointeurs de fonctions ont les mêmes types arguments que les fonctions qu'ils vont recevoir.
/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
/* ------ Fonction f ------------ */
double f(double x){return( pow(x,2.));}
/* ------------------------------ */
char feq[] = "x**2";
/* ------------------------------ */
/* ------ Fonction g ------------ */
double g(double x){return(2.0*x + 3.0);}
/* ------------------------------ */
char geq[] = "2.0*x + 3.0";
/* ------------------------------ */
/* -Fonction fog (g suivie de f)-*/
double f1_o_f2(
double (*P_f1)(double x),/* Pointeur pour la première fonction */
double (*P_f2)(double x),/* Pointeur pour la deuxième fonction */
double a
)
{
return((*P_f1)( ((*P_f2)(a))) );
}
/* ------------------------------ */
/* ------------------------------ */
int main(void)
{
double a = 2.0;
printf(" f : x-> %s\n", feq);
printf(" g : x-> %s\n", geq);
printf(" \n\n");
printf(" f(g(%.0f)) = %6.1f\n", a, f1_o_f2(f,g,a));
printf(" g(f(%.0f)) = %6.1f\n", a, f1_o_f2(g,f,a));
printf(" f(f(%.0f)) = %6.1f\n", a, f1_o_f2(f,f,a));
printf("\n\n Press return to continue.\n");
getchar();
return 0;
}
Résultat ;
f : x-> x**2 g : x-> 2.0*x + 3.0
f(g(2)) = 49.0 g(f(2)) = 11.0 f(f(2)) = 16.0
Press return to continue.
Exemple graphique (avec Gnuplot)
modifier- Testé sous Code Block (Windows,Linux).
- Passer un pointeurs de fonctions à une fonction.
- Vous pourrez approfondir ce travail en analyse avec le cours de Wikiversité: Mathc Home Edition.
Code source
modifier- La fonction Gplt() dessine f(x) et g(x)...
- On peut remarquer que les pointeurs de fonctions ont les mêmes types arguments que les fonctions qu'ils vont recevoir.
/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
/* --- Dessinons f et g --------- */
double f(double x){return( pow(x,2.));}
double g(double x){return(2.0*x + 3.0);}
/* ------------------------------ */
/* Le fichier de points: [a,f(a)] */
void Gplt(
double (*P_f)(double x)
)
{
FILE *fp;
double a;
fp = fopen("data","w");
for(a = -5.0; a <= 5.0; a += 0.3)
fprintf(fp," %6.3f %6.3f\n",
a, ((*P_f)(a)) );
fclose(fp);
}
/* ------------------------------ */
int main(void)
{
printf("f) Dans gnuplot -> plot \"data\" ");
Gplt(f);
getchar();
printf("g) Dans gnuplot -> plot \"data\" ");
Gplt(g);
printf("\n\n Press return to continue.\n");
getchar();
return 0;
}
Tableau de pointeurs de fonctions
modifierPremier exemple
modifierNous avons des fonctions semblables. Nous voulons les associer pour pouvoir les manipuler dans des boucles.
Nous allons créer un tableau de pointeurs de fonctions.
Je vais utiliser les fonctions trigonométriques prédéfinies pour faciliter la lecture.
Déclaration d'un tableau de pointeurs de fonctions
modifierdouble (*TrigF[6])(double x) = {cos,sin,tan,atan,asin,acos};
- Toutes les fonctions ont la même forme.
- double FUNCTION(double)
- Le tableau à la même forme que les fonctions.
- double ARRAY(double)
- Il y a six fonctions. (0,1,2,3,4,5)= {cos,sin,tan,atan,asin,acos}.
Exemple d'un appel
modifier cos(.5) = TrigF[0](.5)
Exemple à tester
modifier/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
int main(void)
{
double (*TrigF[6])(double x) = {cos,sin,tan,atan,asin,acos};
double x= .5;
int i= 0;
printf(" Nous avons declare un tableau "\
" de pointeurs de fonctions.\n "\
" J'ai utilise ici les fonctions predefinie du c.\n");
printf(" cos(%.1f) = %.3f \n", x, cos(x));
printf(" TrigF[%d](%.1f)) = %.3f\n\n",i,x,TrigF[i](x));
printf(" Press return to continue");
getchar();
return 0;
}
Application
modifier- Créer un tableau de valeurs des fonctions trigonométriques.
- Imprimer le résultat dans cette ordre (sin,cos,tan,acos,asin,atan).
- Pour .1 <= x <= .5
- Ce travail pourrait être récupéré dans une matrice.
Résultat dans un fichier
modifier/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
int main(void)
{
FILE *fp = fopen("list.txt","w");
double (*TrigF[6])(double x) = {atan,asin,acos,tan,cos,sin};
int i= 6;
double x= .1;
fprintf(fp," x || sin cos tan acos asin atan \n");
for(;x<=.5;x+=.1)
{
fprintf(fp," %.1f ||",x);
for(i=6;i;)
fprintf(fp," %.3f ",TrigF[--i](x));
fprintf(fp,"\n");
}
fclose(fp);
printf("\n\n Ouvrir le fichier list.txt\n");
getchar();
return 0;
}
Le résultat :
x || sin cos tan acos asin atan 0.1 || 0.100 0.995 0.100 1.471 0.100 0.100 0.2 || 0.199 0.980 0.203 1.369 0.201 0.197 0.3 || 0.296 0.955 0.309 1.266 0.305 0.291 0.4 || 0.389 0.921 0.423 1.159 0.412 0.381 0.5 || 0.479 0.878 0.546 1.047 0.524 0.464
Remarque :
- Attention à l'ordre des fonctions dans la déclaration du tableau.
- double (*TrigF[6])(double x) = {atan,asin,acos,tan,cos,sin};
Au démarrage :
- La décrémentation se fait dans le tableau. TrigF[--i](x)
- Il entre 6 dans le tableau.
- 6 est décrémenté -> 5 (avant l'appel de la fonction --i)
- La sixième fonction est appelée (Sin).
- La numéro est cinq. :)
Au final :
- Il entre 1 dans le tableau.
- 1 est décrémenté -> 0
- La première fonction est appelée (atan).
- La numéro zéro. :)
- i est égal à zéro en rentrant dans la boucle.
- Le cycle est cassé. :(
Avec résultat à l'écran
modifier/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
int main(void)
{
double (*TrigF[6])(double x) = {atan,asin,acos,tan,cos,sin};
int i= 6;
double x= .1;
for(;x<=.5;x+=.1)
{
printf("\n");
for(i=6;i;) printf(" %.3f ",TrigF[--i](x));
}
printf("\n\n Press return to continue.\n");
getchar();
return 0;
}
Deuxième exemple
modifierNous voulons créer la fonction Derivate capable de calculer la dérivé première et seconde d'une fonction, en utilisant un tableau de pointeurs de fonctions.
Déclaration d'un tableau de pointeurs de fonctions
modifier- Voir listing en fin de page.
double (*Derivate[3])(double (*P_f)(double x),double a,double h) = {fx,Dx_1,Dx_2};
- Toutes les fonctions (fx,Dx_1,Dx_2) ont la même forme.
- double FUNCTION(double (*P_f)(double x) double double)
- Le tableau à la même forme que les fonctions.
- double ARRAY(double (*P_f)(double x) double double)
- Il y a trois fonctions. (0,1,2)= {fx, Dx_1, Dx_2}.
- La fonction fx donne f.
- Supprimer cette fonction et travailler sur deux fonctions.
- Réfléchissez.
Exemple d'un appel
modifier f(x)=Derivate[0](f,x,0.)
- Derivate[0] donne f(x).
- Voir la fonction fx() la première fonction du tableau.
- h = 0 dans cet appel parce qu'il n'est pas utilisé (voir code de fx())
Exemple à tester
modifier/* ------------------------------ */
#include <stdio.h>
#include <math.h>
/* ------------------------------ */
/* ------ Fonction f ------------ */
double f(double x){return( pow(x,2.));}
/* ------------------------------ */
char feq[] = "x**2";
/* ------------------------------ */
/* ------ Fonction g ------------ */
double g(double x){return(
pow(cos(x),2.)+sin(x)+x-3);}
/* ------------------------------ */
char geq[] = "cos(x)**2+sin(x)+x-3";
/* ------------------------------ */
/* ----------------------------- */
double fx(
double (*P_f)(double x),
double a,
double h
)
{
return( ((*P_f)(a)) );
}
/* ------------------------------
f'(a) = f(a+h) - f(a-h)
-------------
2h
------------------------------ */
double Dx_1(
double (*P_f)(double x),
double a,
double h
)
{
return( ( ((*P_f)(a+h))-((*P_f)(a-h)) ) / (2.*h) );
}
/* -----------------------------
f''(a) = f(a+h) - 2 f(a) + f(a-h)
----------------------
h**2
------------------------------- */
double Dx_2(
double (*P_f)(double x),
double a,
double h
)
{
return( (((*P_f)(a+h))-2*((*P_f)(a))+((*P_f)(a-h))) / (h*h) );
}
/* ------------------------------ */
int main(void)
{
double (*Derivate[3])(double (*P_f)(double x),
double a,
double h) = {fx,Dx_1,Dx_2};
double x = 2;
double h = 0.001;
printf("\n\n");
printf(" f(%.3f) = %.3f = %.3f \n",x,f(x), Derivate[0](f,x,0.));
printf(" f'(%.3f) = %.3f = %.3f \n",x,Dx_1(f,x,h),Derivate[1](f,x,h));
printf("f''(%.3f) = %.3f = %.3f \n",x,Dx_2(f,x,h),Derivate[2](f,x,h));
printf("\n\n");
printf(" g(%.3f) = %.3f = %.3f \n",x,g(x), Derivate[0](g,x,0.));
printf(" g'(%.3f) = %.3f = %.3f \n",x,Dx_1(g,x,h),Derivate[1](g,x,h));
printf("g''(%.3f) = %.3f = %.3f \n",x,Dx_2(g,x,h),Derivate[2](g,x,h));
printf("\n\n Press return to continue.");
getchar();
return 0;
}