1.                Structure d'un programme en C.

 

Un programme en C est composé de différents blocs, chacun d'eux ayant un rôle dans la compilation, dans l’édition des liens ou dans l'exécution. Certains de ces blocs sont facultatifs .

 

·       directives de compilation ,

·       définition des types ,

·       déclaration des prototypes des fonctions ,

·       déclarations des variables ,

·       définition des fonctions.

 

Une fonction obligatoire que vous retrouverez dans un programme C est la fonction main() qui est le corps principal de votre programme. C'est cette fonction qui est appelé lors de l'exécution de votre programme avec d'éventuels passages de paramètres possibles.

 

2.                Les commentaires dans un programme.

 

Pour pouvoir insérer des commentaires dans votre programme, la syntaxe suivante est utilisée:

 

début du commentaire

commentaire

fin du commentaire

/*

ceci est un commentaire

/*

 

Un commentaire peut s'étaler sur plusieurs lignes sans devoir nécessairement fermer et rouvrir le commentaire à la ligne suivante.

 

3.                Les déclarations de variables.

3.1.           Introduction.

 

Les variables permettent le stockage de données modifiables lors de l'exécution de votre programme.

Toute utilisation d'une de ces variables n'est possible que si elle est déclarée précédemment. En C, les déclarations peuvent s'effectuer avant la fonction min() ou au début de la définition des  fonctions avant toute ligne de code.

Il va de soit que pour permettre une lisibilité correcte du programme, les déclarations avant le main() doivent s'effectuer en bloc.

 

3.2.           Syntaxe.

 

Permanence classe de stockage modifieur type identificateur = valeur

 

à      La permanence permet de définir si cette variable sera ou non sujette à des modifications dans le programme ou autrement dit si c'est une constante. Elle peut prendre deux valeurs, à savoir:

·       constante

·       volatile (par défaut)

 

à      La classe de stockage permet d'identifier l'endroit ou est stockée la variable et en découlant si cette variable est visible dans l’entièreté du programme. On retrouve les valeurs suivantes possibles:

 

·       auto

·       static

·       extern

·       register

 

à      Les modifieurs permettent d'agir sur le type de donnée utilisée. Ils sont au nombre de quatre:

·       unsigned (non signé)

·       signed (signé)

·       long

·       short

à      Les types de données utilisés en C peuvent être classés en quatre catégories:

 

·       les variables simples

·       les pointeurs (adresses)

·        les tableaux

·       les types définis par l'utilisateur

 

Pour les trois premières catégories, on retrouve les types de base prévus en C:

 

·       char (caractère)

·       int (entier)

·       float (flottants)

·       double (flottants en double précision)

·       void (pas de type)

 

Pour la dernière catégorie, on retrouve les types définissables suivants:

 

·       struct (type structuré)

·       union (type union)

·       enum (type énuméré)

·       typedef

                               

à      L'identificateur est le nom de la variable vous permettant d'y accéder dans votre programme.

 

à      La valeur vous permet d'initialiser la variable avant toute utilisation. Il va de soit que la valeur assignée doit être conforme au type de la variable utilisée

 

3.3.           Les types de variables.

 

3.3.1.      Le type char.

 

Une variable de type char permet de stocker une donnée sous forme d'un caractère codé en ASCII sur 8 bits.

 

3.3.2.      Le type int.

 

Le type int permet le stockage de donnée sous forme entière sans partie décimale. Ces variables sont stockées dans 2 ou 4 octets et dépendent généralement de la taille du bus de données du  microprocesseur ou de ses registres internes.

La norme ANSI prévoit pour les entiers une taille de 2 octets.

 

3.3.3.      Le type float.

 

Le type float permet le stockage de variables sous forme de réels en virgule flottante  positifs ou négatifs. Ces variables sont stockées dans 4 octets  et permettent de représenter des nombres entre 3.4e38 et 3.4e-38 avec une précision de 7 chiffres.

 

3.3.4.      Le type double.

 

Comme son nom l'indique, le type float permet une précision double par rapport au type float. Les variables seront stockées sur 8 octets et permettront de représenter des nombres absolus compris entre 1.7e308 et 1.7e-308 avec une précision de 15 chiffres.

 

3.3.5.      Le type void.

 

On le rencontre principalement dans les fonctions pour préciser que ces fonctions ne retournent aucune valeur  ou  qu'elles ne reçoivent aucun paramètre.

Dans certains cas, on peut définir des pointeurs de type void qui peuvent alors pointer vers tous les types de variables connus; on appelle alors ces pointeurs des pointeurs génériques.

 

3.4.           Les modifieurs.

 

3.4.1.      Les  modifieurs signed et unsigned

 

Ces modifieurs permettent de définir si on travaille en mode signé ou en mode non signé avec les variables de type int ou char. Il faut savoir qu'un nombre signé réserve son bit de point fort pour coder le signe et qu'un nombre non signé ne peut que représenter des nombres positifs; le bit de poids fort est utilisé pour coder le nombre.

 

Type

valeur minimale et maximale

signed int

-32768 et 32767 (si 2 octets)

unsigned int

0 et  65535

signed char

-128 et +127

unsigned char

0 et 255

 

3.4.2.      Les modifieurs short et long

 

Les modifieurs short et long permettent de forcer le nombre d'octets sur lesquels les variables de type int  char et float sont codées.

 

Type

nombre d'octets pour le stockage

short int

2 octets

long int

4 octets

short char

codé sur 7bits en mode non signé

long float

équivalent au double et codé sur 8 octets

 

Les modifieurs signed, unsigned, short et long peuvent être combinés. On peut par exemple retrouvé la syntaxe suivante:

unsigned long int à savoir un entier codé sur 8 octets en mode non signé et permettant un intervalle de valeurs compris entre 0 et 4 294 967 295.

 

3.5.           La classe de stockage.

 

3.5.1.      Concept de visibilité des variables.

 

On appelle visibilité des variables, la possibilité d'y faire référence dans tout le code ou uniquement dans une partie du code.

Une variable déclarée dans une fonction n'est accessible que dans le corps de la fonction uniquement. Ces variables sont stockées dans le segment de pile; elles  sont appelées variables locales.

Une variable déclarée avant la fonction principale appelée main() est visible par tout le programme.  Ces variables sont stockées dans le segment de données; elles sont appelées variables globales.

Une variable locale n'est pas initialisée à une valeur déterminée tandis que la variable globale est initialisée à 0.

3.5.2.      La classe auto.

 

La classe de stockage auto est la classe par défaut des variables locales aux fonctions. Une variable globale ne peut pas être définie en auto. Ce mot clef n'aura donc comme utilité que de rappeler la classe de la variable au programmeur.

Cette classe de stockage ne sera donc que très peu utilisée.

 

3.5.3.      La classe static.

 

C'est la classe de stockage par défaut des variables globales. Lorsque cette classe est utilisée, la variable affectée n'est visible uniquement que par le fichier source ou elle est définie.

Cette classe peut être utilisée pour une variable locale.

 

3.5.4.      La classe extern.

 

Il est intéressant lorsqu'une application contient plusieurs sources de pouvoir déclarer une variable dans une des sources et de pouvoir l'utiliser dans une autre source en conservant le même emplacement mémoire. C'est possible en utilisant la classe extern. Le compilateur qui rencontre cette classe n'alloue pas d'emplacement mémoire pour la variable mais c'est l'édition de lien qui  déclenchera la recherche de la déclaration de la variable. Si aucune déclaration n'est trouvée, l'éditeur de lien génère une erreur.

 

3.5.5.      La classe register

 

Lorsque cette classe de stockage est utilisée, le compilateur ne réserve pas d'emplacement mémoire mais utilise un registre interne du microprocesseur s'il y en a de disponible. Il est clair que dans des boucles par exemple, cette possibilité permet une vitesse d’exécution du programme plus rapide.

Certains compilateurs permettant d'optimiser la vitesse d’exécution des programmes utilisent cet artifice sans que le programmeur doivent utiliser ce mot clef.

Comme la taille des registres est limitée, la possibilité d'utilisation des registres n'est possible que pour les variables et pointeurs ayant une taille inférieure ou égale à la taille des registres.

Il est bon à ce sujet de vérifier les possibilités du compilateur utilisé.

 

3.6.           Les types structurés

 

3.6.1.      Introduction.

 

Les variables de type structuré permettent de regrouper sous une même entité des données de type différent. Ce type est utile lorsque l'on veut gérer des bases de données, des index, des catalogues ou des chaînes liées.

 

3.6.2.      Syntaxe de la déclaration.

 

·       déclaration du type: struct identificateur {

                                                                                type 1     champ 1;

                                                                                type 2     champ 2;

                                                                                type 3     champ 3;

                                                                                .

                                                                                .

                                                                                type N    champ N;

                                                                                };

·       déclaration de la variable de type structuré:

                struct identificateur ident_variable;

 

On peut également retrouver deux autres syntaxes moins usitées:

 

1.     On déclare les variables en même temps que la structure en faisant suivre l'accolade fermée des noms des variables.

struct identificateur {

                                                type 1     champ 1;

                                                type 2     champ 2;

                                                type 3     champ 3;

                                                .

                                                .

                                                type N    champ N;

                                                }variable1, variable 2...

 

2.     Si l'intérêt d'utiliser un identificateur de type ne se fait pas sentir car on ne désire plus par la suite déclarer de variables de ce type, on peut omettre celui ci. On retrouvera alors la structure suivante:

struct {

           type 1    champ1;

           type2     champ2;

           type3     champ 3;

          .

          .

          type N    champ N;

          }variable1, variable2...;

 

3.6.3.      Syntaxe de l'accès aux champs.

 

La syntaxe de l'accès dépend si l'on travail avec un pointeur ou une variable du type de la structure. Voici les deux cas possibles:

 

·       variable.nom_champ

·       pointeur®nom_champ

3.6.4.      Structure de champs de bits.

 

Lorsqu'on travaille avec des variables logiques, il serait intéressant pour économiser le nombre de variables utilisées, de pouvoir associer une variable logique par bit dans un octet. En C, ceci est possible en utilisant les structures de champs de bits. La syntaxe est la suivante:

 

struct identificateur

                                {

                                type1      champ1:longueur;

                                type2      champ2:longueur;

                                .

                                .

                                typeN     champN:longueur;

                                }

longueur représente le nombre de bits associés aux champs correspondants.

Remarque: on peut mélanger une structure classique avec des champs de bits.

 

3.7.           Les types union.

 

3.7.1.      Introduction.

 

Le type union permet à plusieurs variables regroupées sous une même entité mais de types différents d'occuper le même emplacement mémoire. La taille mémoire occupée par cette entité correspond, suivant la norme ANSI, à la taille de la plus grande variable (en adresse et non en valeur). Certains compilateurs ne respectant pas la norme ANSI reserve la taille de la première variable et il donc souhaité alors de placer en tête de l'entité la variable la plus grande.

 

3.7.2.      Syntaxe.

 

Pour la déclaration d'un type union, nous utiliserons la syntaxe suivante:

 

union identificateur{

                                type1      champ1;

                                type2      champ2;

                                .

                                .

                                typeN     champN;

                                };

Pour la déclaration d'une variable du type union identificateur, la syntaxe est la suivante: union identificateur variable;

 

Les autres syntaxes possibles peuvent être obtenues en se référant aux informations reprise dans  les structures.

 

3.7.3.      Syntaxe de l'accès à un champ.

 

L'accès aux champs d'une union se fait identiquement à ceux d'une structure à savoir qu'il faut distinguer l'accès à partir d'une variable ou d'un pointeur.

 

 

·       variable.nom_champ;

·       pointeur®nom_champ;

 

3.8.           Les types enum.

 

3.8.1.      Introduction.

 

Le type énuméré reprend sous une même entité une liste ordonnée d'identificateurs  qui sont initialisés lors de la création d'une variable de ce type.

 

3.8.2.      Syntaxe.

 

·       La déclaration du type: enum identificateur{

                                                                                ident1,

                                                                                ident2,

                                                                                ident3,

                                                                                .

                                                                                .

                                                                                identN

                                                                                }

·       La déclaration d'une variable du type identificateur:

                                 enum identificateur variable1,variable2;

D'autres syntaxes sont possibles et pour ce, il faut se référer aux paragraphe traitant des structures.

Sauf indication contraire du programmeur, l'initialisation des identN se fait en commençant par le premier dans la liste et en lui assignant la valeur entière 0. Les ident suivants se voient attribuer la valeur juste supérieure  à la valeur de l'ident précédent.

Le programmeur peut ne pas se plier à cette règle et forcer une valeur de départ à cette incrémentation. Il suffit pour cela de placer la valeur à droite de l'ident juste avant la virgule.

Il est à remarquer que plusieurs ident peuvent avoir la même valeur mais pas le même nom.

 

3.9.           Définition d'un nouveau type: typedef.

 

3.9.1.      Introduction.

                               

Dans les syntaxes précédents de la structure de l'union et de l'énumération, on remarque que la déclaration d'une variable de ce type nécessite l'emploi du mot clef correspondant à savoir struct, union ou enum.

Il serait intéressant de pouvoir définir pour ces cas un nouveau type. C'est possible en utilisant le mot clef typedef à la différence que ce mot clef n'est pas uniquement réservé aux types précités.

 

3.9.2.      Syntaxe.

 

typedef type identificateur.

3.9.3.      Exemple.

 

typedef  unsigned int UINT;

UINT a; /*ce code permet de déclarer une variable de type unsigned int*/

 

4.                Les déclarations de pointeurs.

 

4.1.           Introduction.

 

Le concept de pointeur en C est très puissants et ceux ci sont très utilisés en programmation. En assembleur, on peut comparer les pointeurs à de l'adressage indirecte. Les pointeurs sont destinés à contenir non pas une valeur comme pour les variables classiques mais une adresse qui pointe vers une variable contenant une valeur.

Les pointeurs vont être utilisés dans les tableaux, les variables simples, les fonctions et les types particuliers précités.

Il est à remarquer que la puissance de programmation que représentent ces pointeurs peut se retourner contre le mauvais programmeur  en bloquant régulièrement son application .

 

4.2.           Syntaxe.

 

On retrouve des pointeurs pour tous les types de variables existantes.

 

type * identificateur;

 

On peut don déclarer un pointeur de type char de la façon suivante: char * pointeur;

 

4.3.           Initialisation des pointeurs.

 

Avant de pouvoir travailler avec les pointeurs, il faut obligatoirement les initialiser. Ceci peut se faire en associant au pointeur l'adresse d'une variable déjà déclarer. Vous devez pour cela utiliser l'opérateur unaire &. Vous obtiendrez alors la syntaxe suivante:

 

int a, * b;

b = &a; /* on associe au pointeur la valeur de l'adresse de a */

 

L'initialisation des pointeurs peut aussi se faire par l'allocation dynamique qui renvoit alors une adresse vers le premier élément  de l'espace mémoire réservé. L'allocation dynamique permet d’allouer momentanément dans un programme assez bien de mémoire qu'on peut désallouer ensuite lorsque l'on en a plus besoin. Je vous renvoie pour ce aux fonctions de type alloc, malloc, réalloc...

 

4.4.           Réservation de la mémoire pour les pointeurs.

 

