NEW il y a 5 ans
J'espère qu'il sera à la hauteur de tes attentes :-)
Codnpix
il y a 5 ans
Je suis quasiment au bout (il me reste à implémenter les patterns) !
Vraiment excellent tuto, un grand merci pour la qualité, la clarté et la précision des explications tout au long des étapes. C'est vraiment cool d'avoir été explorer chaque aspect de façon aussi complète.
Encore une fois j'ai appris plein de trucs, j'ai les idées bien plus claires au niveau du raisonnement objet et MVC (ça tombe bien il me semble que c'était le but !).
J'étais super content entre autre de découvrir un exemple d'utilisation des opérateurs bitwise, je n'avais jamais osé mettre le nez là-dedans et c'est vraiment chouette de lever le voile là-dessus et d'en découvrir l'utilité.
J'ai peut-être une petite réserve sur les fonctions de conversions de couleur HSB->RGB que j'ai trouvé un peu obscures, les noms des variables sont assez peu parlants (f, v, q, t, etc...) du coup je n'ai pas vraiment réussi à comprendre ce qui se passait là-dedans en détail.
Sinon j'ai une petite question (que j'avais déjà posé à Chris Scientist dans son tuto POO Sokoban) :
J'ai remarqué que, comme dans le code de Chris, tu utilises les instances de tes classes exclusivement à travers des pointeurs. Si j'ai bien compris, a priori on pourrait aussi choisir de les manipuler directement, du coup je voudrait savoir si il y a une raison précise, ou une convention, qui fait qu'on ne les accède que par des pointeurs.
Encore un grand merci pour ce magnifique travail !
(d'ailleurs il faudrait l'ajouter à la page académie, ça me semble clairement s'imposer)
NEW il y a 5 ans
Je suis quasiment au bout (il me reste à implémenter les patterns) !
Vraiment excellent tuto, un grand merci pour la qualité, la clarté et la précision des explications tout au long des étapes. C'est vraiment cool d'avoir été explorer chaque aspect de façon aussi complète.
Encore une fois j'ai appris plein de trucs, j'ai les idées bien plus claires au niveau du raisonnement objet et MVC (ça tombe bien il me semble que c'était le but !).
J'étais super content entre autre de découvrir un exemple d'utilisation des opérateurs bitwise, je n'avais jamais osé mettre le nez là-dedans et c'est vraiment chouette de lever le voile là-dessus et d'en découvrir l'utilité.
J'ai peut-être une petite réserve sur les fonctions de conversions de couleur HSB->RGB que j'ai trouvé un peu obscures, les noms des variables sont assez peu parlants (f, v, q, t, etc...) du coup je n'ai pas vraiment réussi à comprendre ce qui se passait là-dedans en détail.
Sinon j'ai une petite question (que j'avais déjà posé à Chris Scientist dans son tuto POO Sokoban) :
J'ai remarqué que, comme dans le code de Chris, tu utilises les instances de tes classes exclusivement à travers des pointeurs. Si j'ai bien compris, a priori on pourrait aussi choisir de les manipuler directement, du coup je voudrait savoir si il y a une raison précise, ou une convention, qui fait qu'on ne les accède que par des pointeurs.
Encore un grand merci pour ce magnifique travail !
(d'ailleurs il faudrait l'ajouter à la page académie, ça me semble clairement s'imposer)
Steph
il y a 5 ans
Merci pour ton commentaire... élogieux ! C'est trop :-)
Je suis très content que tu aies pu profiter pleinement de l'ensemble des éléments que j'ai développés dans ce tutoriel. C'était précisément le but que je m'étais fixé. Et bravo pour être arrivé au bout en ayant (presque) tout compris ! C'est somme toute un tuto relativement dense où de très nombreuses notions sont abordées, certaines sont fondamentales et te serviront sans nul doute dans tes développements.
En ce qui concerne la conversion des espaces colorimétriques, je t'invite à lire les deux articles suivants qui détaillent davantage les choses :
Ma préférence va indiscutablement au second, qui est une discussion sur StackOverFlow. Rends-toi directement à la première réponse pour avoir une présentation très détaillée et très pertinente sur la méthode de conversion. Accroche-toi ... ce sont des maths : de la géométrie dans un espace à trois dimensions ;-)
Concernant ta question sur l'intérêt d'utiliser des pointeurs pour manipuler des structures de données comme les objets, je vais essayer d'être synthétique. Considérons les définitions suivantes :
class Base { ... }; class Derived : public Base { ... }; void foo(Base b) { ... } // passage par valeur de l'argument void bar(Base* b) { ... } // passage par pointeur de l'argument void fum(Base& b) { ... } // passage par référence de l'argument
L'intérêt premier concerne le polymorphisme. Une instance de la classe Derived
revêt en effet plusieurs formes : elle peut être considérée à la fois comme un objet de type Derived
, mais également comme un objet de type Base
(conséquence de l'héritage). Par conséquent, examinons les cas suivants :
Derived d;
foo(d); // ici d sera considéré comme une instance de Base et perdra toute sa spécificité
bar(&d); // ici d conservera sa nature spécifique : une instance de Derived
fum(d); // ici d conservera également sa nature spécifique
Le second intérêt concerne la potentielle copie en mémoire.
Base b; foo(b); // cas n°1 bar(&b); // cas n°2 fum(b); // cas n°3
cas n° 1
Ici la fonction foo
reçoit une copie de l'objet b
. Autrement dit, si la fonction foo
modifie l'état de l'objet copié, cela n'aura aucune incidence sur l'objet b
.
cas n° 2
Ici la fonction bar
reçoit une référence à l'objet b
. Autrement dit, l'adresse mémoire à laquelle est stocké l'objet. Par conséquent, toute modification de l'état de b
sera permanente, même après être ressorti de la fonction bar
. La fonction travaille donc directement sur l'instance.
cas n°3
Identique au cas n°2.
Voilà... j'espère avoir été assez clair... il s'agit de notions absolument FONDAMENTALES du langage qui doivent être parfaitement maîtrisées !!!
Encore merci pour ton commentaire. C'est toujours très gratifiant de constater qu'on a réussi à transmettre un peu de ses connaissances à ceux qui ont envie d'apprendre :-) Et ça nous encourage à continuer !
NEW il y a 5 ans
Merci pour ton commentaire... élogieux ! C'est trop :-)
Je suis très content que tu aies pu profiter pleinement de l'ensemble des éléments que j'ai développés dans ce tutoriel. C'était précisément le but que je m'étais fixé. Et bravo pour être arrivé au bout en ayant (presque) tout compris ! C'est somme toute un tuto relativement dense où de très nombreuses notions sont abordées, certaines sont fondamentales et te serviront sans nul doute dans tes développements.
En ce qui concerne la conversion des espaces colorimétriques, je t'invite à lire les deux articles suivants qui détaillent davantage les choses :
Ma préférence va indiscutablement au second, qui est une discussion sur StackOverFlow. Rends-toi directement à la première réponse pour avoir une présentation très détaillée et très pertinente sur la méthode de conversion. Accroche-toi ... ce sont des maths : de la géométrie dans un espace à trois dimensions ;-)
Concernant ta question sur l'intérêt d'utiliser des pointeurs pour manipuler des structures de données comme les objets, je vais essayer d'être synthétique. Considérons les définitions suivantes :
class Base { ... }; class Derived : public Base { ... }; void foo(Base b) { ... } // passage par valeur de l'argument void bar(Base* b) { ... } // passage par pointeur de l'argument void fum(Base& b) { ... } // passage par référence de l'argument
L'intérêt premier concerne le polymorphisme. Une instance de la classe Derived
revêt en effet plusieurs formes : elle peut être considérée à la fois comme un objet de type Derived
, mais également comme un objet de type Base
(conséquence de l'héritage). Par conséquent, examinons les cas suivants :
Derived d;
foo(d); // ici d sera considéré comme une instance de Base et perdra toute sa spécificité
bar(&d); // ici d conservera sa nature spécifique : une instance de Derived
fum(d); // ici d conservera également sa nature spécifique
Le second intérêt concerne la potentielle copie en mémoire.
Base b; foo(b); // cas n°1 bar(&b); // cas n°2 fum(b); // cas n°3
cas n° 1
Ici la fonction foo
reçoit une copie de l'objet b
. Autrement dit, si la fonction foo
modifie l'état de l'objet copié, cela n'aura aucune incidence sur l'objet b
.
cas n° 2
Ici la fonction bar
reçoit une référence à l'objet b
. Autrement dit, l'adresse mémoire à laquelle est stocké l'objet. Par conséquent, toute modification de l'état de b
sera permanente, même après être ressorti de la fonction bar
. La fonction travaille donc directement sur l'instance.
cas n°3
Identique au cas n°2.
Voilà... j'espère avoir été assez clair... il s'agit de notions absolument FONDAMENTALES du langage qui doivent être parfaitement maîtrisées !!!
Encore merci pour ton commentaire. C'est toujours très gratifiant de constater qu'on a réussi à transmettre un peu de ses connaissances à ceux qui ont envie d'apprendre :-) Et ça nous encourage à continuer !
Codnpix
il y a 5 ans
Merci beaucoup !
Ce n'est pas évident de se familiariser avec ces notions de pointeurs, les autres langages que je connais un peu (js, php, java..) font ces choses de façon plus ou moins implicite selon le cas, du coup c'est perturbant de se retrouver avec ces choix possibles. Mais c'est intéressant.
En tout cas ça devient plus clair. Merci !
Bonne journée :).
NEW il y a 5 ans
Merci beaucoup !
Ce n'est pas évident de se familiariser avec ces notions de pointeurs, les autres langages que je connais un peu (js, php, java..) font ces choses de façon plus ou moins implicite selon le cas, du coup c'est perturbant de se retrouver avec ces choix possibles. Mais c'est intéressant.
En tout cas ça devient plus clair. Merci !
Bonne journée :).
Steph
il y a 5 ans
Oui ! C'est le problème de ces langages de plus haut niveau... ils masquent la réalité des choses...
Néanmoins, en PHP par exemple, il faut se méfier... par défaut les passages se font par référence... SAUF pour les tableaux ! Donc il faut le spécifier explicitement dans ce cas à l'aide de l'esperluette &
:
function foo(array &$data) : void { // ... }
Donc, bien lire la doc avant de présumer d'un comportement dont on a l'habitude avec d'autres langages ;-)
NEW il y a 5 ans
Oui ! C'est le problème de ces langages de plus haut niveau... ils masquent la réalité des choses...
Néanmoins, en PHP par exemple, il faut se méfier... par défaut les passages se font par référence... SAUF pour les tableaux ! Donc il faut le spécifier explicitement dans ce cas à l'aide de l'esperluette &
:
function foo(array &$data) : void { // ... }
Donc, bien lire la doc avant de présumer d'un comportement dont on a l'habitude avec d'autres langages ;-)
Codnpix
il y a 5 ans
function foo(array &$data)
J'ai effectivement vu passer cette chose là quelques fois ^^.
NEW il y a 5 ans
function foo(array &$data)
J'ai effectivement vu passer cette chose là quelques fois ^^.
NEW il y a 5 ans
J'ai une autre petite question à laquelle j'ai dû mal à trouver une réponse moi même :
J'ai vu que tu utilises souvent le type size_t
itérer sur des tableaux. Je ne comprends pas bien pourquoi...
NEW il y a 5 ans
La taille des différents types n'est pas garantie par le langage C. Un int n'as pas forcément la même taille sur toutes les architectures. Les types size_t permettent d'avoir une taille fixe quelle que soit l'architecture, à partir du moment où ils sont définis.
Par exemple: int8_t => entier sur 8 bits (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits
tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...
Codnpix
il y a 5 ans
Par exemple: int8_t => entier sur 8 bits (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits
tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...
Je comprends l'intérêt de spécifier la taille de l'entier qu'on utilise avec un uint8_t
, int16_t
etc, mais je ne comprends toujours pas bien ce qu'apporte le fait d'utiliser précisément "size_t
". De ce que j'ai vu dans les références C++ ça renvoie la valeur de l'opérateur sizeof
sur n'importe quel objet, donc ça peut contenir quelque chose d'à peu près n'importe quel taille selon ce qu'on met dedans (?). Mais ça ne m'a pas beaucoup éclairé...
NEW il y a 5 ans
Par exemple: int8_t => entier sur 8 bits (soit de (-(2) puissance 7 - 1) à (2 puissance 7 moins 1)) et int16_t => int stocké sur 16 bits
tu rajoutes un u devant pour les non signés => uint8_t (de 0 à 2 puissance 8 - 1), etc ...
Je comprends l'intérêt de spécifier la taille de l'entier qu'on utilise avec un uint8_t
, int16_t
etc, mais je ne comprends toujours pas bien ce qu'apporte le fait d'utiliser précisément "size_t
". De ce que j'ai vu dans les références C++ ça renvoie la valeur de l'opérateur sizeof
sur n'importe quel objet, donc ça peut contenir quelque chose d'à peu près n'importe quel taille selon ce qu'on met dedans (?). Mais ça ne m'a pas beaucoup éclairé...
Steph
il y a 5 ans
Le type size_t
est un entier non signé. Il convient donc parfaitement à la représentation des tailles, des dimensions de tableaux, des index croissants et non négatifs. Bien entendu, un uint8_t
ou même un uint32_t
feraient également l'affaire, mais le fait d'utiliser size_t
indique plus clairement l'usage qu'on lui réserve et donc la nature de la variable ainsi déclarée. C'est d'ailleurs le type qui est retourné par la fonction sizeof()
.
Par ailleurs, il a la particularité de pouvoir s'adapter à l'architecture pour laquelle on compile. Sur une architecture 8 bits, il sera codé sur 8 bits, alors que si on passe sur une architecture 32 bits (comme la META), il sera codé sur 32 bits. Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)
Est-ce que c'est plus clair pour toi ?
NEW il y a 5 ans
Le type size_t
est un entier non signé. Il convient donc parfaitement à la représentation des tailles, des dimensions de tableaux, des index croissants et non négatifs. Bien entendu, un uint8_t
ou même un uint32_t
feraient également l'affaire, mais le fait d'utiliser size_t
indique plus clairement l'usage qu'on lui réserve et donc la nature de la variable ainsi déclarée. C'est d'ailleurs le type qui est retourné par la fonction sizeof()
.
Par ailleurs, il a la particularité de pouvoir s'adapter à l'architecture pour laquelle on compile. Sur une architecture 8 bits, il sera codé sur 8 bits, alors que si on passe sur une architecture 32 bits (comme la META), il sera codé sur 32 bits. Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)
Est-ce que c'est plus clair pour toi ?
Codnpix
il y a 5 ans
Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)
C'est déjà un peu plus clair oui :). Mais du coup, en terme d'occupation de la mémoire, si tu t'en sers de variable d'itération pour un tableau de disons 50 éléments, alors tu aura une variable sur 32 bits (si c'est le maximum que l'environnement permet) alors qu'un entier sur 8 bit aurait suffit ? C'est là que ça reste un peu flou pour moi..
NEW il y a 5 ans
Son intérêt majeur est justement cette adaptabilité : on se fout complètement de l'intervalle de valeurs qu'il permet de représenter car, quelle que soit l'architecture ciblée, ce sera toujours le plus grand nombre que l'architecture permet de représenter :-)
C'est déjà un peu plus clair oui :). Mais du coup, en terme d'occupation de la mémoire, si tu t'en sers de variable d'itération pour un tableau de disons 50 éléments, alors tu aura une variable sur 32 bits (si c'est le maximum que l'environnement permet) alors qu'un entier sur 8 bit aurait suffit ? C'est là que ça reste un peu flou pour moi..
Steph
il y a 5 ans
Question logique :-) ... si t'es un maniaque de l'occupation mémoire, tu utiliseras un itérateur codé sur le nombre de bits suffisants (dans le cas que tu proposes, un uint8_t
suffira amplement)... sinon tu te dis que, pour une variable volatile (qui sera détruite à la sortie de ta fonction), réserver 1 ou 4 octets... y a pas de quoi s'alarmer ;-)
NEW il y a 5 ans
Question logique :-) ... si t'es un maniaque de l'occupation mémoire, tu utiliseras un itérateur codé sur le nombre de bits suffisants (dans le cas que tu proposes, un uint8_t
suffira amplement)... sinon tu te dis que, pour une variable volatile (qui sera détruite à la sortie de ta fonction), réserver 1 ou 4 octets... y a pas de quoi s'alarmer ;-)
Codnpix
il y a 5 ans
Ok merci !
si t'es un maniaque de l'occupation mémoire
Je pense que je m'en préoccupais pas tant que ça avant, mais ton tuto propose une belle sensibilisation sur le sujet ! C'est assez chouette de découvrir qu'en poussant un peu les meubles, même une petite machine comme la META a des vraies possibilités de calcul. J'adore cet environnement minimaliste^^. (ça change par rapport aux monstres d'ordi qu'on a l'habitude d'utiliser aujourd'hui)
NEW il y a 5 ans
Ok merci !
si t'es un maniaque de l'occupation mémoire
Je pense que je m'en préoccupais pas tant que ça avant, mais ton tuto propose une belle sensibilisation sur le sujet ! C'est assez chouette de découvrir qu'en poussant un peu les meubles, même une petite machine comme la META a des vraies possibilités de calcul. J'adore cet environnement minimaliste^^. (ça change par rapport aux monstres d'ordi qu'on a l'habitude d'utiliser aujourd'hui)
NEW il y a 5 ans
Perso, quand je sais ce que j'aurais dans ma variable, je préfère utiliser les notation type int8_t ou uint8_t que je trouve plus parlante et même un bon vieux char quand c'est un caractère. On peut faire autrement et size_t peut avoir son utilité notamment par ce qu'il transporte une idée de taille et que dans une fonction, comme le disait Steph si on met se type pour dimensionner un index de tableau, on est certain d'avoir le plus grand tableau possible. De plus comme il est non signé, il permet de 'bloquer' un accès à un emplacement négatif suite à une anomalie dans le code qui provoquerait une erreur. Pour moi, c'est bien dans des librairies que l'on veut réutiliser car on ne connait pas forcément la taille des tableaux que l'on utilisera lors de la réutilisation mais en règle générale, je code pour moi (même si je partage) et du coup, je ne l'utilise pas.