Aussi, le nom de la fonction est mauvais: La sortie est peut-être les N premiers nombres naturels, mais ce n'est certainement pas des nombres premiers.
SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.
"Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
Apparently everyone. -- Raymond Chen.
Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.
C'est typiquement le genre d'exemples qui 1) sont plus du C que du C++ et 2) qui donnent l'impression qu'on ne peut rien faire en C++ en moins de deux-cents lignes de code. En C++ moderne on écrirait:#include "stdafx.h"
#include <iostream>
#include <string>
int main()
{
char prenom[] = "Jojo";
char initiales[] = "J";
char nom[] = "Lapatate";
char nomcomplet[50];
int offset = 0;
strcpy_s(nomcomplet, prenom);
offset = strlen(prenom);
strcpy_s(nomcomplet + offset, " ");
offset++;
strcpy_s(nomcomplet + offset, initiales);
offset += strlen(initiales);
strcpy_s(nomcomplet + offset, ". ");
offset += 2;
strcpy_s(nomcomplet + offset, nom);
std::cout << "Vous vous appelez: " << nomcomplet << std::endl;
return 0;
}
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 #include <iostream> #include <string> int main() { std::string prenom("Jojo"), initiale("J"), nom("Lapatate"); auto nom_complet = prenom + " " + initiale + ". " + nom; std::cout << "Votre nom est: " << nom_complet << std::endl; }
Pour un tâche où la performance importe peu, il faut choisir le code le plus clair et le plus court.
Fais-moi signe quand tu veux passer à une autre leçon et d'autres exercices (de C++ vraiment moderne)
Pfff... Le pire c'est que j'avais à peu près ca en tête pour faire l'exo à la différence de "string" plutôt que "auto" dans la déclaration de la variable "nomcomplet"
J'me disais bien que c'était un code bien trop complexe pour un effet tout bête. Le livre que j'ai suivi c'est: http://cpp.developpez.com/livres/ind...us#L2744025461 .....en édition mise à jour pour le c++11.
Apparemment, les Français sont pas très bon pour faire des bouquin sur le C++ :/
Stend, je suis actuellement en train de bucher sur la suite des exos que tu m'a déjà donné ! Attendons que je finisse ceux là ^^
Ok tu me diras pour les exos, comme j'ai un peu de temps aujourd'hui je t'écris la leçon 2 de Stendhal's modern C++, le tutoriel en français qui ne pense pas qu'apprendre le C++ c'est jouer avec des bits et des pointeurs sans recourir aux bibliothèques standard!
Je ne fais qu'un exposé rapide. S'il y des choses que tu ne comprends pas, ce n'est pas grave! N'hésite pas à poser des questions. Les exercices viendront un peu plus tard quand tu auras terminé ceux de la leçon précédente (ou si tu en as marre). On pourra revenir plus en profondeur sur des points si tu veux.
Big leçon n°2: Itérateurs, voitures et fonctions lambda
Comme tu l'as justement remarqué dans le code:
on peut utiliser auto pour demander au compilateur de déduire le type de la variable qu'on déclare. C'est vrai depuis C++11 (donc depuis 2011).
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part auto nom_complet = prenom + " " + initiale + ". " + nom;
En l'occurrence, le compilateur sait que l'opérateur + dont une opérande est: std::string et l'autre: const char* retourne une std::string.
Utiliser auto est recommandé pour trois raisons:
- cela évite de taper des types particulièrement compliqués, comme std::unordered_map<std::maxint_t, std::vector<std::pair<std::fast_uint16_t, std::shared_ptr<my_own_hash_map_type>>>>::iterator
- si on modifie le type à un endroit, le compilateur infère le type modifié partout ailleurs. Par exemple, si je change le type de nom et prenom pour en faire une std::wstring, le type de nom_complet est modifié aussi, par le compilateur. Je ne risque pas d'oublier de le faire avec peut-être une conversion inopportune ou une erreur de compilation à la clé
- il arrive qu'on ne connaisse pas le type de la variable qu'on déclare. C'est le cas pour les fonctions lambda: il n'y a pas deux lambdas du même type, un type spécifique est créé chaque fois par le compilateur. Mais qu'est-ce qu'une fonction lambda?
Les fonctions lambda
Tu sais ce que c'est qu'une fonction. Prenons un exemple:
Que peut-on faire de cette fonction? C'est assez limité: elle doit être déclarée dans l'espace global (pas à l'intérieur d'une autre fonction, par exemple), ce qui veut dire qu'elle ne peut pas non plus être générée par une autre fonction. On peut essentiellement l'appeler et prendre son adresse.
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 auto fois_deux(int i) { // auto peut aussi inférer le type de retour d'une fonction, à certaines conditions return i*2; }
- l'appeler:
- prendre son adresse:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part auto deux_x = fois_deux(x);
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 auto fp = fois_deux; auto deux_x = fp(x); // on l'appelle via le pointeur sur son adresse
Une fonction lambda c'est une fonction qui peut être utilisée comme une variable ordinaire. Voici un exemple, à méditer un bon moment parce-que, mine de rien, ça retourne un peu le cerveau:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 auto fois_x(int x) { // une fonction qui retourne une fonction lambda return [=](int y) { return y*x; }; // c'est la fonction lambda. Elle n'a pas de nom } int main() { auto fois_10 = fois_x(10); // fn contient une fonction lambda créée par fois_x(10), qui multiplie par 10 son argument std::cout << fois_10(5); // affiche 5*10 = 50 }
Un petit mot sur la syntaxe des fonctions lambda:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 [=] // entre crochet ce qu'il faut faire du contexte: = pour en faire une copie, & pour prendre en référence et pouvoir le modifier (int x) // l'argument de la lambda, il peut y en avoir plusieurs. Il peut même être déduit dans certains cas { return y*x; } // le corps de la fonction.
Les fonctions lambda sont extrêmement utiles pour tirer parti de la bibliothèque standard. Pour cela, il faut connaître également les itérateurs. Mais que sont les itérateurs?
Les itérateurs
Un itérateur est une abstraction. Elle permet de parcourir des choses très dissemblables, comme un vecteur, une liste, un dictionnaire, un fichier... comme s'ils étaient semblables. Par exemple:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
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 #include <iostream> #include <string> #include <vector> #include <set> #include <list> int main() { std::string str = "abcdef"; std::vector<int> vec = {1,2,3,4,5}; std::list<double> lst = {6., 7., 8., 9.}; std::set<char> ensemble = { 'g', 'h', 'i'}; for (auto it = std::begin(str); it != std::end(str); ++it) { // it est un itérateur std::cout << *it << ' '; } for (auto it = std::begin(vec); it != std::end(vec); ++it) { // la boucle est identique std::cout << *it << ' '; } for (auto it = std::begin(lst); it != std::end(lst); ++it) { // idem std::cout << *it << ' '; } for (auto it = std::begin(ensemble); it != std::end(ensemble); ++it) { // idem std::cout << *it << ' '; } }
Un itérateur pointe sur un élément d'une séquence. Pour connaître l'élément pointé, on utilise l'opérateur * . Par exemple, std::cout << *iterateur;Pour avancer d'un élément, on utilise l'opérateur ++. Par exemple: ++iterateurLes fonctions std::begin et std::end renvoie un itérateur respectivement sur le début et la fin d'une séquence.
Pour reprendre l'exercice 4 du précédent épisode, tu aurais pu écrire:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 void print_vec(const std::vector<int>& vec) { auto it = std::begin(vec); std::cout << '{' << *it++; for ( ; it != std::end(vec); ++it) { std::cout << ", " << *it; } std::cout << '}'; }
NB: l'opérateur ++ peut se placer avant ou après son opérande. Quand il est placé avant, il incrémente son opérande et la renvoie incrémentée:
Quand il est placé après, il copie son opérande, l'incrémente et renvoie la copie non-incrémentée:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 auto x = 0; std::cout << ++x << " -> " << x; // 1 -> 1
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 auto x = 0; std::cout << x++ << " -> " << x; // 0 -> 1
Itérateurs et fonctions lambda
Itérateurs et fonctions lambda permettent de tirer partie de la bibliothèque standard. En particulier des algorithmes qu'elle définit. C'est un des points-clé du C++ moderne de bien utiliser ces algorithmes. Ils sont dans l'en-tête <algorithm>. Prenons en exemple un des plus souvent utiles: std::sort:
std::sort peut prendre un troisième argument pour préciser comment on compare deux éléments. Par défaut, on prend le plus petit élément. Mais on pourrait vouloir trier dans l'ordre inverse. Il faut alors fournir un autre critère de comparaison. On peut utiliser une fonction, un foncteur (on verra plus tard) ou une fonction lambda. L'avantage de la fonction lambda c'est qu'on peut la définir à l'endroit où on l'utilise et qu'on a pas besoin de lui donner un nom:
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::vector<int> v = {9,3,7,3,1}; std::sort(std::begin(v), std::end(v)); print_vec(v); // {1, 3, 3, 7, 9}
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::vector<int> v = {9,3,7,3,1}; std::sort(std::begin(v), std::end(v), [](int a, int b) { return a > b; }); print_vec(v); // {9, 7, 3, 3, 1}
C'est tout pour aujourd'hui!!
N'hésite pas à poser des questions.
Je n'ai rien lu de la conversion ni du message mais dans la lambda passée à std::sort :
- Pas besoin de capture donc autant mettre [] et non [=] (coding style)
- Le 3ème argument passé doit respecter le concept de comparateur établissant un ordre total strict ce que ne fais pas pas >=.
(Si a == b, a >= b et b >= a renvoie true)
Il faut donc utiliser > au lieu de >=.
C'est vraiment un super geste que tu me fais là! Merci Stend, bien que c'est pas facile à faire rentrer dans le cerveau, j'ai de quoi bucher!
J'ai des questions mais on verra après les premiers exos car sinon je vais trop m'embrouiller...
Voici mon programme pour le crible d’Ératosthène:
Il fonctionne bien, maintenant à voir si j'ai pas fait d'erreur ou mal compris la consigne!
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 #include "stdafx.h" #include <iostream> #include <vector> #include <string> #include <random> std::vector<bool> nombresPremiers(int choix) //Fonction appelé dans le main par la création du tableau "tabPremiers" qui permet de differencier les nombres premiers { std::vector<bool> tab(choix); tab[0] = false; tab[1] = false; tab[2] = true; for (int x = 3; x < tab.size(); x++) { tab[x] = true; } for (int a = 2; a < tab.size(); a++) { if (tab[a] == true) { for (int b = a*2; b < tab.size(); b += a) { tab[b] = false; } } } return tab; } int nombreAleatoire(int choix) //Fonction appelé dans le main pour obtenir un nombre aléatoire par rapport à la taille du tableau { std::random_device a; std::default_random_engine b(a()); std::uniform_int_distribution<int> uniform_dist(2, choix); int nombre = uniform_dist(b); return nombre; } void reponseProg(std::vector<bool> tab,std::string choix,int nombreTab) //Fonction qui donne une réponse au joueur par rapport a son choix { if (choix == "o" || choix == "O") { if (tab[nombreTab] == 1) { std::cout << "Bravo! " << nombreTab << " est bien un nombre premier :)" << std::endl << std::endl; } else if (tab[nombreTab] == 0) { std::cout << "Aie, desole! " << nombreTab << " n'est pas un nombre premier :'(" << std::endl << std::endl; } } else if (choix == "n" || choix == "N") { if (tab[nombreTab] == 1) { std::cout << "Aie, desole! " << nombreTab << " est bien un nombre premier :'(" << std::endl << std::endl; } else if (tab[nombreTab] == 0) { std::cout << "Bravo! " << nombreTab << " n'est pas un nombre premier :)" << std::endl << std::endl; } } } int main() { std::vector<bool> tabPremiers(nombresPremiers(256)); bool recommencer(true); std::cout << "Bienvenue dans le jeu du crible d'Eratosthene!\n\nLa regle est simple, je vous donne si un nombre et"; std::cout << " vous me dites si c'est un\nnombre premier ou pas. Repondez simplement par O pour oui ou par N pour non.\n\nC'est parti!" << std::endl << std::endl; do { int nombreTableau; std::string choixQuestion, choixRecommencer; nombreTableau = nombreAleatoire(255); //Appel de la fonction nombreAleatoire pour obtenir un nombre entre 0 et 255 std::cout << "Le nombre " << nombreTableau << " est-il un nombre premier?" << std::endl; std::cin >> choixQuestion; //L'utilisateur répond à la question std::cout << std::endl; reponseProg(tabPremiers, choixQuestion, nombreTableau); //Appel la fonction reponseProg pour donne rune réponse à l'utilisateur par rapport à son choix std::cout << "Voulez vous recommencer une partie? o/n" << std::endl; std::cin >> choixRecommencer; if (choixRecommencer == "n" || choixRecommencer == "N") //Si l'utilisateur répond "n", la partie s'arrête { recommencer = false; } } while (recommencer); return 0; }
J'ai piqué la fonction random sur cppreference car j'ai vu que ce que j'utilisais avant était obsolète (rand de <cstdlib>) et j'avoue avoir recopié bêtement car je ne l'ai pas très bien comprise :/
Très bien! J'ai compilé et testé, il marche "au poil".Voici mon programme pour le crible d’Ératosthène:
Pour la correction, je pense que tu auras des remarques des uns et des autres. Je te donne une solution qui n'est pas meilleure que la tienne pour le crible lui-même. J'ai choisi de ne traiter que les cas où n > 2 car 0 et 1 ne sont pas premiers, tout le monde le sait, mais avec le recul je pense que ta sémantique est plus consistante et évite des complications.
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 std::vector<bool> erathostene(unsigned n) { // un nombre premier est positif, donc j'utilise un entier non-signé, unsigned if (n < 2) return std::vector<bool>(); auto zn = n-2; // on décale de deux rangs à gauche: 0 == 2 std::vector<bool> crible(zn+1, true); // le constructeur à deux arguments: taille et valeur par défaut. Tous les éléments sont true for (unsigned i = 0; i < zn/2+1 ; ++i) { // zn/2 est le dernier à pouvoir être doublé en restant < zn if (crible[i] == true) { for (unsigned j = 2*i+2; j < zn+1; j += (i+2)) { crible[j] = false; } } } return crible; }
Pour l'aspect aléatoire:
Très bien!! c'est comme ça qu'on apprend! Et en recopiant la référence tu as peu de chances de te tromperJ'ai piqué la fonction random sur cppreference car j'ai vu que ce que j'utilisais avant était obsolète (rand de <cstdlib>) et j'avoue avoir recopié bêtement car je ne l'ai pas très bien comprise :/
Pour l'interface utilisateur, quelques remarques:
La signature de ta fonction implique que tu copies les arguments. Lorsque l'argument n'est pas d'un type primitif (int, bool, char, etc.), il peut être de taile importante et long à copier. On utilise alors une référence. Si on ne compte pas modifier l'argument, on utilise une référence constante:
Code : Sélectionner tout - Visualiser dans une fenêtre à part void reponseProg(std::vector<bool> tab,std::string choix,int nombreTab)
A retenir:
Code : Sélectionner tout - Visualiser dans une fenêtre à part void reponseProg(const std::vector<bool>& tab, const std::string& choix,int nombreTab)
Autres remarques:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 void f(std::string s); // réalise une copie de la string passée en argument void f(std::string& s); // référence sur la string qui pourra être modifiée par la fonction void f(const std::string& s); // référence sur la string qui ne pourra pas être modifiée. S'écrit aussi (std::string const&)
Préférer:
Code : Sélectionner tout - Visualiser dans une fenêtre à part if (tab[nombreTab] == 1)
Cela indique plus clairement qu'on a affaire à un bool
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 if (tab[nombreTab] == true) // ou if (tab[nombreTab]) // et de la même façon if (tab[nombreTab] == false) // ou if (!tab[nombreTab])
Sur:
La bonne pratique est d'initialiser immédiatement les variables (surtout celles de type primitif qui n'ont pas de valeur par défaut). D'où:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 int nombreTableau; std::string choixQuestion, choixRecommencer; nombreTableau = nombreAleatoire(255);
Quand tu commenceras à utiliser des exceptions, tu verras que ça peut éviter des gros bugs bien m...iques.
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part auto nombreTableau = nombreAleatoire(255);
Et maintenant que tu t'y connais en auto:
Bon il est un peu tard...
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::vector<bool> tabPremiers(nombresPremiers(256)); // pourquoi pas auto tabPremiers = nombresPremiers(256); ?
Bon courage et bravo, reste à persévérer!
J'ai modifié mon code avec tes recommandations et en effet c'est plus rapide et plus simple à lire!
Super exo, ca m'a fait travailler pas mal de chose! J'en attends des nouveaux alors ^^
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 #include "stdafx.h" #include <iostream> #include <vector> #include <string> #include <random> std::vector<bool> nombresPremiers(int choix) //Fonction appelé dans le main par la création du tableau "tabPremiers" qui permet de differencier les nombres premiers { std::vector<bool> tab(choix,true); tab[0] = false; tab[1] = false; for (int a = 2; a < tab.size(); a++) { if (tab[a] == true) { for (int b = a*2; b < tab.size(); b += a) { tab[b] = false; } } } return tab; } int nombreAleatoire(int choix) //Fonction appelé dans le main pour obtenir un nombre aléatoire par rapport à la taille du tableau { std::random_device a; std::default_random_engine b(a()); std::uniform_int_distribution<int> uniform_dist(2, choix); int nombre = uniform_dist(b); return nombre; } void reponseProg(const std::vector<bool>& tab, const std::string& choix, const int& nombreTab) //Fonction qui donne une réponse au joueur par rapport a son choix { if (choix == "o" || choix == "O") { if (tab[nombreTab] == true) { std::cout << "Bravo! " << nombreTab << " est bien un nombre premier :)" << std::endl << std::endl; } else if (tab[nombreTab] == false) { std::cout << "Aie, desole! " << nombreTab << " n'est pas un nombre premier :'(" << std::endl << std::endl; } } else if (choix == "n" || choix == "N") { if (tab[nombreTab] == true) { std::cout << "Aie, desole! " << nombreTab << " est bien un nombre premier :'(" << std::endl << std::endl; } else if (tab[nombreTab] == false) { std::cout << "Bravo! " << nombreTab << " n'est pas un nombre premier :)" << std::endl << std::endl; } } } int main() { auto tabPremiers(nombresPremiers(256)); bool recommencer(true); std::cout << "Bienvenue dans le jeu du crible d'Eratosthene!\n\nLa regle est simple, je vous donne si un nombre et"; std::cout << " vous me dites si c'est un\nnombre premier ou pas. Repondez simplement par O pour oui ou par N pour non.\n\nC'est parti!" << std::endl << std::endl; do { std::string choixQuestion, choixRecommencer; auto nombreTableau = nombreAleatoire(255); //Appel de la fonction nombreAleatoire pour obtenir un nombre entre 0 et 255 std::cout << "Le nombre " << nombreTableau << " est-il un nombre premier?" << std::endl; std::cin >> choixQuestion; //L'utilisateur répond à la question std::cout << std::endl; reponseProg(tabPremiers, choixQuestion, nombreTableau); //Appel la fonction reponseProg pour donne rune réponse à l'utilisateur par rapport à son choix std::cout << "Voulez vous recommencer une partie? o/n" << std::endl; std::cin >> choixRecommencer; if (choixRecommencer == "n" || choixRecommencer == "N") //Si l'utilisateur répond "n", la partie s'arrête { recommencer = false; } } while (recommencer); return 0; }
Alors en voilà un autre. Il tourne autour du jeu de poker. L'idée est d'utiliser les concepts de la leçon n°2: les algorithmes de la bibliothèque standard, les itérateurs et, quand nécessaire, les fonctions lambda.
Le jeu de poker (étape 1);
Une carte est représentée par deux entiers non-signés contenus dans une paire (std::pair):
- regarder la documentation de std::pair dans la référence
- la première valeur de la paire représente le rang de la carte: 0 = 2, 1 = 3, ... , 9 = valet, 10 = dame, ... , 12 = as. La deuxième valeur contient la couleur: 0 = carreau, ... 3 = trèfle.
- générer le jeu de 52 cartes dans l'ordre:
a) utiliser la fonction standard std::iota (dans le header <numeric>) pour générer 52 paires: { {0,0}, {1,1}, ... {51,51}}. Pour que iota fonctionne pour des paires, il faut d'abord définir un opérateur ++ pour les paires. En voici une implémentation possible:
b) utiliser la fonction standard std::transform (dans le header <algorithm>) pour transformer le tableau de paires croissantes en un jeu de cartes. Il est possible de recourir à une lambda, utilisant pourquoi pas l'opérateur modulo (11 % 2 == "11 modulo 2" == reste de la division de 11 par 2 == 1)
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 std::pair<unsigned, unsigned>& operator++(std::pair<unsigned, unsigned>& p) { ++p.first; ++p.second; return p; }
- écrire une fonction pour mélanger le paquet de cartes. S'appuyer sur l'algorithme std::shuffle.
- écrire une fonction pour "couper" le paquet de cartes. S'appuyer sur l'algorithme std::rotate
- écrire une fonction pour transformer une paire d'entiers non signés en une string de type "as de trèfle", "deux de carreau", etc.
T'as monté la barre de plusieurs niveaux là!
J'vais d'abord bucher chaque point individuellement avant de tester ton exo car là pour le moment, ca donnerait rien ^^
Et sinon j'ai une question, je l'ai posé à Gbdivers sur le forum d'OC et je voudrais avoir votre point de vue aussi:
Nul part je n'ai justifié du pourquoi j'apprenais le C++ et ca serait bien d'en parler avant de continuer. J'aimerais acquérir les capacités pour coder des jeux vidéo 2D voir 2D en 3D.
Je me suis dit, quitte à apprendre un langage, autant apprendre un comme le C++ qui peut faire un peu de tout mais maintenant que je me rend compte de la complexité, je me
dis que peut être un langage plus haut niveau serait peut être plus adapté et serait moins dur. Qu'en pensez-vous?
Le peu que j'ai appris en C++, j'ai complétement accroché mais j'ai l'impression qu'il me manque des bases d'algo et autre pour vraiment en profiter pleinement.
Oui c'est plus dur. L'avantage du forum c'est qu'on peut faire interactif: si tu bloques ou que tu as besoin de compléments, tu peux me demander. Je ferai peut-être un vrai tutoriel pour le site à partir de nos échanges.T'as monté la barre de plusieurs niveaux là!
J'vais d'abord bucher chaque point individuellement avant de tester ton exo car là pour le moment, ca donnerait rien ^^
Oui, C++ est un bon choix pour écrire des jeux 2D/3D. Une série d'articles très bien va paraître à ce sujet sur le site dans quelques temps: les premiers sont en phase de relecture technique. Cela étant, en C++ ou dans un autre langage, c'est un gros morceau.Et sinon j'ai une question, je l'ai posé à Gbdivers sur le forum d'OC et je voudrais avoir votre point de vue aussi:
Nul part je n'ai justifié du pourquoi j'apprenais le C++ et ca serait bien d'en parler avant de continuer. J'aimerais acquérir les capacités pour coder des jeux vidéo 2D voir 2D en 3D.
L'avantage d'être autodidacte, c'est qu'on peut choisir un, plusieurs langages, passer de l'un à l'autre selon l'inspiration du moment et son bon plaisir. Si tu sens que C++ n'est pas fait pour toi en ce moment, il y a en effet des langages plus simples, comme Python ou, dans une moindre mesure, Java. Ils sont populaires, de nombreuses bibliothèques sont disponibles, y compris pour écrire des jeux. Tu peux regarder Pygame, par exemple: http://pygame.org/hifi.html. Il y a un sous-forum sur le site qui propose des discussions sur les mérites relatifs des différents langages. C'est difficile de se faire un avis à partir de ce genre de discussions mais tu peux au moins voir l'avis des autres.Je me suis dit, quitte à apprendre un langage, autant apprendre un comme le C++ qui peut faire un peu de tout mais maintenant que je me rend compte de la complexité, je me
dis que peut être un langage plus haut niveau serait peut être plus adapté et serait moins dur. Qu'en pensez-vous?
Les algorithmes c'est de toute façon quelque chose à voir à un moment ou à un autre, quel que soit le langage. Peut-être même plus dans des langages plus lents que C++: quand on est déjà un peu handicapé par le langage, il faut être certain d'utiliser l'algorithme le plus efficace.Le peu que j'ai appris en C++, j'ai complétement accroché mais j'ai l'impression qu'il me manque des bases d'algo et autre pour vraiment en profiter pleinement.
Cependant -et c'est mon objectif de te le montrer- on peut repousser ce moment en utilisant les bibliothèques déjà disponibles, qui offrent des algorithmes ou des briques d'algorithme prêts à l'emploi. La bibliothèque standard de C++ est, à mon avis, un des sommets de la programmation. Boost met aussi beaucoup de ressources à disposition. Donc je dirais que tu auras plutôt besoin de bases d'algo pour continuer que pour commencer.
Salut!
Merci, je voulais juste être sûr du chemin que je prends, car si c'est au bout d'un an que je me rend compte que j'ai fais le mauvais choix, ca la fou mal..
Je vais rester sur du C++, c'est p'tete un des plus dur mais au moins j'aurais plus de portes ouvertes. Je vais faire un peu de cours d'algo débutant à coter et commencer
à toucher un peu de unreal 4. Ca me permettra de varier un peu et de pas toujours faire la même chose.
J’espère juste ne pas m'y prendre trop tard, à 27 ans, j'commence à me faire fais vieux ^^
Je sais que ca va être long à apprendre tout ça mais j'ai surtout peur que tout seul, je n'arrive jamais au bout... Heureusement qu'il y a les forum pour que des personnes
comme vous, aide les personnes comme moi.. Merci les gars
Pas forcément. Si tu travailles régulièrement c'est l'affaire de quelques mois pour être capable de faire un jeu pas trop difficile.Je sais que ca va être long à apprendre tout ça
Tu t'en sors avec l'exo? J'ai peut-être frappé trop fort sans m'en rendre compte?
Alors j'ai buché la leçon 2, j'ai compris les iterateurs mais les lambda j'avoue que je suis un peu encore dessus... Mais pour le début de l'exo, j'ai compris a
quoi sert chaque fonction, je sais utiliser chaque partie indépendamment mais j'avoue pas trop savoir par quoi commencer, j'ai pas trouvé beaucoup de détail
sur std::pair et std::iota :/
std::pair est un type générique, comme std::vector; mais au lieu de n'avoir qu'un autre type en paramètre, il en a deux:
Un type pour chacun des membres de la paire (il y en a deux comme le nom l'indique). On peut créer une paire avec std::make_pair:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::pair<int, char> p1; std::pair<std::string, std::vector<int>> p2; etc.
Le premier membre s'appelle first, le second second:
Code : Sélectionner tout - Visualiser dans une fenêtre à part auto contact = std::make_pair("Fred", "0612345678"); // les types paramétrant pair sont déduits par make_pair
il y a un mot-clé très pratique de c++: using, qui permet de donner un nom plus expressif ou plus court à un type compliqué:
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::cout << contact.first << " Tel: " << contact.second; // Fred Tel: 0612345678
Mettons maintenant qu'on veuille créer un paquet de cartes pour le jeu de poker. On peut choisir un std::array, puisque le jeu a toujours 52 cartes:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 using Card = std::pair<unsigned, unsigned>; Card card; // card est de type std::pair<unsigned, unsigned>
L'idée est d'utiliser les algorithmes de la bibliothèque standard pour faire l'exercice. Pour initialiser les cartes on peut utiliser std::iota et std::transform.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 using Deck = std::array<Card, 52>; // Deck = paquet de cartes en anglais // Pour l'instant les cartes sont toutes égales à {0,0}
std::iota fournit une suite croissante à partir d'une valeur intiale:
Pour connaître la prochaine valeur de la série, iota utilise l'opérateur++ (voir la leçon n°2) qui incrémente de 1 son opérande.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::vector<unsigned> entiers_positifs(5); // de la place pour 5 entiers positifs std::iota(std::begin(entiers_positifs), std::end(entiers_positifs), 1); // 1 est le premier entier positif print_vec(entiers_positifs); // {1,2,3,4,5}
La difficulté est que les paires n'ont pas d'opérateur ++ prédéfini.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 auto x = 2; ++x; std::cout << x; // 3
C'est la raison pour laquelle il faut en définir un; les opérateurs en c++ peuvent être redéfinis pour des types non primitifs (c'est-à-dire tous les types qui ne sont pas int, char, float, etc.). Voici la syntaxe, qui est très proche d'une fonction normale:
Dans l'énoncé de l'exercice, je te proposais une définition de l'opérateur++ pour les paires. Tu peux y jeter un coup d'oeil maintenant. Il incrémente chacun des membres de la paire de 1. Donc {0,0} donne {1,1}, {1,1} donne {2,2}, etc.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 type_retour operator++(type_operande); type_retour operator/(type_operande1, type_operande2); etc.
Du coup, après avoir défini cet opérateur tu peux écrire:
Bien entendu cela ne forme pas un paquet de carte. Pour faire une carte valide il faut un premier membre entre 0 et 12 (c-à-d entre 2 et as) et un deuxième entre 0 et 3 (c'est-à-dire entre "coeur" et "trèfle"). Il nous faut un moyen de réduire les valeurs des paires que nous avons créées pour qu'elles rentrent entre ces bornes.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 Deck deck; std::iota(std::begin(deck), std::end(deck), Card(0,0)); // deck = { {0,0}, {1,1}, ..., {51,51}}
L'opérateur % ("modulo") peut être utilisé pour cela. Cet opérateur donne le reste de la division entière entre ses opérandes:
11 / 2 = 5 reste 1 <=> 11 % 2 = 1
14 / 2 = 7 reste 0 <=> 14 % 2 = 0
Comme il s'agit du reste d'une division, il est nécessairement compris entre 0 et l'opérande de droite - 1
Prenons la 42ème carte. Elle est égale à {41,41}. Si j'applique l'opérateur % 13 (le nombre de cartes par couleur) au premier membre, j'aurai une carte de rang valide; si j'applique l'opérateur % 4 au deuxième membre, j'obtiens une couleur valide. D'où:
std::transform permet d'appliquer cette fonction à tout le paquet de cartes créé:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 Card make_valid(Card c) { c.first = c.first % 13; // plus court et équivalent: c.first %= 13 c.second = c.second % 4; // c.second %= 4 return c; }
Néanmoins il est un peu ennuyeux de devoir définir des fonctions comme make_valid: elles servent une seule fois, leur définition sera éloignée de l'endroit où elles sont utilisées, et il faut leur trouver un nom; s'il y a d'autres choses à valider dans le programme, le nom pourrait déjà être pris... D'où l'intérêt d'utiliser une lambda:
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::transform(std::begin(deck), std::end(deck), make_valid);
Maintenant je pense que tu es paré pour faire la suite des exercices...
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::transform(std::begin(deck), std::end(deck), [](Card c) { c.first %= 13; c.second %= 4; return c});
Coucou!
Merci pour ces détails en plus ça m'a bien aidé à comprendre mais malgré que tu m’aies mâché le travail à 95%, je bloque sur std::iota
et l'operator++, je sais pas ou placer la fonction ou comment l'appeler.
Et tant qu'à faire, j'ai des question pour qui peut y répondre:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 #include "stdafx.h" #include <iostream> #include <utility> #include <array> #include <numeric> using Carte = std::pair<unsigned, unsigned>; using Deck = std::array<Carte, 52>; Carte& operator++(Carte& p) { ++p.first; ++p.second; return p; } int main() { Deck jeuDeCarte; std::iota(std::begin(jeuDeCarte), std::end(jeuDeCarte), Carte(0, 0)); std::transform(std::begin(jeuDeCarte), std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; }); for (int x = 0; x < jeuDeCarte.size(); x++) { std::cout << jeuDeCarte[0].first << jeuDeCarte[0].second << std::endl; } }
1- A quoi sert std::print (ou printf ou print_vec)? Ca ressemble à std::cout?
2- Typedef est obsolète par rapport à Using? On doit les utiliser avant le main ou après?
3- J'ai pas saisie la différence entre std::pair<*,*>; et std::make_pair(*,*);
Merci
0) L'opérateur ++ est appelé par la fonction std::iota, qui est définie dans la bibliothèque standard: c'est normal que tu ne vois pas l'appel à l'opérateur puisqu'il est fait dans la définition (cachée) de cette fonction. Il faut qu'il soit défini avant l'appel de la fonction iota pour qu'elle puisse l'appeler à son tour. Fais-le test, enlève la définition de l'opérateur avant d'appeler std::iota: que se passe-t-il? que te dit le compilateur?
1) std::print, je ne connais pas. printf est la fonction standard de C pour afficher à l'écran; il n'est pas recommandé de l'utiliser en C++. print_vec est une fonction que j'ai définie, qui ressemble à la fonction que tu avais définie dans le premier exercice pour afficher les éléments d'un vecteur, séparés par une virgule, entre accolades.
2) oui, typedef est plutôt obsolète. using est plus puissant. En particulier on peut paramétrer une directive using (c'est une notion un peu plus avancée, mais ça ne peut pas te faire de mal de voir à quoi cela ressemble):
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 template <type T> using Tableau = std::vector<T>; ... Tableau<unsigned> nombres_premiers; // nombres premiers est de type std::vector<unsigned> // au contraire, un typedef par type de std::vector: typedef std::vector<unsigned> Tableau_entiers_nonsignes;
On peut les utiliser avant le main, dans le main, dans une fonction... Ce qui change c'est leur portée. La portée va de la directive using à la fin du bloc où elle est utilisée:
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 using Tag = std::string; // Tag est utilisable dans tout le fichier à partir de cette ligne int main() { Tag a; using Bi_tag = std::pair<Tag, int>; // Bi_tag est utilisable de cette ligne jusqu'à la fin de main } Bi_tag mon_bi_tag; // erreur!
3) std::pair<T, U> est un type; std::make_pair(T t, U u) est une fonction.
Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 std::pair<unsigned, unsigned> p1(0,1); // syntaxe avant C++11 std::pair<unsigned, unsigned> p2 = {0,1}; // syntaxe C++11 premier choix. {0,1} peut être autre chose qu'une paire, il faut donc déclarer le type de p2 auto p3 = std::make_pair(0u,1u); // u après un littéral (ex: 56u) signifie que ce littéral est de type unsigned int. on connaît le type de retour de make_pair, on peut donc utiliser auto
Ok mais alors qu'est-ce qui cloche dans mon code? Mon std::array reste à 0,0 dans chaque case grrr
A oui aussi, j'ai du écrire ça:
au lieu de comme toi:
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::transform(std::begin(jeuDeCarte), std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; });
Il me demandait 4 arguments obligatoires alors j'ai doublé le premier mais sans trop savoir pourquoi.
Code : Sélectionner tout - Visualiser dans une fenêtre à part std::transform(std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; });
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager