Partage

[Exercices] Venez vous entraîner !

Ce mois: Parseur de fonctions mathématiques

15 décembre 2008 à 3:08:49

Bonsoir,

Petit problème : quand j'essaie d'afficher les zones "secret", je n'ai pas de texte qui s'affiche, il n'y a que quelques lignes vides.
Ça vient de chez moi ou bien... ?
Anonyme
15 décembre 2008 à 8:36:22

Peut-être que le code est trop large ?
15 décembre 2008 à 11:42:56

En effet. Merci.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 décembre 2008 à 12:55:52

Bonjour,

Il semblerait que le problème persiste dans la zone secrète du niveau 3.

Je profite de ce message pour remercier nanoc pour ses exercices. J'ai commencé seulement vendredi le Chiffre de César, mais je suis sur la bonne voie! (J'en ai profité pour faire une version avec Qt)
J'ai hâte de commencer ce nouvel exercice qui m'a l'air bien plus difficile (mais très formateur :) )

Très bonne journée à tous!
Mamas
15 décembre 2008 à 14:14:40

Bonjour,

Je compte participer à cette exercice, mais je ne suis pas certain du format de la réponse.

On doit envoyer le code source de chaque fichier dans un bloque <code></code>.

Mais doit-on préciser <code type="cpp"> ?
Doit-on mettre le nom du fichier avant le code ? Dans le code en commentaire ?

Tout commentaire doit-il se trouver dans le code source ou peut-on en faire dans le MP ?

Merci d'avance pour vos réponses ^^
Bonne chance à tous ;)

edit : autre question, le code soumis peut-il répondre aux 3 niveaux ou doit on faire un code pour chaque niveau ?
15 décembre 2008 à 18:52:21

Oui les balises de code sont nécessaire. Normalement, il y a pas besoinde plus de 1 ou 2 fichiers, donc indique juste le nom des fichiers avant le code.
Les commentaires vont évidemment dans le code.

La réponse que tu envoies doit pouvoir servir de correction pour les autres, à toi donc de voir si ton code vaut la peine.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
Anonyme
15 décembre 2008 à 19:38:19

Pour le nom, pourquoi ne pas utiliser l'option titre ?
<code titre="MonFichier.cpp" type="cpp">/* Tout plein */ </code>
;)
15 décembre 2008 à 22:40:37

salu je ecris la solution mais ya un probleme ca n'affiche strictement rien dois je poser mon code ici pour que vous m'aidiez
merci !
15 décembre 2008 à 22:59:23

C'est précisé dans le sujet au début.

Tu fois créer un nouveau sujet sur le forum.

PS : Pour le niveau 2, j'espère que la clef n'est pas ZZZZZ. C'est pas que ça me chagrine de faire tourner mon PC à fond pour décrypter le texte, mais quand même ^^
Il lui reste 1 047 000 possibilités à évaluer :-°
(1 043 000 maintenant :lol: )

J'ai bien une idée pour faire aller tout ça beaucoup plus vite mais j'aimerais bien savoir si ça marche avant d'optimiser :(
16 décembre 2008 à 0:50:37

@El_Bareto: Je t'invite à créer un nouveau sujet.

@Xahell: Bonne chance :) Mais je suis pas sûr que tout essayer soit la meilleure solution.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
16 décembre 2008 à 11:00:31

Je trouve le système d'exercice très intéressant le problème étant que je développe en VB plus qu'en C++

(Note pour plus tard : il faudra que je me remette au C++ pour jouer avec vous :p )

Je voulais juste faire une petite remarque sur cet exercice :

Il est très intéressant mais pour réussir à trouver l'algorithme pour décodé ton texte (du niveau 2) il faut quand même faire un minimum de recherche (sur internet) j'avoue que ne participant pas à cet exercice j'ai "tricher" et été voir sur internet.
Je trouve que c'est quand même pas mal coton (surtout pour le niveau 3)
(je ne parle pas du code mais juste de la façon de casser la clé)

je pense que tu aurais peut être du fournir des indices pour casser le chiffre de Vigenère parce que perso moi si j'avais voulu faire cet exercice je n'aurais pas été sur internet pour trouver l'algorithme pour le casser.

Et je pense que je me serais tirer une balle avant de trouver comment faire...
Si jamais un "Zéro" trouve la façon de casser le chiffre tout seul et ba chapeau ^^

Sinon je pense que j'essayerai le prochain exercice (le temps que je me remette au C++ )

A+
16 décembre 2008 à 13:17:39

Par ailleurs, je trouve pas d'algorythme probant pour le niveau 2 :-°

Pour l'instant je procède ainsi :

J'étale l'ensemble des clés sur 26^(longueur de la clef) identifiants.

Je commence par l'identifiant 0 (AAAAA dans notre cas).

Si la clef ne correspond pas, j'analyse sa pertinence en fonction du taux de 'E', 'A' et de 'I' en %.

Je stocke la clef et sa pertinence.

Je choisis la prochaine clef en fonction de la pertinence de la dernière clef choisit (ici AAAAA donc).

Plus le % de pertinence est bas, plus la prochaine clef sera éloigné de la précédente. Inversement, si la clef était proche de ce que l'on cherche, on ne se déplacera que de peu.

On vérifie toujours si la clef générée n'a pas déjà été utilisé. Si oui, on l'incrémente jusqu'à ce qu'elle soit nouvelle.

Le problème majeur de cette algotyhme étant qu'il ne cherche que dans un seul sens.
Si la clef DDDDD à 99% de pertinence, il n'ira jamais voir du côté DDDDC mais forcement du côté DDDDE (en incrémentant).


Malgré ce défaut, l'algotyhme a-t-il une chance d'être perfectionné et d'être utile ou il est totalement inutile ?
16 décembre 2008 à 13:55:37

Xahell > Si tu as fait le chiffre de César, j'imagine que tu n'as pas testé toute les possibilités avec l'analyse de fréquences. Pourquoi ne pas appliquer la même méthode pour le chiffre de vigenère ?

Sinon c'est algorithme et non pas algorythme... ;)
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
16 décembre 2008 à 14:35:58

