Cours gratuits » Cours informatique » Cours programmation » Cours langage C » Ebook : support de cours Introduction au langage C de Bernard Cassagne v2

Ebook : support de cours Introduction au langage C de Bernard Cassagne v2


Télécharger



★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Ebook : support de cours Introduction au langage C de Bernard Cassagne v2

1.1 Le compilateur

Les compilateurs C font subir deux transformations aux programmes:

  1. un préprocesseur fait subir au texte des transformations d'ordre purement lexical,
  2. le compilateur proprement dit prend le texte générépar le préprocesseur et le traduit en instructions machine.

Le but du préprocesseur est de rendre des services du type traitement de macros et compila¬tion conditionnelle. Initialement, il s'agissait de deux outils dif

férents, mais les possibilités du préprocesseur ont étéutilisées de manière tellement extensive par les programmeurs, qu'on en est venu à considérer le préprocesseur comme partie intégrante du compilateur. C'est cette dernière philosophie qui est retenue par l'ANSI dans sa proposition de norme pour le langage C.

1.2 Les types de base

1.2.1 les caractères

Le mot clédésignant les caractères est char. char est le type dont l'ensemble des valeurs est l'ensemble des valeurs entières formant le code des caractères utilisésur la machine cible. Le type char peut donc être considérécomme un sous-ensemble du type entier.

Le code des caractères utilisén'est pas défini par le langage: c'est un choix d'implémentation. Cependant, dans la grande majoritédes cas, le code utiliséest le code dit ASCII.

1.2.2 Les entiers

Le mot clédésignant les entiers est int.

Les entiers peuvent être affectés de deux types d'attributs : un attribut de précision et un attribut de représentation.

Les attributs de précision sont short et long. Du point de vue de la précision, on peut donc avoir trois types d'entiers : les short int, les int et les long int. Les int sont implémentés sur ce qui est un mot "naturel " de la machine. Les long int sont implémentés si possible plus grands que les int, sinon comme des int. Les short int

 sont implémentés plus courts que les int, sinon comme des int. Les implémentations classiques mettent les short int sur 16 bits, les long int sur 32 bits, et les int sur 16 ou 32 bits selon ce qui est le plus efficace.

L'attribut de représentation est unsigned. Du point de vue de la représentation, on peut donc avoir deux types d'entiers : les int et les unsigned int. Les int permettent de contenir des entiers signés, très généralement en représentation en complément à 2, bien que cela ne soit pas imposépar le langage. Les unsigned int permettent de contenir des entiers non signés en représentation binaire.

On peut combiner attribut de précision et attribut de représentation et avoir par exemple un unsigned long int. En résumé, on dispose donc de six types d'entiers: les int, les short int, les long int (tous trois signés) et les unsigned int, les unsigned short int et les unsigned long int.

1.2.3 Les flottants

Les flottants peuvent être en simple ou en double précision. Le mot clédésignant les flottants simple précision est float, et celui désignant les flottants double précision est double. La précision effectivement utilisée pour ces deux types dépend de l'implémentation.

1.3 Les constantes

1.3.1 Les constantes entières

On dispose de 3 notations pour les constantes entières: décimale, octale et hexadécimale.

Les contantes décimales s'écrivent de la manière usuelle (ex: 372). Les constantes octales doivent commencer par un zéro (ex: 0477). Les constantes hexadécimales doivent commencer par 0x ou 0X et on peut utiliser les lettres majuscules aussi bien que les minuscules pour les chiffres hexadécimaux.

ex:

0x5a2b 0X5a2b 0x5A2B

Si une constante entière peut tenir sur un int, elle a le type int, sinon elle a le type long int.

On dispose d'une convention permettant d'indiquer au compilateur qu'une constante entière doit avoir le type long int. Cette convention consiste à faire suivre la con¬stante de la lettre l (en majuscule ou en minuscule). Les notations décimales, octales et hexadécimales peuvent être utilisées avec cette convention.

ex :

0L 0456l

0x7affL

 attention

 1.3.2 Les constantes caractères

La valeur d'une constante caractère est la valeur numérique du caractère dans le code de la machine.

