Random Thoughts And Coding
Reinventing the wheel : C++ Linked List
Hasard du web ou magie des mots clés, j’ai l’impression en ce moment de ne lire que des posts ou les auteurs se demandent si les programmeurs de nos jours ne sont pas incompétents et paresseux comparés à leurs prédécesseurs…
La question du lien précédent a généré un nombre impressionnant de réponses en une demi journée.
Il y a aussi cet article de Joel Spolsky, co-fondateur de Stack Overflow, qui critique la simplification des cours d’IT : selon lui, en enseignant le Java plutôt que le C, et en évitant les vrais problèmes : pointeurs et récursion, la sélection ne se fait plus, et les programmeurs formés sont mauvais.
En tant que recruteur il écrit :
I used to be able to tell the smart kids because they could rip through a recursive algorithm in seconds, or implement linked-list manipulation functions using pointers as fast as they could write on the whiteboard. But with a JavaSchool Grad, I can’t tell if they’re struggling with these problems because they are undereducated or if they’re struggling with these problems because they don’t actually have that special part of the brain that they’re going to need to do great programming work.
Et c’est ce qui m’a interpelé : Je ne programme quasiment qu’en C# – langage (presque) sans pointeurs -, et même si je connais le C ou le C++ de façon théorique (en plus des cours de SUPINFO), je n’ai pas beaucoup pris le temps de faire des choses un peu poussées avec, n’y voyant pas d’intérêt.
Même si je ne le fais pas aussi vite que j’écrirais sur un tableau blanc, suis je capable d’implémenter une liste chainée et les fonctions qui vont avec ?
Do I have that special part of the brain?
Entrons dans le vif
J’ai commencé naïvement, avec une seule classe comprenant à la fois la structure (un int et deux int*), et les fonctions de manipulation, en ayant en tête une liste doublement chainée d’int.
Je me suis vite rendu compte que je ne me servais jamais des deux liens, je suis donc passé à une liste simplement chainée.
Comme j’étais arrivé à un résultat utilisable rapidement, j’en ai profité pour apprendre quelques petites choses sur les templates C++, en rendant ma liste générique o/.
J’ai aussi fini par séparer les éléments de la liste, de la liste elle même.
J’avais en tête les List de C#, accessibles via un indexer, mais le C++ a eu raison de moi sur ce point: pas moyen de faire marcher mon opérateur [],
finalement le principe reste le même, mais avec une fonction Get(int index)…
Et voici venu le moment de vous coller mes 219 lignes de C++ ![]()
(merci WordPress de me supprimer tous mes sauts de ligne…)
(codées avec Visual studio bien sur, d’ou le #pragma once…)
#pragma once
#include <iostream>
template <class T>
//Linked List Item, the user doesn't need to use this class
class LListItem
{
public:
LListItem(T value);
LListItem(LListItem<T>* next, T value);
~LListItem(void);
bool HasNext(void);
T value;
LListItem<T>* next;
protected:
void Init(LListItem<T>* next, T value);
};
/******************************************************************************/
template <class T>
//Linked List object
class LList
{
public:
LList(void);
~LList(void);
void Add(T value);
void InsertAfter(int index, T value);
void RemoveAt(int index);
void Clear(void);
T Get(int index);
int Count(void);
static const int ERROR_OUT_OF_BOUNDS = 0x100;
protected:
LListItem<T>* first;
LListItem<T>* last;
int count;
};
/******************************************************************************/
template <class T>
LListItem<T>::LListItem(T value)
{
Init(NULL, value);
}
template <class T>
LListItem<T>::LListItem(LListItem<T>* next, T value)
{
Init(next, value);
}
template <class T>
void LListItem<T>::Init(LListItem<T>* next, T value)
{
this->next = next;
this->value = value;
}
template <class T>
bool LListItem<T>::HasNext()
{
return this->next != NULL;
}
template <class T>
LListItem<T>::~LListItem(void) {}
/******************************************************************************/
template <class T>
LList<T>::LList(void)
{
this->first = NULL;
this->last = NULL;
this->count = 0;
}
template <class T>
LList<T>::~LList(void)
{
this->Clear();
}
template <class T>
int LList<T>::Count()
{
return this->count;
}
template <class T>
void LList<T>::Add(T value)
{
if(this->first == NULL)
{
this->first = new LListItem<T>(value);
this->last = this->first;
this->count = 1;
}
else
{
LListItem<T>* newLast = new LListItem<T>(NULL, value);
this->last->next = newLast;
this->last = newLast;
this->count++;
}
}
template <class T>
void LList<T>::InsertAfter(int index, T value)
{
if(this->first != NULL && index >= 0)
{
LListItem<T>* iterator = this->first;
int i = 0;
while(i != index)
{
if(!iterator->HasNext())
{throw LList::ERROR_OUT_OF_BOUNDS;}
iterator = iterator->next;
i++;
}
LListItem<T>* afterIterator = iterator->next;
iterator->next = new LListItem<T>(afterIterator, value);
if(this->last == iterator)
{
this->last = iterator->next;
}
this->count++;
}
else if(this->first != NULL && index == -1)
{
//special case, we insert at index 0
this->first = new LListItem<T>(this->first, value);
this->count++;
}
else
{
throw LList::ERROR_OUT_OF_BOUNDS;
}
}
template <class T>
void LList<T>::Clear()
{
if(this->first != NULL)
{
LListItem<T>* iterator = this->first;
LListItem<T>* temp = NULL;
do
{
//keep a reference on the next, and delete the current
temp = iterator->next;
delete iterator;
iterator = temp;
} while(iterator != NULL);
this->count = 0;
this->first = NULL;
this->last = NULL;
}
}
template <class T>
void LList<T>::RemoveAt(int index)
{
if(this->first != NULL && index >= 0)
{
LListItem<T>* iterator = this->first;
LListItem<T>* prev = NULL;
int i = 0;
while(i != index)
{
if(!iterator->HasNext())
{throw LList::ERROR_OUT_OF_BOUNDS;}
prev = iterator;
iterator = iterator->next;
i++;
}
if(prev != NULL)
{
if(iterator->next != NULL)
{
prev->next = iterator->next;
}
else
{
//we are going to delete the last item
prev->next = NULL;
this->last = prev;
}
}
else
{
//we are going to delete the first item
if(iterator->next != NULL)
{
this->first = iterator->next;
}
else
{
//there are only one item
this->first = NULL;
}
}
delete iterator;
this->count--;
}
else
{
throw LList::ERROR_OUT_OF_BOUNDS;
}
}
template <class T>
T LList<T>::Get(int index)
{
if(this->first != NULL && index >= 0)
{
LListItem<T>* iterator = this->first;
int i = 0;
while(i != index)
{
if(!iterator->HasNext())
{throw LList::ERROR_OUT_OF_BOUNDS;}
iterator = iterator->next;
i++;
}
return iterator->value;
}
else
{
throw LList::ERROR_OUT_OF_BOUNDS;
}
}
/******************************************************************************/
Je n’ai bien sur googlé aucune source similaire avant de commencer. Quel challenge sinon ?
J’ai quand même eu besoin de faire quelques recherches sur la syntaxe du C++, pour les templates entre autres…
Conclusion
une soirée pour coder une liste chainée, en en ayant qu’une connaissance théorique. Je me sens un peu rassuré
J’ai appris à mes dépends que le chainage de constructeur n’existait pas en C++,
j’ai appris à utiliser les templates en C++,
j’ai appris que lors de l’utilisation de templates, on ne pouvait pas séparer le .h du .cpp …
Je me suis souvenu pourquoi je faisais du C#…
Un petit bonus pour les développeurs : Test Yourself. (j’en ai réussi deux sur trois facilement.)
Codé sur une terrasse ensoleillée, en écoutant Access To Arasaka.
| Print article | This entry was posted by Yaurthek on July 3, 2011 at 10:00, and is filed under Coding, Personal, Projects. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |









about 1 year ago
Il y a plus grave que de ne pas comprendre une liste doublement chaînée en bas niveau : Ne pas savoir la POO en bas niveau (environ 90% des développeurs ?). Ce qui est grave puisque la POO est normalement seulement un paradigme et non un langage…
about 1 year ago
Article sympa, le fond est très vrai.
Maintenant, certaines problématiques ne sont pas forcément toutes vrai. A t-on réellement besoin de savoir coder une collection en assembleur pour pouvoir l’utiliser correctement ? Je n’en suis pas sur.
A chacun son métier : Ceux qui développent les briques de base (Genre Framework .NET) se doivent de maîtriser les mécaniques de bas niveau. Après, je ne suis pas sur que la connaissance du bas niveau implique un bon niveau algorithmique et vice versa.
Même si j’ai lutté pour comprendre les trois lignes de l’algo des tours de Hanoï, je ne vois pas en quoi pisser du C depuis que je fais de l’info m’aurait aidé à mieux comprendre :p
Sinon, dernière petite chose en passant, les pointeurs existent en C#
http://msdn.microsoft.com/fr-fr/library/t2yzs44b(v=vs.80).aspx
about 1 year ago
On n’a pas besoin de savoir comment est implémentée une collection pour s’en servir, c’est certain, mais je pense que ça aide si on veut coder le mieux possible.
Et ce n’est pas parce que ce n’est pas mon domaine de prédilection que ça ne m’intéresse pas, au contraire
J’ai beau développer principalement en C#, j’aime aussi beaucoup comprendre ce qui est près de la machine.
De plus dans le cas présent, je considère un peu cet exercice comme un “classique”, que tout développeur devrait être capable de refaire.
Mais je suis d’accord, il ne faut pas mélanger connaissances et niveau en algorithmie.
(Je sais que les pointeurs et le code unsafe existent en C#,
mais on les utilise pratiquement jamais. Ils ne sont utiles que dans de très rare cas d’interopérabilité avec du code natif, ou quand une optimisation poussée est nécessaire (traitement d’image, …))
(Mais j’ai nuancé ma phrase ^^)
about 1 year ago
Ça me redonne envie de coder toussa.
Je le ferai dans un peu plus d’une année quand je serai enfin dans une école informatique :3