Le texte niveau 2 de Nanoc serait-il signé Gorges Perec ? :-°
16 décembre 2008 à 15:31:33

@Xahell: Merci de mettre cela en secret pour le moment.

@Xahrov: Il est clair que trouver un moyen de casser le chiffre de Vigenère sans information. Cependant, la recherche sur internet (Wikipedia en particulier dans ce cas) donne d'excellentes informations. De plus, la recherche d'infos est une partie importante du travail de programmeur.
Avoir l'algorithme, ne veut pas forcément dire qu'on sait le programmer ni que la manière dont on le programme sera efficace. Si il faut un million d'année pour y arriver, c'est que c'est pas efficace.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
16 décembre 2008 à 15:58:24

je suis entièrement d'accord sur le fait que la recherche est un travail considérable du développeur, mais dans ce cas là je suis presque sûr (je n'ai pas fait le test mais je vais essayer pour voir) que si je cherche un peu sur internet je peux trouver le code source d'un programme qui fera déjà ça ( ce qui n'est pas non plus le but de l'exercice)

d'un autre coté c'est vrai aussi que je débarque un peu donc je n'ai pas fait ni lu l'exercice précédant et si comme le laisse supposé la réponse de iNaKoll l'algorithme pour casse se type de clé a été donné ça facilite le problème ^^

16 décembre 2008 à 16:01:15

Disons, que je suis pas le premier à proposer des exercices sur tout le web. Donc quelque soit l'exercice, il y aura toujours la possibilité de "pomper" le code sur le web. Mais quel intérêt ?
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
16 décembre 2008 à 16:13:39

Ba aucun c'est sûr ^^

non mais ce que je voulais te dire c'est que quand j'ai lu l'exercice j'ai réfléchit a comment le codé

Et j'ai passer au moi 3h à chercher avec un papier et un crayon comment cassé la clé du niveau 2

je pensais que ça faisait parti de l'exercice
18 décembre 2008 à 19:22:46

Solution du mois de novembre



Bonjour tout le monde ! Il est temps que je dévoile une solution pour l'exercice du mois de novembre. Vous avez été 15 à m'envoyer une solution.
Il y avait beaucoup de bonnes choses dans les codes reçus. Parmi ces réponses, je vous propose la solution de Chlab_lak.
Voici la description de son programme ainsi que son code commenté.

Définiton (cahier des charges)

Le but du programme est de permettre les choses suivantes:

- Chiffrement avec choix du décalage par l'utilisateur (le déchiffrement marche de la même manière)
- Résolution via une analyse fréquencielle avec choix des lettres de comparaison par l'utilisateur
- Chiffrement et résolution d'un fichier avec sauvegarde des résultats si demandé par l'utilisateur

Conception

Trouver les parties du programme

Si l'on imagine le programme fini et qu'on fasse les étapes dans notre tête on a:

1) Entrée des paramètres par l'utilisateur (donc parsage du programme)
2) Demande de donnée par le programme (décalage pour un chiffrement, lettres de comparaison pour une résolution)
3) Traitement
4) Affichage des résultats
5) Si le traitement s'est effectué sur un fichier, demande de sauvegarde
6) Fin

Il va falloir coder chaque mot souligné.

Parsage

Puisque l'utilisateur entre des données via la ligne de commande, il va nous falloir parser ces données (on part du principe que l'utilisateur peut rentrer n'importe quoi). Dans ces données il y a des paramètres, un pour chaque point de la définition, donc:

- Chiffrement : -a
- Résolution : -r
- Fichier : -f

Dans ces données il y a également la chaîne à traiter (si le paramètre -f est actif, la chaîne représentera le nom du fichier).

L'on arrive donc à un synopsis simple (il n'y a aucune faute d'orthographe):
caesar [-f] _string_ -a|-r

Si vous ne comprennez pas ce synopsis, je vous invite à lire le tuto Linux de M@theo21.

Demande

Le programme va devoir demander des données à l'utilisateur:

- Le décalage pour le chiffrement
- Les lettres de comparaison pour la résolution
- Une réponse oui/non pour la sauvegarde du chiffrement
- Les lettres de la résolution que l'utilisateur veut sauvegarder

L'utilisateur pouvant entrer n'importe quoi (s'il rentre une chaîne plutôt qu'un nombre => boucle infini), il nous faut sécuriser les entrées, cela ce fera via trois fonctions libres (les points 2 et 4 étant les mêmes).

Traitement

Cette partie est le coeur du programme, il s'agit du chiffrement par décalage et de la résolution par analyse fréquencielle.

Affichage

Il s'agit simplement d'afficher à l'utilisateur les résultats et ce que fait le programme.

main()

Le main() gère toutes les autres parties, c'est le chef d'orchestre.

Ordre de codage des parties

L'ordre de codage est important, on ne va pas demander à l'utilisateur des données pour le traitement alors que l'on n'a même pas codé le traitement (bien sûr c'est possible avec une conception plus solide et précise que celle que nous avons fait). Donc l'ordre est:

1) Traitement
2) Parsage
3) Demande et Affichage
4) main() et Affichage

Codage

Avant toute chose, je mets les headers avec ce que j'utilise:
#include <iostream> // std::cout, std::cin
#include <fstream> // std::ifstream, std::ofstream
#include <sstream> // std::ostringstream
#include <locale> // std::tolower, std::toupper, std::islower, std::isalpha
#include <algorithm> // std::rotate_copy, std::count_if, std::unique, std::remove_if
#include <functional> // std::bind2nd, std::binary_function, std::unary_function
#include <stdexcept> // std::logic_error, std::runtime_error
#include <limits> // std::numeric_limits
#include <cmath> // std::abs
#include <map> // std::map
#include <utility> // std::pair, std::make_pair
#include <string> // std::string

Je ne les récrirais pas.

Traitement

Premièrement codons la base de la classe:
class Caesar
{
    public:

        /* Typedef */
        typedef int gap_type;

        /* Constructor */
        Caesar(const std::string &String = "")
        {
            Assign(String);
        }

        /* Affector */
        Caesar &operator=(const std::string &String)
        {
            Assign(String);
            return *this;
        }

        /* Assignator */
        void Assign(const std::string &String)
        {
            myString = String;
        }