4.4.1.      Introduction.

 

La déclaration d'un pointeur réserve un emplacement mémoire pour contenir une adresse mémoire. On peut dès lors se poser la question de savoir quelle est la place occupée par un tel pointeur.

La place occupée ne dépend pas du type de la variable mais dépend de l'emplacement où la mémoire va être réservée. On retrouve suivant l'emplacement mémoire utilisé les pointeurs near, les pointeurs far et les pointeurs huge. Pour bien comprendre ces différentes catégories, il est indispensable de connaître la structure mémoire interne des microprocesseurs de la famille 80x86. Une adresse physique est décomposée en deux parties que l'on appelle segment et offset. Le calcul complet de l'adresse physique s'obtient par la formule suivante:

 

adresse physique = (segment *16) + offset. L'adresse physique est codée sur 20 bits et permet d'accéder à  1Mo de mémoire.

La partie offset est codée sur 16 bits et permet d'accéder à des segments dont la taille n’excède pas 64Koctets.

 

Lorsque vous exécutez un programme, l'ensemble des données, du code et de la pile ne se mettent pas n'importe comment. On va retrouver un segment pour le code appelé Code Segment (CS), un segment pour les données appelé Data Segment (DS) et un segment pour la pile que l'on appelle Stack Segment (SS).

Dans certains cas de configuration, certains de ces segments peuvent fusionner avec d'autres et c'est le cas de la pile qui est par défaut confondue avec les données.

 

Le microprocesseur possède des registres internes permettant de mémoriser notamment l'adresse du segment de données, l'adresse du segment de pile et l'adresse du segment de code avec leur pointeur associé.

 

Si on travaille avec un seul segment de données, il n'est pas nécessaire que les  pointeurs des variables contiennent à la fois la valeur du segment et celle de l'offset. Cette dernière suffit car la partie segment est toujours la même et ne doit pas être changée. C'est la raison pour laquelle on retrouve des pointeurs sur 16 bits codant uniquement l'offset; on parle alors de pointeur NEAR.

 

·       Les pointeurs NEAR sont des pointeurs codés sur 16 bits contenant uniquement l'offset.

·       Les pointeurs FAR sont nécessaire si on travaille sur plusieurs segments de données; ils doivent contenir la partie segment et la partie offset. Ces pointeurs seront codés sur 32 bits (16 pour le segment et 16 pour l'offset). On parle alors de pointeurs long ou pointeur FAR. Il est à remarquer que dans l'adressage des tableaux, ces pointeurs ne permettent pas des tailles supérieures à 64K.

·       Les pointeurs HUGE sont eux aussi codés sur 32 bits mais permettent de travailler sur des tableaux dont la taille est comprise entre 64K et 1M.

 

4.4.2.      Les modèles mémoires.

 

Chaques codes et données de programme sont stockées dans des segments. Le modèle mémoire va déterminer l'organisation des segments. On retrouve chez Microsoft les modèles tiny, small, médium, compact, large et huge.

 

Modèles mémoires

le(s) segment(s) de codes

le(s) segment(s) de données

Tiny

Un segment pour le code et les données (<64K)

Un segment pour le code et les données (<64K)

Small

Un segment (<64K)

Un segment (<64K)

Médium

Un segment de code par module (pas de limite)

Un segment (< 64K)

Compact

Un segment (<64K)

Plusieurs segments mais les tableaux doivent être < 64K

Large

Un segment de code par module (pas de limite)

Plusieurs segments mais les tableaux doivent être < 64K

Huge

Un segment de code par module (pas de limite)

Plusieurs segments de données avec des tableaux qui peuvent être > 64K

 

Remarques:

·       pour construire un fichier portant l'extension .COM, il faut compiler votre programme avec l'option TINY.

·       pour les modèles mémoires TINY, SMALL et MEDIUM, tous les pointeurs sont considérés de type NEAR. Pour les modèles COMPACT ou  LARGE, les pointeurs sont de type FAR et pour le modèle HUGE de type HUGE.

·       l'utilisation de pointeurs NEAR est beaucoup plus rapide que celle des pointeurs far et huge car toute lecture ou modification ou calcul sur un pointeurs de type near  ne demande que de considérer l'offset. Les pointeurs HUGE sont encore plus pénalisant du fait que l'on peut passer pour un même tableau d'un segment à l'autre.

 

4.5.           Arithmétique des pointeurs.

 

Le C permet un certain nombre d'opérations arithmétiques sur les pointeurs. Ceci est particulièrement intéressant dans la gestion des tableaux et dans les passages de paramètres et retour de valeur pour les fonctions.

 

Les opérations d’addition et de soustraction sur les pointeurs sont possibles pour autant qu'elles soient effectuées avec des entiers. On pourra donc retrouver les syntaxes suivantes:

 

float * fl_var, tmp, k[100]; /* réservation d'un tableau de 100 flottants*/

int i=10, j=2;

fl_var = &k;

tmp = *( fl_var + 1 ); /* le compilateur sait qu'une variable de type float est codée sur 4 octets et de ce fait, il va donc pouvoir calculer l'adresse final qui vaut fl_var + 1 * 4 octets. */

tmp = *( fl_var + j ); /* Comme j est un entier, le calcul de l'adresse est possible par le compilateur */

tmp = *( fl_var + ( i*j ) ); /* Comme ( i*j ) donne comme résultat un entier, la syntaxe est possible */

fl_var = fl_var +1; /* incrémentation du pointeur de 1 * 4 octets*/

fl_var ++; /* même action que pour la ligne précédente*/

fl_var=fl_var - 1; /* décrémentation du pointeur de 1 * 4 octets*/

fl_var --; /* même action que pour la ligne précédente*/

 

 

                                               

5.                          Les déclarations de tableaux.

 

5.1.           Introduction

               

Le tableau en lui même ne représente pas un type en particulier mais il permet de regrouper plusieurs variables ou pointeurs d'un même type dans un ensemble.  En C, les tableaux peuvent être multidimensionnels permettant ainsi des tableaux à N dimensions.

 

5.2.           Syntaxe.

 

déclaration d'un tableau : déclaration identique à une variable simple excepté que l'identificateur est suivi ces opérateurs  [ et ].

...type identificateur [indice]  pour un tableau à une dimension,

...type identificateur [indice1][indice2] pour un tableau à deux dimensions,

...type identificateur [indice1][indice2]...[indiceN] pour un tableau à N dimensions.

L'accès à un des éléments du tableau se fait en précisant les indices de l'élément recherché entre les mêmes opérateurs.

Exemple:

char tab[10]; /* permet de réserver un tableau à 1 dimension avec 10 éléments*/

char temp;

temp=tab[0];/*le premier élément du tableau est recopié dans la variable temp*/

 

char tab[5][2]; /* permet de réserver un tableau à deux dimensions contenant ( 5*2 ) variables */;

 

Remarque: les constantes entre opérateurs peuvent être remplacées par des variables de type entier.

int i=1,j=2;

char tab[5][6], temp;

temp=tab[i][j];/*l'élément [1][2] du tableau est recopié dans la variable temp*/

 

5.3.           Initialisation d'un tableau.

 

·       Pour un tableau à une dimension:

 

int tab[3]={2,7,5};/* initialisation complète du tableau*/

int tab[4]={1,2}; /* initialisation des deux premiers éléments du tableau*/

 

·       Pour un tableau à deux dimensions:

 

int tab[3][3]={ {1,5,6},{1,3},{5,8,9}};/*l'initialisation de l'élément tab[1][3] n'a pas été effectuée*/

 

Remarque: lorsque un tableau est initialisé, sa dimension peut être omise et c'est le compilateur qui réserve la taille du tableau en fonction du nombre d'éléments initialisés.

int tab[ ] = {2,5,8,9}; /* le compilateur va réservé un tableau de 4 entiers*/

 

5.4.           Allocation mémoire d'un tableau.

 

5.4.1.      Les tableaux à 1 dimension.

 

Prenons comme exemple un tableau de caractères tel que char tab[10].

char tab[]={'I','N','F','O','3'};

En supposant que l'adresse du premier élément du tableau soit 100, nous aurons la configuration suivante:

 

100

101

102

103

104

'I'

'N'

'F'

'O'

'3'

indice 0

indice 1

indice2

indice 3

indice 4

 

Les octets réservés sont contigus en mémoire et respectent l'ordre des indices.

 

5.4.2.      Les tableaux à deux dimensions.

 

Prenons  comme exemple un tableau de caractères tel que char tab[2][2].

char tab[2][2]={ {'a','b'},{'c','d'}};

En supposant due l'adresse du premier élément du tableau soit 100, nous aurons la configuration suivante:

 

100

101

102

103

'a'

'b'

'c'

'd'

indice 0,0

indice 0,1

indice 1,0

indice 1,1

 

Pour accéder à un élément du tableau en connaissant son adresse, vous devez prendre l'adresse de base du premier élément auquel vous ajoutez d'une part la taille du type multipliée par le premier indice et la valeur maximum du second indice et d'autre part le second indice. Si on veut connaître l'adresse de l'élément [1][1], on aura 100+( 1octet * 1 * 1) + 1 = 103.

Si l'on prend le tableau d'entiers int tab[4][4] et que l'on  veut accéder à l'élément d'indices 3,2 on pratiquera de la sorte:

en supposant que l'adresse du premier élément soit 100, on aura 100 + (2 octet * 3 * 3) + 2 = 120.

 

5.4.3.      Les tableaux et les pointeurs.

 

Lorsqu'on travail avec les tableaux, il est parfois utile d'utiliser les pointeurs pour accéder à des éléments en particulier sans devoir gérer les indices. En premier lieu, il faut savoir que le nom du tableau est lui même un pointeur contenant l'adresse du premier élément.

 

int *temp;

int tmp;

int *tempo;

int tab[5]; /* réservation d'un tableau de 5 entiers*/

temp=tab; /* on initialise le pointeur temp avec l'adresse du premier élément du tableau tab*/

tempo=&tab[3]; /* on initialise le pointeur tempo avec l'adresse de l'élément d'indice 3*/

tmp=*( tab + 2); /* cette syntaxe revient à initialiser la variable tmp avec l'élément d'indice 2 du tableau tab*/

 

 

Il est inutile dans le dernier cas de faire entrer en ligne de compte la taille du type car c'est le compilateur qui s'en charge (voir calcul sur les pointeurs).

On peut également retrouver la syntaxe suivante :

tmp=*(&tab[2]+1)

/* on initialise la variable tmp avec l'élément pointé par l'adresse &tab[2] + 1 * la taille d'un entier. 

 

5.4.4.      Les tableaux de pointeurs.

 

Pour réserver un tableau de pointeurs pour un type donné, il faut utiliser la syntaxe suivante:

 

type * identificateur[dimension];

 

type est un type quelconque du langage C, prédéfini ou construit par l'utilisateur sous forme d'un type structuré, énuméré et union.

 

Exemple:

 

char *tab[10]; /* réservation d'un tableau de 10 pointeurs de type char*/

int a,b;

tab[0]=&a;/*initialisation du pointeur tab[0] avec l'adresse de a*/

b=*tab[5]; /* initialisation de la variable b avec l'élément pointé par le pointeur tab[5]*/

 

6.                Les fonctions.

 

6.1.           Introduction.

 

La programmation ne consiste pas à réaliser des programmes monolithiques dans lesquels toute modification ou toute adaptation est très difficile. Il est clair pour tout bon programmeur que les avantages offerts par la modularité sont multiples.

La modularité consiste à réaliser des modules sous formes de fonctions ou procédures qui soient indépendants les uns des autres. Il facile de comprendre qu'une modification d'une de ces fonctions ne doit pas nous obliger à des modifications dans d'autres parties du programme.

Le fait que les fonctions aient souvent leurs sources dans des modules séparés du  corps du programme principal, la compilation est plus rapide car uniquement effectuée pour les codes modifiés.

Le gain en taille du code final est également appréciable car l'utilisation de fonctions évite la répétition de parties équivalentes.

Des fonctions souvent utilisées par le programmeur peuvent donner lieu à la création de librairies personnelles qui contribuent à une diminution du codage et peuvent amener une homogénéisation         dans le développement d'applications semblables.

La modularité est un critère essentiel dans l'analyse d'un projet: un bon découpage en fonctions et sous fonctions est essentiel. Ce découpage commencera par la création des fonctions de bas niveau avec leur test pour ensuite remonter vers les fonctions de haut niveau intégrées à leurs tours dans la fonction principale du programme qui est la fonction main().Dans l'objectif de pouvoir aborder le C++ et la programmation sous windows, il est impératif que  l'analyse modulaire d'un projet soit prédominant dans votre travail.

 

6.2.           Les déclarations des prototypes des fonctions.

 

Le prototype de la fonction est déclarée avant la fonction principale main(). Elle a pour objectif de définir le type de la  valeur retournée par la fonction ainsi que les types et les noms des paramètres de la fonction.

Le but de cette déclaration est double:

1.     elle va permettre au compilateur de pouvoir vérifier les appels à ces fonctions dans le code de votre programme et de pouvoir détecter tout problème lors de passages de paramètres ou les retour de valeurs.

2.     elle permet au programmeur de pouvoir rapidement retrouver une liste de toutes les fonctions définies et d'en retrouver les paramètres dont les noms sont souvent évocateurs ainsi que les types .

La déclaration est facultative en C si la définition de la fonction se trouve dans la même source mais je la conseille fortement pour les raisons données précédemment.

Les syntaxes en C sont les suivantes:

·       identificateur();

·       type identificateur();

·       type identificateur(paramètre1,paramètre2,...);

·       type identificateur(type1,type2,...); /*norme ANSI*/

·       type identificateur(type1 paramètre1, type2 paramètre 2,...);/*norme ANSI*/

Je conseille d'utiliser la dernière syntaxe.

 

Exemple:

 

int fonction (int min, int max);

 

6.3.           Les définitions des fonctions.

 

On entend par définitions des fonctions, la définition des codes faisant partie de cette fonction.

On va retrouver la syntaxe suivante pour les définitions:

 

type identificateur ( type1 paramètre1, type2 paramètre 2,...)

{  /*début du corps de la fonction*/

 

                code;

 

} /* fin du corps de la fonction*/

 

On peut retrouver également la syntaxe suivante qui est moins utilisée:

 

type identificateur (paramètre1,paramètre 2,...)

type1 paramètre1;

type2 paramètre2;

.

.

.

{  /*début du corps de la fonction*/

 

                code;

 

} /* fin du corps de la fonction*/

 

6.4.           Les passages de paramètres aux fonctions.

 

Les paramètres formels d'une fonction sont créés sur la pile lors de l'appel de la fonction et détruits à la fin de son exécution.

On retrouve deux façon de passer des paramètres:

·       par valeur

·       par pointeurs.

Par valeur, les le paramètre formel reçoit une copie de la valeur contenue dans la variable transmise lors de l'appel de la fonction. Cela signifie que toute modification du paramètre formel dans le corps de la fonction n'est pas répercutée sur la valeur de la variable transmise. Cette technique est intéressante car elle permet une encapsulation de la fonction sans débordement possible.

Comme le rôle de la fonction peut être de fournir des informations aux programme appelant, il est possible de retourner une valeur ou un pointeur par la fonction. Il est clair que cette méthode ne permet le retour que d'un seul pointeur ou d'une seule valeur. Pour étendre les possibilités, le passage de paramètres par pointeurs est autorisé.

 

·       Exemple de passage par valeur:

 

/* déclaration des prototypes*/

void main(void);

void fonction(int a, int b);

 

/* coprs principal de la fonction*/

 

void main(void)

{

                int x=1,y=2;

                fonction (x,y); /* appel de la fonction*/

                printf("valeurs initiales: %d %d \n",x,y);

}

 

/* définition de la fonction*/

void fonction(int a,int b)

{

                printf("valeurs de a et b dans la fonction:%d %d\n",a,b);

                a=10;

                b=20;

                                printf("valeurs de a et b modifiés: %d %d\n",a,b);

                }

L'exécution du programme permettra l'affichage suivant:

valeurs de a et b dans la fonction: 1 2

valeurs de a et b modifiés: 10 20

valeurs initiales: 1 2

 

·       Exemple de passage par pointeurs:

 

/* déclaration des prototypes*/

void main(void);

void fonction(int a, int *b);

 

/* coprs principal de la fonction*/

 

void main(void)

{

                int x=1,y=2;

                fonction (x,&y); /* appel de la fonction*/

                printf("valeurs initiales: %d %d \n",x,y);

}

 

/* définition de la fonction*/

void fonction(int a,int *b)

{

                printf("valeurs de a et b dans la fonction:%d %d\n",a,*b);

                a=10;

                *b=20; /* comme b a la même adresse que y, toute modification de *b provoque une           modification de  y*/

 

                                printf("valeurs de a et b modifiés: %d %d\n",a,*b);

                }

L'exécution du programme permettra l'affichage suivant:

valeurs de a et b dans la fonction: 1 2

valeurs de a et b modifiés: 10 20

valeurs initiales: 1 20

 

Remarque: la valeur de a n'a pas été modifiée car le passage de paramètre s'est fait par valeur tandis que la variable y a eu son contenu modifié car le passage de paramètre s'est fait par pointeur.

 

6.5.           Retour de valeur ou de pointeur par une fonction.

 

Cette technique permet de retourner une valeur ou un pointeur d'un type quelconque de la fonction appelée vers la fonction appelante. Dans le coprs de la fonction appelée, le retour est possible par le mot clef RETURN en utilisant les syntaxes suivantes:

 

return (valeur);

return (identificateur);

return (&identificateur);     

 

Le mot clef RETURN peut être utilisé plusieurs fois dans une fonction pour retourner des valeurs différentes mais la fonction se termine lors de l'exécution du premier return rencontré.

 

Exemple de retour par valeur et par pointeur:

 

/* déclaration des prototypes*/

void main(void);

int  maximum(int a, int b);

int *minimum(int a, int b);

 

/* coprs principal de la fonction*/

 

void main(void)

{

                int x=1,y=2,*tmp;

                printf( "valeur maximum:%d\n",maximum (x,y));

                tmp=minimum(x,y);

                printf("valeur minimum: %d\n,*tmp);

}

 

/* définition de la fonction*/

int maximum (int a,int b) /* fonction avec retour par valeur*/

{

                if (a > b) return a;

                else return b;

                }

 

int *minimum(int a,intb) /* fonction avec retour par pointeur*/

{

                if (a<b) return(&a);

                else return(&b);

}

 

L'exécution du programme permettra l'affichage suivant:

valeur maximum: 2

valeur minimum: 1

 

6.6.           Les variables globales et les fonctions.

 

Comme les variables globales sont visibles dans tout le source, il est clair que l'on peut les utiliser pour passer des paramètres dans les fonctions et vis versa. Naturellement, un programme utilisant cette technique serait très peu lisible car ne sachant à priori quelle sont les variables qui sont utilisées dans les fonctions.

Je déconseille vivement cette méthode.

 

6.7.           Les pointeurs sur les fonctions.

 

Les pointeurs sur les fonctions sont une des particularités du langage C. Lors de leur emploi, il ne faudra pas confondre un pointeur de fonction et une fonction retournant un pointeur.

 

La syntaxe est la suivante:

 

type (*identificateur)();

 

Exemple d'utilisation:

 

int maximum();

int (*ptr_maximum)();

ptr_maximum = maximum;

 

On peut également initialiser les pointeurs lors de leur déclaration:

 

int a;

int maximum();

int (*ptr_maximum)()=maximum;

 a=*ptr_maximum(); /* appel à une fonction par pointeur*/

 

6.8.           Les appels aux fonctions.

 

L'appel à une fonction peut se faire par l' identificateur de la fonction suivie par des parenthèse (). Elle peut se faire aussi, comme renseigné au paragraphe précédent, par l'utilisation d'un pointeur.

La syntaxe d'appel à une fonction est la suivante:

identificateur de la fonction (arg1,arg2,...argn);

pointeur de la fonction (arg1,arg2,...argn);

6.9.           Les bibliothèques standard

 

Les bibliothèques standard contiennent de fonctions couramment utilisées en C. On retrouvera  notamment toutes des fonctions d'entrées/sorties, les fonctions de manipulations de données telles que des chaines de caractères, des fonctions graphiques, des accès aux fonctions du BDOS (int 21) permettant la gestion directe du matériel.

Ces fonctions sont reprises dans  des fichiers portant l'extension .LIB et se trouvant généralement dans un répertoire tel que LIB.

L'ensemble des prototypes de ces fonctions sont repris dans des fichiers d'en tête portant l'extension .h et que l'on retrouve généralement dans un répertoire portant le non INCLUDE.

 

Exemple:

 

les fonctions gérant les chaines de caractères ont leur prototype dans le fichier d'en tête STRING.H.

 

L'utilisation de bibliothèques "faites maisons" est possible mais cela fera l'objet d'un chapitre distinct.

 

7.                Les opérateurs.

 

7.1.           Introduction.

 

Le C offre au programmeur un jeu d'opérateurs lui permettant d'effectuer un bon nombre d'opérations sur un ensemble de données. Suivant les types de données ou leurs fonctions, on pourra les classer dans les catégories suivantes:

 

·       les opérateurs arithmétiques,

·       les opérateurs logiques,

·       les opérateurs conditionnels,

·       les opérateurs d'adressage indirect (*) et adresse de  (&),

·       les opérateurs de décalage et de masquage,

·       les opérateurs d'accès aux champs

·       les opérateurs d'affectation.

 

Certains de ces opérateurs ont déjà été abordés dans les paragraphes précédents et ne seront plus que cités.

 

7.2.           Les opérateurs arithmétiques.

 

Ces opérateurs  mettent uniquement en jeu des opérandes numériques réels ou entiers. On retrouve les opérations classiques comme l'addition, la soustraction, la multiplication et le reste de division entière (modulo). La difficulté ne réside pas dans la syntaxe de ces opérateurs mais dans le type des calculs intermédiaires qui peut bien souvent conduire à des erreurs.

 

7.2.1.      L'opérateur plus binaire +.

 

Cet opérateur '+' effectue la somme de deux opérandes. Parmi les opérations d'additions valides, on retrouve les cas suivants:

 

Opérande 1

Opérande 2

Commentaires

Pointeur

Entier

On ajoute au pointeur l'entier multiplié par la taille du type du pointeur. Seul un entier peut être ajouté à un pointeur.

Entier

Entier

Entier

Entier

Flottant

Flottant

Entier

Le résultat est du type flottant après conversion.

Flottant

Flottant

Flottant

 

7.2.2.      L' opérateur binaire -.

 

Cet opérateur '-' effectue la soustraction entre deux opérandes. Parmi les opérations de soustractions valides, on retrouve les cas suivants:

 

Opérande 1

Opérande 2

Commentaires

Pointeur

Entier

On soustrait de l'adresse l'entier multiplié par la taille du type du pointeur

Entier

Flottant

Flottant

Entier

le résultat est de type flottant après conversion.

Entier

Entier

Entier

Flottant

Flottant

Flottant

 

 

7.2.3.      La multiplication *

 

Cet opérateur '*'  effectue la multiplication entre deux opérandes. Parmi les opérations de multiplications valides, on retrouve les cas suivants:

 

Opérande 1

Opérande 2

Commentaires

Entier

Entier

Entier

Entier

Flottant

Le résultat est de type flottant après conversion.

Flottant

Flottant

Flottant

Pointeur

Entier

On ajoute au pointeur la taille du type multipliée par la valeur de l'entier.

 

7.2.4.      La division /

 

Cet opérateur effectue la division de la première opérande par la suivante. Parmi les opérations de divisions valides, on retrouve les cas suivants:

 

Opérande 1

Opérande 2

Commentaires

Entier

Entier

Entier. Le résultat du flottant correspondant à l'opération réelle est tronqué.

Flottant

Entier

Le résultat est de type flottant après conversion.

Entier

Flottant

Le résultat est de de flottant après conversion.

Flottant

Flottant

Flottant.

 

 

7.2.5.      Le modulo %

 

L'opérateur % fournit le reste de la division entière de la première opérande  par la seconde.

Opérande 1

Opérande 2

Commentaires

Entier

Entier

Donne un entier comme résultat

 

Le compilateur microsoft ne permet que d'utiliser des opérandes des type entier.

 

7.3.           Les opérateurs logiques.

 

7.3.1.      Introduction.

 

En C, il n'existe pas de variables booléennes par défaut. On retrouve un type énuméré déclaré sous l'identificateur BOOL. Ce type énuméré reprend les deux champs suivants:

·       FALSE qui est initialisé avec la valeur entière 0 et

·       TRUE qui est initialisé avec la valeur entière 1.

Il faut signaler que l'utilisation de ces valeurs TRUE et FALSE n'est pas tout à fait conforme aux règles utilisées dans les structures de test et de boucle à savoir que:

·       les expressions considérées comme FAUSSES se voient attribuer une valeur nulle,

·       les expressions considérées comme VRAIES se voient attribuer une valeur différentes de 0.

Prenons l'exemple suivant:

 

# include <stdlib.h>

# include <conio.h>

void main (void)

{

BOOL a,b;

a=FALSE;

b=!a; /* je prends le complément de a*/

if (b==TRUE)

                printf("la variable b est à TRUE\n");

else

                printf("la variable b est à FALSE\n");

}

                               

Commentaire:

 

la variable a est initialisée à 0. Lorsque je prend le complément de la variable pour l'affecter à la variable b, je sais que b est VRAIE c-à-d que la valeur qui lui est affectée est différente de 0. Mon test n'est donc pas correct car je le limite à tester si la variable vaut 1.  Pour que mon test soit correct, je dois utiliser une des syntaxes suivantes:

 

if (b) /* le test est correct si la condition est vraie*/

                printf("la variable est  à TRUE\n");

else        

                printf("la variable est à FALSE\n");

 

ou encore:

 

if (b != FALSE) /* se traduit par b différent de FALSE*/

                printf("la variable b est à TRUE\n");

else

                printf("la variable b est à FALSE\n");

 

Généralement nous utiliserons des entiers pour caractériser une variable logique mais il est bon de signaler que les opérateurs logiques peuvent être appliqués à n'importe quel type d'opérande entier, flottant ou pointeur. Attention que le résultat de l'opération est toujours entier.

 

7.3.2.      La négation unaire logique !.

 

A

!A

Vrai

Faux

Faux

Vrai

 

Exemple:

 

int b;

int a=10; /* variable qui  d'un point de vue logique est une variable booléenne à l'état "VRAI"*/

b=!a;

 

7.3.3.      L'opérateur  "ET" logique &&.

 

A

B

A && B

Faux

Faux

Faux

Faux

Vrai

Faux

Vrai

Faux

Faux

Vrai

Vrai

Vrai

 

L'opérateurs peut  être utilisé avec des variables booléennes ou avec tout opérateur renvoyant une information de type booléen.

Exemple:

 

int a=10,b=0;

if (a && b) printf ("a  et b sont vraies\n");

if ((a >100) && (b != 0)) printf ("a est plus grand que 100 et b est différent de 0\n");

if (a && ( b != 0 ) printf(" a est vrai et b est différent de 0\n");

 

Attention:  il faut remarquer que si  l'un des opérandes est faux, l'autre opérande n'est pas évalué, l'expression logique résultante étant fausse.

7.3.4.      L'opérateur "OU" logique ||.

 

A

B

A || B

Faux

Faux

Faux

Faux

Vrai

Vrai

Vrai

Faux

Vrai

Vrai

Vrai

Vrai

 

Exemple:

 

int a=10,b=0;

if (a ||  b) printf ("a  ou b est vraie\n");

if ((a >100) ||  (b != 0)) printf ("a est plus grand que 100 ou b est différent de 0\n");

if (a  ||  ( b != 0 ) printf(" a est vrai ou b est différent de 0\n");

 

Attention:  il faut remarquer que si l'un des opérandes est vrai, l'autre opérande n'est pas évalué, l'expression logique résultante étant vraie.

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

int a=0,c=10;

if (c || fonction(a))

                printf("expression vraie\n");

else

                printf("expression fausse\n");

}

 

int fonction(int a)

{

                printf("fonction appelée\n");

                return (a);

}

 

Comme c est vraie, le deuxième opérande n'est pas testé et don le message "fonstion appelée" n' apparaitra pas sur votre écran.

 

7.4.           Les opérateurs relationnels.

 

7.4.1.      Introduction.

 

Les opérateurs relationnels sont utilisés dans des structures de test et de boucle. Le résultat envoyé par cet opérateur est de type entier et interprété comme une variable booléenne avec les relations suivantes:

·       une relation est vrai et le résutat associé est différent de 0,

·       une relation est fausse est le résultat associé est 0.

 

Plusieurs  opérateurs relationnels peuvent être établies entre elles par les opérateurs logiques repris dans le paragraphe précédent.

 

7.4.2.      L'opérateur d'infériorité <.

 

L'opérateur renvoie la valeur vraie si  son premier  opérande est srictement inférieur au second et  faux dans le cas contraire.

 

Exemple:

 

int a=10,b;

if ( a < 100 ) printf (" a est srictement plus petit que 100\n");

b = ! ( a >= 100);

if ( b) printf ("a est strictement plus petit que 100\n");

 

7.4.3.      L'opérateur d'inféririté large <=.

 

L'opérateur renvoie la valeur vraie si le premier opérande est plus petit ou égale au second opérande et faux dans le cas contraire.

Exemple:

 

int a=10,b;

if ( a < =100 ) printf (" a est  plus petit ou égal à 100\n");

b = ! ( a > 100);

if ( b) printf ("a est plus petit ou égal à 100\n");

 

7.4.4.      L'opérateur de supériorité >.

 

L'opérateur renvoie la valeur vraie si le premier opérande est strictement plus grand le second opérande et faux dans le cas contraire.

Exemple:

 

int a=10000,b;

if ( a > 100 ) printf (" a est srictement plus grand que 100\n");

b = ! ( a <= 100);

if ( b) printf ("a est strictement plus grand que 100\n");

 

7.4.5.      L'opérateur de supériorité large >=.

 

L'opérateur renvoie la valeur vraie si le premier opérande est plus grans ou égale au second opérande et faux dans le cas contraire.

Exemple:

 

int a=1000,b;

if ( a >= 100 ) printf (" a est  plus grand ou égal à 100\n");

b = ! ( a < 100);

if ( b) printf ("a est plus grand ou égal à 100\n");

 

7.4.6.      L'opérateur d'égalité ==.

 

L'opérateur renvoie la valeur vraie si le premier opérande est strictement égal au second et faux dans le cas contraire.

Exemple:

 

int a=10;

if ( a == 10 ) printf("a vaut 10\n");

else printf("a est différent de 10\n");

 

7.4.7.      L'opérateur d'inégalité !=.

 

L'opérateur renvoie la valeur vraie si le premier opérande est différent du second et faux dans le cas contraire.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

 

int fonction(int a);

 

void main (void)

{

 

char a;

do

{

                if (kbhit()) /* la fonction kbhit() renvoie une valeur non nulle si un                                                            caractère est disponible dans le tampon du clavier*/

                {

                                a=getch(); /* la fonction getch() va chercher le caractère en attente                                                          dans le tampon du clavier*/                         

                                printf("le caractère %c a ete tape\n",a);

                }

               

}while ((a !='q') && (a != 'Q')); /* boucle tant que a est différent de q et de Q*/

printf("sortie du programme\n");

 

}

 

 

7.5.           Les opérateurs de décalage et de masquage.

 

7.5.1.      Introduction.

 

Ces opérateurs permettent de travailler comme l'assembleur au niveau des bits.  Les seuls opérandes possibles sont de type entier et le résultat renvoyé par ces opérateurs sont également des entiers. ces opérateurs acceptent les conversions arithmétiques usuelles, le résultat ayant le type de l'opérande après conversion.

 

7.5.2.      L' opérateur de complément ~.

 

Cet opérateur permet de compléménter à 1 son opérande. C'est un complément bit à bit qui est effectué; il ne faut pas le confondre avec l'opérateur ! qui donne le complément logique.

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=0xAAAA;

                printf("%x\n",~a);

 

}

 

si nous convertissons la valeur hexadécimale 0xAAAA en binaire, nous obtenons 101010101010. Si nous complémentons ensuite ce nombre binaire bit à bit, nous obtenons 0101010101010101 ce qui donne en hexadécimal 5555.

 

7.5.3.      L'opérateur "ET" bit à bit &.

 

Cet opérateur effectue un ET bit à bit entre le premier opérande et le second. Il compare chaque bit du prmier opérande à celui  du second opérande. Si les deux bits sont à 1, le bit résultant de l'opération est mis à 1. Dans le cas contraire, le bit résultant sera mis à zéro.

On appelle encore cet opérateur un opérateur de masquage car il permet de n'obtenir que d'une opérande l'ensemble des bits correspondant à ceux du second opérande  qui sont à 1.

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=0xAAAA;

                int b=0x00FF;

                int c=0x1254;

                int d=0x0008;

                printf("%x\n",a & c);

                printf("%x\n",a & b);

                printf("%x\n",a & d);

                if (a & d) printf("le bit du rang 3 est à 1\n");

                else printf("le bit du rang 3 est à 0\n");

}

 

 Commentaires:

 

·       l'opération a & c donne comme résultat 200,

               

Valeur de a en hexadécimal

0xAAAA

Valeur de a en binaire

1010101010101010

Valeur de c en binaire

0001001001010100

Le résultat de  a & c en binaire

0000001000000000

Le résultat de a & c  hexadécimal

0x0200

 

·       l'opération a & b donne comme résultat 0x00AA. On a masqué l'octet de poids fort du premier opérande pour ne conserver que la partie basse.

 

Valeur de a en hexadécimal

0xAAAA

Valeur de a en binaire

1010101010101010

Valeur de b en binaire

0000000011111111

Le résultat de a & b en binaire

0000000010101010

Le résultat de a & b en hexadécimal

0x00AA

 

·       l'opération de  a & d donne comme résultat  0x0008

 

Valeur de a en hexadécimal

0xAAAA

Valeur de a en binaire

1010101010101010

Valeur de d en binaire

0000000000001000

Valeur de d & a en binaire

0000000000001000

Valeur de d & a en hexadécimal

0x0008

 

Cette dernière opération permet de tester si le bit de rang  3 est à 1 ou à 0 dans le premier opérande.

 

7.5.4.      L'opérateur "OU" bit à bit |.

 

L'opérateur ou bit à bit non exclusif compare chaque bit du premier opérande au bit correcpondant du second opérande. Si l'un des deux bits est à 1, le bit résultant est mis à 1. Dans le cas contraire, le bit résultant est mis à 0.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=0x1256;

                int b=0x0008;

                printf("%x\n",a|b);

}

 

