Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » Les pointeurs

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

Les pointeurs


Nous voilà dans le dernier chapitre de présentation des bases du C++. Accrochez-vous car le niveau monte d'un cran !
Le sujet des pointeurs fait peur à beaucoup de monde et c'est certainement un des chapitres les plus complexes de ce cours. Une fois cet écueil passé, beaucoup de choses vous paraîtront plus simples et plus claires.

Les pointeurs sont utilisés dans tous les programmes C++, même si vous n'en avez pas eu conscience jusque là. Il y en a partout. Pour l'instant, ils étaient cachés et vous n'avez pas eu à en manipuler directement. Cela va changer avec ce chapitre. Nous allons apprendre à gérer très finement ce qui se passe dans la mémoire de l'ordinateur.

C'est un chapitre plutôt difficile, il est donc normal que vous ne compreniez pas tout du premier coup. N'ayez pas peur de le relire une seconde fois dans quelques jours pour vous assurer que vous avez bien tout assimilé !





Chapitre précédent     Sommaire     Chapitre suivant


Une question d'adresse


Est-ce que vous vous rappelez du chapitre sur la mémoire ? Oui, oui, celui qui présentait la notion de variable. Je vous invite à le relire et surtout à vous remémorer les différents schémas.

Je vous avais dit que lorsque l'on déclare une variable, l'ordinateur nous prête une place dans sa mémoire et y accroche une étiquette portant le nom de la variable.
1
2
3
4
5
int main()
{
   int ageUtilisateur(16);
   return 0;
}

On pouvait donc représenter la mémoire utilisée dans ce programme sur le schéma suivant :


C'était simple et beau. Malheureusement, je vous ai un peu menti. Je vous ai simplifié les choses !
Vous commencez à le savoir, dans un ordinateur tout est bien ordonné et rangé de manière logique. Le système des étiquettes dont je vous ai parlé n'est donc pas tout à fait correct.

La mémoire d'un ordinateur est réellement constituée de "cases", là je ne vous ai pas menti. Il y en a même énormément. Plusieurs milliards sur un ordinateur récent ! Il faut donc un système pour que l'ordinateur puisse retrouver les cases dont il a besoin. Chaque "case" possède un numéro unique, son adresse.


Sur le schéma, on voit cette fois toutes les cases de la mémoire avec leur adresse. Notre programme utilise une seule de ces cases, la 53768, pour y stocker sa variable.

On ne peut PAS mettre deux variables dans la même case.


L'important est que chaque variable possède une seule adresse. Et chaque adresse correspond à une seule variable.

L'adresse est donc un deuxième moyen d'accéder à une variable. On peut accéder à la case jaune du schéma par deux chemins différents :

  • On peut passer par son nom (l'étiquette) comme on sait déjà le faire...
  • Mais on peut aussi accéder à la variable grâce à son adresse (son numéro de case).. On pourrait alors dire à l'ordinateur "Affiche moi le contenu de l'adresse 53768" ou encore "Additionne les contenus des adresses 1267 et 91238".

Est-ce que ça vous tente d'essayer ? Vous vous demandez peut-être à quoi ça peut bien servir. Utiliser l'étiquette était un moyen simple et efficace. C'est vrai. Mais nous verrons plus loin que passer par les adresses peut parfois être nécessaire.

Commençons par voir comment connaître l'adresse d'une variable.

Afficher l'adresse



En C++, le symbole pour obtenir l'adresse d'une variable est l?esperluette (&). Si je veux afficher l'adresse de la variable ageUtilisateur, je dois donc écrire &ageUtilisateur. Essayons.
1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
 
int main()
{
   int ageUtilisateur(16);
   cout << "L'adresse est : " << &ageUtilisateur << endl; // Affichage de l'adresse de la variable
   return 0;
}

Chez moi, j'obtiens le résultat suivant :
L'adresse est : 0x22ff1c

Vous aurez certainement un résultat différent. La case peut changer d'une exécution à l'autre du programme. ;)


Même si elle contient des lettres, cette adresse est un nombre. Celui-ci est juste écrit en héxadécimal (en base 16), une autre façon d'écrire les nombres. Les ordinateurs aiment bien travailler dans cette base. Pour information, en base 10, dans notre écriture courante, cette adresse correspond à 2 293 532. Ce n'est pas une information très intéressante cependant.

