SUPPORT DE COURS. Langage C
|
|
|
- Marianne Nadeau
- il y a 10 ans
- Total affichages :
Transcription
1 Dpt Informatique SUPPORT DE COURS Langage C Semestre 1 par : «CaDePe» Marie-Françoise Canut Marianne de Michiel André Péninou
2 Table des Matières 1 Généralités Introduction aux langages de programmation Historique du langage C Caractéristiques du langage C Forme générale d'un programme C Structure d'un programme C Commentaires Inclusion de fichiers Programme principal Structure d'une fonction Structure d'un bloc Structure d'une instruction élémentaire Règles d'écriture des programmes C Exemple de programme Eléments de base du langage C : le minimum pour débuter Exemple type d'un fichier de programme C Les littéraux Les variables Les types numériques Les identificateurs Déclaration simplifiée de variable Exemples Les instructions élémentaires Instruction d affectation d une valeur à une variable Expressions arithmétiques Opérateurs arithmétiques Ecriture d'une expression arithmétique Fonctions d'entrées/sorties Entrée de données Sortie de données Structure de contrôle : le if Syntaxe de l alternative : if Expressions conditionnelles Opérateurs conditionnels Ecriture d'une expression conditionnelle Structures de Contrôle Formes d'instructions Structure de contrôle : le if Structure de contrôle : le while Structure de contrôle : le do while Structure de contrôle : le for Exemple de programme C avec boucles Structure de contrôle : le switch
3 4 Constantes, variables et types Les types numériques et les littéraux associés Les types numériques entiers signés Les types numériques entiers non signés Les types numériques en virgule flottante Relations entre entiers et flottants Le type booléen Le type booléen en langage C Le type booléen en langage C Caractères : le type char Définition du type char Chaînes de caractères Déclaration de variables Les identificateurs Les mots clés réservés Déclaration de variable Expressions en langage C Généralités Opérateurs arithmétiques Expressions arithmétiques Opérateurs de modification de valeur d une variable Instruction d affectation d une valeur à une variable Affectations relatives Incrémentation, décrémentation Pré-incrémentation, pré-décrémentation Post-incrémentation, post-décrémentation Opérateurs relationnels et logiques Expressions logiques Priorités des opérateurs Evaluation des expressions Conversions/Promotions de type (cast) et opérations de base du processeur Evaluation d'une affectation Evaluation des expressions Règle d'évaluation des expressions Exemples Quelques conversions implicites automatiques Conversions explicites Le type char dans les expressions Utilisation du type char Evaluation, par le compilateur, des expressions utilisant le type char Conversion char vers int Conversion int vers char Problèmes de calculs limites sur les types lors de l évaluation d une expression Débordement mémoire / dépassement de capacité La capacité des flottants Calculs arrêtant le programme Opérateurs supplémentaires L opérateur de taille : sizeof Opérateur conditionnel
4 6 Les tableaux Exemple de programme utilisant un tableau Définitions Déclaration d un tableau Utilisation d'un tableau Opérateurs et instructions applicables globalement à un tableau Opérateurs et instructions applicables aux éléments d'un tableau Accès aux éléments Opérations valides sur les éléments d'un tableau Déclaration de la taille d'un tableau, directive #define Compilateur et taille d un tableau Initialisation des éléments d'un tableau Tableaux à plusieurs dimensions Sous-Programmes en langage C Rappels et généralités Déclaration d'une fonction C Déclaration ou prototype ou en-tête Commentaire des en-têtes de fonctions C Syntaxe de déclaration et mode de passage des paramètres formels Déclaration des paramètres et passage par valeur pour les types de base (non tableau) Déclaration des paramètres et passage par référence pour les types de base (non tableau) Le type référence en langage C Déclaration des paramètres formels par référence pour les types de base (non tableau) Remarque sur le passage par référence en C++ et le langage C «pur» Déclaration et passage de paramètre particulier : le type tableau Particularité du type tableau Déclaration des paramètres formels de type tableau Fonction C sans paramètre formel Valeur renvoyée ou non par une fonction C Déclaration d une fonction C avec valeur de retour Déclaration d une fonction C sans valeur de retour Définition d une fonction C Programmation d algorithmes de sous-programmes en langage C Passage d'une fonction algorithmique à une fonction C Passage d'une procédure algorithmique à une fonction C Appel d'une fonction C Déclaration et passage des paramètres effectifs de type de base Déclaration et passage des paramètres effectifs tableaux Particularité des fonctions C ayant pour paramètre un tableau Appel d une fonction C Appel d une fonction C avec valeur de retour Appel d une fonction sans valeur de retour Une fonction C particulière : main Exemple de programme C utilisant des fonctions algorithmiques Exemple de programme C utilisant des procédures algorithmiques Appels de fonctions : gestion de la mémoire Déclaration et domaine de visibilité des variables dans les fonctions C Espaces mémoires réservés à un programme Contexte d exécution de fonction Utilisation de la mémoire
5 8 Chaînes de Caractères Introduction et conventions concernant les chaînes de caractères Introduction aux chaînes de caractères Exemple de programme utilisant des chaînes de caractères en langage C Convention concernant les chaînes de caractères en langage C Préambule Convention pour les chaînes de caractères en langage C Manipulation de chaînes de caractères Déclaration d'une variable pour manipuler une chaîne de caractères Traitement des chaînes de caractères Saisie et affichage de chaînes de caractères Saisie de chaînes de caractères avec bcget Affichage de chaînes de caractères avec bcdisplay Quelques fonctions de manipulation de chaînes de caractères Longueur d'une chaîne de caractères : strlen Recopie d'une chaîne de caractères : strcpy Comparaison de chaînes de caractères : strcmp Fonctions C de manipulation de chaînes de caractères Fonctions classiques Fonctions de manipulation de parties de chaînes Remarques concernant les en-têtes de fonctions de la bibliothèque string.h données dans ce document Valeurs de type int de certains en-têtes Valeurs renvoyées par les fonctions Paramètres en entrée des fonctions Initialisation d'une chaîne de caractères Les enregistrements Introduction aux enregistrements Déclaration d'un type structure : struct Syntaxe de déclaration d un type structure Types possibles pour les champs d'une structure Utilisation de variables de type structure Déclaration de variable simple de type structure Accès aux champs d'une variable de type structure : opérateur "." Opérations applicables globalement à une variable de type structure Exemple simple de programme utilisant les structures Structures imbriquées Tableaux et structures Tableaux de structures Structures dont un champ est un tableau Structures dont un champ est un tableau de structures Fonctions et structures Passage de paramètre de type structure Passage de paramètre de type structure par valeur Fonctions retournant une structure Passage de paramètre de type structure par référence Passage de paramètre d un tableau de structures Surnommage de type : l'opérateur typedef Autre mode de déclaration des types structures Déclaration sans typedef d un type structure
6 Déclaration de variables de type structure Manipulation d'une variable de type structure Tableaux de structures Fonctions et structures Compilation séparée Modules Introduction Eléments de qualité des logiciels Découpage d une application La notion de module Définition de la notion de module Liens «utilise»/«est client de» entre modules : l interface de module Implémentation de module Intérêt de la notion de module Module == Abstraction == Réduction de complexité Objectifs de la programmation modulaire Règles de programmation d un module Mise en œuvre de modules en langage C Définition de module en langage C Utilisation de module en langage C Exemple de module et utilisation Elément de syntaxe spécifique pour les fichiers ".h" : clause #ifndef Principes de compilation du langage C Principe des étapes de compilation/édition de liens Pré-processing d un fichier C Compilation de code source C Edition de liens / Génération de code Exemple d étapes de compilation/édition de liens Les objets interprétation - adresse Lvalue/Rvalue Obtenir l adresse mémoire d une variable Les sorties d une fonction font référence à des Lvalue, les entrées à des Rvalue Les pointeurs Définition Déclaration d'une variable pointeur sur un objet d'un type donné Opérateurs et instructions applicables à un pointeur Affectation à un pointeur Initialisation d'un pointeur par valeur NULL Initialisation d'un pointeur par obtention de l'adresse d'une variable typée Initialisation d'un pointeur par affectation d'un autre pointeur Comparaison de pointeurs Affichage de pointeurs Opérateurs et instructions applicables à une zone désignée par un pointeur Utilisation de la zone pointée : opérateur d accès à l emplacement mémoire pointé (*) Opérations applicables à une zone désignée par un pointeur Entrées / Sorties sur une zone désignée par un pointeur Tableaux de bas niveau et pointeurs
7 Rappel sur les tableaux Arithmétique des pointeurs L opérateur [] Exemple Similarités et différences entre tableaux et pointeurs Déclaration des tableaux en paramètres formels de sous-programmes Tableau et retour de fonction L opérateur de taille : sizeof et les tableaux ou pointeurs Opérateur -> accès aux champs d une structure au travers d un pointeur Synthèse concernant les opérateurs & et * Les entrées/sorties formatées Affichage à l écran sortie écran (sur stdout) : printf Format d un spécifieur Exemples Principe des sorties bufferisées formatées Lecture au clavier (sur stdin) : scanf Principe des entrées bufferisées formatées Chargement du buffer Recherche du premier caractère correspondant au début d une donnée Vidage du buffer associé à un stream : fflush Utilisation de primitives de gestion de fichiers de haut niveau en langage C Caractéristiques principales d un fichier Modes d opérations sur les fichiers Manipulation d un fichier Le type de variable descripteur de fichier en C : FILE Le type prédéfini FILE, déclaration de variable fichier Modes de codage Ouverture de fichier : fopen Fermeture de fichier : fclose Fonction de lecture binaire dans un fichier Fonction d écriture binaire dans un fichier Contrôle d'accès aux fichiers Test de fin de fichier en lecture : feof Détection des erreurs d'accès aux fichiers : ferror fichiers de structures Fichiers de caractères Lecture : fgetc Ecriture : fputc Fichiers de chaînes de caractères Lecture : fgets Ecriture : fputs Fichiers de données formatées Gestion du curseur de fichier Fonction de déplacement du curseur dans un fichier : fseek Fonction donnant la position courante du curseur dans un fichier : ftell
8 Notations utilisées dans le document Le texte normal se trouve formaté comme cette ligne, le gras étant utilisé pour attirer l attention. Le texte formaté dans la police suivante correspondra à du code source en langage C. Ex : cf bcdisplayln ("Bonjour"); La rubrique «Exemple» ou «Ex» introduira les exemples donnés. Les rubriques «Attention» et «Remarque» introduiront des paragraphes auxquels le lecteur est invité à porter une attention toute particulière. La rubrique «Syntaxe» introduira des éléments spécifiant la syntaxe (ou grammaire) du langage C. Dans ce contexte, de façon générale : la police utilisée est celle du code source en langage C, des «crochets» (" "," ") introduiront des éléments optionnels, des " " indiqueront une répétition d éléments, les éléments indiqués en gras seront des éléments obligatoires de syntaxe (mots clés, marqueurs, ). les éléments en mode normal seront en général du code source à définir par l utilisateur. Exemple pour la syntaxe de déclaration des variables : type ident1, ident2 ; type, ident1 et ident2 en police normale : à définir par l utilisateur,, et ; en gras : sont des éléments de syntaxe,, ident2 entre : indique que la séquence ", ident2" n est pas obligatoire,, ident2 avec les : indique que la séquence ", ident2" peut se répéter, Exemple d instruction correcte par rapport à la syntaxe : int v1, v2, v3 ; type -> int ident1 -> v1 «, ident2» ->, v2 répétition de «, ident2» ->, v3 ; float f ; type -> float ident1 -> f «, ident2» -> optionnelle et non utilisée ; Contre exemple : float ; -> il manque ident1 int v1 v2 ; -> il manque la virgule après v1 pour former la séquence ident1, ident2 7
9 1 Généralités 1.1 Introduction aux langages de programmation Un langage de programmation est un langage «compréhensible» par un ordinateur. Il permet, pour résoudre un problème, de décrire les informations à traiter et les traitements à effectuer sur ces informations. Un langage définit : Une syntaxe (grammaire) avec des mots clés (du langage) : o pour déclarer des variables, o pour définir les instructions valides. Une sémantique indiquant comment sont «interprétées»/«comprises» les instructions : o en particulier les types existants et leur mode de codage, o les règles d évaluation des instructions (calculs entre types, appels de sousprogrammes, ). Ce document proposera de donner toujours des éléments de syntaxe et certains éléments de sémantique associés. Cette dualité syntaxe/sémantique est très importante car on peut trouver des éléments de syntaxe identiques qui peuvent avoir des significations très différentes selon le contexte (c est-à-dire selon «l endroit» du programme où il sont utilisés ou bien selon la façon dont on les combine avec d autres éléments de syntaxe). Le mot programme peut recouvrir, à l IUT, deux sens distincts : a) l ensemble des cours que vous suivrez durant les deux années (programme de cours), b) le listing d un algorithme traduit dans un langage de programmation (programme C). Seul le contexte peut permettre de choisir entre les deux interprétations possibles. Les éléments importants d'un langage de programmation : les types de données de base existants et les opérations qui leur sont applicables (entier, réel, ), les variables et l opération d'affectation, l écriture des expressions (calculs, conditions), les structures de contrôle disponibles, les entrées/sorties utilisateur de base, ainsi que : la gestion des tableaux : déclaration/manipulation, la gestion des chaînes de caractères, la définition et utilisation de sous-programmes, les types construits disponibles (enregistrements, ), ainsi que : la gestion de fichiers, les bibliothèques (librairies) disponibles. 8
10 Il faut différencier programmation et algorithmique : l algorithmique consiste à concevoir une solution aux traitements à mettre en œuvre pour résoudre un problème : données d'entrée, traitements, résultats en sortie. la programmation consiste à réaliser un programme mettant en œuvre un algorithme : problèmes de types de données sur ordinateur, entrées/sorties agréables pour l'utilisateur, Une approche «naïve» consiste à lier les deux activités et les mener de façon quasi-conjointe. Il faut cependant séparer les deux aspects : conception de la solution, mise en œuvre. 1.2 Historique du langage C Le langage C a été conçu à l'origine pour l'écriture du système d'exploitation UNIX (90-95% du noyau écrit en C) et s'est vite imposé comme le langage de programmation sous UNIX. Très inspiré des langages BCPL (Martin Richard) et B (Ken Thompson), il se présente comme un "superassembleur" ou "assembleur portable". En fait c'est un compromis entre un langage de haut niveau (Pascal, Ada ) et un langage de bas niveau (assembleur). Il a été normalisé en 1989 par le comité X3J 11 de l' American National Standards Institute (ANSI). Quelques dates clés : 1969 : invention du système UNIX par Ken Thomson et Dennis Ritchie des Bells Laboratories : langage B par Ken Thomson, inspiré du BPCL de Martin Richard (1967). Possède les instructions contrôlées, la gestion des pointeurs, les variables dynamiques, la récursivité : langage C par Dennis Ritchie et Brian Kernighan. Langage faiblement typé : le système UNIX écrit à 90% en C. L'évolution de C suit celle d'unix. Depuis 1984 (system V d'unix) : le langage C évolue séparément. On le trouve maintenant implanté sur de nombreux systèmes, avec des versions plus ou moins conviviales. Normalisation fin 1988 : le langage ANSI C (quelques évolutions très mineures depuis). Evolution plus récente : C Caractéristiques du langage C Le langage C est un langage structuré conçu pour traiter les tâches d'un programme en les mettant dans des blocs. Il produit des programmes efficaces : il possède les mêmes possibilités de contrôle de la machine que l'assembleur. Sa spécificité vient de son traitement des pointeurs et à son aptitude à générer un code compact et rapide. Le langage C est : Déclaratif : tout objet C doit être déclaré avant d'être utilisé. Offre un format libre : la mise en page des divers composants d'un programme est totalement libre. Cette possibilité doit être exploitée pour rendre les programmes lisibles. Modulaire : une application pourra être découpée en modules qui pourront être compilés séparément. Un ensemble de programmes déjà opérationnels pourra être réuni dans une librairie. Cette aptitude permet au langage C de se développer de lui-même (s auto-développer). Souple : peu de vérifications et d'interdits d où une attention particulière à apporter lors du développement. 9
11 Econome : des éléments de syntaxe identiques peuvent avoir des significations très différentes selon le contexte (les symboles «*» et «&» en particulier). Transportable : les entrées/sorties sont réunies dans une librairie externe au langage. 1.4 Forme générale d'un programme C Structure d'un programme C Le code source d une application écrite en C est composé d un ou plusieurs fichiers texte. Le langage C est sensible à la casse (majuscules, minuscules). Un programme C est composé : De directives du préprocesseur Une directive du préprocesseur est une ligne de programme source commençant par le caractère dièse (#). Ce n est pas du code en langage C. Elle permet d'effectuer des manipulations sur le texte du programme source avant la compilation (par exemple : inclusion de fichiers, substitutions, macros, compilation conditionnelle). Le préprocesseur, qui est chargé «d enlever» et de traiter ces directives, est appelé automatiquement par la commande de compilation. De déclarations/définitions Déclaration : la déclaration d'un objet C donne simplement ses caractéristiques au compilateur et ne génère aucun code. Définition : la définition d'un objet C déclare cet objet et crée effectivement cet objet. De fonctions Ce sont des sous-programmes dont les instructions vont définir un traitement sur des variables. Elles peuvent retourner une valeur à la fonction appelante. Le programme principal est une fonction particulière dont le nom doit impérativement être main. Les fonctions ne peuvent pas être imbriquées Commentaires Les commentaires sont des parties de texte à destination du programmeur et ignorées par le compilateur, elles seront «enlevées» avant compilation réelle. Ce sont des textes, sur plusieurs lignes éventuellement, compris entre /* et */. On ne doit pas les imbriquer et ils peuvent apparaître en tout point d'un programme. On peut aussi utiliser le double slash (//) pour un commentaire uniligne (syntaxe spécifique C++ admise aujourd hui très répandue). Exemples : /* Ceci est un commentaire */ int v ; // Partie commentaire Inclusion de fichiers Un programme C utilise des fonctions définies dans des fichiers séparés. Ces fichiers sont utilisés lors de la compilation. Il faut l indiquer en réalisant l inclusion des fichiers. 10
12 #include <bcio.h> Le fichier est cherché dans un répertoire déterminé à l avance et paramétré dans le compilateur (comme : P:\Mingw\iut\include pour bcio). Cette macro-commande est utilisée pour les bibliothèques (librairies) du compilateur. Dans l exemple, bcio.h définit les fonctions d'entrées/sorties (bcget et bcdisplay) Programme principal Fonction principale appelée main, point d'entrée dans le programme Prototype (en-tête) : int main (void) Le main est déclaré de type int car renvoie un code état de fonctionnement (0 = OK, cf. cours système). Le "return ;" à la fin est obligatoire. En général, on met "return 0 ;" car on suppose que le programme se termine bien. On y trouve 2 parties bien distinctes : les déclarations des variables (au début), le code du programme à exécuter. Le début du code du programme à exécuter signale implicitement la fin des déclarations de variables Structure d'une fonction Une fonction est un bloc de code d'une ou plusieurs instructions qui peut renvoyer une valeur à l'expression qui l'utilise. Sa forme la plus simple est : Syntaxe type nom ( paramètres_formels ) // Corps de la fonction Structure d'un bloc Exemple int max ( int x, int y ) if (x > y) return x ; return y ; Un bloc est une suite d'instructions élémentaires délimitées par des accolades et. Un bloc peut commencer par des déclarations/définitions d'objets qui seront locaux à ce bloc. Un bloc peut contenir un ou plusieurs blocs inclus Structure d'une instruction élémentaire Une instruction élémentaire est une expression terminée par le caractère ; (point virgule). 1.5 Règles d'écriture des programmes C Afin d'écrire des programmes C lisibles, il est important de respecter un certain nombre de règles de présentation : Ne jamais placer plusieurs instructions sur une même ligne. Utiliser des identificateurs significatifs. 11
13 Grâce à l'indentation 1 des lignes, on fera ressortir la structure syntaxique du programme. Le décalage de chaque niveau sera d une tabulation. On laissera une ligne blanche entre la dernière ligne des déclarations et la première ligne des instructions. Une accolade fermante est seule sur une ligne 2 et fait référence, par sa position horizontale, au début du bloc qu'elle ferme. Aérer les lignes de programme en entourant par exemple les opérateurs avec des espaces. Il est nécessaire de commenter les programmes. Eviter les commentaires triviaux. 1.6 Exemple de programme Voici un exemple de programme C : #include <stdio.h> // Directives du préprocesseur (inclusions) #include <bcio.h> int main (void) // Programme principal // Début du bloc main int j; // Définiton de variables bcdisplayln (" Bonjour"); // Instructions bcdisplayln (" A tous"); printf(" ReBonjour\n A tous\n"); for (j=1; j<=10; j++) printf("et voila (%d eme fois)\n", j); bcgetpause(); return 0; Et le résultat de son exécution : Bonjour A tous ReBonjour A tous Et voila (1 eme fois) Et voila (2 eme fois) Et voila (3 eme fois) Et voila (4 eme fois) Et voila (5 eme fois) Et voila (6 eme fois) Et voila (7 eme fois) Et voila (8 eme fois) Et voila (9 eme fois) Et voila (10 eme fois) Appuyez sur Entree pour continuer Contexte main j int Le schéma ci-dessus représente le contexte du main (occupation mémoire pendant la durée d exécution du programme). Il est créé lors de l appel du main et est supprimé à la fin du main. La réservation de mémoire faite lors de la définition de la variable j est la seule réservation. j est le nom associé à cet espace. Avant le for, l espace réservé n est pas initialisé ( ). Dans le for, il contiendra les valeurs successives de j. Le type de données que l on pourra stocker dans cet espace est int. 1 décalage horizontal des lignes. 2 à l'exception de l'accolade fermante du bloc de la structure do... while. 12
14 2 Eléments de base du langage C : le minimum pour débuter 2.1 Exemple type d'un fichier de programme C On retrouve, dans l exemple suivant : 1) les directives d inclusion : #include, 2) la définition du programme principal (int main (void) ), 3) le début du main ("", 4) la déclaration des variables (cf. commentaire), 5) le code à exécuter (cf. commentaire), 6) la fin du main (""). /* Inclusion de déclarations externes */ #include <bcio.h> /* Situées dans d'autres fichiers */ /* Programme calculant la somme, la différence et la moyenne de deux entiers */ int main (void) /* Point d'entrée du programme */ /* Déclaration des variables */ // v1, v2 : les deux valeurs saisies int v1, v2; // somvals : somme des deux valeurs // diffvals : différence des deux valeurs int somvals, diffvals; // moyvals : moyenne des deux valeurs float moyvals ; Contexte main V1 V2 somvals diffvals int int int /* Instructions du programme */ // Saisie des deux valeurs bcdisplay ("Première valeur "); bcget (v1); bcdisplay ("Deuxième valeur "); bcget (v2); moyvals int float // Calculs : somme, différence et moyenne somvals = v1 + v2; diffvals = v1 - v2; moyvals = v1 + v2; moyvals = moyvals / 2; // Affichage des résultats bcdisplayln("somme : ", somvals); /* Affichage de la valeur d'une variable */ bcdisplayln("différence : ", diffvals); bcdisplayln("moyenne : ", moyvals); bcgetpause (); return 0; 13
15 2.2 Les littéraux Un littéral est une valeur notée littéralement. Exemples : Littéraux entiers : Littéraux flottants : E F 10.5f E10 Littéral caractère : c Littéral chaîne de caractères : "Somme : " 2.3 Les variables Une variable est l association : d'un identificateur («nom») + un type + une zone mémoire repérée par une adresse + une valeur (contenue dans la zone mémoire) Les types numériques Le codage et surtout la taille des types peuvent dépendre de la machine cible et du compilateur. int : entier signé codé sur au moins 2 octets. o Pour nous : 4 octets donc de à float : réel signé en virgule flottante codé sur au moins 4 octets (simple précision). o Pour nous 4 octets : +/- 3.4 * (10**-38) à +/- 3.4 * (10**+38) Les identificateurs Les identificateurs permettent d identifier les variables, les constantes, les fonctions et les types définis par l utilisateur. Règle de construction : un identificateur est constitué d une suite de caractères, commençant par une lettre, et pouvant contenir des lettres, des chiffres ou le caractère _ (underscore ou tiret bas). Tout compilateur doit reconnaître 31 caractères significatifs. Attention, le langage C différencie les majuscules et les minuscules. Règles pour construire des identificateurs de variables significatifs : mots significatifs, doivent commencer par une lettre et sont en minuscules, mots intérieurs commencent par une majuscule. tauxreponsemax Déclaration simplifiée de variable Syntaxe : type ident1, ident2 ; La déclaration d une variable est obligatoire avant toute utilisation, en début de fonction (main par exemple), avant le code qui va l utiliser. La déclaration est obligatoire et doit spécifier le type de la variable car cela permet au compilateur : de connaître son mode de codage (type), de connaître l'identificateur qui permet de la différencier de toutes les autres dans le code, de lui associer un emplacement mémoire (objet mémoire). 14
16 2.3.4 Exemples int indice, nombre_d_elements, coefficient ; float note, somme; int i, j; 2.4 Les instructions élémentaires Une instruction élémentaire est une expression terminée par le caractère ; (point virgule) Instruction d affectation d une valeur à une variable Syntaxe : identificateur = expression ; Attention : en algorithmique, le signe "=" représente l opérateur de test d égalité, ici c'est l'affectation! Ex : cf. code Expressions arithmétiques Opérateurs arithmétiques Syntaxe : Opérateurs arithmétiques + (addition), - (soustraction), * (multiplication), / (division) % (modulo sur les entiers seulement) Attention : le calcul se fait dans le type des opérandes. C est-à-dire que l opération réellement réalisée, au niveau binaire, n est pas la même en fonction du type des opérandes qui entourent l opérateur (cf. cours architecture). le / sur deux int est la division entière. donc int i = 5 / 3; /* Donnera 1 en int */ Le choix de l opération à réaliser revient au compilateur. Ceci explique l importance de déclarer le type d une variable avant toute utilisation : le compilateur pourra ainsi effectuer les «bonnes» opérations en fonction du type des opérandes. Dans l exemple du paragraphe 2.1, dans la ligne : somvals = v1 + v2; l addition "v1 + v2" est interprétée comme l addition de deux int en se référant à la déclaration des variables v1 et v Ecriture d'une expression arithmétique Syntaxe : une expression arithmétique est une combinaison de littéraux et/ou de variables avec des opérateurs arithmétiques et éventuellement des parenthèses. Exemple d expressions arithmétiques : i * j + 3 // Interprété comme (i*j)+3 (v1 + v2) / 2 Règles simplifiées de grammaire pour la construction d une expression arithmétique : terme -> variable littéral exparith -> terme exparith oparith exparith (exparith) 15
17 2.5 Fonctions d'entrées/sorties Pour nous, il existera deux types de fonctions d entrées/sorties : les entrées/sorties standards du langage C (bibliothèque stdio) : scanf, printf, les entrées/sorties non standards, spécifiques à l IUT, utilisées pour simplifier les programmes dans un premier temps de l apprentissage (bibliothèque bcio). Toutes ces fonctions d entrées/sorties commencent par bc : bcget, bcdisplay, bcdisplayln, bcgetpause. Attention : la bibliothèque bcio que nous utiliserons largement au départ, n est pas standard au langage C. Les fonctions qu elle fournit seront enlevées au cours de l'année Entrée de données Les fonctions d'entrées permettent de récupérer la saisie d'une valeur faite par l'utilisateur au clavier. Syntaxe : bcget ( identificateurdevariable ) ; bcget (v1); Nom : bcget (bibliothèque bcio) Description : permet de mettre le programme en attente d une entrée (saisie) utilisateur. La valeur entrée sera donnée en résultat dans identificateurdevariable. Cet identificateur doit correspondre à une variable du programme utilisant bcget. La fonction existe pour tous les types existants et pouvant être «saisis». Précondition/Commentaire : la valeur contenue dans identificateurdevariable avant l entrée sera perdue (écrasée par la valeur entrée). De façon générale, les fonctions d entrée de données permettent : d interrompre momentanément le programme au niveau de l instruction d entrée, d attendre la frappe au clavier, par l utilisateur, des caractères constituant une valeur et la frappe de la touche «return» ou «retour Chariot», signalant ainsi la fin de la saisie des données, de convertir correctement les caractères tapés vers la valeur binaire correspondante, si la conversion est possible (exemple : convertir les caractères 1, 2, 3 en float), de stocker cette valeur dans le paramètre correspondant (identificateurdevariable), de reprendre l exécution du programme à l instruction suivant l instruction d entrée. Autre fonction d entrée utile : Syntaxe : bcgetpause () ; bcgetpause (); Nom : bcgetpause (bibliothèque bcio) Description : permet d attendre que l utilisateur tape la touche «return». Permet, par exemple, de faire une temporisation en fin de programme avant de fermer la fenêtre. Précondition/Commentaire : affiche "Appuyez sur Entree pour continuer " avant d attendre la frappe de la touche «return». 16
18 2.5.2 Sortie de données Les fonctions de sorties permettent d'afficher à l écran la valeur d'une variable ou bien d'une chaîne de caractères. Il en existe quatre formes. Syntaxe et description Nom : bcdisplay (bibliothèque bcio) et bcdisplayln (bibliothèque bcio) Pour les formes 1, 2 et 3 ci-après, avec bcdisplayln, elles réalisent le même traitement que leur homologue (sans ln) et ajoutent un saut de ligne après l affichage réalisé ; c est donc l affichage suivant qui se fera sur la ligne suivante. «ln» est le raccourci de «line» (ligne en anglais). Forme 1) bcdisplay ( expression ) ; bcdisplayln ( expression ) ; Description : affiche la valeur d une expression. Exemples : bcdisplay (v1) ; bcdisplayln ( v1+v2 ) ; Forme 2) bcdisplay ( chainedecaracteres ) ; bcdisplayln ( chainedecaracteres ) ; Description : affiche une suite de caractères tels qu indiqués. Pour préciser le début et la fin de la suite, on utilise la notation de chaînes de caractères en langage C : entre des guillemets ". Les guillemets ne sont pas affichés. Ainsi, le compilateur sait différencier : a) bcdisplay (max) de b) bcdisplay ("max"); Exemples : a) est de la forme 1 : affiche la valeur de la variable appelée max, b) est de la forme 2 : affiche les caractères m, a, x à l écran. bcdisplay ( Bonjour ); bcdisplayln ( madame ) ; Forme 3) bcdisplay ( chainedecaracteres, expression ) ; bcdisplayln ( chainedecaracteres, expression ) ; Description : combinaison des formes 1 et 2, affiche une chaîne de caractères (entre guillemets) immédiatement suivie de la valeur de l expression indiquée. bcdisplay ( "Valeur : ", v1 v2 ); Forme 4) bcdisplayln () ; Description : n affiche rien mais permet de faire un simple saut de ligne. bcdisplayln () ; 2.6 Structure de contrôle : le if Syntaxe de l alternative : if Cette structure est équivalente au «SI Condition ALORS Action SINON Action FSI» de l algorithmique. 17
19 La partie else est optionnelle (comme l indiquent les crochets). Les accolades ouvrante et fermante marquent le début et la fin du bloc associé au if ou au else. Elles sont optionnelles s il n y a qu une seule instruction dans un bloc, mais elles sont recommandées. Syntaxe : if ( expressioncondition ) instruction(s) else instruction(s) Exemples : if (a == b) a = a + b; c = c + 1; else a = b * 2; Expressions conditionnelles Opérateurs conditionnels Syntaxe : opérateurs relationnels : if (x < 2) y = x + 3; z = w * 2; // Hors du if puisque pas d accolade! == test d'égalité de valeur,!= différence, <, >, <=, >= Attention : le test d égalité est == (double égal), le simple = étant l affectation! Syntaxe : opérateurs logiques :! : non logique && : et logique : ou logique Ecriture d'une expression conditionnelle Syntaxe : Exemples : Une expression relationnelle est formée d'expressions arithmétiques reliées par des opérateurs relationnels. Une expression logique est formée d'expressions relationnelles reliées par des opérateurs logiques. i == 33 (i==33) && (j <=3) (i+ j<=155) ((k-v)>(x-z)) Règles simplifiées de grammaire pour la construction d une expression conditionnelle : exprrel -> exparith oprelationnel exparith exprlog -> exprrel exprlog oplogique exprlog (exprlog) 18
20 3 Structures de Contrôle 3.1 Formes d'instructions Les différentes formes d instructions disponibles en langage C sont : une expression terminée par un «;», une instruction contrôlée : cf. structures de contrôle, un bloc d'instructions entre accolades :, permettant de délimiter le début et la fin du bloc, instruction vide : ; appel d une fonction considérée comme une procédure (cf. fonctions) terminée par un «;». 3.2 Structure de contrôle : le if Syntaxe : if ( expressioncondition ) instruction(s) else instruction(s) Cette structure est équivalente au «SI Condition ALORS Action SINON Action FSI» de l algorithmique. La partie else est optionnelle (comme l indiquent les crochets). Les accolades ouvrante et fermante marquent le début et la fin du bloc associé au if ou au else. Elles sont optionnelles s il n y a qu une seule instruction dans un bloc, mais elles sont recommandées. #include <bcio.h> /* Programme exemple de if */ int main (void) int age; bcdisplay ("Quel est votre âge "); bcget (age); if ( (age <= 0) (age >= 110) ) bcdisplayln ("Menteur..."); else if (age <= 15) bcdisplayln ("Adolescent..."); 19
21 else if (age <= 23) bcdisplayln("dans la force de l'age..."); else bcdisplayln ("Vieillissant..."); bcgetpause (); return 0; 3.3 Structure de contrôle : le while Syntaxe : while ( expression ) instruction(s) Cette structure est équivalente à «TQ condition FRE Instruction FTQ» de l algorithmique. Ici aussi, les marquent le début et la fin du bloc associé au while. Elles sont optionnelles s il n y a qu une seule instruction dans ce bloc, mais elles sont recommandées. i = 0 ; while ( i <n ) j = j * j; i = i+1; /* ou i++; */ 3.4 Structure de contrôle : le do while Syntaxe : do instruction(s) while ( expression ) ; Cette structure est spécifique au langage C et s interprète comme «FAIRE instruction TANTQUE condition FINFAIRE». C est une sorte de TQ dans lequel le corps de la boucle est exécuté une fois au moins puisque le test se fait après au moins un passage par les instructions de la boucle. Attention : Cette structure n est pas équivalente au «REPETER instructions JUSQU'A condition ;» de l algorithmique. Autre particularité, c est le seul cas en langage C où on trouve un «;» après une condition. do /* Lecture d'une valeur */ bcget (n) ; while ( (n <= 0) (n > MAXELTS) ) ; 20
22 3.5 Structure de contrôle : le for Syntaxe : for ( initialisationfor ; conditionfor ; incrementfor ) instruction(s) Le for est une structure de répétition, assez proche sémantiquement de la structure algorithmique «POUR i DE x À y FAIRE instruction FPOUR». Elle est donc très souvent utilisée pour «traduire» la structure «POUR». Cependant, elle permet des constructions beaucoup plus complexes (à deux indices ou à conditions complexes par exemple). Les trois éléments importants se trouvent sur la ligne «for ( )» séparés par des «;» (noter que les «;» sont ici différents des «;» de fin d instruction, le même symbole étant utilisé ici pour une autre sémantique) : InitialisationFor est une instruction seule ou une suite d'instructions séparées par des virgules, plus généralement, une expression en langage C. En général, ce sera une seule instruction. Elle n est exécutée qu une seule fois, avant d entrer dans la boucle. conditionfor est une expression conditionnelle, testée à chaque exécution du corps de la boucle, permettant de décider si la boucle est exécutée à nouveau, ou bien si on sort de la boucle. En particulier, conditionfor est évaluée avant la première exécution de la boucle, juste après l évaluation de initialisationfor. incrementfor est une instruction seule ou une suite d'instructions séparées par des virgules, plus généralement, une expression en langage C. En général, ce sera une seule instruction. Elle est exécutée à chaque tour de boucle, après exécution du bloc «instruction» associé à la boucle, avant de revenir sur le test de conditionfor. L interprétation à donner au for est la suivante : for ( initialisationfor ; conditionfor ; incrementfor ) instruction(s) Exemple d utilisation classique du for pour traduire une boucle «POUR» : j = 10; for (i = 1 ; i <= 10 ; i++ ) j = j+1 ; initialisationfor while ( conditionfor ) instruction(s) incrementfor Attention : dans l'exemple précédent, i est modifiable dans la boucle sans contrôle for ( i = 1 ; i <= 10 ; i++ ) j=j+1; i = i 1; Résultat : boucle infinie => A EVITER 3.6 Exemple de programme C avec boucles #include <bcio.h> /* Programme affichant n fois "Bonjour", en utilisant 3 types de structures de contrôle */ int main (void) 21
23 int i; // Travail : Compteur pour itérations int n; // Saisie : nombre saisi par l'utilisateur // Saisie du nombre de fois où "Bonjour" doit être affiché bcdisplay ("Combien de fois afficher \"Bonjour\" "); bcget (n); // Affichage par boucle while bcdisplayln ("Avec while"); i = 1; while ( i <= n ) bcdisplayln ("Bonjour ligne : ", i); i = i + 1; bcgetpause (); // Affichage par boucle do while bcdisplayln ("Avec do while"); i = 1; do bcdisplayln ("Bonjour ligne : ", i); i = i + 1; while ( i <= n ) ; bcgetpause (); // Affichage par boucle for bcdisplayln ("Avec for"); for ( i=1 ; i <= n ; i = i+1 ) bcdisplayln ("Bonjour ligne : ", i); bcgetpause (); return 0; 3.7 Structure de contrôle : le switch Syntaxe : switch ( expressionentiere ) case expr_const1 : instruction(s) break; case expr_const2 : instruction(s) break; default : instruction(s) break; 22
24 Le switch est équivalent au «CAS» en algorithmique et remplace avantageusement des cascades de if imbriqués. Le switch permet de tester la valeur d'une expression entière. Le switch compare la valeur de l expression expressionentiere à chaque valeur de case, dans l ordre des case : lorsque une valeur de case est égale à expressionentiere, les instructions correspondantes sont exécutées, si aucune valeur de case n est égale à expressionentiere, la branche default, si elle est présente, est exécutée. Attention : nécessité d utiliser l instruction break pour finir chaque cas, sinon l ensemble des instructions suivant le cas choisi est exécuté. #include <bcio.h> int main (void) int i; bcdisplay ("Donnez une valeur entière : "); bcget (i); switch (i) case 1 : bcdisplayln ("Cas 1"); break ; /* arrêt de l'évaluation (sortie du switch) */ case 2 : case 2+1 : bcdisplayln ("Cas 2 ou 3"); break ; /* arrêt de l'évaluation (sortie du switch) */ case 5-1 : case 5 : bcdisplayln ("cas 4 ou 5"); break; /* arrêt de l'évaluation (sortie du switch) */ default : bcdisplayln ("Autres valeurs que de 1 à 5, i vaut : ", i); break; /* arrêt de l'évaluation (sortie du switch) */ bcgetpause (); return 0; 23
25 4 Constantes, variables et types 4.1 Les types numériques et les littéraux associés Les types numériques entiers signés Le codage des entiers en binaire se fait en général en complément à deux (cf. cours Architecture). La taille des types peut dépendre de la machine cible et surtout du compilateur. short int ou short : entier signé codé sur au moins 1 octet. o Notation littérale : 31, -56 o Pour nous : 2 octets donc de à int : entier signé codé sur au moins 2 octets. o Notation littérale : 31, -56 o Pour nous : 4 octets donc de à long int ou long : entier signé codé sur au moins 2 octets (très souvent 4). o Notation littérale : 31L, -56L o Pour nous : 4 octets donc de L à L o Attention, pas toujours équivalent à int selon les compilateurs. Relations obligatoires entre les tailles en octets : taille short <= taille int <= taille long Notons qu aujourd hui, la majorité des compilateurs utilisent : des short int sur 2 octets, des int sur 4 octets et des long int sur 4 octets Les types numériques entiers non signés En général, les types entiers sont signés. On peut le préciser en mettant signed devant le type. Par exemple signed short signifie un «entier de taille short signé». Cet élément de typage, s il n est pas précisé, est pris par défaut. On peut demander des types non signés en mettant unsigned devant la définition du type. Compte tenu du codage binaire des entiers, ces types s interprètent comme ne contenant que des valeurs positives. L avantage des types non signés est de doubler la plage des valeurs des types dans les valeurs positives : unsigned short : sur 2 octets : de 0 à unsigned int, unsigned long Notation littérale : comme les valeurs signées mais avec un U : 10U, UL Attention : attention aux expressions (calculs, comparaisons, ) mêlant des entiers signés et non signés car il existe des risques d'erreurs d interprétation avec des valeurs négatives pour les entiers signés. Les détails concernant les conversions et l utilisation des entiers non signés pourront être trouvés dans d autres manuels et sont hors de portée de ce document Les types numériques en virgule flottante Le codage des réels se fait en général en virgule flottante (cf. cours Architecture). La taille des types peut dépendre de la machine cible et du compilateur. float : réel signé en virgule flottante codé sur au moins 4 octets (simple précision). o Notation littérale : 10.5F ou 10.5f ou 10.5 ou 1.56E10 o Pour nous 4 octets : +/- 3.4 * (10**-38) à +/- 3.4 * (10**+38) 24
26 double : réel signé en virgule flottante codé sur au moins 8 octets (double précision). o Notation littérale : 10.5 ou 10.5f ou 10.5 ou 1.56E10 long double : allonge la précision des doubles 3, la taille dépend ici du compilateur. Relations obligatoires entre les tailles en octets : Relations entre entiers et flottants En général, on a les relations suivantes sur les tailles : taille int <= taille float taille long <= taille double taille float <= taille double<= taille long double 4.2 Le type booléen Le type booléen en langage C Le type booléen n'existe pas en langage C (pas de nom de type, pas d'opération spéciale). Une valeur entière : nulle est considérée valoir faux, non nulle est considérée valoir vrai (>0 ou <0, mais 1 est souvent utilisé). Une expression logique renvoie une valeur nulle ou non nulle. int j, i = 5; if (i > 15) i=i+1 ; // i > 15 sera évalué à 0 j = (6 < 10); // j vaudra autre chose que 0 (1) j = (6 > 10); // j vaudra 0 On peut donc utiliser les expressions conditionnelles comme des valeurs entières et les valeurs entières comme des valeurs booléennes dans une expression. Attention : pour manipuler des pseudo-booléens en langage C, il est recommandé de définir des constantes. #define VRAI 1 #define FAUX 0 //et les utiliser pour positionner les valeurs des booléens int trouve ; trouve = FAUX ; while ( ) trouve = VRAI ; Le type booléen en langage C++ Si le langage C ne dispose pas de type booléen, le langage C++, extension du C, propose un tel type. Compte tenu que la plupart des compilateurs actuels compilent en C et en C++, on s autorisera à utiliser la notation C++ définie ci-après. On n oubliera pas que ces constructions n existent pas en C et qu il faudrait les remplacer par des constructions comme définies au paragraphe Noter ici l utilisation d un même mot-clé (ou token) «long» dans un contexte différent de celui des int. Un long double est un double «allongé» alors qu un long, qui est en fait un long int, est un int «allongé». 25
27 Type booléen en C++ : Nom du type : bool Valeurs littérales possibles : true et false Opérations possibles : affectation et opérations booléennes : &&,,! bool trouve, fini ; trouve = false; fini = false; while (!trouve &&!fini)... if (...) trouve = true; 4.3 Caractères : le type char Définition du type char Le type char permet de stocker un caractère parmi l ensemble des caractères du jeu de caractères de la machine. Le langage ne définit pas de jeu de caractères. Dans la grande majorité des cas, c est le code ASCII /ANSI. char : type caractère (char) sur un octet. Taille : 1 octet. Stocke une valeur codée en binaire interprétable comme un entier. Suivant les machines, les char sont considérés comme signés ou pas : o Intervalle -128 à +127 si en entiers signés : signed char o Intervalle 0 à +255 si en entiers non signés : unsigned char Notation littérale : entre ' Ex : 'A', 'a', Codes ASCII principaux : o '0' : 48, '9' : 57, 'A' : 65, 'Z' : 90, 'a' : 97, 'z' : 122 Caractères spéciaux les plus importants : notés entre ' '\n' saut de ligne '\r' retour chariot '\' caractère '\b' backspace '\t' Tabulation '\'' apostrophe ' '\a' bell (bip) '\\' caractère \ '\"' guillemet " Principe d utilisation des caractères : En mémoire : codé comme un entier, sur un seul octet. Lors de l'affichage, valeur sur un octet considérée non signée sert à trouver le caractère (symbole) à afficher (voir tableau des codes ASCII en annexe). Lors d'une saisie, le caractère (symbole) est transformé en son code non signé sur un octet. 26
28 4.3.2 Chaînes de caractères On peut utiliser des séquences de caractères, nommées chaînes de caractères, délimitées par des guillemets ("). bcdisplay ( "Bonjour \"A vous\"\n" ) ; Affichera : «Bonjour "A vous"» suivi d un retour chariot (\n dans la chaîne). La gestion particulière des chaînes de caractères fait qu on leur accordera un chapitre spécial. Attention : les chaînes de caractères ne sont pas équivalentes à une simple suite de caractères. En particulier dans le cas de chaînes contenant un seul caractère : A : est un char et nécessitera un seul octet pour le stoker, "A" : est une chaîne de caractères et nécessitera 2 octets pour la stocker. Attention : veiller à utiliser le bon symbole : la simple quote ( ) pour les caractères, la double quote ou guillemet (") pour les chaînes de caractères. l instruction char c = "A" ; conduira à une erreur car on ne peut affecter une chaîne à un char. 4.4 Déclaration de variables Une variable est l association d'un identificateur («nom») + un type + une zone mémoire repérée par une adresse + une valeur (contenue dans la zone mémoire) Les identificateurs Les identificateurs permettent d identifier les variables, les constantes, les fonctions et les types définis par l utilisateur. Attention : le langage C différencie les majuscules et les minuscules. Règle de construction : un identificateur est constitué d une suite de caractères, commençant par une lettre, et pouvant contenir des lettres, des chiffres ou le caractère _ (underscore ou tiret bas). Selon la norme du langage C, tout compilateur se doit de reconnaître 31 caractères significatifs. Règles pour construire des identificateurs de variables significatifs : mots significatifs, doivent commencer par une lettre et sont en minuscule, mots intérieurs commencent par une majuscule. tauxreponsemax Les mots clés réservés Les mots clés réservés sont des éléments du langage (comme une "ponctuation"). On ne peut donc pas les utiliser comme identificateurs. Attention : Le langage C est case-sensitive. Ainsi IF est différent de if. A éviter auto continue enum if short switch volatile break default extern int signed typedef while case do float long sizeof union char double for register static unsigned const else goto return struct void 27
29 4.4.3 Déclaration de variable Syntaxe : classe modificateur type ident1 =valeur, ident2 =valeur ; La déclaration d une variable est obligatoire avant toute utilisation, en début de fonction (main par exemple), avant le code qui va l utiliser. La déclaration d une variable est obligatoire et doit spécifier le type de la variable car cela permet au compilateur : de connaître son mode de codage (type), de connaître l'identificateur qui permet de la différencier de toutes les autres dans le code, de lui associer une emplacement mémoire (objet mémoire). La classe et le modificateur permettent de définir des caractéristiques particulières de la variable mais ne seront pas détaillés ici. Ils sont optionnels. La déclaration d une variable peut être suivie d une initialisation. L initialisation, si elle est symbolisée par le signe «=», n est pas une affectation car elle dispose de possibilités d écriture qui lui sont propres, impossibles avec l affectation. La valeur donnée pour l initialisation doit être une constante. Exemples : int i = 10 ; char caractere = A ; bool trouve = false ; float f1, f2, f3 ; En l absence d une initialisation, la valeur d une variable, après déclaration, est quelconque (une valeur existe, mais n est pas prévisible). 28
30 5 Expressions en langage C 5.1 Généralités Une expression est composée d opérateurs, de variables, de constantes, d appels de fonctions et de parenthèses. Une expression retourne toujours un résultat ayant un type : la valeur de l expression. La valeur d une expression peut être affectée, testée ou utilisée comme opérande d une autre expression. Exemples d expression : 5 a a+b-5 3*(f(a)+b) 5.2 Opérateurs arithmétiques Syntaxe : opérateurs arithmétiques + (addition), - (soustraction), * (multiplication), / (division) + unaire, - unaire % (modulo sur les entiers seulement) Les opérateurs arithmétiques (sauf le modulo) prennent des opérandes de types entiers, caractères ou réels. Le modulo calcule le reste de la division entière de deux entiers positifs (pour des entiers négatifs, résultat indéterminé a priori, voir la documentation du compilateur). 9 % 2 donne 1 Attention : le calcul d une opération se fait dans le type des opérandes. L opération réellement réalisée au niveau binaire, n est pas la même en fonction du type des opérandes qui entourent l opérateur (cf. cours architecture). le / sur deux int est la division entière. Donc int i = 5 / 3; /* Donnera 1 en int */ Le choix de l opération à réaliser revient au compilateur. Ceci explique l importance de déclarer le type d une variable avant toute utilisation : le compilateur choisira l opération binaire à réaliser en fonction du type des opérandes. Dans l exemple suivant : int somvals, v1, v2; bcget(v1); bcget(v2); somvals = v1 + v2; L addition "v1 + v2" est interprétée comme l addition de deux int en se référant à la déclaration des variables v1 et v Expressions arithmétiques Syntaxe : une expression arithmétique est une combinaison de littéraux et/ou de constantes et/ou de variables et/ou d appels de fonctions avec des opérateurs arithmétiques et éventuellement des parenthèses. Exemple d expressions arithmétiques : // littéral 3 opérateur + littéral 5 i * j + 3 // interprété comme (i*j)+3 (v1 + v2) / 2 29
31 Règles simplifiées de grammaire pour la construction d une expression arithmétique : terme -> variable littéral exprarith -> terme exprarith oparith exprarith ( exprarith ) 5.4 Opérateurs de modification de valeur d une variable Instruction d affectation d une valeur à une variable L opération d affectation permet d affecter la valeur d une expression à un objet, généralement une variable. Syntaxe : identificateur = expression ; Attention : en algorithmique, le signe "=" représente l opérateur de test d égalité, ici c'est l'affectation! i = j+12 ; Une affectation est une expression comme les autres. Une affectation a donc une valeur : la valeur affectée. Cette valeur peut être donc utilisée dans une autre expression. Par souci de clarté, utiliser cette possibilité avec parcimonie. i = j = 4 ; // j et i vaudront 4 Bien mieux lisible en écrivant : i = 4 ; j = 4 ; Affectations relatives Syntaxe : identificateurvar op = expression ; Opérateurs possibles (op=) : += -= *= /= %= Définition : id op= expression <=> id = id op (expression) Exemples : i += 1 <==> i = i+(1); a *= i+1 <==> a = a *(i+1); Incrémentation, décrémentation Le langage C offre deux opérateurs (unaires) pour réaliser une incrémentation ou une décrémentation : ++ : opérateur d incrémentation -- : opérateur de décrémentation Utilisation très pratique et directe : dans un for : for (i=1; i<10; i++) // plus rapide que i=i Pré-incrémentation, pré-décrémentation L opérateur est placé avant la variable. L opération (incrémentation/décrémentation) est réalisée, puis l utilisation de l expression est réalisée. v1 = ++v2 ; // Equivaut à : v2 = v2+1 ; v1 = v2 ; La valeur de ++v2 est la valeur de v2 après incrémentation Post-incrémentation, post-décrémentation L opérateur est placé après la variable. L utilisation de la variable est réalisée (dans l expression courante), puis l opération (incrémentation/décrémentation) est réalisée. 30
32 v1 = v2++ ; // Equivaut à : v1=v2 ; v2 = v2+1 ; La valeur de v2++ est la valeur de v2 avant incrémentation. 5.5 Opérateurs relationnels et logiques Syntaxe : opérateurs relationnels : == test d'égalité de valeur,!= différence, <, >, <=, >= Attention : le test d égalité est == (double égal), le simple = étant l affectation! Syntaxe : opérateurs logiques :! : non logique && : et logique : ou logique 5.6 Expressions logiques Syntaxe : Une expression relationnelle est formée d'expressions arithmétiques reliées par des opérateurs relationnels. Une expression conditionnelle est formée d'expressions relationnelles reliées par des opérateurs logiques. Exemples : i == 33 (i==33) && (j <=3) (i+ j<=155) ((k-v)>(x-z)) Règles simplifiées de grammaire pour la construction d une expression logique: exprrel -> exparith oprelationnel exparith exprcond -> exprrel exprrel oplogique exprrel (exprcond) 5.7 Priorités des opérateurs Lors de l évaluation des expressions, le langage C utilise des priorités entre opérateurs pour réaliser l évaluation. En cas de doute, on peut utiliser le parenthésage pour forcer l évaluation dans un certain ordre et lever les ambiguïtés. Opérateurs les plus prioritaires Ordre d'évaluation des termes ( ) [ ] ->. de gauche à droite! ~ * & sizeof (cast) (op. unaires) 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 gauche à droite = += -= *= /= de droite à gauche, de gauche à droite Opérateurs les moins prioritaires l = i + j * k - 3 est évalué en l = ( ( i + ( j * k ) ) 3 ) car priorité * > priorité + et - > priorité = et pour + et -, à priorité égale, l évaluation se fait de gauche à droite. 31
33 5.8 Evaluation des expressions Conversions/Promotions de type (cast) et opérations de base du processeur Le changement de type d'une valeur ou conversion s'appelle transtypage et correspond à un changement de représentation mémoire, la valeur étant, a priori, conservée. La valeur est «recodée» selon un autre mode de codage. 3 en int est codé en complément à deux sur 4 octets. 3.0 en float est codé en représentation flottante sur 4 octets. Affecter un int de valeur 3 à un autre int correspond à une recopie des octets en mémoire. Affecter un int de valeur 3 à un float nécessite un changement de codage. Le rôle du compilateur est de «traduire» le programme en instructions machines exécutables par le processeur. Quelque soit le processeur, les instructions de base du processeur (addition, ) font toujours intervenir des opérandes de même type, c est-à-dire des opérandes représentés en mémoire selon le même codage (en complément à deux, en flottant, ). De ce fait, une opération comportant des données codées différemment nécessitera des transtypages, implicites ou explicites selon le langage. Le langage C réalise beaucoup d opérations de transtypages de façon implicite. Problème : comment sont évaluées les expressions en langage C C'est la sémantique du langage, c'està-dire comment le compilateur comprend/considère/interprète le code écrit, qui définit cela. Cette sémantique fait intervenir des transtypages implicites, c est-à-dire réalisés par le compilateur C, sans message d erreur ou «d explication». int i; float f; f = 13; // Que faire ici, 13 est un int : i = 13.5 // Que faire ici 13.5 est représenté en flottant ( ) f = i*13.5; // Comment faire l opération int * float i = f * 3; // Comment faire l opération float * int if (i <= f) // Comment faire l opération int <= float Evaluation d'une affectation Règle : l évaluation d une affectation en langage C se fait en deux temps séparés : 1. Evaluation de l expression en partie droite de l affection, avec conversions nécessaires (cf ). Cette évaluation ne prend pas en compte le type de la partie gauche. Cette évaluation s effectue «comme si» chaque variable était remplacée par sa valeur, puis les conversions nécessaires éventuelles sont réalisées, puis le calcul final est réalisé. Le résultat de l évaluation de la partie droite d une affectation a un type qui lui est propre, lié au calcul réalisé. 2. Affectation du résultat à la partie gauche avec conversion si nécessaire. i = f * 3 ; // f * 3 évalué en float puis le résultat est converti en int pour l'affecter à i Evaluation des expressions Le problème d'interprétation provient des différences de codage : entier signé / entier non signé / flottant, taille mémoire différente selon les types. ET que les seules opérations disponibles sur un processeur ne peuvent faire intervenir que des opérandes de même type. Par exemple int et int ou float et float mais pas int et float. 32
34 Exemple «trivial» : L opération «manuelle» ne se «pose» pas ainsi MAIS Sous-entendu 11.00, on a converti l entier 11 en un réel Règle d'évaluation des expressions Règle : les évaluations des expressions sont effectuées dans le type le «plus haut» des opérandes, avec conversions implicites si besoin. Les conversions sont dites implicites car elles sont réalisées automatiquement sans être signalées. L'ordre des types est considéré ainsi : int < long < float < double < long double. De plus, des conversions spéciales sont appliquées si on a des entiers non signés. Il est conseillé d éviter de mélanger des opérandes signés et non signés. Cas particulier : les char et les short sont convertis en int systématiquement avant évaluation (cf. 5.9). Les conversions sont réalisées opérateur par opérateur lorsque le moment du calcul est arrivé. Il n y a pas de conversion globale à toute l expression puis évaluation, mais au fur et à mesure que les opérateurs sont utilisés dans le calcul, les conversions nécessaires ont lieu à chaque étape. Les promotions/ conversions opérées lors des calculs ne modifient pas les valeurs des variables impliquées dans le calcul. Le compilateur intègre au calcul, si besoin, des variables temporaires pour stocker les résultats intermédiaires des conversions et des calculs. Le programmeur n a pas à s en soucier Exemples int i; float f; f = 13; // la valeur 13 est convertie ("recodée" en float) et affectée i = 13.5 // la valeur 13.5 est convertie en int (très souvent tronquée à 13) et affectée f = i*13.5; // int et float donc conversion de la valeur de i en float, puis calcul en float // puis affectation du résultat à f (float) i = f * 3; // float et int donc conversion de la valeur 3 en float puis calcul en float // puis affectation du résultat à i avec conversion (int) // NB : Conversion de 3 en float signifie passer de // (entier signé) à une représentation en flottant. if (i <= f) // int et float donc conversion de la valeur de i en float puis comparaison «Les conversions sont réalisées opérateur par opérateur lorsque le moment du calcul est arrivé» int i = 3 ; double d = 13.0 ; float f = 5.0; // 5.0 est codé en double => conversion en float // Pour l éviter, on peut écrire : float f = 5.0F ; i = i + f + d ; // L évaluation se fait ainsi : i+f puis le résultat +d soit ((i+f)+d) // i+f : int + float => conversion de la valeur de i en float et calcul du résultat en float // float+d => conversion du float en double et calcul du résultat en double // i=double => conversion de la valeur en int 33
35 Attention à l évaluation de l'expression suivante (on souhaite obtenir 5.5 en résultat de 11/2) : float moy; moy = (5+6) / 2 ; // Résultat : 5! CAR 5+6 : int + int => calcul en int résultat = 11 codé en int 11/2 : int / int => calcul en int résultat = 5 codé en int (division entière) Une solution possible : chercher à forcer un des opérandes de la division en float moy = ; // Calcul en int puis conversion en float moy = moy/2 ; // float / int => conversion en float pour faire float / float Quelques conversions implicites automatiques Ce type de conversion peut intervenir lors du calcul d'une expression, d'une affectation ou d'un passage de paramètre. Elles sont appliquées automatiquement, sans message. Les conversions qui sont OK quelle que soit la valeur du type sont appelées promotions. De Vers Problèmes potentiels char int Ok sauf parfois problème de signe sur certaines valeurs. short int Ok. int long Ok. float double Ok. Complément des bits de poids faible par des 0. entier réel En général Ok. (Risque de perte de précision, ordre de grandeur respecté). int short Elimination des bits de poids fort (modulo). int char Elimination des bits de poids fort (modulo). double float Arrondi. Elimination des bits de poids faible. réel entier Dépend du compilateur (souvent partie entière, perte possible) Conversions explicites On peut opérer des conversions explicites de type, c'est à dire demander explicitement au compilateur de convertir une valeur dans un type donné. Syntaxe : Exemples : ( type ) expression Convertit la valeur de expression dans le type indiqué. float f = (float) 12; int i = (int)13.5 ; // Conversion de 13.5 de double à int char c = (char) 65 ; //Inutile mais sert parfois, ici 'A' moy = ((float) (5 + 6)) / ((float)(2)); // un seul (float) suffit car un seul opérande float implique la promotion de tous les autres 5.9 Le type char dans les expressions Utilisation du type char En utilisant des variables et des constantes de type char, les opérations d affectation et de comparaison fonctionnent selon une «logique de caractère». L affectation consiste à affecter une valeur de code ASCII de caractère. Les comparaisons consistent à comparer les codes ASCII des caractères impliqués. 34
36 char c ; c = A ; if ( (c>='a') && (c <= 'Z') ) bcdisplay ("Lettre en majuscule"); bcget (c); if ( (c>='0') && (c <= '9') ) bcdisplay ("Chiffre du pavé numérique"); Ne pas oublier que les lettres majuscules et minuscules ont des codes différents (cf. Table ASCII Polycop de synthèse) Ainsi le test ( a == A ) vaut faux. Le type char peut se manipuler aussi comme un entier et on peut faire des calculs avec le type char : le type char peut être considéré comme signé ou non signé, en général signé (-128 à +127), l'interprétation se fait comme un entier codé en complément à deux. char c ; bcget (c); // Saisie de 'H' c = c+1; bcdisplay (c); // Saisie de 'H' soit le code 72, c passe à 73, donc affichage de 'I' (cf. Table ASCII Polycop de synthèse) Evaluation, par le compilateur, des expressions utilisant le type char Règle d utilisation des char dans les expressions : dans toutes les expressions, les char sont d'abord convertis en int avant l évaluation de l expression. C est le cas dans les expressions arithmétiques comme dans les expressions conditionnelles. Cas particulier : soit l affectation suivante : char c1, c2 ; c1 = A ; c2 = c1 ; L affectation de c1 à c2 entraîne : a) la conversion de la valeur de c1 en int, puis b) l affectation du résultat à c2 et donc la conversion de l int en char. Ceci peut paraître bizarre, mais la partie droite de l affectation, ici réduite à une seule variable, est considérée comme une expression, la règle précédente s applique donc. L affectation de A à c1 fonctionne d ailleurs selon le même mode : a) conversion de A en int (65) puis, b) affectation à c1 avec conversion en char de la valeur 65 (int) Conversion char vers int Règle : la conversion d une valeur char vers un int s effectue de la manière suivante : l'octet du char devient l octet de poids faible de l int, les octets de poids forts sont complétés par la valeur du 1 bit du char (bit de gauche). 'P' codé en char 80 soit devient en int car report du 0 (1 bit) car report de l'octet complet en poids faibles OK, valeur numérique entière 80 35
37 ' ' codé en char 169 soit devient en int car report du 1 (1 bit) car report de l'octet complet en poids faibles mais la valeur numérique obtenue ici est -87!! La conversion char vers int conserve les valeurs pour les codes ASCII de 0 à 127 (1 bit à zéro). Pour les autres codes, il y a perte de valeur. Pour résoudre le problème sur les codes 128 à 255, préférer le type unsigned char. Cas particulier : exemple de cas où la conversion des char en int dans les expressions peut poser problème. Supposons que l on veuille saisir un caractère et tester s il est égal au caractère ' ' ou P. La frappe de ce caractère dans l éditeur de texte étant difficile, on utilise le code ASCII de ' ' soit 169 et l on écrit : char c; bcget (c); if (c== P ) bcdisplayln("p") ; if (c==169) // Ou ' ' bcdisplayln ("OK"); Le compilateur «interprète» les if comme quelque chose qui ressemble à : if ( conversion de c en int == ) bcdisplayln("p") ; if ( conversion de c en int == ) bcdisplayln ("OK"); Si l utilisateur tape P, la conversion de c en int donnera et le premier if répond vrai Si l utilisateur tape, la conversion de c en int donnera et le second if répond faux!! Conversion int vers char Règle : la conversion d une valeur int vers un char s effectue de la manière suivante : on ne garde que l'octet de poids faible (de droite), tous les autres sont perdus. valeur int 80 codée devient en char soit le code ASCII 80 soit 'P' à l'affichage valeur int -176 codée devient en char soit le code ASCII 80 soit 'P' à l'affichage La conversion d un int en char n a de «sens réel» que pour des valeurs int entre 0 et 255, les autres cas pouvant donner des résultats inattendus. Pour utiliser facilement les caractères étendus et leurs codes (128 à 255), préférer le type unsigned char. 36
38 5.10 Problèmes de calculs limites sur les types lors de l évaluation d une expression Débordement mémoire / dépassement de capacité Exemple «trivial» : essayez de faire, sur une feuille A4, la multiplication de deux nombres de 1890 chiffres ; impossible car pas assez de place pour poser la multiplication et stocker (écrire) le résultat. En machine, les données sont codées selon des types qui ont une taille fixe. Si la valeur à stocker dans une variable dépasse la longueur de son type, rien de particulier ne se passe (pas d erreur) et le résultat est faux. short sv = ; // Limite des short : sv = sv+2; bcdisplay (sv); // Affiche Explication de l exemple : Sans le signe : Donne Donc on met dans sv qui est en entier signé Ce problème peut intervenir dans tous les calculs, dans toute évaluation d'une expression. short s1, s2; int i; s1 = 32000; s2 = 32000; i = s1; i = i + s2 ; // Calculs en int car conversion de la valeur s1 vers int => ok s1 = s1 + s2 ; // Calculs en short => dépassement bcdisplayln (i); // bcdisplayln (s1); // La capacité des flottants Bien penser que les flottants ne stockent que des valeurs approchées (cf. cours Architecture). En particulier, certaines valeurs pourtant triviales ne sont pas représentables exactement. De plus, sur les grands nombres, seuls sont utilisés un certain nombre de chiffres significatifs. float f; f = 20.7; bcdisplay (f) ; // f = ; bcdisplay (f) ; // Calculs arrêtant le programme Les calculs sont réalisés, parfois avec dépassement de capacité, parfois avec des conversions qui peuvent mener à perdre de l'information. 37
39 Mais un seul calcul arrête l'exécution du programme : la division par zéro entre entiers. int i; bcget (i); // On entre 0 i = 12/i; // Arrêt du programme lors de la division bcdisplay (i) ; En flottants, deux cas sont possibles. Pour certains compilateurs, la division par zéro entre réels arrête aussi l exécution du programme. Dans beaucoup de compilateurs maintenant, on a deux valeurs limites : +infini et infini qui ne représentent aucune valeur, mais qui permettent de représenter des valeurs extrêmes. En particulier, dans le cas d une division par zéro, nous avons ce résultat. Toutefois, la valeur de la variable n'est plus utilisable et il faut tester éventuellement ce résultat (fonction isnan, bibliothèque math). float f; f = 12.0/0; // Calcul en float => +infini bcdisplay (f); // affiche : 1.#INF00 f = f+3; // f reste à + infini bcdisplay (f); // affiche : 1.#INF Opérateurs supplémentaires L opérateur de taille : sizeof L opérateur sizeof permet de déterminer la taille (en octets) d une variable ou d un type. Syntaxe : sizeof ( type ) sizeof ( variableouexpression ) sizeof variableouexpression // Forme non utilisable pour un type inti i ; double d ; sizeof (double) donne 8 sizeof (i) donne 4 (taille du type int) sizeof (i+d) donne 8 (taille du résultat, c est-à-dire double) Opérateur conditionnel Il permet de tester de façon «courte» une condition ayant un résultat simple (une valeur). Syntaxe : ( expression ) expression1 : expression2 ; Si expression est vrai, le résultat est expression1, sinon la résultat est expression2. n = ( a<0 ) a : a ; // Calcule la valeur absolue de a Remplace utilement if ( a < 0 ) n = -a ; else n = a ; 38
40 6 Les tableaux 6.1 Exemple de programme utilisant un tableau #include <bcio.h> #define KMAXVALS 5 // Programme de calcul de la moyenne d'une suite de valeurs avec n connu int main (void) int i; int nbv; float som; float moy; float tabvaleurs[kmaxvals]; // Déclaration de tableau Contexte main i // Saisie du nombre de valeurs bcdisplay ("Nombre de valeurs, ", KMAXVALS); bcdisplay (" maxi : "); bcget (nbv); som nbv int int while ( (nbv <= 0) (nbv > KMAXVALS) ) bcdisplay ("Erreur, resaisir : "); bcget (nbv); // Saisie des données dans un tableau for (i=0; i < nbv; i=i+1) bcdisplay ("Valeur : "); bcget (tabvaleurs [i] ); moy tabvaleurs tabvaleurs + 0 tabvaleurs + 1 tabvaleurs + 2 tabvaleurs + 3 float float float float float float // Calculs : somme pour la moyenne // et calcul de la moyenne som = 0; for (i=0; i < nbv; i=i+1) som = som + tabvaleurs [i]; moy = som / nbv; tabvaleurs + 4 float float[5] // Affichage de la moyenne bcdisplayln ("Moyenne des valeurs : ", moy); bcgetpause (); return 0; 39
41 6.2 Définitions Caractéristiques d un tableau : Un tableau est le regroupement sous le même identificateur de plusieurs données de même type, contiguës en mémoire. C est un regroupement de données homogènes. Chaque élément d un tableau est repéré par son indice. Les indices des éléments d'un tableau sont obligatoirement des entiers en C. Le premier élément d un tableau est repéré par l indice 0. Un tableau de 50 entiers contient des éléments numérotés de 0 à 49. Le nombre maximal d éléments d un tableau doit être obligatoirement précisé lors de la déclaration. 6.3 Déclaration d un tableau Syntaxe : classe modificateur type idtab [ nb_elts ] ; type détermine le type des éléments du tableau. Les crochets [] sont obligatoires 4. Ce sont eux qui indiquent que l on veut déclarer un tableau. nb_elts détermine le nombre maximal d éléments du tableau et doit être une expression entière évaluable par le compilateur, une constante par exemple (une variable ne peut donc pas apparaître dans cette expression). Cette taille sert à la réservation de mémoire, forcément contiguë. idtab est l identificateur du tableau. La classe et le modificateur permettent de définir des caractéristiques particulières du tableau mais ne seront pas détaillés ici. Ils sont optionnels. La déclaration de tableau permet au compilateur de connaître : l identificateur qui le différencie des autres variables, le nombre d éléments (maximal), le type et donc le mode de codage de chaque élément, et donc de calculer et réserver l espace mémoire nécessaire pour stocker le tableau. int tab [4+6] ; // Ou bien : int tab [10] ; La déclaration entraîne une réservation en mémoire d un emplacement suffisant pour stocker 10 entiers. int Eléments en mémoire Indices Règle concernant les identificateurs de tableaux : les identificateurs de tableaux commencent par tab. Règle : Il est conseillé de déclarer les dimensions des tableaux comme des constantes à l'aide de la clause #define (cf. exemple 6.5). L intérêt est de pouvoir changer facilement la taille d un tableau, tout le programme faisant référence à cette constante (déclaration, accès à sa valeur lors de la saisie, ). 4 Ne pas les confondre avec les crochets donnant un élément de syntaxe optionnel. Ici, les crochets sont des éléments faisant partie de la syntaxe. 40
42 Valeur représentée par une variable de type tableau : Rappel : une variable simple est associée à (référence) un emplacement mémoire contenant un élément d un type donné. Une variable simple est, en quelque sorte, un «nom symbolique» pour accéder à un emplacement (objet) mémoire ; un type est associé à cet emplacement mémoire. Exemples : int i : i est associée à un emplacement mémoire de 4 octets codés en int. char c : c est associée à un emplacement mémoire de 1 octet codé en char. float f : f est associée à un emplacement mémoire de 4 octets codés en flottant. Règle : un identificateur de tableau n est pas associé à un emplacement mémoire, il ne donne pas accès à un emplacement mémoire comme une variable simple. L'identificateur d'un tableau est une valeur constante qui est l'adresse mémoire du premier élément du tableau en mémoire. Dans l exemple précédent, l identificateur tab est une constante qui vaut l adresse mémoire de tab[0]. Cette remarque est sans importance pour le moment et prendra sens lorsqu on parlera de passage de paramètres et de pointeurs. 6.4 Utilisation d'un tableau Opérateurs et instructions applicables globalement à un tableau Sur un identificateur de tableau, de façon globale (sans indice), et compte tenu de la remarque sur la nature d un identificateur de tableau faite au paragraphe 6.3 : aucune instruction prédéfinie n est applicable globalement à un tableau. Pas de saisie ni d affichage global. Pas d affectation (adresse constante). Comparaisons sans signification (comparaison des adresses des premiers éléments et non des valeurs contenues dans les éléments du tableau). tab = 0 n'a pas de sens, if (tab1==tab2) non plus. La seule opération possible sera le passage en paramètre effectif de fonction (cf. paragraphe 7.5.2) Opérateurs et instructions applicables aux éléments d'un tableau Accès aux éléments Syntaxe : idtab [ exprentiere ] Exemples : exprentiere représente l'indice de l'élément accédé, c est une expression de type int. Noter que les crochets [] sont ici aussi indispensables. C est la même notation que pour déclarer un tableau. tab [ 3 ] identifie le 4ème élément du tableau tab tab [ i ] tab [ i + 1] tab[i*2+k] Règle : il faut respecter la contrainte suivante : 0 <= exprentiere < taille max du tableau Remarque : la notation d accès à un élément de tableau est une expression associée/donnant accès à un objet mémoire. Par exemple, l expression «tab [3]» est associée/donne accès au 4 élément du tableau tab directement. L interprétation de la notation d accès à un élément de tableau est alors la même que pour une variable simple : elle est associée à (référence) un emplacement mémoire contenant un élément d un type donné. 41
43 Compte tenu de la nature d un identificateur de tableau, l interprétation que l on peut donner à un accès tableau est le suivant (on reprend le tableau tab précédent, tableau d int) : «tab [3]» devient «(adresse du premier élément de tab) [3]» qui consiste, à partir de l adresse du premier élément de tab, à se déplacer de 3 éléments : on accède au 4 élément. «tab [0]» devient «(adresse du premier élément de tab) [0]» qui consiste, à partir de l adresse du premier élément de tab, à se déplacer de 0 élément : on n a pas bougé, on accède au 1 élément Opérations valides sur les éléments d'un tableau Les éléments d un tableau ont le même comportement qu une variable élémentaire de même type : On peut les utiliser partout où une variable simple peut être utilisée : partie droite d'une affectation, partie gauche, expression logique (condition), expression arithmétique, appel de sousprogramme, Seule la «dénomination» dans le programme change : idtab[indice]. Utiliser l identificateur du tableau et un indice valide. On peut leur appliquer toutes les opérations du type de l élément. Exemples pour les éléments d un tableau d entiers : Expression : tab [ i ] == 5 tab[i]!= 0 tab[i]*3 Affectation : tab [ 0 ] = 25 ; Affichage : bcdisplay (tab [i]) ; Saisie : bcget ( tab [ i ] ); Parcours : S'utilise ensuite comme en algorithmique Attention : En algorithmique : n éléments : indices de 1 à n (inclus) Pour i de 1 à n faire tab[i] = 0; En langage C : n éléments : indices de 0 à n-1 (n exclu) for (i = 0 ; i < n; i = i+1) tab[i] = 0; 6.5 Déclaration de la taille d'un tableau, directive #define Souvent dans un programme, plusieurs tableaux doivent avoir une taille identique (cf. algorithmique). Un programme fait référence à la taille maximum d'un tableau : dans la déclaration du tableau, lors de la saisie d'un nombre d'éléments, éventuellement ailleurs Si on indique la taille maximum explicitement avec une valeur numérique, changer la taille maximum d un tableau implique de la changer à tous les endroits nécessaires. Une meilleure solution consiste à utiliser des symboles constantes pour désigner les tailles de tableaux. 42
44 Syntaxe de définition de constante : #define KNOMCONST expressionconstante KNOMCONST représente le nom du symbole (de la constante). expressionconstante représente la valeur qui lui est associée (expression calculable à la compilation). La clause #define s utilise habituellement en début de programme, avant le main. Règle : les noms de constantes : s'écrivent en majuscule, commencent par K (constante). Syntaxe d'utilisation de constante : Utiliser le symbole de la constante définie comme un littéral. Ce symbole est utilisable partout où on peut mettre un littéral. Une constante n est pas une variable. Toute occurrence du symbole sera remplacée textuellement par sa valeur avant compilation. cf avant compilation : La ligne «#define KMAXVALS 5» sera enlevée du programme. La ligne «float tabvaleurs[kmaxvals];» sera remplacée par : float tabvaleurs[5]; Idem pour les autres lignes référençant KMAXVALS. Remarque de programmation : des constantes sont à définir pour toutes les valeurs utilisées dans un programme et autres que la valeur entière 0 (ou 1) : les sentinelles, les booléens VRAI et FAUX, les tailles des tableaux 6.6 Compilateur et taille d un tableau Les compilateurs C ne vérifient jamais la validité de l indice d'accès à un élément d un tableau. Il est donc possible de consulter ou d'écrire en mémoire en dehors du tableau. Accéder à un élément en dehors du tableau, au mieux arrête le programme (par le système d exploitation), au pire fonctionne et donne un résultat imprévisible. Il faut donc vérifier l'utilisation des tableaux. bcget (tab [ ] ); // même tab [ -350 ] Accepté par le compilateur MAIS Erreur d'exécution sans doute D où l intérêt des constantes de taille de tableaux : permet de déclarer des petites dimensions (3 ou 5) et donc de tester des tableaux pleins lors des tests de programme. 6.7 Initialisation des éléments d'un tableau Tout comme pour les variables simples, on peut initialiser les éléments d'un tableau lors de la déclaration. Syntaxe de déclaration de tableau avec initialisation : classe modificateur type idtab [ nb_elts ] = val1, val2 ; Les accolades introduisent la liste des valeurs d initialisation des éléments du tableau. 43
45 Attention : nous sommes ici dans une déclaration de tableau avec initialisation. Le signe = ne signifie donc pas une affectation, puisque cette opération n est pas admissible sur un tableau. En résumé, cette opération est interdite comme instruction seule, hors déclaration de tableau. Ici, la déclaration nb_elts de la taille du tableau est optionnelle. Si elle est omise, le nombre de valeurs d initialisation donne la taille du tableau. Par contre, les [] restent obligatoires. Si la liste d initialisation est plus restreinte que la taille du tableau, les éléments restants sont initialisés à une valeur par défaut (souvent 0) ou pas, selon le compilateur. Exemples d initialisations : int tab1 [] = 1, 2, 3, 4 ; int tab2 [5] = 10, 20, 30, 40, 50 ; int tab3[50] = 10, 11, 12 ; // tab3[0]==10, tab3[1]==11, tab3[2]==12, // tab3[3] à tab3[49] seront initialisés à zéro Utilisation potentielle : initialiser un tableau pour tester un programme sans en faire la saisie explicite et donc augmenter la rapidité des tests. 6.8 Tableaux à plusieurs dimensions En langage C, les tableaux à plusieurs dimensions sont des tableaux de tableaux, c est-à-dire des tableaux dont les éléments sont des tableaux. Syntaxe de déclaration : classe modificateur type idtab [ nbelts1 ] [ nbelts2 ] [ nbelts3 ]... ; En général : première dimension = lignes, deuxième = colonnes. Lors de l utilisation, les indices des deux dimensions commencent à zéro. Exemple de tableau à deux dimensions : #define KNBL 2 #define KNBC 3 int t2d [KNBL][KNBC] ; Représentation mémoire : t2d [0][0] t2d [0][1] t2d [0][2] t2d [1][0] t2d [1][1] t2d [1][2] // Déclaration d un tableau de 2 tableaux de 3 int Remarque : intuitivement, la représentation est la suivante : t2d [ 0 ] 1 er tableau de 3 entiers t2d [ 1 ] 2 ème tableau de 3 entiers t2d[0] est un tableau : valeur constante qui est l'adresse mémoire de t2d[0][0], le premier élément d un tableau de 3 int. t2d[1] est un tableau : valeur constante qui est l'adresse mémoire de t2d[1][0], le premier élément d un tableau de 3 int. t2d est un tableau : valeur constante qui est l'adresse mémoire de t2d [0], le premier élément d un tableau de 2 tableaux de 3 int. Interprétation de t2d [1] [2] : 1. Se déplacer de 1 élément à partir de l adresse t2d qui donne accès à un tableau de 3 int. Appelons cette adresse tabtemp. 2. Se déplacer de 2 éléments à partir de l adresse tabtemp qui donne accès à un int. Tableaux à 2 dimensions et initialisation : 44
46 On utilise la syntaxe des tableaux à une dimension, mais avec des imbrications d accolades (c est un tableau de tableaux). Exemple de tableau à deux dimensions avec initialisation : #define KNBL 2 #define KNBC 3 int t2d [KNBL][KNBC] = 1, 2, 3, 4, 5, 6 ; // On a bien 2 fois des accolades avec 3 valeurs : 2 tableaux de 3 int 45
47 7 Sous-Programmes en langage C 7.1 Rappels et généralités Une fonction algorithmique est un sous-programme ayant x paramètres d'entrée, aucun paramètre de sortie et un seul résultat, la valeur renvoyée, de type de base (c est-à-dire non tableau). Une procédure algorithmique est un sous-programme ayant x paramètres d'entrée et en paramètre de sortie : - soit aucun, - soit un tableau, - soit plusieurs paramètres de sortie de type de base (non tableau), - soit ces deux derniers cas. Les paramètres décrits dans la déclaration et la définition d un sous-programme s appellent des paramètres formels. Les paramètres déclarés dans le programme appelant et utilisés à l appel du sous-programme s appellent des paramètres effectifs ou réels. En langage C, les sous-programmes (procédures et fonctions algorithmiques) sont tous des fonctions C. Une fonction C est déclarée dans un en-tête ou prototype (déclaration) et définie dans un corps (définition). Une fonction C sera : soit déclarée avant la fonction main puis définie après la fonction main, soit déclarée dans un fichier d en-têtes séparé (xx.h) puis définie généralement dans le fichier associé (xx.cpp). 7.2 Déclaration d'une fonction C Déclaration ou prototype ou en-tête La déclaration d une fonction est nécessaire avant son utilisation. C'est l'en-tête ou prototype de la fonction, le code source est ailleurs (définition). Syntaxe : typeretour idfonction (type1 mode de passage pnompar1 typeretour : type de la valeur retournée par la fonction. idfonction : identificateur de la fonction. typen, type2 mode de passage pnompar2... ) ; mode de passage pnomparn : déclaration d un paramètre formel, le mode de passage est optionnel. float puissance (float px, int pexp); S interprète comme une fonction dont le typeretour est float, d identificateur puissance, prenant deux paramètres, le premier d identificateur px est un float, le second d identificateur pexp est un int. 46
48 Règle 1: les identificateurs de fonction sont constitués : soit d'un verbe à l'infinitif ou d'un substantif (ex : trier, tri, recherche) éventuellement complété par un(des) sujet(s)/compléments(s), soit d'un mot décrivant le résultat (ex : minimum, maximum) éventuellement complété par un(des) sujet(s)/compléments(s), soit d'une combinaison des deux (ex : rechercherminimundesvaleurs). Règle 2 : les identificateurs des paramètres doivent commencer par p (comme paramètre), ils sont optionnels dans une déclaration mais recommandés pour les commentaires et la lisibilité. Un mode de passage devant l identificateur du paramètre peut être précisé (cf 7.2.3). Ex 1 : float puissance (float px, int pexp); Ex 2 : bool valpositivesdans ( const int ptab [], int pnb ); Ex 3 : void echange (float & pa, float & pb); Ex 4 : void saisietab (float ptab [], int pnb); Ex 5 : void rechmaxetrang ( const float ptab [], int pnb, float & pmax, int & prg ); Le compilateur utilise la déclaration d une fonction pour vérifier la validité des appels (identificateur de la fonction, types des paramètres d'appel et type de retour) + conversion éventuelle des paramètres effectifs de l appel + conversion éventuelle de la valeur de retour Commentaire des en-têtes de fonctions C Il est nécessaire de commenter tout en-tête de fonction C par une rubrique : /* N : Nom du sous-programme */ D : Description de ce que fait le sous-programme et comment sont utilisés les paramètres d entrée et de sortie, en 1 à 3 lignes (doit faire intervenir tous les identificateurs des paramètres) E : (Entrées) énumération des identificateurs des paramètres d'entrée S : (Sorties) énumération des identificateurs des paramètres de sortie R : (Return value) indique le résultat éventuellement retourné par la fonction Prec : Préconditions sur les paramètres d entrée (et de sortie éventuellement) pour la bonne exécution du sous-programme Ce commentaire revient à décrire la «boite» de définition de sous-programme en algorithmique. Ce commentaire décrit le contrat de la fonction : ce qu'elle fait Quoi : Description + Nom + Paramètres S + R sur quelles données Sur quoi : Paramètres E dans quelles conditions d'appel Quand : Préconditions Le comment sera défini par le corps de la fonction. Hors des préconditions : résultat non prédictible et sans doute faux (cf cours Structures de Données). Ce commentaire est pratique pour savoir comment utiliser : - des fonctions écrites par d'autres, - des fonctions de la bibliothèque du langage C. 47
49 7.2.3 Syntaxe de déclaration et mode de passage des paramètres formels C est la déclaration du mode de passage des paramètres formels qui indique comment s effectuera ensuite le passage des paramètres réels aux paramètres formels à l appel de la fonction dans un programme appelant Déclaration des paramètres et passage par valeur pour les types de base (non tableau) Définition : le passage par valeur d un paramètre formel indique que toute modification de ce paramètre au cours de l appel de la fonction ne sera pas répercutée sur le paramètre effectif correspondant au niveau du programme appelant. Ce mode de passage concerne les paramètres d entrée de la fonction. Pour les paramètres de type de base (non tableau) : ils sont énumérés un par un, séparés par des virgules : (type1 pnompar1, type2 pnompar2... ) Ils sont déclarés comme des variables simples. Aucun mode de passage particulier n est à préciser (par défaut : passage par valeur). float puissance (float px, int pexp); S interprète comme une fonction prenant deux paramètres, le premier d identificateur px est un float passé par valeur, le second d identificateur pexp est un int passé par valeur Déclaration des paramètres et passage par référence pour les types de base (non tableau) Les paramètres sont, par défaut, passés par valeur en langage C. Exemple de problème potentiel : sous-programme echange qui échange le contenu de 2 variables : void echangefaux (float pa, float pb) + corps // Att : cet en-tête est faux!!! Appel : float val1,val2; echangefaux ( val1,val2 ) ; // Sans effet car appel de la fonction avec passage des paramètres par valeur // => val1 et val2 non modifiées // => Utiliser un autre mode de passage des paramètres : par référence Définition : le passage par référence d un paramètre formel indique que toute modification de ce paramètre au cours de l appel de la fonction sera répercutée sur le paramètre réel correspondant au niveau du programme appelant. Ce mode de passage concerne les paramètres d entrée/sortie et les paramètres de sortie d une fonction Le type référence en langage C++ Un identificateur de variable permet de "référencer"/est associé à une variable en mémoire. Il y donne accès. On peut créer en C++ d'autres références vers une même variable en mémoire. Cela devient une sorte "d'alias" sur une même variable. Syntaxe : utiliser l opérateur de référence & lors de la déclaration de l identificateur. type autreidvar ; type & refvar = autreidvar ; refvar est une référence vers la variable associée à autreidvar. l opérateur = n'est pas une affectation mais une initialisation : initialisation de la référence. 48
50 int nbv ; int & refnbv = nbv ; Par la suite, toute modification de refnbv porte sur la variable associée à nbv. refnbv est un «alias» de nbv. Contexte nbv refnbv refnbv désigne le même emplacement mémoire que nbv. int Usage : int nbv ; int & refnbv = nbv ; nbv = 10 ; bcdisplayln (refnbv) ; // Affiche 10 refnbv = refnbv + 20 ; bcdisplayln (nbv) ; // Affiche Déclaration des paramètres formels par référence pour les types de base (non tableau) On va utiliser le type référence de C++ pour déclarer les paramètres formels d entrée/sortie ou de sortie des sous-programmes. Ces références seront initialisées par les variables utilisées comme paramètres effectifs ou réels. C est le mot clé & (à indiquer en mode de passage) devant l identificateur du paramètre à la déclaration de la fonction qui indique la déclaration du paramètre comme référence. Syntaxe : dans l'en-tête : (type1 & pnompar1, type2 & pnompar2... ) void echange (float & pa, float & pb); S interprète comme une fonction prenant deux paramètres, le premier d identificateur pa est un float passé par référence, le second d identificateur pb est un float passé par référence Remarque sur le passage par référence en C++ et le langage C «pur» Le passage de paramètres formels par référence tel que décrit au paragraphe n existe pas en langage C standard. C est une extension du langage C++ que nous utiliserons. En langage C standard, pour réaliser correctement le passage des paramètres formels de type de base en entrée/sortie ou en sortie, on aurait recours au «passage de paramètre par adresse» utilisant la notion de pointeur. Cet élément ne sera pas abordé dans ce cours. 49
51 Déclaration et passage de paramètre particulier : le type tableau Particularité du type tableau Rappel : Un identificateur de type tableau représente une adresse constante en mémoire (cf. chapitre tableaux). Il en découle que : lorsque l on passe un tableau en paramètre effectif, on passe l adresse du premier élément du tableau, le paramètre formel contient donc l adresse du même emplacement mémoire que le paramètre effectif, il s agit donc d un vrai passage par adresse. Ce qui est réellement passé en paramètre est une valeur : la valeur de l'adresse du premier élément du tableau. Le compilateur réalise donc un passage de paramètre par valeur d une adresse mémoire. Ce mode de passage spécifique aux tableaux fait que toute modification d un paramètre formel de type tableau dans une fonction est appliquée au paramètre effectif de type tableau du programme appelant Déclaration des paramètres formels de type tableau Syntaxe : typeretour nomfonction ( const type pidtab [],... ) const : mode de passage, optionnel, cf. ci-après. type : type des éléments du tableau. pidtab : identificateur du paramètre tableau. [] : crochets obligatoires car indiquent que pidtab est un tableau d éléments de type type, et non pas une variable simple. La taille maximale n'est pas indiquée car seule l'adresse mémoire du premier élément est transmise. Règle : en vertu des éléments indiqués au paragraphe , tout paramètre de type tableau déclaré sans le mot-clé const fera que toute modification du paramètre formel de type tableau sera répercutée sur le paramètre effectif correspondant. void saisietab (float ptab [], int pnb); S interprète comme une fonction d identificateur saisietab prenant deux paramètres, le premier d identificateur ptab est un tableau ([]) d éléments float modifiables, le second d identificateur pnb est un int passé par valeur. Règle : pour «simuler» un passage par valeur d un paramètre formel de type tableau, on indique le motclé const devant le type de l identificateur de tableau concerné. Ce mot-clé rend le paramètre «constant» et interdit donc toute modification sur le contenu des éléments de ce paramètre dans le corps du sous-programme. bool valpositivesdans ( const int ptab [], int pnb ); // ptab déclaré const => NON MODIFIABLE // Toute tentative de modification conduit à une erreur de compilation // Ex : bcget (ptab[i]) ; => Erreur de compilation (modification de ptab interdite (const)) // Ex : ptab[i] = 0; => Erreur de compilation (modification de ptab interdite (const)) S interprète comme une fonction d identificateur valpositivesdans prenant deux paramètres, le premier d identificateur ptab est un tableau ([]) d éléments float non modifiables (const), le second d identificateur pnb est un int passé par valeur. Ex : void rechmaxetrang ( const float ptab [], int pnb, float & pmax, int & prg ); 50
52 Fonction C sans paramètre formel Les fonctions sans paramètre doivent être déclarées avec void comme paramètre, c'est-à-dire "rien" (void signifie vide en anglais). Ex : int choixmenu ( void ) ; Valeur renvoyée ou non par une fonction C Déclaration d une fonction C avec valeur de retour Une fonction C peut renvoyer une valeur d'un type de base (jamais un tableau local), elle se comporte alors comme une fonction algorithmique. C est le mot clé du typeretour de l entête qui précise s il y a renvoi d une valeur par la fonction ou non. Rappel syntaxe : typeretour idfonction (type1 mode de passage pnompar1 typeretour précise le type de la valeur renvoyée par la fonction,, type2 mode de passage pnompar2... ) ; une telle fonction doit contenir dans son corps une instruction de retour : return expression, expression doit être du type déclaré pour la fonction (typeretour), expression est : - un littéral (ex : return 1) ; - une variable (ex : return resultat) ; - une expression (ex: return 3*i+j). Ex 1 : float puissance (float px, int pexp); S interprète comme une fonction d identificateur puissance qui renvoie un float. Ex 2 : bool valpositivesdans ( const int ptab [], int pnb ); S interprète comme une fonction d identificateur valpositivesdans qui renvoie un bool. Il est impossible de renvoyer un tableau statique déclaré dans la fonction. L instruction return arrête l'exécution en cours dans la fonction. Si d'autres lignes suivent, elles ne sont pas exécutées. Une fonction peut contenir plusieurs return (avec des tests par exemple) : à éviter! Règle : programmer de façon à n avoir qu un seul point de sortie par fonction C (une seule instruction return). Cela simplifie la compréhension d une fonction, et donc sa mise au point Déclaration d une fonction C sans valeur de retour Une fonction C peut être définie comme n'ayant aucune valeur de retour. On précise alors void comme valeur de retour dans l'en-tête. Ex 1 : void affichemenu ( void ) ; Ex 2 : void saisietab ( int ptab[], int pnb ) ; Une telle fonction se comporte alors comme une procédure algorithmique. Particularité : l'appel devient une instruction à part entière, il ne peut pas intervenir dans une expression (car pas de valeur de retour). 51
53 7.3 Définition d une fonction C La définition d une fonction C est composée d un en-tête et d un bloc (corps). Le corps contient les déclarations des variables locales et les instructions de la fonction. Syntaxe : typeretour idfonction (type1 mode de passage pnompar1, type2 mode de passage pnompar2... ) déclarations de variables locales instructions de la fonction L'en-tête doit être identique à la déclaration : typeretour, identificateur, type et ordre des paramètres formels. Les identificateurs des paramètres formels sont ici obligatoires. Les déclarations de variables locales et instructions de la fonction sont les éléments classiques en langage C. Toutes les fonctions sont disjointes (une fonction ne peut pas contenir une autre fonction). Exemples : cf. 7.7, Programmation d algorithmes de sous-programmes en langage C Passage d'une fonction algorithmique à une fonction C Une fonction algorithmique est un sous-programme ayant x paramètres d'entrée, aucun paramètre de sortie et un seul résultat, la valeur renvoyée, de type de base (c est-à-dire non tableau). Une fonction algorithmique se traduit par : une fonction C de même identificateur, la valeur de retour devient le type de retour de la fonction C, les paramètres formels d'entrée deviennent les paramètres formels de la fonction C et sont passés par valeur, void si pas de paramètre, les paramètres formels de type tableau étant passés par adresse et considérés comme des entrées pour une fonction algorithmique, la déclaration des tableaux se fera avec le mot-clé const Passage d'une procédure algorithmique à une fonction C Une procédure algorithmique se traduit par : une fonction C de même identificateur, le type de retour de la fonction C est void, les paramètres de la procédure deviennent les paramètres de la fonction C, void si pas de paramètre, les paramètres de type tableau sont passés par adresse : - en entrée : déclaration avec const, - en entrée/sortie ou en sortie : déclaration normale, paramètres de type de base : - en entrée : passage par valeur, - en entrée/sortie ou en sortie : passage par référence. Appel dans un programme : c est une instruction isolée. 52
54 7.5 Appel d'une fonction C Déclaration et passage des paramètres effectifs de type de base A l appel d une fonction : lorsque le paramètre formel est passé par valeur, il y a recopie de la valeur du paramètre réel dans un emplacement mémoire réservé pour le paramètre formel pendant l exécution de la fonction (ce qui explique que toute modification du paramètre formel n est pas répercutée au niveau du paramètre effectif du programme appelant). On pourra donc donner comme paramètre réel toute expression du type attendu (partie droite d une affectation, Rvalue). Ex : float puissance (float px, int pexp) ; int main (void) float val; val = 12.3; bcdisplayln (puissance (val, 6+5) ); // Appel de puissance dans une expression bcgetpause (); return 0; Contexte main val 12.3 float Contexte puissance px pexp 12.3 float 11 int Lors de l appel de puissance, un contexte pour ce sous-programme est créé. px désigne l emplacement mémoire qui contiendra la valeur du paramètre réel correspondant (val). Lors de l exécution de puissance, toute modification sur px n est pas répercutée sur val. Idem pour pexp. Le contexte de puissance est libéré à la fin de l exécution de puissance. lorsque le paramètre formel est passé par référence, le paramètre formel référence le même emplacement mémoire que le paramètre effectif (ce qui explique que toute modification du paramètre formel est répercutée au niveau du paramètre effectif du programme appelant). Lors d'un appel, on donnera une variable du type attendu comme paramètre réel, car ce dernier sera modifié (tout ce qu'on pourrait mettre en partie gauche d'une affectation, Lvalue). 53
55 void echange (float & pa, float & pb); int main (void) val1 = 12.3; val2 = 35.25; echange (val1, val2); bcdisplayln (val1); // affichera : bcdisplayln (val2); // affichera : 12.3 bcgetpause (); return 0; Contexte main val1 Contexte echange pa val float float pb Lors de l appel d echange, un contexte correspondant est créé. L'appel echange (val1, val2) revient à faire : «int &pa=val1» et «int &pb=val2», et donc pa et pb désignent respectivement les mêmes emplacements mémoires que val1 et val2 du main. Les modifications apportées à pa et pb porteront sur val1 et val2 respectivement. Le contexte d echange est libéré à la fin de l exécution d echange Déclaration et passage des paramètres effectifs tableaux Rq : Attention, ne pas mélanger tableau et élément de tableau ("considéré" comme une variable de base). Syntaxe : lors de l'appel d une fonction comportant un paramètre formel de type tableau, utiliser comme paramètre effectif l'identificateur du tableau seul (sans indice, donc sans crochets). C'est la seule opération globale sur un tableau qui soit possible : passage en paramètre. 54
56 void saisietab (float ptab [], int pnb); void rechmaxetrang ( const float ptab [], int pnb, float & pm, int & pr ); int main (void) int rg ; float max; float tabtest[5]; saisietab (tabtest, 3); rechmaxetrang (tabtest, 3, max, rg); bcdisplayln (max); bcdisplayln (rg); bcgetpause (); return 0; Contexte main tabtest Contexte saisietab ptab pnb 3 rg Contexte rechmaxetrang ptab max pnb pr 3 pm Attention : dans le schéma précédent les contextes sont représentés ensemble. Il faut imaginer que chaque contexte est créé à l appel du programme ou sous programme correspondant et libéré à la fin de l exécution de chacun d eux Particularité des fonctions C ayant pour paramètre un tableau Pour une fonction C ayant un paramètre formel de type tableau : pas d'indication de la taille maximale dans l'en-tête de la fonction, passer en paramètre le nombre d'éléments (idem algorithmique), peut accepter en paramètre réel des tableaux de taille maximale quelconque (en effet le sousprogramme travaille directement sur le paramètre effectif de type tableau et donc sa taille n a pas d importance, cf
57 int tab1[20], tab2[50], tab3[1000] saisietab (tab1, 20); saisietab (tab2, 50); saisietab (tab3, 1000); saisietab (tab1, 1000); // Pas d'erreur de compilation MAIS Sans doute erreur d'exécution! Appel d une fonction C Appel d une fonction C avec valeur de retour Appel dans un programme d une fonction C avec valeur de retour : le résultat de la fonction doit être "utilisé" lors de l'appel dans une expression. Peut être utilisé partout où on attend une valeur, par exemple un littéral : à droite d'une affectation, dans une expression, une condition, un affichage, un appel de sous-programme Remarque : si l appel est seul sur une ligne, le résultat est perdu. L'appel est «remplacé» par la valeur de la fonction après évaluation. Les paramètres effectifs sont énumérés, dans l'ordre/type demandé par la déclaration, séparés par des virgules. Syntaxe : idfonction (pareff1, pareff2... ) idfonction () si pas de paramètre Pour les paramètres de type de base passés par valeur : une expression quelconque du type attendu c'està-dire une valeur du type attendu. Ex : res = puissance (puissance(2,3), 4) ; appel de puissance (2, 3) donne 8.0, l'appel est remplacé par 8.0 appel de puissance (8.0, 4) donne , l'appel est remplacé par res = Pour les paramètres de type tableau : un identificateur de tableau du type attendu Appel d une fonction sans valeur de retour Appel dans un programme d une fonction sans valeur de retour : l appel doit être utilisé comme une instruction, seul sur une ligne. Les paramètres effectifs sont énumérés, dans l'ordre/type demandé, séparés par des virgules : Syntaxe : idfonction (pareff1, pareff2... ) ; idfonction () ; si pas de paramètre Pour les paramètres de type de base passés par valeur : une expression quelconque du type attendu c'està-dire une valeur du type attendu. Pour les paramètres de type de base passés par référence : un identificateur de variable du type attendu (ou bien sûr un élément de tableau). Pour les paramètres de type tableau : un identificateur de tableau du type attendu. 56
58 7.6 Une fonction C particulière : main main est une fonction particulière : point d'entrée (programme principal). C'est la fonction recherchée et lancée lorsqu'on lance le programme. Plusieurs prototypes de déclaration sont possibles mais un seul doit exister par programme. int main ( void ) Ne prend pas de paramètre : donc void renvoie un int : 0 si ok,!= 0 si problème, cf cours Système 7.7 Exemple de programme C utilisant des fonctions algorithmiques #include <bcio.h> /* */ /* Déclaration des sous-programmes */ /* */ /* N : puissance D : calcul de px à la puissance pexp E : px, pexp S : - R : retourne la valeur de px puissance pexp Prec : pexp >= 0 */ float puissance (float px, int pexp) ; /* N : valpositivesdans D : teste si le tableau ptab de pnb éléments ne contient que des valeurs positives E : ptab, pnb S : - R : retourne true si ptab ne contient que des valeurs positives et false sinon. Retourne true si pnb==0. Prec : - */ bool valpositivesdans ( const int ptab [], int pnb ); int main (void) int tabtest [5] = 102, 145, 78, 98, 325; float val; val = 12.3; bcdisplayln (puissance (val*2, 6+5) ); // Appel de puissance dans une expression val = puissance (10.25, 12) ; if ( valpositivesdans (tabtest, 5) ) bcdisplayln ("Test ok"); bcgetpause (); return 0; 57
59 /* */ /* Définition des sous-programmes */ /* */ float puissance (float px, int pexp) float res; int i; res = 1.0; for ( i=1 ; i <= pexp; i++) res = res * px; return res; // Fin de la fonction, renvoi du résultat bool valpositivesdans (const int ptab [], int pnb ) int i; bool quedespos ; quedespos = true ; for (i=0 ; (i< pnb) && (quedespos) ; i++) if (ptab [i] < 0) quedespos = false; return quedespos; // Fin de la fonction, renvoi du résultat 7.8 Exemple de programme C utilisant des procédures algorithmiques #include <bcio.h> /* */ /* Déclaration des sous-programmes */ /* */ /* N : echange D : echange le contenu de deux variables pa et pb E : pa, pb S : pa, pb R : - Prec : - */ void echange (float & pa, float & pb); /* N : saisietab D : saisie pnb éléments d'un tableau de réels ptab E : pnb S : ptab R : - Prec : pnb <= taille maximum de ptab */ void saisietab (float ptab [], int pnb); /* N : rechmaxetrang D : recherche le maximum pmax d'un tableau de réels ptab contenant pnb éléments et son dernier rang d'occurrence prg E : pnb, ptab S : pmax, prg R : - Prec : pnb >= 1 // Car si pnb ==0 ou pnb <0, pmax n existe pas!!! */ void rechmaxetrang ( const float ptab [], int pnb, float & pmax, int & prg ); 58
60 int main (void) float tabtest [5] = 102, 145, 78, 98, 325; int rang ; float val1, val2, maximum; val1 = 12.3; val2 = 35.25; echange (val1, val2); bcdisplayln (val1); // affichera : bcdisplayln (val2); // affichera : 12.3 echange (tabtest [0], tabtest [1] ); saisietab (tabtest, 5); rechmaxetrang (tabtest, 5, maximum, rang); bcdisplayln (maximum); bcdisplayln (rang); bcgetpause (); return 0; /* */ /* Définition des sous-programmes */ /* */ void echange (float & pa, float & pb) float temp ; temp = pa; pa = pb; pb = temp ; void saisietab (float ptab [], int pnb) int i; for (i=0; i<pnb; i++) bcget (ptab[i]) ; // Modification de ptab autorisée (non const) void rechmaxetrang ( const float ptab [], int pnb, float & pmax, int & prg ) int i; pmax = ptab[0]; prg = 0; for (i=0; i<pnb; i++) if ( pmax <= ptab[i] ) pmax = ptab[i]; prg = i; // ptab déclaré const => NON MODIFIABLE, toute tentative de modification conduit à une erreur de compilation // Ex : bcget (ptab[i]) ; => Erreur de compilation (modification de ptab interdite (const)) // Ex : ptab[i] = 0; => Erreur de compilation (modification de ptab interdite (const)) 59
61 7.9 Appels de fonctions : gestion de la mémoire Déclaration et domaine de visibilité des variables dans les fonctions C Déclaration : notion de bloc Un bloc est une séquence de déclarations et d instructions comprise entre et. On déclare les variables dans un bloc de définition de fonction. Les déclarations de variables doivent toujours précéder les instructions. Une variable n est visible, accessible, que dans le bloc qui la déclare. Cela s applique aussi au main. Une variable déclarée dans un bloc est qualifiée de variable locale à ce bloc. Une variable n'est donc visible et accessible que depuis et dans le bloc où elle a été déclarée, durant la durée d'exécution de ce bloc (cf ). Une variable déclarée en dehors de toute fonction est une variable globale. Une variable globale existe pendant toute la durée d exécution de l application. Parfois obligatoire car on ne peut pas faire autrement (cf cours Système/Réseau). Règle : ne pas utiliser de variables globales (sauf cas particulier bien défini). Masquage : une variable déclarée dans un bloc imbriqué masque toute variable de même nom déclarée globalement ou dans un des blocs englobant Espaces mémoires réservés à un programme La mémoire centrale (RAM) dédiée à un programme n'est pas réservée fonction par fonction (code + variables de chaque fonction) mais selon l'implantation suivante : Un segment de code : mémoire utilisée pour stocker le code de toutes les fonctions nécessaires à l'exécution du programme (déterminé à l'édition de lien), Trois segments de données : - un segment pour la pile : variables automatiques des fonctions, - un segment pour le tas : mémoire gérée par le programmeur, - un segment permanent : données globales, données statiques des fonctions. En particulier, les chaînes de caractères contenues dans le programme source (chaînes " " des affichages, ) sont stockées dans ce segment Contexte d exécution de fonction Un paramètre formel de fonction : est considéré comme une variable locale à cette fonction, possède comme une variable : - un identificateur, - un type, - et désigne un emplacement mémoire. On appelle contexte d exécution d une fonction l ensemble des informations système nécessaires au système d exploitation pour permettre son exécution. En particulier, les paramètres et les variables locales de la fonction font partie de ce contexte. 60
62 7.9.4 Utilisation de la mémoire Principe d'utilisation de la mémoire : Au lancement du programme : - seul le segment de code est chargé, de même que le segment permanent (les variables globales et statiques du segment permanent sont créées), - aucune variable de n importe quel sous-programme n'est placée en mémoire. Dès qu'une fonction f est appelée, y compris le main : - le contexte de la fonction f (comprenant les variables et les paramètres) est placé en mémoire, à un endroit "libre" dans le segment de pile, - f est exécutée, - à la fin de l exécution de la fonction f, le contexte de f présent dans la pile est effacé (ses variables et paramètres sont donc "effacés" de la mémoire). => Une fois la fonction f terminée, la mémoire occupée par f peut être utilisée pour un autre appel de fonction. Intérêt : ne placer en mémoire à un instant t que les variables des fonctions "actives" et non terminées. Les variables des fonctions non encore appelées "n'existent pas". => Gain de place mémoire Cette gestion est assurée par le compilateur qui insère dans le programme les instructions nécessaires pour réaliser cela. Soit un programme main, appelant successivement 100 fonctions différentes déclarant chacune en variable locale un tableau de int. Si les 100 tableaux existaient au lancement : 4 octets x 100 x = 4 MO Un seul tableau présent à tout instant en mémoire : 4 octets x = 40 KO nécessaires Ainsi, deux variables de même identificateur déclarées dans deux fonctions différentes ne référencent pas le même emplacement mémoire. Elles sont différentes. Une variable déclarée dans une fonction n'est pas accessible depuis une autre fonction. Si une fonction est appelée 1000 fois, le contexte est créé et supprimé 1000 fois. Lorsqu'on retourne une valeur (instruction return), c'est la valeur de l'expression qui est retournée. Cela explique pourquoi un tableau déclaré localement ne peut pas être retourné par une fonction car on renverrait une adresse sur un emplacement mémoire qui n'est plus accessible après l exécution de la fonction. 61
63 8 Chaînes de Caractères 8.1 Introduction et conventions concernant les chaînes de caractères Introduction aux chaînes de caractères Le type chaîne de caractères permet de traiter une suite de caractères ASCII comme un tout. C'est un type de donnée qui correspond à la notion triviale de libellé : noms, prénoms, lignes d'adresse, ville, libellé de produit, Un libellé est une suite de caractères différents les uns des autres, ordonnés les uns par rapport aux autres, et qui forme une valeur. Il existe un ensemble d'opérations envisageables afin de manipuler les chaînes : affectation : affecter une chaîne à une variable, lecture : lire sur un périphérique une chaîne et la ranger dans une variable, écriture d une chaîne sur un périphérique, comparer deux chaînes pour dire laquelle est la plus grande ou la plus petite, modifier les chaînes : passage en majuscules, en minuscules, extraire une partie de chaîne dans une chaîne, concaténer une chaîne à la fin d'une autre chaîne, accéder à un caractère particulier d'une chaîne, Une propriété intéressante des chaînes est leur longueur. La longueur est le nombre de caractères contenus dans la chaîne. "Aurélien" : longueur 8, "Bye" longueur 3. Les chaînes de caractères ont été manipulées, pour le moment, sous forme de littéraux (valeurs littérales), par exemple dans les sorties écran (cf bcdisplay) Exemple de programme utilisant des chaînes de caractères en langage C #include <bcio.h> #include <string.h> #include <stdio.h> #define KTAILLE (50+1) // Programme manipulant des chaînes de caractères int main (void) char nom [KTAILLE], prenom [KTAILLE]; char rue [KTAILLE], cpetville [KTAILLE]; int lg ; bcdisplayln ("Donnez votre nom, prenom, adresse (rue puis cp/ville)"); bcget (nom); bcget (prenom); bcget (rue); bcget (cpetville); 62
64 bcdisplayln (); printf ("Madame/Monsieur %s %s\n", nom, prenom); strlwr (rue) ; strupr (cpetville); printf (" Vous habitez %s a %s\n\n", rue, cpetville); lg = strlen (nom); bcdisplay (" Votre nom comporte ", lg ); bcdisplayln (" caracteres"); bcdisplayln (); if ( strcmp (nom, "Dupont") == 0 ) bcdisplayln ("Bienvenue, Monsieur ", nom); else bcdisplayln ("Desole, je ne vous connais pas"); bcgetpause (); return 0; Convention concernant les chaînes de caractères en langage C Préambule En C, le type chaîne de caractères, n existe pas en tant que tel. Il n'existe pas un nom spécifique de type pour les chaînes offrant des opérations de manipulation de chaînes (affectation, ). Du point de vue de l homme, nous manipulons des informations sous forme de nombres ou de chaînes de caractères tous les jours, de façon indifférente. D un point de vue de la machine, l absence d un type «chaîne de caractères» est liée à la particularité des chaînes : la représentation d'une chaîne peut avoir une longueur variable. En général, les types en machine, représentables en RAM et/ou traitables par le processeur, ont une longueur fixe. Remarque : int : 2 ou 4 octets mais fixe, quelle que soit la valeur à représenter, tous les octets sont utilisés : nombre d'octets "utiles" non lié à la valeur. chaîne : nécessite x caractères de la chaîne, dépend de la valeur : nombre d'octets "utiles" lié à la valeur. "Aurélien" : au moins 8 caractères, "Bye" au moins 3 caractères. Le fait qu il n y ait pas de type spécifique chaîne de caractères entraîne que : il n existe pas d ensemble de valeurs + mode de codage (comme pour les int par exemple), il n existe pas d opérations (opérateurs) prédéfinies : affectation, concaténation, Définition : Pour manipuler des chaînes de caractères en langage C, on va les considérer comme ce qu elles sont en mémoire, c est-à-dire des caractères stockés les uns à coté des autres. En langage C, une chaîne de caractères se stocke, en mémoire, dans un tableau de caractères Convention pour les chaînes de caractères en langage C Le fait de mettre une chaîne de caractères dans un tableau, s il résout le problème du stockage, ne résout pas celui de la gestion de la longueur. 63
65 Gérer la longueur d une chaîne comme un traitement «classique» de tableau, dans une variable comportant le nombre d éléments utilisés, n est pas «pratique» dans le cadre des chaînes de caractères. Convention pour les chaînes de caractères en langage C : En langage C, une chaîne de caractères se stocke en mémoire dans un tableau comportant, en fin des caractères de la chaîne, le caractère spécial '\0' de code ASCII 0. Ces chaînes s'appellent des ZTS : Zéro Terminated String. Cette convention permet de repérer la fin d'une chaîne lors des traitements (affichages, copies, ). On n a plus besoin de connaître le nombre d'éléments utilisés dans le tableau : il suffit de parcourir le tableau jusqu'à trouver '\0'. Une chaîne se stocke dans un tableau par : un char pour chaque caractère de la chaîne (espaces compris), un char de fin en plus valant TOUJOURS '\0', éventuellement des char de fin du tableau, non utilisés. Définition : Une valeur littérale chaîne de caractères se repère par des ". Le \0 est implicite. Exemples : La chaîne de caractères "BONJOUR" sera stockée en mémoire dans des char (octets) contigus : 'B' 'O' 'N' 'J' 'O' 'U' 'R' '\0' La chaîne de caractères "" sera stockée en mémoire dans des char (octets) contigus : chaîne spéciale de longueur zéro caractères. '\0' La chaîne "A" sera stockée en mémoire dans des char (octets) contigus : Attention : 'A' '\0' En langage C, les notations "A" et A n ont rien à voir même si elles paraissent très «ressemblantes». 'A' est une valeur littérale de type char sur un seul octet. 8.2 Manipulation de chaînes de caractères Déclaration d'une variable pour manipuler une chaîne de caractères Définition : Une variable contenant une chaîne de caractères sera déclarée en langage C comme un tableau de char. Il est nécessaire de réserver de la place mémoire pour le nombre maximal de caractères significatifs que l on veut stocker, PLUS un char pour le caractère de fin de chaîne ( \0 ). Attention : la déclaration d un tableau de char ne positionne aucun caractère '\0' dans le tableau. Une telle déclaration ne fait que réserver un tableau de caractères. 64
66 #define KTAILLE (50+1) char nom [KTAILLE], prenom [KTAILLE] ; Par extension, on désigne un tableau de caractères qui va stocker une chaîne de caractères sous le nom de «chaîne». nom et prenom sont appelés des chaînes. Ne jamais oublier que : ce sont des tableaux, ils doivent contenir un '\0' pour être traités comme des chaînes. Remarque : dans un programme, toutes les chaînes sont stockées dans un tableau, même les chaînes littérales du code source. Par exemple, dans bcdisplay ("Programme TP 11"), la chaîne "Programme TP 11" est stockée dans un tableau situé dans une zone de données spéciale, le segment permanent de données. Remarque/Convention : on définira les constantes de tailles de chaînes en indiquant systématiquement le nombre de caractères utiles «plus un» pour le caractère \0. Pour ce faire, on utilisera les parenthèses dans la clause #define pour «entourer» l expression et éviter des calculs ultérieurs erronés dans le programme, erreurs difficiles à localiser. #define KTAILLE (50+1) // Plutôt que #define KTAILLE 51 // Mauvaise syntaxe #define KTAILLE Traitement des chaînes de caractères Pour manipuler les chaînes : Déclarer des tableaux de char pour les stocker. Les manipuler : o soit comme un tableau (caractère par caractère) utilisé pour accéder aux différents char contenus dedans, o soit à travers des sous-programmes respectant la convention des chaînes ZTS (suite de caractères terminée par \0, cf paragraphe ). Il n existe aucune instruction directe manipulant des chaînes de caractères car ce sont des tableaux. Il existe des fonctions permettant de manipuler les chaînes (longueur, recopie, ) qui utilisent les conventions suivantes : leurs paramètres seront des tableaux de caractères, lorsque le paramètre est en entrée, c est une ZTS et elles utilisent les caractères entre les indices 0 et celui précédant le premier caractère '\0' rencontré, lorsque le paramètre est en sortie, elles construisent une ZTS, c est-à-dire une chaîne contenant les caractères de la chaîne PLUS le caractère '\0' à la fin (sauf cas particuliers). 8.3 Saisie et affichage de chaînes de caractères Les fonctions classiques de la bibliothèque bcio.h (bcget, bcdisplay) traitent les chaînes de caractères de façon correcte, c est-à-dire comme indiqué au De même, la fonction printf de stdio.h traite les chaînes de caractères en utilisant un spécifieur de format spécifique (cf documentation sur printf : %s). 65
67 8.3.1 Saisie de chaînes de caractères avec bcget Syntaxe : bcget ( char identchaine [] ) ; Prend en paramètre un tableau de caractères (une chaîne). bcget réalise les opérations suivantes : saisit les caractères frappés jusqu'au premier Retour Chariot rencontré et les place dans le tableau identchaine, dans l ordre de la saisie, à partir de l indice 0. Le retour chariot ne fait pas partie du résultat, place le caractère '\0' à la fin de la chaîne, bcget ne réalise aucun contrôle de débordement du tableau. char ch [10] ; bcget (ch) ; Saisie de : Peninou<return> Contenu : 'P' 'e' 'n' 'i' 'n' 'o' 'u' '\0' Indices : ACCES ANORMAL!!! Saisie de : Contenu : 'a' 'n' 't' 'i' 'c' 'o' 'n' 's' 't' 'i' 't' 'u' 't' 'i' 'o' 'n' '\0' anticonstitution<return> Indices : Affichage de chaînes de caractères avec bcdisplay Syntaxe : bcdisplay ( const char identchaine [] ) ; Prend en paramètre un tableau de caractères (une chaîne). Elle attend un const char [] : tableau de char constant (non modifié car en entrée uniquement). bcdisplay affiche tous les caractères de identchaine à partir de l'indice 0 jusqu'au premier '\0' rencontré. char ch [10] ; bcget (ch) ; bcdisplay (ch); Sur l exemple du paragraphe 8.3.1, bcdisplay parcourt et affiche le tableau ch de l indice 0 au premier \0 trouvé en mémoire. 8.4 Quelques fonctions de manipulation de chaînes de caractères Toutes les fonctions décrites ci-dessous appartiennent à la bibliothèque string.h Longueur d'une chaîne de caractères : strlen Pour les variables chaînes de caractères, qui sont des tableaux en C, on distingue deux longueurs : la longueur maximale qui est le nombre maximal de caractères que l on peut stocker dans la variable, soit la taille du tableau 1 (pour le \0 qui est obligatoire), la longueur ou longueur utile qui est le nombre de caractères qui constituent la chaîne stockée dans la variable. 66
68 Soit la déclaration char ch[12] ; Supposons que ch contienne la chaîne "Bonjour". 'B' 'o' 'n' 'j' 'o' 'u' 'r' '\0' Longueur maximale de ch : 12-1 == 11. Longueur de ch : 7 (longueur utile). La fonction suivante permet de connaître la longueur d une chaîne de caractères. int strlen ( const char pch [] ) ; /* N : strlen D : calcule la longueur de la chaîne pch. E : pch S : - R : retourne le nombre de caractères de pch, '\0' NON compté Prec : pch est une ZTS */ Remarque : le fait que le paramètre pch soit passé en const permet d utiliser, à l appel, un littéral chaîne de caractères entre guillemets (" "). bcdisplay ( strlen ("Bonjour") ) ; // Affiche 7 Exemple de problème potentiel : char ch2 [51]; bcdisplay ( strlen (ch2) ) ; // Affiche n'importe quoi car ch2 non initialisée // Comptera les caractères jusqu'à trouver un '\0' en mémoire Recopie d'une chaîne de caractères : strcpy La fonction strcpy permet de recopier une chaîne dans une autre, plus exactement la chaîne contenue dans une variable chaîne de caractères dans une autre variable chaîne de caractères. strcpy ( char pdest [], const char psource [] ) ; /* N : strcpy D : effectue une recopie de la chaîne psource dans pdest. E : psource S : pdest R : - (mais ce n'est pas tout à fait vrai...) Prec : taille max du tableau pdest >= strlen (psource) + 1 */ strcpy effectue : une copie des caractères de psource dans pdest, aux mêmes indices, jusqu'à trouver '\0' dans psource, ajoute le caractère '\0' à la fin de pdest, après le dernier caractère copié. Remarque : le fait que le paramètre psource soit passé en const permet d utiliser, à l appel, un littéral chaîne de caractères entre guillemets (" "). Ce n est pas le cas de pdest qui devra, à l appel, toujours être une variable de type tableau de char ou équivalent. 67
69 Contrainte : psource doit contenir un '\0', la précondition stipule que le tableau pdest doit être «assez grand» pour contenir tous les caractères de psource + le caractère '\0' : le tableau pdest doit avoir une taille max >= strlen(source)+1. La fonction strcpy renvoie quelque chose MAIS on s'en sert comme une procédure, typique en C. On n en dira pas plus ici cf On peut considérer que cette fonction remplace «l'affectation des variables classiques» pour les chaînes de caractères. Mais l affectation de chaînes (opérateur =) n est pas permise en C puisque ce sont des tableaux de caractères. char nom1[51], nom2 [51], ch1[5]; strcpy (nom1, "Dupontel"); strcpy (nom2, nom1); bcdisplay (nom2); // Affiche : "Dupontel" // INCORRECT : strcpy (nom2, ch1); // ch1 n'est pas initialisée strcpy (ch1, "AUREVOIR"); // ch1 est de taille insuffisante (précondition non respectée) strcpy ("Bonjour", nom1); // "Bonjour" est une chaîne const Comparaison de chaînes de caractères : strcmp La comparaison de chaînes consiste à comparer deux chaînes, en partant du début et à chercher une différence entre les deux chaînes. Ce sont donc des char qui sont comparés. Attention compte tenu du codage des caractères, les majuscules sont considérées «plus petites» que les minuscules. int strcmp ( const char pch1 [], const char pch2 [] ) ; /* N : strcmp R : comparaison des chaînes pch1 et pch2 E : pch1, pch2 S : - R : retourne 0 si pch1 est identique à pch2 > 0 si pch1 est supérieure à pch2 ("après" sur les codes ASCII) < 0 si pch1 est inférieure à pch2 ("avant" sur les codes ASCII) Prec : - */ Remarque : le fait que les paramètres pch1 et pch2 soient passés en const permet d utiliser, à l appel, des littéraux chaînes de caractères entre guillemets (" "). if ( strcmp ("ancien", "Bonjour") > 0 ) printf("plus grand"); // Affichage de "plus grand" car a est > B bcget (nom); if ( strcmp (nom, "Dupont") == 0 ) bcdisplay ("Vous êtes bien Dupont."); 68
70 Exemples de comparaisons : ch1 ch2 valeur de strcmp (ch1, ch2) "BON" "BON" 0 "bon" "bon" 0 "BON" "BONJOUR" négatif "BON" "AUREVOIR" positif "BON" "DON" négatif "BON" "bon" négatif "bon" "BON" positif "jepensedoncjesuis" "je pense donc je suis" positif (code de : 32, donc < p ) "LeBon" "Le Bon" positif (idem) "LeBon" "le Bon" négatif ( L < l ) " " "ASSEZ" Négatif : 1 code Ascii 49 donc < A Fonctions C de manipulation de chaînes de caractères Fonctions classiques int strlen ( const char pch[] ) ; E : pch / S : - cf : retourne la longueur de la chaîne pch, \0 non compté. int strcmp ( const char pch1 [], const char pch2 [] ) ; E : pch1, pch2 / S : - cf : comparaison des chaînes pch1 et pch2, cf.. Retourne : 0 si pch1 est identique à pch2 > 0 si pch1 est supérieure à pch2 ("après" sur les codes ASCII) < 0 si pch1 est inférieure à pch2 ("avant" sur les codes ASCII) int strcasecmp ( const char pch1 [], const char pch2 [] ) ; E : pch1, pch2 / S : - Idem strcmp sans distinction des majuscules et des minuscules. strcpy ( char pdest [], const char psource [] ) ; E : psource / S : pdest cf : effectue une recopie de la chaîne psource dans pdest. Le tableau pdest doit avoir une taille max >= strlen (psource) + 1. strcat ( char pdest [], const char psource[] ) ; E : pdest, psource / S : pdest Concatène («ajoute») psource à la fin de pdest. Le tableau pdest doit avoir une taille max >= strlen(pdest) + strlen (psource) + 1. char nom [21], prenom [21], identite [41]; strcpy (prenom, "Marcel"); strcpy (nom, "Dupont"); strcpy (identite, prenom); // "Marcel" strcat (identite, "-"); // "Marcel-" strcat (identite, nom); // " Marcel-Dupont" 69
71 strlwr ( char pch [] ) ; E : pch / S : pch Transforme les majuscules en minuscules dans la chaîne pch. strupr ( char PCh [] ) ; E : pch / S : pch Transforme les minuscules en majuscules dans la chaîne pch Fonctions de manipulation de parties de chaînes int strncmp ( const char pch1 [], const char pch2 [], int pn) ; E : pch1, pch2, pn / S : - Idem strcmp pour les pn premiers caractères des deux chaînes. int strncasecmp ( const char pch1 [], const char pch2 [], int pn ) ; E : pch1, pch2, pn / S : - Idem strncmp sans distinction des majuscules et des minuscules. strncpy ( char pdest [], const char psource [], int pn ) ; E : psource, pn / S : pdest Copie au plus les pn premiers caractères de la chaîne psource dans la chaîne pdest. Le '\0' n'est pas toujours ajouté en fin de chaîne. strncat ( char pdest [], const char psource[], int pn) ; E : pdest, psource, pn / S : pdest Concatène («ajoute») au plus les pn premiers caractères de la chaîne psource à la fin de la chaîne pdest. Le '\0' n'est pas toujours ajouté en fin de chaîne Remarques concernant les en-têtes de fonctions de la bibliothèque string.h données dans ce document Les déclarations de fonctions de la bibliothèque string données tout au long de ce paragraphe 8.4 sont et «justes» et «fausses» Valeurs de type int de certains en-têtes Si vous consultez une documentation quelconque de la fonction strlen, vous trouverez l en-tête suivant : size_t strlen ( const char pch [] ) ; Mais que signifie size_t C est en fait un nom de type qui est en général équivalent à unsigned int, et défini dans les bibliothèques du compilateur. On admettra qu il équivaut à int dans notre contexte d utilisation. Pour la définition de types, cf 9.9. De même, toutes les fonctions du type strnxxx prennent en paramètre non pas des int mais des size_t. Notons que ce n est pas le cas de la valeur de retour des fonctions de comparaisons (strxxxcmp) qui peuvent renvoyer des valeurs négatives ; le unsigned ne peut donc pas s appliquer pour ces valeurs de retour Valeurs renvoyées par les fonctions On aura noté que certains en-têtes sont incomplets. Par exemple l en-tête : strcpy ( char pdest [], const char psource [] ) ; est incomplet puisqu on aurait du donner une valeur de retour de la fonction, c est-à-dire void ou autre. 70
72 Le vrai en-tête de la fonction strcpy aurait pu être : char * strcpy ( char pdest [], const char psource [] ). Mais que signifie «char *» Notons simplement que «char *» dénote un «pointeur sur char». Oublions le pour le moment et supposons que l en-tête aurait pu comporter void à la place. Comme ce n est pas le cas, nous avons souvent omis les valeurs de retour pour ne pas donner des «entêtes faux» Paramètres en entrée des fonctions Si vous consultez une documentation quelconque de la fonction strcpy, vous trouverez l en-tête suivant, le vrai en-tête de strcpy : char * strcpy ( char * pdest, const char * psource) ; Pour la valeur de retour, l explication en est donnée au paragraphe Pour les paramètres, ils ne sont pas déclarés en char[] mais en char *. Mais que signifie «char *» Encore une fois, on va l oublier pour le moment. Cette remarque vaut pour les autres fonctions de la bibliothèque et tous les paramètres de type char[]. Notons simplement que «char *» dénote un «pointeur sur char». Une telle variable peut contenir l adresse mémoire d un char. Ce type est donc compatible avec un passage de paramètre tableau puisqu à l appel, on enverra l adresse du premier élément d un tableau de char. On acceptera l équivalence : en déclaration de paramètre en en-tête de fonction : char [] <==> char * 8.5 Initialisation d'une chaîne de caractères Tout tableau de caractères doit être initialisé correctement pour pouvoir être utilisé comme une chaîne, c est-à-dire utilisé en paramètre des fonctions de manipulation de chaînes : ils doivent contenir une ZTS. Solution 1 : Initialisation par saisie : utiliser bcget. Solution 2 : Initialisation par recopie : strcpy. strcpy (chaine, ""); // Initialisation «à vide» strcpy (chaine, "Hello"); Solution 3 : Initialisation d'une chaîne comme un tableau de caractères. char chaine[10]= "Bonjour"; Contenu : 'B' 'o' 'n' 'j' 'o' 'u' 'r' '\0' Indices : Equivaut à : chaine[10]= B, o, n, j, o, u, r, \0 ; char chaine [10]= ""; Contenu : '\0' Indices : Equivaut à : chaine[10]= \0 ; Hors initialisation, l affectation d une valeur à un tableau de caractères est illégale. 71
73 Solution 4 : Initialisation "manuelle" d'une chaîne. Affecter les caractères case par case, sans oublier le dernier '\0'. char chaine [10]; chaine [0] = 'B'; chaine [1] = 'Y'; chaine [2] = 'E'; chaine [3] = '\0'; // Ne pas oublier dans ce cas!!! Une chaîne de caractères sera toujours manipulable comme un tableau : élément par élément en donnant l'indice. char chaine [10]; int i; for (i=0; i<5; i++) chaine[i] = i + 'A'; chaine[5] = '\0'; printf("%s\n", chaine); // Affiche "ABCDE" 72
74 9 Les enregistrements 9.1 Introduction aux enregistrements Un chapitre entier a été dédié aux tableaux. Ils permettent de regrouper des données homogènes en type, contiguës en mémoire, et d'y accéder en donnant un indice. Un enregistrement permet de manipuler des données structurées, hétérogènes en type, contiguës en mémoire, et d'y accéder en donnant un "nom" de champ correspondant à une donnée de la structure. On manipule, dans la vie courante, des informations complexes et structurées. Par exemple, les données sur un individu : nom, prénom, date de naissance, adresse constituée d'un numéro, rue, code postal, ville la carte d'identité, le dossier d'inscription à l IUT, Définition : un enregistrement est une structure de données qui permet de regrouper des données caractéristiques d'une même entité. Les données sont structurées sous une forme hiérarchique constituée de champs (ou membres) nommés. Individu numero nom adr numero rue cpetville age taille Pour manipuler ces données, on ne peut le faire qu au travers de variables. Mais déclarer une variable impose de lui donner un type. Il est donc nécessaire de déclarer le type avant de pouvoir déclarer des variables du type. Une déclaration de type permettra de définir les différents champs qui seront disponibles sur la variable de ce type. 9.2 Déclaration d'un type structure : struct Syntaxe de déclaration d un type structure Syntaxe : typedef struct type idchamp1 ; type idchampn ; IdentType ; Cette déclaration permet : de déclarer un type (typedef) dont l identificateur est IdentType, ce type est structuré (mot-clé struct ), ce type définit les champs idchamp1, Une telle définition de type est dénommée enregistrement ou structure ou plus rapidement struct. En général, la déclaration d un type se fait en début de fichier, hors de toute fonction, et plutôt dans un fichier ".h". Règle de programmation : les identificateurs de types (IdentType ci-dessus) commencent obligatoirement par une majuscule ou commencent par T (pour Type), suivi du nom du type. La syntaxe de déclaration des champs est la même que celle des identificateurs de variables ; mais les champs ne sont pas des variables. 73
75 Règle : les identificateurs de champ commencent par une minuscule. Les identificateurs de champ doivent être uniques dans une structure (au même niveau d'imbrication), mais un même identificateur de champ peut être utilisé dans des structures différentes. #define KLGLIB (20+1) typedef struct int numero ; char nom [KLGLIB] ; int age ; float taille ; TIndividu ; Types possibles pour les champs d'une structure Les champs d'une structure peuvent prendre un type de base quelconque, le type tableau, le type pointeur, et même le type structure. 9.3 Utilisation de variables de type structure Déclaration de variable simple de type structure La déclaration d une variable simple de type structure se fait comme une déclaration classique. Syntaxe : IdentType idvar1, idvar2 ; Cette syntaxe déclare : des variables d identificateurs idvar1, ces variables sont de type IdentType et comporteront donc les champs définis par le type IdentType. TIndividu untel, ind2; Initialisation à la déclaration : ajout des valeurs entre accolades. Syntaxe avec initialisation : IdentType idvar = valchamp1, valchamp2 ; 74
76 TIndividu untel = 301, "Machin", 25, 1.70, ind2; Représentation structurée (gauche) et représentation mémoire (droite) : untel numero 301 int numero nom age 301 "Machin" 25 nom M a char [21] taille 1.70 age 25 int taille 1.70 float ind2 numero nom age taille ind2 numero nom int 0 20 int char [21] taille float Accès aux champs d'une variable de type structure : opérateur "." Syntaxe : idvar.idchamp Un champ s'utilise et se comporte comme une variable du même type : on peut lui appliquer les opérations compatibles avec son type. Exemples et interprétation du type de l expression : untel.taille Expression Type untel TIndividu untel.taille float untel.numero Expression untel TIndividu untel.numero int Type //Affecter "Dupont" au nom : untel.nom Expression Type untel TIndividu untel.nom char[21] (chaîne de caractères) => strcpy (untel.nom, "Dupont"); untel.nom[0] = 'D'; Expression Type untel TIndividu untel.nom char[21] (chaîne de caractères) untel.nom [0] char 75
77 Exemples : untel.age = untel.age + 1; bcget (untel.taille); bcget (untel.age); if (untel.taille >= 1.70) bcdisplayln ("Plutot grand ") ; bcget (untel.nom); if (strcmp (untel.nom, "Dupont")==0) bcdisplayln ("Bonjour les frères ") ; strcpy (untel.nom, "Dupont"); bcdisplay (untel.nom); bcdisplay (untel.age); 9.4 Opérations applicables globalement à une variable de type structure On s intéresse ici aux opérations qui peuvent être appliquées «globalement» à une variable de type structure. On entend par là, les opérations applicables à une expression de type structure, sans préciser de champ à l intérieur de la structure. Une seule opération est applicable globalement à une variable de type structure : l affectation. Aucune autre opération n est applicable globalement à une variable de type structure. En particulier, la comparaison, la saisie ou l affichage de structures n ont pas de sens et doivent se faire champ par champ, sur des champs de type autre que structure. L opération affectation entre variables de même type structure implique, de la part du compilateur, une recopie «à l identique» du contenu binaire de la zone mémoire à affecter. Ceci produit l effet suivant selon le type des champs : types de base : recopie de la valeur (comme une affectation classique), tableaux : recopie élément par élément, structure : recopie champ par champ. Remarque : bien noter la particularité ici de l effet produit sur les champs tableaux. Alors que l affectation directe de tableaux n est pas possible en C, l affectation de structures contenant un tableau provoque bien la recopie intégrale de chaque élément de tableau. Ceci est lié au fait que l affectation de structure n est pas réalisée par une affectation champ par champ par le compilateur, mais par une «simple» recopie du contenu binaire des zones mémoires mises en jeu. 76
78 ind2 = untel; // Recopie de chaque champ // Les chaînes sont des tableaux => recopie de toute la chaîne Représentation structurée (gauche) et représentation mémoire (droite) : à comparer avec l image mémoire suite à la déclaration au untel numero 301 int numero nom age 301 "Machin" 25 nom M a char [21] taille 1.70 age 25 int taille 1.70 float ind2 numero 301 int numero nom 301 "Machin" nom M a... 0 char [21] age taille 1.70 age 25 int taille 1.70 float 9.5 Exemple simple de programme utilisant les structures #include <bcio.h> #define KLGLIB (20+1) // Déclaration du type TIndividu typedef struct int numero ; char nom [KLGLIB] ; int age ; float taille ; TIndividu ; int main (void) // Déclaration de variables de type TIndividu int i, j ; TIndividu untel, ind2 ; 77
79 bcdisplayln ("Saisissez votre identité : "); bcdisplay (" Votre nom : "); bcget (untel.nom); bcdisplay (" Votre age : "); bcget (untel.age); bcdisplay (" Vous mesurez : "); bcget (untel.taille); untel.numero = 1001; ind2 = untel ; bcdisplayln ("Vous êtes : "); bcdisplayln (" Numéro : ", ind2.numero); bcdisplayln (" ", ind2.nom); bcdisplayln (" âgé de : ", ind2.age); bcdisplayln (" haut de : ", ind2.taille); bcgetpause (); return 0; 9.6 Structures imbriquées Un champ de structure peut avoir pour type une autre structure. L accès aux champs de type structure se fait par l opérateur «classique» ".". Les opérations applicables à un champ de type structure sont celles du type structure, à savoir : affectation et accès à un champ. #define KLGLIB (20+1) typedef struct int numerorue ; char rue [KLGLIB]; char codepostal [5]; char ville [KLGLIB]; TAdresse ; typedef struct int numero ; char nom [KLGLIB] ; int age ; float taille ; TAdresse adrind; TIndividuAdr ; On a ajouté au type TIndividu un champ : d identificateur : adrind, de type : TAdresse. 78
80 Déclaration de variable : TIndividuAdr untelavecadresse ; Interprétation de l expression : untelavecadresse.adrind Expression Type untelavecadresse TIndividuAdr untelavecadresse.adrind TAdresse Interprétation de l expression : untelavecadresse.adrind.numerorue Expression Type untelavecadresse TIndividuAdr untelavecadresse.adrind TAdresse untelavecadresse.adrind.numerorue int Interprétation de l expression : untelavecadresse.adrind.ville Expression Type untelavecadresse TIndividuAdr untelavecadresse.adrind TAdresse untelavecadresse.adrind.ville char [21] => strcpy (untelavecadresse.adrind.ville, "Toulouse") ; Représentation structurée (gauche) et représentation mémoire (droite) : numero int untelavecadresse numero nom age taille adrind numerorue rue codepostal ville "Toulouse" nom age taille adrind.numerorue adrind.rue adrind.codepostal int float int char [21] char [21] char [5] adrind.ville T char [21] 9.7 Tableaux et structures Tableaux de structures Un tableau peut contenir des éléments de type structure. Chaque élément se manipule comme une variable "simple" de type structure : on peut lui appliquer les opérations légales sur une structure (affectation et accès aux champs). 79
81 TIndivividu tabpers [50]; for (i=0 ; i<50 ; i++) strcpy (tabpers [i].nom, "Dupont") ; for (i=0 ; i<50 ; i++) afficherindok (tabpers [i]); tabpers[0] = tabpers [1]; // Affectation d un TIndividu à un TIndividu Structures dont un champ est un tableau Ce point a déjà été vu dans la déclaration des structures, cf Structures dont un champ est un tableau de structures Une structure peut aussi contenir un tableau dont les éléments sont des structures. C est donc une combinaison des deux points précédents (9.7.1 et 9.7.2), sans remarque syntaxique particulière à ajouter. Exemple de programme : TListeInd etu1a; #define KMAXIND (100) typedef struct TIndividuAdr tabind [KMAXIND]; int nbind ; TListeInd ; etu1a.nbind = 1; strcpy ( etu1a.tabind [0].nom, "Dupont"); etu1a.tabetu [0].age = 18; etu1a.tabetu [0].adrInd.numeroRue = 10; strcpy ( etu1a.tabind [0].adrInd.rue, "Rue des Altas"); Interprétation de l expression : etu1a.tabind [0].adrInd.rue Expression Type etu1a TListeInd etu1a.tabind TIndividuAdr [100] etu1a.tabind [0] TIndividuAdr etu1a.tabind [0].adrInd TAdresse etu1a.tabind [0].adrInd.rue char [21] 80
82 etu1a Représentation structurée : [ 0] [ 1] [99] tabind numero nom "Dupont" numero nom numero nom age 18 age age taille taille taille adrind numerorue rue codepostal 10 "Rue des Altas" adrind numerorue rue codepostal adrind numerorue rue codepostal nbind 1 ville ville ville 9.8 Fonctions et structures Le seul point particulier de l utilisation de structures dans les fonctions concerne le passage de paramètres. Comme pour les types de base, on distinguera le passage de paramètre d une variable simple de type structure, et le passage de paramètre d une variable de type tableau de structure Passage de paramètre de type structure En paramètre ou valeur de retour d'une fonction, une structure se comporte comme un type de base : passage par valeur si rien n'est précisé, quel que soit le contenu de la structure. Une structure étant un type déclaré et pour lequel l affectation existe, en paramètre de sous-programme, une structure est «assimilable» à un type de base. Les règles définies pour les types de base s'appliquent sur les structures. Les paragraphes ci-après reprennent ces règles en y ajoutant des remarques propres aux structures Passage de paramètre de type structure par valeur Par défaut, le passage d une structure en paramètre se fait par valeur. // E : pind void afficherindexemple (TIndividu pind) ; Appel : TIndividu vi; afficherindcontreexemple (vi); // Recopie de vi dans pind Ce mode de passge implique une recopie similaire à l affectation de structure Fonctions retournant une structure Une fonction peut retourner une structure, ce qui implique une recopie similaire à l affectation de structure. 81
83 // S : pind //En supposant Tindividu comme une "petite" structure, //La définition de la fonction qui saisit les données de l'individu pind serait : TIndividu saisirindividu ( void ); Appel : TIndividu vi; vi =saisirindividu (); // avec recopie de la variable locale utilisée pour le return dans vi Remarque : on évitera de déclarer des fonctions retournant une structure, lorsqu'on considèrera que les recopies de structures occupent une place trop importante en mémoire et prennent du temps pour la recopie. On préfèrera alors un passage par référence, cf Passage de paramètre de type structure par référence Pour une fonction, puisqu un type structure est «assimilable» à un type de base, il peut être passé par référence. Tout accès au paramètre formel se fera sur le paramètre effectif. // S : pind //En supposant Tindividu comme une "grosse" structure, //La définition de la fonction qui saisit les données de l'individu pind serait : void saisirindividu (TIndividu & pind ); Appel : TIndividu vi; saisirindividu (vi); // les accès (modifications) de pind porteront directement sur vi Remarque : le passage de paramètre de type structure par référence est à privilégier pour éviter les temps de recopies inutiles et lorsque la structure occupe beaucoup de place en mémoire. Cette remarque est évidente lorsqu un paramètre de type structure est en sortie de fonction. Pour les paramètres en entrée seulement, passer un paramètre formel de type structure par référence rend la modification du paramètre effectif possible, ce qui est en contradiction avec le mode de passage de paramètre en entrée. Dans ce cas, on utilisera un mode de passage par référence déclaré const, ce qui rendra impossible toute modification du paramètre. Ce mode de programmation, pour les paramètres en entrée, permet : de minimiser l espace mémoire utilisé (pas de recopie car passage par référence) et d'éviter le temps de recopie, de respecter le mode de passage de paramètre en entrée : pas de modification possible du paramètre car const. Règle : Le passage d un paramètre de type structure se fait alors selon les modalités suivantes : En paramètre d'entrée : - par valeur (avec recopie) si le type structure occupe peu de place en mémoire, - par référence et constant (sans recopie) si le type structure occupe beaucoup de place mémoire. En paramètre de sortie : par référence (ou valeur retournée par une fonction lorsque le type structure occupe peu de place en mémoire). 82
84 (1) // E : pind //En supposant Tindividu comme une "petite" structure void afficherind (TIndividu pind) bcdisplayln (pind.nom) ; // OK // MAIS : // pind.age = 39 ; Possible mais n'aura aucune répercution sur le paramètre effectif à l'appel car pind passé par valeur // strcpy (pind.nom, "Dupont") ; Possible mais n'aura aucune répercution sur le paramètre effectif à l'appel car pind passé par valeur (2) // E : pind //En supposant Tindividu comme une "grosse" structure void afficherind (const TIndividu & pind) bcdisplayln (pind.nom) ; // OK // MAIS : // pind.age = 39 ; Impossible car pind const // strcpy (pind.nom, "Dupont") ; Impossible car pind const Passage de paramètre d un tableau de structures Lorsqu un tableau dont les éléments sont de type structure doit être passé en paramètre, «l aspect tableau» de la variable prime sur le contenu des éléments. Les règles de passage de paramètre à appliquer sont donc celles d un tableau classique. // E : ptabind, pnbi void affichertabind (const TIndividu ptabind [], int pnbi ); // E : pnbi // S : ptabind void saisirtabind (TIndividu ptabind [], int pnbi ); 9.9 Surnommage de type : l'opérateur typedef L'opérateur typedef permet de créer un identificateur de type. Il associe un identificateur de type à une expression définissant un type. Son utilisation permet de simplifier l écriture des déclarations de variables, en particulier pour les structures en évitant de devoir toujours répéter le mot-clef struct, comme utilisé jusqu ici dans ce chapitre 9. Syntaxe : typedef déclaration_de_type IdentificateurType ; Syntaxe de déclaration de variables liées à un type défini par typedef : IdentificateurType idvar, idvar ; 83
85 Exemples : Expression typedef Définition de variables typedef int Entier ; Entier i, nb ; // <=> int i, nb ; typedef int[50] TabEnt ; TabEnt tab ; // <=> int tab [50] ; typedef unsigned int size_t ; size_t x; // <=> unsigned int x ; 9.10 Autre mode de déclaration des types structures La syntaxe utilisée pour déclarer les types structures tout au long de ce chapitre 9 est l utilisation du typedef. Une autre notation de déclaration est possible sans utiliser typedef. Les possibilités offertes par ces deux catégories de déclarations sont strictement équivalentes ; la seule différence réside dans la syntaxe à utiliser. Remarque : Les bibliothèques standards du langage C définissent des types struct (fichiers, ) mais très souvent sans utiliser la syntaxe typedef. Afin d introduire la notation struct sans typedef, nous allons comparer les deux notations en reprenant les exemples traités dans ce paragraphe Déclaration sans typedef d un type structure Syntaxe : struct IdentStruct type idchamp1 ; type idchampn ; ; #define KLGLIB (20+1) struct Individu int numero ; char nom [KLGLIB] ; int age ; float taille ; ; #define KLGLIB (20+1) Déclaration de variables de type structure Syntaxe : struct IdentStruct idvar, idvar ; typdefef struct int numero ; char nom [KLGLIB] ; int age ; float taille ; TIndividu ; Syntaxe avec initialisation : struct IdentStruct idvar = valchamp1, valchamp2 ; // Individu déclaré en struct Individu struct Individu ind2; struct Individu untel = 301, "XX", 25, 1.70 ; // TIndividu déclaré en typedef TIndividu ind2; TIndividu untel = 301, "XX", 25, 1.70 ; Manipulation d'une variable de type structure Quelle que soit la déclaration de variable, les manipulations possibles sont les mêmes : - globalement : affectation de variables de type struct, - accès aux champs : opérateur ".". 84
86 // Individu déclaré en struct Individu struct Individu ind2; struct Individu untel = 301, "XX", 25, 1.70 ; untel.age = untel.age + 1 ; strcpy (untel.nom, "Durand") ; ind2 = untel ; // TIndividu déclaré en typedef TIndividu ind2; TIndividu untel = 301, "XX", 25, 1.70 ; untel.age = untel.age + 1 ; strcpy (untel.nom, "Durand") ; ind2 = untel ; Tableaux de structures La déclaration des tableaux se fait avec le mot clé struct, les manipulations des tableaux restent identiques // Individu déclaré en struct Individu struct Individu tab[10] ; for (i=0 ; i<10 ; i++) bcget (tab[i].nom) ; tab[0] = tab[1] ; // TIndividu déclaré en typedef TIndividu tab[10] ; for (i=0 ; i<10 ; i++) bcget (tab[i].nom) ; tab[0] = tab[1] ; Fonctions et structures Les modes de passage des struct ne sont pas liés à leur syntaxe de déclaration (avec ou sans typedef). Les règles de passage de paramètres de type struct ou de paramètres tableaux de struct sont donc les mêmes, cf. paragraphe 9.8. // Individu déclaré en struct Individu // E : pind void affind (const struct Individu & pind) ; // E : ptabind, pnbi void affichertabind (const struct Individu ptabind [], int pnbi ); // TIndividu déclaré en typedef // E : pind void affind (const TIndividu & pind) ; // E : ptabind, pnbi void affichertabind (const TIndividu ptabind [], int pnbi ); 85
87 10 Compilation séparée Modules 10.1 Introduction Eléments de qualité des logiciels L informatique, discipline encore «récente» (moins de 60 ans), a subi une évolution extrêmement rapide, de «l artisanat» de quelques «informaticiens isolés» à ses débuts, à l ère «de l industrie de l informatique» aujourd hui. Cette évolution très rapide a mis en exergue le besoin de maîtriser le développement de logiciels et d en améliorer la qualité. Ce processus d évolution est commun avec beaucoup d autres domaines : maturation des processus de production vers la maîtrise de la qualité (médecine, services, industries, ). De nombreux auteurs ont beaucoup documenté cette problématique de la qualité dans le logiciel. Nous ne prétendrons pas synthétiser ici toute cette littérature mais nous proposons quelques éléments de réflexion permettant de caractériser ce qu est la qualité des logiciels en donnant quelques critères recherchés lors de la production de logiciels. Si ces critères peuvent paraître, de prime abord, évidents et triviaux, développer un logiciel répondant de façon optimale à ces critères n en est pas moins difficile et, pour les logiciels de taille importante (> lignes de code), reste un défi majeur posé aux informaticiens. On peut attendre d un logiciel qu il ait les qualités suivantes : Vérifiabilité : il devrait être possible de concevoir facilement les tests à réaliser et détecter les erreurs. Extensibilité : un logiciel devrait pouvoir être facilement adapté à un changement de spécification : nouveau besoin, ajout de fonction, changement d environnement, Réutilisabilité : un logiciel devrait être facile à reprendre en totalité ou en partie, «sans modification», pour l'utiliser dans un autre logiciel. Fiabilité : un logiciel devrait être valide par rapport au Cahier des Charges auquel il répond et aussi robuste, c est-à-dire capable de «résister» aux conditions anormales (rester à tout moment dans un «état cohérent»). Facilité à utiliser : un logiciel devrait être facile à faire fonctionner par les utilisateurs et facile à apprendre. Compatibilité : un logiciel devrait être combiné simplement avec d autres logiciels. Cela pose des problèmes de formats de données principalement. Intégrité : un logiciel devrait assurer une protection efficace aux accès non autorisés. Portabilité : un logiciel devrait être facile à adapter à un changement logiciel (système d exploitation, SGBD, ) ou matériel. Efficacité : un logiciel devrait exploiter de façon optimale les ressources informatiques. L augmentation importante de la complexité des logiciels depuis ces dix dernières années fait que tous les critères deviennent critiques, en particulier la réutilisabilité. Notons enfin l importance de la maintenance des logiciels, souvent sous-estimée. Cette maintenance concerne en moyenne : à 42 % des changements des exigences utilisateurs (besoins, fonctions), à 21 % des corrections des erreurs (bugs), à 18 % des modifications des formats de données, à 6 % des modifications matérielles. La fiabilité, la vérifiabilité et l extensibilité deviennent alors des critères de qualité très importants. 86
88 Face à ce constat (rapide et péremptoire il est vrai), notons que la notion de module est un concept permettant d atteindre plus facilement les critères ci-dessus, en particulier la réutilisabilité et l extensibilité Découpage d une application Lors du développement d une application importante en taille, si l on écrit tout le code (programme et sous-programmes) dans un même fichier, cela devient vite très peu pratique. En effet, on y perd en lisibilité et la maintenance en est difficile. On considère souvent que le cerveau humain peut difficilement comprendre des dépendances entre des lignes de code au delà d une page de taille A4. Par contre, sur une seule page de code, le cerveau humain est capable d en comprendre les dépendances, même si des sousprogrammes «complexes» sont utilisés. Par ailleurs, certains programmes (sous-programmes) sont réutilisés d une application dans une autre. La «simple» recopie de code source est peu pratique dans ce cas et mal adaptée. En particulier, en cas de mise à jour d une fonction copiée, les modifications seraient à apporter sur chaque recopie. Enfin, il existe des bibliothèques système que l on peut utiliser dans différentes applications. Leur «réutilisation» nécessite un mécanisme d'utilisation de code développé séparément. C est ce mécanisme que l on peut utiliser pour écrire une application en la découpant en plusieurs fichiers sources. Le code source d une application en langage C peut s écrire dans plusieurs fichiers sources. On utilise alors la notion de module pour relier entre eux les différents fichiers sources et «expliquer» au compilateur les relations entre ces différents fichiers sources. La notion de module, a, dans l informatique d aujourd hui, une signification très forte et est un outil puissant permettant de produire des applications «fiables». En première approximation, on peut définir un module comme un «regroupement cohérent de sous-programmes». C est à cette notion de module que nous ferons référence dans ce cours. En langage C, l unité de base de compilation est le fichier source. Indépendamment des modules mis en œuvre par le programmeur, le compilateur ne s intéresse qu aux fichiers sources constituant l application et qu il doit traiter durant le processus de compilation/édition de liens. Ainsi, en langage C, un module peut être défini dans une seule unité de compilation ou «éclaté» dans plusieurs. Inversement, une unité de compilation peut définir un module, ou un «morceau» de module, ou plusieurs modules/«morceaux» de modules. Dans ce cours, nous supposerons que : une unité de compilation ne définit qu un seul module, un module n est défini que dans une seule unité de compilation. Par la suite, nous parlerons uniquement de module, désignant par là, à la fois le module informatique développé et l unité de compilation qui le définit (deux fichiers sources en C) La notion de module Définition de la notion de module Définition : Un module est un ensemble de déclarations de types et sous-programmes, formant un tout cohérent (exemple : le module string de la bibliothèque du compilateur définit les fonctions de manipulation de chaînes de caractères en langage C). Un module sert à regrouper des sous-programmes dans deux objectifs : réutiliser les modules : bibliothèques (ex : string, stdio, bcio, ctype, ), découper un programme en unités plus simples et plus faciles à gérer : décomposition modulaire d'un programme pour les projets complexes (plusieurs centaines de lignes). 87
89 Un module est constitué de deux parties : une interface : types et constantes nécessaires pour utiliser le module, déclaration (ou signatures ou prototypes ou en-têtes) des sous-programmes proposés par le module (services), une implémentation : mise en oeuvre des sous-programmes (définition des corps). Un module Etudiant définissant : Le type Etudiant : enregistrement contenant des informations sur l étudiant dont ses notes à des matières, Les fonctions de manipulation d un Etudiant : mise à jour des données, consultation des informations d un étudiant, calcul de moyennes, Un module ListeEtudiant définissant : Le type ListeEtudiant : enregistrement contenant un tableau d étudiants et le nombre d éléments dans le tableau. Les fonctions de manipulation d une ListeEtudiant : ajout d étudiant, consultation d un étudiant, retrait d un étudiant, Un module Gsle de gestion des sauvegardes d une liste d étudiants définissant : Pas de type particulier. Les fonctions permettant de sauvegarder une ListeEtudiant, charger une ListeEtudiant, Liens «utilise»/«est client de» entre modules : l interface de module On dit qu un programme «utilise» ou «est client d» un module lorsque ce programme fait appel aux services proposés par le module, c est-à-dire utilise les types et appelle les sous-programmes définis dans l interface du module. Par extension, un module peut utiliser/être client d un autre module. Un programme utilisant/étant client d un module doit uniquement en connaître l'interface. Il fonctionnera quelle que soit l'implémentation du module, tant que cette dernière respecte l'interface et les contrats associés. Exemple «trivial» : lorsqu on conduit une voiture, on utilise une interface, le poste de pilotage, permettant d utiliser la voiture. On sait que la pédale du milieu permet toujours de freiner, celle de droite d accélérer. Pour autant, on ne sait pas comment fonctionne la voiture. On peut conduire une voiture, quelle que soit son implémentation car l interface est la même. Utiliser le module ListeEtudiant par son interface est indépendant de son implémentation. Peu importe comment est gérée la «liste» d étudiants en interne, on connaît les opérations applicables à cette liste. Il en est de même pour le module Gsle. Dans l exemple du paragraphe , les liens «utilise» (ou «est client de») entre modules sont : Le module ListeEtudiant «est client du/utilise le» module Etudiant. le module Gsle «est client des/utilise les» modules ListeEtudiant et Etudiant Implémentation de module L'implémentation d'un module est complètement indépendante de son utilisation, elle n'est "liée" qu'aux contraintes définies dans l'interface. 88
90 Exemple «trivial» : une voiture peut fonctionner de différentes manières : moteur diesel, essence, GPL, électrique, freins à disques ou à tambours,. L implémentation d une voiture est «libre» (choisie par le constructeur). Pour autant, toute voiture est liée à un poste de pilotage toujours «similaire» : les voitures ont la même interface. L implémentation d une voiture doit respecter les «fonctionnalités définies dans l interface». le module Gsle peut mettre en œuvre la sauvegarde d une liste d étudiants comme choisi par le programmeur (1 seul fichier physique par liste d étudiant, plusieurs fichiers, ). Pour autant, il lui faudra respecter son interface : sauvegarder une ListeEtudiant, charger une ListeEtudiant, Intérêt de la notion de module Module == Abstraction == Réduction de complexité L'intérêt majeur des modules se situe dans l ensemble des inter-relations suivantes : On construit les modules en précisant leur "mode d'emploi" : o interface : liste des commandes possibles, o contraintes : commentaires avec préconditions. Par ailleurs, on en fait l implémentation qui respecte l interface, mais sans tenir compte de l utilisation qui en sera faite. Par ailleurs, on les utilise, plusieurs fois si besoin, en respectant l interface, mais sans tenir compte de la manière dont ils sont mis en œuvre (implémentés). La notion de module est réductrice de complexité. En effet, le module représente une abstraction et permet d utiliser des services sans en connaître la mise en œuvre Objectifs de la programmation modulaire Un module est une unité cohérente. Un programme «modulaire» se construit comme un assemblage de modules. On appelle architecture du système, l ensemble des modules constituant le programme et l ensemble des liaisons entre les modules (partage de données, échange d informations, appels de sous programmes, ). L approche de construction des programmes devient ascendante : des modules unitaires vers la solution globale dirigée par les données. Module = Sous-Programmes Programme = Modules L objectif de la programmation modulaire est de produire des programmes : Lisibles et compréhensibles pour un programmeur. Faciles à maintenir, à modifier par un programmeur c'est-à-dire «bien programmés». Fiables : valides (font ce qui est spécifié/prévu) et robustes (conditions anormales «prévues» ou anticipées). Réutilisables. Un module est un ensemble de sous-programmes regroupés de façon cohérente et peut utiliser les services d autres modules. Chaque sous-programme est court (10 lignes - 1 page maxi lorsque simple) et donc lisible, facile à maintenir, fiable, réutilisable. => Donc, on peut espérer que chaque module «soit» : lisible, facile à maintenir, fiable, réutilisable. => Donc, on peut espérer que le programme complet «soit» lisible, facile à maintenir, fiable, réutilisable. 89
91 Règles de programmation d un module Pour définir un module, il faut définir l ensemble des sous-programmes que l on veut regrouper. Cet ensemble doit être cohérent, c est-à-dire que l on doit regrouper des sous-programmes ayant «un lien», «un rapport» entre eux. Cette cohérence se mesure par rapport à l utilité du module qui doit pouvoir se définir en quelques lignes. La composition de modules se fait selon plusieurs axes : 1 module par structure de donnée. 1-x modules d entrées/sorties utilisateurs (s/p séparés). 1-x modules pour les sauvegardes/restaurations (s/p séparés). 1-x modules pour les sous-programmes "utilitaires" (tris, ). 1-x modules pour les sous-programmes pouvant être réutilisés (bibliothèque, ). Il existe quatre règles pour construire un module : Un module est cohérent : on peut décrire en quelques lignes son utilité. Il s agit de construire une unité présentant une cohérence syntaxique et linguistique (sémantique) : les sous-programmes regroupés «parlent de la même chose», «dans les mêmes mots». Un module a une interface «restreinte» et explicite. Il est donc constitué d un nombre plutôt restreint de sous-programmes. Un module doit «encapsuler» les données qu il gère. Les données gérées par un module ne devraient pas être accessibles en dehors des sous-programmes de l interface du module. Un module doit alors offrir des services d accès aux données. Ainsi, le module «masque» le stockage de ces données, leur traitement, Un module est autonome : il peut être compris isolément de son contexte d utilisation. De plus, il a des liens avec «peu» d autres modules (limiter les liens entre modules). Ces quatre règles permettent de développer de «bons modules» : lisibles, faciles à maintenir, fiables, réutilisables. Une nécessité demeure : l architecture globale des modules doit être «bien construite» Mise en œuvre de modules en langage C Définition de module en langage C Soit un module m à développer en langage C. Sa mise en œuvre se fera au travers : d une interface : un fichier m.h de déclaration de constantes, de types et de fonctions. d une implémentation : un fichier m.cpp de définition des fonctions déclarées dans m.h + éventuellement des constantes/types/fonctions locales. Chaque fichier (".h" et ".cpp") doit réaliser les «includes» nécessaires à la compilation de son contenu. Un fichier de définition/d implémentation (m.cpp) doit inclure le fichier de déclaration associé (m.h) pour réaliser le lien entre les déclarations de m.h et les définitions de m.cpp Utilisation de module en langage C Soit un programme p utilisant/client d un module m. En C : le programme p doit faire un include du fichier ".h" de l'interface du module m (m.h). Les liens entre les fichiers sources ".cpp" ne sont pas à réaliser explicitement, le processus de compilation s en chargera «automatiquement», cf 10.4 ; seuls les include des ".h" sont à réaliser. 90
92 Exemple de module et utilisation Cet exemple présente un module utils (utils.h et utils.cpp) proposant une fonction d affichage d un menu et de saisie d une réponse utilisateur (un char) ainsi qu un fichier de test, test.cpp. Contenu du fichier utils.h : // Evite les includes multiples #ifndef _UTILS_H #define _UTILS_H // Includes éventuels nécessaires char menuclient (void); // + Commentaire complet #endif // Fin du test #ifndef _UTILS_H Contenu du fichier utils.cpp : // Includes nécessaires pour la compilation #include <stdio.h> #include <conio.h> // Lien entre interface et implémentation #include "utils.h" char menuclient (void) // + Commentaire complet char rep ; bcdisplay ( ); return rep ; Contenu du fichier test.cpp : #include <stdio.h> // Includes nécessaires pour la compilation #include "utils.h" // Utilisation du module utils // Test.cpp client de utils int main (void) char choixutilisateur ; choixutilisateur = menuclient (); Elément de syntaxe spécifique pour les fichiers ".h" : clause #ifndef Lors de la compilation d un fichier source, il y a des risques d importations multiples d un fichier ".h", c est-à-dire que par transitivité, un fichier ".h" peut être importé plusieurs fois. Cela génère des erreurs de compilation, les mêmes éléments (principalement les types) se trouvant définis plusieurs fois. dans l exemple du paragraphe , Le module ListeEtudiant utilise le module Etudiant. listeetudiant.h : #include "etudiant.h" Le module Gsle utilise le module Etudiant ET le module ListeEtudiant gsle.h : #include "etudiant.h" #include "listeetudiant.h" Un programme de test, test.cpp, peut utiliser Gsle ET Etudiant ET Liste Etudiant test.cpp : #include "etudiant.h" #include "listeetudiant.h" #include "gsle.h" 91
93 On obtient le diagramme de liens entre modules suivant : Etudiant ListeEtudiant est client de/utilise Gsle test.cpp Dans le cas de la compilation du programme principal test.cpp, on aura une importation multiple du fichier etudiant.h : par importation directe dans test.cpp : #include "etudiant.h" par importation indirecte (transitive) par le #include "listeetudiant.h". par importation indirecte (transitive) par le #include "Gsle.h". De même, le fichier listeetudiant.h sera lui aussi importé plusieurs fois. Règle : afin d éviter les importations multiples, tout fichier xxx.h sera défini selon le modèle (template) de fichier suivant : #ifndef _XXX_H #define _XXX_H #endif // Définition du contenu du fichier ".h" // #define nécessaires // #includes nécessaires // types et fonctions du module De façon «simpliste», une paraphrase possible des directives ci-dessus serait la suivante : #ifndef _XXX_H #define _XXX_H #endif // Définition du contenu du fichier ".h" SI _XXX_H n est pas défini (if n def) ALORS Définir le symbole _XXX_H // Définition du contenu du fichier ".h" Le but des directives #ifndef, #define, et #endif est de ne réaliser un include que s il n'a pas encore été réalisé, et de ce fait, éviter les importations multiples. Cela évite au programmeur de gérer «manuellement» les risques d importations multiples. FSI Directives de compilation conditionnelles contrôlant le pré-processeur : #ifdef : test de la définition d'un symbole. #ifndef : test de la non définition d un symbole. #endif : fin de directive ifdef ou ifndef. Entre un test (#ifdef, #ifndef) et un #endif, on trouve : des directives de compilation (#define, #include, d'autres tests), et/ou du code C. 92
94 Ainsi, en reprenant l exemple du paragraphe : lorsque le compilateur rencontre le premier #include "utils.h", le symbole _UTILS_H n étant pas encore défini (#ifndef vaut vrai), le symbole _UTILS_H est défini puis les déclarations effectuées (elles sont vraiment mises dans le texte source). lorsque le compilateur rencontre d autres #include "utils.h", _UTILS_H est défini (#ifndef vaut faux), rien n'est alors ajouté, la définition est donc unique. Les directives de compilation conditionnelles servent aussi à réaliser des alternatives de compilation suivant le type de la machine cible, le système d exploitation cible, Elles permettent de rendre les programmes plus facilement portables. Cet aspect ne sera pas abordé dans ce cours Principes de compilation du langage C Principe des étapes de compilation/édition de liens Ce qu on appelle généralement «compilation» d un programme comprend trois étapes : Préprocessing, Compilation, Edition de liens. La compilation d un programme multi-fichiers se fait de la manière suivante : Pour chaque fichier source (".c" ou ".cpp") : o Pré-processing du fichier : traitement des directives #xxx. Il correspond à des manipulations textuelles et à la génération d'un fichier en "C pur". o Compilation proprement dite de code source : passage du C à un code proche assembleur et génération d'un fichier objet (.o). Edition de liens / Génération de code : génération d'un seul fichier exécutable à partir de plusieurs fichiers objet Pré-processing d un fichier C En C, il existe une spécificité de la compilation : le pré-processeur. Cette étape de traitement n existe pas dans tous les langages de programmation. Chaque fichier source C est traité séparément par le pré-processeur. Les directives #xxx sont des directives à destination du pré-processeur. Le pré-processeur prend un fichier source en C contenant des directives de pré-processing et génère un fichier source intermédiaire en langage C ne contenant plus de directives mais uniquement du code source C qui peut alors être compilé. Le pré-processeur intervient pour effectuer des manipulations textuelles : #define XX YY : cette ligne est enlevée, et toute occurrence textuelle du symbole XX est remplacée par YY. #include <xx.h> : cette ligne est enlevée et remplacée par le contenu du fichier xx.h, lui même aussitôt traité par le pré-processeur. directives conditionnelles : o #ifdef #endif : test de la définition d'un symbole. o #ifndef #endif : test de la non définition d'un symbole. Le pré-processeur a enfin pour rôle d enlever tous les commentaires du programme source original. 93
95 Compilation de code source C Chaque fichier source C est compilé séparément, après traitement par le pré-processeur. La compilation consiste en la vérification syntaxique et sémantique du fichier source et en la génération du code objet. En général, la compilation lance automatiquement au préalable le préprocessing avant de débuter elle-même. La compilation se déroule en plusieurs étapes : Vérification lexicale : reconnaissance des mots du texte source : mots clés du langage, identificateurs, commentaires, o Ex : 88PPO = 10; => 88PPO impossible en langage C car ne peut correspondre à «rien» (pas un littéral nombre, pas un identificateur, pas un mot-clé, ). Vérification syntaxique : correction des "phrases" du texte source (vérification du C) : reconnaissance des déclarations, reconnaissance des instructions et des structures de contrôle, appels de sous-programmes, o Ex : if n==0 i=1 ; => impossible (il manque les () du if) Vérification sémantique : possibilité d'exécuter les instructions o Vérification des types des opérandes + conversions implicites si besoin Ex : float_f = int_i + 3.0F => conversion de la variable int_i en float. o Vérification de l'existence d'une unique déclaration de sous-programme appelé pour chaque appel (signature de fonction). Vérification des appels de sous-programmes : vérification des types des paramètres et conversion des paramètres si nécessaire. #include du module utils.h (cf ) dont : char menuclient (void); char rep ; Appel de : rep = menuclient (10) ; => erreur appel. Ici, la seule déclaration du fichier utils.h déclarant menuclient permet de lever l'ambiguïté : char menuclient (void); Quel que soit le code de menuclient, l'appel ne prend pas de paramètre. strcpy (chaine) : impossible, il manque un paramètre (cf string.h). Génération d'un code intermédiaire proche assembleur (fichier objet). 94
96 Edition de liens / Génération de code L'édition de liens prend en entrée plusieurs fichiers objets et génère un seul fichier exécutable. L édition de liens est la construction du programme final : fichier exécutable. Les opérations réalisées par l édition de liens sont, de façon simplifiée : Recherche, dans les fichiers objets, de tous les sous-programmes réellement nécessaires et appelés au moins une fois (recherche du main, puis de façon récursive, recherche des appels de fonction). Réalisation du lien entre les appels de chaque sous-programme et le code machine du sousprogramme. o appel de menuclient : insérer le code de la fonction dans l exécutable et l'appel au code de la fonction. o utilisation de chaînes : ne sera réellement inséré dans l exécutable que le code des fonctions de manipulation de chaînes réellement appelées (strcpy, ). Le code des fonctions de string.h non utilisées ne sera pas inséré dans l exécutable final. Assemblage (génération de code) Exemple d étapes de compilation/édition de liens Soit un programme main.cpp utilisant deux modules m1 et m2 : m1 m2 Etapes engendrées par le processus de compilation : main.cpp A. Compilation des fichiers sources : dans un ordre quelconque, 3 phases de compilation : 1. Compilation de m1.cpp Pré-processing de m1.cpp : génère un fichier m1.cpp.temp Compilation du code de m1.cpp.temp : génération de m1.o 2. Compilation de m2.cpp Pré-processing de m2.cpp : génère un fichier m2.cpp.temp Compilation du code de m2.cpp.temp : génération de m2.o 3. Compilation de main.cpp Pré-processing de main.cpp : génère un fichier main.cpp.temp Compilation du code de main.cpp.temp : génération de main.o B. Edition de lien / Génération de code : Utilisation de m1.o, m2.o, main.o + fichiers objet de la bibliothèque (stdio, ) pour générer l'exécutable. 95
97 11 Les objets interprétation - adresse Un objet est un littéral, une variable (identificateur), une constante 11.1 Lvalue/Rvalue Pour chaque nom de variable (identificateur) ou, plus généralement, chaque expression, le compilateur détermine si l'on s'intéresse à : On appelle : la valeur contenue dans la zone mémoire (objet) associée à l'identificateur (respectivement à la valeur de l'expression), la zone mémoire (objet) à laquelle l'identificateur donne accès (respectivement la zone mémoire à laquelle l'expression donne accès). Rvalue : expression ayant une valeur utilisable en tant que telle, c est-à-dire correspondant à un objet. Rvalue signifie «right value» ou «expression pouvant être à droite d une affectation». Exemples : o valeurs littérales : 10, , 'A', o valeurs retournées par l évaluation d'une fonction : factorielle (10), o nom de variable qui n est pas en partie gauche (left) d une affectation, o le résultat de l évaluation d une expression : i+1, j<3. Lvalue : expression désignant un objet modifiable (non constant). Lvalue signifie «left value» ou «expression pouvant être à gauche d une affectation». Une Lvalue = tout ce qui peut être utilisé en partie gauche d'une affectation. Une Lvalue a une valeur (Rvalue) l inverse n est pas vrai. f = f * 20; // «f =» : f est une Lvalue, «= f * 20» : f est ici une Rvalue 25 = f * 20 ; // Erreur : «Lvalue required on left side of =» tab[10] = 10; // tab[10] est une Lvalue ATTENTION : un identificateur de tableau n'est pas une Lvalue car tab= est interdit Obtenir l adresse mémoire d une variable L'opérateur de référencement & renvoie la valeur d'adresse mémoire d'un objet. & f // renvoie l'adresse mémoire de f & tab [10] renvoie l'adresse mémoire de la 11 ème case de tab <==> & ( tab[10] ) Attention : ne pas confondre avec les références en passage de paramètres. 96
98 11.3 Les sorties d une fonction font référence à des Lvalue, les entrées à des Rvalue Toute variable en sortie d une fonction doit être passée par référence ou par adresse. void f(int * pptri) ; appel : int i ; f(&i) ; void g(int &prefi) ; appel: int i; g(i); Lors de l appel de fonction, les deux fonctions précédentes attendent des Lvalues en paramètre effectif. f attend l adresse d une variable modifiable donc une Lvalue et g attend une Lvalue qui servira d initialisation de prefi. 97
99 12 Les pointeurs 12.1 Définition Un pointeur sur un objet de type T est un type. Un objet de ce type permet de mémoriser l adresse d un objet de type T. Une variable pointeur est donc une variable contenant l'adresse d'un emplacement mémoire d'un objet d'un type donné. Une variable de type pointeur sur(de) float : est associée à une zone mémoire qui contient une adresse, ce devra être l'adresse d'une zone mémoire qui elle, contient un float. Il faut donc différencier le contenu du pointeur (une adresse) et le contenu de la zone mémoire pointée (de type classique). Le type pointeur sur un objet d'un type donné définit un type : les valeurs possibles sont des adresses mémoires (segment : offset ou offset seul), leur codage binaire est défini (en général sur 4 octets), les opérations sur ce type sont des manipulations d'adresses (affectations, calculs, affichage mais aussi passage de paramètre), la valeur permet d'accéder à un emplacement mémoire ayant lui-même un type donné. Pour un pointeur, 2 questions à se poser : pointeur sur... un pointeur contient l'adresse de quoi... un objet d'un type donné la zone pointée est sensée contenir quoi Remarques : On appelle un int un entier, un float un réel. Les variables dites pointeurs devraient s'appeler adresse : c'est ce qu'elles contiennent en réalité. Le nom de pointeur vient du fait qu'on s'en sert pour manipuler la zone située à l'adresse stockée, la zone "pointée". Une variable pointeur sur un objet de type T contient une adresse. C est une variable comme les autres : elle a donc elle-même une adresse mémoire. Il faut bien différencier l adresse contenue dans cette variable de l adresse de cette variable Déclaration d'une variable pointeur sur un objet d'un type donné Syntaxe : Exemples : float * ptrf, * ptrf2 ; char * ptrc, * ptrc2 ; int * ptri, * ptri2 ; TypePointé * identificateur_pointeur; TypePointé peut être tout type C, en particulier les structures (enregistrements). Attention : ici * n'est pas la multiplication! L opérateur * modifie ici la déclaration de la variable float * ptrf ; // float est le type de la zone pointée, * s'applique à ptrf : c'est un pointeur 98 --
100 Interprétation : ptrf est un float *, c est à dire un pointeur de float, peut contenir l'adresse d'un emplacement mémoire de float. la zone pointé par ptrf est un float. TOUJOURS PENSER A CES DEUX ASPECTS Lorsqu on déclare un pointeur sur un objet de type T, un emplacement mémoire est réservé. Il permet de mémoriser l adresse d un objet de type T. Mais rien n est réservé pour l objet de type T lui-même. Remarque : La déclaration d'un pointeur fait intervenir le type de l'objet pointé. Un pointeur est typé par l'objet qu'il pointe. Exception : le type pointeur générique void * utilisé comme type de retour de certaines fonctions. Exemple de déclaration de pointeur sur float (opérateur *) : float *ptrf; Contexte ptrf // Je réserve un emplacement en mémoire permettant de mémoriser // l adresse d un flottant, attention pas un :10 float * La valeur contenue dans ptrf est indéfinie et essayer d accéder à l objet sensé être mémorisé à l adresse à laquelle il fait référence entraînera une erreur. Cette erreur n est souvent pas détectée à la compilation ni à l édition de liens et entraînera alors des erreurs à l exécution. Ces erreurs sont parfois difficiles à détecter et à corriger car elles ne semblent pas toujours régulières. Par exemple, vous avez parfois des résultats incohérents, ou des arrêts de votre programme avec «plantage» de la machine Elles sont la principale cause des difficultés d utilisation des tableaux de bas niveau et des pointeurs. Ces erreurs peuvent poser des problèmes même à de bons programmeurs. L adresse mémorisée par un pointeur sur un objet de type T doit être initialisée Opérateurs et instructions applicables à un pointeur Il faut bien différencier les opérations sur les pointeurs (les adresses) VERSUS les zones pointées. On s intéresse ici aux opérations sur les pointeurs Affectation à un pointeur Affecter une valeur à un pointeur sur un objet d'un type donné : la valeur affectée doit être une adresse sur un objet du type pointé. ptrf = // doit être une valeur adresse d'un float Lors de la déclaration, le pointeur contient une valeur indéterminée => Il est nécessaire, avant de manipuler un pointeur de s'assurer que l'emplacement pointé est bien une adresse correcte : => INITIALISATION!! 99 --
101 Initialisation d'un pointeur par valeur NULL Il existe une valeur d'adresse "nulle" : NULL qui représente une adresse impossible en mémoire. C est une constante définie dans stdio.h et dans stdlib.h. Contexte :10 NULL float * ptrf = NULL; Initialisation d'un pointeur par obtention de l'adresse d'une variable typée Obtenir l'adresse mémoire d'une variable : opérateur de référencement : &. Exemple 1 : int main() float * ptrf ; float valf ; ptrf = &valf ; //ptrf reçoit l adresse de valf L opérateur & placé avant valf permet d indiquer qu on s intéresse à l adresse de valf. :14 Adresse de float (float *) :14 float Exemple 2 : float * ptrf; float tabf [10]; int vali; ptrf = &tabf[9] ; ptrf = &vali ; // INCORRECT car c'est une adresse de int!!! Initialisation d'un pointeur par affectation d'un autre pointeur Un pointeur contient une adresse. Sa valeur peut donc être affectée à un autre pointeur sur le même type d objet
102 float * ptrf, * ptrf2; float valf ; ptrf = & valf; ptrf2 = ptrf; // Affecte le contenu : adresse. // Les deux pointeurs pointent la même zone mémoire :14 Adresse de float (float *) :14 Adresse de float (float *) :14 float 12.4 Comparaison de pointeurs Comparer des pointeurs <==> comparaison d'adresses : seuls les opérateurs == et!= ont un intérêt réel. Opérateurs < et > parfois utilisés (cf. tableaux). float * ptrf, * ptrf2; float valf, autrefloat ; ptrf = & valf ; ptrf2 = & valf ; if (ptrf == ptrf2) // vaut true ptrf2 = & autrefloat ; if (ptrf == ptrf2) // vaut false 12.5 Affichage de pointeurs Affichage d'adresse : on utilise le spécifieur de format %p printf ("%p", idpointeur) ; // Affiche le contenu du pointeur, c est à dire une adresse (celle de la // valeur pointée). A utiliser pour le débogage. float * ptrf, * ptrf2; float valf ; ptrf = &valf; printf("%p, %p", ptrf, &valf); // Affichera deux fois la même valeur du style 1A2C:0FF6 La saisie d'une adresse n'a que rarement du sens et ne sera pas étudiée
103 12.6 Opérateurs et instructions applicables à une zone désignée par un pointeur Une zone pointée à travers un pointeur peut être utilisée comme tout objet du type pointé (affectation, expressions, tests, conditions,...) Utilisation de la zone pointée : opérateur d accès à l emplacement mémoire pointé (*) Syntaxe : * identificateur_pointeur Interprétation : (cf. Déclaration de pointeurs) : *identificateur_pointeur est un TypePointé => il identifie le contenu de cet emplacement mémoire (celui dont identificateur_pointeur contient l'adresse). L expression * identificateur_pointeur peut s interpréter comme «utiliser l objet mémoire dont l adresse est identificateur_pointeur». Cette expression référence la zone mémoire se trouvant à l adresse contenue dans identificateur_pointeur. * est appelé opérateur de déréférencement ou d'indirection. En général, * identificateur_pointeur est une lvalue (sauf cas particulier : déclaration const). float * ptrf, * ptrf2; float valf ; ptrf = &valf; //*ptrf est associé au même emplacement que valf. ptrf2 = ptrf ; * ptrf est un float, il peut être utilisé comme un float! L'opérateur de déréférencement * permet d'accéder à la zone mémoire (objet) associée à une adresse donc peut être une lvalue. *(& valf) <==> à valf On peut donc faire *(& valf) = *(& valf) + 1; // Ajoute 1 à valf. Ici, *(& valf) (le premier) est une lvalue. Ce qui explique comment par exemple scanf peut, à partir d'une adresse, accéder à une zone mémoire. :14 Adresse de float (float *) :14 Adresse de float (float *) :14 float
104 Opérations applicables à une zone désignée par un pointeur Une zone référencée à travers un pointeur peut être utilisée comme tout objet du type pointé (affectation, expressions, tests, conditions,...) : Affectations, utilisation dans un calcul,... Contraintes de types : celles de la zone pointée. float * ptrf, * ptrf2; float valf, valf2 ; ptrf = &valf; // équivaut à "ptrf reçoit l'adresse mémoire de valf" *ptrf = 10.5; // équivaut à la zone pointée par ptrf reçoit 10.5 // valf vaut maintenant 10.5; ptrf2 = ptrf; *ptrf2 = ( *ptrf2 + 3 ) * 10; // valf vaudra ptrf2 = & valf2 ; *ptrf2 = ; // valf2 vaut if (*ptrf == *ptrf2) //compare les zones pointées, ici vrai //revient ici à faire if (valf == valf2 ); if (ptrf ==ptrf2) //compare les contenus des pointeurs : des adresses, ici faux. Etat des variables en fin d exécution du code ci-dessus. :18 Adresse de float (float *) :14 Adresse de float (float *) : float : float Entrées / Sorties sur une zone désignée par un pointeur Pour afficher une zone désignée par un pointeur, on utilise des formats liés au type de la zone pointée. Affichage de la zone pointée : : printf ("%...", * identificateur_pointeur) ; Exemple: printf ("%f", *ptrf) Saisie : scanf ("%...", identificateur_pointeur) ; //Effectue une saisie pour l emplacement pointé
105 scanf ("%f", ptrf) La zone float où doit se trouver le résultat est *ptrf La zone pointée par ptrf est un float format %f scanf attend l'adresse de cette zone soit & (*ptrf) qui est <==> à ptrf (ptrf==& (*ptrf)) Devient scanf ("%f", ptrf); <==> scanf ("%f", &(*ptrf)); 12.7 Tableaux de bas niveau et pointeurs Rappel sur les tableaux Un tableau de bas niveau est un objet en mémoire qui permet de stocker consécutivement un nombre déterminé d objets de même type. Déclarer un tableau de 10 entiers correspond à réserver en mémoire de l espace pour stocker 10 entiers consécutifs, c est-à-dire que le second aura pour adresse celle du premier +1*sizeof(int) et ainsi de suite. Exemple de déclaration d un tableau de 10 entiers : int tabi[10] ; Le nombre d éléments (10) est obligatoire et ce ne peut être qu une constante puisqu il est nécessaire de connaître l espace mémoire à réserver au moment de la déclaration. Rappel : Un identificateur de tableau est un pointeur constant sur le premier élément du tableau. T tabt[kt] ; tabt vaut la valeur & tabt[0]. Ex : if (tabt==&tabt[0]) vaut true). tabt est une adresse constante, elle n est donc pas modifiable. Un identificateur de tableau représente l'adresse vers le premier élément du tableau en mémoire : Un identificateur tableau est un pointeur constant (valeur = adresse). Ainsi, un identificateur de tableau : ressemble à un pointeur, puisque c est une adresse mémoire, n est pas comme un pointeur, puisque sa valeur est déterminée lors de l exécution mais ne peut jamais être modifiée (tabt = INTERDIT). Définition : Pour un tableau déclaré : T tabt[kt] par définition : tabt donne l adresse mémoire du premier élément du tableau (tabt[0]), tabt+1 donne l adresse mémoire du deuxième élément du tableau (tabt[1]), tabt+2 donne l adresse mémoire du troisième élément du tableau (tabt[2]), tabt+i donne l adresse mémoire du i+1 élément du tableau (tabt[i]), Interprétation Comment interpréter l accès à un élément de tableau Soit l instruction : tabi[0] = 10 ; son évaluation équivaut à : calculer l adresse mémoire suite à un déplacement de 0 élément int à partir de l adresse tabi, faire une indirection sur l adresse calculée
106 Soit l instruction : tabi[3] = 10 ; son évaluation équivaut à : calculer l adresse mémoire suite à un déplacement de 3 éléments int à partir de l adresse tabi, faire une indirection sur l adresse calculée. Soit l instruction : tabi[i] = 10 ; son évaluation équivaut à : calculer l adresse mémoire suite à un déplacement de i éléments int à partir de l adresse tabi, faire une indirection sur l adresse calculée Arithmétique des pointeurs Par définition, l ajout d une valeur entière à un pointeur revient à effectuer un calcul d adresse suite à un déplacement mémoire. soit la déclaration int * ptri ; ptri + expression (respectivement - expression) revient à ajouter (respectivement soustraire) à l adresse initiale ptri : expression * sizeof(int). ptri + 0 : donne l adresse mémoire de l élément pointé par ptri, ptri + 1 : donne l adresse mémoire de l élément int suite à un déplacement de 1 int en mémoire, à partir de l adresse ptri, ptri + i : donne l adresse mémoire de l élément int suite à un déplacement de i int en mémoire, à partir de l adresse ptri. Plus généralement : T * ptrt ; ptrt + expression (respectivement - expression) revient à ajouter (respectivement soustraire) à l adresse initiale ptrt : expression * sizeof(t) avec T le type des éléments pointés par ptrt. Cette opération n a de sens que si ptrt pointe sur des éléments de tableau. On peut également, pour un pointeur ptrt de type T *, écrire ptrt++ (respectivement ptrt--) ou ptrt = ptrt + 1.(respectivement ptrt = ptrt - 1). On peut donc à partir d un pointeur parcourir un tableau. La modification de la valeur d un pointeur par affectation le fait pointer sur une autre adresse mémoire. Remarque : On a les mêmes définitions pour les tableaux. int tabi[10] ; tabi + expression (respectivement - expression) revient à ajouter (respectivement soustraire) à l adresse initiale tabi : expression * sizeof(int). tabi + 0 : donne l adresse mémoire du premier élément de tabi, tabi + i : donne l adresse mémoire du i+1 élément de tabi. Plus généralement : T tabt[10] ; tabt + expression (respectivement tabi - expression) revient à ajouter (respectivement soustraire) à l adresse initiale tabt : expression * sizeof(t) avec T le type des éléments du tableau. La modification de la valeur d un identificateur n est par contre pas possible puisqu il correspond à une adresse non modifiable. Les écritures de la forme : tabi++, tabi-- ou tabi = sont donc interdites
107 int tabi[10] ; int * ptri ; int i ; ptri = tabi ; // ou ptri = &tabi[0] ; // ptri pointe le premier élément de tabi printf("%d", *ptri) ; // affiche tabi[0] ptri ++ ; printf("%d", *ptri) ; // affiche tabi[1] ptri ++ ; printf("%d", *ptri) ; // affiche tabi[2] ptri = tabi ; // Affichage de tout le tableau for (i=0 ; i<10 ; i++) printf("%d", *(ptri+i) ) ; ptri = tabi ; // Affichage de tout le tableau : autre démarche possible for (i=0 ; i<10 ; i++) printf("%d", *ptri) ; ptri ++ ; L opérateur [] Cet opérateur permet d écrire tabi[0] au lieu de *tabi, tabi[1] au lieu de *(tabi+1) On peut noter que, lors de la compilation d un programme, le compilateur remplace toutes les écritures tabi[expression] en *(tabi + expression). Cet opérateur s applique autant aux tableaux qu aux pointeurs. En effet, il prend à gauche, comme opérande, une adresse. Ainsi, on peut écrire : int tabi[10] ; int * ptri ; int i ; ptri = tabi ; // ou ptri = &tabi[0] ; // ptri pointe le premier élément de tabi printf("%d", ptri[0]) ; // affiche tabi[0] printf("%d", ptri[1]) ; // affiche tabi[1] printf("%d", ptri[2]) ; // affiche tabi[2] ptri = tabi ; // Affichage de tout le tableau for (i=0 ; i<10 ; i++) printf("%d", ptri[i] ) ; Exemple int tabi [5]=1, 2, 3, 4, 5 ;// c est un agrégat, il ne peut être utilisé que pour l initialisation // et pas pour l affectation char chaine[]= "texte.txt" ;// ici la taille du tableau est fixée par la constante // chaîne de caractères qui sert à l initialiser. // Ceci ne peut être utilisé que pour l initialisation // et pas pour l affectation int *ptri = tabi; //pas besoin de &, tabi est une adresse // on peut aussi écrire : int *ptri = &tabi[0]; int *ptrj ; ptrj = tabi ;
108 printf( %d %d, * ptri, * tabi) ; //les 2 affichent le premier élément du tableau tabi c est-à-dire 1 * ptri = 2*(* tabi) ; //l objet pointé par ptri (reçoit 2*1) printf( %d, * ptri ); //affiche la valeur du 1 er élément de tabi c est-à-dire 2 printf( %d, *( tabi +1)) ; //affiche le second élément du tableau car tabi +1 est l adresse // du second élément du tableau printf( %d, *( ptrj +1)) ; //affiche le second élément du tableau car ptrj +1 est l adresse // du second élément du tableau *( tabi +1) = 7 ; //l objet pointé par (tabi+1) reçoit 7 printf( %d, *( ptri +1) ); // affiche le second élément du tableau c est-à-dire Similarités et différences entre tableaux et pointeurs Similarités : Un tableau est une valeur de pointeur constant (non modifiable). Equivalence des écritures : Différences : o pour un tableau : tabfloat[i] <==> *(tabfloat + i). o pour un pointeur : *(ptrf + i) <==> ptrf[i]. Réservation mémoire à la déclaration. o Déclarer un tableau entraîne une réservation mémoire de plusieurs éléments. L identificateur de tableau est une constante symbolique permettant de connaître l adresse mémoire du premier élément du tableau. Aucune place mémoire n est prise pour stocker cette valeur. o Déclarer un pointeur ne réserve qu'un objet mémoire permettant de stocker une adresse. Un pointeur a une valeur modifiable Déclaration des tableaux en paramètres formels de sous-programmes Lorsqu on passe un tableau en paramètre effectif d un sous-programme, on donne l identifiant du tableau c est donc un pointeur constant sur son premier élément qu on donne en paramètre. Soit le sous-programme d'affichage d'un tableau : void afftabint (const int ptab[], int pnb); Soit l'appel de afftabint : int tabint [5]; afftabint (tabint, 5); C'est donc l'adresse mémoire du premier élément du tableau tabint qui est envoyée (&tabint[0]). Si ce qu on envoie est une adresse, alors qu elle est la nature de ptab dans la déclaration void afftabint (const int ptab[], int pnb); Si ptab peut recevoir une adresse alors ptab est un pointeur
109 REGLE : En déclaration de sous-programme comme paramètre formel on a l'équivalence : Type t[] <==> Type * t void afftabint (const int t[], int n) <==> void afftabint (const int *t, int n) ATTENTION : valable uniquement en en-tête de sous-programme. En en-tête de sous-programme, le compilateur transforme automatiquement toute déclaration de la forme «tableau de T» («T [ ]») en «pointeur de T» («T *»). Règle d utilisation : lorsqu on veut déclarer un tableau en paramètre, préférer la notation classique avec []. void f(int *pt, int pnb) ; void g(int pt[], int pnb) ; //ici la taille n est pas utile car en fait c est uniquement un pointeur constant qui // sera réservé à l exécution pour mémoriser l adresse du premier élément void main() int tabk [5]=1, 2, 3, 4, 5 ; int *ptri = tabk; f(ptri, 5) ; f(&tabk [1], 4) ; g(tabk, 5) ; g(ptri+1, 4) ; On peut remarquer que comme le tableau est considéré comme un pointeur constant sur son premier élément, le tableau passé en argument ne contient aucune information sur le nombre d éléments à traiter. C est pourquoi, lorsqu on passe un tableau en paramètre il faut : soit indiquer son nombre d éléments (void g(int ptab[], int plg);) soit donner un intervalle d indice, (void g(int ptab[], int pidebut, int pifin);) soit enfin terminer le tableau par une valeur qui, par convention, indique qu il est terminé. Ce dernier cas est utilisé en C pour coder les chaînes de caractères qui, par convention, se terminent par le code 0 ( \0 ). Utilisation. La notation par pointeur est très utilisée en C, en particulier dans les bibliothèques du compilateur. Par exemple, la bibliothèque string.h utilise cette notation. Exemple d en-tête de la bibliothèque string.h : int strlen (const char * pch) ; char * strcpy (char * pdest, const char * psource) ; char * strupr (char * pch) ; Attention à la lecture de ces en-têtes lorsqu on souhaite appeler ces fonctions. Lire la déclaration de strlen comme : «strlen attend un pointeur en paramètre, donc je vais déclarer un pointeur dans mon programme» est une erreur classique
110 La lecture de cet en-tête doit se faire à la lumière de la description (commentaire) de la fonction. L explication de la fonction strlen est : «strlen calcule la longueur d une chaîne de caractères». Donc, dans ce contexte (une chaîne de caractères est un tableau de caractères), le programmeur comprend que «strlen attend un tableau en paramètre». Il faudra donc déclarer un tableau de char dans le programme pour stocker la chaîne et appeler strlen. Exemple d utilisation correcte : char chaine [51] ; strcpy (chaine, "Dupont") ; printf ("%d", strlen (chaine) ) ; chaine D u p o n t \ ptrc Exemple d utilisation INCORRECTE : char * ptrc ; // ptrc ne pointe rien strcpy (ptrc, "Dupont") ; // INCORRECT car pas d espace réservé printf ("%d", strlen (ptrc) ) ; // INCORRECT Tableau et retour de fonction On ne peut pas retourner un tableau par une fonction. On ne peut donc pas écrire : int []f(int) ; ni même int [5]f(int) ; Ceci poserait un problème de réservation et de recopie des éléments. Une fonction peut par contre retourner un pointeur : On peut donc écrire : int * f (int) ; Attention cependant de ne pas retourner un pointeur sur une variable locale : Exemples : On peut par contre écrire : int * f (int *j) *j=2 ; return j ; On NE peut PAS écrire int * f (int pi) int vari ; return &vari ; int * f (int pj) int *ptri =&pj; return ptri; int * f (void) int tabi[5] ; return tabi ; On peut également avoir alloué dynamiquement le pointeur dans le corps de la fonction
111 L opérateur de taille : sizeof et les tableaux ou pointeurs Rappel : L opérateur sizeof permet de déterminer la taille (en octets) d une variable ou d un type. La taille de la variable est donnée en fonction de la taille mémoire réservée dans le contexte de la fonction. Syntaxe : sizeof ( type ) sizeof ( variableouexpression ) sizeof variableouexpression Exemple 1 : int tab [5] ; int * ptr ; ptr = tab ; // Forme non utilisable pour un type printf( %d, %d \n, sizeof(tab), sizeof(tab) /sizeof(int) ); //affiche 20, 5 (20= 5*sizeof(int)) printf( %d \n, sizeof(ptr) ); //affiche 4 car on affiche la taille réservée pour ptr // et non pointée par ptr Exemple 2 : void affichertabint(int pt[], const int pnbelt) ; void saisirtabint (int pt[], const int pnbelt) ; //on suppose que les fonctions sont définies ici void afficherinfo(int pt[], const int pnbelts) ; void afficherinfo(int pt[], const int pnbelts) printf( %d, %d \n, sizeof(pt), pnbelts); int main(void) int tab [5] ; printf( %d, %d \n, sizeof(tab), sizeof(tab) /sizeof(int) ); //affiche 20, 5 (20= 5*sizeof(int)) saisirtabint ( tab, 5) ; affichertabint(tab, sizeof(tab) /sizeof(int)) ; //affiche les éléments du tableau afficherinfo(tab, sizeof(t) /sizeof(int)) ; //affiche 4, 5 (4= sizeof(int *)) tab main pt A l appel de :0C int :10 taille de la zone allouée 5* sizeof(int) :14 int :20 5 :24 pt A l appel de afficherinfo :14 5 int * taille de la zone allouée sizeof(int*) int
112 12.9 Opérateur -> accès aux champs d une structure au travers d un pointeur Lorsqu un pointeur pointe un struct, il faut utiliser, à la fois : - la syntaxe pointeur pour l indirection (*ptr), - la syntaxe d accès à un champ de la zone pointée (.). ce qui donne : (*ptr). idchamp Note : les () sont ici obligatoires car l opérateur. est prioritaire sur l opérateur *. Autre Syntaxe possible : idptr -> idduchamp La notation (* idptr).idduchamp devient alors (plus simple) : idptr -> idduchamp typedef struct TEtudiant ; int numero ; char nom [51] ; void main() TEtudiant etu ; TEtudiant * ptr ; etu.numero = ; strcpy (etu.nom, "Dupont") ; ptr = & etu ; // initialise ptr à l adresse de la variable etu (*ptr).numero = ; ptr->numero = ; // Même résultat, mais syntaxe plus simple strcpy ((*ptr).nom, "Dupont") ; strcpy ( ptr -> nom, "Dupont") ; // Même résultat, mais syntaxe plus simple Accès au champ d une variable stucture idvariable. idchamp Accès au champ d une stucture à partir d un pointeur sur une structure idpointeur -> idchamp
113 12.10 Synthèse concernant les opérateurs & et * Soit T un nom de type, a et k des variables de type int et ptri une variable de type int *. T & void f(int &pval) ; T * int k =3 ; int *ptri; & a int a =3 ; int *ptri = & a; * ptri int k =3 ; int *ptri = & k; printf( %d, *ptri); Déclaration d une référence à un objet de type T Vous connaissez déjà une utilisation de l opérateur &. Lorsque cet opérateur est précédé par un nom de type T cet opérateur est utilisé pour une déclaration de paramètre de fonction qui est une référence à un objet de type T. Déclaration d un pointeur sur un objet de type T & a est l adresse de a * ptri est l'objet contenu à l adresse ptri En plus & est un opérateur logique et * l opérateur multiplicatif. POUR NE PAS SE TROMPER DANS L UTILISATION des opérateurs & et * il faut regarder leur contexte c est-à-dire ce qu il y a autour. Leur sens dépend de ce contexte
114 13 Les entrées/sorties formatées Les systèmes d'exploitation étant multitâches, plusieurs programmes fonctionnent en même temps. Les dispositifs d'entrées/sorties étant partagés, ils sont gérés par le système d'exploitation qui "dispatche" les entrées au bon programme et les sorties au bon fichier qui peut être un périphérique (cf. notion de fichier dans les systèmes d exploitation). Un flot est une abstraction permettant à un programme d'accéder à une ressource externe en échangeant avec celui-ci des données : des suites d'octets. Une ressource externe possible est un fichier, mais aussi l écran, le clavier, La notion de flot est très proche des notions UNIX correspondantes (stream). Un flot : o Se manifeste par une suite d'octets échangée avec un fichier. o Le flot peut être orienté : programme vers fichier, fichier vers programme. o Un flot peut être ou non "bufferisé" : non bufferisé : les octets échangés arrivent directement au destinataire, bufferisé : les octets échangés sont gérés au travers d'un tampon (buffer), les octets arrivent au destinataire selon des règles (particulières) de gestion du buffer (un buffer est assimilable à un tableau de caractères). Un flot se manipule au travers du type FILE et de fonctions d'accès aux flots qui sont des fonctions de lecture ou d'écriture dans des flots ou, par exemple, par les fonctions printf (écriture sur stdout c est-àdire sur la sortie standard) et scanf (lecture sur stdin c est-à-dire sur l entrée standard). Pour tout programme, dès le lancement, trois flots par défaut sont définis : o stdin : flot bufferisé initialisé en lecture sur l'entrée standard (clavier en général), o stdout : flot bufferisé initialisé en écriture sur la sortie standard (fenêtre d'écran en général), o stderr : flot non bufferisé initialisé en écriture immédiate sur la sortie erreur standard (fenêtre d'écran en général). Les fonctions d entrée/sortie bufferisées sont déclarées dans stdio.h Affichage à l écran sortie écran (sur stdout) : printf Syntaxe : int ou printf (const char chaineformat [], argument,... ); //syntaxe pas tout à fait correcte mais facilitant la compréhension int printf (const char * chaineformat, argument,... ); //syntaxe correcte Description : Ecrit sur la sortie standard (stdout) une sortie formatée qui peut être soit une chaîne de caractères soit une chaîne dans laquelle les arguments apparaîtront. Elle applique à chaque argument, dans l ordre, les spécifieurs de format contenus dans la chaîne chaineformat. En fait, elle remplace dans la chaîne de caractères à afficher (chaineformat) le premier spécifieur de format par la valeur du premier argument et ainsi de suite
115 Entrée : chaineformat :, argument,... : liste éventuelle d arguments Sortie : - Retourne : Si OK retourne le nombre d octets écrits, si échec retourne EOF (constante définie dans stdio.h). Préconditions : Il doit y avoir au moins autant d arguments que de spécifieurs de format sans quoi le résultat est imprédictible. Par contre s il y a trop d arguments, ils sont ignorés. Le spécifieur de format doit être compatible avec le type de l argument qui lui correspond. int i = 25; printf ( "Essai : %10d et %d fin\n", i, (i+10) ); Affiche : Essai : 25 et 35 fin (curseur) 13.2 Format d un spécifieur Syntaxe : %, flags, width, prec, F N h l L type_char donc trois parties : % les composants optionnels (entre crochets pour optionnels). type_char Description : seules les principales options seront décrites. o Composants optionnels flags - justifier à gauche. + toutes les valeurs signées sont précédées de leur signe. width n 0n.prec Définit le nombre minimum de caractères à écrire (complète avec des blancs ou des zéros). au moins n caractères seront écrits, complétés par des blancs. au moins n caractères seront écrits, complétés par des zéros. définit la précision de la valeur à écrire (son action dépend du type à afficher). Exemple pour les réels :.n définit le nombre de chiffres après la virgule, le dernier chiffre est arrondi. Exemple pour les chaînes de caractères :.n définit le nombre maximum de caractères de la chaîne qui seront affichés (avec troncature). F N h l L permet de donner plus de précision quant à la taille des données à afficher. N = pointeur near. F = pointeur far. h = short int. l = long (long int). L = long double. o type_char : format du type de la valeur à afficher
116 Affichage de valeurs numériques d ou i : entier signé en décimal. o entier non signé en octal. u entier non signé en décimal. x entier non signé en hexadécimal (avec a, b, c, d, e, f). X entier non signé hexadécimal (avec A, B, C, D, E, F). f flottant sous la forme [-]dddd.dddd. e flottant sous la forme e[+/-]ddd Affichage de Caractères c caractère (une valeur de type char). s chaîne de caractères ZTS. % affichage du caractère % Affichage de Pointeurs p pointeur. Le format dépend du modèle mémoire utilisé. Ce peut être XXXX:YYYY (segment:offset) ou YYYY (offset uniquement) Exemples int i = 13 ; printf ("ceci est un entier %d ", 12) ; printf ("ceci est un entier affiche sur 5 caractères %5d ", i) ; printf ("ceci est un entier affiche sur 5 caractères avec des 0 devant %05d ", i+1) ; printf ("ceci est un entier précédé de son signe %+d ", 3*i) ; printf ("ceci est un flottant notation classique %f ", 2.5) ; printf ("ceci est un flottant notation scientifique %e ", 2.5) ; printf ("taux : %d %% ", i) ; //Affiche " taux : 13 % Equivalence entre affichage par bcdisplay et affichage par printf : int som = 10; float moy = 20.0F; char c = 'A'; bcdisplayln ("S : ", som); bcdisplay ("Moyenne : ", moy); bcdisplayln (". Char : ", c); int som = 10; float moy = 20.0F; char c = 'A'; printf("s : %d\nmoyenne : %f. Char : %c\n", som, moy, c); 13.4 Principe des sorties bufferisées formatées Programme Buffer stdout Système d Exploitation printf ( "...", ); Ecran Le fonctionnement des sorties bufferisées est le suivant (cf. figure ci-dessus) : Le programme, lors de l'exécution d'une instruction de sortie (printf) : o positionne les caractères à afficher dans le buffer : transformation de valeurs numériques en caractères correspondants,..., o "prévient" le système d'exploitation lorsqu une demande de vidage du buffer est transmise
117 Le système d'exploitation : o lors de "l'arrivée" de la demande de vidage du buffer, affiche tous les caractères du buffer à l'écran et vide le buffer. L'instruction printf utilise le buffer stdout. Le vidage du buffer s'obtient : o par ajout du caractère '\n' (<Return>) dans le buffer (printf("...\n") ), o par l'instruction fflush (stdout) (voir fonction fflush) demandant le vidage du buffer (sans ajout d'un retour à la ligne), o lorsque le buffer est plein (taille maxi du buffer atteinte), o à la terminaison normale du programme (fin normale du main) Lecture au clavier (sur stdin) : scanf Syntaxe : int scanf (const char format [],address,... ); //syntaxe pas tout à fait correcte mais facilitant la compréhension ou int scanf (const char * format,address,... ); //syntaxe correcte Description : Lit sur l entrée standard (stdin) une ou plusieurs entrées formatées, dont le format est précisé dans format, et place les valeurs lues après les avoir converties en fonction de leur format aux adresses mémoire address Les formats sont de la même forme que ceux de printf (cf. 13.2). Entrée : format : chaîne de caractères contenant des spécifieurs de format de lecture. Sortie :, address,... : liste éventuelle des adresses des sorties. Retourne : Si OK retourne le nombre le nombre d entrées lues, converties et stockées avec succès. Sinon retourne 0 ou EOF (rien à lire). Préconditions : Il doit y avoir un spécifieur de format et une adresse pour chacune des entrées attendues : il doit donc y avoir autant de spécifieurs de format que d adresses. Il doit y avoir au moins autant d arguments address que de spécifieurs de format sans quoi le résultat est imprédictible. De plus, les spécifieurs de format doivent correspondre au type de la zone d adresse address qui leur est associée. Exemples : int i, j; int t[20] ; scanf( %d, &i) ; // Il faut indiquer une adresse où mettre le résultat => &i // Via cette adresse, scanf stockera la valeur lue dans la variable i for (j=1; j<20; j++) scanf( %d, &t[j]) ; // Idem : adresse d un élément du tableau t
118 13.6 Principe des entrées bufferisées formatées La lecture bufferisée d une entrée se déroule comme suit : o attente que le buffer d entrée contienne un ou des caractères, o recherche du premier caractère correspondant à un caractère interprétable comme le début d une donnée du format spécifié (cf. spécifieur de format), o lecture de la donnée caractère par caractère jusqu à ce que : le prochain caractère à lire soit un caractère de mise en page (par exemple un espace, une tabulation, entrée), une fin de fichier le prochain caractère à lire soit le début d une nouvelle donnée (exemple : un entier commence par un chiffre ou + ou - et les caractères suivants sont tous des chiffres ; si le prochain caractère à lire est une lettre alors il s agit du début d une nouvelle donnée). le nombre de caractères à lire soit atteint (exemple lire un caractère, lire une chaîne de 5 caractères). Remarque : il y aura un problème si dans le buffer (non vide) aucun caractère n est interprétable comme le début d une entrée du format spécifié. Le buffer n est pas modifié (donc pas de nouveau remplissage du buffer) et l entrée se termine sans que le contenu de l adresse soit modifié (pas de lecture effective) Chargement du buffer Programme Buffer stdin Système d Exploitation scanf ( "%d", &nbelts ); Clavier Le fonctionnement des entrées bufferisées est le suivant (cf. figure ci-dessus) : Le programme, lors de l'exécution d'une instruction d'entrée (scanf) : o si le buffer est vide : se met en attente de caractères dans le buffer, lors de l'arrivée de caractères dans le buffer, il est "prévenu", il traite les caractères un par un. o si le buffer n'est pas vide, il traite les caractères un par un. Le système d'exploitation : o lors de la frappe de caractères au clavier, il les stocke dans une mémoire tampon propre, o lors de la frappe d'un return, il positionne les caractères (return compris) dans le buffer et "prévient" le programme Recherche du premier caractère correspondant au début d une donnée Format numérique : %d. L'instruction scanf va fonctionner ainsi : passer tous les caractères "séparateurs" ou de mise en page (espace, return, tabulation, ), rechercher le premier caractère correspondant au début d un entier soit : '+','-' ou un chiffre, traiter tous les caractères chiffres qui suivent jusqu'à trouver un autre caractère que chiffre. Format numérique : %f. L'instruction scanf va fonctionner comme pour le format %d, mais en traitant le '.' décimal, les exposants,. Format caractère : %c. L'instruction scanf va prendre le premier caractère qui se trouve dans le buffer, quelque soit son code ASCII. En particulier un return sera considéré comme une valeur acceptable. En conséquence lorsqu on lit à l écran caractère par caractère avec des scanf (par exemple des choix de menus), il faut faire attention à ne pas prendre en compte des caractères résiduels qui ne nous intéressent pas pour une nouvelle entrée
119 Exemple: char c ; scanf("%c",&c) ; // si on tape : A scanf lit A printf("%c",c) ; scanf("%c",&c) ;//le buffer contient le caractère, scanf lit Pour éviter cela, on vide les entrées résiduelles qui se trouvent dans le buffer d entrée (introduction d instructions fflush) (cf. 13.7) Vidage du buffer associé à un stream : fflush Pour éviter les problèmes d'utilisation du buffer stdin par les différentes instructions de lecture, utiliser l'instruction fflush (FILE *stream) qui vide un buffer. A utiliser juste avant un scanf. Permet de s'assurer que l'on se mettra bien en attente d'une saisie, sans prendre en compte un "résidu". Syntaxe : int fflush(file *stream); Description : Vide le buffer associé à un stream (stdin, stdout et stderr sont des streams). Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : - Retourne : 0 si succès, EOF sinon. Exemple: char c ; scanf(" %c ", &c) ; // si on tape : A, scanf lit A printf(" %c ",c) ; fflush(stdin) ; //vide le buffer (il y restait ) scanf(" %c ", &c) ; //attente d une nouvelle entrée car le buffer est vide
120 14 Utilisation de primitives de gestion de fichiers de haut niveau en langage C Un fichier est un ensemble structuré d informations. Cet ensemble est stocké sur un périphérique de l ordinateur (disque dur, CD-ROM, clef USB, disquette). Il est constitué d une suite d éléments appelés enregistrements (de même structure). L enregistrement est l unité d accès au fichier. Un fichier est indépendant et existe dans le système d exploitation de façon distincte des programmes qui le manipulent (création, lecture). Intérêt des fichiers : o conserver des données entre deux exécutions d un programme, o échanger des données entre programmes, o traiter des volumes de données dont la taille ne peut contenir en mémoire centrale (traitements directs sur fichiers) Caractéristiques principales d un fichier L'unité de stockage dans un fichier est l'enregistrement. L'enregistrement peut être simple (un entier, un réel, un char) ou complexe (un struct) mais aussi une chaîne de caractères. Deux opérations de base existent sur un fichier : o l écriture des enregistrements dans un fichier : les données sont transférées de la mémoire centrale vers le fichier sur disque, o la lecture des enregistrements à partir d un fichier : les données sont transférées du fichier sur disque vers la mémoire centrale. Quel que soit le type de fichier manipulé, une caractéristique importante est que, a priori, le contenu du fichier ne se trouve jamais en totalité en mémoire centrale (sauf cas particulier). Une autre caractéristique importante est que, pour pouvoir traiter un enregistrement, il doit auparavant être chargé en mémoire à partir du fichier. Les fichiers séquentiels sont constitués d'une suite d'enregistrements terminée par un marqueur de fin de fichier. Un fichier séquentiel est caractérisé par le fait que les enregistrements sont lus et écrits séquentiellement dans le fichier. Lors de l écriture, l écriture du n ième enregistrement implique d écrire auparavant les n-1 qui le précèdent. Lors de la lecture, l accès au n ième enregistrement implique d accéder auparavant au n-1 qui le précèdent. Les enregistrements d un fichier séquentiel ne sont jamais tous «accessibles» en même temps. On doit les «accéder» un par un (lecture ou écriture). Schéma Physique (pour la lecture): LABEL Enreg 1 Enreg 2 Enreg n-2 Enreg n-1 Enreg n EOF LABEL : en-tête de fichier sur disque (utilisé par le système d exploitation). Inaccessible au programme. Enreg 1, Enreg n : enregistrements. EOF : marqueur de fin de fichier (End Of File) permettant de repérer la fin du fichier, le nombre d enregistrements étant inconnu a priori
121 Exemple de fichier d'entiers : LABEL FF 4 octets 4 octets 4 octets 4 octets Les enregistrements sont rangés, dans le fichier, dans l'ordre où ils y ont été écrits. Lors de la lecture d'un fichier, on ne connaît pas, a priori, le nombre d'enregistrements contenus dans le fichier. Seul le marqueur de fin repère la fin de fichier. Les opérations sur un fichier se font enregistrement par enregistrement. Un fichier doit être écrit et lu avec le même format d'enregistrement (sinon le contenu du fichier sera mal interprété). stocker des entiers => lire des entiers On peut stocker dans un fichier des valeurs de pointeurs, c'est à dire des adresses, mais elles sont inutilisables ultérieurement car ce ne sont jamais les mêmes à chaque exécution. Pour un fichier, on différencie : o son nom externe ou physique qui permet l identification du fichier par le système d'exploitation (cf. Unix). fichier.cpp mais aussi bcio.h ou c:\test\tp1.pdf o son nom interne ou logique, à l intérieur d un programme (en général, c est une variable) Modes d opérations sur les fichiers Par principe, les modes d opération applicables à un fichier sont : o la création : créer tous les enregistrements, o la consultation : accéder aux enregistrements, o les mises à jour (d un fichier physique) : modification d un ou plusieurs enregistrements, ajout d un ou plusieurs enregistrements, suppression d un ou plusieurs enregistrements. Dans le cas des fichiers séquentiels, seuls trois modes d opération sont disponibles : création, consultation et ajout en fin (cas particulier de la mise à jour). A un instant donné, un fichier ne peut être utilisé que dans un seul de ces modes. Pour effectuer des mises à jour de fichiers séquentiels, on doit utiliser des recopies temporaires des fichiers afin d obtenir le résultat souhaité Manipulation d un fichier Un fichier est une ressource externe au programme. On y accède au travers d opérations spécifiques. Un fichier séquentiel ne peut se traiter que enregistrement par enregistrement. En particulier, pour la consultation (lecture), les enregistrements doivent être chargés en mémoire un à un (dans une variable), puis traités. On peut éventuellement charger un à un les enregistrements dans un tableau par exemple, mais il faut être sûr de disposer d un tableau comprenant suffisamment d éléments. Ceci n est pas toujours possible, voire très rarement. Les différentes opérations d accès à un fichier séquentiel comprennent : o la déclaration d un fichier : cette variable, de type "Fichier", sera «le nom interne du fichier» et fera le lien entre le programme et le fichier réel,
122 o des instructions d'ouverture et fermeture de fichier : - ouverture de fichier : faire le lien entre le nom externe du fichier et la structure interne permettant de gérer le fichier ; elle fournit si ce lien est créé le nom interne du fichier, - fermeture de fichier : pour libérer la structure interne qui n est plus utile, et rendre le fichier disponible aux autres programmes, o des instructions de manipulation : o lecture et d'écriture, o test de fin de fichier, o éventuellement, test de bon fonctionnement des instructions d'accès aux fichiers. Toutes les fonctions et type pour gérer les fichiers sont définis dans stdio.h. Remarque : Ces fonctions s utilisent dans l ordre : ouverture, manipulation, fermeture. Plus aucun accès au fichier n est possible dès lors que le fichier est fermé. En langage C, un fichier se manipule au travers d un flot associé au fichier. Nous nous intéresserons ici à l accès aux fichiers au travers de flots bufferisés (entrées/sorties dites de haut niveau). Nous parlerons dorénavant de fichier. Il ne faudra pas oublier que c est toujours un flot sur un fichier qu on manipule Le type de variable descripteur de fichier en C : FILE Le type FILE est le type descripteur de fichier. C est une structure prédéfinie décrite dans le fichier "stdio.h". Elle contient toutes les informations nécessaires à la gestion d un fichier (dont le buffer qui lui est associé). Les informations contenues dans la structure sont peu utiles au programmeur mais sont utilisées par le système pour accéder au fichier ainsi que par les fonctions d accès aux fichiers Le type prédéfini FILE, déclaration de variable fichier En langage C, on ne manipule pas directement une variable de type FILE (un descripteur) mais un pointeur sur un descripteur (un FILE *) - ceci pour éviter des recopies de structures importantes -. Syntaxe : FILE * nomvariablefichier ; // Attention : on déclare un pointeur FILE * f-in, * f-out; Ces pointeurs n'ont de sens que pour être manipulés au travers des fonctions de gestion de fichier (ouverture, fermeture, lecture, écriture, ) Modes de codage En langage C, les informations copiées dans un fichier à partir d un emplacement mémoire peuvent être transcrites sans aucune modification, c est le mode binaire, ou peuvent être codées en ASCII, c est le mode texte. Dans le cas du mode texte, le codage n affecte en général que des caractères spéciaux (par exemple fin de ligne), mais il dépend du compilateur et de l environnement utilisés. On utilisera surtout le mode binaire. En mode binaire, les données sont stockées dans un fichier comme une recopie de leur représentation binaire en mémoire. Par exemple, l écriture dans un fichier d un int induira la recopie de sa représentation binaire : 4 octets codés en complément à deux
123 14.5 Ouverture de fichier : fopen Syntaxe : FILE * fopen (const char * nomfichier, const char * mode) ; Description : Ouvre le fichier physique nomfichier (nom externe) suivant le mode spécifié (recherche le fichier nomfichier du système d exploitation et demande au système d exploitation de l ouvrir dans le mode mode). Pour cela, si le fichier nomfichier existe et est accessible en mode mode : o alloue un emplacement pour un objet du type FILE, o met en relation le nom physique (externe) avec le nom logique (interne) (on appelle cela l assignation), o retourne l adresse de l objet alloué. Dans le cas où l ouverture n est pas possible, fopen retourne NULL. Cette erreur peut se produire par exemple si le fichier n est pas accessible dans le mode choisi ( mode "r " par exemple), ou si le fichier n existe pas à l emplacement cité (erreur de chemin) Entrée : nomfichier mode : caractérise le mode d'ouverture et le mode de codage. C est une chaîne de caractères composée du ou des caractères indiquant le mode d ouverture suivis du caractère b pour binaire ou t pour texte. Sortie : - Modes d ouverture principaux : mode d'accès fichier physique existant fichier physique inexistant r Lecture seule Ok Erreur (renvoi NULL) w Ecriture seule Ecrasement Création a Ajout seul Ajout fin fichier Création indiquer le mode "rb" pour ouvrir en lecture en mode binaire. Retourne : Renvoie un pointeur vers un FILE, et NULL si une erreur se produit lors de l'ouverture. La validité de l ouverture d un fichier doit toujours être testée. ouvrir en lecture un fichier inexistant => erreur d'ouverture => renvoi d'une valeur NULL. FILE * fin ; fin = fopen ("monfichier.don", "rb") ; if (fin == NULL) exit (1) ; 14.6 Fermeture de fichier : fclose Syntaxe : int fclose (FILE * stream); Description : Vide le buffer de stream et ferme le fichier au niveau du système d exploitation. Entrée : stream : valeur retournée par fopen. Sortie : - Retourne : 0 si OK, EOF sinon (constante définie dans stdio.h). Précondition : stream est la valeur de FILE * retournée par fopen
124 FILE * fin;... fin = fopen("monfich.dat", "rb"); if (fin == NULL ) printf("erreur d'ouverture de fichier"); exit(1);. /* Travail sur fichier */ fclose (fin); 14.7 Fonction de lecture binaire dans un fichier Syntaxe : size_t fread ( void * adresse, size_t taille, size_t nbre, FILE * stream ) ; Description : Lit taille * nbre octets dans le fichier de nom logique stream et place ces informations en mémoire à l adresse spécifiée (paramètre de sortie). Entrée : taille : nombre d octets d un enregistrement logique (utiliser sizeof). nbre : nombre d enregistrements logiques lus en une fois (généralement 1). Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : adresse : adresse mémoire où sont placés les octets lus. Retourne : Le nombre d'enregistrements logiques effectivement lus dans le fichier. Exemples : int i; FILE * fin ; fin = fopen("monfich.dat", "rb"); if (fin == NULL ) printf("erreur d'ouverture de fichier"); exit(1); fread ( &i, sizeof(int), 1, fin ); // Pour lire un fichier d'entiers TIndividu pers; FILE * fin ; fin = fopen("monfich.dat", "rb"); if (fin == NULL ) printf("erreur d'ouverture de fichier"); exit(1); fread ( &pers, sizeof ( TIndividu ), 1, fin ); //Un fichier de TIndividu
125 14.8 Fonction d écriture binaire dans un fichier Syntaxe : size_t fwrite ( void * adresse, size_t taille, size_t nbre, FILE * stream ) ; Description : Ecrit taille * nbre octets dans le fichier de nom logique stream. Ces informations sont copiées depuis la mémoire centrale à partir de l adresse spécifiée (paramètre d entrée passé par adresse). Entrée : taille : nombre d octets d un enregistrement logique (utiliser sizeof). nbre : nombre d enregistrements logiques écrits en une fois (généralement 1). adresse : adresse mémoire où sont placées les données à écrire. Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : - Retourne : Le nombre d enregistrements logiques effectivement écrits dans le fichier. TIndividu pers; FILE * fout ; fout = fopen("monfich.dat", "wb"); if (fout == NULL ) printf("erreur d'ouverture de fichier"); exit(1); fwrite (&tpers[i], sizeof (TIndividu), 1, fout ); 14.9 Contrôle d'accès aux fichiers Test de fin de fichier en lecture : feof Syntaxe : int feof ( FILE * stream ); Description : Retourne la valeur de l indicateur feof qui est positionné à vrai si la fin du stream est déjà atteinte. Entrée : stream Sortie : - Retourne : La valeur de la variable booléenne feof positionnée après un accès au fichier seulement. Précondition : L indicateur de fin de fichier est positionné après un accès en lecture au fichier seulement. feof teste donc si le dernier accès en lecture réalisé a rencontré la fin de fichier ou pas
126 Détection des erreurs d'accès aux fichiers : ferror Les fichiers étant des ressources externes au programme, les accès par les fonctions peuvent provoquer des erreurs (par exemple : clé USB retirée durant une lecture de fichier). Chaque opération sur fichier devra donc être contrôlée pour s assurer du bon déroulement de l opération sur le fichier. Syntaxe : int ferror ( FILE * stream ); Description : Chaque opération d accès à un fichier (hormis fopen car le flot doit exister avant l opération) positionne sur le flot concerné un indicateur d erreur de l opération. ferror permet de tester ce code erreur. Retourne la valeur de l indicateur d erreur positionné par le dernier accès au stream. Entrée : stream. Sortie : - Retourne : 0 si aucune erreur ne s'est produite, une valeur <>0 sinon. Remarque : ferror est à tester systématiquement après chaque opération sur fichier pouvant positionner l indicateur d erreur (toutes sauf fopen). Précondition : Une opération d accès au fichier doit avoir été effectuée préalablement au test de ferror. Remarque : Bien noter que la rencontre de la fin de fichier par une opération de lecture ne positionne que fin de fichier à vrai, mais ne positionne pas d indicateur d erreur. En effet, rencontrer la fin d un fichier en lecture est une condition normale d utilisation fichiers de structures Un fichier peut contenir comme enregistrement logique de type structure : o L'accès par fread et fwrite : utiliser sizeof (NomType). o Ecrire des variables de type structure. o Lire des variables de type structure. L écriture ou la lecture d un enregistrement de type structure dans un fichier utilise la logique relative à l affectation de structure : o écriture/lecture : tous les champs sont écrits/lus comme s ils l étaient champ par champ, o les champs tableaux sont écrits/lus comme s ils l étaient élément par élément ; les éléments «vides» sont écrits/lus aussi (ex des chaînes de caractères). o les pointeurs ne voient que leur valeur (adresse mémoire) écrite/lue dans les fichiers, la zone pointée n étant en aucun cas copiée. L utilité de la «sauvegarde» d une valeur de pointeur est donc à bien réfléchir (en général inutile). Ecrire dans un fichier un enregistrement de type TDate défini ainsi : typedef struct int jour, mois, an; TDate ; Equivaut à écrire 3 int!! Il faut lire et écrire le fichier avec la même structure exactement en terme d'ordre des types de données ; en général, le même type structure
127 Exemples : // Ecriture dans un fichier du contenu // du tableau pt void sauvtab ( const TIndividu pt[], int pnb ) FILE * fout; char nomfic [MAX]; int i; printf ("Nom fic à créer : "); scanf ("%s", nomfic); fout = fopen (nomfic, "wb"); if (fout == NULL) exit (1) ; // Chargement dans un tableau pt // du contenu d un fichier void restauretab (TIndividu pt[], int &pnb ) FILE * fin; char nomfic [MAX]; int i; TIndividu temp; printf ("Nom fic à restaurer : "); scanf ("%s", nomfic); fin = fopen (nomfic, "rb"); if (fin == NULL) exit (1); // Accède au premier enregistrement // ou positionne EOF à vrai fread (&temp, sizeof(tindividu), 1, fin); if ( ferror (fin) ) exit (1); pnb = 0; for ( i=0; i <pnb; i++) fwrite (&pt[i], sizeof(tindividu),1, fout); while ( feof(fin) == 0) // OU (! feof (fin) ) pt[pnb] = temp; pnb = pnb + 1; if ( ferror (fout)!= 0) exit (1); fclose (f); fread (&temp, sizeof(tindividu), 1, fin); if ( ferror (fin) ) exit (1); fclose (f); Fichiers de caractères Les fichiers peuvent être traités en mode caractère (suite de caractères avec des retours chariot, ). Ils ont le même fonctionnement que les fichiers pour les boucles de parcours et contrôles d'accès fichier. Tous les caractères sont traités de la même manière, par exemple les '\n' (écrits et lus) Lecture : fgetc Ouverture : Ouvrir en mode texte (t) : "rt", wt", "at". (t optionnel car par défaut) Syntaxe : int fgetc ( FILE * stream ) ; Description : Lit un caractère dans le stream. Entrée/sortie : stream (il est modifié car on se déplace dans le stream) Sortie : - Retourne : Renvoie le char lu si lecture effectuée, ou EOF si un «problème» de lecture est rencontré (par exemple : fin de fichier ou erreur d accès)
128 Ecriture : fputc Syntaxe: int fputc ( int c, FILE * stream ) ; Description : Ecrit le caractère c dans le stream. Entrée : c : le caractère à écrire. Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : - Retourne : Renvoie c si écriture effectuée, EOF si un problème d'écriture est rencontré (erreur d accès). Lecture d un fichier texte (par exemple : le source d un programme) caractère par caractère, '\n' compris. FILE * fin; char car; fin = fopen ("tp6.cpp", "r"); if (fin ==NULL) exit (1); car = fgetc (fin); if ( ferror(fin) ) exit (1); // ou bien int i = fgetc (fin) ; if (i==eof) exit(1); c= i; while (! feof (fin)) printf("%c", car); car = fgetc (fin); if (ferror(fin)) exit (1); fclose (fin); Fichiers de chaînes de caractères L'accès aux fichiers peut aussi se faire en mode chaînes de caractères (tableau contenant un '\0'). Ils ont le même fonctionnement que les fichiers pour les boucles de parcours et contrôles d'accès fichier Lecture : fgets Ouverture : Ouvrir en mode texte (t) : "rt", wt", "at". (t optionnel car par défaut) Syntaxe : char * fgets (const char * ligne, int maxl, FILE * stream ) ; Description : Lit maximum maxl-1 caractères dans le stream, les range dans ligne en ajoutant '\0'. La lecture est arrêtée lorsque maxl-1 caractères sont lus ou bien '\n' est rencontré. Entrée : maxl. Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : ligne. Retourne : NULL si problème de lecture,!= NULL sinon (en fait retourne ligne)
129 Ecriture : fputs Syntaxe: int fputs ( const char * ligne, FILE * stream ) ; Description : Ecrit tous les caractères de ligne dans stream sauf '\0'. strlen (ligne) caractères seront donc écrits. Entrée : ligne : contient la chaîne de caractères à écrire terminée par '\0'. Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : - Retourne : Renvoie EOF si problème d'écriture, une valeur >=0 sinon Fichiers de données formatées Les fichiers de données peuvent être formatés (cf. scanf et printf). Syntaxes : int fprintf (FILE *stream, const char * format, argument,... ) int fscanf (FILE *stream, const char * format, adresse... ) Remarque : printf et scanf réalisent les mêmes opérations que fprintf et fscanf, mais sur des flots prédéfinis (respectivement stdout et stdin). On a donc : printf ( "Valeur : %d", i ) <=> fprintf ( stdout, "Valeur : %d", i ) ; En effet, stdout est un flot bufferisé (FILE *) ouvert en écriture sur la ressource écran. Son ouverture est réalisée par le système d exploitation au lancement du programme Gestion du curseur de fichier Chaque accès à un fichier déplace le curseur d accès au fichier. Ce curseur repère la position courante dans le fichier pour le prochain accès. Il est possible de gérer la position du curseur de lecture dans le fichier Fonction de déplacement du curseur dans un fichier : fseek Syntaxe : int fseek (FILE *stream, long deplacement, int positiondereference); Description : Déplace le curseur du stream de deplacement octets à partir de la position de référence positiondereference. Entrée : deplacement : nombre d'octets dont on souhaite déplacer le curseur de fichier (en général calculé à partir de la fonction sizeof ()). positiondereference : valeur donnant la position de départ pour le déplacement dans le fichier. Ce sont des constantes définies dans stdio.h. SEEK_SET : depuis le début du fichier SEEK_CUR : depuis la position courante SEEK_END : depuis la fin de fichier
130 Entrée/sortie : stream (il est modifié car on se déplace dans le stream). Sortie : - Retourne : 0 si l opération a réussi une valeur différente de zéro sinon. Cette fonction est utile en lecture pour faire des accès directs à un enregistrement particulier dont on connaît la position (le numéro par exemple) Fonction donnant la position courante du curseur dans un fichier : ftell Syntaxe : long int ftell (FILE *stream); Description : Renvoie la position du curseur, en nombre d'octets, dans le stream. Entrée : stream. Sortie : - Retourne : Renvoie la position du curseur, en nombre d'octets, dans le stream. Cette fonction est utile pour connaître la position courante afin de savoir comment exprimer un déplacement ultérieur à l aide de la fonction fseek
Licence ST Université Claude Bernard Lyon I LIF1 : Algorithmique et Programmation C Bases du langage C 1 Conclusion de la dernière fois Introduction de l algorithmique générale pour permettre de traiter
Cours d Algorithmique-Programmation 2 e partie (IAP2): programmation 24 octobre 2007impérative 1 / 44 et. structures de données simples
Cours d Algorithmique-Programmation 2 e partie (IAP2): programmation impérative et structures de données simples Introduction au langage C Sandrine Blazy - 1ère année 24 octobre 2007 Cours d Algorithmique-Programmation
Info0101 Intro. à l'algorithmique et à la programmation. Cours 3. Le langage Java
Info0101 Intro. à l'algorithmique et à la programmation Cours 3 Le langage Java Pierre Delisle, Cyril Rabat et Christophe Jaillet Université de Reims Champagne-Ardenne Département de Mathématiques et Informatique
Algorithmique et Programmation, IMA
Algorithmique et Programmation, IMA Cours 2 : C Premier Niveau / Algorithmique Université Lille 1 - Polytech Lille Notations, identificateurs Variables et Types de base Expressions Constantes Instructions
Introduction au langage C
Introduction au langage C Cours 1: Opérations de base et premier programme Alexis Lechervy Alexis Lechervy (UNICAEN) Introduction au langage C 1 / 23 Les premiers pas Sommaire 1 Les premiers pas 2 Les
INITIATION AU LANGAGE C SUR PIC DE MICROSHIP
COURS PROGRAMMATION INITIATION AU LANGAGE C SUR MICROCONTROLEUR PIC page 1 / 7 INITIATION AU LANGAGE C SUR PIC DE MICROSHIP I. Historique du langage C 1972 : naissance du C dans les laboratoires BELL par
UE Programmation Impérative Licence 2ème Année 2014 2015
UE Programmation Impérative Licence 2 ème Année 2014 2015 Informations pratiques Équipe Pédagogique Florence Cloppet Neilze Dorta Nicolas Loménie [email protected] 2 Programmation Impérative
IN 102 - Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C
IN 102 - Cours 1 Qu on le veuille ou non, les systèmes informatisés sont désormais omniprésents. Même si ne vous destinez pas à l informatique, vous avez de très grandes chances d y être confrontés en
1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.
1. Structure d un programme C Un programme est un ensemble de fonctions. La fonction "main" constitue le point d entrée pour l exécution. Un exemple simple : #include int main() { printf ( this
UE C avancé cours 1: introduction et révisions
Introduction Types Structures de contrôle Exemple UE C avancé cours 1: introduction et révisions Jean-Lou Desbarbieux et Stéphane Doncieux UMPC 2004/2005 Introduction Types Structures de contrôle Exemple
Programmation en langage C
Programmation en langage C Anne CANTEAUT INRIA - projet CODES B.P. 105 78153 Le Chesnay Cedex [email protected] http://www-rocq.inria.fr/codes/anne.canteaut/cours C 2 Table des matières 3 Table des
Bases de programmation. Cours 5. Structurer les données
Bases de programmation. Cours 5. Structurer les données Pierre Boudes 1 er décembre 2014 This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. Types char et
Cours d Algorithmique et de Langage C 2005 - v 3.0
Cours d Algorithmique et de Langage C 2005 - v 3.0 Bob CORDEAU [email protected] Mesures Physiques IUT d Orsay 15 mai 2006 Avant-propos Avant-propos Ce cours en libre accès repose sur trois partis pris
Claude Delannoy. 3 e édition C++
Claude Delannoy 3 e édition Exercices Exercices C++ en en langage langage delc++ titre 4/07/07 15:19 Page 2 Exercices en langage C++ AUX EDITIONS EYROLLES Du même auteur C. Delannoy. Apprendre le C++.
UEO11 COURS/TD 1. nombres entiers et réels codés en mémoire centrale. Caractères alphabétiques et caractères spéciaux.
UEO11 COURS/TD 1 Contenu du semestre Cours et TDs sont intégrés L objectif de ce cours équivalent a 6h de cours, 10h de TD et 8h de TP est le suivant : - initiation à l algorithmique - notions de bases
Langage C. Patrick Corde. [email protected]. 22 juin 2015. Patrick Corde ( [email protected] ) Langage C 22 juin 2015 1 / 289
Langage C Patrick Corde [email protected] 22 juin 2015 Patrick Corde ( [email protected] ) Langage C 22 juin 2015 1 / 289 Table des matières I 1 Présentation du langage C Historique Intérêts
Programmation système I Les entrées/sorties
Programmation système I Les entrées/sorties DUT 1 re année Université de Marne La vallée Les entrées-sorties : E/O Entrées/Sorties : Opérations d échanges d informations dans un système informatique. Les
Le Langage C Version 1.2 c 2002 Florence HENRY Observatoire de Paris Université de Versailles [email protected]
Le Langage C Version 1.2 c 2002 Florence HENRY Observatoire de Paris Université de Versailles [email protected] Table des matières 1 Les bases 3 2 Variables et constantes 5 3 Quelques fonctions indispensables
INTRODUCTION A JAVA. Fichier en langage machine Exécutable
INTRODUCTION A JAVA JAVA est un langage orienté-objet pur. Il ressemble beaucoup à C++ au niveau de la syntaxe. En revanche, ces deux langages sont très différents dans leur structure (organisation du
Programmer en JAVA. par Tama ([email protected]( [email protected])
Programmer en JAVA par Tama ([email protected]( [email protected]) Plan 1. Présentation de Java 2. Les bases du langage 3. Concepts avancés 4. Documentation 5. Index des mots-clés 6. Les erreurs fréquentes
Introduction à l algorithmique et à la programmation M1102 CM n 3
Introduction à l algorithmique et à la programmation M1102 CM n 3 DUT Informatique 1 re année Eric REMY [email protected] IUT d Aix-Marseille, site d Arles Version du 2 octobre 2013 E. Remy (IUT d
Dans le chapitre 1, nous associions aux fichiers ouverts des descripteurs de fichiers par lesquels nous accédions aux fichiers.
I Présentation : Dans le chapitre 1, nous avons vu comment utiliser les appels-systèmes de bas niveau pour créer et accéder à des fichiers sur le disque. Nous avons notamment mis en évidence leur dépouillement
1/24. I passer d un problème exprimé en français à la réalisation d un. I expressions arithmétiques. I structures de contrôle (tests, boucles)
1/4 Objectif de ce cours /4 Objectifs de ce cours Introduction au langage C - Cours Girardot/Roelens Septembre 013 Du problème au programme I passer d un problème exprimé en français à la réalisation d
Algorithme. Table des matières
1 Algorithme Table des matières 1 Codage 2 1.1 Système binaire.............................. 2 1.2 La numérotation de position en base décimale............ 2 1.3 La numérotation de position en base binaire..............
Chapitre 1 : La gestion dynamique de la mémoire
Chapitre 1 : La gestion dynamique de la mémoire En langage C un programme comporte trois types de données : Statiques; Automatiques ; Dynamiques. Les données statiques occupent un emplacement parfaitement
Cours d introduction à l informatique. Partie 2 : Comment écrire un algorithme? Qu est-ce qu une variable? Expressions et instructions
Cours d introduction à l informatique Partie 2 : Comment écrire un algorithme? Qu est-ce qu une variable? Expressions et instructions Qu est-ce qu un Une recette de cuisine algorithme? Protocole expérimental
DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51
DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51 PLAN DU COURS Introduction au langage C Notions de compilation Variables, types, constantes, tableaux, opérateurs Entrées sorties de base Structures de
INITIATION A LA PROGRAMMATION
2004-2005 Université Paris Dauphine IUP Génie Mathématique et Informatique INITIATION A LA PROGRAMMATION PROCEDURALE, A L'ALGORITHMIQUE ET AUX STRUCTURES DE DONNEES PAR LE LANGAGE C Maude Manouvrier La
Les structures. Chapitre 3
Chapitre 3 Les structures Nous continuons notre étude des structures de données qui sont prédéfinies dans la plupart des langages informatiques. La structure de tableau permet de regrouper un certain nombre
Rappels Entrées -Sorties
Fonctions printf et scanf Syntaxe: écriture, organisation Comportement Données hétérogènes? Gestion des erreurs des utilisateurs 17/11/2013 Cours du Langage C [email protected] ibrahimguelzim.atspace.co.uk
Notions fondamentales du langage C# Version 1.0
Notions fondamentales du langage C# Version 1.0 Z 2 [Notions fondamentales du langage Csharp] [Date : 25/03/09] Sommaire 1 Tout ce qu il faut savoir pour bien commencer... 3 1.1 Qu est ce qu un langage
Cours 1 : Introduction Ordinateurs - Langages de haut niveau - Application
Université de Provence Licence Math-Info Première Année V. Phan Luong Algorithmique et Programmation en Python Cours 1 : Introduction Ordinateurs - Langages de haut niveau - Application 1 Ordinateur Un
TP 1. Prise en main du langage Python
TP. Prise en main du langage Python Cette année nous travaillerons avec le langage Python version 3. ; nous utiliserons l environnement de développement IDLE. Étape 0. Dans votre espace personnel, créer
Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski [email protected]
Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski [email protected] Mars 2002 Pour Irène et Marie Legal Notice Copyright c 2002 Patrick Cégielski Université
TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile
TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile Dans ce TP, vous apprendrez à définir le type abstrait Pile, à le programmer en Java à l aide d une interface
Initiation. àl algorithmique et à la programmation. en C
Initiation àl algorithmique et à la programmation en C Initiation àl algorithmique et à la programmation en C Cours avec 129 exercices corrigés Illustration de couverture : alwyncooper - istock.com Dunod,
Les structures de données. Rajae El Ouazzani
Les structures de données Rajae El Ouazzani Les arbres 2 1- Définition de l arborescence Une arborescence est une collection de nœuds reliés entre eux par des arcs. La collection peut être vide, cad l
Les chaînes de caractères
Les chaînes de caractères Dans un programme informatique, les chaînes de caractères servent à stocker les informations non numériques comme par exemple une liste de nom de personne ou des adresses. Il
STAGE IREM 0- Premiers pas en Python
Université de Bordeaux 16-18 Février 2014/2015 STAGE IREM 0- Premiers pas en Python IREM de Bordeaux Affectation et expressions Le langage python permet tout d abord de faire des calculs. On peut évaluer
Algorithmique et programmation : les bases (VBA) Corrigé
PAD INPT ALGORITHMIQUE ET PROGRAMMATION 1 Cours VBA, Semaine 1 mai juin 2006 Corrigé Résumé Ce document décrit l écriture dans le langage VBA des éléments vus en algorithmique. Table des matières 1 Pourquoi
Langage Éric Guérin 5 octobre 2010
Langage Éric Guérin 5 octobre 2010 Langage C TABLE DES MATIÈRES Table des matières 1 Introduction 7 1.1 Historique........................................... 7 1.2 Architecture matérielle....................................
Programmation Classique en langage C
DI GALLO Frédéric Programmation Classique en langage C Cours du Cycle d Approfondissement CNAM ANGOULEME 2000-2001 DI GALLO Frédéric Page 1 01/04/01 PROGRAMMATION CLASSIQUE : LANGAGE C DI GALLO Frédéric
Programmation C. J.-F. Lalande. 15 novembre 2012
Programmation C J.-F. Lalande novembre 0 Ce cours est mis à disposition par Jean-François Lalande selon les termes de la licence Creative Commons Attribution - Pas d Utilisation Commerciale - Partage à
Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if
Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if Pierre Boudes 28 septembre 2011 This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
Le langage C. Introduction, guide de reference
Le langage C Introduction, guide de reference Ce document est une présentation du langage de programmation C, de sa syntaxe et de ses spécificités. Il est destiné essentiellement à servir de mémo-guide
Programmation Structurée en Langage C
École Centrale Marseille Programmation Structurée en Langage C Stéphane Derrode Mathématique et Informatique Révision 2.5, 2006. Table des matières 1 En guise d'introduction... 7 1.1 Quelques repères
Introduction au Langage de Programmation C
Faculté Polytechnique de Mons Service d'informatique Introduction au Langage de Programmation C Mohammed Benjelloun 1 ère Candidature Année académique 2003-2004 Avant-propos Ces notes permettent de se
Méthodes de programmation systèmes UE n NSY103. Notes de cours. Nombre d'heures : 55h (~18 + 1 cours de 3 heures)
Méthodes de programmation systèmes UE n NSY103 Notes de cours Code de l UE : NSY103 Titre de la formation : Méthodes de programmation systèmes Ouvert : Ouvert Type de diplôme : Unité de valeur CNAM. Nombre
BTS IRIS Cours et Travaux Pratiques. Programmation C. A. Lebret, TSIRIS, Lycée Diderot, 1995/06. en conformité avec le référentiel du BTS IRIS
BTS IRIS Cours et Travaux Pratiques Programmation C A. Lebret, TSIRIS, Lycée Diderot, 1995/06 en conformité avec le référentiel du BTS IRIS Activité Codage et Réalisation Tâches T3.3, T3.4 et T3.5 Temps
Algorithmes et Programmes. Introduction à l informatiquel. Cycle de vie d'un programme (d'un logiciel) Cycle de vie d'un programme (d'un logiciel)
Algorithmes et Programmes Introduction à l informatiquel! Vie d'un programme! Algorithme! Programmation : le langage! Exécution et test des programmes Chapitre : Algorithmes et Programmes 2 Cycle de vie
Cours intensif Java. 1er cours: de C à Java. Enrica DUCHI LIAFA, Paris 7. Septembre 2009. [email protected]
. Cours intensif Java 1er cours: de C à Java Septembre 2009 Enrica DUCHI LIAFA, Paris 7 [email protected] LANGAGES DE PROGRAMMATION Pour exécuter un algorithme sur un ordinateur il faut le
Informatique Générale
Informatique Générale Guillaume Hutzler Laboratoire IBISC (Informatique Biologie Intégrative et Systèmes Complexes) [email protected] Cours Dokeos 625 http://www.ens.univ-evry.fr/modx/dokeos.html
Anis ASSÈS Mejdi BLAGHGI Mohamed Hédi ElHajjej Mohamed Salah Karouia
Ministère de l Enseignement Supérieur, de la Recherche Scientifique et de la Technologie Direction Générale des Etudes Technologiques Institut Supérieur des Etudes Technologiques de Djerba SUPPORT DE COURS
Programmation C++ (débutant)/instructions for, while et do...while
Programmation C++ (débutant)/instructions for, while et do...while 1 Programmation C++ (débutant)/instructions for, while et do...while Le cours du chapitre 4 : le for, while et do...while La notion de
Cours 1 : Introduction. Langages objets. but du module. contrôle des connaissances. Pourquoi Java? présentation du module. Présentation de Java
Langages objets Introduction M2 Pro CCI, Informatique Emmanuel Waller, LRI, Orsay présentation du module logistique 12 blocs de 4h + 1 bloc 2h = 50h 1h15 cours, 45mn exercices table, 2h TD machine page
Cours d initiation à la programmation en C++ Johann Cuenin
Cours d initiation à la programmation en C++ Johann Cuenin 11 octobre 2014 2 Table des matières 1 Introduction 5 2 Bases de la programmation en C++ 7 3 Les types composés 9 3.1 Les tableaux.............................
Introduction à MATLAB R
Introduction à MATLAB R Romain Tavenard 10 septembre 2009 MATLAB R est un environnement de calcul numérique propriétaire orienté vers le calcul matriciel. Il se compose d un langage de programmation, d
Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère
L'héritage et le polymorphisme en Java Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère En java, toutes les classes sont dérivée de la
Conventions d écriture et outils de mise au point
Logiciel de base Première année par alternance Responsable : Christophe Rippert [email protected] Introduction Conventions d écriture et outils de mise au point On va utiliser dans cette
Initiation à la programmation en Python
I-Conventions Initiation à la programmation en Python Nom : Prénom : Une commande Python sera écrite en caractère gras. Exemples : print 'Bonjour' max=input("nombre maximum autorisé :") Le résultat de
Chap III : Les tableaux
Chap III : Les tableaux Dans cette partie, on va étudier quelques structures de données de base tels que : Les tableaux (vecteur et matrice) Les chaînes de caractères LA STRUCTURE DE TABLEAU Introduction
Notes du cours 4M056 Programmation en C et C++ Vincent Lemaire et Damien Simon
Notes du cours 4M056 Programmation en C et C++ Vincent Lemaire et Damien Simon 13 janvier 2015 2 Table des matières Organisation générale du cours 7 1 Notions générales d algorithmique et de programmation
Utilisation d objets : String et ArrayList
Chapitre 6 Utilisation d objets : String et ArrayList Dans ce chapitre, nous allons aborder l utilisation d objets de deux classes prédéfinies de Java d usage très courant. La première, nous l utilisons
Représentation des Nombres
Chapitre 5 Représentation des Nombres 5. Representation des entiers 5.. Principe des représentations en base b Base L entier écrit 344 correspond a 3 mille + 4 cent + dix + 4. Plus généralement a n a n...
INF 321 : mémento de la syntaxe de Java
INF 321 : mémento de la syntaxe de Java Table des matières 1 La structure générale d un programme 3 2 Les composants élémentaires de Java 3 2.1 Les identificateurs.................................. 3 2.2
Les fichiers. Chapitre 4
Chapitre 4 Les fichiers Jusqu à maintenant tous les programmes que nous avons conçus travaillaient sur des données qui étaient perdues après chaque session de travail. On peut cependant, c est bien naturel,
Plan du cours. Historique du langage http://www.oracle.com/technetwork/java/index.html. Nouveautés de Java 7
Université Lumière Lyon 2 Faculté de Sciences Economiques et Gestion KHARKIV National University of Economic Introduction au Langage Java Master Informatique 1 ère année Julien Velcin http://mediamining.univ-lyon2.fr/velcin
Le Langage C Licence Professionnelle Qualité Logiciel Pr. Mouad BEN MAMOUN [email protected] Année universitaire 2011/2012
Le Langage C Licence Professionnelle Qualité Logiciel Pr. Mouad BEN MAMOUN [email protected] Année universitaire 2011/2012 2011/2012 Pr. Ben Mamoun 1 Plan du cours (1) 1. Introduction 2. Types, opérateurs
Le langage C. Séance n 4
Université Paris-Sud 11 Institut de Formation des Ingénieurs Remise à niveau INFORMATIQUE Année 2007-2008 Travaux pratiques d informatique Le langage C Séance n 4 But : Vous devez maîtriser à la fin de
Cours de C++ François Laroussinie. 2 novembre 2005. Dept. d Informatique, ENS de Cachan
Cours de C++ François Laroussinie Dept. d Informatique, ENS de Cachan 2 novembre 2005 Première partie I Introduction Introduction Introduction Algorithme et programmation Algorithme: méthode pour résoudre
Structure d un programme et Compilation Notions de classe et d objet Syntaxe
Cours1 Structure d un programme et Compilation Notions de classe et d objet Syntaxe POO 1 Programmation Orientée Objet Un ensemble d objet qui communiquent Pourquoi POO Conception abstraction sur les types
I. Introduction aux fonctions : les fonctions standards
Chapitre 3 : Les fonctions en C++ I. Introduction aux fonctions : les fonctions standards A. Notion de Fonction Imaginons que dans un programme, vous ayez besoin de calculer une racine carrée. Rappelons
MICROINFORMATIQUE NOTE D APPLICATION 1 (REV. 2011) ARITHMETIQUE EN ASSEMBLEUR ET EN C
Haute Ecole d Ingénierie et de Gestion Du Canton du Vaud MICROINFORMATIQUE NOTE D APPLICATION 1 (REV. 2011) ARITHMETIQUE EN ASSEMBLEUR ET EN C Programmation en mode simulation 1. DOCUMENTS DE RÉFÉRENCE...
Seance 2: En respectant la méthode de programmation par contrat, implémentez les autres fonctions de jeu.
Seance 2: Complétion du code de jeu. (durée max: 2h) Mot clé const et pointeurs: En respectant la méthode de programmation par contrat, implémentez les autres fonctions de jeu. Implémentez jeu_recupere_piece
Cours de programmation avancée. Le langage C. Université du Luxembourg 2005 2006
Université du Luxembourg 2005 2006 Cours de programmation avancée. Le langage C Sébastien Varrette Version : 0.4 Nicolas Bernard 2 Table des matières
V- Manipulations de nombres en binaire
1 V- Manipulations de nombres en binaire L ordinateur est constitué de milliards de transistors qui travaillent comme des interrupteurs électriques, soit ouverts soit fermés. Soit la ligne est activée,
Programmation C. Apprendre à développer des programmes simples dans le langage C
Programmation C Apprendre à développer des programmes simples dans le langage C Notes de cours sont disponibles sur http://astro.u-strasbg.fr/scyon/stusm (attention les majuscules sont importantes) Modalités
COMPARAISONDESLANGAGESC, C++, JAVA ET
REPUBLIQUE DU BENIN *******@******* MINISTERE DE L ENSEIGNEMENT SUPERIEUR ET DE LA RECHERCHE SCIENTIFIQUE(MESRS) *******@******* UNIVERSITE D ABOMEY CALAVI(UAC) *******@******* ECOLE POLYTECHNIQUE D ABPOMEY
Solutions du chapitre 4
Solutions du chapitre 4 Structures de contrôle: première partie 4.9 Identifiez et corrigez les erreurs (il peut y en avoir plus d une par segment de code) de chacune des proposition suivantes: a) if (
Logiciel de Base. I. Représentation des nombres
Logiciel de Base (A1-06/07) Léon Mugwaneza ESIL/Dépt. Informatique (bureau A118) [email protected] I. Représentation des nombres Codage et représentation de l'information Information externe formats
Programmation en langage C Eléments de syntaxe
Programmation en langage C Eléments de syntaxe Université Paul Sabatier IUP Systèmes Intelligents L2 Module Informatique de base 2 Initiation à la programmation en langage C Isabelle Ferrané SOMMAIRE I-
IV- Comment fonctionne un ordinateur?
1 IV- Comment fonctionne un ordinateur? L ordinateur est une alliance du hardware (le matériel) et du software (les logiciels). Jusqu à présent, nous avons surtout vu l aspect «matériel», avec les interactions
EXCEL TUTORIEL 2012/2013
EXCEL TUTORIEL 2012/2013 Excel est un tableur, c est-à-dire un logiciel de gestion de tableaux. Il permet de réaliser des calculs avec des valeurs numériques, mais aussi avec des dates et des textes. Ainsi
Cours d Informatique
Cours d Informatique 1ère année SM/SMI 2007/2008, Info 2 Département de Mathématiques et d Informatique, Université Mohammed V [email protected] [email protected] 2007/2008 Info2, 1ère année SM/SMI 1
Programmation en langage C d un µcontrôleur PIC à l aide du compilateur C-CCS Sommaire
Programmation en langage C d un µcontrôleur PIC à l aide du compilateur C-CCS CCS Sommaire Généralités sur le langage. 2 Structure d un programme en C.. 3 Les constantes et équivalences.. 4 Les variables...
EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE
EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE QCM Remarque : - A une question correspond au moins 1 réponse juste - Cocher la ou les bonnes réponses Barème : - Une bonne réponse = +1 - Pas de réponse = 0
Algorithmique, Structures de données et langage C
UNIVERSITE PAUL SABATIER TOULOUSE III Algorithmique, Structures de données et langage C L3 IUP AISEM/ICM Janvier 2005 J.M. ENJALBERT Chapitre 1 Rappels et compléments de C 1.1 Structures Une structure
Chapitre 2 Devine mon nombre!
Python 3 : objectif jeux Chapitre 2 Chapitre 2 Devine mon nombre! 2.1. Thèmes abordés dans ce chapitre commentaires modules externes, import variables boucle while condition : if... elif... else la fonction
Programmation impérative
Programmation impérative Cours 4 : Manipulation des fichiers en C Catalin Dima Organisation des fichiers Qqs caractéristiques des fichiers : Nom (+ extension). Chemin d accès absolu = suite des noms des
PROJET ALGORITHMIQUE ET PROGRAMMATION II
PROJET 1 ALGORITHMIQUE ET PROGRAMMATION II CONTENU DU RAPPORT A RENDRE : o Fiche signalétique du binôme o Listing des différents fichiers sources o CD sources o Il sera tenu compte de la présentation du
TP : Gestion d une image au format PGM
TP : Gestion d une image au format PGM Objectif : L objectif du sujet est de créer une classe de manipulation d images au format PGM (Portable GreyMap), et de programmer des opérations relativement simples
Conception de circuits numériques et architecture des ordinateurs
Conception de circuits numériques et architecture des ordinateurs Frédéric Pétrot Année universitaire 2014-2015 Structure du cours C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 Codage des nombres en base 2, logique
Langage et Concepts de ProgrammationOrientée-Objet 1 / 40
Déroulement du cours Introduction Concepts Java Remarques Langage et Concepts de Programmation Orientée-Objet Gauthier Picard École Nationale Supérieure des Mines de Saint-Étienne [email protected]
Support de Cours de Langage C. Christian Bac
Support de Cours de Langage C Christian Bac 15 février 2013 ii Table des matières Avant-propos xi 1 Historique et présentation 1 1.1 Historique........................................... 1 1.2 Présentation
L informatique en BCPST
L informatique en BCPST Présentation générale Sylvain Pelletier Septembre 2014 Sylvain Pelletier L informatique en BCPST Septembre 2014 1 / 20 Informatique, algorithmique, programmation Utiliser la rapidité
Analyse de sécurité de logiciels système par typage statique
Contexte Modélisation Expérimentation Conclusion Analyse de sécurité de logiciels système par typage statique Application au noyau Linux Étienne Millon UPMC/LIP6 Airbus Group Innovations Sous la direction
REPUBLIQUE ALGERIENNE DEMOCRATIQUE ET POPULAIRE UNIVERSITE M HAMED BOGARA DE BOUMERDES
REPUBLIQUE ALGERIENNE DEMOCRATIQUE ET POPULAIRE MINISTERE DE L ENSEIGNEMENT SUPERIEUR ET DE LA RECHERCHE SCIENTIFIQUE UNIVERSITE M HAMED BOGARA DE BOUMERDES FACULTE DES SCIENCES - DEPARTEMENT PHYSIQUE
ALGORITHMIQUE ET PROGRAMMATION En C
Objectifs ALGORITHMIQUE ET PROGRAMMATION Une façon de raisonner Automatiser la résolution de problèmes Maîtriser les concepts de l algorithmique Pas faire des spécialistes d un langage Pierre TELLIER 2
Manuel d utilisation 26 juin 2011. 1 Tâche à effectuer : écrire un algorithme 2
éducalgo Manuel d utilisation 26 juin 2011 Table des matières 1 Tâche à effectuer : écrire un algorithme 2 2 Comment écrire un algorithme? 3 2.1 Avec quoi écrit-on? Avec les boutons d écriture........
Algorithmique & Langage C IUT GEII S1. Notes de cours (première partie) cours_algo_lgc1.17.odp. Licence
Licence Algorithmique & Langage C Paternité - Pas d'utilisation Commerciale Partage des Conditions Initiales à l'identique 2.0 France Vous êtes libres : * de reproduire, distribuer et communiquer cette