Si le caractère dispose d'une représentation imprimable, une constante caractère s'écrit entourée du signe '. ex: 'g'

En ce qui concerne les caractères ne disposant pas de représentation imprimable, C autorise une notation à l'aide d'un caractère d'escape qui est \ :

  • Certains d'entres eux, utilisés très fréquemment, disposent d'une notation partic-ulière. Il s'agit des caractères suivant:

new line '\n'

horizontal tabulation '\t'

back space '\b'

carriage return '\r'

form feed '\f'

back slash '\\'

single quote '\~''~

  • les autres disposent de la notation \x oùx est un nombre expriméen octal. ex:

'\1' SOH

'\2' STX

'\3' ETX etc...

1.3.3 Les constantes flottantes

La notation utilisée est la notation classique par mantisse et exposant. Pour introduire l'exposant, on peut utiliser la lettre e sous forme minuscule ou majuscule. ex:

notation C notation mathématique

2 x 104

2 x 104

Toute constante flottante est considérée comme étant de type flottant double précision.

1.4 Les chaînes de caractères littérales

Une chaîne de caractères littérale est une suite de caractères entourés du signe ". ex: "ceci est une chaine"

Toutes les notations de caractères non imprimables définies en 1.3.2 sont utilisables dans les chaînes. ex:

"si on m'imprime,\nje serai sur\ntrois lignes a cause des deux new line"

Le caractère \ suivi d'un passage à la ligne suivante est ignoré. Cela permet de faire tenir les longues chaînes sur plusieurs lignes de source. ex:

"ceci est une tres tres longue chaine que l'on fait tenir \ sur deux lignes de source"

Le compilateur rajoute à la fin de chaque chaîne un caractère mis à zéro. (Le caractère dont la valeur est zéro est appellénull dans le code ASCII). Cette convention de fin de chaîne est utilisée par les fonctions de la librairie standard.

1.5 constantes nommées

Il n'y a pas en C de possibilitéde donner un nom à une constante. On peut cependant réaliser un effet équivalent grâce au préprocesseur. Lorsque le préprocesseur lit une ligne du type:

#define identificateur reste-de-la-ligne

il remplace dans toute la suite du source, toute nouvelle occurence de identificateur par reste-de-la-ligne.

Par exemple on peut écrire:

#define pi 3.14159

et dans la suite du programme on pourra utiliser le nom pi pour désigner la constante 3.14159.

1.6 Déclarations de variables ayant un type de base

Une telle déclaration se fait en faisant suivre le nom du type, par la liste des noms des variables.

ex :

int i;

int i,j;

short int k; float f;

double d1,d2;

Il est possible de donner une valeur initiale aux variables ainsi déclarées. ex:

int i = 54;

int i = 34,j = 12;

1.7. LES OPÉRATEURS LES PLUS USUELS 9

1.7 Les opérateurs les plus usuels 1.7.1 l'affectation

En C, l'affectation est un opérateur et non pas une instruction.

  • Syntaxe:

expression :

=* lvalue = expression

Dans le jargon C, une lvalue est une expression qui doit délivrer une variable (par opposition à une constante). Une lvalue peut être par exemple une variable, un élément de tableau, mais pas une constante. Cette notion permet d'exprimer dans la grammaire l'impossibilitéd'écrire des choses du genre 1 = i qui n'ont pas de sens.

Exemples d'affectation:

i = 3 f = 3.4

i = j + 1

  • Sémantique:

L'opérateur a deux effets:

  1. un effet de bord consistant à affecter la valeur de expression à la variable désignée par la lvalue
  2. l'opérateur délivre la valeur ainsi affectée, valeur qui pourra donc être utilisée dans une expression englobant l'affectation.

ex :

i = (j = k) + 1

La valeur de k est affectée à j et cette valeur est le résultat de l'expression (j = k), on y ajoute 1 et le résultat est affectéà i.

  • Restrictions:

La lvalue doit désigner une variable ayant un type de base. Il n'est donc pas possible d'affecter un tableau à un tableau, par exemple.

  • Conversions de type:

