Issu de : http://www.tangentex.com/IntroCCN.htm#Par6
Cette page est destinée aux débutants en programmation et en calcul numérique: étudiants en sciences, élèves de classes prépa. et tous ceux qui veulent s'initier aux joies de la physique numérique ou plus simplement du calcul. Elle est orientée C. Ce langage et son frère le C++ prennent de plus en plus d'essor en calcul scientifique et sont déjà très répandus dans l'industrie. Pour ceux qui préfèrent le FORTRAN, je les invite dans la page "Introduction au FORTRAN".
Le langage C est d'un abord assez rébarbatif, surtout lorsqu'on aborde certains aspects comme les pointeurs ou les structures de données. Dans cette page, je présente un résumé des principales caractéristiques du C indispensables pour écrire des programmes de calcul. Comme pour le FORTRAN, je me limiterai au stricte nécessaire à un physicien. Nous ne sommes pas informaticiens, quoique...
Le langage C a été mis au point par Brian Kernighan et Dennis Ritchie (les célèbres K&R) des Bell Laboratories en 1972, après qu'ils se soient fait la main sur les langages A et B. Le langage C est le langage de prédilection pour le développement des systèmes. La quasi-totalité des systèmes d'exploitation comme Unix et Linux (par nature) mais aussi Windows sont écrits en C, avec quelques parties intimes en assembleur.
La définition du langage C est du domaine public. L'institut américain de normalisation, l'ANSI, a normalisé ce langage en 1989, que l'on connaît maintenant sous le vocable C-ANSI.
Il existe un compilateur C pour presque toutes les plateformes et ordinateurs. Un programme écrit en C standard (conforme à la norme ANSI) pourra être compilé sur un PC ordinaire, une station de travail SUN ou un gros IBM... Toutefois, attention! Chaque machine possède sa propre bibliothèque spécialisée et optimisée pour sa CPU. Donc, votre code sera portable que s'il reste strictement ANSI. Cependant, pour optimiser le code, on aura tendance à utiliser la bibliothèque "top" du constructeur, et là, on perd l'avantage de la portabilité: c'est un choix difficile. Pour tout dire, on sacrifie la plupart du temps la portabilité à l'efficacité.
On ne peut parler de C sans évoquer le C++. Ce dernier découle directement d'une extension fondamentale (le concept objet) du C, introduite par Bjarne Stroustrup en 1982. L'ISO a normalisé le C++ en 1998, ce qui a permis son essor dans l'industrie. Le C++ tend à supplanter le C dans tous les domaines d'applications, sauf peut être les systèmes embarqués où le besoin d'un code compact est primordial. Les programmeurs intéressés devront absolument lire le "The C++ programming language" (dernière édition de 2000) publié en 1985 par Bjarne, qui est la bible du C++ (son K&R...)
L'apprentissage du C++ est assez difficile. J'estime pour ma part que l'apport du concept objet en calcul numérique n'est pas primordial, et qu'en tous les cas, il ne compense pas le travail et l'expérience requise pour maîtriser le C++. Je n'aborderai donc pas ce langage ici.
Organisation d'un programme C
Un programme C est construit autour d'une architecture assez typique. Elle ressemble à cela:
#include <stdio.h>
#include <stdlib.h>
<déclaration des constantes symboliques>
float fonction1(<liste d'arguments>);
void fonctionN(<liste d'arguments>);
<déclaration des variables globales>
int main(<liste d'arguments>)
{
< déclaration des variables locales>
<instructions>
return <code retour>
}
float fonction1(<liste d'arguments>)
{
<déclaration des variables locales>
<instructions>
return <code retour>
}
void fonctionN(<liste d'arguments>)
{
<déclaration des variables locales>
<instructions>
return
}
Un peu d'explications!
Un programme C est un assemblage de blocs de programmation que l'on appelle fonctions, dont l'une est un peu particulière. On la nomme main et c'est la fonction principale du programme, celle que l'on exécute en premier et qui appelle toutes les autres.
Toutes les fonctions sont organisées de la même façon:
Une fonction est identifiée par son prototype, qui contient:
Le corps de la fonction est borné par une parenthèse ouvrante et une parenthèse fermante.
Dans le corps d'une fonction, on trouve:
Avant de rencontrer la description des fonctions, on observe:
Les types de données sont assez similaires à ceux du FORTRAN. Les principaux utilisés en calcul sont:
NOTA : les int et les long peuvent être non signés (unsigned)variant donc resp. entre 0 et 216-1 et 0 et 232-1
Citons encore le char, qui stocke un caractère sur un octet. Il en existe d'autres dont nous parlerons plus loin et encore d'autres dont nous ne parlerons pas.
Il existe deux manières de déclarer une constante:
1 - Utiliser la directive #define, par exemple:
#define PI 3.141592
Cette directive impose au compilateur de remplacer dans tout le texte du programme la chaîne PI par la valeur 3.141592. Il est possible de rassembler tous les #define dans un même fichier header et de l'inclure dans le programme par une directive #include. Une constante symbolique n'est pas typée et prend le type de la variable d'affectation. Avec cette directive, la portée de la constante couvre tout le programme.
2 - Utiliser le mot clé const, par exemple:
const int nbmax = 1000;
const double PI = 3.141592654
Dans ce cas, la portée de la constante dépend de l'endroit de sa déclaration: globale si elle est déclarée dans la zone globale, locale lorsqu'elle est déclarée dans une fonction.
Dans un programme C, on doit déclarer toutes les variables en précisant le type de chaque variable. Une variable est donc caractérisée par son nom et son type.
Son nom peut contenir des chiffres et des lettres. Sa longueur ne doit pas dépasser 256 caractères mais seuls les 31 premiers caractères sont significatifs. Le nom doit débuter par une lettre.
Voici quelques déclarations de variables:
int toto; // Correcte
float Rayon; // Correcte
double double; // Incorrecte, le mot double est un mot clé du C
float Rayon, Volume; // il est possible de définir plusieurs variables sur la même ligne...
int 9neuf; // Incorrecte, le nom débute par un chiffre.
Le C possède les opérateurs arithmétiques binaires classiques:
Notez qu'il ne possède pas d'opérateur d'exponentiation. Il faut utiliser la fonction mathématique pow().
C possède deux opérateurs unaires assez pratiques:
ATTENTION : la place de l'opérateur unaire est importante, selon qu'il soit disposé avant ou après la variable. S'il est disposé:
et cela a une grande importance. Par exemple, dans le code suivant:
x = i++; x prend la valeur de i puis i st incrémenté, i.e si i= 10, après l'exécution de l'instruction, x=10 et i =11
x = ++i; i est incrémenté puis x prend la valeur de i, et donc dans ce cas, après exécution, x=11 et i=11, ce qui n'est pas tout à fait pareil!
Le C permet quelques fantaisies du style:
Les règles de position de l'opérateur énoncées ci-dessus sont aussi valables dans ces cas.
Ces subtilités peuvent être pratiques pour écrire un code concis. Ne pas en abuser sous peine de rendre votre code illisible....
La libraire qui correspond au header math.h contient toutes les fonctions mathématiques courantes. Lorsque vous utilisez un sin ou un cos ou encore un pow, n'oubliez pas:
Avant d'aborder la suite, je suis obligé de faire une petite digression pour parler des pointeurs. Il n'est guère envisageable de faire du C sans évoquer les pointeurs et leur gestion. On en aura besoin pour manipuler certaines fonctions d'entrées/sorties et dans certains cas pour les tableaux. Alors allons-y!
Dans une mémoire d'ordinateur, une information, ar exemple un entier I, occupe un espace, un certain nombre de mots mémoire (2 pour I si I est de type int). Cet espace est identifié par le système d'exploitation par son adresse. I est stocké à l'adresse X et occupe donc l'espace mémoire X et X+1.
Un pointeur est une variable dans laquelle on stocke l'adresse d'une autre variable, adresse qui a été attribuée par le compilateur. Reprenons notre exemple.
Créons la variable p_I pour stocker l'adresse de I (adresse qui vaut X dans notre exemple). En C, on créé cette variable par l'instruction:
int I;
int *p_I;
Le * (nommé opérateur d'indirection) rappelle que la variable p_I désigne un pointeur sur une variable de type int.
Il nous reste à récupérer l'adresse de la variable I, ce que nous faisons en utilisant un autre opérateur, qui est l'opérateur d'adresse noté & ce qui nous donne: p_I = &I;
Cette instruction indique que l'on stocke dans le pointeur p_I l'adresse de la variable I (qui vaut X dans notre exemple). Attention ici, premier piège des pointeurs: la règle est de charger l'adresse d'une variable dans un pointeur de même type que cette variable. Pas question de charger l'adresse d'une variable float dans un pointeur d'int!
Voyons maintenant l'usage de l'opérateur d'indirection *. Il vous permet d'écrire indifféremment I ou *p_I pour désigner le contenu de la variable I. *p_I signifie "le contenu de la zone mémoire pointée par p_I". Et là, on comprend l'intérêt du typage du pointeur, si l'on se rappelle qu'un int occupe 2 octets et un float 4!
Les pointeurs étant des variables comme les autres, on peut leur appliquer tous les opérateurs arithmétiques, bien que très généralement on se limite à l'addition et la soustraction. Nous verrons un exemple de cela lorsqu'on abordera les tableaux.
Pour l'instant, on va en rester là avec les pointeurs. Sachez qu'il existe aussi des pointeurs de pointeurs, notés **, sources de pleins de pièges mais aussi de pas mal de subtilités du C. Voir votre cours de C habituel!
Voyons les instructions qui nous permettent d'afficher un résultat sur l'écran ou de saisir un paramètre depuis notre clavier. A ce stade nous en resterons à ce genre d'entrée/sortie. Nous parlerons des fichiers plus loin.
Sachez néanmoins que le C renferme un bon nombre de fonctions permettant de lire un caractère ou une chaîne de caractères depuis le clavier ou un autre périphérique d'entrée (fichier, port série, etc..) et d'écrire des caractères vers ces mêmes périphériques (on dit flux en C...). Je vous renvoie vers votre cours de C pour toutes ces subtilités inutiles dans le cadre de cette introduction.
Une autre chose importante : le C ne connaît pas les entrées/sorties "non formatées", analogues au WRITE(*,*) du FORTRAN. Il faut toujours préciser le format de ce que l'on lit ou écrit.
Dans le cadre de cette initiation, nous n'utiliserons qu'une seule fonction C pour lire nos paramètres depuis le clavier. Il s'agit de la fonction scanf(). Sa syntaxe est:
int scanf( const char *format [,argument]... );
La fonction scanf()lit la suite de caractères introduits au clavier jusqu'au caractère RETURN (ou ENTREE) puis écrit cette suite de caractères (sans le RETURN) dans la variable argument, au format fixé par format. Par exemple, si format est "%d" alors les caractères saisis seront considérés comme constituant un entier et écrit comme tel dans une variable qui devra être de type int.
La fonction scanf() retourne une valeur entière qui est nulle en cas de problème et non nulle si la lecture a été correctement réalisée. Bien souvent (ce sera le cas dans cette introduction), on ne tient pas compte du code retour de scanf().
Prenons un exemple. Il s'agit de lire un réel (au format double) depuis le clavier. Le code correspondant sera:
scanf("%lf",&Rayon);
Le format "%lf" indique qu'il s'agit d'un flottant long, ce qui correspond à un double.
L'écriture &Rayon désigne comme on l'a vu plus haut l'adresse de la variable Rayon, l'endroit où le programme doit écrire la suite de caractères saisis.
On peut également lire plusieurs données, même de type différent, comme par exemple:
scanf("%lf %d",&Rayon,&Nbpas);
La chaîne de caractères format indique le type de variable lue. Citons parmis tous les formats possibles, ceux qui nous servirons les plus:
Il en existe bien d'autres. Je vous invite à consulter votre cours de C ou bien l'aide de votre compilateur.
Pour utiliser scanf(), n'oubliez pas de faire l'include de <stdio.h>.
Une des fonctions du C les plus utilisées, pour debugger! Il s'agit même souvent de la première instruction C connue: la célèbre printf(). Cette fonction écrit ce que l'on veut sur notre écran.
Sa syntaxe est identique à celle de scanf():
int printf( const char *format [, argument]... );
La fonction printf() écrit la suite de caractères contenus dans la variable argument, au format fixé par format. Par exemple, si format est "%d"alors les caractères seront considérés comme constituant un entier et écrit comme tel à l'écran.
La fonction printf() retourne une valeur entière qui est négative en cas de problème et le nombre de caractères écrits en cas de succès. Bien souvent (ce sera le cas dans cette introduction), on ne tient pas compte du code retour de printf().
La chaîne format prend les mêmes valeurs que pour scanf(), auquel on peut ajouter:
"\a" pour écrire (déclencher plutôt) un ring (la cloche...)
"\n" pour passer au début de la ligne suivante. Très employé!
entre autres. L'instruction printf() peut être assez sophistiquée. Pour mieux la connaître, je vous renvoie à l'aide de votre compilateur. Avec ce que j'ai mentionné ci-dessus, on devrait couvrir tous nos besoins.
Un exemple d'utilisation de printf():
printf("\nLe volume de la sphere est de: %f cm3 \n", Volume);
Cette instruction revient à la ligne suivante sur l'écran (par la présence du \n) et écrit sur l'écran le contenu de la variable Volume en flottant. Notez l'usage des constantes chaînes (le texte dont vous souhaitez agrémenter vos résultats), entourés par des guillemets. Dans cet exemple, la valeur de Volume s'affiche à l'endroit du texte où se situe le %f.
Un exemple d'usage très courant de printf(),accompagné de scanf(), pour saisir un paramètre:
printf("Rayon de la sphere (en cm) : ");
scanf("%lf", &Rayon);
Ici, le texte d'invite à la saisie est affiché à l'écran sans saut de ligne. L'utilisateur peut saisir le rayon, qui sera récupéré par le programme comme un double.
Le programme VolumeSphere
Pour notre premier programme C, nous calculerons le volume d'une sphère en fonction de son rayon que nous indiquerons au programme.
Le code source de ce programme:
/* Declaration des headers des librairies utilisés */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/* Declaration des constantes */
#define PI 3.141592654
/* Programme de calcul du volume d'une sphere
Dominique LEFEBVRE TangenteX.com avril 2006
*/
int main(int argc, char *argv[])
{
// declaration des variables du programme
double Rayon, Volume;
// Saisie du rayon
printf("Rayon de la sphere (en cm) : ");
scanf("%lf", &Rayon);
// Calcul du volume
Volume = (4*PI/3)*pow(Rayon,3);
// Affichage du resultat
printf("\nLe volume de la sphere est de: %f cm3 \n",Volume);
// Fin du programme
system("PAUSE");
return EXIT_SUCCESS;
}
Il commence par une ligne de commentaire. Bonne habitude à prendre que de commenter son code. Notez qu'en C moderne on peut utiliser deux types de commentaires:
Puis viennent les déclarations des fichiers d'en-têtes (header, avec une extension .h) des librairies qui seront utilisées dans le programme. Vous notez que le nom du fichier est encadré par <>. Ce n'est pas toujours le cas. Vous pourrez voir des noms de headers encadrés par "". C'est une histoire de localisation des fichiers, sur laquelle je n'insisterai pas ici. Notez la directive include précédé d'un #.
Puis la déclaration de la constante PI à l'aide d'un #define, assez classique.
Et le corps du programme... Là application bête et méchante du cours...
On déclare les variables "double". En effet, la librairie mathématique de C ne manipule que des double! Si un jour dans un programme, vous avez des résultats surprenants, il y a fort à parier que vous avez utilisez des float avec la librairie math. La conversion implicite float <-> double est parfois déroutante.
Puis vient la saisie du rayon. Le printf() est sans piège (ici...) mais attention au scanf() et à son opérateur d'adresse (le & devant Rayon). Si vous l'oubliez, le compilateur ne dira rien. Et à l'exécution, plantage garanti! Cette instruction est un vraie piège. Rien à voir avec un bête READ de FORTRAN!
Dans le printf(), le \n qui débute le commentaire permet d'effectuer un retour à la ligne suivante.
Puis fin du programme, avec un system("PAUSE") pour vous permettre de lire votre résultat. La commande system permet de faire exécuter par le programme C une commande du système d'exploitation. Cela peut être utile, mais pas vraiment en calcul!
Dans le return, notez l'usage de EXIT_SUCCESS, une constante de la librairie standard, qui vaut 0.
Pour développer en C sous Windows, il faut un environnement de développement, intégré de préférence, ce que l'on nomme un IDE. Il vous permet d'éditer vos codes sources, d'organiser vos projets, de compiler, de debugger, d'exécuter vos programmes et plein d'autres choses!
On peut citer deux IDE gratuits : Code::Blocks (nous utiliseraons principalement celui-ci) et Dev-C++.
Saisir le programme dans votre IDE.
Commençons par lancer notre IDE en cliquant sur l'icone Dev-C++ (sur le bureau ou dans la barre de lancement rapide). Un espace de travail s'ouvre avec un menu au standard Windows en haut de la fenêtre.
Nous allons créer le projet VolumeSphere dans le répertoire de votre choix. Pour ce faire, allez dans le menu Fichier\Nouveau\Projet et cliquez. Une fenêtre s'ouvre pour saisir le nouveau projet. Choisissez l'option "Console Application", indiquez le nom du projet (VolumeSphere) et indiquez aussi qu'il s'agit d'un projet C puis cliquez sur OK. L'IDE ouvre une fenêtre pour vous permettre de choisir votre répertoire. Sélectionnez-le et enregistrez.
Puis une fenêtre de saisie s'ouvre en présentant un squelette de programme, qui ressemble à ça:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
system("PAUSE");
return 0;
}
Nous allons l'utiliser en le complétant pour saisir notre programme d'après le code source ci-dessus.
Lorsque vous avez saisi ce code, il convient de sauvegarder le fichier en faisant Fichier\Sauvegarder. L'IDE ouvre une fenêtre pour vous permettre de choisir le nom du fichier source (VolumeSphere) et le répertoire de sauvegarde. Par défaut, c'est le répertoire du projet, conservez-le. L'IDE attribut automatiquement l'extension .c au fichier, donc ne vous en préoccupez pas.
Faites attention à la saisie du code ! Comme indiqué plus haut, le C est "case sensitive": Rayon et rayon ne désignent pas la même variable ! C'est un piège classique. Attention aussi à ne pas oublier le ; en fin d'instruction.
Rien de plus simple. Compiler et Exécuter le programme. Une fenêtre s'ouvre qui vous permet de suivre la compilation. Si vous avez respecté la syntaxe, il ne doit pas y avoir de problème. Lorsque le programme est compilé avec succès, une fenêtre de commande s'ouvre et le programme débute son exécution.
Il vous demande un rayon. Entrez une valeur quelconque, 1.7 par exemple. Puis il affiche le volume (20,579526 en l’occurrence). Et voilà...
Nous allons pouvoir aller un peu plus loin maintenant...
Pour aborder les conditions, il faut d'abord parler d'expression logique.
Il existe en C un type de variable boolean. Une variable de type boolean peut prend deux valeurs: true ou false Il faut savoir que true vaut 1 et false vaut 0.
C définit les expressions logiques suivantes, dans lesquelles x et y sont deux variables :
Il définit aussi les opérateurs logiques binaires ou unaires:
Munis de ces expressions logiques, on peut définir les instructions conditionnelles suivantes:
if (expression) <instruction>;
ou, s'il y a plusieurs instructions à exécuter si expression est vraie:
if (expression) then
{
<liste d'instructions>
}
La signification de ce jeu d'instructions est simple: si expression est vraie, par exemple x > 0, alors la liste d'instructions contenue dans le bloc entre parenthèses est exécutée. Sinon, le programme continu et exécute l'instruction immédiatement suivante le bloc.
Il existe plusieurs variantes d'instructions conditionnelles. Par exemple, la condition alternative:
if (expression)
{
<liste d'instructions 1>
}
else
{
<liste d'instructions 2>
}
Si expression est vraie, alors la liste d'instructions 1 est exécutée (et pas la liste d'instructions 2). Si expression est fausse, c'est la liste d'instruction 2 qui est exécutée. Il s'agit en fait de la forme complète de l'instruction conditionnelle if.
Le langage C possède une instruction très sympathique, le 'switch', qui permet de faire des traitements différents en fonction de la valeur d'une variable. En voici le principe:
switch(expression)
{
case cond1:
{
liste d'instructions 1;
}
case cond2:
{
liste d'instructions 2;
}
case cond3:
{
liste d'instructions 3;
}
default:
{
liste d'instructions en cas de défaut
}
}
Le fonctionnement est simple. Si la valeur expression est égale à une des valeurs condi déterminées, alors le bloc d'instructions correspondant (et seulement celui-ci) est exécuté. Si aucune valeur déterminée ne correspond à la valeur expression, c'est le bloc d'instructions default qui est exécuté. Pratique non!
Attention, expression doit être un long, un char ou un int. Sinon, il faut utiliser l'instruction conditionnelle :
if (expression1)
{
<liste d'instructions 1>
}
else if
(expression2)
{
<liste d'instructions 2>
}
else if (expression3)
{
<liste d'instructions 3>
}
La boucle for
Sa syntaxe est:
for (initial; condition; incrément)
{
liste d'instructions
}
Cette boucle est exécutée tant que condition est vraie. Par défaut, incrément vaut 1. Il peut être négatif ou positif.
Exemple de boucle for
int i;
for (i=0;i<= 10; i++)
{
printf("%d\n", i);
}
Dans ce cas, les parenthèses sont inutiles: je les mets par habitude, au cas où il me prendrait envie d'ajouter du code dans la boucle...
La boucle while
Sa syntaxe est:
while (condition)
{
liste d'instructions
}
Cette boucle est exécutée tant que condition est vraie. Si j'écris le code précédent en utilisant un boucle while, cela donne:
int i;
i = 0;
while (i<= 10)
{
printf("%d\n", i);
i++;
}
Dans ce cas, pas grand intérêt! Pourtant la boucle while est souvent utilisée lorsque la valeur de condition dépend d'une action extérieure (opérateur, environnement).
Notez que si condition est fausse avant d'entrer dans la boucle, la liste d'instructions n'est jamais exécutée.
La boucle do..while
Sa syntaxe est:
do
{
liste d'instructions
}
while (condition)
La boucle do while est très similaire à la boucle while. La seule différence est algorithmique. Dans la boucle do while , le bloc d'instruction est toujours exécuté au moins une fois, même si condition est fausse avant d'entrer dans la boucle. Cela peut servir dans certains traitements.
Bien sur, toutes ces boucles peuvent être imbriquées entre elles.
Comme en FORTRAN, il est bien sur possible de déclarer et d'utiliser des tableaux de une ou plusieurs dimensions (vecteurs ou matrices). Mais en C, les choses ne sont pas aussi simples qu'en FORTRAN...
Voyons d'abord la déclaration et l'usage de tableaux dont on connait les dimensions et que l'on ne veut pas passer en paramètre d'une fonction.
Pour déclarer un tableau de type donné et de dimension donnée, rien de plus simple. Considérons le cas d'un vecteur de double de 10 éléments. Je le déclare en écrivant:
double tableau1[10];
Une chose très importante: en C la première position d'un tableau est la position 0 (en FORTRAN c'est 1). Les éléments de mon tableau1 sont donc numérotés de 0 à 9.
Autre chose : si vous essayez d'adresser le10eme élément du tableau, gare à vous...
Si vous voulez déclarer une matrice de float à 2 dimensions vous écrirez:
float matrice1[10][10];
ce qui vous donnera une matrice 10x10 de float.
Pour désigner un élément d'un tableau, par exemple pour l'assigner à une variable, vous écrirez:
int i,j;
float v,w;
float matrice1[10][10];
v = 0.112;
matrice1[i][j] = v;
w = matrice1[i][j];
Pour initialiser un tableau, lors de sa déclaration: int tableau2[4] = {1,2,3,4};
int tableau3[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
Notez l'ordre: on commence par la première ligne en faisant varier le dernier indice. Les valeurs pour chaque ligne sont regroupées entre parenthèses.
Une chose importante lors de la déclaration d'un tableau. Vous devez utiliser une constante numérique pour dimensionner votre tableau. Il est par exemple interdit d'écrire:
const int imax = 10;
float tableau1[imax];
mais vous pouvez, et c'est même une très bonne habitude à prendre, écrire:
#define IMAX 10
float tableau1[IMAX]; // par convention, j'écris les constantes symboliques en majuscules
Mais il parfois est indispensable d'utiliser les pointeurs. Voyons cela.
Il faut d'abord retenir que le nom d'un tableau désigne en fait un pointeur sur la zone mémoire qui va contenir les données stockées dans le tableau. Lorsque vous utilisez le nom du tableau comme variable sans les crochets vous utilisez en fait le pointeur sur le premier élement du tableau.
Par exemple, si je déclare le tableau tableau1[], la variable tableau1 désigne l'adresse de tableau1[0] et donc tableau1 est strictement équivalente à &tableau1[0].
Ceci étant dit, voyons comment déclarer et manipuler un tableau avec des pointeurs. Considérons d'abord le cas où l'on alloue la mémoire nécessaire au tableau à sa déclaration, ce qui suppose que l'on connaisse sa taille.
Soit
int tableau[5] = {0,1,2,3,4};
int *p_tableau;
J'initialise le pointeur p_tableau pour désigner le premier élément de tableau:
p_tableau = tableau;
Je peux accéder aux éléments du tableau en faisant un peu d'arithmétique des pointeurs. Par exemple, pour afficher les éléments du tableau je peux écrire:
int i;
for (i=0;i<5;i++)
printf("%d ", *p_tableau++);
Je peux ainsi parcourir le tableau en incrémentant ou en décrémentant le pointeur p_tableau. C'est très pratique mais attention! Si vous incrémentez le pointeur un peu trop, par exemple au delà de la taille du tableau, vous tapez dans une zone mémoire qui contient des choses inconnues... C'est le plantage assuré! Combien de bugs en C sont du à des défauts de maîtrise des pointeurs? Presque tous...
Cependant, l'intérêt d'utiliser des pointeurs pour travailler avec des tableaux ne réside pas dans ces subtilités. C'est indispensable quand on ne connaît pas la taille des tableaux lorsqu'on rédige le programme.
Imaginons que l'on veuille créer et afficher le contenu d'un tableau dont la taille varie selon un paramètre saisie par l'utilisateur (cas fréquent en stat.) ou encore selon la quantité de données lues dans un fichier (très classique en calcul). On peut déclarer le tableau avec une taille maximum. Mais alors bonjour le gaspillage de mémoire! Et dans le cas d'une lecture fichier, c'est le plantage assuré.
Il existe une autre solution très pratique en C. C'est l'allocation dynamique de la mémoire à un tableau. Je m'explique:
je déclare un pointeur sur un tableau de float par exemple
float *tableau;
>
au cours de mon programme, je saisis sa taille imax dans un int
int i,imax;
scanf("%d", &imax);
j'alloue la zone mémoire qui va bien pour que mon tableau puisse exister et recevoir des données
tableau = (float *)malloc((unsigned)(imax)*sizeof(float));
>
Cette instruction mérite explication. La fonction malloc()alloue une certaine quantité de mémoire et assigne l'adresse de début de cette zone à la variable tableau (qui est un pointeur, je vous rappelle...). La taille allouée est un entier non signé, calculé à partir de la taille d'un float (4 octets) multiplié par le nombre de float que l'on veut stocker, ici imax. Le (float *) qui figure en tête d'instruction est un cast, qui opére la transformation du pointeur sur int retourné par malloc() en pointeur sur float. Ce n'est pas clair? Pas de panique, vous trouverez un exemple d'utilisation de cette fonction dans le programme Euler qui suit!
Pour accéder à un élément de mon tableau:
float x;
i = 10; // attention, il faut que i soit < imax,sinon plantage! x = tableau[i];
tableau[i] = 10.12;
Je peux aussi faire ce genre de boucle:
for (i=0;i<imax;i++)
printf("%f ", *tableau++);
Important: avant de sortir de mon programme, je libère la mémoire allouée à mon tableau (c'est à ces détails que l'on reconnait les bons programmeurs...):
free(tableau);
Vous trouverez dans le programme Euler des exemples de manipulations des tableaux et leur passage en paramètre d'une fonction.
ATTENTION : l'usage des fonctions malloc() et free() nécessitent un include du header <malloc.h>
C'est un très vaste sujet! Comme pour le FORTRAN je me limiterai ici aux instructions nécessaires pour lire et écrire sur des fichiers disques des données de calcul.
Ouvrir un fichier séquentiel
Un fichier est désigné dans un programme C par une variable pointeur de type FILE. On le déclare par un:
FILE *fp;
où ici fp désigne le pointeur qui désignera le fichier dans toutes les instructions.
Pour l'ouvrir, on utilise la fonction fopen() dont la syntaxe est:
FILE *fopen(const char *filename, const char *mode);
où filename est une chaîne de caractères contenant le nom du fichier et mode une chaine de caractères décrivant le mode d'ouverture du fichier qui peut être :
La fonction fopen() retourne le pointeur sur le fichier (plus exactement sur la structure de description du fichier, mais peu importe...)
Lire un fichier
Pour lire des données dans un fichier disque, on utilisera une fonction qui doit vous être familière:
int fscanf( FILE *stream, const char *format [, argument]... );
Elle s'utilise strictement de la même façon que la fonction scanf(). Le premier argument est bien le pointeur fp obtenu à l'ouverture du fichier.
Ecrire dans un fichier
Idem pour l'écriture pour laquelle nous utiliserons la fonction fprintf(), dont la syntaxe est:
int fprintf( FILE *stream, const char *format [, argument ]...);
Elle s'utilise strictement de la même façon que la fonction printf(). Le premier argument est bien le pointeur fp obtenu à l'ouverture du fichier.
Fermer un fichier
Encore plus simple puisqu'on ferme le fichier en appelant fclose(), dont la syntaxe est:
int fclose( FILE *stream);
Ne pas oublier de fermer les fichiers dans ses programmes...
Exemple d'utilisation
Supposons que nous ayons stocké les résultats d'un calcul dans deux vecteurs x et y de dimension n=50. Nous voulons sauvegarder ces valeurs dans un fichier pour les plotter avec un logiciel comme Matlab, Scilab ou GnuPlot.
Voilà le bout de code correspondant. On supposera que le fichier data.txt n'existe pas.
int i;
float t[50], x[50], y[50];
FILE *fp;
fp=fopen("data.txt","w+");
for(i=0;i<50; i++)
fprintf(fp,"%f %f %f\n", t[i], x[i], y[i]);
fclose(fp);