    private:

        /* Data member */
        std::string myString;
};


Ensuite il nous faut un chargement depuis un fichier:
/* From a file */
void LoadFromFile(const std::string &Name)
{
    std::ifstream Ifs(Name.c_str());
    if(!Ifs)
        throw std::runtime_error(
            "Impossible d'ouvrir le fichier en lecture");

    std::ostringstream Oss;
    Oss << Ifs.rdbuf();

    Assign(Oss.str());
}

Ce code est simple et se passe de commentaire.

maintenant passons au chiffrement, le prototype est:
std::string Apply(gap_type Gap) const;

Vous remarquez que la fonction est const et qu'elle renvoie une std::string du message chiffré plutôt que de modifier directement myString, c'est un choix d'implémentation qui nous servira plus tard.

Premièrement nous allons faire quelques verifications:
/* Check */
bool PGap = Gap > 0;
Gap = std::abs(Gap) % 26;
if (Gap == 0 or myString.empty())
    return myString;

En effet, ça ne sert à rien de chiffrer avec un décalage de 0 ou avec une chaîne vide.

Ensuite un typedef et une constante qui ne servent pas forcément mais je l'ai codé comme ça:
/* Constant */
typedef std::string::size_type size_type;
const size_type Size = myString.size();


La suite est plus intéressante, le but est d'avoir deux tableaux, un qui contient l'alphabet dans l'ordre, et l'autre qui contient l'alphabet décalé. pour faire ça nous allons utiliser std::rotate_copy (info: ici ou ici):
/* Tables */
const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
gap_type Mid = PGap ? Gap : 26 - Gap;
std::string Replace;
std::rotate_copy(Table.begin(), Table.begin() + Mid,
    Table.end(), std::back_inserter(Replace));


A partir d'ici, nous avons deux tableaux comme ça (pour un décalage de 1):
Table = ABCDEFGHIJKLMNOPQRSTUVWXYZ
Replace = BCDEFGHIJKLMNOPQRSTUVWXYZA
maintenant le chiffrement est très simple:
/* Cipher */
std::string R;
for (size_type i = 0; i < Size; ++i)
{
    const bool IsLower = std::islower(myString[i]);
    const size_type I = Table.find(std::toupper(myString[i]));
    if (I != std::string::npos)
        R += IsLower ? std::tolower(Replace[I])
             : Replace[I];
    else
        R += myString[i];
}

/* End */
return R;

Vous remarquez tout de même la gestion des minuscules.

Donc le chiffrement en entier:
/* Apply the caesar cipher */
std::string Apply(gap_type Gap) const
{
    /* Check */
    bool PGap = Gap > 0;
    Gap = std::abs(Gap) % 26;
    if (Gap == 0 or myString.empty())
        return myString;

    /* Constant */
    typedef std::string::size_type size_type;
    const size_type Size = myString.size();

    /* Tables */
    const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    gap_type Mid = PGap ? Gap : 26 - Gap;
    std::string Replace;
    std::rotate_copy(Table.begin(), Table.begin() + Mid,
        Table.end(), std::back_inserter(Replace));

    /* Cipher */
    std::string R;
    for (size_type i = 0; i < Size; ++i)
    {
        const bool IsLower = std::islower(myString[i]);
        const size_type I = Table.find(std::toupper(myString[i]));
        if (I != std::string::npos)
            R += IsLower ? std::tolower(Replace[I])
                 : Replace[I];
        else
            R += myString[i];
    }

    /* End */
    return R;
}


Maintenant passons à la résolution par analyse fréquencielle, dont le prototype est:
std::string Resolve(char C) const;

Le paramètre C servira à calculer le décalage avec la lettre la plus fréquente.

Ici le code est "très simple":
private:

    /* Predicat for count_if() */
    struct NoCaseMatch
    : std::binary_function<char, char, bool>
    {
        bool operator()(char CS, char C) const
        {
            return std::toupper(CS) == C;
        }
    };

public:

    /* Resolve (frequency analysis with C) */
    std::string Resolve(char C) const
    {
        /* Get F */
        /* This is not the best method because
           if two letter have the same frequency
           the second letter is unused, but that
           isn't a problem */
        std::pair<char, size_t> F; // Most frequency letter
        const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        for (size_t i = 0; i < Table.size(); ++i)
        {
            const char C = Table[i];
            size_t R = std::count_if(myString.begin(), myString.end(),
                std::bind2nd(NoCaseMatch(), C));

            if (R > F.second)
                F = std::make_pair(C, R);
        }

        /* Build resolved string */
        return Apply(C - F.first);
    }

Je vous mets les références vers les choses un peu plus complexes: std::binary_function, std::pair, std::count_if et std::bind2nd.
NB: L'on pourrait éviter de recalculer la lettre la plus fréquente en la mettant en attribut et en la calculant dans la fonction Assign(), je n'ai pas montré cette méthode car c'est plus facile pour la correction comme ça, de plus la modification n'est pas difficile et peut être faîte par vous.

Voici donc la classe Caesar, qui cloture la partie Traitement:
class Caesar
{
    public:

        /* Typedef */
        typedef int gap_type;

        /* Constructor */
        Caesar(const std::string &String = "")
        {
            Assign(String);
        }

        /* Affector */
        Caesar &operator=(const std::string &String)
        {
            Assign(String);
            return *this;
        }

        /* Assignator */
        void Assign(const std::string &String)
        {
            myString = String;
        }

        /* From a file */
        void LoadFromFile(const std::string &Name)
        {
            std::ifstream Ifs(Name.c_str());
            if(!Ifs)
                throw std::runtime_error(
                    "Impossible d'ouvrir le fichier en lecture");

            std::ostringstream Oss;
            Oss << Ifs.rdbuf();

            Assign(Oss.str());
        }

        /* Apply the caesar cipher */
        std::string Apply(gap_type Gap) const
        {
            /* Check */
            bool PGap = Gap > 0;
            Gap = std::abs(Gap) % 26;
            if(Gap == 0 or myString.empty())
                return myString;

            /* Constant */
            typedef std::string::size_type size_type;
            const size_type Size = myString.size();

            /* Tables */
            const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            gap_type Mid = PGap ? Gap : 26 - Gap;
            std::string Replace;
            std::rotate_copy(Table.begin(), Table.begin() + Mid,
                Table.end(), std::back_inserter(Replace));

            /* Cipher */
            std::string R;
            for(size_type i = 0; i < Size; ++i)
            {
                const bool IsLower = std::islower(myString[i]);
                const size_type I = Table.find(std::toupper(myString[i]));
                if(I != std::string::npos)
                    R += IsLower ? std::tolower(Replace[I])
                        : Replace[I];
                else
                    R += myString[i];
            }

            /* End */
            return R;
        }

    private:

        /* Predicat for count_if() */
        struct NoCaseMatch
        : std::binary_function<char, char, bool>
        {
            bool operator()(char CS, char C) const
            {
                return std::toupper(CS) == C;
            }
        };

    public:

        /* Resolve (frequency analysis with C) */
        std::string Resolve(char C) const
        {
            /* Get F */
            /* This is not the best method because
               if two letter have the same frequency
               the second letter is unused, but that
               isn't a problem */
            std::pair<char, size_t> F; // Most frequency letter
            const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            for(size_t i = 0; i < Table.size(); ++i)
            {
                const char C = Table[i];
                size_t R = std::count_if(myString.begin(), myString.end(),
                    std::bind2nd(NoCaseMatch(), C));

                if(R > F.second)
                    F = std::make_pair(C, R);
            }

            /* Build resolved string */
            return Apply(C - F.first);
        }

    private:

        /* Data member */
        std::string myString;
};


Parsage

Nous allons passer par une classe CLOption qui recupère les paramètres du main, les parse, si il y a une erreur, lance une exception et elle offre une interface pour accéder aux options de l'utilisateur (pour le main()).

Voici la classe:
/* Command line options */
class CLOption
{
    public:

        /* Constructor */
        CLOption(int argc, char *argv[])
        : myApply(false)
        , myIsAFile(false)
        , myManual(false)
        {
            /* No arg */
            if(argc <= 1)
            {
                myManual = true;
                return;
            }

            /* Else */
            bool ApplyOrResolveCall = false;
            std::string Buf;
            for(int i = 1; i < argc; ++i)
            {
                Buf = argv[i];

                /* Filename */
                if(Buf == "-f")
		{
		    /* Check */
		    if(myIsAFile)
			throw std::logic_error(
			    "Le parametre -f a ete trouve plusieurs fois");

                    myIsAFile = true;
		}

                /* Apply */
                else if(Buf == "-a")
                {
                    /* Check */
                    if(myApply or ApplyOrResolveCall)
                        throw std::logic_error(
                            "Les parametres -r et/ou -a ont ete trouves plusieurs fois");

                    myApply = true;
                    ApplyOrResolveCall = true;
                }

                /* Resolve */
                else if(Buf == "-r")
                {
                    /* Check */
                    if(ApplyOrResolveCall)
                        throw std::logic_error(
                            "Les parametres -r et/ou -a ont ete trouves plusieurs fois");

                    myApply = false;
                    ApplyOrResolveCall = true;
                }

                /* Append to myString */
                else
                    myString += Buf;
            }

            /* Check */
            if(myString.empty())
                throw std::logic_error("Le parametre chaine n'a pas ete trouve");
            if(!ApplyOrResolveCall)
                throw std::logic_error("Aucun parametre -a ou -r trouve");
        }

        /* Accessor */
        const std::string &String(void) const
        {
            return myString;
        }

        bool Apply(void) const
        {
            return myApply;
        }

        bool IsAFile(void) const
        {
            return myIsAFile;
        }

        bool ShowManual(void) const
        {
            return myManual;
        }

    private:

        /* Data member */
        std::string myString;
        bool myApply; // if myApply -> apply else resolve
        bool myIsAFile; // if myString is a filename
        bool myManual; // show the "man"
};

Il n'y a pas de difficulté majeure.

Demande et Affichage

Si vous ne savez pas comment faire une saisie sécurisée, je vous renvoie à la FAQ C++ de developpez.com. Vous remarquerez également que std::cout apparait dans ces fonctions, c'est la partie Affichage.