Cet exemple permet de montrer comment on peut placer un bit à un dans une configuration binaire, sans changer la valeur des autres bits. Nous aurons ici le bit du rang 3 qui sera placé à 1 et nous obtiendrons donc comme résultat 0x125E.

 

7.5.5.      L'opérateur "OU exclusif" bit à bit ^.

 

L'opérateur ou exclusif compare chaque bit du premier opérande au bit correspondant du second. si un des bits est à 1 est que l'autre est à zéro, le bit résultant est à 1. Dans les autres cas, le bit résultant est à zéro.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=0x1256;

                int b=0x0008;

                int c=a|b;

                printf("%x\n",c);

                printf("%x\n",c^b);

 

}

 

Cet exemple montre la façon dont on peut complémenter un bit sans modifier la valeur des autres bits.

 

7.5.6.      L'opérateur de décalage à gauche <<.

 

L'opérateur de décalage va décaler le premier opérande vers la gauche du nombre de positions spécifiées par le second opérande. Les deux opérandes doivent être de type entier. Avec le décalage vers la gauche, les bits vacant situés à droite sont mis à 0. Le résultat du décalage est indéfini si le second opérande est négatif ou plus grand que le nombre maximum de positions du  premier opérande.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=0x1256;

                int b=0x0001;

                printf("%x\n",a | (b<<3));

 

}

 

Cet exemple permet de positionner un bit à 1 en renseignant uniquement le rang occupé par le bit sans devoir  jongler avec les notations hexadécimales.

Remarque: l'opération de décalage à gauche de une position revient à multiplier le premier opérande par 2.

 

7.5.7.      L'opérateur de décalage à droite >>.

                               

L'opérateur de décalage va décaler le premier opérande vers la droite du nombre de positions spécifiées par le second opérande. Les deux opérandes doivent être de type entier. Avec le décalage vers la droite, les bits vacant situés à gauche sont mis à 0 si l'entier est non signé. Si l'entier est signé, le remplissage s'effectue avec la valeur du bit de signe. Le résultat du décalage est indéfini si le second opérande est négatif ou plus grand que le nombre maximum de positions du  premier opérande.

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=-127,b=127;

                printf("a vaut %x, b vaut %x\n",a,b);

                printf("%x %x\n",(a>>4),(b>>4));

 

}

 

 

a avant le décalage (en hexadécimal)

0xFF81

a après le décalage

0xFFF8

b avant le décalage

0x007F

b après le décalage

0x0007

 

 

7.5.8.      L'opérateur conditionnel ?:.

 

Syntaxe: opérande1 ? opérande2 : opérande3

 

L'opérande 1 est  considérée comme une variable booléenne. Elle peut don être de type entier, flottant ou pointeur.

En fonction de la valeur de cette variable booléenne, l'opérande2  ou l'opérande3 sera évaluée. Ces deux opérandes peuvent être de type entier, flottant ou pointeur.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

int fonction(int a);

 

void main (void)

{

                int a=10,c,b=50;

                c=(a>20)?a:b;

                printf("%d\n",c);

}

 

La fonction printf  donnera comme résultat c=50.

 

7.6.           Les opérateurs d'affectation.

 

7.6.1.      Introduction.

 

Un opérateur d'affectation assigne la valeur de l'opérande situé à main droite  à l'emplacement mémoire désigné par l'opérande située à main gauche. Cependant, l'opérateur situé à main gauche de l'opérateur d'assignement doit être une expression lvalue modifiable. Les opérateurs d'affectation convertisent automatiquement leur opérande de droite en fonction du type de leur opérande de gauche.

 

7.6.2.      L'opérateur d'affectation simple =.

 

L'opérateur affecte la valeur du second opérande à son premier.

 

Exemple:

 

int a,b=125;

a=10;

a=b+10;

 

7.6.3.      Les opérateurs d'affectations arithmétiques.

 

Ces opérateurs reprennent l'ensemble des opérateurs arithmétiques *, +, -, /, % et l'opérateur d'affectation simple = pour former les opérateurs suivants: *=, +=, -=,  /=, %=.

 

Exemple:

 

int a=10,b=20;

a=a+10;

/* Comme l'opérande de gauche se retrouve comme opérande de l'opérateur arithmétique, nous pouvons utiliser l'opérateur d'affectation arithmétique suivant: */

a+=10;

/* Cette règle est valable pour tous les autres opérateurs*/

a=a*b;

a*=b;

 

7.6.4.      Les opérateurs d'affectations binaires (bit à bit).

 

Ces opérateurs reprennent l'ensemble des opérateurs binaires &, |, >>, <<, ^ et l'opérateur d'affectatin simple = pour former les opérateurs suivants: &=, |=, >>=, <<=, ^=.

 

Exemple:

 

int a=0x1256,b=0x56AB;

a=a & b;

/* Comme l'opérande de gauche se retrouve comme opérande dans l'opérateur binaire, nous pouvons utiliser l'opérateur d'affectation binaire suivant: */

a &=b;

/* Cette règle est valable pour les autres opérateurs.*/

a=a>>b;

a>>=b;

 

7.6.5.      Les opérateurs d'incrémentation et de décrémentation unaires.

 

L'opérateur d'incrémentation unaire est ++ et permet d'incrémenter son opérande de 1.

L'opérateur de décrémentation unaire est -- et permet de décrémenter son opérande de 1.

Cet opérateur peut affecter des variables de type entier, flottant ou des pointeurs. Pour ce dernier point, je vous renvoie au paragraphe traitant des opérations arithmétiques sur les pointeurs.

Ces opérateurs peuvent se placer avant ou après l'opérande. Si cet opérateur est seul , l'effet sera identique que l'opérande se place avant ou après. La différence de comportement apparaît lorsque l' opérateur d'affectation est utilisé dans la même expression.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>

 

 

void main (void)

{

                int a=10,b=20,c;

    ++a;  / équivalent à a=a+1;

    b++; / équivalent à b=b+1;

    printf("a vaut: %d, b vaut: %d\n",a,b);

    c=++a; /* a est incrémenté et ensuite affecté  à c*/

    printf("c vaut: %d, a vaut: %d\n",c,a);

    c=a++; /* a est affecté à c et ensuite incrémenté*/

    printf("c vaut: %d, a vaut: %d\n",c,a);

}

 

Pour la syntaxe "c=++a" on parlera de pré-incrémentation tandis que pour  "c=a++" on parlera de post-incrémentation.

 

Il est util de signaler que l'emploi de cet opérateur est conseillé car il fait appel aux instructions équivalente INC ou SUB de l'assembleur qui sont pluis rapides que les instructions ADD et SUB.

 

7.6.6.      L'opérateur de taille "sizeof".

 

Syntaxe: sizeof  (expression)

 

Cet opérateur renvoie la place mémoire en octets associée à une variable ou un type. Lorsque cet opérateur est appliqué uaux types structurés ou aux variables, l'opérateur renvoie la taille actuelle qui peut donc inclure des caractères de remplissage insérés pour l'alignement.

Quand l'opérateur s'applique à un tableau dimensionné de façon statique,  celui-ci renvoie la taille complète du tableau.

L'opérateur ne peut pas retourner la taille d'un tableau alloué dynamiquement ou un tableau externe.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>                   

 

struct test

{

char a;

int b;

};

 

int tab[10];

 

void main (void)

{

                printf("taille:%d\n",sizeof(tab));

                printf("taille:%d\n",sizeof(struct test));

}

 

L'exécution du programme nous donne l'affichage suivant:

taille: 20

taille: 4

 

Le premier opérateur sizeof renvoie la valeur 20 car nous avons un tableau de 10 entiers codés chacun sur 2 octets, ce qui nous donne 20 octets au total.

Le deuxième opérateur nous renvoie la valeur 4 ce qui peut nous étonné car la structure contient un caratère codé sur un octet et un entier codé sur 2, ce qui donne trois octets au total et non quatre. C'est sans compter les caractères de remplissages pour les alignements.

 

7.7.           L'opération de "casting" (moulage).

 

7.7.1.      Introduction.

 

L'opération de casting consiste à forcer la conversion d'un type de donnée dans un autre type. Les conversions peuvent être effectuées automatiquement par le compilateur ou explicitement en utilisant ce que l'on applelle un "cast".

Il faut  apporter une information importante:

·       Si vous utilisé le forçage automatique, le compilateur  vous enverra un "WARNING" si vous tentez d'assigner une opérande occupant un emplacement mémoire plus grand que le second opérande.

·       Si vous utilisez un cast, ce message d'erreur n'apparaîtra pas.

 

Exemples:

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

               

                int a=10,c;

                long int b=20,d;

                d=a;

}

 

Ce premier exemple ne donne pas de warning lors de la compilation car vous affectez un entier de type court (2 octets) à un entier de type long (4 octets). Il n'y a donc pas de risque de pertes d'informations.

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

               

                int a=10,c;

                long int b=20,d;

                c=b;

}

 

Ce deuxième exemple donne un warning lors de la compilation car il y a des risques de pertes d'informations: on veut affecter un entier long à un entier court.

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

               

                int a=10,c;

                long int b=20,d;

                c=(int)b;

}

 

Ce troisième exemple, bien qu'il puisse conduire à des pertes d'informations, ne donne pas de warning car vous avez effectué le "cast" vous-même.

 

7.7.2.      Conversion entre types signés et non signés.

 

·       Pour le passage d'un entier court signé à un entier court non signé, tous les bits sont conservés.

·       Pour le passage d'un  entier court signé à un entier court non signé, le bit de signe est décalé vers la position la plus à gauche et les positions vides sont remplies avec des 0.

·       Pour le passage d'un caractère signé à un entier signé ou non signé, le bit de signe est décalé vers la position la plus à gauche et les positions vides sont remplies par le bit de signe.

·       Pour le passage d'un caractère non signé à un entier signé ou non signé, les positions vides sont remplies par des zéros.

·       Si on passe d'un entier long à un entier court ou d'un entier à un caractère, seules les bits de poids faibles sont recopiés.

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

                int a=0x8010;

                int b;

                signed char c=-126;

                signed long int d;

                b=a;

                printf("valeur de b en hexa: %x\n",b);

                printf("a: %d b: %u\n",a,b);

                b=c;

                printf("valeur de b en hexa: %x\n",b);

                d=a;

                printf("valeur de d: %ld\n",d);

                printf("taille de d: %d\n",sizeof(d));

                printf("valeur de c: %d\n",c);

                d=c;

                printf("valeur de a en hexa: %x\n",d);

}

 

L'exécussion du programme donne l'affichage suivant:

 

Valeur de b en hexa: 8010

a=-32752  b=32784

Valeur de b en hexa:ff82

valeur de d: -32752

taille de d: 4

