---------------------------------------------------------------
Module A2I-1: Informatique 1
Masters:
* Électronique et Systèmes de Communication (ESCom)
* Mécanique et Énergétique(ME)
* Physique et Sciences de la Matière(PSM)
Pr. A. ZIYYAT
2007/2008
1. Présentation du langage C par l’exemple
-------------------------------------------------------------------
1.1. Un exemple de programme en langage C :
Voici un programme en C :
#include <stdio.h>
#define TVA 18.6
main()
{
float HT;
float TTC;
printf ("Veuillez entrer le prix H.T. :");
scanf("%f",&HT);
TTC=HT*(1+(TVA/100));
printf("Prix T.T.C. :%f\n",TTC);
}
Voici un exemple de l’exécution de ce programme:
Veuillez entrer le prix H.T. :100
Prix T.T.C. :118.600000
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.2 Structure d’un programme en langage C
La ligne : main()
C’est une entête de fonction.
Dans ce cas, le programme ne possède qu'une seule fonction, la fonction
principale (main function). Cette ligne est obligatoire en C : elle définit le
"point d'entrée" du programme, c'est à dire l'endroit où débutera l'exécution
du programme.
Le nom main est imposé. Le programme principal vient à la suite de cette
entête. Il est délimité par des accolades { et }.
Les instructions situées entre ces accolades forment un bloc, dit bloc
d’instructions.
Le bloc d’instruction de notre exemple comporte des déclarations de
variables et des instructions.
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.3 Déclarations
Les deux instructions :
float HT;
float TTC;
sont des déclarations.
Elles sont sous la forme :
type listevariables;
Une variable est une case mémoire de l'ordinateur, que l'on se réserve pour
notre programme.
On définit le nom que l'on choisit pour chaque variable, ainsi que son type,
ici float, c'est à dire réel (type dit à virgule flottante, d'où ce nom). Les trois
types scalaires de base du C sont l'entier (int), le réel (float) et le caractère
(char).
On ne peut jamais utiliser de variable sans l'avoir déclarée
auparavant.
Une faute de frappe devrait donc être facilement détectée.
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.3 Instructions
Une instruction est un ordre élémentaire que l'on donne à la machine, qui
manipulera les données (variables) du programme, ici soit par appel de
fonctions (printf , scanf) soit par affectation (=).
scanf("%f",&HT);
scanf attend que l'on entre une valeur au clavier, puis la met dans
la mémoire (on préfère dire variable) HT, sous format réel (%f). La
fonction scanf reçoit ici deux arguments.
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.3 Instructions
TTC=HT*(1+(TVA/100));
une affectation : on commence par diviser TVA par 100 (à cause
des parenthèses), puis on y ajoute 1, puis on le multiplie par le
contenu de la variable HT.
Le résultat de ce calcul est stocké (affecté) dans la variable cible
TTC.
Une affectation se fait toujours dans le même sens : on détermine
(évalue) tout d'abord la valeur à droite du signe =, en faisant tous
les calculs nécessaires, puis elle est transférée dans la mémoire
dont le nom est indiqué à gauche du =. On peut donc placer une
expression complexe à droite du =, mais à sa gauche seul un nom
de variable est possible, aucune opération.
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.4 Directives du pré processeur
Les directives du pré processeur commencent toujours par #
Exemple: #include.
Ici la fonction principale main() contient la fonction printf(). En réalité le
compilateur ne connaît pas la fonction printf() bien qu'il s'agisse d'une
fonction standard du langage C. Cette fonction est effectivement stockée
dans un fichier annexe, contenant une librairie de fonctions, appelé fichier
de définition (dits aussi fichier d'en-tête ou fichier header), dont l’extension
est .h.
Il s'agit donc de préciser au compilateur dans quel fichier se trouve la
définition de la fonction printf()...
Celle-ci se trouve dans le fichier d'en-tête appelé stdio.h.
L'incorporation de la déclaration de la fonction printf() se fait au moyen de
l'instruction #include (que l'on place en début de fichier) suivie des balises
< et > contenant le nom de fichier contenant la définition de la fonction. La
déclaration include doit se trouver avant toute utilisation des méthodes
déclarées, sinon le compilateur générera au minimum un warning
1. Présentation du langage C par
l’exemple
-------------------------------------------------------------------
1.4 Directives du pré processeur
Les directives du pré processeur commencent toujours par #
#include : inclure le fichier définissant (déclarant) les fonctions
standard d'entrées/sorties (en anglais STanDard In/Out), qui feront
le lien entre le programme et la console (clavier/écran). Dans cet
exemple il s'agit de scanf et printf.
Les caractères suivants sont choisis parmi les lettres (A...Z, a...z),
les chiffres (0...9) ou l'underscore ( _).
La norme ANSI n'impose aucune limite sur la longueur d'un nom.
Toutefois le compilateur ne tiendra compte que des 32 premiers
caractères.
Attention : le C est sensible à la casse (case sensitive) C'est à
dire qu'il fait la distinction entre les majuscules et les minuscules.
Ex.: les spécifications du langage C précisent que la fonction
principale doit être appelée main() et non Main() ou MAIN()
2. Quelques règles d’écriture
-------------------------------------------------------------------
Exemples d’identificateurs:
Le type int peut être précédé par les mots-clés: short, long.
Chacun de ces types peut être spécifié comme étant sans signe en le
précédant du mot-clé unsigned : unsigned int, unsigned long... . .
short a = 12, b = 19;
unsigned long d = 3123456789ul;
3. LES VARIABLES - TYPES DE DONNEES
-------------------------------------------------------------------
3.3 Les types de base
Nombre à virgule (float)
Un nombre à virgule flottante est un nombre à virgule, il peut
toutefois être représenté de différentes façons :
•un entier décimal : 895
•un nombre comportant un point (et non une virgule) : 845.32
•une fraction : 27/11
•un nombre exponentiel, c'est-à-dire un nombre (éventuellement à
virgule) suivi de la lettre e (ou E), puis d'un entier correspondant à
la puissance de 10 (signé ou non, c'est-à-dire précédé d'un + ou
d'un -) : 2.75e-2 35.8E+10 .25e-2
Les nombres réels sont des nombres à virgule flottante, c'est-à-
dire un nombre dans lequel la position de la virgule n'est pas fixe,
et est repérée par une partie de ses bits (appelée l'exposant), le
reste des bits permettent de coder le nombre sans virgule (
la mantisse).
3. LES VARIABLES - TYPES DE DONNEES
-------------------------------------------------------------------
3.3 Les types de base
Nombre à virgule (float)
Les nombres de type float sont codés sur 32 bits dont :
23 bits pour la mantisse, 8 bits pour l'exposant et 1 bit pour le
signe
-------------------------------------------------------------------
3.4 Les constantes
Une constante est une variable dont la valeur est inchangeable lors de
l'exécution d'un programme.
En langage C, les constantes sont définies grâce à la directive du
préprocesseur #define, qui permet de remplacer toutes les occurrences du
mot qui le suit par la valeur immédiatement derrière elle.
Par exemple la directive : #define _Pi 3.1415927
Avec cette méthode les constantes ne sont pas typées. Il est ainsi préférable
d'utiliser le mot clef const, qui permet de déclarer des constantes typées :
const int dix = 10;
Cela permet d'éviter certains problèmes du #define, qui fait du « chercher-
remplacer » textuel sans réfléchir.
On distingue quatre types de constantes : entières, flottantes, caractères et
chaînes.
3. LES VARIABLES - TYPES DE DONNEES
-------------------------------------------------------------------
3.4 les constantes entières
Les constantes entières sont représentées par une suite de chiffres (en
décimal par défaut) précédée éventuellement par un signe (+ ou - ). Si cette
suite commence par 0, la constante s'exprime en octal (base 8). Si cette suite
commence par 0x ou 0X, la constante s'exprime en héxadécimal (base 16).
Par défaut, une constante entière est de type int. Si la constante se termine
par l ou L, elle est de type long. Si, elle se termine par u ou U, elle est de type
unsigned.
Les constantes entières
Constante Valeur décimale Type
1924 1924 int
tabulation HT \t
retour arrière BS \b
retour chariot CR \r
saut de page FF \f
point d'interrogation ? \?
Affiche :
Pi = 3.141593e+00
Pi = 3.141593
Pi = 3.141593
Pi = 3.1415926535898
Pi = +3.1415926535898
Pi = +3.1415926535898
Pi = 3.1415926535897931159979630
4. ENTREES - SORTIES
-------------------------------------------------------------------
4.2 La fonction scanf
% f un réel float *
% e un réel float *
% g un réel float *
4. ENTREES - SORTIES
-------------------------------------------------------------------
4.2 La fonction scanf
Attention : il faut faire précéder les caractères spécifiant un entier (d, o, x, u)
du caractère l pour spécifier que l'entier est de type long * ou du caractère h
pour spécifier que l'entier est de type short *. De même pour les réels, si le
caractère est précédé d'un l alors le type est double *.
Exemples :
double a;
int g;
char str[300];
Remarque : dans le cas d'un tableau de caractères, il ne faut pas mettre de &
devant le nom du tableau puisque ce nom représente déjà un pointeur sur le
tableau. .
4. ENTREES - SORTIES
-------------------------------------------------------------------
4.3 putchar et getchar
La macro putchar
putchar (c) joue le rôle de: printf ("%c", c)
La macro getchar
getchar (c) joue le rôle de: scanf ("%c", &c)
Notion de bloc
Une expression suivie d'un point-virgule est appelée instruction.
Exemple d'instruction : a=a+b;
Lorsque l'on veut regrouper plusieurs instructions, on peut créer ce que l'on appelle un
bloc, c'est-à-dire un ensemble d'instructions (suivies respectivement par des points-
virgules) et comprises entre les accolades { et }.
Les instructions if, while et for peuvent par exemple être suivies d'un bloc d'instructions
à exécuter...
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.2 L'instruction if (Structure de choix: instruction SI…ALORS…)
L'instruction if permet d'exécuter une série d'instructions si une condition est
réalisée. La syntaxe de cette expression est la suivante :
if (condition réalisée) { liste d'instructions;}
Remarques :
•la condition doit être entre des parenthèses
•s'il n'y a qu'une instruction, les accolades ne sont pas nécessaires...
•il est possible de définir plusieurs conditions à remplir avec les opérateurs ET
et OU (&& et ||)
Exemple: l'instruction suivante teste si les deux conditions sont vraies :
if ((condition1)&&(condition2))
L'instruction suivante exécutera les instructions si l'une ou l'autre des deux
conditions est vraie :
if ((condition1)||(condition2))
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.3 L'instruction if ….else (instruction SI…ALORS…SINON)
L'instruction if dans sa forme basique ne permet de tester qu'une
condition, or la plupart du temps on aimerait pouvoir choisir les
instructions à exécuter en cas de non réalisation de la condition...
L'expression if ... else permet d'exécuter une autre série d'instructions
en cas de non-réalisation de la condition.
La syntaxe de cette expression est la suivante :
if (condition réalisée)
{ liste d'instructions
}
Else
{ autre série d'instructions
}
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.3 L'instruction if et if … else
Organigrammes :
Instruction if Instruction if …..else
suite du programme suite du programme
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.4 Autre façon d’écriture du if …else
Il est possible de faire un test avec une structure beaucoup moins lourde
grâce à la structure suivante :
Remarques :
•la condition doit être entre des parenthèses
•Lorsque la condition est vraie, l'instruction de gauche est exécutée
•Lorsque la condition est fausse, l'instruction de droite est exécutée
•En plus d'être exécutée, la structure ?: renvoie la valeur résultant de
l'instruction exécutée. Ainsi, cette forme ?: est souvent utilisée comme suit :
position = ((enAvant == 1) ? compteur+1 : compteur-1);
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.3 L'instruction if ….else
Il est possible d'enchaîner plusieurs tests successifs grâce à else if :
if (condition1)
/* si condition1 est vraie */
instruction
else if (condition2)
/* si condition1 est fausse et condition2 est vraie */
instruction
else
/* si les deux conditions sont fausses */
instruction
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.5 L’instruction switch (structure de choix multiples)
L'instruction switch permet de faire plusieurs tests de valeurs sur le contenu
d'une même variable (instruction AU CAS OU ... FAIRE .. ).
Ce branchement conditionnel simplifie beaucoup le test de plusieurs valeurs
d'une variable, car cette opération aurait été compliquée (mais possible) avec
des if imbriqués. Sa syntaxe est la suivante :
switch (Variable de type char ou int) {
case Valeur1 : Liste d'instructions;
break;
case Valeur2 : Liste d'instructions;
break;
case Valeurs... : Liste d'instructions;
break;
default: Liste d'instructions;
}
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.5 L’instruction switch
Les parenthèses qui suivent le mot clé switch indiquent une
expression dont la valeur est testée successivement par chacun des
case. Lorsque l'expression testée est égale à une des valeurs suivant
un case, la liste d'instructions qui suit celui-ci est exécutée. Le mot clé
break indique la sortie de la structure conditionnelle. Le mot clé
default précède la liste d'instructions qui sera exécutée si l'expression
n'est jamais égale à une des valeurs.
Remarques:
N'oubliez pas d'insérer des instructions break entre chaque test, ce
genre d'oubli est difficile à détecter car aucune erreur n'est signalée...
En effet, lorsque l'on omet le break, l'exécution continue dans les
blocs suivants !
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.5 L’instruction switch
Remarques:
Cet état de fait peut d'ailleurs être utilisé judicieusement afin de faire exécuter
les mêmes instructions pour différentes valeurs consécutives, on peut ainsi
mettre plusieurs cases avant le bloc :
switch(variable) {
case 1:
case 2: { instructions exécutées pour variable = 1 ou pour variable =
2}
break;
case 3: { instructions exécutées pour variable = 3 uniquement }
break;
default: { instructions exécutées pour toute autre valeur de variable }
}
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6 LES BOUCLES
Les boucles sont des structures qui permettent d'exécuter plusieurs
fois la même série d'instructions jusqu'à ce qu'une condition ne soit
plus réalisée...
On appelle parfois ces structures instructions répétitives ou bien
itérations.
La façon la plus commune de faire une boucle est de créer un
compteur (une variable qui s'incrémente, c'est-à-dire qui augmente
de 1 à chaque tour de boucle) et de faire arrêter la boucle lorsque le
compteur dépasse une certaine valeur. Voyons les trois types de
boucles en C:
La boucle for
La boucle while
La boucle do … while
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6.1 L’instruction FOR (Instruction Pour)
initialisation
Par exemple :
for (i=1; i<6; i++) {printf(" %d", i);} condition de
continuité vraie
Cette boucle affiche 5 fois la valeur de i, non
oui
c'est-à-dire 1, 2, 3, 4, 5.
Elle commence à i=1,
bloc d'instructions
vérifie que i est bien inférieur à 6,
etc. jusqu'à atteindre la valeur i=6,
pour laquelle la condition ne sera
modification
plus réalisée, suite du programme
la boucle s'interrompra
et le programme continuera son cours.
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6.1 L’instruction FOR (Instruction Pour)
Remarque:
•il faudra toujours vérifier que la boucle a bien une condition de sortie
(i.e. le compteur s'incrémente correctement)
•une instruction printf(); dans votre boucle est un bon moyen pour
vérifier la valeur du compteur pas à pas en l'affichant !
•il faut bien compter le nombre de fois que l'on veut faire exécuter la
boucle :
–for(i=0;i<10;i++) exécute 10 fois la boucle (i de 0 à 9)
–for(i=0;i<=10;i++) exécute 11 fois la boucle (i de 0 à 10)
–for(i=1;i<10;i++) exécute 9 fois la boucle (i de 1 à 9)
–for(i=1;i<=10;i++) exécute 10 fois la boucle (i de 1 à 10)
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6.2 L'instruction while (La boucle TANT QUE ... FAIRE ... )
L'instruction while représente un autre moyen d'exécuter plusieurs fois
la même série d'instructions.
La syntaxe de cette expression est la suivante :
while (condition réalisée) { liste d'instructions;}
Cette instruction exécute la liste d'instructions tant que (while est un
mot anglais qui signifie tant que) la condition est réalisée.
Le test se fait d'abord, le bloc d'instructions n'est pas forcément exécuté.
les {} ne sont pas nécessaires lorsque le bloc ne comporte qu'une seule
instruction.
Attention:
La condition de sortie pouvant être n'importe quelle structure
conditionnelle, les risques de boucle infinie (boucle dont la condition
est toujours vraie) sont grands, c'est-à-dire qu'elle risque de provoquer
un plantage du programme en cours d'exécution !
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6.2 L'instruction while (La boucle TANT QUE ... FAIRE ... )
Organigramme:
Par exemple :
i=1;
while (i<6) oui non
condition
{printf(" %d", i); i++;} vraie
Cette boucle affiche 5 fois la valeur de i,
c'est-à-dire 1, 2, 3, 4, 5.
Au début i=1, bloc d'
vérifie que i est bien inférieur à 6, et l’affiche, instructions
etc. jusqu'à atteindre la valeur i=6,
pour laquelle la condition ne sera
plus réalisée,
la boucle s'interrompra
et le programme continuera son cours.
6. LES INSTRUCTIONS DE CONTRÔLE
-------------------------------------------------------------------
6.6.3 L'instruction do while (La boucle REPETER … TANT QUE )
L'instruction do … while représente un autre moyen d'exécuter une boucle.
La syntaxe de cette expression est la suivante :
do{liste d'instructions;}
while (expression); bloc d'
instructions
non
condition
oui vraie
suite du programme
-------------------------------------------------------------------
Nous avons déjà vu les types de base disponibles en C. Nous allons
maintenant aborder les types plus complexes que sont les tableaux, les
pointeurs et les structures.
7.1 TABLEAUX
On appelle tableau une variable composée de données de même
type,
type stockée de manière contiguë en mémoire (les unes à la suite
des autres).
Pour déclarer un tableau, il faut donner le type de ses éléments puis son nom
et enfin sa taille entre crochets. Tous les éléments d'un tableau sont
obligatoirement du même type.
Syntaxe: type Nom_du_tableau [Nombre d'éléments];
On peut utiliser des tableaux de dimension 2 ou plus.
Définition de tableaux:
Dans l'exemple suivant, nous définissons deux tableaux de 100
éléments, l'un contenant des float, l'autre des int.
float VecteurA[100];
int VecteurB[100];
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
initialisation d'un tableau
On peut initialiser un tableau dès sa déclaration en lui affectant une
liste de valeurs séparées par des virgules et entourée par des
accolades. L'exemple suivant initialise le tableau toto:
int toto[5] = {4, 6, 8, 12, 20};
– Le nombre de valeurs entre accolades ne doit pas être supérieur au
nombre d'éléments du tableau
– Les valeurs entre accolades doivent être des constantes (l'utilisation de
variables provoquera une erreur du compilateur)
– Si le nombre de valeurs entre accolades est inférieur au nombre
d'éléments du tableau, les derniers éléments sont initialisés à 0
– Il doit y avoir au moins une valeur entre accolades
Un cas particulier est l'initialisation d'un tableau de caractères pour laquelle on
peut utiliser une chaîne de caractères. Les deux lignes suivantes sont
équivalentes :
char Str[20] = {'B', 'o', 'n', 'j', 'o', 'u', 'r'};
char Str[20] = "Bonjour";
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
Calcul de la taille du tableau
Etant donné qu'un tableau est composé d'un nombre fixé d'éléments d'un type
donné, la taille d'un tableau est déterminée dès sa définition.
Pour connaître la taille d'un tableau, c'est-à-dire déterminer le nombre d'octets
que celui-ci occupe en mémoire, il y a deux possibilités :
Calculer manuellement la taille du tableau : il suffit de multiplier la taille du
type d'élément par le nombre d'éléments qui composent le tableau
Utiliser l'opérateur sizeof() : l'opérateur sizeof() permet de retourner
directement la taille de l'élément qui lui est passé en argument, ainsi en lui
passant un tableau comme opérande, sizeof() est capable de vous retourner
directement la taille de celui-ci
Voici différents exemples de tableaux, et leurs tailles respectives :
Définition du tableau Taille du tableau (en octets)
char Tableau1[12] 1 * 12 = 12
short int Tableau2[10] 2 * 10 = 20
float Tableau3[8] 4 * 8 = 32
double Tableau4[15] 8 * 15 = 120
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
Accès aux valeurs d'un tableau
Pour accéder à un élément du tableau, il faut donner le nom du tableau, suivi
de l'indice de l'élément entre l'opérateur [ ] (crochets) :
Nom_du_tableau[indice ]
-------------------------------------------------------------------
Tableau à N dimensions
Définition
Un tableau multidimensionnel se définit de la manière suivante :
type Nom_du_tableau [a1][a2][a3] ... [aN]
Chaque élément entre crochets désigne le nombre d'éléments dans
chaque dimension.
Le nombre de dimensions n'est pas limité.
Un tableau d'entiers positifs à deux dimensions (3 lignes, 4 colonnes)
se définira avec la syntaxe suivante :
int Tableau [3][4] On peut représenter un tel tableau de la manière
suivante (Tableau [i: ligne][j:colonne]) :
Tableau[0][0] Tableau[0][1] Tableau[0][2] Tableau[0][3]
Tableau[1][0] Tableau[1][1] Tableau[1][2] Tableau[1][3]
Tableau[2][0] Tableau[2][1] Tableau[2][2] Tableau[2][3]
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
Initialisation d'un tableau multidimensionnel
L'initialisation d'un tableau multidimensionnel se fait à peu près de la même
façon que pour les tableaux unidimensionnels. Il y a donc plusieurs façons
d'initialiser un tableau multidimensionnel :
1) Initialisation individuelle de chaque élément :
Nom_du_tableau [0][0] = 2; Nom_du_tableau [0][1] = 3;...
-------------------------------------------------------------------
Initialisation d'un tableau multidimensionnel
1) Initialisation à la définition :
type Nom_du_tableau [Taille1][Taille2]...[TailleN] = {a1, a2, ... aN};
Les valeurs sont attribuées aux éléments successifs en incrémentant
d'abord les indices de droite, c'est-à-dire pour un tableau à 2
dimensions : [0][0], [0][1], [0][2] ... puis [1][0] etc.
-------------------------------------------------------------------
7.2 POINTEURS
Un pointeur contient l'adresse en mémoire d'un objet d'un type
donné. Ainsi, on parle de « pointeur sur int » ou de « pointeur sur
double ». L'utilisation des pointeurs en C est l'un des points les plus
complexes du langage. Mais c'est aussi une fonctionnalité qui rend le
C très puissant surtout si on l'utilise avec les fonctions d‘allocation
dynamique de la mémoire.
Définition de pointeur:
Pour définir un pointeur, on doit écrire le type d'objet sur lequel il
pointera suivi du caractère * pour préciser que c'est un pointeur puis
enfin son nom.
Dans l'exemple suivant, p est défini comme un pointeur sur un double
et q est défini comme un pointeur sur un pointeur sur int :
double * p;
int * * q;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.2 POINTEURS
Attention : dans la définition d'un pointeur, le caractère * est rattaché au nom
qui le suit et non pas au type. Ainsi, dans la définition qui suit, p est bien un
pointeur sur char mais t est simplement une variable de type char. La seconde
ligne, par contre, définit deux pointeurs sur double :
char * p, t;
double * p2, * p3;
p = & car;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.2 POINTEURS
Accès au contenu d'un pointeur
Pour accéder au contenu de l'adresse mémoire pointée par un
pointeur, on utilise l'opérateur * . Ainsi, en continuant l'exemple
précédent, la ligne suivante stockera dans la variable car le caractère
A puisque p pointe sur son adresse en mémoire :
*p = 'A';
Adresses et tableaux
On peut récupérer l'adresse de n'importe quel objet. Par exemple, il
est possible d'obtenir l'adresse d'un élément d'un tableau (dans cet
exemple, le onzième élément) :
double a[20];
double * p;
p = & (a[10]); Par convention, le nom d'un tableau est une constante
égale à l'adresse du premier élément du tableau. En continuant
l'exemple précédent, les deux lignes suivantes sont équivalentes :
p = & a[0];
p = a;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.2 POINTEURS
calculs sur les pointeurs
Il est possible de faire des calculs sur les pointeurs. On peut ajouter ou
soustraire une valeur entière à un pointeur. Dans l'exemple suivant, p pointe à
la fin sur le troisième élément du tableau a (donc sur a[2]) :
double a[20];
double * p;
p = & (a[10]);
p = p - 8;
Pour effectuer ce calcul tous les opérateurs classiques d'addition et de
soustraction sont utilisables en particulier les opérateurs d'incrémentation.
Nous avons vu qu'une chaîne de caractères se terminait toujours par le
caractère de code ASCII 0 (\0). L'exemple suivant permet de compter le
nombre de caractères stockés dans le tableau de caractères str (le caractère
nul ne fait pas partie du compte) :
char * p = str;
int NbCar = 0;
while ( *p != '\0') {
p++;
NbCar++;
}
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.2 POINTEURS
calculs sur les pointeurs (suite)
L'arithmétique des pointeurs en C a cette particularité que l'opération
dépend du type de variable pointée, ajouter 1 consistant à ajouter à
l'adresse la taille de l'objet pointé.
En effet, les calculs sur pointeurs et l'utilisation de l'opérateur [] d'accès à un
élément d'un tableau peuvent être considérés comme équivalent. Sachant que
Tab est un tableau de double, les deux lignes suivantes sont équivalentes :
Tab[45] = 123.456;
*(Tab + 45) = 123.456; Ceci est tellement vrai qu'on peut même utiliser un
pointeur directement comme un tableau.
Les deux écritures suivantes sont donc exactement équivalentes que p soit le
nom d'un pointeur ou celui d'un tableau :
p[i] est équivalent à *(p + i)
On a le même type d'équivalence au niveau des paramètres d'une fonction.
Les deux lignes suivantes déclarent toutes les deux que le paramètre p de la
fonction f est un pointeur sur double :
void f(double * p);
void f(double q[]);
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
En général, les types de base que propose le C ne suffisent pas pour
stocker les données à utiliser dans un programme. Par exemple, il
serait bien embêtant de devoir utiliser deux variables de type double
pour stocker un nombre complexe. Heureusement le C permet de
déclarer de nouveaux types.
Déclaration d'une structure
Une structure possède un nom et est composée de plusieurs
champs. Chaque champ à son propre type et son propre nom. Pour
déclarer un structure on utilise le mot-clé struct :
struct nomStructure {
type1 champ1;
...
typeN champN;
};
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
Voici un exemple qui déclare une structure permettant de stocker un nombre
complexe :
struct complex {
double reel; /* partie reelle */
double imag; /* partie imaginaire */
};
Accès aux champs
A partir de cette déclaration, il est possible d'utiliser ce nouveau type.
L'opérateur . permet d'accéder à l'un des champs d'une structure. En
continuant l'exemple précédent, les lignes suivantes initialisent un complexe à
la valeur (2 + 3i).
struct complex a;
a.reel = 2;
a.imag = 3;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
Utilisation de typedef
Le mot-clé typedef permet d'associer un nom à un type donné.
On l'utilise suivi de la déclaration d'un type (en général une structure
ou une union) puis du nom qui remplacera ce type. Ceci permet, par
exemple, de s'affranchir de l'emploi de struct à chaque utilisation d'un
complexe. Il n'est pas alors nécessaire de donner un nom à la
structure. L'exemple précédent peut donc se réécrire de la manière
suivante :
typedef struct {
double reel; /* partie reelle */
double imag; /* partie imaginaire */
} complexe;
complexe a;
a.reel = 2;
a.imag = 3;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
Initialisation et affectation
Il est possible d'affecter une variable de type structure dans une autre variable
du même type. Le contenu de chacun des champs de la première variable
sera alors recopié dans le champ correspondant de la seconde variable. On
peut initialiser une variable de type structure dès sa définition en lui affectant
une liste de valeurs séparées par des virgules et entourées par des accolades.
complexe a = { 1, 0 }; /* le reel 1 */
complexe b;
b = a;
Il est par contre impossible de comparer ou d'effectuer des calculs entre deux
structures.
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
Exemple de structures imbriquées
On peut imbriquer plusieurs structures. Dans l'exemple suivant nous déclarons
une structure pour stocker une commande d'un client contenant :
la référence du produit commandé (refProd),
une sous-structure (prix) stockant : le prix unitaire hors taxe (HT) et le taux de
TVA associé (TVA),
le nombre d'unités commandées (q),
la remise accordée en pourcentage (remise).
Cette structure se déclare de la manière suivante :
typedef struct {
int refProd; /* reference produit */
struct {
double HT; /* prix hors taxe */
double TVA; /* taux de TVA en pourcentage */
} prix;
int q; /* quantite commandee */
double remise; /* remise en pourcentage */
} commande;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
Exemple de structures imbriquées(suite)
-------------------------------------------------------------------
7.3 STRUCTURES (struct)
les pointeurs sur structures
L'utilisation de pointeurs sur structures est très courante en C. Voici
un exemple d'utilisation d'un pointeur sur un complexe :
complexe a = { 3.5, -5.12 }; complexe * p = &a;
(*p).reel = 1;
(*p).imag = -1; /* a vaut (1 - i) */
Nous avons été obligé de mettre des parenthèses autour de *p car
l'opérateur . est plus prioritaire que l'opérateur * . Cela rend difficile la
lecture d'un tel programme. Heureusement, l'utilisation de pointeurs
sur structures est si courante que le C définit l'opérateur -> pour
accéder aux champs d'une structure via un pointeur. Les deux
expressions suivantes sont donc équivalentes :
(*pointeur).champ équivalent à pointeur->champ
Ainsi l'exemple précédent s'écrit beaucoup plus facilement de la
manière suivante :
complexe a = { 3.5, -5.12 };
complexe * p = &a;
p->reel = 1;
p->imag = -1; /* a vaut (1 - i) */
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.4 STRUCTURES (union)
les unions
Les unions se déclarent de la même manière que les structures.
union nom_type {déclaration des champs} liste_variables ;
Elles possèdent donc elles aussi des champs typés. Mais on ne peut
utiliser qu'un seul champ à la fois.
En fait tous les champs d'une union se partagent le même espace
mémoire. Les unions sont rarement nécessaires sauf lors de la
programmation système.
Les champs peuvent être de tout type, y compris structures. On les
utilise comme les structures, avec les opérateurs "." et "->".
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.4 STRUCTURES (union)
Exemple
Supposons que l'on désire écrire un package mathématique qui manipulera
des nombres qui seront implémentés par des int, tant que la précision des
entiers de la machine sera suffisante, et qui passera automatiquement à une
représentation sous forme de réels dès que ce ne sera plus le cas. Il sera
nécessaire de disposer de variables pouvant prendre soit des valeurs entières,
soit des valeurs réelles.
Ceci peut se réaliser en C, grâce au mécanisme des unions. ex:
union nombre
{
int i;
float f;
}
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.4 STRUCTURES (union)
Exemple: les unions
Dans l'exemple précédent, si on déclare la variable n comme étant de type
union nombre par:
union nombre n;
cette variable pourra posséder soit une valeur entière, soit une valeur réelle,
mais pas les deux à la fois.
Accès aux champs de l'union
Cet accès se fait avec le même opérateur sélection (note .) que celui qui sert a
accéder aux champs des structures.
Dans l'exemple précèdent, si on désire faire posséder à la variable n une
valeur entière, on pourra écrire:
n.i = 10;
si on désire lui faire posséder une valeur réelle, on pourra écrire:
n.f = 3.14159;
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.4 Les opérateurs sur les pointeurs, tableaux et
structures
Le tableau suivant récapitule les cinq opérateurs que nous avons vu sur les
pointeurs, tableaux et structures.
Opérateurs Signification
[] (binaire) accès à un élément d'un tableau
& (unaire) adresse de
* (unaire) contenu de
-> (binaire) accès à un champs pointé par
. (binaire) accès à un champs
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.5 Allocation dynamique de mémoire
La taille déclarée d'un tableau est définie à la compilation.
Dans le cas d'une taille inconnue à l'avance, il faut surdimensionner le
tableau (et donc réserver des mémoires dont on ne servira que
rarement, aux dépends d'autres variables ou tableaux).
-------------------------------------------------------------------
7.5 Allocation dynamique de mémoire
- void *malloc(int taille) : réserve une zone mémoire contiguë de taille octets,
et retourne un pointeur sur le début du bloc réservé. Retourne le pointeur
NULL en cas d'erreur (en général car pas assez de mémoire).
-void *calloc(int nb, int taille) : équivalent à malloc(nb*taille).
Exemple : float *tab;int nb;
puts("taille désirée ?"); scanf("%d",&nb);
tab=(float*)calloc(nb,sizeof(float));
malloc et calloc nécessitent un cast pour que le compilateur ne signale
pas d'erreur.
- void free(void *pointeur) libère la place réservée auparavant par
malloc ou calloc. Pointeur est l'adresse retournée lors de l'allocation.
En quittant proprement le programme, la mémoire allouée est
automatiquement restituée même si on omet d'appeler free.
Ces fonctions sont définies dans stdlib.h ou alloc.h (suivant le
compilateur).
7. TABLEAUX, POINTEURS ET STRUCTURES
-------------------------------------------------------------------
7.5 Allocation dynamique de mémoire
Une fois la zone de mémoire réservée, on peut y accéder soit par
"l'écriture pointeurs" soit par "l'écriture tableaux", puisqu'elles sont
équivalentes. Pour le dernier exemple :
Remarques :
•type_de_donnee représente le type de valeur que la fonction est
sensée retourner
•Si aucun type de donnée n'est précisé(cela n’est pas bon!), le type
int est pris par défaut
8. LES FONCTIONS
-------------------------------------------------------------------
•Le nom de la fonction suit les mêmes règles que les noms de
variables
–le nom doit commencer par une lettre
–un nom de fonction peut comporter des lettres, des chiffres et les
caractères _ et & (les espaces ne sont pas autorisés !)
–le nom de la fonction, comme celui des variables est sensible à la casse
(différenciation entre les minuscules et majuscules)
•Les arguments sont facultatifs, mais s'il n'y a pas d'arguments, les
parenthèses doivent rester présentes
•Une fois cette étape franchie, votre fonction ne s'exécutera pas tant
que l'on ne fait pas appel à elle quelque part dans le programme!
8. LES FONCTIONS
-------------------------------------------------------------------
Retour du résultat
Le mot-clé return est suivi d'une expression. La valeur de cette expression
sera le résultat de la fonction. La dernière instruction d'une fonction doit être
une instruction return permettant de renvoyer le résultat.
Il est possible d'utiliser return n'importe où et plusieurs fois dans une fonction.
La fonction s'arrêtera immédiatement et le résultat sera renvoyé.
Néanmoins, dans le cadre d'une programmation structurée, l'instruction return
ne devrait apparaître que comme dernière instruction sauf si cela est
nécessaire.
La syntaxe de l'instruction return est simple : : return (valeur_ou_variable);
Le type de valeur retourné doit correspondre à celui qui a été précisé dans la
définition (et le prototype).
Appel d'une fonction
Pour appeler une fonction, il suffit de mettre son nom suivi de la liste des
valeurs à donner aux paramètres entre parenthèses.
Un appel de fonction est considérée comme une expression du type du
résultat de la fonction et la valeur de cette expression est le résultat de
l'exécution de la fonction. Dans l'exemple suivant, c contiendra la valeur
moyenne de 30 et 75 :
c = moyenne(30, 75);
8. LES FONCTIONS
-------------------------------------------------------------------
Type du résultat de la fonction
Le type de la valeur retournée par une fonction peut être n'importe quel type
(un type de base ou même une structure). La fonction suivante calcule la
somme de deux nombres complexes :
complexe AddComplexe(complexe op1, complexe op2) {
complexe somme;
somme.reel = op1.reel + op2.reel;
somme.imag = op1.imag + op2.imag;
return (somme);
}
Comme tout autre bloc, le corps de la fonction peut débuter par une
série de définitions de variables. Ces variables sont appelées
variables locales. Chaque appel de la fonction crée de nouvelles
instances des variables locales.
Les fonctions C sont réentrantes. Cela signifie qu'elles peuvent être appelées
récursivement.
La suite de Fibonacci se définit récursivement de la manière suivante :
Déclarations locales
Dans tout bloc d'instructions, avant la première instruction, on peut déclarer
des variables. Elles seront alors "locales au bloc" :elles n'existent qu'à
l'intérieur du bloc. Ces variables sont mises en mémoire dans une zone de
type "pile" : quand, à l'exécution, on arrive sur le début du bloc ({), on réserve
la mémoire nécessaire aux variables locales au sommet de la pile (ce qui en
augmente la hauteur), et on les retire en quittant le bloc.
Cette période est appelée sa durée de vie. Mais pendant sa durée de vie, une
variable peut être visible ou non. Elle est visible : dans le texte source du bloc
d'instruction à partir de sa déclaration jusqu'au }, mais tant qu'une autre
variable locale de même nom ne la cache pas.
Par contre elle n'est pas visible dans une fonction appelée par le bloc (puisque
son code source est hors du bloc).
8. LES FONCTIONS
-------------------------------------------------------------------
La notion de portée d'un nom
La portée d'un nom représente la région (ou les régions) dans le
programme source où un nom est connu du compilateur.
Les fonctions définies dans un fichier source C sont visibles (et donc
utilisables) dans tout le programme sauf si leur définition est précédée
du mot-clé static. Dans ce dernier cas, la fonction n'est visible que
dans le fichier source la contenant.
fonction(a); [4] [4a] a=1 | b=2 | a=3 | b=3 : entrée dans la fonction, recopie de
l'argument réel (a) dans l'argument formel (b). Mais a n'est plus
visible.
[4b] a=1 | b=2 | a=3 | b=3 | c=0
[4c] a=1 | b=2 | a=3 | b=3 | c=11 : quand le compilateur cherche
la valeur de b, il prend la plus haute de la pile donc 3 (c'est la seule
visible), met le résultat dans le c le plus haut. L'autre b n'est pas modifié.
[4d] a=1 | b=2 | a=3 : suppression des variables locales b et c
du sommet de la pile
} [5] a=1 | b=2 : sortie de bloc donc libération de la pile
fonction(a); [6] [6a] a=1 | b=2 | b=1 : l'argument réel (a) n'est plus le même qu'en [4a]
[6b] a=1 | b=2 | b=1 | c=0
[6c] a=1 | b=2 | b=1 | c=9
[6d] a=1 | b=2 : suppression b et c
} [7] a=1
} [8] la pile est vide, on quitte le programme
Pour faire juste une déclaration d'une variable qui sera définie plus
tard ou dans un autre fichier source, il faut précéder la définition du
mot-clé extern.
Ainsi, les deux lignes suivantes déclarent que a et b existent sans leur
réserver d'espace en mémoire :
#include <stdio.h>
main(){
char nomfich[21] ; int n ;
FILE * sortie ;
printf ("nom du fichier à créer : ") ;
scanf ("%20s", nomfich) ;
sortie = fopen (nomfich, "w") ;
do{ printf ("donnez un entier : ") ;
scanf ("%d", &n) ;
if (n) fwrite (&n, sizeof(int), 1, sortie) ;
}while (n) ;
fclose (sortie) ;
}
9. CRÉATION SÉQUENTIELLE D'UN
FICHIER
-------------------------------------------------------------------
nomfich : tableau de caractères destiné à contenir, sous forme
d'une chaîne, le nom du fichier que l'on souhaite créer.
FILE * sortie ;
signifie que sortie est un pointeur sur un objet de type FILE. C’est
un modèle de structure défini dans le fichier stdio.h (par une
instruction typedef, ce qui explique l'absence du mot struct).
#include <stdio.h>
main(){
char nomfich[21] ;int n ;
FILE * entree ;
printf ("nom du fichier à lister : ") ;
scanf ("%20s", nomfich) ;
entree = fopen (nomfich, "r") ;
while ( fread (&n, sizeof(int), 1, entree), ! feof(entree) )
printf ("\n%d", n) ;
fclose (entree) ;
}
9. LISTE SÉQUENTIELLE D'UN FICHIER
-------------------------------------------------------------------
On trouve cette fois, dans l'ouverture du fichier, l'indication r
(abréviation de read). Elle précise que le fichier en question ne
sera utilisé qu'en lecture. Il est donc nécessaire qu'il existe déjà
(nous verrons un peu plus loin comment traiter convenablement le
cas où il n'existe pas).
b) L'information est recherchée dans un "tampon", image d'une ligne. Il y a donc une certaine
désynchronisation entre ce que l'on frappe au clavier (lorsque l'unité standard est connectée à
ce périphérique) et ce que lit la fonction. Lorsqu'il n'y a plus d'information disponible dans le
tampon, il y a déclenchement de la lecture d'une nouvelle ligne. Pour décrire l'exploration de ce
tampon, il est plus simple de faire intervenir un indicateur de position que nous nommerons
"pointeur".
d) La rencontre dans le format d'un caractère différent d'un séparateur (et de %) provoque la
prise en compte du caractère courant (celui désigné par le pointeur). Si celui-ci correspond au
caractère du format, la fonction poursuit son exploration du format. Dans le cas contraire, il y a
arrêt prématuré de la fonction.
* : la valeur lue n'est pas prise en compte ; elle n'est donc affectée à
aucun élément de la liste
h|l|L :
h : l'élément correspondant est l'adresse d'un "short int". Ce modificateur n'a
de signification que pour les caractères de conversion : d, i, n, o, u, ou x
l : l'élément correspondant est l'adresse d'un élément de type :
- long int pour les caractères de conversion d, i, n, o, u ou x
- double pour les caractères de conversion e ou f
L : l'élément correspondant est l'adresse d'un élément de type long double. Ce
modificateur n'a de signification que pour les caractères de conversion e, f ou
g.
10. Les principales fonctions
de la bibliothèque standard
-------------------------------------------------------------------
conversion :
Ce caractère précise à la fois le type de l'élément correspondant (nous
l'avons indiqué ici en italique) et la manière dont sa valeur sera exprimée.
Les types numériques indiqués correspondent au cas où aucun
modificateur n'est utilisé (voir ci-dessus).
strncpy char * strncpy (char * but, const char * source, int lgmax)
Copie au maximum lgmax caractères de la chaîne source à l'adresse but
en complétant éventuellement par des caractères \0 si cette longueur
maximale n'est pas atteinte. Fournit en retour l'adresse de but.