La fonction de oui/non:
/* 2 choice (yes/no) */
bool IsYes(const std::string &Q)
{
    int R = -1;
    while(R != 0 and R != 1)
    {
        std::cout << "> " << Q << " (1 = oui, 0 = non) ? " << std::flush;
        if(!(std::cin >> R))
        {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
    return R == 1;
}


La fonction pour obtenir le décalage:
/* Gap choice */
Caesar::gap_type GetGap(const std::string &Q)
{
    Caesar::gap_type R = 0;
    while(R == 0)
    {
        std::cout << "> " << Q << " (0 interdit) ? " << std::flush;
        if(!(std::cin >> R))
        {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            R = 0;
        }
    }
    return R;
}


Et la fonction pour les lettres:
/* Letters choice */
struct NoAlpha
: std::unary_function<char, bool>
{
    bool operator()(char C) const
    {
        return !std::isalpha(C);
    }
};

std::string GetLetters(const std::string &Q)
{
    std::cout << "> " << Q << " ? " << std::flush;

    std::string Buf;
    std::getline(std::cin, Buf);

    Buf.erase(std::remove_if(Buf.begin(), Buf.end(), NoAlpha()), Buf.end());
    for(size_t i = 0; i < Buf.size(); ++i)
        Buf[i] = std::toupper(Buf[i]);
    Buf.erase(std::unique(Buf.begin(), Buf.end()), Buf.end());

    return Buf;
}

Cette dernière fonction est un peu plus complexe, en fait, elle récupère une ligne (elle permet une saisie vide), elle supprime tous les caractères non-alphabetic, elle passe tout en majuscules et au final elle supprime les doublons. Pour les références => ici.

main() et Affichage

Maintenant, il ne faut plus que appeler les bonnes fonctions au bon endroit en affichant ce que se passe. Mais avant ça une dernière petite fonction:
/* Save in a file */
void SaveInFile(const std::string &Name, const std::string &String,
    const std::string &Extention)
{
    std::cout << "> Sauvegarde de \"" << (Name + Extention) << "\"... "
              << std::flush;

    std::ofstream Ofs((Name + Extention).c_str());
    if(!Ofs)
        throw std::runtime_error("Impossible d'ouvrir le fichier en ecriture");

    Ofs << String;

    std::cout << "OK" << std::endl;
}


Pour le main(), le code n'est que l'application de la première partie de la conception (entouré d'un bloc try-catch puisque toute nos erreurs sont gérées via des exceptions):
int main(int argc, char *argv[])
{
    try
    {
        std::cout << "> Chiffrement de Cesar" << std::endl
                  << "> --------------------\n" << std::endl;

        /* Options of the command line */
        CLOption Options(argc, argv);

        /* Manual */
        if(Options.ShowManual())
        {
            std::cout << "> Synopsis : caesar [-f] _string_ -a|-r\n\n"
                         "> _string_ : chaine de caractere, a entourer "
                                      "d'apostrophes si possible\n"
                         "> -f : Indique si _string_ est un nom de fichier\n"
                         "> -a : Appliquer un (de)chiffrement a _string_\n"
                         "> -r : Resoudre _string_ a l'aide d'une "
                                "analyse frequencielle"
                      << std::endl;

            return 0;
        }

        /* Caesar cipher */
        Caesar Cipher;
        if(Options.IsAFile())
        {
            std::cout << "> Chargement du fichier : \"" << Options.String()
                      << "\" ... " << std::flush;
            Cipher.LoadFromFile(Options.String());
        }
        else
        {
            std::cout << "> Chargement... " << std::flush;
            Cipher = Options.String();
        }
        std::cout << "OK" << std::endl;

        /* Action */
        if(Options.Apply())
        {
            /* Apply */
            Caesar::gap_type Gap = GetGap("Decalage");
            std::cout << "> Traitement (decalage de " << Gap
                      << ")... " << std::flush;
            std::string Applied = Cipher.Apply(Gap);
            std::cout << "OK" << std::endl;

            /* Show results */
            std::cout << "> Resultat apres decalage :\n" << Applied << std::endl;

            /* Save */
            if(Options.IsAFile())
            {
                if(IsYes("Voulez-vous sauvegarder le fichier"))
                {
                    std::ostringstream Oss;
                    Oss << "." << Gap << ".applied";
                    SaveInFile(Options.String(), Applied, Oss.str());
                }
            }
        }
        else
        {
            /* Resolve */
            typedef std::map<char, std::string> map_type;
            map_type Resolved;
            std::string Letters = GetLetters(
                "Quelles lettres utiliser pour l'analyse");
            std::cout << "> Traitement (analyse frequencielle)... " << std::flush;
            for(size_t i = 0; i < Letters.size(); ++i)
                Resolved[Letters[i]] = Cipher.Resolve(Letters[i]);
            std::cout << "OK" << std::endl;

            /* Show results */
            std::cout << "> Resultats apres resolution avec " << Letters
                      << " :" << std::endl;
            typedef map_type::const_iterator resolved_iterator;
            for(resolved_iterator It = Resolved.begin(); It != Resolved.end();
                ++It)
            {
                std::cout << "> " << It->first << " :\n" << It->second
                          << std::endl;
            }
            std::cout << "> Si aucune de ces solutions ne convient, "
                           "c'est peut-etre à cause du texte\n"
                         "> trop court ou à cause de l'utilisation "
                           "de mauvaises lettres, essayez\n"
                         "> avec d'autres lettres"
                      << std::endl;

            /* Save */
            if(Options.IsAFile())
            {
                std::string SaveLetters = GetLetters(
                    "Voulez-vous sauvegarder "
                    "(entrez les lettres qui vous interressent)");

                for(resolved_iterator It = Resolved.begin();
                    It != Resolved.end(); ++It)
                {
                    if(SaveLetters.find(It->first) != std::string::npos)
                    {
			std::ostringstream Oss;
                        Oss << "." << It->first << ".resolved";
                        SaveInFile(Options.String(), It->second, Oss.str());
                    }
                }
            }
        }
    }
    catch(const std::exception &E)
    {
        std::cout << "\n> Erreur.. " << E.what() << std::endl
                  << "> De...... " << typeid(E).name() << std::endl;

        return 1;
    }
    catch(...)
    {
        std::cout << "\n> Erreur.. Inconnue" << std::endl
                  << "> De...... " << std::endl;

        return 1;
    }

    return 0;
}


Conclusion

Avec une bonne conception, le codage est beaucoup plus facile. Le code en entier:
#include <iostream> // std::cout, std::cin
#include <fstream> // std::ifstream, std::ofstream
#include <sstream> // std::ostringstream
#include <locale> // std::tolower, std::toupper, std::islower, std::isalpha
#include <algorithm> // std::rotate_copy, std::count_if, std::unique, std::remove_if
#include <functional> // std::bind2nd, std::binary_function, std::unary_function
#include <stdexcept> // std::logic_error, std::runtime_error
#include <limits> // std::numeric_limits
#include <cmath> // std::abs
#include <map> // std::map
#include <utility> // std::pair, std::make_pair
#include <string> // std::string

class Caesar
{
    public:

        /* Typedef */
        typedef int gap_type;

        /* Constructor */
        Caesar(const std::string &String = "")
        {
            Assign(String);
        }

        /* Affector */
        Caesar &operator=(const std::string &String)
        {
            Assign(String);
            return *this;
        }

        /* Assignator */
        void Assign(const std::string &String)
        {
            myString = String;
        }

        /* From a file */
        void LoadFromFile(const std::string &Name)
        {
            std::ifstream Ifs(Name.c_str());
            if(!Ifs)
                throw std::runtime_error(
                    "Impossible d'ouvrir le fichier en lecture");

            std::ostringstream Oss;
            Oss << Ifs.rdbuf();

            Assign(Oss.str());
        }

        /* Apply the caesar cipher */
        std::string Apply(gap_type Gap) const
        {
            /* Check */
            bool PGap = Gap > 0;
            Gap = std::abs(Gap) % 26;
            if(Gap == 0 or myString.empty())
                return myString;

            /* Constant */
            typedef std::string::size_type size_type;
            const size_type Size = myString.size();

            /* Tables */
            const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            gap_type Mid = PGap ? Gap : 26 - Gap;
            std::string Replace;
            std::rotate_copy(Table.begin(), Table.begin() + Mid,
                Table.end(), std::back_inserter(Replace));

            /* Cipher */
            std::string R;
            for(size_type i = 0; i < Size; ++i)
            {
                const bool IsLower = std::islower(myString[i]);
                const size_type I = Table.find(std::toupper(myString[i]));
                if(I != std::string::npos)
                    R += IsLower ? std::tolower(Replace[I])
                        : Replace[I];
                else
                    R += myString[i];
            }

            /* End */
            return R;
        }

    private:

        /* Predicat for count_if() */
        struct NoCaseMatch
        : std::binary_function<char, char, bool>
        {
            bool operator()(char CS, char C) const
            {
                return std::toupper(CS) == C;
            }
        };

    public:

        /* Resolve (frequency analysis with C) */
        std::string Resolve(char C) const
        {
            /* Get F */
            /* This is not the best method because
               if two letter have the same frequency
               the second letter is unused, but that
               isn't a problem */
            std::pair<char, size_t> F; // Most frequency letter
            const std::string Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            for(size_t i = 0; i < Table.size(); ++i)
            {
                const char C = Table[i];
                size_t R = std::count_if(myString.begin(), myString.end(),
                    std::bind2nd(NoCaseMatch(), C));

                if(R > F.second)
                    F = std::make_pair(C, R);
            }

            /* Build resolved string */
            return Apply(C - F.first);
        }

    private:

        /* Data member */
        std::string myString;
};

/* Command line options */
class CLOption
{
    public:

        /* Constructor */
        CLOption(int argc, char *argv[])
        : myApply(false)
        , myIsAFile(false)
        , myManual(false)
        {
            /* No arg */
            if(argc <= 1)
            {
                myManual = true;
                return;
            }

            /* Else */
            bool ApplyOrResolveCall = false;
            std::string Buf;
            for(int i = 1; i < argc; ++i)
            {
                Buf = argv[i];

                /* Filename */
                if(Buf == "-f")
		{
		    /* Check */
		    if(myIsAFile)
			throw std::logic_error(
			    "Le parametre -f a ete trouve plusieurs fois");

                    myIsAFile = true;
		}

                /* Apply */
                else if(Buf == "-a")
                {
                    /* Check */
                    if(myApply or ApplyOrResolveCall)
                        throw std::logic_error(
                            "Les parametres -r et/ou -a ont ete trouves plusieurs fois");

                    myApply = true;
                    ApplyOrResolveCall = true;
                }

                /* Resolve */
                else if(Buf == "-r")
                {
                    /* Check */
                    if(ApplyOrResolveCall)
                        throw std::logic_error(
                            "Les parametres -r et/ou -a ont ete trouves plusieurs fois");

                    myApply = false;
                    ApplyOrResolveCall = true;
                }

                /* Append to myString */
                else
                    myString += Buf;
            }

            /* Check */
            if(myString.empty())
                throw std::logic_error("Le parametre chaine n'a pas ete trouve");
            if(!ApplyOrResolveCall)
                throw std::logic_error("Aucun parametre -a ou -r trouve");
        }

        /* Accessor */
        const std::string &String(void) const
        {
            return myString;
        }

        bool Apply(void) const
        {
            return myApply;
        }

        bool IsAFile(void) const
        {
            return myIsAFile;
        }

        bool ShowManual(void) const
        {
            return myManual;
        }

    private:

        /* Data member */
        std::string myString;
        bool myApply; // if myApply -> apply else resolve
        bool myIsAFile; // if myString is a filename
        bool myManual; // show the "man"
};

/* 2 choice (yes/no) */
bool IsYes(const std::string &Q)
{
    int R = -1;
    while(R != 0 and R != 1)
    {
        std::cout << "> " << Q << " (1 = oui, 0 = non) ? " << std::flush;
        if(!(std::cin >> R))
        {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
    return R == 1;
}

/* Gap choice */
Caesar::gap_type GetGap(const std::string &Q)
{
    Caesar::gap_type R = 0;
    while(R == 0)
    {
        std::cout << "> " << Q << " (0 interdit) ? " << std::flush;
        if(!(std::cin >> R))
        {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            R = 0;
        }
    }
    return R;
}

/* Letters choice */
struct NoAlpha
: std::unary_function<char, bool>
{
    bool operator()(char C) const
    {
        return !std::isalpha(C);
    }
};

std::string GetLetters(const std::string &Q)
{
    std::cout << "> " << Q << " ? " << std::flush;

    std::string Buf;
    std::getline(std::cin, Buf);

    Buf.erase(std::remove_if(Buf.begin(), Buf.end(), NoAlpha()), Buf.end());
    for(size_t i = 0; i < Buf.size(); ++i)
        Buf[i] = std::toupper(Buf[i]);
    Buf.erase(std::unique(Buf.begin(), Buf.end()), Buf.end());

    return Buf;
}

/* Save in a file */
void SaveInFile(const std::string &Name, const std::string &String,
    const std::string &Extention)
{
    std::cout << "> Sauvegarde de \"" << (Name + Extention) << "\"... "
              << std::flush;

    std::ofstream Ofs((Name + Extention).c_str());
    if(!Ofs)
        throw std::runtime_error("Impossible d'ouvrir le fichier en ecriture");

    Ofs << String;

    std::cout << "OK" << std::endl;
}

int main(int argc, char *argv[])
{
    try
    {
        std::cout << "> Chiffrement de Cesar" << std::endl
                  << "> --------------------\n" << std::endl;

        /* Options of the command line */
        CLOption Options(argc, argv);

        /* Manual */
        if(Options.ShowManual())
        {
            std::cout << "> Synopsis : caesar [-f] _string_ -a|-r\n\n"
                         "> _string_ : chaine de caractere, a entourer "
                                      "d'apostrophes si possible\n"
                         "> -f : Indique si _string_ est un nom de fichier\n"
                         "> -a : Appliquer un (de)chiffrement a _string_\n"
                         "> -r : Resoudre _string_ a l'aide d'une "
                                "analyse frequencielle"
                      << std::endl;

            return 0;
        }

        /* Caesar cipher */
        Caesar Cipher;
        if(Options.IsAFile())
        {
            std::cout << "> Chargement du fichier : \"" << Options.String()
                      << "\" ... " << std::flush;
            Cipher.LoadFromFile(Options.String());
        }
        else
        {
            std::cout << "> Chargement... " << std::flush;
            Cipher = Options.String();
        }
        std::cout << "OK" << std::endl;

        /* Action */
        if(Options.Apply())
        {
            /* Apply */
            Caesar::gap_type Gap = GetGap("Decalage");
            std::cout << "> Traitement (decalage de " << Gap
                      << ")... " << std::flush;
            std::string Applied = Cipher.Apply(Gap);
            std::cout << "OK" << std::endl;

            /* Show results */
            std::cout << "> Resultat apres decalage :\n" << Applied << std::endl;

            /* Save */
            if(Options.IsAFile())
            {
                if(IsYes("Voulez-vous sauvegarder le fichier"))
                {
                    std::ostringstream Oss;
                    Oss << "." << Gap << ".applied";
                    SaveInFile(Options.String(), Applied, Oss.str());
                }
            }
        }
        else
        {
            /* Resolve */
            typedef std::map<char, std::string> map_type;
            map_type Resolved;
            std::string Letters = GetLetters(
                "Quelles lettres utiliser pour l'analyse");
            std::cout << "> Traitement (analyse frequencielle)... " << std::flush;
            for(size_t i = 0; i < Letters.size(); ++i)
                Resolved[Letters[i]] = Cipher.Resolve(Letters[i]);
            std::cout << "OK" << std::endl;

            /* Show results */
            std::cout << "> Resultats apres resolution avec " << Letters
                      << " :" << std::endl;
            typedef map_type::const_iterator resolved_iterator;
            for(resolved_iterator It = Resolved.begin(); It != Resolved.end();
                ++It)
            {
                std::cout << "> " << It->first << " :\n" << It->second
                          << std::endl;
            }
            std::cout << "> Si aucune de ces solutions ne convient, "
                           "c'est peut-etre à cause du texte\n"
                         "> trop court ou à cause de l'utilisation "
                           "de mauvaises lettres, essayez\n"
                         "> avec d'autres lettres"
                      << std::endl;

            /* Save */
            if(Options.IsAFile())
            {
                std::string SaveLetters = GetLetters(
                    "Voulez-vous sauvegarder "
                    "(entrez les lettres qui vous interressent)");

                for(resolved_iterator It = Resolved.begin();
                    It != Resolved.end(); ++It)
                {
                    if(SaveLetters.find(It->first) != std::string::npos)
                    {
			std::ostringstream Oss;
                        Oss << "." << It->first << ".resolved";
                        SaveInFile(Options.String(), It->second, Oss.str());
                    }
                }
            }
        }
    }
    catch(const std::exception &E)
    {
        std::cout << "\n> Erreur.. " << E.what() << std::endl
                  << "> De...... " << typeid(E).name() << std::endl;

        return 1;
    }
    catch(...)
    {
        std::cout << "\n> Erreur.. Inconnue" << std::endl
                  << "> De...... " << std::endl;

        return 1;
    }

    return 0;
}
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
19 décembre 2008 à 21:17:40

Voici la correction du première exercice que je fais. Et je suis loin du compte :p
19 décembre 2008 à 22:33:35

Crypto2 niveau 3 terminé et fonctionne pas trop mal pour le moment. Je viens de le tester. Pour les petites clés (inférieur à 20), la résolution est instantanée, alors qu'avec une clé de 97 caractères sur un texte crypté de 3872 caractères de long il faut quand même compter pas loin de 10 secondes.
Amusant comme exercice ! J'aimerais bien pouvoir voir le code d'autres personnes en attendant la correction si c'est possible...



Pour le parsage de la ligne de commande, je me suis inspiré de la classe 'CLOption' de Chlab_lak qui a pris un peu de poids au passage (170 lignes.. :-° ).

Pour le traitement je n'ai pas été tenté par la création d'une classe, le code est bien découpé en fonctions (8 fonctions dont 2 inline), le tout en moins de 150 lignes de code. Il y a peut-être encore quelques copies inutiles qui se baladent encore par ci par là...

La fonction main fait simplement joujoue avec une instance de 'CLOption', appelle directement 2 fonctions de traitement (crypter/decrypter), affiche et/ou sauvegarde les résultats du traitement le tout pour 80 lignes de code.


Nanoc >
C'est toi qui a rédigé la dernière correction ? Si oui, chapeau bas ! C'est jamais facile de commenter aussi bien le code de quelqu'un d'autre !
Sinon afin que les codes répondent directement à la question posée en se passant du superflu. Est ce qu'il serait intéressant de se passer de classes du type 'CLOption' et de faire un fichier main par niveau ? Le tout en utilisant simplement les entrées/sorties de la console ?
Je pense particulièrement aux débutants qui pourraient se poser des questions inutiles sur les entrées/sorties en lisant entièrement les corrections... (c'est moi qui dit ca ? :p ).
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
19 décembre 2008 à 22:53:52

Citation : iNaKoll

Crypto2 niveau 3 terminé et fonctionne pas trop mal pour le moment.


Pas encore commencé, et je ne sais même pas si je le ferait, si je trouve la motivation.

Citation : iNaKoll

Pour le parsage de la ligne de commande, je me suis inspiré de la classe 'CLOption' de Chlab_lak qui a pris un peu de poids au passage (170 lignes.. :-° ).


Personnellement, dès que l'utilisateur doit entrer quelque chose dans la console, je préfère éviter tout risque, quitte (ortho?) à faire une classe de plus.

Citation : iNaKoll

Pour le traitement je n'ai pas été tenté par la création d'une classe, le code est bien découpé en fonctions (8 fonctions dont 2 inline)


Ca se discute, mais ce n'est pas le sujet, et ça dépend du contexte.

Citation : iNaKoll

La fonction main fait simplement joujoue avec une instance de 'CLOption'


C'est ça que j'aime.

Citation : iNaKoll

C'est toi qui a rédigé la dernière correction ? Si oui, chapeau bas ! C'est jamais facile de commenter aussi bien le code de quelqu'un d'autre !


Je lui ai envoyé toute la correction directement déjà faîte, tout simplement car ce n'est jamais facile de commenter le code de quelqu'un d'autre, comme tu l'a dit.

Citation : iNaKoll

Sinon afin que les codes répondent directement à la question posée en se passant du superflu. Est ce qu'il serait intéressant de se passer de classes du type 'CLOption' et de faire un fichier main par niveau ? Le tout en utilisant simplement les entrées/sorties de la console ?
Je pense particulièrement aux débutants qui pourraient se poser des questions inutiles sur les entrées/sorties en lisant entièrement les corrections... (c'est moi qui dit ca ? :p ).


Le mieux serait d'avoir l'avis des débutants, ou de n'importe quelle personne du forum.


Sinon c'est toujours un plaisir de voir son code comme correction.
20 décembre 2008 à 0:34:05

Citation : Chlab_lak

Citation : iNaKoll

Crypto2 niveau 3 terminé et fonctionne pas trop mal pour le moment.


Pas encore commencé, et je ne sais même pas si je le ferait, si je trouve la motivation.


Il reste encore un mois pour ronger l'os, il faut se motiver ! :p

Citation : Chlab_lak

Citation : iNaKoll

Pour le traitement je n'ai pas été tenté par la création d'une classe, le code est bien découpé en fonctions (8 fonctions dont 2 inline)


Ca se discute, mais ce n'est pas le sujet, et ça dépend du contexte.


Entièrement d'accord.. plus ca va et plus j'ai l'impression que le contexte s'y prête.

Citation : Chlab_lak


Le mieux serait d'avoir l'avis des débutants, ou de n'importe quelle personne du forum.


Oui! Malheureusement je trouve qu'il y a trop peu de commentaires sur les corrections.. :(

Ce sujet commence un peu à devenir une mine d'or au niveau des exercices pour différents niveaux! Dommage qu'il n'y ait pas de système de commentaires pour les corrections!
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
20 décembre 2008 à 3:48:15

@iNaKoll: Non, c'est Chlab_lak qui a rédigé le texte. Personellement, je n'aurais pas fait de classes dans ce cas là, car je ne ressens pas le besoin de lier cela dans un objet. Mais c'est un choix d'implémentation qui finalement ne change pas grand chose.
Un fichier main() par niveau est tout à fait possible et même plus simple, car les entrées/sorties ne sont pas les points importants de cet exercice.
De plus comme l'a dit Chalb_lak, une fois que la classe CLOption est écrite, on peut l'adapter facilement aux cas qui nous intéressent. C'est un peu un outil que tout programmeur devrait avoir quelque part sur son disque dur pour éviter de recopier inutiliement des mêmes lignes de code et d'oublier de gérer certains cas.
Quand tu dis qu'il y a trop peu de commentaires, est-ce que tu parles des commentaires de l'auteur ou des autres participants ? Les corrections qui sont données ne sont pas figées, si quelqu'un a quelque chose de mieux à proposer, je suis bien entendu ouvert à tout changement.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
20 décembre 2008 à 14:55:27

@Nanoc
voila, j'aurais juste voulue savoir pourquoi a t on choisi d'utiliser des classes dans le crypto 1, parce que moi j'ai tout simplement utiliser des fcts....
et faut il donc pour le 2eme aussi utiliser des classes ?
20 décembre 2008 à 15:47:28

Citation : Nanoc

Quand tu dis qu'il y a trop peu de commentaires, est-ce que tu parles des commentaires de l'auteur ou des autres participants ? Les corrections qui sont données ne sont pas figées, si quelqu'un a quelque chose de mieux à proposer, je suis bien entendu ouvert à tout changement.



Je parle des autres participants ou tout simplement des gens du forum qui passent par là. Les corrections sont, comme tu l'as dit, toujours perfectibles et il peut toujours y avoir de petites questions sur certains choix techniques d'anciennes corrections.
Je faisais simplement la remarque que le cadre de ces exercices (ce sujet) n'incite pas forcément les gens à déposer des commentaires sur des exercices passés. Le mieux ça serait d'avoir les exercices dans un Big-Tuto pour profiter du système de commentaires des chapitres... :)
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
21 décembre 2008 à 12:44:26

Citation : M41d3n-dc

@Nanoc
voila, j'aurais juste voulue savoir pourquoi a t on choisi d'utiliser des classes dans le crypto 1, parce que moi j'ai tout simplement utiliser des fcts....
et faut il donc pour le 2eme aussi utiliser des classes ?



C'est le choix de l'auteur de la réponse. Personellement, je n'aurais pas non plus fait de classe car comme je l'ai dit, je n'en sens pas le besoin ici. Et non, on est pas obligé d'en faire pour les autres exercices.

Citation : iNaKoll

Je faisais simplement la remarque que le cadre de ces exercices (ce sujet) n'incite pas forcément les gens à déposer des commentaires sur des exercices passés. Le mieux ça serait d'avoir les exercices dans un Big-Tuto pour profiter du système de commentaires des chapitres... :)



C'est ce que j'avais pensé faire au début. Cela avait été refusé. Et le forum a aussi l'avantage d'être plus visible.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
21 décembre 2008 à 12:50:20

Et puis si on a des questions, faisont un pm a nanoc :D (aaaaaah non ne me tappe pas).

En general, les réponses sont plutot bien expliquées et comme on a fait l'exercice, il est d'autant plus facile de lire la correction (d'ou l'interet de ne pas harceler nanoc en pm ...).

J'en profite pour poser une question : si on utilise un interface qt, ca vaut la peine d'envoyer sa réponse ? Je ne sais pas si ca répond vraiment a l'exercice.
21 décembre 2008 à 12:54:47

Disons que je n'y connais rien en Qt et que ça ne peut pas vraiment servir de réponses puisque il y a des gens qui n'utilisent pas Qt.

Si il y a des questions, mieux vaut les poser dans le thread puisque cela peut profiter à tout le monde et surtout parce que je n'ai pas réponse à tout.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.