valeur de c: -126

valeur de a en hexa:ff82

 

7.7.3.      Conversion d'un flottant vers un entier et vis-versa.

 

·       Si l'on passe d'un entier en un flottant, le type entier est converti en entier long puis cet entier long est converti en un type flottant. La partie décimale du flottant est alors remplie de zéros.

·       Lorsque l'on convertit un flottant en en entier, la partie décimale du flottant disparaît.

 

7.7.4.      Conversion entre flottants.

 

Les conversion de flottants sont possibles mais pour le forçage d'un double vers un flottant, il faut faire attention  aux valeurs maximum et minimum admises pour ces différents types.

 

8.                Les instructions en C.

 

8.1.           Introduction.

 

On peut classer les instructions du langage C en trois grandes familles:

 

1.     Les instructions de base

·       Expression

·       Instruction nulle

·       blocs d'instructions

2.     Les instructions de contrôle

·       if else

·       switch ...case

3.     Les instructions de boucle

·       while

·       do...while

·       for

4.     Les instructions d'échappement

·       break

·       continue

·       return

·       goto

8.2.           Les instructions de contrôle.

 

8.2.1.      L'instruction if...else

 

Syntaxe:

 

if (condition logique )

                instruction 1

[else

                instruction2]

 

Le mot clef if  permet l'exécution de l'instruction1 si la condition logique est vraie (valeur non nulle); si else est présent et que la condition logique est fausse (zéro), l'instruction 2 est exécutée. Après l'exécution de l'instruction1 ou l'instruction2, le contrôle passe à l'instruction suivante.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>                    

 

void main (void)

{

                int a=10;

                printf( "la valeur de a vaut:%d\n",a);

                if (a >= 0)

                                printf("a est positif\n");

                else

                                printf("a est négatif\n");

                printf("fin du contrôle if..else\n");

 

}

 

8.2.2.      L'instruction switch.

 

Les mots clef sont switch, case et default.

 

Syntaxe: switch(expression)

                {

                                [ case expression constante:]

                                ...

                                                [instruction]

                                ...

                                [default:

                                                instruction]          

                }

L'expresion doit obligatoirement être de type entier.Il faut faire très attention car l'emploi d'un type différent n'amène pas d'erreur à la compilation mais donne un fonctionnement de l'instruction tout à fait imprévisible.

Les mots clef switch et case permettent l'évaluation de l'expression et l'exécution des instructions associées avec l'expression constante qui correspond à l'expression initiale.

Si il n'y a pas de correspondance avec une des expressions constantes, l'instruction associée avec le mot clef default est exécutée. Si le mot clef default n'est pas utilisé, le contrôle passe à l'instruction qui suit le bloc switch.

Si rien n'est prévu dans votre bloc switch, la première correspondance entre l'expression initiale et une expression constante permettra l'exécution des instructions qui suivent le case correspondant et même ceux qui suivent. Pour pouvoir sortir du bloc avant le mot clef case suivant, il faut le faire précéder d'une instruction break, return ou goto.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

                int a=1;

                switch (a)

                {

                                case 1: printf("a vaut 1\n");

                                case 2: printf("a vaut 2\n");

                                default: printf("a est différent de 1 et de 2\n");

                }

                                                               

 

}

 

L'exécution du programme donnera l'affichage des informations suivantes:

a vaut 1

a vaut 2

a est différent de 1 et de 2

Pour sortir correctement, nous allons utiliser l'instruction break qui permet de sortir du bloc de contrôle dans lequel on se trouve.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h>                   

 

void main (void)

{

                int a=2;

                switch (a)

                {

                                case 1:

                                                printf("a vaut 1\n");

                                                break;

                                case 2:

                                                printf("a vaut 2\n");

                                                break;

                                default:

                                                printf("a est différent de 1 et de 2\n");

                                               

                }

                                                               

 

}

 

L'exécussion du programme donne l'affichage suivant:

a vaut 2

Cette instruction de contrôle est particulièrement utile lorsque l'on désire réaliser des menus.

 

Exemple:

 

#include <stdio.h>

#include <stdlib.h> 

                                                  

void fonction1(void);

void fonction2(void);

 

void main (void)

{

                char a;

                printf("1 option 1 du menu\n");

                printf("2 option 2 du menu\n");

               

                printf("\n veuillez introduire votre choix(1 ou 2): ");

                a = getchar(); /* permet la saisie d'un caractère au clavier*/

                switch (a)

                {

                                case '1':

                                fonction1();

                                                break;

                                case '2':

                                                fonction2();

                                                break;

                                default:

                                                printf("\nvous n'avez pas introduit une valeur correcte\n");

                                               

                }

 

}

 

void fonction1(void)

{

                printf("l'option 1 du menu a été choisie\n");

}

 

void fonction2(void)

{

                printf("l'option 2 du menu a été choisie\n");

}

 

8.3.           L'instruction de boucle.

 

8.3.1.      L'instruction while.

 

Syntaxe: while (condition logique)

                                instruction

 

Le mot clef while permet l'exécution répétée de l'instruction  tant que la condition logique reste vraie. Si au départ la condition logique est fausse, l'instruction  n'est pas exécutée une seule fois.

Si aucune modification dans la condition logique n'est apportée par l'instruction exécutée, le programme rentre dans un eboucle dont on ne peut sortir qu'au moyen des instructions goto, break ou return.

Nous pouvons modifier notre programme de menu de la façon suivante:

 

#include <stdio.h>

#include <stdlib.h> 

#include <conio.h>

                                                  

void fonction1(void);

void fonction2(void);

 

void main (void)

{

                int a;

               

               

               

                while (a!='3')

                {

               

                printf("1 option 1 du menu\n");

                printf("2 option 2 du menu\n");

                printf("3 quitter le menu\n");

                printf("\n veuillez introduire votre choix(1 ou 2): ");

               

                a = getche();/* permet la saisie d'un caractère au clavier*/

                printf("\n");

                switch (a)

                                {

                                                case '1':

                                                fonction1();

                                                                break;

                                                case '2':

                                                                fonction2();

                                                                break;

                                                case '3':

                                                                break;

                                                default:

                                                                printf("\nvous n'avez pas introduit une valeur correcte\n");

                                               

                                }

                }

 

}

 

void fonction1(void)

{

                printf("l'option 1 du menu a été choisie\n");

}

 

void fonction2(void)

{

                printf("l'option 2 du menu a été choisie\n");

}

 

9.                Les fonctions d’entrées et de sorties standard.

 

9.1.           Introduction.

9.2.           Les fonctions d’accès aux flux stdin et stdout.

9.2.1.      La fonction « printf ».

 

                But de la fonction : permettre un affichage formaté de variables  sur le flot de sortie standard (stdout).

               

                Syntaxe : int printf(const char *format [,argument] ) ;

 

Entête requise : <stdio.h>

 

Valeur retournée par la fonction : elle retourne le nombre de caractères affichés ou une valeur négative si une erreur s’est produite.

 

Si des arguments suivent le champ du format, celui ci devra comprendre des spécifications pour déterminer le format de sortie de ces arguments. Ces spécifications sont reprisent dans des séquences d’échappement  qui pour les formats d’arguments sont  précédées par le caractère %.

D’autres séquences d’échappement peuvent être utilisées et elles sont alors précédées du caractère \.. Elles sont reprises dans le tableau suivant :

 

\a

Sonnerie (Bell)

\b

Retour en arrière (Backspace)

\f

Saut de page (Form Feed)

\n

Saut de ligne (Form Feed) Line Feed et Carriage Return

\r

Retour chariot (Carriage Return)

\t

Tabulation horizontale

\v

Tabulation verticale

\’

simple guillemet (quote)

\’’

doubles guillemets (double quote)

\\

backslash

\ddd

caractère ASCII codé en octal

\xddd

caractère ASCII codé en hexadécimal

 

 

Spécification du format pour les arguments : %[flags][width][.précision][{h|l|L}] type.

 

               

·       Le type

caractère obligatoire qui permet de renseigner que l’argument associé doit être interprété comme un caractère, comme une chaîne de caractères ou comme un nombre.

 

 

caractère

type

format de sortie

 

c

char

spécifie un caractère codé sur un seul octet

d

int

entier décimal signé

o

int

octal entier non signé

u

int

entier décimal non signé

x

int

hexadécimal entier non signé (avec les lettres en minuscule)

X

int

hexadécimal entier non signé (avce les lettres en majuscule)

f

float ou double

valeur signée sous la forme [-] dddd.dddd. Le nombre de chiffres avant le point décimal dépend de la grandeur du nombre et le nombre de chiffres après dépend de la précision demandée

e

float ou double

valeur signée sous la forme [-]d.dddd e [signe]ddd. Il y a un seule chiffre avant le point décimal et le nombre de chiffres pour l’exposant est de trois.

E

float ou double

identique  execxpté que le e est mis en majuscule E.

g

float ou double

valeur signée donné dans le format f ou e selon la forme la plus compacte pour la valeur et la précision donnés.

n

pointeur d’entier

donne le nombre de caractères écrits ; cette valeur est stockée dans un entier dont l’adresse est donnée en argument.

p

pointeur de void

affiche l’adresse sous la forme xxxx :yyyy où xxxx est le segment et yyyy l’offset. Les chiffres x et y sont donnés en hexadécimal sous forme majuscule.

s

string

spécifie une chaîne de caractère.

               

 

·       Spécification de format « width »

 