Ce qui est sûr c'est qu'afficher une adresse est très rarement utile. Souvenez-vous simplement de la notation. L'esperluette veut dire "adresse de". Donc cout << &a; se traduit en français par "Affiche l'adresse de la variable a".

On a déjà utilisé l'esperluette dans ce cours pour tout autre chose : lors de la déclaration d'une référence. C'est le même symbole qui est utilisé pour deux choses différentes.
Attention à ne pas vous tromper !


Voyons maintenant ce que l'on peut faire avec ces adresses.


Les pointeurs


Les adresses sont des nombres. Vous connaissez plusieurs types permettant de stocker des nombres : int, unsigned int, double. Peut-on donc stocker une adresse dans une variable ?

La réponse est "oui". C'est possible. Mais pas avec les types que vous connaissez. Il nous faut utiliser un type un peu particulier : le pointeur.

Un pointeur est une variable qui contient l'adresse d'une autre variable.

Retenez bien cette phrase. Elle peut vous sauver la vie dans les moments les plus difficiles de ce chapitre.

Déclarer un pointeur



Pour déclarer un pointeur, il faut, comme pour les variables, deux choses :

  • Un type
  • Un nom

Pour le nom, il n'y a rien de particulier à signaler. Les mêmes règles que pour les variables s'appliquent. Ouf !
Le type d'un pointeur a une petite subtilité. Il faut indiquer quel est le type de variable dont on veut stocker l'adresse et ajouter une étoile ([code=cpp]*[//code]). o_O Je crois qu'un exemple sera plus simple.
1
int *pointeur;

Ce code déclare un pointeur qui peut contenir l'adresse d'une variable de type int.

On peut également écrire int* pointeur (l'étoile collée au mot int).
Cette notation a un léger désavantage, c'est qu'on ne peut pas déclarer plusieurs pointeurs sur la même ligne comme ceci : int* pointeur1, pointeur2, pointeur3;. En faisant ça, seul pointeur1 sera un pointeur, les deux autres variables seront des entiers tout à fait standards.

On peut bien sûr faire ça pour n'importe quel type :
1
2
3
4
5
6
7
8
9
double *pointeurA; //Un pointeur qui peut contenir l'adresse d'un nombre a virgule
 
unsigned int *pointeurB; //Un pointeur qui peut contenir l'adresse d'un nombre entier positif
 
string *pointeurC; //Un pointeur qui peut contenir l'adresse d'une chane de caractres
 
vector<int> *pointeurD; //Un pointeur qui peut contenir l'adresse d'un tableau dynamique de nombres entiers
 
int const *pointeurE; //Un pointeur qui peut contenir l'adresse d'un nombre entier constant

Pour le moment, ces pointeurs ne contiennent aucune adresse connue. C'est une situation très dangereuse. Si vous essayez d'utiliser le pointeur, vous ne savez pas quelle case de la mémoire vous manipulez. Ca peut être n'importe laquelle, par exemple la case qui contient votre mot de passe Windows ou la case qui contient l'heure actuelle. J'imagine que vous vous rendez compte des conséquences que peut avoir une mauvaise manipulation des pointeurs. Il ne faut donc JAMAIS déclarer un pointeur sans lui donner d'adresse.

Il faut donc toujours déclarer un pointeur en lui donnant la valeur 0 pour être tranquille :
1
2
3
4
5
6
int *pointeur(0);
double *pointeurA(0);
unsigned int *pointeurB(0);
string *pointeurC(0);
vector<int> *pointeurD(0);
int const *pointeurE(0);

Vous l'avez peut-être remarqué sur mon schéma un peu plus tôt, la première case de la mémoire avait l'adresse 1. En effet, l'adresse 0 n'existe pas.
Lorsque vous créez un pointeur contenant l'adresse 0, cela signifie qu'il ne contient l'adresse d'aucune case.

Je me répète, mais c'est très important : déclarez toujours vos pointeurs en les initialisant à l'adresse 0.


Stocker une adresse



Maintenant qu'on a la variable, il n'y a plus qu'à mettre une valeur dedans. Vous savez déjà comment obtenir l'adresse d'une variable (rappelez-vous du &). Allons-y !
1
2
3
4
5
6
7
8
9
int main()
{
   int ageUtilisateur(16);    //Une variable de type int.
   int *ptr(0);               //Un pointeur pouvant contenir l'adresse d'un nombre entier.
 
   ptr = &ageUtilisateur;  //On met l'adresse de 'ageUtilisateur' dans le pointeur 'ptr'.
 
   return 0;
}

La ligne 6 est celle qui nous intéresse. Elle écrit l'adresse de la variable ageUtilisateur dans le pointeur ptr. On dit alors que le pointeur ptr pointe sur ageUtilisateur.

Voyons comment tout cela se déroule dans la mémoire avec un ... schéma ! lol


On retrouve quelques éléments familiers. La mémoire avec sa grille de cases et la variable ageUtilisateur dans la case n°53768.
La nouveauté est bien sûr le pointeur. Dans la case mémoire n°14566, il y a une variable nommée ptr qui a pour valeur l'adresse 53768, c'est-à-dire l'adresse de la variable ageUtilisateur.

Voilà. Vous savez tout ou presque. Cela peut sembler absurde pour le moment ("Pourquoi stocker l'adresse d'une variable dans une autre case ?"), mais faites-moi confiance les choses vont progressivement s'éclairer pour vous.
Si vous avez compris le schéma précédent, alors vous pouvez vous attaquer aux programmes les plus complexes.

Afficher l'adresse



Comme pour toutes les variables, on peut afficher le contenu d'un pointeur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;
 
int main()
{
   int ageUtilisateur(16);
   int *ptr(0);  
 
   ptr = &ageUtilisateur;
 
   cout << "L'adresse de 'ageUtilisateur' est : " << &ageUtilisateur << endl;
   cout << "La valeur de pointeur est : " << ptr << endl;    
 
   return 0;
}

Ce qui donne :
L'adresse de 'ageUtilisateur' est : 0x2ff18
La valeur de pointeur est : 0x2ff18

La valeur du pointeur est donc bien l'adresse de la variable pointée. On a bien réussi à stocker une adresse ! :)

Accéder à la valeur pointée



Vous vous souvenez du but des pointeurs ? Accéder à une variable sans passer par son nom. Voici comment faire. Il faut utiliser l'étoile (*) sur le pointeur pour afficher la valeur de la variable pointée.
1
2
3
4
5
6
7
8
9
10
11
int main()
{
   int ageUtilisateur(16);
   int *ptr(0);  
 
   ptr= &ageUtilisateur;

   cout << "La valeur est :  " << *ptr << endl;  
 
   return 0;
}

En faisant cout << *ptr, le programme va effectuer les étapes suivantes :

  • Aller dans la case mémoire nommée ptr.
  • Lire la valeur enregistrée.
  • "Suivre la flèche" pour aller à l'adresse pointée.
  • Lire la valeur stockée dans la case.
  • Afficher cette valeur. Ici, ce sera bien sûr 16 qui sera affiché.

En utilisant l'étoile, on accède à la valeur de la variable pointée. C'est ce qui s'appelle déréférencer un pointeur.
Voici donc un deuxième moyen d'accéder à la valeur de ageUtilisateur.

Mais, à quoi cela sert-il ? o_O


Je suis sûr que vous vous êtes retenu de poser la question avant. :) C'est vrai que ça a l'air assez inutile. Eh bien, je ne peux pas vous répondre rapidement pour le moment. :(
Il va falloir lire la fin de ce chapitre pour tout savoir.

Récapitulatif de la notation



Je suis d'accord avec vous, la notation est compliquée. L'étoile a deux significations différentes et on utilise l'esperluette alors qu'elle est déjà utilisée pour les références... Ce n'est pas ma faute ! Si vous voulez vous plaindre, il faut voir du côté des concepteurs du langage. C'est eux les responsables de ce charabia.
Nous, on ne peut que faire avec. Essayons donc de récapituler le tout.

Pour une variable int nombre :

  • nombre permet d'accéder à la valeur de la variable.
  • &nombre permet d'accéder à l'adresse de la variable.

Sur un pointeur int *pointeur :

  • pointeur permet d'accéder à la valeur du pointeur, c'est-à-dire à l'adresse de la variable pointée.
  • *pointeur permet d'accéder à la valeur de la variable pointée.

C'est ce qu'il faut retenir de cette sous-partie. Je vous invite à tester tout ça chez vous pour bien vérifier que vous avez compris comment afficher une adresse, comment utiliser un pointeur, etc.

"C'est en forgeant qu'on devient forgeron" dit le dicton, eh bien c'est en programmant avec des pointeurs que l'on devient programmeur. Il faut impérativement s'entraîner pour bien comprendre. Les meilleurs sont tous passés par là et je peux vous assurer qu'ils ont aussi souffert en découvrant les pointeurs. Si vous ressentez une petite douleur dans la tête, prenez un cachet d'aspirine, faites une pause, puis relisez ce que vous venez de lire encore et encore. Aidez-vous en particulier des schémas ! ;)


L'allocation dynamique


Vous vouliez savoir à quoi servent les pointeurs ? Vous êtes sûr ? Bon, alors je vous montre une première utilisation.

La gestion automatique de la mémoire



Dans notre tout premier chapitre sur les variables, je vous avais expliqué que lors de la déclaration d'une variable, le programme effectuait deux étapes :

  • Il demande à l'ordinateur de lui fournir une zone dans la mémoire. En termes techniques, on parle d'allocation de la mémoire.
  • Il remplit cette case avec la valeur fournie. On parle alors d'initialisation de la variable.

Tout cela est entièrement automatique, le programme se débrouille tout seul. De même lorsque l'on arrive à la fin d'une fonction, le programme rend la mémoire utilisée à l'ordinateur. C'est ce qu'on appelle la libération de la mémoire. C'est à nouveau automatique. Nous n'avons jamais dû dire à l'ordinateur : "Tiens reprends cette case mémoire, je n'en ai plus besoin."

Tout ceci se faisait automatiquement. Nous allons maintenant apprendre à le faire manuellement, et pour cela... vous vous doutez sûrement qu'on va utiliser les pointeurs.

Allouer un espace mémoire



Pour demander manuellement une case dans la mémoire, il faut utiliser l'opérateur new.
new va demander une case à l'ordinateur et renvoyer un pointeur pointant vers cette case.
1
2
int *pointeur(0);
pointeur = new int;

La deuxième ligne demande une case mémoire pouvant stocker un entier et l'adresse de cette case est stockée dans le pointeur. Le mieux est encore de prendre un petit schéma.


Ce schéma est très similaire au précédent. Il y a deux cases mémoires utilisées :

  • La case 14563 qui contient une variable de type int non-initialisée.
  • La case 53771 qui contient un pointeur pointant sur la variable.

Rien de neuf. Mais, la chose importante, c'est que la variable à la case 14563 n'a pas d'étiquette. Le seul moyen d'y accéder est donc de passer par le pointeur.

Si vous changez la valeur du pointeur, vous perdez le seul moyen d'accès à la case mémoire. Vous ne pourrez donc plus l'utiliser ni la supprimer ! Elle sera définitivement perdue mais elle continuera à prendre de la place. C'est ce qu'on appelle une fuite de mémoire.
Il faut donc faire très attention !


Une fois allouée manuellement, la variable s'utilise comme n'importe quelle autre. On doit juste se rappeler qu'il faut y accéder par le pointeur en le déréférençant.
1
2
3
4
int *pointeur(0);
pointeur = new int;
 
*pointeur = 2; //On accde la case mmoire pour en modifier la valeur

La case sans étiquette est maintenant remplie. La mémoire est donc dans l'état suivant :


A part son accès un peu spécial (via *pointeur), nous avons donc une variable en tout point semblable à une autre.

Il nous faut maintenant rendre la mémoire que l'ordinateur nous a gentiment prêtée.

Libérer la mémoire


Une fois que l'on a plus besoin de la case mémoire, il faut la rendre à l'ordinateur. Cela se fait via l'opérateur delete.
1
2
3
4
int *pointeur(0);
pointeur = new int;
 
delete pointeur;    //On libre la case mmoire

La case est alors rendue à l'ordinateur qui pourra l'utiliser pour autre chose. Le pointeur, lui, existe toujours. Et il pointe toujours sur la case mais vous n'avez plus le droit de l'utiliser.


L'image est très parlante. Si l'on suit la flèche, on arrive sur une case qui ne nous appartient pas. Il faut donc impérativement empêcher ça. Imaginez que cette case soit soudainement utilisée par un autre programme ! Vous risqueriez de modifier les variables de cet autre programme.
Il est donc essentiel de supprimer cette "flèche" en mettant le pointeur à l'adresse 0 après avoir utilisé delete. Ne pas le faire est une source très courante de plantage des programmes.
1
2
3
4
5
int *pointeur(0);
pointeur = new int;
 
delete pointeur;    //On libre la case mmoire
pointeur = 0;       //On indique que le pointeur ne pointe vers plus rien

N'oubliez pas de libérer la mémoire. Si vous ne le faites pas, votre programme risque d'utiliser de plus en plus de mémoire jusqu'au moment où il n'y aura plus aucune case disponible ! Votre programme va alors planter.


Un exemple complet



Terminons cette section avec un exemple complet : un programme qui demande son âge à l'utilisateur et qui l'affiche en utilisant un pointeur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
int main()
{
    int* pointeur(0);
    pointeur = new int;
 
    cout << "Quel est votre age ? ";
    cin >> *pointeur; //On crit dans la case mmoire pointe par le pointeur 'pointeur'
 
    cout << "Vous avez " << *pointeur << " ans." << endl; //On utilise nouveau *pointeur
 
    delete pointeur;   //Ne pas oublier de librer la mmoire
    pointeur = 0;      //Et de faire pointer le pointeur vers rien
 
    return 0;
}

Ce programme est plus compliqué que sa version sans allocation dynamique ! C'est vrai. Mais on a le contrôle complet sur l'allocation et la libération de la mémoire.

Dans la plupart des cas, ce n'est pas utile de le faire. Mais vous verrez plus tard que, pour faire des fenêtres, la bibliothèque Qt utilise beaucoup new et delete. On peut ainsi maîtriser précisément quand une fenêtre est ouverte et quand on la referme par exemple.


Quand utiliser des pointeurs


Je vous avais promis des explications sur quand utiliser des pointeurs. Les voici. ^^

Il y a en réalité trois cas d'application :

  • Gérer soi-même le moment de la création et de la destruction des cases mémoire.
  • Partager une variable dans plusieurs morceaux du code.
  • Sélectionner une valeur parmi plusieurs options.

Si vous n'êtes pas dans un de ces trois cas, c'est très certainement que vous n'avez pas besoin des pointeurs.

Vous connaissez déjà le premier de ces trois cas. Concentrons nous sur les deux autres.

Partager une variable



Pour l'instant, je ne peux pas vous donner un code source complet pour ce cas d'utilisation. Ou alors, il ne sera pas intéressant du tout. Quand vous aurez quelques notions de programmation orientée objet, vous aurez de vrais exemples.

En attendant, je vous propose un exemple plus ... visuel. :)

Vous avez déjà joué à un jeu de stratégie ? J'imagine que oui, prenons un exemple tiré d'un jeu de ce genre. Voici une image issue du fameux Warcraft III.


Programmer un tel jeu est bien sûr très compliqué. Mais on peut quand même réfléchir à certains des mécanismes utilisés. Sur l'image, on voit des humains en rouge attaquer des orcs en bleu. Chaque personnage a une cible précise. Par exemple, le fusilier au milieu de l'écran semble tirer sur le gros personnage bleu qui tient une hache.

Nous verrons dans la suite de ce cours comment créer des objets, c'est-à-dire des variables plus évoluées. Par exemple une variable de type "personnage", de type "orc" ou encore de type "batiment". Bref, chaque élément du jeu pourra être modélisé en C++ par un objet.

Comment feriez-vous pour indiquer, en C++, la cible du personnage rouge ? o_O
Bien sûr, vous ne savez pas encore comment faire en détail, mais vous avez peut-être une petite idée. Rappelez-vous du titre de ce chapitre. :)
Oui oui, un pointeur est une bonne solution ! Chaque personnage possède un pointeur qui pointe vers sa cible. Il a ainsi un moyen de savoir qui viser et attaquer. On pourrait par exemple écrire quelque chose comme :
1
Personnage *cible; //Un pointeur qui pointe sur un autre personnage

Quand il n'y a pas de combat en cours, le pointeur pointe vers l'adresse 0, il n'a pas de cible. Quand le combat est engagé, le pointeur pointe vers un ennemi. Et finalement, quand cet ennemi meurt, on déplace le pointeur vers une autre adresse, c'est-à-dire vers un autre personnage.

Le pointeur est donc réellement utilisé ici comme une flèche reliant un personnage à son ennemi.

Nous verrons comment écrire du code comme cela dans la suite, je crois même qu'un des TP sera de créer un mini-jeu de ce genre. Mais chut, c'est pour plus tard. :)

