La programmation en C++ moderne

Apprenez la programmation de zéro jusqu'à l'infini !

a marqué ce sujet comme résolu.

Salut,
Tu devrais effectivement créer ton propre sujet pour chaque problème que tu rencontres ou questions que tu te poses, afin de laisser celui-ci libre pour les retours sur le cours. Si dans ton sujet les questions ou problèmes sont manifestement liés à un défaut du cours, il sera alors pertinent de venir le signaler ici.

Pour les pratiques essaie de découpler les fonctionnalités de ton programme, ici tu as la saisie utilisateur + le calcul de la moyenne + le retour à l’utilisateur, de la manière dont tu t’y es pris le total est calculé pendant la saisie, et donc le jour où tu veux faire évolué les entrées de ce programme ça va affecter ton calcul de moyenne. Déclare tes variables au plus proche de leurs utilisations (et limite donc leurs visibilités au minimal)

Ta solution est "fausse", car le tableau notes ne contient pas les notes entrés par l’utilisateur, tu y as ajouté un zéro au début (avant la première saisie utilisateur) et retire la dernière valeur saisie par l’utilisateur (pour que size te donne la valeur que tu cherches je suppose). ça fonctionne car un 0 n’influe pas sur le total, que tu calcul d’ailleurs au fil des saisie et pas au contenu du tableau notes. Le tableau ne te sert alors que pour sa taille et pourrait-être remplacé par un compteur du nombre de saisie incrémenté dans la boucle. Mais le but de l’exercice est de manipuler les tableaux, le calcul de moyenne doit être une fonction qui a pour entrée le tableau de note, la saisie utilisateur n’est la que pour pouvoir la tester, tu pourrais aussi bien travailler sur un tableau initialisé avec des valeurs en dur dans le code.

+3 -0

Au passage, @informaticienzero et moi-même venons de recevoir des contrats d’édition pour ce livre, que nous allons signer incessamment sous peu : ce livre va exister en version papier, et c’est en partie grâce à vous, chers relecteurs ! C’est un vrai honneur pour nous, et je trouve que c’est une bonne occasion de vous remercier de votre précieuse aide, donc merci beaucoup, vraiment.

J’en profite pour préciser que cela ne change en rien le fait que le cours sera disponible gratuitement et librement sur Zeste de Savoir, notre contrat contient une clause spécialement dédiée à l’autorisation de publication sur ZdS ! :)

mehdidou99

N’hésite pas à dire quand il sortira :-)

+2 -0

Salut,
Tu devrais effectivement créer ton propre sujet pour chaque problème que tu rencontres ou questions que tu te poses, afin de laisser celui-ci libre pour les retours sur le cours. Si dans ton sujet les questions ou problèmes sont manifestement liés à un défaut du cours, il sera alors pertinent de venir le signaler ici.

Pour les pratiques essaie de découpler les fonctionnalités de ton programme, ici tu as la saisie utilisateur + le calcul de la moyenne + le retour à l’utilisateur, de la manière dont tu t’y es pris le total est calculé pendant la saisie, et donc le jour où tu veux faire évolué les entrées de ce programme ça va affecter ton calcul de moyenne. Déclare tes variables au plus proche de leurs utilisations (et limite donc leurs visibilités au minimal)

Ta solution est "fausse", car le tableau notes ne contient pas les notes entrés par l’utilisateur, tu y as ajouté un zéro au début (avant la première saisie utilisateur) et retire la dernière valeur saisie par l’utilisateur (pour que size te donne la valeur que tu cherches je suppose). ça fonctionne car un 0 n’influe pas sur le total, que tu calcul d’ailleurs au fil des saisie et pas au contenu du tableau notes. Le tableau ne te sert alors que pour sa taille et pourrait-être remplacé par un compteur du nombre de saisie incrémenté dans la boucle. Mais le but de l’exercice est de manipuler les tableaux, le calcul de moyenne doit être une fonction qui a pour entrée le tableau de note, la saisie utilisateur n’est la que pour pouvoir la tester, tu pourrais aussi bien travailler sur un tableau initialisé avec des valeurs en dur dans le code.

romantik

je vois, merci de tes précisions et de tes explications, je vais bien revoir ce chapitre, lors des exo suivant, je me suis plutôt bien débrouiller, mais c’est vrai que j’emploie pas toujours la bonne logique. Et a l’avenir je créerai un sujet uniquement pour mon cas, histoire de pas polluer ici ^^ ;)

