Cours de Langage C (ANSI)

Version de septembre 2002

 

Roger GUÉRIN (guerin@ccr.jussieu.fr)

UMR 7619 Sisyphe - Université Pierre et Marie Curie (Paris 6)

 

Sommaire

I)       Introduction  3

II)      Types d’objets. 3

II.1) types primitifs. 3

II.2) écriture des constantes. 4

II.2.1) constantes numériques. 4

II.2.2) constantes caractères. 4

II.2.3) constantes chaînes de caractères. 4

II.3) types construits. 4

II.3.1) tableaux. 4

II.3.2) structures (struct) 5

II.3.3) énumérations (enum) 5

II.3.4) unions (union) 5

II.4) types définis par l’utilisateur. 5

II.5) format 6

II.6) identificateur d’une variable. 6

II.7) existence d’une variable. 6

III)          Opérateurs  6

III.1) opérateurs sur nombre. 6

III.1.1) opérateurs arithmétiques. 6

III.1.2) opérateurs logique bit à bit 6

III.2) opérateurs logiques. 6

III.2.1) comparateurs. 7

III.2.2) logiques. 7

III.2.3) expression conditionnelle. 7

III.3) opérateurs sur pointeurs, tableaux. 7

III.4) opérateur d’affectation. 7

III.5) conversion de types (transtypages) 7

III.6) priorité. 8

IV)          Structures de contrôle. 8

IV.1) instruction, bloc. 8

IV.2) sélection. 9

IV.2.1) if (test) 9

IV.2.2) switch (test à choix multiple) 9

IV.3) itérations. 10

IV.3.1) while (boucle tant que) 10

IV.3.2) do ... while (boucle jusqu’à ce que) 10

IV.3.3) for (boucle avec compteur) 10

IV.3.4) remarque. 10

IV.4) ruptures. 11

IV.4.1) break (échappement de boucle ou de test à choix multiple) 11

IV.4.2) continue (au suivant) 11

IV.5) branchement 12

V)      Pointeurs et tableaux. 12

V.1) pointeur. 12

V.2) tableau. 13

V.3) correspondance. 13

VI)     Passage des paramètres. 14

VI.1) passage par valeur. 14

VI.2) passage par adresse. 14

VI.3) différence. 15

VII)             Fonction  16

VII.1) fonction main. 16

VII.2) autres fonctions. 16

VIII)             Allocation dynamique. 17

IX)          Remarques  18

X)      Premier programme. 18

XI)          Fonctions prédéfinies. 20

XII)       C++. 21

 


             I)      Introduction

* intérêt : portabilité (réputation), langage système (Unix) et indépendant de tout processeur, « proche de la machine » (il demande plus au programmeur et il permet de mieux comprendre certains mécanismes systèmes), permissif (!)

* référence : « Le langage C » de B.W. Kernighan et D.M. Ritchie, édition Masson (la bible !)

* histoire : début d’Unix en 1968, écriture successive du système en assembleur, en langage B, en langage NB et en fin en langage C en 1973, première commercialisation d’Unix en 1975, norme ANSI (American Standards National Institute) en 1983 pour un langage C non ambiguë (c’est-à-dire prototypage des fonctions : conformité entre déclaration et utilisation)

 

          II)      Types d’objets

II.1) types primitifs

type

nature

taille en octets

void

rien (!!!)

0

char

caractère ou entier signé

1

short

entier signé

2

int

entier signé

4

long

entier signé

4

float

réel

4

double

réel

8

Le type void est utilisé lors des déclarations, c’est un type créé avec la norme ANSI.

Les types short et long sont les abréviations de short int et long int.

Sur certaines machines (pas les PC), un int occupe 2 octets.

Rappel : 1 octet (ou byte) = 8 bits, et 1 bit est la plus petite représentation informatique qui vaut soit 0, soit 1 (représentation binaire).

Les variables sont signées par défaut (les deux mots-clés signed et unsigned indiquent si le bit de poids fort doit être considéré ou non comme un bit de signe, i.e. si la variable est signée ou non. Ainsi un unsigned int désignera une quantité comprise entre 0 et 4 294 967 295 (c’est-à-dire 232-1, où 32 correspond 4 octets x 8 bits) ; à la différence de signed int, qui se comporte comme un int, qui désignera une quantité comprise entre –2 147 483 648 (-231) et 2 147 483 647 (231-1). Le calcul avec des entiers définis comme unsigned correspond à l’arithmétique modulo 2n.

Un flottant (un réel défini en virgule flottante) comporte un signe positif ou négatif, une mantisse qui est un décimal positif avec un seul chiffre devant la virgule, puis l’exposant de la puissance de 10 qui est un entier. La précision minimum d’un réel est 3.4 * 10-38, la valeur maximum étant 3.4 * 1038, le nombre de chiffres significatifs de la mantisse est 6.

Pour expliquer la gestion de la mémoire, nous pouvons représenter cette mémoire, sous forme d’un ruban découpé en case (attention la taille en octets de ces cases dépend du type de l’objet et donc deux adresses de deux objets consécutifs sont distantes du nombre d’octet occupé par le premier objet), et indiquer au dessus de la case le nom des variables employées, et en dessous l’adresse de ces variables.

exemple :

char c,d;                     int i,j;               float r,s;

nom des variables :

 

 

c

d

 

 

 

i

j

 

 

 

r

s

 

 

valeur des variables :

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

adresse des variables :

 

 

&c

&d

 

 

 

&i

&j

 

 

 

&r

&s

 

 

Attention si les objets consécutifs ne sont pas du même type, le déplacement dans la mémoire n’est pas aisé.

II.2) écriture des constantes

II.2.1) constantes numériques

Les constantes entières de type entier sont codées :

en base 10 : 12, 52, 1034 ; en base 8 : 014, 064, 02012 ; en base 16 : 0xc, 0x34, 0x40a.

Les constantes entières de type long sont codées de façon identique mais suffixées par la lettre L (10000L, 0L, 0x1fffL).

Les constantes réelles sont implicitement de type double et sont codées (en fractionnaire : 3.0, 12.514 ; en format exponentielle : 2.54E-3, 3E5, -1.1E+12).

II.2.2) constantes caractères