Choisir parmi plusieurs éléments



Le troisième et dernier cas permet de faire évoluer un programme en fonction des choix de l'utilisateur.
Prenons le cas d'un QCM. Nous allons demander à l'utilisateur de choisir parmi trois réponses possibles à une question. Une fois qu'il aura choisi, nous allons utiliser un pointeur pour indiquer quelle réponse a été choisie.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <string>
using namespace std;
 
int main()
{
   string reponseA, reponseB, reponseC;
   reponseA = "La peur des jeux de loterie";
   reponseB = "La peur du noir";
   reponseC = "La peur des vendredis treize";
 
   cout << "Qu'est-ce que la 'kenophobie' ? " << endl; //On pose la question
   cout << "A) " << reponseA << endl; //Et on affiche les trois propositions
   cout << "B) " << reponseB << endl;
   cout << "C) " << reponseC << endl;
 
   char reponse;
   cout << "Votre reponse (A,B ou C) : ";
   cin >> reponse;                         //On rcupre la rponse de l'utilisateur
 
   string *reponseUtilisateur(0);          //Un pointeur qui pointera sur la rponse choisie
   switch(reponse)
   {
   case 'A':
       reponseUtilisateur = &reponseA;  //On dplace le pointeur sur la rponse choisie
       break;
   case 'B':
       reponseUtilisateur = &reponseB;
       break;
   case 'C':
       reponseUtilisateur = &reponseC;
       break;
   }
 
   //On peut alors utiliser le pointeur pour afficher la rponse choisie
   cout << "Vous avez choisi la reponse : " << *reponseUtilisateur << endl;
 
   return 0;
}