La matrice

a- Plutôt que lignes(), nb_lignes() serait plus propre. Ne pourrait-on pas imaginer après tout un for (auto lig_idx : m.lignes())

b- index ne prend pas de 's’ au pluriel

c- Si le truc interne n’était pas un vecteur, il serait intéressant de disposer de 2 constructeurs: qui initialise à une valeur par défaut ou pas — avec constructeurs qui s’appellent.

d- À propos de vecteur, affectation et copie devraient être gardées sous silence, ou defaulted.

e- Mais cela change pour les 2 opérations de déplacement: besoin de maintenir les invariants (taille allouée = C x L)

f- Et cela change si en interne on utilise un std::unique_ptr<double[]>

g- En général, pour des raisons de DRY, et de SRP, je préfère avoir une fonction offset

h- Malgré l'assert() je pousse le vice au noexcept. Mais je ne sais plus si cela a déjà été abordé

i- Je ne me souvenais pas que le cours avait fait le choix de endl en place de '\n'.


Entités

1- Vous pouvez constater que la sémantique d’entité est l’opposée de la sémantique de valeur, où deux instances identiques représentent le même objet.

Deux termes me gênent: opposé et représente le même objet.

Car, il y a d’autres visions, parfois hybrides (exceptions), parfois sans rapport (handles)…

Et ensuite, chez les valeurs, on ne représente pas le même objet, mais la même valeur. Et dans ce cas seule la valeur compte. Alors que les entités vivent leur vie (quantité d’encre qui change), et ce sont elles qui comptent. C’est une vision, taxinomie, relativement artificielle qui permet de simplifier la vie.

2- Pour leur déplacement, je pense aujourd’hui que déplacer par affectation n’a que peu de sens chez les entités. Et un problème technique revient si elles sont impliquées dans une hiérarchie: le slicing est de retour.

3- C’est cool de voir en code mon analogie/exemple de l’appareil de nettoyage!

4- Il manque un destructeur virtuel. Et du coup, une instanciation, ou alors indiquer que l’on n’a pas fini, mais que l’on va y revenir.

5- redefine est bizarre. C’est sûr c’est le terme officiel en VO pour l'overload masquant? Car en VF, c’est la traduction quasi-officielle de override. NB: quand j’explique, j’aime employer supplanter pour décrire le mécanisme à l’oeuvre. Et un certain auteur emploie surdéfinir, alors que le reste de la communauté et des développeurs traduit override par redéfinir. Et d’autres emploient à tord surcharger.

6- J’aurai presque envie de mettre une parenthèse pour ceux qui se demandent quand il faut hériter (probablement pour un autre chapitre, cf les histoires de pédagogie): "Remarque, ce qui est factorisé, ce ne sont pas les données, mais les comportements"

Pour trace et inspiration, petite prose sur le discord au sujet des destructeurs virtuels.

dans quel doit-on utiliser des destructeurs virtuels?

Pour les destructions polymorphiques

Mere * p = new Fille();
delete p;

KA-BOOM sans destructeur virtuel car ce serait alors le destructeur la mère seulement qui serait appelé et non celui de la fille en premier lieu avant de remonter vers la mère.

Même cas de figure en situation moderne avec RAII

{
    std::unique_ptr<Mere> p = std::make_unique<Fille>();
}

ca veut dire que dans le cas où Mere a un destructeur virtuel et qu’on fait

delete p

la Fille sera détruite avant la Mere ?

En situation normale, le C++ est bien foutu et s’occupe de préserver les invariants. Un invariant primordial est que dans le corps de la construction ou de la destruction d’un objet, toute variable membre est manipulable.

Sans ça on ne pourrait pas faire

struct C {
    C() { do_something_with(s); }
    ~C() { do_some_other_thing_with(s); }
    std::string s;
};

Il est impératif que s existe et soit correct.

Cette propriété s’étend à la composition et à l’héritage. Ce qui signifie qu’avant de passer dans le corps d’un constructeur d’une classe fille, on passe dans les constructeurs des classes parentes, puis des variables membres de la fille. A l’inverse dans la destruction, on passe d’abord dans le corps du destructeur de la fille, puis on détruit ses variables membres (en ordre inverse), puis on appelle les destructeurs parents (en ordre inverse toujours)

Bref. Tout ça est ce qui va se passer dans le cas

{
    Fille f;
}

Et c’est ce que l’on doit retrouver lorsque l’on détruit des objets des façon polymorphique. Le choix syntaxique pris pour exiger/permettre cela consiste à annoter les destructeurs de plus haut niveau du mot clé virtuel. Cela enclenche les mécanismes de résolution dynamique d’appel.

Merci pour ce retour !

La matrice

Je laisse ça de côté, je laisse @informaticienzero s’occuper de ça (ou alors je le ferai mais plus tard).

Entités

1- Vous pouvez constater que la sémantique d’entité est l’opposée de la sémantique de valeur, où deux instances identiques représentent le même objet.

Deux termes me gênent: opposé et représente le même objet.

Car, il y a d’autres visions, parfois hybrides (exceptions), parfois sans rapport (handles)…

Effectivement, c’est maladroit. Globalement, je vais essayer de reformuler un peu le début qui présente plusieurs petites maladresses du genre.

Et ensuite, chez les valeurs, on ne représente pas le même objet, mais la même valeur. Et dans ce cas seule la valeur compte. Alors que les entités vivent leur vie (quantité d’encre qui change), et ce sont elles qui comptent. C’est une vision, taxinomie, relativement artificielle qui permet de simplifier la vie.

Idem, c’est bien mieux formulé comme cela.

2- Pour leur déplacement, je pense aujourd’hui que déplacer par affectation n’a que peu de sens chez les entités. Et un problème technique revient si elles sont impliquées dans une hiérarchie: le slicing est de retour.

Effectivement !

3- C’est cool de voir en code mon analogie/exemple de l’appareil de nettoyage!

:)

4- Il manque un destructeur virtuel. Et du coup, une instanciation, ou alors indiquer que l’on n’a pas fini, mais que l’on va y revenir.

Effectivement, je ne sais pas comment mais j’ai oublié ça ! ^^ Je vais voir mais effectivement peut-être que ça ira se greffer au chapitre suivant.

5- redefine est bizarre. C’est sûr c’est le terme officiel en VO pour l'overload masquant? Car en VF, c’est la traduction quasi-officielle de override. NB: quand j’explique, j’aime employer supplanter pour décrire le mécanisme à l’oeuvre. Et un certain auteur emploie surdéfinir, alors que le reste de la communauté et des développeurs traduit override par redéfinir. Et d’autres emploient à tord surcharger.

Euh je ne sais pas si c’est le terme officiel. Je l’ai lu récemment quelque part mais je ne sais plus où.

6- J’aurai presque envie de mettre une parenthèse pour ceux qui se demandent quand il faut hériter (probablement pour un autre chapitre, cf les histoires de pédagogie): "Remarque, ce qui est factorisé, ce ne sont pas les données, mais les comportements"

lmghs

Oui, c’est intéressant.

Merci pour le commentaire du Discord, c’est noté, ça synthétise bien ce qui est important.

+1 -0

Grosse question existentielle sur le disord à propos des termes utilisées pour les sémantiques de classes. Je recopie ici pour avoir vos avis.

gbdivers—Aujourd’hui à 17:46

question a propos de semantique

on (je) utilise souvent les termes de "sémantique de valeur" et "sémantique d’entité". Pour la sémantique de valeur, pas de problème pour la source, c’est du Stepanov et c’est largement utilisé en anglais

par contre, pour "entité", ca vient d’ou ? J’utilise ce terme depuis developpez.com, mais je ne vois jamais ce terme utilisé en anglais. Et je le trouve trop proche de Entity dans ECS vous savez d’où cela vient a l’origine ?

j’aime bien cette opposition d’archétypes de classes pour l’enseignement, donc je voudrais garder ca

Praetonus—Aujourd’hui à 17:50

C’est reference semantics en anglais me semble

Aucune idée d’où vient le terme français par contre

gbdivers—Aujourd’hui à 17:52

je crois pas. Je vois régulièrement l’utilisation de reference type ou de pointer type en anglais, mais c’est bien la sémantique de pointeur qui est concerné (je prefere "indirection", pour parler du concept, pour éviter la confusion avec les syntaxes proprement dites)

un exemple : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1412r0.pdf dans ce doc, ils font clairement la difference entre value type (ca ok), pointer type (ca ok aussi) mais utilise le terme polymorphic type pour ce que j’appelle la sémantique d’entité