Une constante char est un caractère écrit entre apostrophes : 'A' (code ascii 65, soit en binaire 01000001 = 0x27+1x26+0x25+0x24+0x23+0x22+0x21+1x20, codé sur 8 bits=1 octet), 'a' (code ascii 97), '+' (code ascii 43), '1' (code ascii 49)

Il existe des caractères spéciaux :

'\n' saut de ligne ; '\t' tabulation ; '\b' backspace ; '\\' caractère \ ; '\'' caractère ' ; '\0' caractère nul

Attention lorsque l’on rentre une information au clavier, on termine l’acquisition par la touche Entrée (ou Enter, ou Return) qui correspond à un passage à la ligne, c’est-à-dire au caractère '\n'.

Il est facile, de convertir des lettres majuscules dans des minuscules :

if (c>='A' && c<='Z') c = c-'A'+'a';

ou vice-versa :

if (c>='a' && c<='z') c = c-'a'+'A';

II.2.3) constantes chaînes de caractères

Une chaîne de caractères est une succession de plusieurs caractères se terminant par le caractère nul : '\0', et écrite entre guillemets ; exemple : "paris6" contient 7 caractères 'p', 'a', 'r', 'i', 's', '6' et '\0'.

Plusieurs chaînes de caractères séparées par des signes d’espacement (espaces, tabulateurs ou interlignes) seront réunies en une seule chaîne lors de la compilation.

II.3) types construits

II.3.1) tableaux

Un tableau de n éléments est une série de n cellules mémoires consécutives, de même type (de la même taille).

int            vecteur[100];

(tableau d’entiers à une dimension, contenant 100 éléments)

double    matrice[10][20];

(tableau de réels double précision à deux dimensions, constitué de 10 lignes de 20 colonnes)

float         tab3d[4][6][5];

(tableau de réels simple précision à trois dimensions)

la case mémoire correspondant à l’élément tab3d[i][j][k] est &tab3d[0][0][0]+(i*6*5+j*5+k)*sizeof(float)

Dans un tableau de dimension supérieur à 1, les indices de droite sont les plus internes.

Le constante entière donne le nombre d’éléments du tableau, qui sont numérotés de 0 à (nombre-1).

Quand on déclare et initialise un tableau, la taille n’est pas nécessaire.

exemples :

int            tab1d[ ]={19,63,-8,29} est un vecteur à 4 éléments

int  tab2d[3][2]={{1,2},{3,4},{5,6}} est une matrice à 3 lignes, 2 colonnes,

                dont tab2d[2][0] est l’élément de la 3ème ligne, 1ère colonne, soit 5

II.3.2) structures (struct)

Une structure est une suite d’éléments de type quelconque appelés champs accessibles par leur nom, qui sert à lier entre des variables de type disparate. Lors de la définition d’une structure des objets peuvent être déclarés ; si ils sont déclarés ultérieurement, la structure devra nécessairement avoir un nom.

exemple de définition :

struct  date {

    int  an;

    short  mois, jour;

    };

exemple de déclaration de variables :

struct  date  t={1789,7,14}, *pt;

date est une structure de trois champs (pour accéder au champ : t.an, ... et pt->an équivalent de (*pt).an ...)

Une structure qui contient un pointeur sur cette structure définit une liste chaînée (exemple : nœud d’un maillage relié à ses voisins).

Exemple d’utilisation :

struct  duree {

    int  heur, minut, second;

    };

int conversion_duree_sec(duree d)

{

    return(3600*d.heur+60*d.minut+d.second);

}

void conversion_sec_duree(int nbsec, duree *d)

{

  *d.heur=nbsec/60;               /* équivalent à : d->heur=nbsec/60; */

  *d.minut=(nbsec%3600)/60;           /* équivalent à : d->minut=(nbsec%3600)/60; */

  *d.second=nbsec%60;      /* équivalent à : d->second=nbsec%60; */

}

II.3.3) énumérations (enum)

Une énumération est un regroupement d’un ensemble de constantes. Ces constantes ont une valeur entière affectée automatiquement par le compilateur, en partant de 0 par défaut et avec une progression de 1.

exemple de définition :

enum  couleur { rouge, vert, bleu};  alors rouge=0, vert=1 et bleu=2

II.3.4) unions (union)

Une union est un regroupement de plusieurs types sur une même zone mémoire.

II.4) types définis par l’utilisateur

L’utilisateur peut donner de nouveaux noms aux objets qu’il manipule.

typedef            float            reel;


II.5) format

%d pour un entier signé (%u pour un entier non signé, %x pour un entier en héxadécimal, %o pour un entier en octal), %ld pour un entier long avec signe, %f pour un réel en virgule flottante (%e pour un réel en exponentielle), %lf pour un réel double précision, %c pour un caractère, %s pour une chaîne de caractères, …

Les codes peuvent être précédés de nombre : %3d signifie que l’on cherche à afficher un entier signé sur 3 chiffres, %5.2f signifie que l’on cherche à afficher un réel avec 2 chiffres après la virgule (le 5 correspond au nombre minimum de chiffres utilisées).

II.6) identificateur d’une variable

Les identificateurs (nom des variables) contiennent des lettres non accentuées (en minuscule, les majuscules étant réservé aux variables du pré-compilateur), des chiffres (sauf le 1er caractère) et _ (« underscore »).

II.7) existence d’une variable

Les variables locales (définies entre { et }) ne sont connues que dans le bloc ou la fonction où elles sont déclarées ; par défaut elles sont temporaires, pour qu’elles soient permanentes (leur valeur étant sauvegardée entre deux passages dans le bloc ou la fonction) il suffit de les spécifier static à leur déclaration.

Les variables globales dans un fichier source ne sont connues que dans les fonctions qui suivent leur déclaration.

Les variables globales exportées (spécifiées extern à la déclaration) sont connues dans tous les fichiers où elles sont déclarées (grâce à une compilation séparée des différents fichiers).

 

       III)      Opérateurs

III.1) opérateurs sur nombre

III.1.1) opérateurs arithmétiques

+  addition

-   soustraction (ou changement de signe)

*   multiplication

/   division (entière si les deux opérandes sont entiers, flottante sinon)

%  modulo (reste de la division entière)

++  incrémentation

++i; (pré-incrémentation) : incrémentation (augmentation de 1) puis utilisation de i

i++; (post-incrémentation) : utilisation de i puis incrémentation

n=++i;

º

i=i+1;

n=i;

et

n=I++;

º

n=i;

i=i+1;

--  décrémentation

III.1.2) opérateurs logique bit à bit

~  complément à 1 (si x=101101 en binaire, ~x devient 010010)

&  et bit à bit (si a=0011 et b=1010 en binaire, a&b devient 0010)

|    ou inclusif bit à bit (a|b devient 1011)

^   ou exclusif bit à bit (Xor) (a^b devient 1001)

<<  décalage à gauche (x<<2 devient 10110100)

>>  décalage à droite (x>>1 devient 10110)

III.2) opérateurs logiques

Il n’a pas de type booléen en C.

Toute expression sera interprétée comme vraie si sa valeur est différente de 0 (c’est-à-dire 1, 36, -1963, …).

Toute expression sera interprétée comme fausse si sa valeur est 0.

III.2.1) comparateurs

>  strictement supérieur

<  strictement inférieur

>=  supérieur ou égal

<=  inférieur ou égal

==  égalité

!=  différence

III.2.2) logiques

!   non logique

&&  et logique

||   ou logique

III.2.3) expression conditionnelle

condition ? expression1 : expression2            signifie expression1 si condition est vraie et expression2 si condition est fausse

exemple :

le minimum de deux expressions a et b peut s’écrire : ((a)<(b)) ? (a) : (b) ) ; c’est d’ailleurs ainsi qu’est définit min(a,b) dans stdlib.h

III.3) opérateurs sur pointeurs, tableaux

*          contenu (attention à ne pas confondre avec l’opérateur de multiplication)

&         adresse (attention à ne pas confondre avec l’opérateur et logique bit à bit)

.           champ de structure

->        champ de structure avec pointeur

III.4) opérateur d’affectation

=          peut s’utiliser sous deux formes :

variable = expression1 opérateur expression2

variable opérateur = expression (º variable =variable opérateur expression)

            (exemple :            x+=5            º            x=x+5)

(ce qui le plus proche de la logique humaine, on ajoute 5 à x …)

III.5) conversion de types (transtypages)

L’opérateur (cast) correspond à une commande pour changer le type d’un objet (exemple : si on a deux déclarations int i; et float r; alors on peut écrire i=(int)r;)

L’opérateur sizeof permet de connaître la taille d’un objet ou tableau (exemple : si on une déclaration int i, tab[4]; alors sizeof(i) donnera 2=sizeof(int) et sizeof(tab) donnera 8=4sizeof(int))

Lorsque des opérandes de types différents interviennent dans le calcul d’une même expression, ou lorsqu’ils sont des arguments transférés à une fonction, ils sont tous convertis en un seul type suivant les lois suivantes :

- toute opérande char ou short est transformé en int

- tout opérande float est transformé en double

- si un des opérandes est de type double, les autres sont transformés en double et le résultat sera double

- si un des opérandes est de type long, les autres sont transformés en long et le résultat sera long

- autrement les opérandes doivent être int et le résultat sera int

Attention : (int)(2*2.5) peut donner 4 ou 5 selon les compilateurs !


III.6) priorité

opérateur

associativité

priorité

( )    [ ]    ->    .

de gauche à droite

niveau le plus prioritaire

!   ~   ++   --   -   (cast)   *   &   sizeof

de droite à gauche

 

*    /    %

de gauche à droite

 

+    -

de gauche à droite

 

<<    >>

de gauche à droite

 

<    <=    >    >=

de gauche à droite

 

==    !=

de gauche à droite

 

&

de gauche à droite

 

^

de gauche à droite

 

|

de gauche à droite

 

&&

de gauche à droite

 

||

de gauche à droite

 

?    :

de droite à gauche

 

=  +=  -=  *=  /=  %=  &=  ^=  |=  <<=  >>=

de droite à gauche

 

,

de gauche à droite

niveau le moins prioritaire

exemples :

1)

a=b=c+d**p++>5&&c<*p--;

º

a=(b=(((c+(d*(*(p++))))>5)&&(c<(*(p--)))));

 

2)

int        tab[ ]={8,4,2,1};

int        *p;

p=tab;

 

º

 

 

8

4

2

1

 

 

 

 

 

 

 

p

 

 

 

 

 

 

printf("%d\n",*p++);

º

printf("%d\n",*(p++));

ou encore :

x=*p;

p=p+1;

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

º

 

8

4

2

1

 

 

 

 

 

 

 

 

p

 

 

 

affichage :

8

 

printf("%d\n",*++p);

º

printf("%d\n",*(++p));

º

 

8

4

2

1

 

 

 

 

 

 

 

 

 

p

 

 

affichage :

2

 

printf("%d\n",++*p);

º

printf("%d\n",++(*p));

ou encore :

*p=*p+1;

y=*p;

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

º

 

8

4

3

1

 

 

 

 

 

 

 

 

 

p

 

 

affichage :

3

 

printf("%d\n",(*p)++);

 

º

 

 

8

4

4

1

 

 

 

 

 

 

 

 

 

p

 

 

affichage :

3

 

       IV)      Structures de contrôle

IV.1) instruction, bloc

Une instruction en C se termine toujours par ; (instruction = expression + ; ). Les expressions (constantes, variables) peuvent être combinées ensemble à l’aide d’opérateur, pour former des expressions plus complexes ; elles peuvent contenir des appels à des fonctions, et aussi apparaître comme paramètres dans des appels de fonctions.

Différentes instructions peuvent être rassemblées en bloc à l’aide d’accolades { et }

Lorsqu’un bloc ne contient qu’une et une seule instruction alors les accolades ne sont plus obligatoires.

IV.2) sélection

IV.2.1) if (test)

if ( condition )

{

            instruction1;

            instruction2;

            }

else

{

            instruction3;

            instruction4;

            }

Si la condition est vraie (c’est-à-dire sa valeur différente de 0) alors les instruction1 et instruction2 sont réalisées, si la condition est fausse alors les instruction3 et instruction4 sont réalisées.

