Home | News | Hacking | Sciences | Technology | Ti 92 | Programming | Free articles | Links | Webmaster |
Homepage / TS / Coding / C/C++ |
|- /Programming in C |
Nous allons
expliquer pourquoi et comment découper un programmes en modules. Ce découpage
ne s'illustre pas par un code car il repose d'avantage sur une
utilisation judicieuses du système d'exploitation et du compilateur.
Le but Tant qu'un programme répond
simplement à un objectif simple, il conserve une talle réduite. Mais dès
lors qu'on lui ajoute des fonctionnalités, son nombre de classes
commence à augmenter. Il devient alors impossible de gérer l'ensemble
des classes comme un tout. C'est comme manger un poulet en entier. Le
programmeur doit alors répartir son programme en modules. Il commencera
par l'aile à moins qu'il ne préfère la cuisse. Le fonctionnement Si le programmes doit
être découper en modules, il convient de choisir ceux-ci. Normalement
une phase de spécification préalable a permis de mettre en évidence
les grandes fonctionnalités. En général, elles vont former les différents
modules. Toutefois, ce n'est pas toujours si simple. Dans notre exemple,
les 2 fonctionnalités sont la lecture du fichier scénario et la
gestion de la partie. Cependant, ses fonctions sont effectuées par les
mêmes classes et ne peuvent pas être séparées. En revanche, la
prochaine fonction, qui va permettre de résoudre des actions plus
complexes à l'aide de formules conditionnelles, va utiliser des classes
de lecture et d'interprétation de ces formules. Elles vont probablement
former un modules indépendant du premier. Le schéma des classes
indique lorsque l'on peut ou non séparer facilement des classes, en
mettant en évidences les liens qui existent entre elles.
Dans cet exemple, le
programme se scinde en 2 modules. Le second contient le main du
programme et une panoplie de tes plus complète. De plus, chaque module
contient un makefile. Ce fichier doit permettre par un jeu d'options de
compiler un programme de test, ainsi que les fichiers objets de debug ou
de livraison. En outre, il faut qu'ils aient la capacité d'accéder au
fichier en-tête ou objet de l'autre module le cas échéant. |
Référence > C++ (ed. Micro Application)
Notre sujet
concerne 2 sujets de la STL : les strings et les streams. Les premiers
permettent de manipuler un ensemble de caractères comme une classe à
part entière, et non plus comme un tableau de "char". Les
seconds remplacent le bibliothèques d'entrées/sorties, en implémentant
le concept de flux.
Les Strings Les strings furent développés
pour effectuer une tâche courante à l'intérieur des programmes : le
traitement de texte. La solution adoptée par le C - un tableau de char
terminé par un 0 - ne s'avérait guère satisfaisante. Elle laisse à
la charge du programmeur les opérations de gestion de la mémoire et
rend complexe l'affectation, ainsi que la comparaison avec des chaînes
littérales. De plus, les chaînes de caractères en C se trouvaient liées
au jeu de caractères ASCII. Le C++, pour sa part, utilise un template
appelé basic_string. Celui-ci procure la capacité de concevoir des
classes reposant sur un jeu de caractères propre. La classe string désigne
une implémentation de ce template avec le jeu de symboles ASCII étendu. #include <string> Les Streams En C les entrées/sorties formatées se font via la famille de fonctions printf / scanf de la bibliothèque standard, qui prennent comme paramètre une spécification de format :#include <stdio.h> Un inconvénient de ces fonctions est que si on modifie le type d'une variable, il faut également modifier la spécification de format. Par ailleurs ce schema n'est pas extensible aux struct. Les iostream de C++ permettent d'effectuer des entrées/sorties formatées sans avoir à spécifier de format, grâce aux opérateurs d'insertion << et d'extraction >>. On peut insérer sur un ostream (écriture) et extraire d'un istream (lecture). C++ prédéfinit deux ostream, cout et cerr, qui correspondent respectivement à stdout et stderr de C, et un istream , cin, qui correspond à stdin. Ils s'utilisent comme suit : cout << "entrez un nombre : ";int i; cin >> i; cout << "vous avez saisi la valeur " << i << endl; Ce mécanisme est extensible aux struct : il suffit de définir les fonctions appropriées. Celles-ci ont pour nom operator << pour l'insertion, et operator >> pour l'extraction. Ce sont des fonctions libres. #include <iostream>#include <cmath> /* hypot */ struct Pt { int x, y; }; ostream & operator << (ostream & os, Pt const & p) { // opérateur d'insertion pour les Pt return cout << "[" << p.x << "," << p.y << "]"; } int main() Pt p; p.x = 3; p.y = 4; cout << "le point " << p << " est à la distance " << hypot(p.x, p.y) << " de l'origine" << endl; // génère la ligne suivante sur la sortie standard : // le point [2,3] est à la distance 5 de l'origine } Cette uniformité de traitement de tous les types vis à vis de l'insertion ou de l'extraction s'avère très pratique à l'usage. En particulier, elle permet l'écriture de macros comme celle ci : #define
SHOW(X) cout << #X << " = " << (X) <<
endl Par ailleurs, il existe différents types d'iostream : les fstream qui travaillent sur des fichiers, les strstream qui travaillent sur des chaînes de caractères, etc. Chacun peut se définir ses propres variantes d'iostream, soit en variant la source ou la destination des caractères (fichiers, chaînes de caractères, sockets...) soit en variant le comportement (par exemple impression préfixée par un numéro de ligne, ou lecture sautant les commentaires). Les opérateurs << et >> fonctionneront avec tous les types d'iostream. De plus ce principe
autorise une manipulation uniforme des données issues ou à destination
de l'entrée/sortie standard, d'un fichier ou d'une chaîne. Il connaît
toutefois des limites, car la concept d'entrée/sortie standard n'a pas
de sens avec un bureau graphique #include <iostream> |
Par François
Chenebit
Références > Bruce
Eckel ; Cetus
Un conteneur ne présente
pas, en lui-même, un grand intérêt. Il faut pouvoir lui appliquer des
opérations afin de pouvoir l'ordonner, d'accéder à ses éléments...
Les algorithmes interviennent alors : il permettent d'effectuer
simplement des opérations classiques, quel que soit son type ou la
classe qu'il contient. Leur but consiste à se transformer en nouvelle
fonctionnalités du C++, et de rendre leur emploi aussi naturel que
celui des fonctions d'entrées/sorties du C, comme printf. Cet article
ne présente pas un inventaire exhaustif des différents algorithmes
standards car il nous faudrait un livre entier pour faire le tour de la
question. Il se contente d'introduire les différents principes qui s'avèrent
nécessaires à la manipulation et de proposer une brève
classification.
Utilisation d'un algorithme standard Le principe d'un algorithme standard consiste à effectuer un traitement sur un conteneur. Celui-ci, afin de se montrer le plus paramétrable possible, est appelé avec une fonctionne paramètre. Par exemple, l'algorithme for_each applique à tous les éléments d'une séquence la fonction qu'il reçoit en argument. En effet, si il reçoit en paramètre une fonction qui imprime un entier, il accomplira cette tâche pour tout ceux d'un vecteur. Ainsi les algorithmes génériques se voient développés sans qu'il faille se préoccuper du type de données. Les objets fonctions Il existe 2 manières de passer des fonctions en paramètre d'un algorithme standard. Le première est héritée du C. Nous avons affaire à des pointeurs de fonctions. La seconde se montre plus conforme au paradigme objet : il s'agit donc des objets fonctions. Ceux-ci consIl existe 2 manières de passer des fonctions en paramètre d'un algorithme standard. Le première est héritée du C. Nous avons affaire à des pointeurs de fonctions. La seconde se montre plus conforme au paradigme objet : il s'agit donc des objets fonctions. Ceux-ci constituent des instanciations des classes pour lesquelles l'opérateur () se montre surchargé. class ImprimerCarre{ Un objet fonction, créé à partir de cette classe, permet d'afficher le carré d'un entier qu'il reçoit en paramètre. void main() Comme vous le voyez, les objets fonctions ressemblent aux routines callback, parfois mises à contribution dans le C. Ceux-ci se classent par leur nombre de paramètres et par le renvoi d'un booléen ou de tout autre type de données. De son côté, un générateur ne prend aucun paramètre et retourne une donnée du type spécifié. Les objets fonctions unitaire et binaire, lorsqu'il renvoient de surcroît un booléen. Par ailleurs, nous qualifions également les classes qui font office de paramètres des fonctions objets. Elles sont comparables, assignables ou ordonnables si elles possèdent respectivement un opérateur "égal", "affectation" ou "inférieur à". De plus, nous avons tout loisir de manipuler les objets fonctions et de les combiner entre eux. Il existe notamment un template binder qui autorise la transformation d'un objet fonction binaire en un objet fonction unitaire. Par exemple, l'objet fonction "plus" est binaire : il inclut ses 2 paramètres. Pour ajouter toujours le même nombre, on le binde, afin que son second paramètre ait toujours la même valeur. #include <vector> Enfin, comme nous l'avons évoqué en introduction, nous avons l'opportunité d'employer à la place d'un objet fonction un pointeur sur une fonction, comme en C. Rappelons à ce propos que le nom d'une fonction est considéré comme un pointeur de fonction. #include <vector> Récapitulatif des algorithmes standards A l'aide des objets
fonctions, vous vous trouvez en mesure d'utiliser les algorithmes
standards comme vous le souhaitez. Vous pouvez avoir recours à fill et
generate afin d'initialiser des séquences. count fera le compte des élément
égaux à une valeur donnée. Il faut donc que la classe contenue soit
comparable. De plus, count_if fonctionne d'une manière similaire, avec
un prédicat unitaire à la place d'une valeur. copy, pour sa part, procède
à la copie d'une séquence vers une cible, tandis que d'autres
algorithmes comme permutation ou swap_range favorisent des manipulations
plus complexes. Par ailleurs, find vous offre la capacité de rechercher
un élément égal à une valeur, et find_if, celui qu'un prédicat
unitaire vérifiera. min_element et max_element quant à eux cherchent
le plus petit et le plus grand élément d'une séquence. De surcroît,
equal vous donne le moyen de savoir si 2 séquences sont identiques et
mismatch indique le point à partir duquel elles diffèrent. Pour
supprimer des éléments, il faut faire confiance à remove, qui ordonne
une séquence en les plaçant à la fin et renvoie un itérateur sur le
premier d'entre eux. Grâce à celui-ci, nous pouvons utiliser erase :
celui-ci supprime les éléments entre 2 itérateurs. Signalons au
passage que vous avez le moyen de trier n'importe quelle séquence à
l'aide de la commande sort. Les listes, quant à elle, possède une méthode
sort certainement plus efficace. Une fois la séquence triée, nous nous
voyons à même de recourir aux algorithmes binary_search, lower_bound
et upper_bound pour trouver l'élément le plus petit et le plus grand
de la séquence. Il demeure aussi envisageable de copier 2 séquences
triées dans un 3ème, qui se verra également ordonnancée par merge.
Les opérations sur les ensembles sont également concevables sur les séquences
triées : vérifiez si une séquence constitue le sous-ensemble d'une
autre avec Include et créez l'union des 2 séquences avec set_union, la
différence avec set_difference, ainsi que le comportement avec
set_symetric. Comme nous l'avons vu, l'application d'un traitement à
tous les éléments d'une séquences grâce à for_each s'avère réalisable.
L'algorithme transform pour sa part effectue un travail similaire et
redirige en outre le résultat vers une sortie. |
depuis le 30/09/2003