Najib Tounsi
Ecole Mohammadia d'Ingénieurs,
Rabat 
Année 2017/2018
1ère année Génie Informatique
Modularité
Programmation par morceaux appelés modules.
Module qui implémente une fonctionnalité (variables et codes sources) bien définie et dont la description est faite à travers une interface précise de sorte qu'on n'a pas besoin de connaître son code pour utiliser un module.
Reste à savoir décomposer : comment?
Abstraction
L'abstraction tente de réduire et factoriser les détails afin que le programmeur puisse se concentrer sur les concepts importants.
L'abstraction de données, est l'idée importante derrière la notion de type. (ℝ est l'ensemble des réels et ℕ l'ensemble des entiers ... propriétés et manipulations différentes).
Cela permet la programmation sans avoir à connaître les détails (données, codes) de représentation des objets manipulés, détails qui sont définis séparément.
L'important c'est de savoir quoi faire avec un objet.
Cette séparation entre utilisation et représentation permet
Soit le programme qui lit une chaîne de caractère et l'imprime
en ordre inverse (image miroir)
(Exemple1)
#include <stdio.h>
#define MAX 80
main(){
int curChar=0;
char c, t[MAX];
/* On lit et mémorise des caractères */
while ((c=getchar())!='\n') {
t[curChar++] = c;
if(curChar==MAX) break;
}
/* On les imprime en ordre inverse */
while (curChar>0)
putchar( t[--curChar] );
}
A
noter: On a choisi un tableau, on manipule directement
le tableau. (Autre méthode)
Programme développé par raffinements successifs (on définit les grandes lignes et les dégrossit au fur et à mesure).
Soit maintenant à réutiliser/adapter programme pour tester si la chaîne est un palindrome (le problème est proche).
(Exemple2)
#include <stdio.h>
#define MAX 80
char t[MAX], s[MAX];
main(){
int curChar=0, i=0;
char c;
/* On lit et mémorise des caractères */
while ((c=getchar())!='\n') {
t[curChar++]=c;
if(curChar==MAX) break;
}
/* On duplique la chaîne en ordre inverse */
i = 0;
while (curChar>0)
s[i++] = t[--curChar];
/* et on l'imprime pour voir (ou on teste l'identité des deux) */
printf("%s %s\n", t, s);
}
A noter: Même remarque. On a utilisé un deuxième tableau pour comparer.
On pourrait tester sur le même tableau... Cela reste un programme dépendant de la notation tableau!
Soit maintenant un programme qui lit une "expression arithmétique" et vérifie si elle est bien parenthésée (parenthèses en correspondances, car images miroirs)
Réutiliser/modifier le même programme devient plus difficile. Mais le problème est proche. (Exemple3)
#include <stdio.h> #define MAX 80 char t[MAX]; main(){ int curChar=0; int nbO=0; // nombre parenthèses Ouvrantes int nbF=0; // nombre parenthèses Fermantes char c; int i; /* On lit et mémorise uniquement les parenthèses */ while ((c=getchar())!='\n') { if (c=='(' || c==')' ) t[curChar++] = c; else; if(curChar==MAX) break; } /* On parcourt le tableau est compte les parenthèses */ for (i=0; i<curChar; i++){ if (t[i] == '(') nbO++; else { nbF++;
if (nbF >nbO) break; } } if (i<curChar || nbO != nbF) printf ("Mal Parenthésée :-("); else printf ("bien Parenthésée :-)"); }
A
noter: Cela reste un programme dépendant de sa structure
de donnée: on est contraint de penser et coder en terme de
tableau.
Problème: Vérifier si une expression générale est bien parenthésée.
avec { ( [ < > ] ) }
Reprendre le programme précédent et l'adapter se complique. Il faut chercher une autre idée.
L'idée c'est de mémoriser dans leur
ordre d'arrivée les symboles ouvrants déjà rencontrés,
et de vérifier si chaque symbole fermant correspond bien au
dernier
symbole ouvrant rencontré. C'est la notion de
pile.
Considérer les données sous la forme d'une pile, et raisonner avec la pile.
A la place d'un tableau t [MAX] des programmes
précédents, on va déclarer une abstraction PILE
matérialisée avec un objet p.
#include <stdlib.h>
#include "pile.h" // Voir plus loin
#define MAX 20
main(){
int curChar=0;
char c; int b=0;
PILE p; // a la place t[MAX]
p = create ();
/* On lit et empile (resp. depile) les parenthèses
* ouvrantes (resp. fermantes)
*/
while ((c=getchar())!='\n') {
if (c=='(') empiler (p, c);
else if (c==')')
if(estVide(p)) b=1;
else if (sommet(p) != '(') b=1;
else depiler (p);
else;
if(curChar==MAX) break;
}
/* On teste si la pile est vide et b=0. Cas favorable. */
if (estVide(p) && b==0)
printf ("bien Parenthésée :-)");
else printf ("Mal Parenthésée :-(");
}
A noter:
La définition de la Pile (en C)
/*
Type Abstrait PILE de caractères
CopyLeft: :-)
Author: Najib Tounsi
Date: 06/10/04 00:19
Description: TAD pile en langage C
*/
/*
* Structure de données d'une pile
*/
#define MAX 20
typedef struct {
char t[MAX]; /* tableau des éléments empilés */
int top; /* indice du sommet de la pile */
} * PILE;
/*
* Interface abstraite : Liste des opérations que l'on fait sur une pile
(déclarations des profiles)
*/
PILE create();
/* crée et initialise une pile vide */
void empile(PILE, char);
/* empile le caractère donné */
char sommet(PILE);
/* retourne le caractère au sommet de la pile */
void depiler(PILE);
/* décapite la pile (retire le sommet ) */
int estVide(PILE);
/* teste si la pile est vide */
int estPleine(PILE);
/* teste si la pile est pleine */
/*
* Représentation (Implementation)
* On choisit une structure de données
* et on programme les opérations
*/
/* cf. structure de données plus haut */
/* Programmation des opérations */
PILE create(){
/* On crée une pile et on retourne son adresse */
PILE p = (PILE) malloc(MAX);
p->top = -1;
return p;
}
void empiler(PILE p, char c){
if (!estPleine(p))
p->t[++p->top] = c;
else
printf("Pile Pleine : %c\n", p->t[p->top]);
}
char sommet(PILE p){
return p->t[p->top];
}
void depiler(PILE p){
if (!estVide(p))
p->top--;
else
printf("Pile vide\n");
}
int estVide(PILE p){
return (p->top < 0);
}
int estPleine(PILE p){
return (p->top >= (MAX - 1));
}
On a deux parties
Tout ce qui dépend du "tableau" est alors confiné dans les définitions de fonctions. Donc localisé.
Abstraction
Modularité
On appelle type abstrait de données (TAD) un ensemble d'objets caractérisés par les opérations qui leur sont applicables. Ang. Abstract Data Type (ADT)
Ce qui importe, c'est le concept véhiculé par un objet (et non la structure de données sous-jacente)
/* C'est plus stable */
N x N →
NProfile des opérations (ou entêtes)
Type ℙ =
vide : → ℙ
/* Crée une pile vide et la retourne en résultat.*/
sommet : ℙ → ℕ
emPiler : ℙ x ℕ → ℙ
dépiler : ℙ → ℙ
pileVide: ℙ → 𝔹
pilePleine: ℙ → 𝔹
Fin
Spécification du comportement (Fait partie de la description d'un TAD)
Dépiler (Vide ()) = ERREUR
Dépiler (Empiler (p, n)) = p
Sommet (Vide ()) = ERREUR
Sommet (Empiler (p, n)) = n
PileVide (Vide()) = VRAI
PileVide (Empiler (p, n)) = FAUX
Les axiomes traduisent les
invariants qui caractérisent les propriétés d’un objet.
Opérations permettant d'obtenir (construire) tous objets potentiels d'un certain type.
Les axiomes spécifient l'effet de certaines opérations
(dites sélecteurs ou fonctions d’accès) sur d'autres (dites
constructeurs)
Constructeurs pour le TAD pile:
Exemples:
Objet Date (constituée de 3 entiers)
Type Date =
Opérations
Date : 𝐍 X 𝐍 X 𝐍 → Date
Jour : Date → 𝐍
Mois : Date → 𝐍
Année : Date → 𝐍
Demain : Date → Date
Axiomes
Jour ( Date ( j, m, a)) = j;
Mois ( Date ( j, m, a)) = m;
Année ( Date ( j, m, a)) = a;
Demain ( Date ( j, m, a)) = si j< 28 alors Date(j+1, m, a)
sinon /* faire le plus dur ...*/
Date(1, m+1, a)
Fin
Remarque
: Pour plus d’abstraction, introduire les TADs Jour/Mois/Année au lieu des trois
entiers.
Exercices:
Type Collection C =
Opérations
Vide : → C
Ajouter : C X 𝐍 → C
Appartient : C X 𝐍 → B Supprimer : C X 𝐍 → C EstVide : C → B
Axiomes
/* Ensemble sans double */
Collection c; x, y entiers */
Appartient ( Vide(), x) = Faux
Appartient ( Ajouter (c, y), x) = si (x=y) alors Vrai
sinon Appartient( c, x)
Supprimer ( Vide(), x) = Vide() Supprimer ( Ajouter (c, y), x) = si (x=y) alors Supprimer (c, x)
sinon Ajouter ( Supprimer (c, x), y)
EstVide ( Vide()) = Vrai
EstVide ( Ajouter (c, y)) = Faux
Fin
Exercice: Collection avec doubles?
Type Pile:
Représentation
tableau t [MAX]; /* Tableau pour les éléments de la pile*/
entier top; /* Indice du sommet de la pile +/
opérations
Vide (p) { p.top = -1; }
Empiler (p, n) { p.top = p.top+1;
p.t[p.top] = n;
}
Depiler(p) { p.top = p.top-1; } Sommet(p) { return p.t[p.top]; } PilePleine(p) { return p.top = (MAX-1); } PileVide(p) { return p.top < 0; }
fin
Remarque:
Prise en compte des cas d'exceptions dans les algorithmes
(spécification. sans Pré/post):
On teste les cas d'erreurs à l'intérieur des opérations
Empiler (p, n) {
if top<(MAX)
p.top = p.top+1;
p.t[p.top] = n;
else
erreur("pile pleine");
endif
}
où erreur() est une fonction donnée de traitement d'erreurs.
(Mais voir plus tard les exceptions)
Exercices:
Au lieu de programmer avec t [ expression ], top++, ... on fait empiler (p, c), sommet (p) etc.
On ne connaît pas comment est fait l'objet p. On a uniquement besoin de savoir avec quoi le manipuler (quelles opérations?). On appelle cela Interface de l'objet
pile.h) différemment ou
améliorer les algorithmes, on n'a pas besoin de refaire
les programmes qui l'utilisent. Tant qu'on n'a pas touché
à l'entête des fonctions, i.e. l'interface de la
pile).pile.h) les
variables représentant un objet pile et les instructions qui
les utilisent dans les opérations sur l'objet.