La négation de la condition else est totalement facultative ; si elle existe, la clause else se rapporte au dernier if ouvert :

if ( condition1 )

if ( condition2 )

                        instruction1;

else

                        instruction2;

 

º

if ( condition1 )

{

if ( condition2 )

                        instruction1;

else

                        instruction2;

            }

si après une instruction else , un autre if est ouvert, on peut écrire else if

if ( condition1 )

            instruction1;

else

{

if ( condition2 )

                        instruction2;

else

                        instruction3;

}

º

if ( condition1 )

            instruction1;

else if ( condition2 )

            instruction2;

else

            instruction3;

 

IV.2.2) switch (test à choix multiple)

switch ( expression )

{

case                 constante1 :

instruction1.1;

instruction1.2;

            case        constante2 :

instruction2;

            case        constante3 :

            case        constante4 :

instruction34.1;

instruction34.2;

default :

instructionD;

            }

L’expression est évaluée puis comparée aux différentes valeurs de la liste qui sont des constantes ; si la valeur de l’expression est trouvée dans la liste ou si l’option default est présente, l’exécution commence à partir de la liste d’instructions et continue ensuite jusqu’à la fin du bloc switch ; l’option default est facultative.

IV.2.3) remarque

Dans un test avec plusieurs membres, en cas d’égalité de priorité, le membre le plus à gauche est évalué en premier. Si la valeur du premier membre est suffisante pour définir la valeur du test, les autres membres ne sont pas évalués.

IV.3) itérations

IV.3.1) while (boucle tant que)

while ( condition )

            {

            instruction1;

            instruction2;

            }

les deux instructions s’exécuteront tant que la condition sera vraie

IV.3.2) do ... while (boucle jusqu’à ce que)

do

            {

            instruction1;

            instruction2;

            } while ( condition );

les deux instructions s’exécuteront une fois puis autant de fois que la condition sera vraie

IV.3.3) for (boucle avec compteur)

for ( initialisation ; condition ; in(dé)crémentation )

            {

            intsruction1;

            instruction2;

            }

l’initialisation est exécutée avant d’entrer dans la boucle, la condition est évaluée à chaque tour de boucle, l’in(dé)crémentation est effectuée après les instructions et avant de remonter à la condition

exemple :

int  i, j, k;

 

for ( i=0 , j=10 ; i<j && k>0 ; i++ , j-- )

{

            }

IV.3.4) remarque

boucle infinie :

while ( 1 )

            {

            ...

            }

º

#define      ever            ;;

 

for ( ever )

            {

            ...

            }

boucle qui permet de se déplacer dans un tableau de caractères à l’aide d’un pointeur sur caractères pointant sur un tableau, à la fin le pointeur se trouve sur la case contenant '\0' :

while ( *p != '\0' )

            {

            ++p;

            }

º

for ( ; *p != '\0' ; p++ )

            ;

attention la boucle suivante permet aussi de se déplacer dans un tableau de caractères à l’aide d’un pointeur sur caractères pointant sur un tableau, mais à la fin le pointeur se trouve sur la case suivant celle contenant '\0' :

while ( *p++ != '\0' )

            ;

IV.4) ruptures

IV.4.1) break (échappement de boucle ou de test à choix multiple)

permet d’arrêter prématurément une et une seule instruction itérative (while, do ... while, for) ou une sélection sur choix (switch) avant la fin normale

while ( condition1 )

            {

            instruction1;

            if ( condition2 )

              {

              instruction2;

              break;

              }

            instruction3;

            }

dès que la condition2 est vraie, on effectue l’instruction2 et on sort de la boucle while quelque soit l’état de la condition1

exemple :

char  c;

 

switch ( c )

{

            case        'a' :

            case        'e' :

            case        'i' :

            case        'o' :

            case        'u' :

            case        'y' :

printf("voyelles\n");

break;

default :

printf("consonnes\n");

            }

IV.4.2) continue (au suivant)

permet de remonter à la partie condition dans la boucle courante

for ( initialisation ; condition1 ; in(dé)crémentation )

            {

            intsruction1;

            if ( condition2 )

              {

              instruction2;

              continue;

              }

            instruction3;

            }

dès que la condition2 est vraie, on effectue l’instruction2 et on revient au début de la boucle for sans effectuer l’instruction3

IV.5) branchement

goto : à ne pas utiliser !

 

          V)      Pointeurs et tableaux

V.1) pointeur

Un pointeur est une variable dont le contenu a pour valeur l’adresse d’un autre objet.

Il n’existe pas de type pointeur à proprement parler.

avec :               int            i,*p;

                        p=&i;

p est un pointeur (indiqué par l’*) vers l’entier i (i.e. la valeur de p, c’est l’adresse de i, i.e. &i), *p représente la valeur (le contenu) de l’objet pointé, i.e. l’entier i.

Le pointeur nul (NULL) est un pointeur qui pointe nulle part.

Le contenu d’un pointeur (=adresse de l’objet pointé) est du même type que l’objet pointé, c’est-à-dire qu’il occupe une place identique (une adresse n’est pas forcément un entier !).

L’arithmétique sur les pointeurs est une arithmétique qui se soucie du type de l’objet pointé (un pointeur p + un entier n donne l’ancienne adresse + n éléments du type pointé ; différence de deux pointeurs du même type donne le nombre d’éléments situés entre les deux pointeurs).

exemple :

int        i,j,*p;

i=5;

p=&i;

j=*p;

*p=j+2;

au début il y a l’allocation de l’espace mémoire nécessaire pour les trois variables : i, j et p, à la fin le contenu est le suivant : 7 pour i, 5 pour j, et l’adresse de i pour p

après la déclaration :                 int            i,j,*p;                , on a :

nom :

 

 

i

 

 

 

j

 

 

 

p

 

 

valeur :

 

 

 

 

 

 

 

 

 

 

 

 

 

adresse :

 

 

&i

 

 

 

&j

 

 

 

&p

 

 

puis après :                   i=5;                  , on a :

nom :

 

 

i

 

 

 

j

 

 

 

p

 

 

valeur :

 

 

5

 

 

 

 

 

 

 

 

 

 

adresse :

 

 

&i

 

 

 

&j

 

 

 