Ce champ est un nombre entier décimal non négatif contrôlant le nombre minimum de caractères affichés. Si le nombre de caractères de la valeur de sortie est plus petit que ce paramètre, des blancs sont ajoutés à gauche ou à droite de la valeur, dépendant des paramètres du flag (alignement à gauche ou à droite, jusqu'à ce que la largeur minimale soit atteinte.

Cette spécification de largeur ne va jamais causer de troncature : si le nombre de caractères sur la sortie est plus grand que la largeur précisée, alors tous les caractères seront affichés.

Si la spécification de la largeur est un astérisque (*), un entier dans la liste des arguments en fournit la valeur.

 

·       Spécification de format « précision »

 

Ce champ est un entier décimal non négatif, précédé par un point et qui spécifie le nombre de caractères devant être affichés, le nombre de positions décimales ou le nombre de chiffres significatifs. Contrairement à la spécification de la largeur, la précision peut amener une troncature de la valeur de sortie ou un arrondi de la valeur en virgule flottante.

Si la spécification de la précision est un astérisque, un entier dans la liste des arguments  en fournit la valeur.

 

Type

 

effets

par défaut

c

la précision n’a pas d’effet

le caractère est affiché

d,i,u,o,x,X

la précision spécifie le nombre minimum de chiffres qui doivent être affichés.Si le nombre de chiffres dans l’argument est inférieur que la précision, la valeur de sortie est complétée par des zéros sur la gauche. La valeur n’est pas tronquée lorsque le nombre de chiffres excède la précision.

la précision par défaut est 1

e,E

la précision spécifie le nombre de chiffres devant être affichés après le point décimal. Le dernier chiffre affiché est arrondi.

La précision par défaut est de 6.Si la précision est 0 ou qu’il n’y a pas de chiffre renseigné après le point, aucune décimale n’est affichée.

f

la précision spécifie le nombre de chiffres après le point décimal. Si un point décimal apparaît, au moins un chiffre avant lui sera affiché. La valeur est arrondie au nombre approprié de chiffres.

La précision par défaut est de 6.Si la précision est 0 ou qu’il n’y a pas de chiffre renseigné après le point, aucune décimale n’est affichée.

g,G

la précision spécifie le nombre de chiffres significatifs affichés.

Six chiffres significatifs sont affichés.

s

la précision spécifie le nombre maximum de caractères devant être affichés.

Les caractères sont affichés jusqu'à ce que un caractère nul soit rencontré.

 

·       Spécification de format « flags »

 

La directive de fanion est un caractère qui justifie la sortie, l’affichage du signe, des blancs, des points décimaux et du préfixe octal ou hexadécimal. Plus d’une directive de fanion peut apparaître dans une spécification de format.

 

Flags

 

effets

par défaut

-

alignement à gauche du résultat avec le champ de largeur donné

alignement à droite

+

fait précédé la valeur en sortie d’un signe plus ou moins à condition qu’elle soit de type signé

seul le signe négatif apparaît

0

 

 

blanc ‘ ‘

fait précéder la valeur en sortie par un blanc si celle ci est positive et de type signé. Cette directive est ignorée si le + est utilisé.

aucun blanc n’apparaît.

#

pour les types o, x et X, la directive force la valeur de sortie non nulle à être précédée respectivement de 0,0x et 0X.

aucun blanc n’apparaît.

 

pour les types e, E et f, la directive force la valeur de sortie à contenir un point décimal dans tous les cas.

Le point décimal n’apparaît que s’il est suivi d’au moins un chiffre

 

Pour les types g et G, la directive force la valeur de sortie à contenir un point décimal et les zéros après le point décimal sont affichés.

le point décimal n’apparaît que s’il est suivi d’au moins un chiffre. Les zéros sont tronqués.

 

Ignoré pour les types c,d,i,u et s

 

 

·       Spécification de format « size ».

 

Les préfixes h, l et L spécifient la « taille » de l’argument ( long ou court, caractère simple ou large)

 

 

Préfixe utilisé

 

effets

type

l

long int

d, i, o, x ou X

l

long unsigned int

u

h

short int

d, i, o, x ou X

h

short unsigned int

u

 

 

Exemple :

 

#include <stdio.h>

 

void main (void)

{

int a=-10;

                                unsigned int b=20;

                char *string="ceci est un essai de texte";

 

                                float tab[]={          -12.321F,

                                                                4.7F,

                                                                256.56F,

                                                                1.0F,

                                                                -58.12F};

                                int i;

                                for (i=0;i<=4;i++)

                                                printf("%10.4f\t%-10.4f\n",tab[i],tab[i]);

// cette boucle permet d’afficher deux tableaux contenant les mêmes valeurs. Le tableau de //gauche 

                // présente une justification à gauche et celui de droite une justification à droite.

               

                                for (i=0;i<=4;i++)

                                                printf("%*.4f\t%-*.4f\n",10,tab[i],10,tab[i]);

                                                // même boucle en faisant passer le champ de largeur en argument

                               

                               

                                printf("\nvaleur de l'entier a:%d\n",a);

                printf("valeur de l'entier a avec l'option %%u: %u\n",a);

                                printf("affichage d'une chaîne de caractères: %s\n",string);

                                printf("affichage des 8 premiers caractères :%.8s\n",string);

                                // il faut remarquer que pour le type s, l'argument doit être un pointeur.

 

                                printf("affichage d'une adresse: %p\n",tab);

                                printf("affichage d'un nombre hexadécimal %#lX\n",0xaabc);

                printf("affichage d'un nombre en hexadécimal %#hX\n",0x1d);

}

 

               

 

 

9.2.2.      La fonction « scanf ».

 

                But de la fonction : permettre une lecture de données formatées  sur le flot d’entrées standard (stdin).

               

                Syntaxe : int scanf(const char *format[,argument]...) ;

 

Entête requise : <stdio.h>

 

Valeur retournée par la fonction : la fonction retourne le nombre de champs convertis avec succès et assignés ; le retour n’inclut pas les champs qui ont été lus mais pas assignés. Un retour d’une valeur nulle indique qu’aucun champ n’a été assigné. La valeur de retour est EOF pour une erreur ou si un caractère de fin de fichier ou un caractère de fin de chaîne est rencontré lors d’une lecture d’un caractère.

 

Remarque : la fonction « scanf » lit des données sur le flot d’entrées standard et ecrit ces données dans des emplacements mémoire donnés par les arguments. Chaque argument doit être le pointeur d’une variable du type qui correspond au type spécifié dans le format.

 

Spécification du format des arguments :   %[*][width][{h|l|L}]type

 

Le format peut contenir les caractères suivants :

 

1.     Un caractère d’espacement  (un blanc ‘ ‘, une tabulation ‘\t’, une nouvelle ligne ‘\n’ ). Un caractère d’espacement a pour conséquence la lecture mais non le stockage de tous les caractères blancs consécutifs dans le flot d’entrée jusque le caractère suivant qui ne soit pas un caractère d’espacement. Un caractère d’espacement dans le format équivaut à n’importe quel nombre et combinaison de caractères blancs  dans le flot d’entrée.

2.     Les caractères non blancs, excepté le signe pour-cent ont pour conséquence la lecture mais non le stockage d’un caractère identique non blanc. Si le caractère suivant dans stdin n’est pas identique, la fonction « scanf » se termine.

3.     La spécification du format, introduit par le caractère pour-cent permet à scanf de lire et de convertir les caractères du flot d’entrée dans une valeur du type spécifié. La valeur est assignée à un argument dans la liste des arguments.

 

Le format est lu de la gauche vers la droite. Les caractères qui ne correspondent pas à une spécification de format attendent une correspondance dans le flot d’entrée stdin. Les caractères de correspondance sont scannés mais pas stockés. Si un caractère dans stdin est en conflit avec le format spécifié, la fonction scanf se termine sans que le caractère fautif ne soit stocké.

Lorsque la première spécification de format est rencontré, la valeur du premier champ d’entrée est convertie en accord avec cette spécification et est stockée à l’emplacement mémoire renseigné par le premier argument. Le second format permet la conversion  du second champ d’entrée et la valeur est stockée dans le second argument et ainsi de suite.

Un champ d’entrée est défini par l’ensemble des caractères précédents le premier caractère blanc (espacement, tabulation ou nouvelle ligne), ou jusqu’au caractère qui ne peut être converti en accord avec le format spécifié, ou jusqu'à ce que la largeur  du champ soit atteinte si elle est spécifiée. Si il y a trop d’arguments pour les spécifications de formats donnés, les arguments en trop sont évalués mais ignorés. Le résultat de la fonction n’est pas prévisible si il n’y a pas assez d’arguments pour le nombre de formats.

Chaque champ dans la spécification du format est un simple caractère ou un nombre signifiant une option de format particulier. Le caractère « type » qui apparît après le dernier paramètre optionnel détermine si le champ d’entrée doit être interprété comme un caractère, une chaîne de caractères ou un nombre.

Une spécification de format simple contient seulement le signe pour-cent et le caractère de type (par exemple %d). Si le signe pour-cent est suvi par un caractère qui n’a pas de signification particulière comme caractère de contrôle de format, ce caractère et ceux qui suivent (jusqu’au prochain caractère pour-cent) sont traités comme une séquence ordinaire de caractères devant trouver une équivalence dans le flot d’entrée stdin.

Un signe pour-cent suivi d’un astérisque supprime l’affectation du champ suivant sur l’entrée, qui est interprété comme un champ du type spécifique mais qui n’est pas stocké.

 

 

·       Spécification de format  « type »

 

 

caractère(s)

type d’entrée attendu

type d’argument

 

c

un caractère

pointeur de char

d

entier décimal

pointeur sur un int

i

entier décimal, octal ou hexadécimal

pointeur sur un int

o

entier sous forme octale

pointeur sur un int

u

entier décimal non signé

pointeur sur un unsigned int

x

entier sous forme hexadécimale

pointeur sur un int

e, E, f, g, G

valeur en virgule flottante composée d’un signe optionnel, d’une série de un ou plusieurs chiffres décimaux contenant un point décimal, et d’en exposant optionnel (e ou E) suivi par une valeur entière signée optionnelle.

Pointeur sur un float.

n

pas d’entrée lue

pointeur sur un int, dans lequel est stocké le nombre de caractères lus successivement à partir du flot d’entrée.

s

chaîne de caractère jusqu’au premier caractère blanc (espace, tabulation ou nouvelle ligne). Pour lire des chaîne de caractères non délimitées par des caractères blancs, il faut utiliser le jeu des crochets ([ ]) comme décrit au tableau suivant.

Pointeur sur un tableau de char suffisamment grand pour contenir le champ d’entrée ainsi que le caractère nul qui est automatiquement ajouté.

 

 

·       Spécification de format « width ».

 

 

Effets

 

Préfixe utilisé

type spécifié

double

l

e, E, f, g ou G

long int

l

d, i, o, x ou X

long unsigned int

l

u

short int

h

d, i, o, x ou X

short unsigned int

h

u

caractère simple

l

c

chaîne de caractères

h

s

 

Pour lire des chaîne de caractères qui ne sont pas délimitées par des espaces blancs, un jeu de caractères entre crochets peut remplacé le caractère de type « s ». Le champ d’entrée correspondant est lu jusqu’au premier caractère qui n’apparaît pas dans le jeu de caractères entre crochets. Si le premier caractère dans le jeu est l’accent circonflexe, l’effet est inversé.

Notons que la notation %[a-z] et %[z-a] sont interprétés comme équivalent à %[abcde...z]. Attention que ce n’est pas une convention de la norme ANSI standard.

Pour stocker une chaîne de caractères sans le caractère nul de fin de chaîne (‘\O’), il faut utiliser la spécification %nc, ou c est un entier décimal. Dans ce cas, le caractère c indique que l’argument est un pointeur de char. Les n caractères suivants sont lus et stockés sans que le caractère nul ne soit ajouté.

Si n n‘est pas spécifié, la valeur par défaut est de 1.

 

Exemple :

 

#include <stdio.h>

 

void main(void)

{

                                int entier;

                                char buffer[20];

                int retour;

 

                                //lecture d'un entier

                                printf("introduisez un entier:");

                                scanf("%d",&entier);

                printf("valeur introduite:%d\n",entier);

 

 

                                //lecture d'un entier de deux chiffres

                                printf("introduisez un entier:");

                                scanf ("%2d",&entier);

                printf("valeur introduite:%d\n",entier);

 

                                //instruction permettant de vider le buffer d'entrée

                                //qui contient encore un carriage return.

                                fflush(stdin);

 

                //lecture d'une chaîne de caractères

                                //cette lecture s'arrête au premier blanc rencontré

                                printf("1 introduisez une chaîne de caractères:");

                                scanf("%s",buffer);

                printf("contenu du buffer:%s\n",buffer);

               

                                fflush(stdin);

                                //lecture d'une seconde chaîne de caractères

                                //la lecture ne s'arrête plus sur un blanc mais sur le cr

                printf("2 introduisez une chaîne de caractères:");

                                scanf("%[^\n]",buffer);

                                printf("contenu du buffer:%s\n",buffer);

 

                                fflush(stdin);

                //lecture d'une chaîne de 5 caractères y compris les blancs

                                //5 caractères sont attendus

                                printf("3 introduisez une chaîne de 5 caractères:");

                                scanf("%5c",buffer);

                buffer[5]='\0';

                                printf("contenu du buffer:%s\n",buffer);

 

                                fflush(stdin);

                                buffer[0]='\0';

                printf("4 introduisez une chaîne de caractères précédée du mot essai:");

                                retour=scanf("essai%[^\n]",buffer);

                                printf("contenu du buffer:%s\n",buffer);

                                printf("nombre de champs lus:%d\n",retour);

                }

 

9.2.3.      La fonction « getchar » (macro)

 

But de la fonction : permet la lecture d’un caractère sur le flot d’entrée stdin.

 

Syntaxe : int getchar(void)

 

Entête requise : <stdio.h>

 

Valeur retournée par la fonction : la fonction retourne le caractère lu. Pour indiquer une erreur de lecture ou une condition de fin de fichier, la fonction renvoie un EOF. 

 

 

Exemple :

 

#include <stdio.h>

#include <conio.h>

 

void main(void)

{

               

                char buffer[30];

                                int i=0;

                printf("veuillez introduire une chaîne de caractères\n");

                                while ((buffer[i++]=getchar())!='\n');

                buffer[i]='\0';

                                printf("chaîne introduite: %s",buffer);

 

}

 

9.2.4.      La fonction « putchar » (macro)

 

But de la fonction : permet l’envoi d’un caractère sur le flot de sortie stdout.

 

Syntaxe : int putchar (int c)

 

Entête requise : <stdio.h>

 

paramètres : c est le caractère à écrire. Seule la partie basse de 8 bits de l’entier est écrite.

 

Valeur retournée par la fonction : la fonction retourne le caractère écrit. Pour indiquer une erreur d’écriture ou une condition de fin de fichier, la fonction renvoie un EOF.               

 

Exemple :

 

#include <stdio.h>

#include <conio.h>

 

void main(void)

{

               

char buffer[20];

                                int i=0;

                                printf("veuillez introduire une chaîne de caractères\n");

                                //saisie d'une chaîne de caractète

                scanf("%[^\n]",buffer);

                                //affichage de la chaîne, caractère par caractère

                                while (putchar(buffer[i++])!='\0');

 

}

 

9.2.5.      La fonction « gets »

 

But de la fonction :permet de lire une chaîne de caractères à partie de la console.

 

Syntaxe : char* gets(char* buffer) 

 

Entête requise : <stdio.h >

 

paramètres :  buffer est le pointeur d’un tableau de caractères devant recevoir la chaîne.

 

Valeur retournée par la fonction : la fonction renvoie le pointeur buffer si aucune erreur ne s’est produite et un pointeur NULL dans le cas contraire. Vous pouvez utiliser les macros ferror ou feof pour déterminer la cause de l’erreur.

 

Remarques :  la fonction gets lit une ligne à partir du flot d’entrée standard stdin et la stocke dans un buffer. La ligne est constituée de tous les caractères jusqu’au premier caractère newline inclus. La fonction remplace alors la caractère newline par un caractère null .

 

Exemple :

 

#include <stdio.h>

#include <conio.h>

 

void main(void)

{

               

                                char buffer[81];

                printf("veuillez introduire une chaîne de caractères\n");

                                gets(buffer);

                printf("chaîne introduite: %s\n",buffer);

 

}

 

9.2.6.      La fonction « puts ».

 

But de la fonction :permet d’écrire une chaîne de caractères vers stdout.

 

Syntaxe : int puts(const char* buffer) 

 

Entête requise : <stdio.h >

 

paramètres :  bufferr est le pointeur d’un tableau de caractères contenant la chaîne à écrire.

 

Valeur retournée par la fonction : la fonction renvoie une valeur non négative si  il n’y a pas d’erreur. Si  la fonction puts échoue, elle retourne un EOF.

 

Remarques : la fonction puts écrit une chaîne de caractères vers le flot de sortie standard stdout, en remplaçant le caractère null par un caractère newline.

 

Exemple :

 

#include <stdio.h>

#include <conio.h>

 

void main(void)

{

                                char* buffer="écriture d'une chaîne";

                                puts(buffer);

//vous pouvez aussi utiliser la syntaxe suivante

                puts("écriture d'une seconde chaîne");

 

}

 

9.3.           Les fonctions d’accès aux fichiers.

 

9.3.1.      Introduction.

 

9.3.2.      La fonction « fopen ».

 

But de la fonction :permet l’ouverture d’un fichier en mode bufferisé.

 

Syntaxe : FILE *fopen(const char *filename, const char *mode)

 

Entête requise : <stdio.h >

 

paramètres :          filename est le nom du fichier à ouvrir,

                                                                mode est le type d’accès permis.

 

Valeur retournée par la fonction : la fonction renvoie un pointeur sur un descripteur de fichier. Une valeur de retour valant -1 indique une erreur et dans ce cas, errno est mis à une des valeurs suivantes :

·       EACCES essai d’ouverture d’un fichier marqué en lecture seule pour y écrire ou si le mode de partage ne permet les opérations spécifiées ou si le nom donné est un répertoire.

·       EEXIST demande de création d’un fichier dont le nom existe déjà.

·       EINVAL

·       ENOENT fichier ou chemin non trouvé.

 

Remarques :

 

1.     La chaîne de caractères « mode » spécifie le type d’accès demandé pour le fichier comme suit :

r

Ouverture d’un fichier en lecture. Si le fichier n’existe pas ou ne peut être trouvé, la fonction avorte.

w

Ouverture d’un fichier vide pour l’écriture. Si le fichier existe, son contenu est effacé.

a

Ouverture d’un fichier pour l’écriture en fin de fichier sans enlever le marqueur de fin de fichier EOF avant d’écrire les nouvelles données dans le fichier ; La fonction crée le fichier en premier si il n ‘existe pas.

r+

Ouverture d’un fichier en lecture et en écriture. Le fichier doit exister.

w+

Ouverture d’un fichier vide pour à la fois effectuer des lectures et des écritures. Si le fichier donné existe, ses données sont détruites.

a+

Ouverture d’un fichier en lecture et en ajout. L’opération d’ajout inclut la suppression du marqueur de fin de fichier EOF avant l’écriture de nouvelles données et ce EOF est restauré avant que l’écriture soit terminée. Le fichier sera dans un premier temps créé si il n’existe pas.

 

Quand un fichier est ouvert  avec le mode d’accès a ou a+, toutes les opérations d’écritures se passent en fin de fichier. Le pointeur de fichier peut être repositionné en utilisant fseek ou rewind mais sera toujours repositionné en fin de fichier lorsqu’une opération d’écriture est effectuée. De cette façon, aucune donnée ne peut être endommagée.

Si le mode a est utilisé, le marqueur de fin de fichier n’est pas enlevé. Cela signifie que toute écriture nouvelle se fait au delà de ce caractère et que la commande « TYPE » du dos ne fera pas apparaître vos nouveaux enregistrements.

Quand les modes d’accès r+, w+ ou a+ sont spécifiés, les lectures et écritures sont spécifiées. Cependant, quand vous passez d’une lecture à une écriture, vous devez utiliser une  des fonctions fflush, fsetpos, fseek ou rewind pour  initialiser le tampon du fichier. La position courante peut être spécifiée en utilisant la fonction fsetpos ou fseek.

 

En plus d’un des attribut de mode précédent, les caractères suivants peuvent être inclus pour spécifier le mode de translation pour le caractère « new line ».

 

t

Ouverture en mode texte. Dans ce mode, le caractère CTRL+Z est interprété comme un caractère de fin de fichier. Dans ce cas, il faut utiliser l’ouverture de fichier en mode a+ pour l’enlever. Cela doit être fait car une utilisation de fseek ou ftell pour se rendre en fin d’un fichier se terminant par un CTRL+Z  pourrait poser des problèmes.

 

b

Ouverture d’un fichier en mode binaire. Danss ce mode, les translations faisant intervenir des  « carriage return » ou « line feed » sont supprimées.

 

Exemple :

 

9.3.3.      La fonction « fclose ».

 

 

But de la fonction :permet la fermeture d’un fichier ouvert en mode bufferisé ou la fermeture de tous les fichiers si la fonction fcloseall( ) est utilisée.

 

Syntaxe : int fclose (FILE * ptr_des)

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des  est le pointeur du descripteur du fichier à fermer.

               

Valeur retournée par la fonction : la fonction retourne 0 si le fichier a é té fermé avec succès. La fonction fcloseall retourne le nombre total de fichiers fermés. Les deux fonctions renvoient un EOF pour indiquer qu’une erreur s’est produite.

 

Remarques: dans les deux fonctions, tous les tampons associés aux fichiers sont libérés avant toute fermeture.

 

Exemple:

 

 

9.3.4.      Les fonctions de positionnement dans les fichiers.

 

9.3.4.1. La fonction « rewind »

But de la fonction :positionne le pointeur de fichier au début de celui ci.

 

Syntaxe : void rewind (FILE*  ptr_des)

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des  est le pointeur de descripteur du fichier à positionner.

                                                

 

Valeur retournée par la fonction : pas de retour de valeur.

 

Remarques : La fonction rewind est similaire à (void) fseek ( ptr_des, 0L, SEEK_SET) ;

Cependant, contrairement à fseek, la fonction rewind efface les indicateurs d’erreurs ainsi que l’indicateur de fin de fichier. Cette fonction permet aussi de vider le contenu du tampon. Vous pouvez donc l’utiliser  pour vider le tampon du clavier en utilisant le descripteur de flot STDIN.

Attention car la fonction ne retourne pas d’erreur en cas d’échec de positionnement.

 

Exemple:

 

9.3.4.2. La fonction « fseek ».

 

But de la fonction : permet de positionner le pointeur à un endroit spécifique du fichier.

 

Syntaxe : int fseek ( FILE *ptr_des, long offset, int origine)

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est le pointeur du descripteur de fichier

                                 offset précise le nombre d’octets dont on doit bouger par rapport à la référence renseignée par le paramètre origine.

                                 Origine précise la position initiale.

 

Valeur retournée par la fonction : La fonction retourne 0 si elle se déroule sans échec et une valeur non nulle en cas de succès. Pour les périphérique pour lesquels le déplacement n’a aucun sens, la valeur retournée est indéfinie.

 

Remarques : Pour les fichiers ouverts pour la mise à jour, l’opération suivante peut être une lecture ou une écriture.

L’argument d’origine doit être une des constantes suivantes :

 

·       SEEK_CUR pour la position courante dans le fichier,

·       SEEK_END pour la fin du fichier,

·       SEEK_SET pour le début du fichier.

 

Pour un fichier ouvert en mode texte, la fonction fseek a ses limites parce que les translations CR/LF produisent des résultats inattendus. Les seules opérations autorisées en mode texte sont les suivantes :

 

1.     se déplacer avec un offset de 0 relativement à une valeur d’origine,

2.     se déplacer à partir du début du fichier en utilisant un offset retourné par la fonction ftell.

Dans les fichiers ouverts en mode texte en lecture/écriture, le caractère CTRL+Z  est supprimé si possible par la fonction fopen pour permettre un fonctionnement correct de la fonction fseek.

 

En mode ajout, l’écriture se fait toujours en fin de fichier, et cette position ne peut être modifiée. La position courante est déterminée par la dernière opération d’entrée/sortie et non l’endroit de la prochaine opération d’écriture. Si aucune opération d’entrée/sortie ne s’est encore produite, alors le pointeur est au d ébut du fichier.

 

 

Exemple :

 

 

9.3.4.3.  La fonction « ftell ».

 

But de la fonction :permet de déterminer la position courante du pointeur de fichier.

 

Syntaxe : long ftell (FILE * ptr_des )

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est un pointeur de descripteur de fichier,

 

Valeur retournée par la fonction : ftell retourne la position courante du fichier. La valeur retournée par ftell pour un fichier ouvert en mode texte ne reflète pas l’offset physique en octet à cause de la translation des paires CR/LF. L’utilisation conjointe des fonctions ftell et fseek permet un positionnement correct dans les fichiers. En cas d’erreur, la fonction retourne -1 et la variable errno contient alors une des constantes suivantes :

·       EBADF pour préciser  que le pointeur du descripteur de fichier n’est pas valide,

·       EINVAL pour préciser que le descripteur n’est pas valide.

 

Pour les périphériques dont le positionnement n’aurait pas de sens, tels que terminaux ou imprimantes, ou lorsque le descripteur ne pointe pas sur un fichier ouvert, la valeur retournée est indéfinie.

 

Remarques : La position retournée par la fonction est l’offset relatif au début du fichier.

Notons que lorsque un fichier est ouvert en mode ajout, la posituion courante du fichier est déterminée par la dernière opération d’entrée/sortie et non par l’endroit ou la prochaine opération d’écriture doit se produire. Si par exemple un fichier est ouvert en mode ajout est que la dernière opération était une lecture, la position du fichier est l’endroit ou la prochaine opération de lecture doit se produire. Si aucune opération d’entrée/sortie ne s’est encore produite avec un fichier ouvert en mode ajout, le pointeur est initialisé au début du fichier.

 

9.3.4.4.La fonction « fsetpos ».

 

But de la fonction :fixe l’indicateur de position du descripteur de fichier.

 

Syntaxe : int fsetpos ( FILE* ptr_des, const fpos_t *pos )

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est le pointeur de descripteur de fichier,

                                 pos est le pointeur de la variable contenat la nouvelle position.

 

Valeur retournée par la fonction : Si elle se produit avec succès, la fonction renvoie 0. En cas d’échec, la fonction renvoie une valeur non nulle et la variable errno contient une des constantes suivantes :

·       EBADF pour préciser que le fichier n’est pas accessible ou que l’objet pointé par ptr_des n’est pas valide,

·       EINVAL pour préciser que le descripteur n’est pas valide.

 

Remarques : La position précisée comme paramètre doit nécessairement être obtenue en utilisant la fonction fgetpos. Après un appel à fgetpos, l’opération suivante peut être une entrée ou une sortie.

 

Exemple :

 

 

9.3.4.5.La fonction « fgetpos ».

 

 

 

But de la fonction :permet de récupérer la position du pointeur de lecture/écriture.

 

Syntaxe : int fgetpos ( FILE * ptr_des, fpos_t* pos ) 

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est le pointeur de descripteur de fichier,

                                 pos est le pointeur de la variable contenat la nouvelle position.

 

Valeur retournée par la fonction : La fonction fgetpos récupère la valeur courante de l’indicateur de position de fichier du descripteur et la stocke dans l’objet pointé par pos. Cette information peut ensuite être utilisée par la fonction fsetpos. La valeur de pos est dans un format tel que son utilisation n’est possible que par les fonctions fsetpos et fgetpos.

 

9.3.5.      Les fonctions d’accès dans les fichiers.

 

9.3.5.1.La fonction « fwrite »

 

But de la fonction : écriture de données vers un flot (exemple : un fichier). L’écriture n’est pas formatée.

 

Syntaxe :  size_t fwrite (const void *buffer, size_t size, size_t count, FILE * ptr_des)

 

Entête requise : <stdio.h >

 

paramètres :          buffer est un pointeur vers les données à être écrites

                                 size représente la taille de l’item à écrire

                                 count représente le nombre de fois que l’item doit être écrit       

                                 ptr_des représente un pointeur de descripteur de fichier

 

Valeur retournée par la fonction : la fonction retourne le nombre d’items complets écrits qui peut être plus petit que le paramètre count si une erreur s’est produite. Si une erreur se produit, l’indicateur du pointeur de position de fichier ne peut être déterminé.

 

Remarques :  la fonction fwrite écrit un nombre count d’items, chacun d’une taille size, du buffer vers la sortie ptr_des. Le pointeur de position est incrémenté par le nombre d’octets écrits. Si le fichier est ouvert en mode texte, chaque carriage return est remplacer avec la paire carriage return line feed. Le remplacement n’a pas d’effet sur la valeur retournée.

 

Exemple :

 

9.3.5.2.La fonction « fread ».

 

 

But de la fonction : lecture d’une donnée non formatée sur un flot. (Exemple : un fichier).

 

Syntaxe :  size_t fread ( void *buffer, size_t, size_t count, FILE * ptr_des)

 

Entête requise : <stdio.h >

 

paramètres :          buffer est un pointeur vers le lieu de stockage des données

                                 size représente la taille de l’item à lire

                                 count représente le nombre de fois que l’item doit être lu           

                                 ptr_des représente un pointeur de descripteur de fichier

 

Valeur retournée par la fonction : la fonction fread retourne le nombre d’items complètement lus, qui peut être plus petit que le paramètre count si une erreur s’est produite ou si la fin de fichier est rencontrée avant que le total ne soit atteint. Il faut utiliser la fonction feof ou ferror pour distinguer une erreur d’une fin de fichier. Si la taille et le nombre valent 0, la fonction retourne 0 et le contenu du buffer reste inchangé.

 

Remarques : La fonction fread lit le nombre count d’items, chacun ayant une taille de size octets, à partir de ptr_des et les stocke dans le buffer. Le pointeur de position du fichier est incrémenté du nombre d’octets lus. Si le fichier est ouvert en mode texte, une paire CR/LF est remplacé avec un simple caractère LF. Le remplacement n’a pas d’effet sur le pointeur de position ou sur la valeur retournée. Le pointeur de position est indéterminé si une erreur se produit. La valeur d’une lecture partielle de l’item ne peut être déterminé.

 

Exemple :

 

9.3.5.3.La fonction « fprintf ».

 

But de la fonction : écriture formatée de données sur un flot (Exemple : un fichier)

 

Syntaxe : int fprintf( FILE *ptr_des, const char *format, [arguments] )

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est un pointeur de descripteur de fichier

                                 format est une chaîne de caractère de contrôle de format

                                 arguments sont les arguments optionnels     

                                

                                

Valeur retournée par la fonction : la fonction retourne le nombre d’octets écrits. Elle retourne une valeur négative en cas d’erreur.

 

Remarques : pour les formats et arguments, nous vous renvoyons à la fonction printf qui est analogue.

 

Exemple :

 

 

9.3.5.4.La fonction « fscanf »

 

But de la fonction : lecture formatée d’une donnée sur le flot (fichier par exemple)

 

Syntaxe :  int fscanf( FILE *ptr_des, const char *format [,arguments] )

 

Entête requise : <stdio.h >

 

paramètres :          ptr_des est un pointeur de descripteur de fichier

                                 format est une chaîne de caractère de contrôle de format

                                 arguments sont les arguments optionnels     

 

Valeur retournée par la fonction : la fonction retourne le nombre de champs convertis et assignés avec succés ; la valeur retournée n’inclut pas les champs lus mais non assignés. La valeur de retour 0 indique qu’aucun champ n’a été assigné. Si une erreur se produit ou si la fin de fichier est atteinte avant la première conversion, la valeur retournée sera EOF.

 

Remarques : la fonction fscanf lit les données à partir de la position courante du fichier. Chaque argument doit être un pointeur d’une variable du type correspondant au format renseigné. Pour les formats, nous vous renvoyons à la fonction scanf qui est analogue.

 

Exemple :

 

 

9.3.5.5.La fonction « fgetchar ».

 

 

Remarque : c’est une fonction correspondant à la macro getchar.

 

Syntaxe : int fgetchar (void )

 

En-tête requise : <stdio.h>

 

 

9.3.5.6.La fonction « fputchar ».

 

 

Remarque : c’est une fonction correspondant à la macro putchar.

 

Syntaxe : int fputchar (int c )

 

En-tête requise : <stdio.h>

 

 

9.3.5.7.La fonction «  fgets ».

 

 

But de la fonction : saisie d’une chaîne de caractère sur le flot (un fichier par exemple)

 

Syntaxe : char *fgets(char *buffer, int n, FILE *ptr_des)

 

Entête requise : <stdio.h >

 

paramètres :          buffer est l’endroit de stockage de la chaîne

                                 n est le nombre maximum de caractères devant être lus

                                 ptr_des est un pointeur de descripteur de fichier                                                          

 

Valeur retournée par la fonction : la fonction retourne une chaîne de caractères. NULL est retourné pour indiquer une erreur ou si la fin de fichier est atteinte. Il faut utiliser la fonction feof ou ferror pour déterminer quelle erreur s’est produite.

 

Remarques : la fonction lit une chaîne de caractères depuis le flot et la stocke dans le buffer. La fonction lit cette chaîne à partir de la position courante du fichier jusqu’au premier caractère new line rencontré, jusqu'à la fin du fichier ou si le nombre maximum de caractères lus est atteint. Le résultat stocké dans le buffer se voit ajouté un caractère nul. Le caractère new line, si il est lu, est inclus dans la chaîne.

 

Exemple :

 

 

9.3.5.8.La fonction « fputs ».

 

 

But de la fonction : écriture d’une chaîne de caractères sur le flot

 

Syntaxe :  int fputs (const char *buffer, FILE *ptr_des )

 

Entête requise : <stdio.h >

 

paramètres :          buffer contient la chaîne de caractères à écrire

                                 ptr_des est le pointeur de descripteur de fichier           

 

Valeur retournée par la fonction : la fonction retourne une valeur non négative en cas de réussite. En cas d’erreur, la fonction renvoie un EOF.

 

Remarques : la fonction écrit la chaîne de caractères à la position courante du fichier. Elle ne copie pas le caractère nul.

 

10.          Les fonctions de conversion de données.

 

10.1.      Introduction.

 

 

Nous retrouvons dans la conversion des données des conversions implicites qui ne réclament pas l’utilisation de fonctions spécifiques. Ces conversions ont été analysées dans le paragraphe traitant des types de variables et des moulages (casting).

Je reprendrais à titre informatif la conversion implicite d’un flottant vers un entier avec la troncature de la partie fractionnaire.

 

#include <stdio.h>

#include <stdlib.h>

 

void main(void)

{

                int a;

                                float b=10.55F;

                                a=(int)b; /*ou a=b mais cette dernière syntaxe

                                                  génère un warning lors de la compilation*/

                                printf("valeur de l'entier:%d\n",a);

}

               

                Nous retrouvons également des conversions de données explicites qui réclament l’utilisation de fonctions. Ces fonctions permettent la conversion d’une donnée numérique vers une chaîne de caractères et d’une chaîne de caractères vers une donnée numérique.

 

Ces fonctions sont reprises dans le tableau ci après.

 

 

Entier

Entier long

Flottant

signé

non signé

Conversion en chaîne

 

itoa

ltoa

ultoa

ecvt, fcvt, gcvt

Conversion en donnée numérique

atoi

atol

strtol

strtoul

atof, strtod

 

 

10.2.      La fonction «itoa »

 

But : cette fonction permet la conversion d’un entier vers une chaîne de caractères. Cette conversion peut se faire en différentes bases.

 

Syntaxe : char *itoa( int valeur, char *buffer, int base) ;

 

Paramètres :         valeur est la donnée numérique à convertir

                                                *buffer est le pointeur sur le tableau de caractères devant contenir la conversion

base est la base dans laquelle doit s’effectuer la conversion. La valeur doit être comprise entre 2 et 36.

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur le tableau contenant la conversion.

 

Remarques : il est important que la taille du tableau soit suffisante pour contenir le résultat de la conversion. Le compilateur ne génère pas d’erreur en cas du non respect de cette règle mais l ‘exécution du programme risque de provoquer des plantages. La chaîne convertie peut contenir jusqu’à 17 caractères.

 

Si la base est 10, la présence d’un nombre négatif dans la variable numérique aura comme conséquence l’ajout du caractère –dans la conversion.

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char buffer[20];

   int  i = 3445;

 

   itoa( i, buffer, 10 );

   printf( "conversion de l’entier %d (base 10): %s\n", i, buffer );

   itoa( i, buffer, 16 );

   printf( "conversion de l’entier %d (base 16): 0x%s\n", i, buffer );

   itoa( i, buffer, 2  );

   printf( "conversion de l’entier %d (base 2): %s\n", i, buffer );

}

 

 

10.3.      La fonction « ltoa »

 

 

But : cette fonction permet la conversion d’un entier long vers une chaîne de caractères. Cette conversion peut se faire en différentes bases.

 

Syntaxe : char *ltoa( long  valeur, char *buffer, int base) ;

 

Paramètres :         valeur est la donnée numérique à convertir

                                                *buffer est le pointeur sur le tableau de caractères devant contenir la conversion

base est la base dans laquelle doit s’effectuer la conversion. La valeur doit être comprise entre 2 et 36.

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur le tableau contenant la conversion.

 

Remarques : il est important que la taille du tableau soit suffisante pour contenir le résultat de la

conversion. Le compilateur ne génère pas d’erreur en cas du non respect de cette règle mais l ‘exécution du programme risque de provoquer des plantages. La chaîne convertie peut contenir jusqu’à 33 caractères.

 

Si la base est 10, la présence d’un nombre négatif dans la variable numérique aura comme conséquence l’ajout du caractère –dans la conversion.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char buffer[20];

   long l = -344115L;

 

   ltoa( l, buffer, 16 );

   printf( "conversion de l’entier long %ld (base 16): 0x%s\n", l, buffer );

   ltoa( l, buffer, 10 );

   printf( "conversion de l’entier long %ld (base 10):%s\n", l, buffer );

}

 

 