Une fois que le pointeur a été déplacé (dans le switch) on peut l'utiliser comme moyen d'accès à la réponse de l'utilisateur. On a ainsi un moyen d'atteindre directement cette variable sans devoir refaire le test à chaque fois qu'on en a besoin.
C'est une variable qui contient une valeur que l'on ne pouvait pas connaître avant (puisqu'elle dépend de ce que l'utilisateur a entré).

C'est certainement le cas d'utilisation le plus rare des trois, mais il arrive parfois qu'on soit dans cette situation. Il sera alors temps de vous rappeler des pointeurs ! ;)




Nous en avons fini avec les bases du C++ !

Vous n'avez peut-être pas tout compris dans les moindres détails, ce n'est pas grave ! C'est surtout en pratiquant que l'on apprend et je vous assure que de nombreuses personnes (dont moi :p ) ont eu besoin de beaucoup de temps pour bien tout saisir. Certaines des notions présentées jusque-là vont réapparaître plus loin dans le cours, ce sera alors le moment de venir lire ce qu'il vous manquait.

Vous êtes prêt ? Alors attaquons le monde magique de la programmation orientée objet, le coeur du C++...



Chapitre précédent     Sommaire     Chapitre suivant



Distribué et adapté par David
Consulté 6877 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 240 jours 11 heures 35 minutes
Espace libre : 1570 Mo
Dernière sauvegarde : inconnue
Taille de la sauvegarde : 1112 Mo


5357142 pages ont été consultées sur le site !
Dont 2679 pages pendant les 24 dernières heures.

Page générée en 0.578 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.