&p

 

 

puis après :                   p=&i;               , on a :

nom :

 

 

i

 

 

 

j

 

 

 

p

 

 

valeur :

 

 

5

 

 

 

 

 

 

 

&i

 

 

adresse :

 

 

&i

 

 

 

&j

 

 

 

&p

 

 


puis après :                   j=*p;                , on a :

nom :

 

 

i

 

 

 

j

 

 

 

p

 

 

valeur :

 

 

5

 

 

 

5

 

 

 

&i

 

 

adresse :

 

 

&i

 

 

 

&j

 

 

 

&p

 

 

puis après :                   *p=j+2;                        , on a :

nom :

 

 

i

 

 

 

j

 

 

 

p

 

 

valeur :

 

 

7

 

 

 

5

 

 

 

&i

 

 

adresse :

 

 

&i

 

 

 

&j

 

 

 

&p

 

 

V.2) tableau

La déclaration d’un tableau de n éléments réserve en mémoire la place pour loger les n éléments.

Les éléments d’un tableau sont situés consécutivement en mémoire ; le premier élément a pour indice 0, le dernier n-1.

si :                    int            tab[4];

tab (l’identificateur du nom du tableau) = &tab[0] (adresse de la 1ère case de ce tableau en mémoire), tab est donc un pointeur

nom :

 

 

tab[0]

tab[1]

tab[2]

tab[3]

 

 

valeur :

 

 

 

 

 

 

 

 

adresse :

 

 

&tab[0] ou tab

&tab[1]

&tab[2]

&tab[3]

 

 

&tab[0]+1 correspond à &tab[1], ...

V.3) correspondance

char    **c;                  c est un pointeur de pointeurs de caractères,

*c est pointeur de caractères,

**c est un caractère

char    *d[ ];                d est un tableau de pointeurs de caractères

d[ ] est un pointeur de caractères

*d[ ] est un caractère

char    e[ ][ ];               e est un tableau de tableau de caractères

(tableau de chaînes de caractères)

e[ ] est un tableau de caractères (chaîne de caractères)

e[ ][ ] est un caractère

Il y a équivalence entre ces différentes définitions.

exemple :

#include <stdio.h>

 

main()

{

            static char    *f[ ]={"MOINS","PLUS","GRAND"};

            static char    **g[ ]={f,f+1,f+2};

/* les tableaux f et g se trouvent dans la zone d’allocation fixe */

            char                *p;

            int                    i;

 

            printf("%c\n",*(f[0]+2));        /* donne   'I'            (3ème caractère de la 1ère chaîne) */

            printf("%c\n",*(*f+2));            /* donne   'I'            (3ème caractère de la 1ère chaîne) */

            printf("%c\n",**(f+2));            /* donne   'G'            (1er caractère de la 3ème chaîne) */

            printf("%s\n",*(f+1));            /* donne            "PLUS" */

            for ( p=**(g+1) ; *p ; p++ )

                        *p='A';

            printf("%s\n",*(f+1));             /* donne            "AAAA" */

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

                        *(**(g+1)+i)=' ';

            printf("%s",*(f+1));                /* donne   "    " */

}

alors la mémoire contient au départ :

(attention : ci-dessous les adresses sont des exemples numériques à but démonstratif)

valeur :

 

'M'

'O'

'I'

'N'

'S'

'\0'

'P'

'L'

'U'

'S'

'\0'

'G'

'R'

'A'

'N'

'D'

'\0'

 

adresse :

 

100

 

 

 

 

 

106

 

 

 

 

111

 

 

 

 

 

 

et :

nom :

 

 

f[0]

f[1]

f[2]

 

 

 

g[0]

g[1]

g[2]

 

 

valeur :

 

 

100

106

111

 

 

 

130

131

132

 

 

adresse :

 

 

130

131

132

 

 

 

 

 

 

 

 

Notons que :

(g+1) contient l’adresse (pointe vers) g[1]

*(g+1) contient le contenu de g[1], c’est-à-dire l’adresse de f[1] (soit &f[1])

**(g+1) contient l’adresse de "PLUS", c’est-à-dire l’adresse de 'P'

**(g+1)+i contient du ième caractère de "PLUS"

 

       VI)      Passage des paramètres

VI.1) passage par valeur

C’est le passage de la valeur de la variable.

Un paramètre passé par valeur ne peut pas être modifié.

exemple : printf("%d\n",i);

Le passage par valeur a l’avantage que nous pouvons utiliser les paramètres comme des variables locales bien initialisées. De cette façon, nous avons besoin de moins de variables d’aide.

Habituellement, on utilise un passage par valeur, quand on veut transmettre une valeur (sans plus) à une fonction, c’est-à-dire pour un « argument en entrée ».

Attention : on peut passer un pointeur par valeur, on passe alors le contenu du pointeur, c’est-à-dire une adresse, mais pas l’adresse du pointeur !

VI.2) passage par adresse

C’est le passage d’un pointeur sur la variable, c’est-à-dire de l’adresse de la variable.

exemple : scanf("%d",&i);

Comme il est impossible de passer « la valeur » de tout un tableau à une fonction, on fournit l’adresse d’un élément du tableau. En général, on fournit l’adresse du premier élément du tableau, qui est donnée par le nom du tableau.

Habituellement, on utilise un passage par adresse, quand on veut récupérer une valeur d’une fonction, ou quand on veut transmettre une valeur à une fonction et récupérer sa nouvelle valeur ; c’est-à-dire pour un « argument en entrée-sortie », ou pour un « argument en sortie ».

Attention : on peut passer un pointeur par adresse, on passe alors l’adresse du pointeur qui contient lui-même une adresse !


VI.3) différence

#include <stdio.h>

 

void fct1(int);

void fct2(int *);

 

main()

{

            int        x=10, y=20;

 

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

            fct1(x);

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

 

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

            fct2(&y);

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

}

 

                        /* (suite à droite) */

 

  ┌──►

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

──┘

 

void fct1(int valx) /* par valeur */

{

            valx=100;

}

 

void fct2(int *px) /* par adresse */

{

            *px=100;

}

 

 

 

l’exécution de ce programme donne 

10

10

20

100

Dans le programme principal, après la déclaration-initialisation des deux variables, on a :