Praetonus—Aujourd’hui à 17:53

https://isocpp.org/wiki/faq/value-vs-ref-semantics

gbdivers—Aujourd’hui à 17:54

(je trouve personnellement que parler de "type polymorphique" est meilleur que de parler "entité")

j’avais déjà vu cette faq et c’est bien dans le sens reference/pointeur qu’ils parlent de reference semantic

par contre, je réalise qu’il y a une confusion supplémentaire avec cette faq. Cela concerne plus le "passage par valeur ou par reference" et n’a pas trop a voir avec les sémantiques de classes dont on parle quand on parle de valeur vs entité

c’est la distinction entre "value type" et "value semantic" en anglais

en parlant de "classe a sémantique de valeur", c’est probablement ambigue

pour la source sur developpez.com : https://cpp.developpez.com/faq/cpp/?page=Les-classes-en-Cplusplus#Quand-est-ce-qu-une-classe-a-une-semantique-de-valeur Developpez.com

+1 -0

Effectivement, je m’étais déjà posé lz question avant sans y trouver de réponse satisfaisante. Ça mérite réflexion !

L’avantage de parler de sémantique d’entité plutôt que de parler de types polymorphiques est avant tout pédagogique pour moi ; cela permet d’introduire le concept plus naturellement je trouve.

+1 -0

(je trouve personnellement que parler de "type polymorphique" est meilleur que de parler "entité")

gbdivers

Oui et non : les entités au sens où on l’entend en français ne sont pas nécessairement polymorphiques. Même si dans un tel cas, les questions de copies sont moins casse gueule.

La difficulté avec ce jargon là, c’est qu’actuellement il est relativement restreint au langage C++ et à rien d’autre. Le problème se posant dans un contexte :

  • orienté classe
  • où les classes peuvent être utilisées pour représenter des value-types

Il est sûr que tout ceci aurait été beaucoup plus simple si l’on avait finalement fait des choix plus tranchés au niveau du langage, mais c’est un peu tard pour revenir dessus.

Le terme VF est possiblement influencé par moi et d’autres avant. Il faudrait creuser sur fclc++. J’ai souvenir d’avoir croisé cette taxinomie dans un vieil article de Howard Hinnant — enfin, je crois…

Je n’arrive pas à le retrouver dans mes bookmarks. Je regarde si je n’ai pas une version électronique quelques part

Perdu. C’était Kevlin Henney, Objects of value: http://www.two-sdg.demon.co.uk/curbralan/papers/ObjectsOfValue.pdf

Leurs noms commençaient par des 'H' et avaient 2 ’n’ … ^^'

EDIT: Sur fclc++, je retrouve donc 2 messages de James à qui j’ai très certainement piqué le vocabulaire:

L’avantage de parler de sémantique d’entité plutôt que de parler de types polymorphiques est avant tout pédagogique pour moi ; cela permet d’introduire le concept plus naturellement je trouve.

Pour un apprenant qui découvre ces termes, je ne suis pas sur que l’un soit plus compréhensible que l’autre.

Oui et non : les entités au sens où on l’entend en français ne sont pas nécessairement polymorphiques. Même si dans un tel cas, les questions de copies sont moins casse gueule.

Dans une perspective de nomenclature précise, ca serait effectivement problématique d’utiliser "polymorphic type" pour des types non polymorphiques.

Mais le but ici est pédagogique : avoir des archétypes pour simplifier l’apprentissage. Dans mon ancien cours, dans la partie "sémantique d’entité", c’était très clairement des types polymorphiques que je présentais (héritage, pointeurs, etc).

A mon avis, une "entité" qui ne serait pas "polymorphique" est plus un hybride qu’un des 2 archétypes et devrait donc être présenté séparément (voire pas du tout, dans un cours débutant. Juste dire a la fin "en fait le C++ vous autorise à ne pas respecter ces sémantiques et a mettre tout ce que vous voulez dans une classe, mais sachez que cela a un coût en termes de qualité du code et de maintenance")

Il est sûr que tout ceci aurait été beaucoup plus simple si l’on avait finalement fait des choix plus tranchés au niveau du langage, mais c’est un peu tard pour revenir dessus.