Lorsque la valeur de l'expression est affectée à la lvalue, la valeur est éventuellement convertie dans le type de la lvalue. On peut par exemple affecter à un flottant, la valeur d'une expression entière.

1.7.2 L'addition

  • Syntaxe:

expression :

) expression, + expression2

  • Sémantique:

Les deux expressions sont évaluées , l'addition réalisée, et la valeur obtenue est la valeur de l'expression d'addition.

L'ordre dans lequel les deux expressions sont évaluées, n'est pas déterminé. Si ex¬pression, et expression2 font des effets de bords, on n'est pas assuréde l'ordre dans lequel ils se feront.

Après évaluation des expressions, il peut y avoir conversion de type de l'un des opérandes, de manière à permettre l'addition. On pourra par exemple faire la somme d'une expression délivrant un réel et d'une expression délivrant un entier.

1.7.3 La soustraction

  • Syntaxe:

L'opérateur peut être utiliséde manière unaire ou binaire:

expression :

) - expression

) expression, - expression2

  • Sémantique:

Les deux expressions sont évaluées, la soustraction réalisée, et la valeur obtenue est la valeur de l'expression soustraction. (La sémantique de - expression est celle de 0 - expression).

Les mêmes remarques concernant l'ordre d'évaluation des opérandes ainsi que les éventuelles conversions de type faites au sujet de l'addition s'appliquent à la sous¬traction.

1.7.4 La multiplication

  • Syntaxe:

expression :

) expression, * expression2

  • Sémantique:

Les deux expressions sont évaluées, la multiplication réalisée, et la valeur obtenue est la valeur de l'expression multiplicative.

Les mêmes remarques concernant l'ordre d'évaluation des opérandes ainsi que les éventuelles conversions de type faites au sujet de l'addition s'appliquent à la multi¬plication.

1.7.5 La division

  • Syntaxe:

expression:

=* expression, / expression2

  • Sémantique:

Contrairement a d'autres langages, le langage C ne dispose que d'une seule notation pour désigner deux opérateurs différents: le signe / désigne à la fois la division entière et la division réelle.

Si expression, et expression2 délivrent deux valeurs entières, alors il s'agit d'une division entière. Si l'une des deux expressions au moins délivre une valeur réelle, il s'agit d'une division réelle.

Dans le cas de la division entière, si les deux opérandes sont positifs, l'arrondi se fait vers zéro, mais si au moins un des deux opérandes est négatif, la façon dont se fait l'arrondi dépend de l'implémentation (mais il est généralement fait zéro). ex: 13 / 2 délivre la valeur 6 et -13 / 2 ou 13 / -2 peuvent délivrer -6 ou -7 mais le résultat sera généralement -6.

Dans le cas de la division réelle, les remarques concernant l'ordre d'évaluation des opérandes ainsi que les éventuelles conversions de type faites au sujet de l'addition s'appliquent également.

1.7.6 L'opérateur modulo

  • Syntaxe:

expression :

=* expression, % expression2

  • Sémantique:

Les deux expressions sont évaluées et doivent délivrer une valeur de type entier, on évalue le reste de la division entière de expression, par expression2 et la valeur obtenue est la valeur de l'expression modulo.

Si au moins un des deux opérandes est négatif, le signe du reste dépend de l'implémentation,

mais il est généralement pris du même signe que le dividende. ex : 13 % 2 délivre 1

-13 % 2 délivre généralement -1 13 % -2 délivre généralement 1

Les choix d'implémentation faits pour les opérateurs division entière et modulo doivent être cohérents, en ce sens que l'expression b * (a / b) + a % b doit avoir pour valeur a.

1.7.7 Les opérateurs de comparaison

  • Syntaxe:

expression :

=* expression, opérateur expression2 oùopérateur peut être l'un des symboles suivants:

...

Chapter 3

Les tableaux

Dans ce chapitre nous allons voir tout d'abord comment déclarer un tableau, comment l'initialiser, comment faire référence à un des ses éléments. Du point de vue algorithmique, quand on utilise des tableaux, on a besoin d'intructions itératives, nous passerons donc en revue l'ensemble des instructions itératives du langage. Nous terminerons par un certains nombre d'opérateurs très utiles pour mettre en oeuvre ces instructions.

3.1 Les tableaux

3.1.1 Déclaration de tableaux dont les éléments ont un type de base

Une déclaration de tableau dont les éléments ont un type de base, a une structure très proche d'une déclaration de variable ayant un type de base. La seule différence consiste à indiquer entre crochets le nombre d'éléments du tableau après le nom de la variable. ex:

int t[10]; /* t tableau de 10 int */

long int t1[10], t2[20]; /* t1 tableau de 10 long int,

t2 tableau de 20 long int */

En pratique, il est recommandéde toujours donner un nom à la constante qui indique le nombre d'éléments d'un tableau.

ex:

#define N 100 int t[N];

Les points importants sont les suivants:

  • les index des éléments d'un tableau vont de 0 à N - 1.
  • la taille d'un tableau doit être connue statiquement par le compilateur. Impossible donc d'écrire:

int t[n];

où n serait une variable.

3.1.2 Initialisation d'un tableau

Lorsqu'un tableau est externe à toute fonction, il est possible de l'initialiser avec une liste d'expressions constantes séparées par des virgules, et entourée des signes { et 1. ex:

#define N 5

int t[N] _ {1, 2, 3, 4, 51;

Il est possible de donner moins d'expressions constantes que le tableau ne comporte d'éléments. Dans ce cas, les premiers éléments du tableau seront initialisés avec les valeurs indiquées, les autres seront initialisés à zéro.

ex:

#define N 10

int t[N] _ {1, 21;

Les éléments d'indice 0 et 1 seront initialisés respectivement avec les valeurs 1 et 2, les autres éléments seront initialisés à zéro.

Il n'existe malheureusement pas de facteur de répétition, permettant d'exprimer "ini-tialiser n éléments avec la même valeur v". Il faut soit mettre n fois la valeur v dans l'initialiseur, soit initialiser le tableau par des instructions.

Cas particulier des tableaux de caractères

Un tableau de caractères peut être initialiséselon la même technique. On peut écrire par exemple:

char ch[3] _ {'a', 'b', 'c'1;

Comme cette méthode est extrêmement lourde, le langage C a prévu la possibilitéd'initialiser un tableau de caractères à l'aide d'une chaine littérale. Par exemple: char ch[8] _ "exemple";

On se rappelle que le compilateur complète toute chaine littérale avec un caractère pull, il faut donc que le tableau ait au moins un élément de plus que le nombre de caractères écrits par le programmeur dans la chaine littérale.

Il est admissible que la taille déclarée pour le tableau soit supérieure à la taille de la chaine littérale.

ex:

char ch[100] _ "exemple";

dans ce cas, seuls les 8 premiers caractères de ch seront initialisés.

Il est également possible de ne pas indiquer la taille du tableau, et dans ce cas, le compilateur a le bon goût de compter le nombre de caractères de la chaine littérale et de donner cette taille au tableau.

ex:

char ch[] _ "ch aura 22 caracteres";

3.2. LES INSTRUCTIONS ITÉRATIVES 27

3.1.3 Référence à un élément d'un tableau

  • Syntaxe:

Dans sa forme la plus simple, une référence à un élément de tableau a la syntaxe suivante:

expression :

) nom-de-tableau [ expression, ]

  • Sémantique: expression, doit délivrer une valeur entière, et l'expression délivre l'élément d'indice expression, du tableau. Une telle expression est une lvalue, on peut donc la rencontrer aussi bien en partie gauche qu'en partie droite d'affectation.

ex:

Dans le contexte de la déclaration:

#define N 10 int t[N];

on peut écrire:

x = t[i]; /* reference a l'element d'indice i du tableau t */

t[i+j] = k; /* affectation de l'element d'indice i+j du tableau t */

3.2 Les instructions itératives

3.2.1 Instruction for

  • Syntaxe:

instruction :

) for ( expression, option ; expression2 option ; expression3 option ) instruction

  • Sémantique: l'exécution réalisée correspond à l'organigramme suivant:

expression1

Lorsque l'on omet expression1 et/ou expression2 et/ou expression3, la sémantique est celle de l'organigramme précédent, auquel on a enlevéla ou les parties correspondantes.

Remarques

On voit que la vocation de expression1 et expression3 est de réaliser des effets de bord, puisque leur valeur est inutilisée. Leur fonction logique est d'être respectivement les parties initialisation et itération de la boucle. expresion2 est elle utilisée pour le test de bouclage. instruction est le travail de la boucle.

Exemple de boucle for initialisation d'un tableau

#define N 10 int t[N];

for (i = 0; i < N; i = i + 1) t[i] = 0;

3.2.2 Instruction while

  • Syntaxe:

instruction :

=:> while ( expression ) instruction

  • Sémantique:

l'exécution réalisée correspond à l'organigramme suivant:

 Il

non

oui

Il

instruction

3.2.3 Instruction do

  • Syntaxe:

instruction :

) do instruction while ( expression ) ;

  • Sémantique:

l'exécution réalisée correspond à l'organigramme suivant:

Il

3.2.4 Instruction break

  • Syntaxe:

instruction :

) break ;

  • Sémantique:

Provoque l'arrêt de la première instruction for, while, do englobante.

Exemple

L'instruction for ci-dessous est stoppée au premier i tel que t[i] est nul:

for (i = 0; i < N; i = i + 1) if (t[i] == 0) break;

3.2.5 Instruction continue

  • Syntaxe:

instruction :

=:> continue ;

  • Sémantique:

Dans une instruction for, while ou do, l'instruction continue provoque l'arrêt de l'itération courante, et le passage au début de l'itération suivante.

Exemple

Supposons que l'on parcoure un tableau t à la recherche d'un élément satisfaisant une certaine condition algorithmiquement complexe à écrire, mais que l'on sache qu'une valeur négative ne peut pas convenir:

for (i = 0; i < N; i = i + 1) {

if (t[i] < 0 ) continue; /* on passe au i suivant dans le for */

~~~ /* algorithme de choix */

1

3.3 Les opérateurs

3.3.1 Opérateur préet postincrément

Le langage C offre un opérateur d'incrémentation qui peut être utilisésoit de manière préfixé, soit de manière postfixé. Cet opérateur se note ++ et s'applique à une lvalue. Sa syntaxe d'utilisation est donc au choix, soit ++ lvalue (utilisation en préfixé), soit lvalue ++ (utilisation en postfixé).

Tout comme l'opérateur d'affectation, l'opérateur d'incrémentation réalise à la fois un effet de bord et délivre une valeur.

++ lvalue incrémente lvalue de 1 et délivre cette nouvelle valeur.

lvalue ++ incrémente lvalue de 1 et délivre la valeur initiale de lvalue.

Exemples:

Soient i un int et t un tableau de int:

i = 0:     

t[i++] = 0; /* met a zero l'element d'indice 0 */

t[i++] = 0; /* met a zero l'element d'indice 1 */

t[++i] = 0; /* met a zero l'element d'indice 2 */

t[++i] = 0; /* met a zero l'element d'indice 3 */

3.3.2 Opérateur préet postdécrement

Il existe également un opérateur de décrémentation qui partage avec l'opérateur incrément les caracteristiques suivantes:

  1. il peut s'utiliser en préfixe ou en postfixé,
  2. il s'applique à une lvalue,
  3. il fait un effet de bord et délivre une valeur.

Cet opérateur se note -- et décrémente la lvalue de 1.

-- lvalue décrémente lvalue de 1 et délivre cette nouvelle valeur. lvalue -- décrémente lvalue de 1 et délivre la valeur initiale de lvalue. Exemples: Soient i un int et t un tableau de int:

i = 9;     

t[i--] = 0; /* met a zero l'element d'indice 9 */

t[i--] = 0; /* met a zero l'element d'indice 8 */

i = 8;     

t[--i] = 0; /* met a zero l'element d'indice 7 */

t[--i] = 0; /* met a zero l'element d'indice 6 */

3.3.3 Quelques utilisations typiques de ces opérateurs Utilisation dans les instructions expression

On a vu qu'une des formes d'instruction possibles en C est:

expresssion ;

et que cela n'a de sens que si l'expression réalise un effet de bord.

Les opérateurs ++ et -- réalisant précisément un effet de bord, permettent donc d'écrire des instructions se réduisant à une expression utilisant un de ces opérateurs.

Une incrémentation ou une décrémentation de variable se fait classiquement en C de la manière suivante:

i++; /* incrementation de i */

j--; /* decrementation de j */

Utilisation dans les instructions itératives

Une boucle for de parcours de tableau s'écrit typiquement de la manière suivante:

for (i = 0; i < N; i++) {

3.3.4 Opérateur et logique

  • Syntaxe:

expression:

=* expression1 && expression2

  • Sémantique:

expression1 est évaluée et:

  1. si sa valeur est nulle, l'expression && rend la valeur 0
  2. si sa valeur est non nulle, expression2 est évaluée, et l'expression && rend la valeur 0 si expression2 est nulle, et 1 sinon.

On voit donc que l'opérateur && réalise le et logique de expression1 et expression2 (en prenant pour faux la valeur 0, et pour vrai toute valeur différente de 0).

Remarque sur la sémantique

On a la certitude que expression2 ne sera pas évaluée si expression1 rend la valeur faux. Ceci présente un intérêt dans certains cas de parcours de tableau ou de liste de blocs chainés.

Par exemple dans le cas d'un parcours de tableau à la recherche d'un élément ayant une valeur particulière, supposons que l'on utilise comme test de fin de boucle l'expression

i < n && t[i] == 234

oùi < n est le test permettant de ne pas déborder du tableau, et t[i] == 234 est le test de l'élément recherché. S'il n'existe dans le tableau aucun élément satisfaisant le test t[i] == 234, il va arriver un moment oùon va évaluer i < n && t[i] == 234 avec i > n et la sémantique de l'opérateur && assure que l'expression t[i] == 234 ne sera pas

évaluée. On ne cours donc pas le risque d'avoir une erreur matérielle dans la mesure oùt[i+1] peut référencer une adresse mémoire invalide. Exemples d'utilisation

int a,b; 

if (a > 32 && b < 64) ...

if ( a && b > 1) ...

b = (a > 32 && b < 64);

3.3.5 Opérateur ou logique

  • Syntaxe:

expression :

=* expression1 expression2

  • Sémantique:

expression1 est évaluée et:

— si sa valeur est non nulle, l'expression || délivre la valeur 1.

— sinon, expression2 est évaluée, si sa valeur est nulle, l'expression || délivre la valeur 0 sinon elle délivre la valeur 1.

On voit donc que l'opérateur || réalise le ou logique de ses opérandes, toujours avec les mêmes conventions pour les valeurs vrai et faux, à savoir 0 pour faux, et n'importe quelle valeur non nulle pour vrai.

Dans ce cas également, on a la certitude que le second opérande ne sera pas évaluési le premier délivre la valeur vrai.

Exemples

int a,b;

if (a > 32 || b < 64) ...

if ( a || b > 1)

b = (a > 32 || b < 64);

3.3.6 Opérateur non logique

  • Syntaxe:

expression :

~ ! expression

  • Sémantique:

expression est évaluée, si sa valeur est nulle, l'opérateur ! délivre la valeur 1, sinon il délivre la valeur 0.

Cet opérateur réalise le non logique de son opérande.

3.4 Exercice

Déclarer un tableau nb_jour qui doit être initialiséde façon à ce que nb_jour[i] soit égal au nombre de jours du i` mois de l'année pour i allant de 1 à 12 (nb_jour[0] sera inutilisé).

Ecrire une procédure d'initialisation de nb_jour qui utilisera l'algorithme suivant:

  • si i vaut 2 le nombre de jours est 28
  • sinon si i pair et i <= 7 ou i impair et i > 7 le nombre de jours est 30
  • sinon le nombre de jours est 31

Ecrire une procédure d'impression des 12 valeurs utiles de nb_jour. La procédure main se contentera d'appeler les procédures d'initialisation et d'impression de nb_jour.


104