nom :

 

 

x

y

 

 

 

 

 

 

 

 

 

 

valeur :

 

 

10

20

 

 

 

 

 

 

 

 

 

 

adresse :

 

 

&x

&y

 

 

 

 

 

 

 

 

 

 

à l’entrée de la fonction fct1 (quand on passe de main à fct1, il y a identification entre la valeur de la variable en argument d’appel de fct1 dans main, et la valeur de la variable propre à fct1 dans fct1), on a :

nom :

 

 

x

 

 

 

 

valx

 

 

 

 

 

 

valeur :

 

 

10

 

 

 

 

10

 

 

 

 

 

 

adresse :

 

 

&x

 

 

 

 

&valx

 

 

 

 

 

 

après l’instruction de la fonction fct1, on a :

nom :

 

 

x

 

 

 

 

valx

 

 

 

 

 

 

valeur :

 

 

10

 

 

 

 

100

 

 

 

 

 

 

adresse :

 

 

&x

 

 

 

 

&valx

 

 

 

 

 

 

au retour dans le programme principal, on a :

nom :

 

 

x

y

 

 

 

 

 

 

 

 

 

 

valeur :

 

 

10

20

 

 

 

 

 

 

 

 

 

 

adresse :

 

 

&x

&y

 

 

 

 

 

 

 

 

 

 

à l’entrée de la fonction fct2, on a :

nom :

 

 

 

y

 

 

 

 

 

 

 

px

 

 

valeur :

 

 

 

20

 

 

 

 

 

 

 

&y

 

 

adresse :

 

 

 

&y

 

 

 

 

 

 

 

&px

 

 

après l’instruction de la fonction fct2 (quoique la variable y ne soit pas connu dans la fonction fct2, mais du fait de l’identification entre la valeur de la variable en argument d’appel de fct2 dans main, et la valeur de la variable propre à fct2 dans fct2), on a :


 


nom :

 

 

 

y

 

 

 

 

 

 

 

px

 

 

valeur :

 

 

 

100

 

 

 

 

 

 

 

&y

 

 

adresse :

 

 

 

&y

 

 

 

 

 

 

 

&px

 

 

au retour dans le programme principal, on a :

nom :

 

 

x

y

 

 

 

 

 

 

 

 

 

 

valeur :

 

 

10

100

 

 

 

 

 

 

 

 

 

 

adresse :

 

 

&x

&y

 

 

 

 

 

 

 

 

 

 

 

    VII)      Fonction

VII.1) fonction main

C’est une fonction comme les autres, plus certaines choses ; en fait elle retourne un entier et reçoit en entrée trois arguments :

int main(int argc,char **argv,char **envp)

où :

argc correspond à un compteur d’arguments, c’est-à-dire au nombre de mots tapés sur la ligne de commande.

argv correspond aux arguments, c’est-à-dire à un tableau de pointeur sur des chaînes de caractères correspondant chacune aux mots tapés sur la ligne de commande.

envp correspond à un pointeur d’environnement (ne sert plus à rien).

VII.2) autres fonctions

Une fonction qui ne retourne rien est de type void, son exécution se termine par la commande return; (si la sortie de cette fonction se fait normalement en fin de fonction, l’instruction return est facultative).

Une fonction qui retourne quelque chose (uniquement un, et un seul, objet, variable simple ou pointeur, de type primitif : char, int, short, long, float, double ; donc pas de tableau) renvoie cette information par l’instruction                  return( objet retourné );

Une fonction qui n’a pas d’argument est une fonction de type : fct(void)

Le nombre d’arguments d’une fonction n’est pas limité.

Attention : une variable définie dans une fonction (vrai aussi pour un bloc), n’existe pas en dehors de cette fonction.

Une fonction peut appeler une autre fonction (exemple : main()) ou même s’appeler elle-même (fonction récursive).

exemple de fonction :

/* calcul de la factorielle de n */

long factorielle(int n)

{

            int        i;

            long     produit=1;

 

            if ( n > 1 )

                        for ( i=2 ; i<=n ; ++i )

                                   produit *= i;

            return( produit );

}

º

/* calcul de la factorielle de n en récursif */

long factorielle(int n)

{

            long     produit=1;

 

            if ( n > 1 )

                        produit = n * factorielle(n-1);

            return( produit );

}

 


 VIII)      Allocation dynamique

L’allocation dynamique permet d’allouer et de libérer de la mémoire, au fur et à mesure des besoins (c’est-à-dire qu’il n’est plus nécessaire de fixer arbitrairement une taille limite d’un tableau ; la taille nécessaire est définie lors de l’exécution et non de la compilation).

La fonction malloc (dont le prototype est void *malloc(size_t taille)) retourne un pointeur sur le premier élément du tableau que l’on cherche à allouer (il faut convertir le résultat dans le type d’objet désiré) ; la valeur retournée est un pointeur nul (NULL) dans le cas où l’allocation a échoué. L’argument de la fonction est de type prédéfini (par un typedef dans stddef.h inclus dans stdlib.h), il correspond à la taille désirée en octet.

La fonction realloc (dont le prototype est void *realloc(void *pointeur,size_t taille)) contient en argument, le pointeur sur le premier élément du tableau déjà alloué et dont on veut modifier la taille (la taille passée en argument est la nouvelle taille désirée). La fonction retourne un pointeur sur le premier élément du tableau que l’on cherche à réallouer (il faut convertir le résultat dans le type d’objet désiré) ; si la modification de taille demandée ne peut se faire à l’adresse initiale (par exemple, parce qu’il n’y a pas assez de mémoire contiguë disponible à cet endroit là), alors la fonction déplace le bloc à un emplacement mémoire approprié et donc l’adresse du bloc n’est plus la même. Dans le cas où la ré-allocation a échoué, la valeur retournée est un pointeur nul (NULL).

La fonction free (dont le prototype est void free(void *pointeur)) permet de libérer l’espace mémoire qui a été alloué par les fonctions précédentes. L’argument est le pointeur contenant l’adresse du premier élément réservé par une des fonctions d’allocation.

exemple d’allocation :

#include <stdio.h>

#include <stdlib.h>

 

main()