10.4.      La fonction « ultoa »

 

 

But : cette fonction permet la conversion d’un entier long non signé vers une chaîne de caractères. Cette conversion peut se faire en différentes bases.

 

Syntaxe : char *ultoa( unsigned long  valeur, char *buffer, int base) ;

 

Paramètres :         valeur est la donnée numérique à convertir

                                                *buffer est le pointeur sur le tableau de caractères devant contenir la conversion

base est la base dans laquelle doit s’effectuer la conversion. La valeur doit être comprise entre 2 et 36.

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur le tableau contenant la conversion.

 

Remarques : il est important que la taille du tableau soit suffisante pour contenir le résultat de la

conversion. Le compilateur ne génère pas d’erreur en cas du non respect de cette règle mais l ‘exécution du programme risque de provoquer des plantages. La chaîne convertie peut contenir jusqu’à 33 caractères.

 

Exemple :

 

void main( void )

{

   char buffer[20];

   unsigned long ul = 1234567890UL;

 

   ultoa( ul, buffer, 16 );

   printf( "conversion de l’entier non signé %lu (base 16): 0x%s\n", ul, buffer );

}

 

 

10.5.      La fonction « ecvt »

 

But : cette fonction permet la conversion d’un double vers une chaîne de caractères en précisant le nombre de chiffres significatifs.

 

Syntaxe : char *ecvt( double  valeur, int  compte, int *pos_dec, int *signe) ;

 

Paramètres :         valeur est la donnée numérique à convertir

compte est le nombre de chiffres désirés

pos_dec est un pointeur sur un entier qui contiendra la position du point décimal

signe est un pointeur d’entier contenant le signe du nombre converti.

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur  une chaîne interne. Lors de l’appel suivant à ecvt ou fcvt, la valeur stockée est détruite.

 

Remarques :  La fonction convertit un nombre en virgule flottante vers une chaîne de caractères. Le paramètre valeur contient le nombre devant être converti. Le paramètre compte indique le nombre maximal de chiffres significatifs à convertir. Si la valeur dépasse cette précision, elle est arrondie ; au contraire, si cette valeur est inférieure, la chaîne de caractères est complétée avec des zéros. La chaîne de caractère est terminée après conversion par un caractère nul (‘\0’).

L’entier contenant la position du point décimal a une valeur nulle ou négative si le point décimal se situe en début de chaîne ou avant le premier chiffre.

L’entier contenant le signe a une valeur nulle si le nombre est positif et dans les autres cas, le nombre est négatif.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   int     decimal,   sign;

   char    *buffer;

   int     precision = 10;

   double  source = 3.1415926535;

 

   buffer =ecvt( source, precision, &decimal, &sign );

   printf( "source: %2.10f   buffer:  %s  decimal: %d  sign: %d\n",

           source, buffer, decimal, sign );

}

 

 

10.6.      La fonction « fcvt »

 

But : cette fonction permet la conversion d’un double vers une chaîne de caractères en précisant le nombre de chiffres après le point décimal.

 

Syntaxe : char *fcvt( double  valeur, int  compte, int *pos_dec, int *signe) ;

 

Paramètres :         valeur est la donnée numérique à convertir

compte est le nombre de chiffres décimaux désirés

pos_dec est un pointeur sur un entier qui contiendra la position du point décimal

signe est un pointeur d’entier contenant le signe du nombre converti.

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur  une chaîne interne. Lors de l’appel suivant à ecvt ou fcvt, la valeur stockée est détruite.

 

Remarques :  La fonction convertit un nombre en virgule flottante vers une chaîne de caractères. Le paramètre valeur contient le nombre devant être converti. Le paramètre compte indique le nombre maximal de chiffres décimaux à convertir. Si la valeur dépasse cette précision, elle est arrondie ; au contraire, si cette valeur est inférieure, la chaîne de caractères est complétée avec des zéros. La chaîne de caractère est terminée après conversion par un caractère nul (‘\0’).

L’entier contenant la position du point décimal a une valeur nulle ou négative si le point décimal se situe en début de chaîne ou avant le premier chiffre.

L’entier contenant le signe a une valeur nulle si le nombre est positif et dans les autres cas, le nombre est négatif.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   int  decimal, sign;

   char *buffer;

   double source = 3.1415926535;

 

   buffer = fcvt( source, 7, &decimal, &sign );

   printf( "source: %2.10f   buffer: %s   decimal: %d   sign: %d\n",

            source, buffer, decimal, sign );

}

 

 

10.7.      La fonction « gcvt »

 

But : cette fonction permet la conversion d’un double vers une chaîne de caractères. La conversion se fait dans un tableau fourni comme paramètre à la fonction

 

Syntaxe : char *gcvt( double  valeur, int  chiffre, char *buffer) ;

 

Paramètres :         valeur est la donnée numérique à convertir

chiffre est le nombre de chiffres significatifs désirés

buffer est un pointeur sur un tableau contenant la conversion

 

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne un pointeur sur le tableau contenant la conversion .

 

Remarques :  La fonction convertit un nombre en virgule flottante vers une chaîne de caractères (le point décimal et le signe sont inclus). Le paramètre valeur contient le nombre devant être converti. Le paramètre chiffre indique le nombre maximal de chiffres significatifs à convertir. La fonction essaye d’abord la conversion dans le format décimal ; si la taille disponible n’est pas suffisante, le format  exponentiel sera utilisé. Un caractère nul est ajouté à la chaîne après conversion.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char buffer[50];

   double source = -3.1415e5;

   gcvt( source, 7, buffer );

   printf( "source: %f  buffer: '%s'\n", source, buffer );

   gcvt( source, 7, buffer );

   printf( "source: %e  buffer: '%s'\n", source, buffer );

}

10.8.      La fonction « atoi »

 

But : cette fonction permet la conversion d’une chaîne de caractères vers un entier.

 

Syntaxe : int atoi (const char *chaîne) ;

 

Paramètres :         * chaîne est un pointeur sur une chaîne de caractères à convertir.

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne l’entier résultant de la conversion . Si la fonction ne peut convertir la chaîne, elle renvoie 0.

 

Remarques :  la fonction atoi ne reconnaît pas le point décimal ni la notation exponentielle.

La chaîne de caractère doit avoir la forme suivante :

[espace][signe] chiffres

La conversion s’arrête au premier caractère que la fonction ne peut traiter.

Il faut veiller à ce que la donnée à convertir ne puisse dépasser la valeur maximale représentable par un entier court.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char *s;

   int i ;

 

   s = "  9885 francs";     

   i = atoi( s );

   printf( "atoi test: chaîne ASCII : %s\t\tentier: %d\n", s, i );

 

}

 

10.9.      La fonction « atol »

 

But : cette fonction permet la conversion d’une chaîne de caractères vers un entier long.

 

Syntaxe : long atol (const char *chaîne) ;

 

Paramètres :         * chaîne est un pointeur sur une chaîne de caractères à convertir.

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne l’entier résultant de la conversion . Si la fonction ne peut convertir la chaîne, elle renvoie 0.

 

Remarques :  la fonction atol ne reconnaît pas le point décimal ni la notation exponentielle.

La chaîne de caractère doit avoir la forme suivante :

[espace][signe] chiffres

La conversion s’arrête au premier caractère que la fonction ne peut traiter.

Il faut veiller à ce que la donnée à convertir ne puisse dépasser la valeur maximale représentable par un entier court.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char *s;

   long int i ;

   s = "98854 dollars"; 

   l = atol( s );

   printf( "atol test: chaîne ASCII: %s\t\tentier long: %ld\n", s, l );

 

}

 

10.10.  La fonction « atof »

 

 

But : cette fonction permet la conversion d’une chaîne de caractères vers un double.

 

Syntaxe : double atof (const char *chaîne) ;

 

Paramètres :         * chaîne est un pointeur sur une chaîne de caractères à convertir.

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne le double résultant de la conversion . Si la fonction ne peut convertir la chaîne, elle renvoie 0.

 

Remarques :  la fonction atol ne reconnaît pas le point décimal ni la notation exponentielle.

La chaîne de caractère doit avoir la forme suivante :

[espace] [signe] [chiffres] [.chiffres] [ {d | D | e | E }[signe]chiffres]

La conversion s’arrête au premier caractère que la fonction ne peut traiter.

Il faut veiller à ce que la donnée à convertir ne puisse dépasser la valeur maximale représentable par un entier court.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

   char *s;

   double x ;

   s = "  -2309.12E-2";   

   x = atof( s );

   printf( "atof test: chaîne ASCII : %s\tdouble:  %lf\n", s, x );

 

   s = "7.8912654773d2"; 

   x = atof( s );

   printf( "atof test: chaîne ASCII : %s\tdouble:  %lf\n", s, x );

}

 

 

10.11.  La fonction « strol »

 

But : cette fonction permet la conversion d’une chaîne de caractères vers un double.

 

Syntaxe : long strtol (const char *chaîne, char **fin_conv, int base) ;

 

Paramètres :         * chaîne est un pointeur sur une chaîne de caractères à convertir.

**fin_conv  est l’adresse du  pointeur vers la variable contenant le caractère ayant arrêté la convertion.

                                                base  base dans laquelle la valeur est exprimée. Entre 2 et 36.

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne le long résultant de la conversion . La conversion s’arrête lorsque un caractère n’est pas reconnu comme faisant partie du nombre. Cela peut être le caractère nul ou le premier caractère numérique plus grand ou égal à la base.

 

 

Remarques : 

 

La chaîne de caractères doit avoir la forme suivante :

 

[espaces] [{+ | –}] [0 [{ x | X }]] [chiffres]

 

Un espace blanc peut être considéré comme un espace ou une tabulation et est ignoré .Si la base est nulle, le premier chiffre de la chaîne permet à la fonction de déterminer la base. Si le premier caractère est nul et que le second n’est pas ‘x’ ou ‘X’, la chaîne est interprétée comme un octal ; autrement, elle est considérée comme un entier décimal. Si le premier caractère est nul est le suivant ‘x’ ou ‘X’, la chaîne est interprétée en hexadécimal.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

                    char   *string, *stopstring;

    long   l;

 

                    string = "3756Arrêt de la conversion";

    l=strtol(string,&stopstring,10);

    printf("dernier caractère converti:%c\n",*stopstring);

    printf("chaîne '%s' convertie en entier:%ld\n",string,l);

               

                    /* conversion automatique hexadécimale*/

                    string="0x1f";

    l=strtol(string,&stopstring,0);

    printf("chaîne '%s'convertie en entier:%ld\n",string,l);

 

                    /*conversion automatique octale*/

    string="012";

                    l=strtol(string,&stopstring,0);

    printf("chaîne '%s'convertie en entier:%ld\n",string,l);

 

}

 

10.12.  La fonction « strtod »

 

 

But : cette fonction permet la conversion d’une chaîne de caractères vers un double.

 

Syntaxe : double  strtod (const char *chaîne, char **fin_conv) ;

 

Paramètres :         * chaîne est un pointeur sur une chaîne de caractères à convertir.

**fin_conv  est l’adresse du  pointeur vers la variable contenant le caractère ayant arrêté la convertion.

En tête requise : <stdlib.h>

 

Valeur retournée : la fonction retourne la valeur du double, excepté lorsque la représentation du nombre provoque un débordement et dans ce cas, la fonction retourne +/- HUGE_VAL. Le signe de HUGE_VAL correspond au signe de la valeur qui ne peut être représenté. La fonction retourne 0 si la conversion ne peut être effectuée ou que le nombre est trop petit pour être représenté. La variable errno contient la valeur ERANGE.

 

Remarques : La fonction arrête la conversion lors de la lecture du premier caractère qu’elle ne peut reconnaître comme faisant partie du nombre. Cela peut être le caractère nul.

La chaîne de caractères doit avoir la forme suivante :

[espaces] [signe] [chiffres] [.chiffres] [ {d | D | e | E}[signe]chiffres]

 

Un espace blanc peut comprendre un espace ou une tabulation, et est ignoré . Si aucun caractère ne précède le point décimal, au moins un caractère doit le suivre.

 

Exemple :

 

#include <stdlib.h>

#include <stdio.h>

 

void main( void )

{

                    char   *string, *stopstring;

                    double d;

               

    string="1.12345678e5test";

                    d=strtod (string,&stopstring);

                    printf("caractère ayant stoppé la conversion:%c\n",*stopstring);

                    printf("chaîne '%s'convertie en double:%e\n",string,d);

}

 

 

11.          Manipulation de chaînes de caractères.

 

 

11.1.      La fonction « strcpy »

 

 

But : cette fonction permet la copie d’une chaîne source vers une chaîne destination.

 

Syntaxe : char* strcpy (char *chaîne1, char *chaîne2) ;

 

Paramètres :         * chaîne1 est un pointeur sur la chaîne source.

* chaîne2  est un pointeur sur la chaîne destination.

 

En tête requise : <string.h>

 

Valeur retournée :

 

Remarques :

 

Exemple :