J'ai récemment commencer un début de moteur 3D, et pour avoir un moteur 3D complet, il faut au moins une petite gestions de structures de données.
Le côté des tableaux dynamiques du C++ m'attiraient pas mal (std::vector), ainsi que les piles et les files sont utiles pour certaines choses.
J'ai donc décidé, avec l'astuce d'un ami qui se reconnaîtra, et l'aide pour l'optimisation au niveau de la compilation de rz0, j'ai put "finir" ou presque du moins, le vector, la pile, et la file.
J'ai aussi implémenter une petite gestion d'erreur(mais ça on s'en fout un peu). J'aimerais donc savoir, si pour des utilisateurs, ma façon de faire est plutôt naturel, si il y a des bugs, c'est possible aussi, (bien que valgrind ne m'en est pas trouver, mais je n'ai pas fait beaucoup de test faut dire ^^.)
Je n'ai pas put faire de commentaire sur le code en préprocesseur, mais bon, il y a environ 100 / 150 lignes maximum(et certaines revienne plusieurs fois), et plusieurs fonctions, cela permet de s'y retrouver un peu.
Voilà la gestion d'erreur.
Header
#ifndef ERROR_H_INCLUDED
#define ERROR_H_INCLUDED
typedef enum{
/** Succès **/
QGM_SUCCESS, /** Rien à signalé **/
/** Mauvaise allocations **/
QGM_BADALLOC, /** Allocation de mémoire échouée **/
/** Fuites de mémoires **/
QGM_MEMORYLEAKSALLOC, /** Fuites de mémoires (Allocation) **/
/** L'élément n'existe pas **/
QGM_NOELEMENTALLOC, /** Pointeur à détruire n'existe pas **/
QGM_NOELEMENTSTACK, /** Aucun élément à dépiler **/
QGM_NOELEMENTQUEUE, /** Aucun élement à défiler **/
QGM_NOELEMENTVECTOR, /** Aucun élément à supprimer **/
}QGM_Error_t;
/** Variable recevant l'erreur **/
extern QGM_Error_t QGM_Error;
/** Synopsis : Indique si il y a ou non une erreur
Retour : 1 : erreur. 0 : Pas d'erreur **/
int QGM_IsError(void);
/** Synopsis : Permet de récupèrer l'erreur
Retour : Chaîne de caractère contenant l'erreur **/
char const *QGM_GetError(void);
#endif
fichier C
#include <stdio.h>
#include "error.h"
/** Variable recevant l'erreur **/
QGM_Error_t QGM_Error = QGM_SUCCESS;
/** Tableau des erreurs en fonction de QGM_Error **/
char const *QGM_String_Error[] = {
/** Succès **/
"",
/** Mauvaise allocations **/
"Not space\n",
/** Fuites de mémoires **/
"Memory Leaks Are Detected (Allocation)\n",
/** L'élément n'existe pas **/
"This pointer can't be free (Allocation)\n",
"Nothing element to pop (Stack)\n",
"Nothing element to pop (Queue)\n",
"Nothing element to delete (Vector)\n",
};
int QGM_IsError(void){
return (QGM_Error == QGM_SUCCESS) ? 0 : 1;
}
/** Fonction permettant de récupérer l'erreur **/
char const *QGM_GetError(void)
{
char const *t = QGM_String_Error[QGM_Error];
QGM_Error = QGM_SUCCESS;
return t;
}
et voilà mes structures de données implémenter sous forme de préprocesseur.
On y retrouver au début la macro permettant de "dévoiler" les prototypes et structures. A mettre dans le fichier structure.h par exemple.
Et la macro permettant de "dévoiler" les fonctions. A mettre dans le fichier structure.c par exemple.
J'ai regardé en travers. Je pense savoir qui est ton ami vu ton implémentation.
Tu pourrais essayer de faire quelque chose de plus générique encore, à la manière de queue.h et stack.h.
Pour chaque nouveau type, pourquoi générer le header et les fonctions séparément? Pas possible en 1 seule fois?
Dans tes fonctions, comme par exemple ici
int QGM_Vector_##TYPE##_Insert\
(QGM_Vector_##TYPE *self, TYPE data, unsigned place){\
Tu ne testes pas le pointeur self, à l'entrée.
Si l'utilisateur pass un pointeur NULL, par exemple, ça va crasher sans infos. peut être utiliser un assert non?
Citation : Qnope
si il y a des bugs, c'est possible aussi, (bien que valgrind ne m'en est pas trouver, mais je n'ai pas fait beaucoup de test faut dire ^^.)
C'est pas trop le rôle de valgrind de détecter les bugs(c'est très spécifique ).
Il faut vraiment que tu fasses une batterie des test!
et la gestion d'erreur, à base de goto n'apporte rien.
C'est étrange, ton code me rappel quelque chose
Plus sérieusement, comme l'a souligné GurneyH, la gestion des erreurs via goto n'apporte ici pas grand chose, tu pourrais la plupart du temps te contenter de deux return (sauf pour la fonction Vector_Init et la fonction main). Pour le reste, ton implémentation m'a l'air correcte, sauf pour une petite chose (mais qui a son importance):
TYPE *ptr = (realloc)(self->data, self->size * 3 * sizeof *ptr);
Il faut bien faire attention à ce que le calcul ne dépasse pas la capacité maximale du type size_t, auquel cas tu vas allouer une taille nulle ou inférieure à tes besoins. Sinon, personnellement, j'aurais plutôt recouru à une méthode utilisant les pointeurs génériques histoire de n'avoir qu'une seule suite de fonction.
Citation : GurneyH
Tu ne testes pas le pointeur self, à l'entrée.
Si l'utilisateur pass un pointeur NULL, par exemple, ça va crasher sans infos. peut être utiliser un assert non?
Je serais plutôt d'avis de ne pas le tester, sauf pour la fonction de destruction (afin de facilité la gestion d'éventuelles erreurs). Au final, un pointeur nul n'est qu'un cas d'argument invalide et n'empêchera pas l'utilisateur de provoquer un crash avec ce type d'appel : QGM_Vector_int_PushBack((void*)42,42). Si l'utilisateur ne vérifie pas la validité des données qu'il envoie, il ne peut s'en prendre qu'à lui-même
J'ai regardé en travers. Je pense savoir qui est ton ami vu ton implémentation.
Tu pourrais essayer de faire quelque chose de plus générique encore, à la manière de queue.h et stack.h.
Salut. Merci de ta réponse. Peux tu mieux expliqué pour queue.h et stack.h?
Tu parles de sys/queue.h par exemple? Si oui, je trouve que c'est un peu compliqué. Mais je ne vois pas en quoi c'est encore plus générique .
@GurneyH. Pour le header, et fonctions, je pense que c'est plus "naturel" de faire comme ça. En effet, l'utilisateur n'aura qu'à mettre dans un fichier vector.h les header des types qu'il souhaite utilisé. Et dans vector.c, les fonctions des types qu'il souhaite utilisé.
Enfin, ce n'est qu'un avis personnel ça.
Ensuite pour testé les valeurs d'entrée, je ne veux pas pour les même raison de Taurre, mais je ne le teste pas non plus pour la fin, pour les mêmes raisons. De plus, une comparaison de gagner, ça fait toute la différence sur les performances .
Pour la gestion d'erreur à base de goto, c'est surtout que j'ai copier le _error: .
Sinon Taurre, que veux tu dire par pointeur générique? Le pointeur void*?
Il est légèrement moins efficace je dirais non? Car il faut utiliser de la recopie par memcpy, et même si la fonction est interne au compilateur, je pense que le fait de faire de la copie par type sera plus rapide non? Puis, c'est moins naturel, car on ne peut pas retourner directement le code .
Le type size_t==unsignedlong; ?
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
Sinon Taurre, que veux tu dire par pointeur générique? Le pointeur void*?
Tout à fait.
Citation : qnope
Il est légèrement moins efficace je dirais non? Car il faut utiliser de la recopie par memcpy, et même si la fonction est interne au compilateur, je pense que le fait de faire de la copie par type sera plus rapide non? Puis, c'est moins naturel, car on ne peut pas retourner directement le code.
Effectivement, tu vas perdre un peu de temps comparer au code que tu as actuellement. Cependant, je pense que tu y gagneras en lisibilité, en maintenabilité et en facilité d'utilisation.
Citation : qnope
Le type size_t==unsignedlong; ?
Sur une architecture 64 bits c'est le plus souvent le cas, oui. Maintenant, la norme ne spécifie pas le type correspondant de size_t, elle précise juste qu'il s'agit d'un entier non signé. Toutefois, depuis le C99, tu peux connaître le maximum possible du type size_t à l'aide de la macroconstante SIZE_MAX (déclarée dans stdint.h). À noter que si tu souhaites rester en C89, tu peux aussi l'obtenir à l'aide de ce code:
Niveau facilité d'utilisation, je pense que ma méthode est la meilleur, car il n'est pas possible de récupérer la valeur par pop comme je le fais par exemple.
Je me suis renseigné sur sys/queue.h, et pareil, on ne peut pas récupérer les valeurs, hors c'est pour ça que je suis attaché à cette méthode ^^. Elle est super naturel.
Donc je vais rajouter le test de la taille maximale, même si je pense, qu'en théorie, ce n'est pas vraiment important ^^. En effet, avant d'avoir 3 go de donnée dans une pile, ou une stack, je pense que le malloc l'aura virer avant ^^.
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
Niveau facilité d'utilisation, je pense que ma méthode est la meilleur, car il n'est pas possible de récupérer la valeur par pop comme je le fais par exemple.
Si, c'est possible.
Il te suffit de demander à l'utilisateur de fournir une variable dans laquelle tu stockeras la valeur à l'aide de memcpy comme pour l'ajout.
Citation : qnope
Je me suis renseigné sur sys/queue.h, et pareil, on ne peut pas récupérer les valeurs
Ce que je veux dire, c'est qu'il n'est pas possible, par exemple, de faire.
printf("%d\n", pop());
Aah ok, je comprends mieux où tu veux en venir. En effet, ce n'est pas possible avec une implémentation utilisant des pointeurs génériques ou sys/queue.h.
Ouais, mais cependant, tu as raison, c'est mieux quand même. On a très rarement besoin de l'exemple que j'ai donner. Au pire, ça m'a permis d'apprendre à utiliser le ## ^^.
Je recoderais tout en mode préprocesseur(sans cacher de fonctions), et je reviendrais vous faire chierdemandez des conseils
quand j'aurais finit, donc je pense vers vendredi soir. Ou samedi. De plus, mon objectif est d'être le plus rapide possible, donc c'est justement un bon avantage, on économisera tout les appels de fonctions, mais l’exécutable sera plus gros, mais ça, c'est pas trop grave ^^.
Merci. A samedi^^.
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
De plus, mon objectif est d'être le plus rapide possible, donc c'est justement un bon avantage, on économisera tout les appels de fonctions, mais l’exécutable sera plus gros, mais ça, c'est pas trop grave ^^.
Comme déjà dit qnope, code une structure de donnée de manière simple.
Si il y a des problèmes de perfs et seulement si, alors tu pourras penser profiler pour savoir quels sont les goulots d'étranglements.
Avant, c'est prématuré de pensé à l'optimisation(voir la célèbre phrase de Knuth ).
Citation : Qnope
Au pire, ça m'a permis d'apprendre à utiliser le ## ^^.
Gurney, tu connais mon perfectionnisme ^^. Je fais ça pour m’amuser, si, même dans 3 mois mon moteur 3D ne sait toujours pas créer une fenêtre, ça ne me dérange pas du tout. Ce que je souhaite c'est m’amuser à programmer, et ce qui m’amuse sur des trucs comme ça, c'est d'être le plus efficace possible ^^.
Finalement, tu m'as encore appris un truc "L'optimisation prématurée est la source de tous les maux ".
En fait, ce n'est pas que d'être efficace qui m'intèrésse, c'est le fait de pouvoir voir des débutants en C pouvoir relire mes codes, et les utilisateurs de mon moteur 3D (cf dans 3 ans le prochain call of duty, utilisera mon moteur vous verrez :D) ont le plus de facilités possible ^^.
A Simbilou : Ce n'est pas pareil, apparemment ut utilise des pointeurs de type void* (enfin je pense, je suis pas sûr).
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
J'ai donc "amélioré" ma façon de gérer mes structures. Vous m'avez conseillez de regarder du côté de queue.h, mais je vous est dit kikoo c'est nul, on ne peut pas récupérer les valeurs dans un printf ^^.
J'ai donc trouvé le moyen de le faire, en utilisant le magnifique opérateur virgule, plus des ternaires. (Pouet, je comprends pourquoi tu ne codes qu'en prépro (ou avant du moins ))
Je n'étais pas parti sur ça au début, mais en réfléchissant bien, on voit que ça passe tranquillement .
Je ne gère pas les erreurs du type pointeur NULL, et je ne gère pas le cas ou "place" est supérieur a nElements, l'utilisateur doit donc savoir ce qu'il fait.
Voilà le code, qui est normalement fonctionnel, il est légèrement plus long que l'ancien, mais est tout aussi compréhensible si on va doucement je pense. Par ordre de facilité : La pile, le vector, et enfin la file(Le push m'a fait galérer )
QGM_Data.h
#ifndef DATA_H_INCLUDED
#define DATA_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error.h"
/** Définit la Pile
-Un tableau dynamique
-Le nombre d'élément
-Le nombre d'élément allouer
-Le nombre d'élément maximale :~(size_t)0;
-Un pointeur inutile pour la réallocation **/
#define QGM_STACK(TYPE)\
struct{\
TYPE *data;\
size_t nElements;\
size_t sizeAlloc;\
size_t sizeMaxAlloc;\
TYPE *unusedPointer;\
}*
/** Définit la File
-Un tableau dynamique
-Le nombre d'élément
-Le nombre d'élément allouer
-Le nombre d'élément maximale : ~(size_t)0;
-L'index de fin de file
-L'index de début de file
-Un pointeur inutile pour la réallocation
-Une valeure inutile pour accélérer la file si push = pop d'élément **/
#define QGM_QUEUE(TYPE)\
struct{\
TYPE *data;\
size_t nElements;\
size_t sizeAlloc;\
size_t sizeMaxAlloc;\
unsigned push;\
unsigned pop;\
TYPE *unusedPointer;\
TYPE unusedData;\
}*
/** Définit le Vector
-Un tableau dynamique
-Le nombre d'élément
-Le nombre d'élément allouer
-Le nombre d'élément maximale : ~(size_t)0;
-Un pointeur inutile pour la réallocation
-Une valeure inutile pour certains retraits **/
#define QGM_VECTOR(TYPE) \
struct{\
TYPE *data;\
size_t nElements;\
size_t sizeAlloc;\
size_t sizeMaxAlloc;\
TYPE *unusedPointer;\
TYPE unusedData;\
}*
/** Initialise la structure
-Créer la structure
-Création d'un élément non initialisé
-Paramètre les attributs :
-0 élément
-1 élément pré-alloué
-maxAlloc = maxSize_t / taille d'une donnée
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_INIT(self)\
((((self) = (malloc)(sizeof *(self))) != NULL) ?\
((((self)->data = (malloc)(sizeof *(self)->data)) != NULL) ?\
((self)->nElements = 0,\
(self)->sizeAlloc = 1,\
(self)->sizeMaxAlloc = ~(size_t)0 / sizeof(*(self)->data),\
QGM_SUCCESS)\
:\
(free((self)),\
QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))\
:\
(QGM_Error = QGM_BADALLOC, QGM_BADALLOC))\
/** Réalloue les données des Stacks, Vector, Queue
-On vérifie que la taille ne dépassera pas un size_t.
-On realloue une taille plus grande.
-On réinitialise les paramètres, et on renvoie QGM_SUCCESS.
Si pas d'erreur, retorune QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_RESIZE(self)\
(((self)->sizeAlloc * 3 <= (self)->sizeMaxAlloc) ?\
((((self)->unusedPointer = (realloc)((self)->data,\
(self)->sizeAlloc * 3 *\
sizeof *(self)->data))\
!= NULL) ? \
((self)->data = (self)->unusedPointer,\
(self)->sizeAlloc *= 3,\
QGM_SUCCESS)\
:\
(QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))\
:\
(QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))
/** QGM_SIZE : Renvoie le nombre d'élément **/
#define QGM_SIZE(self) (self)->nElements
/** EMPTY : Renvoie 1 si vide, sinon 0 **/
#define QGM_EMPTY(self) !(self)->nElements
/** QGM_QUIT : Détruit la structure **/
#define QGM_QUIT(self) (free((self)->data), free(self))
/*****************************************************************************/
/*********************************** STACK ***********************************/
/*****************************************************************************/
/** Initialise la Pile
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_STACK_INIT(self) QGM_INIT(self)
/** Push : Ajoute un élément sur la pile.
-Véfifie si il y a de la place, sinon realloue
-Ajoute _data sur la pile
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_STACK_PUSH(self, _data)\
(((self)->nElements == (self)->sizeAlloc) ?\
((QGM_RESIZE(self) == QGM_SUCCESS) ?\
((self)->data[(self)->nElements++] = (_data), QGM_SUCCESS)\
:\
(QGM_Error = QGM_BADALLOC, QGM_BADALLOC))\
:\
((self)->data[(self)->nElements++] = (_data), QGM_SUCCESS))\
/** POP : Dépile un élément.
Si erreur, tout les bits de la valeur renvoyé sont mis à un,
et QGM_Error = QGM_NOELEMENTSTACK
Si pas d'erreur, renvoie tout simplement la donnée **/
#define QGM_STACK_POP(self)\
((QGM_STACK_EMPTY(self) == 0) ?\
((self)->data[--(self)->nElements])\
:\
(QGM_Error = QGM_NOELEMENTSTACK,\
memset((self)->data, 0xFF, sizeof *(self)->data),\
*(self)->data))
/** CLEAR : Remet a 0 la pile **/
#define QGM_STACK_CLEAR(self) (self)->nElements = 0
/** QGM_SIZE : Renvoie le nombre d'élément **/
#define QGM_STACK_SIZE QGM_SIZE
/** EMPTY : Renvoit un si la pile est vide **/
#define QGM_STACK_EMPTY QGM_EMPTY
/** Détruit la pile **/
#define QGM_STACK_QUIT QGM_QUIT
/*****************************************************************************/
/*********************************** QUEUE ***********************************/
/*****************************************************************************/
/** Initialisation :
Appele QGM_INIT, et met a 0 push et pop
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_QUEUE_INIT(self)\
((QGM_INIT((self)) == QGM_SUCCESS) ?\
(((self)->push = (self)->pop = 0, QGM_SUCCESS)) : QGM_BADALLOC)
/** ADJUST : déplace la file au début du bloc de mémoire
push = nElements;
pop = 0 **/
#define QGM_QUEUE_ADJUST(self)\
(memmove((self)->data,\
(self)->data + (self)->pop,\
((self)->push - (self)->pop) * sizeof *(self)->data),\
(self)->push = (self)->nElements,\
(self)->pop = 0)
/** PUSH : Ajoute un élément à la file
Si il n'y a plus de place, on réalloue
Si on n'arrive à la fin du bloc de mémoire avec la fin de la file
Alors on ajuste : On déplace la file au début de la mémoire
On ajoute _data
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_QUEUE_PUSH(self, _data)\
(((self)->nElements == (self)->sizeAlloc) ?\
((QGM_RESIZE(self) == QGM_SUCCESS) ?\
(((self)->push == (self)->sizeAlloc) ?\
(QGM_QUEUE_ADJUST(self),\
(self)->data[(self)->push++] = (_data),\
++(self)->nElements,\
QGM_SUCCESS)\
:\
((self)->data[(self)->push++] = (_data),\
++(self)->nElements,\
QGM_SUCCESS))\
:\
(QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))\
:\
(((self)->push == (self)->sizeAlloc) ?\
(QGM_QUEUE_ADJUST(self),\
(self)->data[(self)->push++] = (_data),\
++(self)->nElements,\
QGM_SUCCESS)\
:\
((self)->data[(self)->push++] = (_data),\
++(self)->nElements,\
QGM_SUCCESS)))
/** POP : Défile un élément.
Si il n'y a plus d'élément, on accèlère la file avec pop = push = 0
ce qui évite un éventuel déplacement inutile, ou le ralentit
Si erreur, tout les bits de la valeur renvoyé sont mis à un,
et QGM_Error = QGM_NOELEMENTQUEUE
Si pas d'erreur, renvoie tout simplement la donnée **/
#define QGM_QUEUE_POP(self)\
((QGM_QUEUE_EMPTY(self) == 0) ?\
((--(self)->nElements == 0) ?\
((self)->unusedData = (self)->data[(self)->pop],\
(self)->push = (self)->pop = 0,\
(self)->unusedData)\
:\
(self)->data[(self)->pop++])\
:\
(QGM_Error = QGM_NOELEMENTQUEUE,\
memset((self)->data, 0xFF, sizeof *(self)->data),\
*(self)->data))
/** CLEAR : Remet a 0 la file **/
#define QGM_QUEUE_CLEAR(self) (self)->nElements = (self)->push = (self)->pop = 0
/** SIZE : Renvoie le nombre d'élément **/
#define QGM_QUEUE_SIZE QGM_SIZE
/** EMPTY : Renvoit un si la file est vide **/
#define QGM_QUEUE_EMPTY QGM_EMPTY
/** QUIT **/
#define QGM_QUEUE_QUIT QGM_QUIT
/*****************************************************************************/
/*********************************** VECTOR **********************************/
/*****************************************************************************/
/** Initialise un vector **/
#define QGM_VECTOR_INIT QGM_INIT
/** PUSH_BACK : équivalent à un empilement **/
#define QGM_VECTOR_PUSH_BACK QGM_STACK_PUSH
/** PUSH_FRONT : Ajoute un élément au début
On déplace toutes les données de 1, et on ajoute la donnée
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_VECTOR_PUSH_FRONT(self, _data)\
(((self)->nElements == (self)->sizeAlloc) ?\
((QGM_RESIZE(self) == QGM_SUCCESS) ?\
(memmove((self)->data + 1,\
(self)->data,\
(self)->nElements++ * sizeof *(self)->data),\
*(self)->data = (_data),\
QGM_SUCCESS)\
:\
(QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))\
:\
(memmove((self)->data + 1,\
(self)->data,\
(self)->nElements++ * sizeof *(self)->data),\
*(self)->data = (_data),\
QGM_SUCCESS))
/** INSERT : Insère un élément
même principe que PUSH_FRONT, sauf que l'on fait en fonction d'une "place"
Si pas d'erreur, retourne QGM_SUCCESS, sinon QGM_BADALLOC **/
#define QGM_VECTOR_INSERT(self, _data, place)\
(((self)->nElements == (self)->sizeAlloc) ?\
((QGM_RESIZE(self) == QGM_SUCCESS) ?\
(memmove((self)->data + place + 1,\
(self)->data + place,\
((self)->nElements++ - place) * sizeof *(self)->data),\
(self)->data[place] = (_data),\
QGM_SUCCESS)\
:\
(QGM_Error = QGM_BADALLOC,\
QGM_BADALLOC))\
:\
(memmove((self)->data + place + 1,\
(self)->data + place,\
((self)->nElements++ - place) * sizeof *(self)->data),\
(self)->data[place] = (_data),\
QGM_SUCCESS))
/** POP_BACK : équivalent à un dépilement
Si erreur, tout les bits de la valeur renvoyé sont mis à un,
et QGM_Error = QGM_NOELEMENTVECTOR
Si pas d'erreur, renvoie tout simplement la donnée **/
#define QGM_VECTOR_POP_BACK(self)\
((QGM_VECTOR_EMPTY(self) == 0) ?\
((self)->data[--(self)->nElements])\
:\
(QGM_Error = QGM_NOELEMENTVECTOR,\
memset((self)->data, 0xFF, sizeof *(self)->data),\
*(self)->data))
/** POP_FRONT : Retire un élément en début de tableau
On récupère la donnée, et on déplace le tableau de 1 case vers le début
Si erreur, tout les bits de la valeur renvoyé sont mis à un,
et QGM_Error = QGM_NOELEMENTVECTOR
Si pas d'erreur, renvoie tout simplement la donnée **/
#define QGM_VECTOR_POP_FRONT(self)\
((QGM_VECTOR_EMPTY(self) == 0) ?\
((self)->unusedData = *(self)->data,\
memmove((self)->data,\
(self)->data + 1,\
--(self)->nElements * sizeof *(self)->data),\
(self)->unusedData)\
:\
(QGM_Error = QGM_NOELEMENTVECTOR,\
memset((self)->data, 0xFF, sizeof *(self)->data),\
*(self)->data))
/** ERASE : Retire un élément
même principe que POP_FRONT, sauf que l'on fait en fonction d'une place
Si erreur, tout les bits de la valeur renvoyé sont mis à un,
et QGM_Error = QGM_NOELEMENTVECTOR
Si pas d'erreur, renvoie tout simplement la donnée **/
#define QGM_VECTOR_ERASE(self, place)\
((QGM_VECTOR_EMPTY(self) == 0) ?\
((self)->unusedData = (self)->data[place],\
memmove((self)->data + place,\
(self)->data + place + 1,\
(--(self)->nElements - place)* sizeof *(self)->data),\
(self)->unusedData)\
:\
(QGM_Error = QGM_NOELEMENTVECTOR,\
memset((self)->data, 0xFF, sizeof *(self)->data),\
*(self)->data))
/** CLEAR : Remet a 0 le vector **/
#define QGM_VECTOR_CLEAR(self) (self)->nElements = 0
/** SIZE : Renvoie le nombre d'élément **/
#define QGM_VECTOR_SIZE QGM_SIZE
/** EMPTY : Renvoit un si le vector est vide **/
#define QGM_VECTOR_EMPTY QGM_EMPTY
/** Détruit un vector **/
#define QGM_VECTOR_QUIT QGM_QUIT
#endif
Et voilà quelques codes exemples.
Vector :
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
int main(void){
QGM_VECTOR(int) vectorInt;
QGM_VECTOR(double) vectorDouble;
int i;
if(QGM_VECTOR_INIT(vectorInt) != QGM_SUCCESS)
return 1;
if(QGM_VECTOR_INIT(vectorDouble) != QGM_SUCCESS){
QGM_VECTOR_QUIT(vectorInt);
return 1;
}
for(i = 0; i < 20; ++i)
if(QGM_VECTOR_PUSH_FRONT(vectorDouble, (double)i / 10.0)
!= QGM_SUCCESS)
goto end;
for(i = 0; i < 20; ++i)
if(QGM_VECTOR_PUSH_BACK(vectorInt, i) != QGM_SUCCESS)
goto end;
/** Affichage du vector double sans rien supprimer **/
printf("Vector Double\n");
for(i = 0; i < 20; ++i)
printf("%.1f ", vectorDouble->data[i]);
printf("Il y a %u elements dans le vectorDouble\n",
QGM_VECTOR_SIZE(vectorDouble));
/** Affichage du vector int en supprimant toutes les données **/
printf("\nVector int\n");
for(i = 0; i < 20; ++i)
printf("%d ", QGM_VECTOR_POP_BACK(vectorInt));
printf("\nLe vector int est il vide ? %d\n",
QGM_VECTOR_EMPTY(vectorInt));
end:
QGM_VECTOR_QUIT(vectorInt);
QGM_VECTOR_QUIT(vectorDouble);
return 0;
}
Pile
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
int main(void){
QGM_STACK(double) stack;
int i;
if(QGM_STACK_INIT(stack) != QGM_SUCCESS) return 1;
for(i = 0; i < 20; ++i)
if(QGM_STACK_PUSH(stack, (double)i / 10.0) != QGM_SUCCESS)
goto end;
printf("Il ya %u elements\n", QGM_STACK_SIZE(stack));
for(i = 0; i < 21; ++i)
printf("%.1f ", QGM_STACK_POP(stack));
printf("\n%s\n", QGM_GetError());
printf("La pile est elle vide?%d", QGM_STACK_EMPTY(stack));
end:
QGM_STACK_QUIT(stack);
return 0;
}
file
#include "QGM/alloc.h"
#include "QGM/error.h"
#include "QGM/data.h"
int main(void){
QGM_QUEUE(double) queue;
int i;
if(QGM_QUEUE_INIT(queue) != QGM_SUCCESS) return 1;
for(i = 0; i < 20; ++i)
if(QGM_QUEUE_PUSH(queue, (double)i / 10.0) != QGM_SUCCESS)
goto end;
printf("il y a %u elements\n", QGM_QUEUE_SIZE(queue));
for(i = 0; i < 18; ++i)
printf("%.1f ", QGM_QUEUE_POP(queue));
printf("\nLa queue est elle vide?%d", QGM_QUEUE_EMPTY(queue));
end:
QGM_QUEUE_QUIT(queue);
return 0;
}
Pour moi ce fut un vrai entraînement :
-Découverte de l'opérateur virgule(approfondissement)
-Découverte de l'utilité des ternaires
-Découverte de la puissance du préprocesseur.
Et un grand progrès sur le langage C.
Merci pour vos conseils (passés, et futur)
Cordialement
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
Alors :
Realloc permet d augmenté la taille de mes tableaux. Memmove me permet de déplacer les données de un cran pour les vector.
; à la place de , déclencherais sûrement une erreur de compilation. De plus, un ; signale la fin d une instruction, hors ce que l on veut justement nous, c est avoir seulement une seule instruction par macro. Histoire de faire par exemple variable = pop(pile);
Realloc a la place d une liste chaine est certainement bien plus optimisé dans le cas de la pile et de la file. Pour le vector, ce sera plus rapide dans le push / pop back et de plus en plus long vers l ajout au début où pres du début. Mais vut que généralement, on s en sert comme ça, ça ne doit pas poser de problème de vitesses d exécution.
Cependant, il est vrai que si l on souhaite utilisé mon vector comme liste chainee, on risque d avoir de gros problème de perf sur l ajout en début, car il faut tout redeplacer de une case.
Cependant, je ne vois pas l intérêt d utilisé une liste chaine. J ai cependant envie de coder, pour mon vector, une macro permettant d ajouter les données en les triant. Mais pas tout de suite, je verrais plus tard.
Mon code permet certe de ne pas utilisé de pointeur void, mais bien plus que cela, permet une utilisation naturel de ces structures de données. Pouvoir récupéré à l intérieur d un appel de fonction une valeur avec pop est par exemple impossible( à moins de renvoyer le pointeur directement ) . Cela permet aussi d écrire push(self, 5). Alors que avec le type void on est obligé de passé l adresse d une variable près initialisée.
Les conteneurs de la SL sont sûrement coder dans le même genre, mais en utilisant des template, et avec du code sûrement plus optimisé que le mien.( bien que je sois fier du mien, je doute être plus rapide que la SL.
Moi aussi c est la première fois que je code en prepo xD
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
En fait c est a la base pour éviter d appelée une eventuele macro realloc dans le cas ou je code une gestion d allocation dynamique. Mais je pense retirer les parenthèse afin d appeler ma propre fonction
http://cpp-rendering.io : Vous trouverez tout ce dont vous avez besoin sur Vulkan / OpenGL et le rendu 3D !
Vector, Stack, Queue en préprocesseur
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.