{

int            nbelt, *pt, *qt;

 

nbelt=10;

pt=(int *)malloc(nbelt*sizeof(int));                 /* allocation de 10 places */

if ( !pt )

                        {

                        printf("allocation memoire impossible\n");

                        exit(1);

                        }

qt=pt;

nbelt=30;

pt=(int *)realloc(qt,nbelt*sizeof(int));            /* rajout de 20 places */

if ( !pt )

                        {

                        printf("re-allocation memoire impossible\n");

                        exit(2);

                        }

free(pt);                                                           /* liberation des 30 places, mais attention

contient toujours la même adresse */

}

 

       IX)      Remarques

L’écriture d’un programme informatique en langage C (vrai également avec les autres langages !) demande de la rigueur, de la préparation, ... Chaque programme devra être lisible, modifiable, certaines parties ré-utilisables, par un autre programmeur ainsi que par le créateur plusieurs mois après la création. C’est-à-dire qu’un programme doit être correctement indenté, commenté, modulaire (découpé en fonctions le plus élémentaires possibles), ..., qu’un organigramme précis et complet doit être écrit au préalable à l’écriture du code informatique.

 

La présentation de l’indentation peut suivre des règles diverses :

while ( condition )

            {

            instruction;

            }

º

while ( condition )

{

            instruction;

}

º

while ( condition ) {

            instruction;

}

l’important est de garder la même règle au cours de l’écriture d’un programme

 

Par clarté, on a habitude d’utiliser des lignes blanches pour séparer les déclarations et les instructions.

 

          X)      Premier programme

/* programme d’impression des carrés des dix chiffres : CECI EST UN COMMENTAIRE QUI COMMENCE AVEC LE SYMBOLE slash-étoile ET SE TERMINE AVEC LE SYMBOLE étoile-slash */

#include <stdio.h>                                /* LE COMPILATEUR AURA ACCES AU FICHIER

stdio.h QUI DEFINIT NOTAMMENT LA FONCTION

printf ; LE LIEN ENTRE LE COMPILATEUR ET CE

FICHIER EST AUTOMATIQUE ; LE NOM DE CE

FICHIER, EST INDIQUE ENTRE CHEVRONS CAR IL

N’EST PAS PRESENT DANS LE REPERTOIRE

COURANT */

 

int power(int ,int );                           /* PROTOTYPAGE DE LA FONCTION :

DECLARATION DU TYPE DE L’OBJET RETOURNE

PAR LA FONCTION, ET DU TYPE DES */

/* ARGUMENTS DE LA FONCTION power */

 

main()                                                /* LA FONCTION main EST TOUJOURS LE PROGR.

PRINCIPAL ; LES PARENTHESES, USUELLES POUR

TOUTE FONCTION, INDIQUENT QUE LA

FONCTION main N’A PAS D’ARGUMENT */

{                                                          /* DEBUT DE BLOC,

DU PROGRAMME PRINCIPAL */

int            i;

            int            res;                              /* DECLARATION DES VARIABLES i ET res

EN ENTIER */

            i=1;                                          /* AFFECTATION A 1 DE LA VARIABLE i :

INITIALISATION */

            while ( i < 10 )             /* BOUCLE TANT QUE : LES INSTRUCTIONS

COMPRISES DANS LE BLOC SERONT EFFECTUES

TANT QUE LA CONDITION i<10 SERA VERIFIEE */

                        {                                   /* DEBUT DE BLOC, DE LA BOUCLE TANT QUE */

                        res=power(i,2);            /* APPEL DE LA FONCTION power,

LES ARGUMENTS SONT PASSES PAR VALEUR,

C’EST-A-DIRE QUE LES VALEURS SONT

TRANSMISES A LA FONCTION power */

                        printf("%d au carré = %d\n",i,res);

/* AFFICHAGE A L’ECRAN DE : (la valeur de i) au

carré = (résultat calculé par power)

LE %d CORRESPOND AU TYPE ET AU FORMAT

DES DONNEES */

                        i=i+1;                           /* INCREMENTATION DE LA VARIABLE i */

                        }                                   /* FIN DE BLOC, DE LA BOUCLE TANT QUE */

}                                                          /* FIN DE BLOC, DU PROGRAMME PRINCIPAL */

 

int power(int x,int n)                         /* LA FONCTION power RETOURNE UN ENTIER,

ET A DEUX ARGUMENTS ENTIERS

CES ARGUMENTS SERONT UTILISES, MAIS SI ILS

SONT MODIFIES, CETTE NOUVELLE VALEUR NE

SERA PAS TRANSMISE AU PROGRAMME

PRINCIPAL */

{                                                          /* DEBUT DE BLOC, DE LA FONCTION power */

            int            res;                              /* VARIABLE LOCALE A LA FONCTION,

DONC DIFFERENTE DE LA VARIABLE res

DU PROGRAMME PRINCIPAL */

            res=1;                                     /* AFFECTATION A 1 DE LA VARIABLE res :

INITIALISATION */

            while ( n > 0 )             /* BOUCLE TANT QUE */

                        {                                   /* DEBUT DE BLOC, DE LA BOUCLE TANT QUE */

                        res=res*x;                    /* AFFECTION DE LA MULTIPLICATION DE LA

VALEUR INITIALE DE res PAR x, A LA */

/* VARIABLE res */

                        n=n-1;             /* DECREMENTATION DE LA VARIABLE n */

                        }                                   /* FIN DE BLOC, DE LA BOUCLE TANT QUE */

            return(res);                              /* SPECIFICATION DE LA VALEUR RETOURNEE

PAR LA FONCTION power */

}                                                          /* FIN DE BLOC, DE LA FONCTION power */

 

            /* ON ECRIT USUELLEMENT LES PROGRAMMES EN MINUSCULE */

            /* CHAQUE INSTRUCTION EN LANGAGE C SE TERMINE PAR ; */

            /* TOUTE LIBERTE EST LAISSEE DANS L’USAGE DES BLANCS ET */

            /* DE L’INDENTATION ; MAIS COMMENTAIRES, LIGNES VIDES ET

            INDENTATIONS PERMETTENT DE HIERARCHISER LES DIFFERENTES

            PARTIES D’UN PROGRAMME ET D’EN AUGMENTER LA LISIBILITE.

            DONC BIEN QUE FACULTATIVES, CES REGLES FONT PARTIE

            D’UNE BONNE PROGRAMMATION */

 

       XI)      Fonctions prédéfinies

Voici quelques fonctions prédéfinies dans la bibliothèque standard :

 

printf : fonction, définit dans stdio.h, qui permet d’écrire à l’écran (sortie standard : stdout) du texte

scanf : fonction, définit dans stdio.h, qui permet d’acquérir en mémoire une valeur tapée au clavier (entrée standard : stdin)

 

getchar : fonction, définit dans stdio.h, qui permet d’acquérir en mémoire un, et un seul, caractère tapé au clavier (exemple :              char c;                  c=getchar();)

putchar : fonction, définit dans stdio.h, qui permet d’écrire à l’écran un, et un seul, caractère (exemple :              char c;                  putchar(c);)

 

fopen : fonction, définit dans stdio.h, qui permet d’ouvrir un fichier (exemple :          FILE *fp;          fp=fopen(nomdefichier,mode);            où fp est le pointeur sur le fichier, où nomdefichier est une chaîne de caractères correspondant au nom de fichier à ouvrir, et mode une chaîne de caractères correspondant au mode d’ouverture du fichier correspondant, "r" pour une ouverture en lecture, "w" pour une ouverture en écriture, "r+" pour une ouverture en lecture et écriture d’un fichier devant exister, "w+" pour une ouverture en lecture et écriture d’un fichier vide, si il existe, son contenu est détruit, …)

fread : fonction, définit dans stdio.h, qui permet de lire des informations dans un fichier

fwrite : fonction, définit dans stdio.h, qui permet d’écrire des informations dans un fichier

fscanf : fonction, définit dans stdio.h, qui permet de lire des informations formatées dans un fichier

fprintf : fonction, définit dans stdio.h, qui permet d’écrire des informations formatées dans un fichier

fseek : fonction, définit dans stdio.h, qui permet de positionner le pointeur à une position indiquée du fichier

rewind : fonction, définit dans stdio.h, qui permet de positionner le pointeur au début du fichier

fclose : fonction, définit dans stdio.h, qui permet de fermer un fichier

 

strcmp : fonction, définit dans string.h, qui permet de comparer deux chaînes de caractères (la valeur retournée est négative sur la première chaîne en argument est inférieure à la deuxième chaîne en argument ; la valeur est positive si la première est supérieure à la deuxième ; la valeur est zéro quand les deux chaînes sont identiques)

strlen : fonction, définit dans string.h, qui fournit la longueur d’une chaîne de caractères

 

sin (« cos », « tan », …) : fonction, définit dans math.h, qui fournit la valeur du sinus (« cosinus », « tangente », …) (exemple :              double x,y;            y=sin(x);          y aura pour valeur sin(x))

atan2 : fonction, définit dans math.h, qui fournit la valeur de l’arctan (exemple :          double x,y,z;            z=atan2(y,x);                  z aura pour valeur arctan(y/x))

pow : fonction, définit dans math.h, qui fournit la valeur d’une variable à une certaine puissance (exemple :              double x,y,z;            z=pow(y,x);                    z aura pour valeur xy)

log10 (« log ») : fonction, définit dans math.h, qui fournit la valeur du logarithme décimal (« népérien ») (exemple :          double x,y;            y=log10(x);           y aura pour valeur log10(x))

sqrt : fonction, définit dans math.h, qui fournit la valeur de la racine carrée (exemple :          double x,y;            y=sqrt(x);               y aura pour valeur sqrt(x))

fabs : fonction, définit dans math.h, qui fournit la valeur de la valeur absolue (exemple :          double x,y;            y=fabs(x);             y aura pour valeur abs(x))

 

rand : fonction, définit dans stdlib.h, qui fournit un nombre entier aléatoire, en fait pseudo-aléatoire, compris entre 0 et RAND_MAX (cette dernière valeur est au moins égale à 32767)

malloc : fonction, définit dans stdlib.h, qui permet d’allouer un emplacement d’un certain nombre d’octets, sans l’initialiser, et qui fournit l’adresse correspondante lorsque l’allocation a réussi ou sinon un pointeur nul

exit : fonction, définit dans stdlib.h, qui termine l’exécution d’un programme

 

    XII)      C++

Le langage C++ est une extension du C (il s’appelle C++ en référence à l’opérateur d’incrémentation du C). Il est considéré comme un langage orienté objet car il répond aux trois principes : 1) encapsulation (ou peut définir des classes qui sont des structures évoluées dont certains champs, appelés membres, sont des fonctions, appelées méthodes), 2) polymorphisme (deux fonctions peuvent avoir des paramètres facultatifs dont la valeur par défaut est mentionné dans le prototypage, appelé signature) et 3) héritage (les classes peuvent être définies à partir d’autres classes).

Référence : « Le langage C++ » de B. Stroustrup, édition Campus Press (une autre bible !)

exemple d’un programme qui effectue des conversions pouce vers centimètre et inversement (l’utilisateur rentre un nombre suivi d’un caractère indiquant l’unité : i pour une valeur rentrée en pouce, c pour une valeur rentrée en centimètre ; le programme affiche la valeur correspondante dans l’autre unité) :

// CECI EST UN COMMENTAIRE, QUI SE TERMINE A LA FIN DE LA LIGNE

#include <iostream.h>

 

int main()

{

            const float            factor=2.54;                // 1 pouce = 2.54 cm

            float                 x, in, cm;

            char                ch;

 

            cout<<”Entrez une longueur (suivi d’un caractere, i pour pouce, c pour cm) : “;

            cin>>x;                        // LECTURE D’UN NOMBRE REEL VIRGULE FLOTTANTE

            cin>>ch;                     // LECTURE D’UN SUFFIXE

            switch ( ch )

                        {

                        case ‘i’ :

                                    in=x;

                                    cm=x*factor;

                                    break;

                        case ‘c’ :

                                    in=x/factor;

                                    cm=x;

                                    break;

                        default :

                                    in=cm=0;

                                    break;

                        }

            cout<<in<<” in = ”<<cm<<” cm\n”;

}