C’est bien le propos des sémantiques : donner des guidelines de façon à donner un sens précis à une classe. Sens qui n’est pas contraint par le langage, donc qui doit l’être par les sémantiques.

En gros, pour moi, cela va dans le sens de : "le langage ne le fait pas pour vous, donc si vous voulez faire les choses proprement, voilà comment il faut faire". Cela rend l’apprentissage plus long et nécessite une plus grande rigueur dans l’écriture des classes, mais rien insurmontable.

Peut etre qu’il faudrait indiquer ce que je viens de dire dans le cours. L’apprenant suivra plus facilement les guidelines s’il sait le pourquoi. (Et plus généralement, il ne faut pas hésiter, je pense, à mettre des petites notes dans le cours pour justifier certains choix pédagogiques. Par exemple quand il y a plusieurs syntaxes et qu’on en présente/utilise qu’une seule)

Le terme VF est possiblement influencé par moi et d’autres avant.

Merci pour les références.

Le point important a mon avis, c’est que l’utilisation des termes évoluent. Sauf erreur de ma part (tu pourras mieux dire que moi), l’utilisation du terme "value semantic" était assez rare, même en anglais, en dehors des spécialistes du langage. Cela a commencé à changer ces dernières années, avec l’importance des value types pour simplifier la gestion de la mémoire.

Cette différence entre l’anglais et le français peut être une source de confusion. (C’est pour cela que je pense qu’il faut systématiquement donner les termes anglais). Je me demande s’il ne faut tout simplement pas corriger nos habitudes et utiliser des traductions plus directe de l’anglais.

  • "classe a sémantique de valeur" -> un type-valeur? (type de valeur ? une valeur ? ne pas changer ?)
  • "classe a sémantique d’entité" -> un type polymorphique? (un type-polymorphe ?)

(EDIT: et on pourrait parler de "liste d’initialisation" aussi un jour…)

+1 -0

L’entité n’est pas nécessairement à vocation polymorphique. L’article original ne faisait pas la distinction à cet endroit là.

Avec une recherche, je vois que James (Américain IIRC, et francophone) parle des entity objects qui doivent avoir une reference semantics: https://stackoverflow.com/a/5380255/15934

A quelques exceptions près (https://stackoverflow.com/questions/13209545/does-objective-c-have-c11-rvalue-references-and-move-semantics-equivalents-or, https://stackoverflow.com/questions/12174581/c-base-type-member-variables-as-references-or-pointer, http://lambda-the-ultimate.org/node/1514), je trouve "entity semantics" en VO dans des écrits de francophones à priori.

Maintenant, si semantics est utilisé avec une connotation plus technique en VO (value, reference, move), je trouve l’utilisation en VF plus proche du sens (sic) du mot.

Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l’adresse suivante :

Merci d’avance pour vos commentaires.


Salut à tous.

Une petite mise à jour, avec une section consacrée au copy-and-swap ainsi qu’une mise à jour du code du TP sur les matrices.

Merci d’avance à tous. :)

Duplication

Il y a des détails qui me chagrinent. Il n’y a pas de ressource pour l’instant qui vienne mettre la pagaille.

Du coup, on n’a pas besoin de tout ça.

Avec une ressource, on verrait alors vite l’intérêt de libérer, d’une copie propre, et d’autres éléments. Faut-il faire ça dans un TP séparé avec une bonne grosse variable globale ?

p.ex: une

class EtagereAPeinture
{
public:
    EtagereAPeinture() = default;
    std::size_t attrape() {
        auto wh = std::find(std::begin(pots_libres), std::end(pots_libres), false);
        if (wh == std::end(pots_libres))
            throw std::bad_alloc(); // vous voyez où je veux en venir?
        *wh = true;
        return std::distance(std::begin(pots_libres), wh);
    }
    void rend(std::size_t pot) noexcept {
        std::assert(pot_libres[pot]== false && "Tu ne peux pas me rendre un pot que j'ai chez moi!");
        pot_libres[pot] = false;
    } 

private:
    std::array<bool, N> pots_libres = {false}; // syntaxe?
};

EtagereAPeinture<4> etagere_du_garage; // globale, ou singleton...

Et de là, on définit une classe valeur qui possède des pots, et quand on copie, on acquiert/attrape un nouveau pot dans l’étagère.

De là, on arrive rapidement à l’épuisement si on oublie de restituer, à l’observation du double release si on ne blinde pas la copie. Et ce peut être moins abstrait que des cases mémoires pour quelqu’un qui n’a jamais manipulé. Et c’est de fait une préparation à l’exo sur les matrices. C’est aussi une introduction aux exceptions: on ne peut pas continuer, on déroute l’exécution. Pas besoin de parler de catch pour l’instant, c’est du teasing pour un futur chapitre.

Maintenant… un machin qui copie des pots de peintures, c’est très certainement une entité. Ca peut servir d’intro à la suite d’ailleurs.

Dernier détail, le copy-and-swap. L’idiome est intéressant, au lieu d’être WET, on est DRY. Mais il apporte autre chose, qu’il faudrait expliquer en annexe: la résistance aux exceptions. On micro-sacrifie des performances, pour plus de sûreté en toute simplicité. En général, je tends à exposer les étapes intermédiaires pour vendre le copy-and-swap

// Pas de résistance
// - aux exceptions
// - à l'auto-affectation
T& T::operator=(T const& rhs) {
    release_resource(r);
    r = acquire_resource();
    copy rhs.r into this->r;
    return *this;
}

// On est en 199x, on a un neat trick à montrer aux camarades pour se la péter
// "Eh: regardez, on résiste à `a = a;` avec ça"
// Donc auto-affectation corrigée
// Mais toujours pas de résistance aux exceptions
T& T::operator=(T const& rhs) {
    if (this != &rhs) {
        release_resource(r);
        r = acquire_resource();
        copy rhs.r into this->r;
    }
    return *this;
}

// Un chouilla plus tard, on commence à réfléchir à l'exception-safety
// - on introduit une résistance faible/basique aux exceptions parce qu'on aime bien
//   libérer avant d'acquérir -- notre côté économe?
// - et on a toujours auto-affectation corrigée avec le neat trick
T& T::operator=(T const& rhs) {
    if (this != &rhs) {
        release_resource(r);
        r = nullptr;
        r = acquire_resource();
        copy rhs.r into this->r;
    }
    return *this;
}

// Et puis on se dit que ça serait cool de ne pas modifier l'objet en cas d'échec
// On cherche et trouve la garantie Forte aux exceptions
// Et, diantre! Mais ça corrige aussi l'auto-affectation -- la question 
// devient alors optimisation (si fréquent) ou micro-pessimisation (si rare)?
T& T::operator=(T const& rhs) {
    auto tmp = acquire_resource();
    copy rhs.r into tmp;
    release_resource(r);
    r = tmp;
    return *this;
}

// Et nouveau neat-trick qui sacrifie les performances 
// -> tableaux à ne pas forcément redimensionner
// ===> le copy-and-swap

Où la version qui est à retenir fait dans l’ordre

  1. les actions qui peuvent échouer (allocation et duplication)
  2. les actions qui sont garanties sans échec.
Matrice

Pour les cartouche doxygen j’aime bien spécifier le sens des paramètres, ils sont tous @param[in] ici.

Aussi @return ne prend pas le type de retour. Il n’y a pas de traitement particulier pour le premier terme. Cf la coloration syntaxique avec vim, ou la doc en ligne, ou les résultats générés (comme p.ex. ici, où on voit qu’il n’y a pas de style appliqué au premier mot après le @return)

Waouh ! 4 pouces! J’en ai jamais eu autant! (C’est [presque] la gloire!)

Vous n’auriez pas dû me donner confiance, je vais continuer mes remarques …

Quand je m’étais bricolé une classe matrice, (j’avais fait des matrices carrés), et pour moi, le constructeur par défaut réalise une matrice unitaire, pas une matrice vide. De même je me suis fait un constructeur pour une matrice de rotation … Je trouve que c’est plus utile.

"enfin, c’est vous qui voyez!"

Par contre, je ne connaissais pas opertator(), ça m’ouvre des perspectives !

Bien cordialement.

@Dedeun : ton aide et tes remarques sont plus que bienvenues. :)

Oui, on pourrait faire une matrice unitaire. Reste que ça peut être une complication de plus pour ceux qui ne sont pas trop amis avec les maths, sans vraiment ajouter de valeur à l’exercice, le but étant vraiment de pratiquer la sémantique de valeur.

Mais après, rien n’empêche un lecteur d’aller plus loin. Au contraire, on l’y encourage très fortement. :)

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte