[Build Error]
Fermé
abiroxa
Messages postés
1
Date d'inscription
vendredi 5 juillet 2013
Statut
Membre
Dernière intervention
5 juillet 2013
-
Modifié par mamiemando le 13/07/2013 à 13:19
mamiemando Messages postés 33410 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 décembre 2024 - 13 juil. 2013 à 14:43
mamiemando Messages postés 33410 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 décembre 2024 - 13 juil. 2013 à 14:43
1 réponse
mamiemando
Messages postés
33410
Date d'inscription
jeudi 12 mai 2005
Statut
Modérateur
Dernière intervention
2 décembre 2024
7 808
13 juil. 2013 à 14:43
13 juil. 2013 à 14:43
C'est bizarre que ton compilateur n'affiche pas plus de message d'erreur. Chez moi ça donne :
... ce qui correspond à la ligne :
... qui effectivement n'a pas de sens.
La réponse qui suit est longue mais elle fait office de cours et t'explique le pourquoi du comment et surtout, t'explique comment programmer à peu près correctement en C et en C++. Donc il faut plus la voir comme un cours dans laquelle tu vas piocher pour répondre à ton problème.
Quelques conseils avant de commencer
- évite using namespace std, ce n'est pas une bonne habitude au début (ce genre d'instruction sert à alléger les notations et éviter d'écrire std::cout, std::endl, std::cin, mais elle est à proscrire dans un header, donc autant s'habituer au plus tôt au "vrai nom").
- ça n'a pas vraiment d'intérêt de mettre ta fonction nbrSup en fin de programme car ça te force à la déclarer avant le main, alors que tu pourrais simplement l'écrire au début du programme
- essaye d'être précis au niveau des types : par exemple un int c'est extrêmement vague, un "unsigned int" (ou "unsigned" en abrégé c'est plus clair).
- aère ton code (en mettant des espaces après les virgules, autour des opérateurs "==", ">", ">>")
- et la plus importante de toute : on n'écrit jamais system("PAUSE") car cette instruction n'a de sens que sous windows. Tu peux la remplacer au profit d'un équivalent, par exemple en lisant un caractère sur l'entrée standard (getchar ou autre). Ce genre d'instruction n'a d'ailleurs pas vraiment de sens sous windows non plus, c'est simplement que tu lances ton programme depuis l'explorateur et pas depuis ton IDE ou depuis des commandes ms-dos (démarrer exécuter cmd). Mais si je devais appuyer sur "entrée" à chaque fois que je quitte un programme, ce serait un peu ... pénible :-)
- jamais de caractères accentués dans un nom de fichier
Revenons au programme en lui-même et à ton erreur.
Pourquoi c'est faux ?
En C tu peux parler d'un paramètre int t[20]. Ça du sens parce qu'au moment ou tu compiles, le compilateur sait que tu vas réserver 20 int en pile pour passer le paramètre "t". Le problème dans ton cas, c'est que "x" est une variable, donc il ne peut pas savoir et il plante. Ce n'est donc tout simplement pas possible de passer ton tableau en paramètre de cette manière, il faut utiliser des pointeurs.
Pourquoi le passage par recopie est contre indiqué ?
Avant d'expliquer les pointeurs, j'aimerais souligner que ce genre de passage est extrêmement mauvais en terme de performances. Il faut être conscient qu'en C (ou en C++) un paramètre passé de cette manière à une fonction est une recopie. Pour s'en convaincre regardons ce petit programme :
À l'exécution :
On voit donc incrementer manipule une variable "x" qui est une recopie de la variable "x" du main... mais qu'au final il s'agit bien de deux zones mémoires différentes ! Ainsi si tu passes en paramètre à ta fonction nbrSup un tableau de disons 10000 élements, tu recopies 10000 entiers... pour rien, puisque finalement les recopier ne te sert à rien !
Comment faire (passages par pointeur et par référence) ?
Tout ceci nous amène au passage par pointeur, sachant qu'en C++ on utiliserait plutôt le passage par référence (qui n'existe pas en C). Voyons déjà le passage par pointeur qui a le mérite de marcher aussi bien en C qu'en C++.
L'idée c'est si on reprend le programme ci-dessus de passer l'adresse (= pointeur) de x (que je vais nommer px), et d'aller altérer l'entier qui se trouve à cette endroit.
- L'opérateur unaire & permet de récupérer l'adresse d'une variable. Si celle ci est de type toto_t, l'adresse correspondante sera de type "toto_t *". Une adresse générique est notée "void *". Bien entendu le type attaché à une adresse ne change rien sa nature d'adresse : une adresse fait toujours 32 bits sur une architecture 32 bits, 64 bits sur une architecture 64 bits etc, peu importe sur quoi elle "pointe".
- L'opérateur unaire * permet de considérer ce qui est à cette adresse. Si mon pointeur px est de type "int *", alors *px est vu comme un entier. Cet opérateur n'a de sens que si le pointeur n'est pas "void *" pour savoir combien d'octets il doit considérer à cet endroit (un int et un double ne font pas la même taille en mémoire !).
- De la même manière les opérateurs + et [] sur les pointeurs n'ont de sens que si ce pointeur est typé. En fait il faut comprendre que la notion de tableau n'existe juste pas en C, c'est juste un jeu syntaxique. Quand on parle de "px + 3" avec px de type "int *" on parle d'un entier qui est situé en mémoire 3 entiers après px. La notation px[3] est un raccourci de *(px +3). C'est d'ailleurs pour ça qu'un "tableau" en C est "indexé" à partir de 0.
Quand on parle des opérateurs * ou [] Il faut bien sûr que px désigne l'adresse d'une zone mémoire utilisée par mon programme (sinon j'aurai une erreur mémoire). Le programme précédent deviendrait :
Maintenant considérons notre tableau de 10000 éléments. Finalement la seule chose importante c'est de savoir où commence mon tableau et combien il contient d'éléments pour en déduire où il s'arrête. Donc supposons que je veuilles écrire un tableau d'entier, ma fonction serait du genre :
C'est donc exactement le même principe pour ta fonction. Bon je n'ai pas très bien compris ce qu'elle était sensé faire car dans a tu initialises la valeur 0 et par la suite tu écris "a += a" ce qui signifie ajoute la valeur de a à l'ancienne valeur de a... et 0 + 0 ça fait toujours 0 :-)
Vu que cette fonction me paraît fausse, je vais garder à titre d'exemple mes fonctions "ecrire_tableau" et "incrementer" pour illustrer le propos.
const ?
Tu noteras que j'ai écrit "const int * t" et non "int * t". C'est parce que la fonction nbrSup ne modifie pas les valeurs de mon tableau t (contrairement à ma fonction incrementer). Donc je peux annoncer qu'elle garantit la "constance" des données pointées par t. Je ne vais pas rentrer dans les détails mais globalement, quand une fonction garantit la constance de certains paramètres passé par référence ou par pointeur c'est mieux de l'indiquer car ça généralise leur domaine d'utilisation (pour un passage par recopie, ça n'a pas d'utilité).
Peut-on faire mieux ?
Ce qu'on a vu, c'est la manière de faire ça en C... mais on ne profite pas vraiment de la force du C++. Regardons ce qui est "moche" dans ce programme :
- on doit rappeler la taille du tableau
- la fonction dépend du type des données embarquées dans le tableau
- les pointeurs engendrent des notations souvent plus lourdes que les références (dont je n'ai pas encore parlé).
Tout cela, le C++ permet de le faire.
Les vecteurs
Ici on veut manipuler un tableau d'éléments non triés et pas forcément deux à deux distincts, le type adéquat est donc std::vector.
https://forums.commentcamarche.net/forum/affich-37604421-introduction-a-la-stl-en-c-standard-template-library
Note que la STL n'est pas forcément installée de base sur ton PC, mais si tu utilises un environnement du genre code::blocks ou devcpp tu devrais l'avoir. Et vu que ça simplifie grandement la vie, je te conseille de t'y habituer !
Bon mais ici on fait une recopie du tableau, donc c'est mauvais. Donc il faudrait faire un passage par pointeur pour éviter une recopie coûteuse, ou mieux un ...
Passage par référence (C++)
Déjà expliquons ce qu'est une référence. En gros c'est presque un pointeur, sauf que
- syntaxiquement parlant tout ce passe comme si je manipulais directement la variable, pas son adresse
- dans les faits, je ne fais pas de recopie
- la dernière nuance par rapport à un pointeur, c'est qu'une référence est toujours initialisée et référence forcément une variable existant dans mon programme.
Une référence sur une variable de type "toto_t" est noté "toto_t &" (de la même manière qu'une adresse sur une telle variable serait notée "toto_t *"). Mon programme devient alors :
Ok ok ça commence à être pas mal mais maintenant supposons que je veuilles que ma fonction marche aussi pour un tableau de double, je vais devoir réécrire une autre fonction qui fera exactement la même chose mais avec le bon type... Un peu dommage... à moins qu'on puisse écrire des fonctions qui fonctionnent avec des types génériques...
Les templates (C++)
Une fonction template doit être déclarée ET implémentée dans un header (.hpp) (contrairement aux fonctions classiques qui sont déclarées dans le .hpp et implémentée dans le .cpp). Le compilateur va générer automatiquement l'implémentation en fonction des besoins du programme. Ici comme mon programme est intégralement écrit dans le même fichier .cpp, je peux directement mettre le code dans ce fichier, mais ce n'est pas le cas en général.
Comme ma fonction "ecrire_tableau" marcherait pour n'importe quel tableau (du moment que les éléments qu'il embarque supporte l'opérateur <<). Ainsi je peux écrire :
Les iterators (C++)
Bon ensuite il faut bien voir qu'il existe plein d'autres containers en C++. Notamment des std::set, des std::list, etc. Et finalement un calcul de moyenne ou un affichage consiste toujours à itérer sur ces les éléments et faire le traitement adéquat. Pour itérer sur mon tableau j'ai utilisé un index (i) que j'ai incrémenter mais j'aurais pu utiliser ce qu'on appelle des iterators. Comme je ne modifie pas le contenu du vector, on peut même utiliser des const_iterator. Un (const_)iterator se manipule syntaxiquement comme un pointeur (donc en particulier, l'incrémenter permet d'avancer d'une case).
Pour la généralisation template, on est obligé d'utiliser le mot clé "typename" car le type std::vector<T> est générique.
Bon présenté comme ça, on a l'impression que les iterators on plus complexifié le code qu'autre chose. Mais en fait leur intérêt c'est qu'on peut les utiliser sur n'importe quels containers de la STL. Donc notre fonction d'affichage pourrait être généralisée pour des std::list ou des std::set par exemple. Du coup on serait générique sur le container et non sur les données embarquées par le containers.
Pour plus de détails :
http://www.cplusplus.com/reference/iterator/InputIterator/
Foreach (C++ boost)
Personnellement je ne me suis jamais trop intéressé à comment généraliser une fonction avec des iterators "génériques", d'ailleurs j'utilise de moins en moins souvent les iterators au profit de BOOST_FOREACH. Cette macro repose justement sur ces fameux iterators et les "masque" intelligemment pour rendre le code beaucoup plus lisible et simple.
Il faut au préalable installer boost (je ne sais pas comment on fait sous windows vu que je n'utilise pas windows). Tu peux voir boost comme une extension de la STL. Ici la fonctionnalité qui nous intéresse
c'est celle la :
https://www.boost.org/doc/libs/1_35_0/doc/html/foreach.html
Surcharge d'opérateurs (C++) et flux (streams) (hors sujet par rapport à ton problème)
Enfin pour rendre à César ce qui est à César, on n'écrirait pas une fonction d'affichage de cette manière. Je précise ici car cette réponse fait plus office de cours que de réponse directe à ton problème. Finalement écrire quelque chose dans un fichier ou sur la sortie standard (std::cout) ou la sortie d'erreur standard (std::cerr) c'est un peu le même combat.
Donc déjà notre fonction devrait être plutôt :
Ensuite on aurait envie d'écrire directement "std::cout << tableau << std::endl". Pas de problème, le C++ (contrairement au C) permet de redéfinir les opérateurs. On va ici redéfinir l'opérateur "<<". Ici on veut qu'il prenne en opérande gauche un flux (par exemple std::cout) et à droite un const std::vector<T> &. On également lui faire retourner le flux modifié, ce qui permettra d'écrire des choses du genre "std::cout << tableau << "coucou" << ...".
On obtient donc :
Et voilà ;-)
(mando@silk) (~) $ g++ toto.cpp toto.cpp:31:19: error: `x' was not declared in this scope toto.cpp:31:21: error: expected `)' before `,' token toto.cpp:31:22: error: expected unqualified-id before `int'
... ce qui correspond à la ligne :
int nbrSup(int t[x],int MC,int x)
... qui effectivement n'a pas de sens.
La réponse qui suit est longue mais elle fait office de cours et t'explique le pourquoi du comment et surtout, t'explique comment programmer à peu près correctement en C et en C++. Donc il faut plus la voir comme un cours dans laquelle tu vas piocher pour répondre à ton problème.
Quelques conseils avant de commencer
- évite using namespace std, ce n'est pas une bonne habitude au début (ce genre d'instruction sert à alléger les notations et éviter d'écrire std::cout, std::endl, std::cin, mais elle est à proscrire dans un header, donc autant s'habituer au plus tôt au "vrai nom").
- ça n'a pas vraiment d'intérêt de mettre ta fonction nbrSup en fin de programme car ça te force à la déclarer avant le main, alors que tu pourrais simplement l'écrire au début du programme
- essaye d'être précis au niveau des types : par exemple un int c'est extrêmement vague, un "unsigned int" (ou "unsigned" en abrégé c'est plus clair).
- aère ton code (en mettant des espaces après les virgules, autour des opérateurs "==", ">", ">>")
- et la plus importante de toute : on n'écrit jamais system("PAUSE") car cette instruction n'a de sens que sous windows. Tu peux la remplacer au profit d'un équivalent, par exemple en lisant un caractère sur l'entrée standard (getchar ou autre). Ce genre d'instruction n'a d'ailleurs pas vraiment de sens sous windows non plus, c'est simplement que tu lances ton programme depuis l'explorateur et pas depuis ton IDE ou depuis des commandes ms-dos (démarrer exécuter cmd). Mais si je devais appuyer sur "entrée" à chaque fois que je quitte un programme, ce serait un peu ... pénible :-)
- jamais de caractères accentués dans un nom de fichier
Revenons au programme en lui-même et à ton erreur.
Pourquoi c'est faux ?
En C tu peux parler d'un paramètre int t[20]. Ça du sens parce qu'au moment ou tu compiles, le compilateur sait que tu vas réserver 20 int en pile pour passer le paramètre "t". Le problème dans ton cas, c'est que "x" est une variable, donc il ne peut pas savoir et il plante. Ce n'est donc tout simplement pas possible de passer ton tableau en paramètre de cette manière, il faut utiliser des pointeurs.
Pourquoi le passage par recopie est contre indiqué ?
Avant d'expliquer les pointeurs, j'aimerais souligner que ce genre de passage est extrêmement mauvais en terme de performances. Il faut être conscient qu'en C (ou en C++) un paramètre passé de cette manière à une fonction est une recopie. Pour s'en convaincre regardons ce petit programme :
#include <iostream> void incrementer(int x) { std::cout << "incrementer: 1) x = " << x << std::endl; x++; std::cout << "incrementer: 2) x = " << x << std::endl; } int main() { int x = 7; std::cout << "main: 1) x = " << x << std::endl; incrementer(x); std::cout << "main: 2) x = " << x << std::endl; return 0; }
À l'exécution :
main: 1) x = 7 incrementer: 1) x = 7 incrementer: 2) x = 8 main: 2) x = 7
On voit donc incrementer manipule une variable "x" qui est une recopie de la variable "x" du main... mais qu'au final il s'agit bien de deux zones mémoires différentes ! Ainsi si tu passes en paramètre à ta fonction nbrSup un tableau de disons 10000 élements, tu recopies 10000 entiers... pour rien, puisque finalement les recopier ne te sert à rien !
Comment faire (passages par pointeur et par référence) ?
Tout ceci nous amène au passage par pointeur, sachant qu'en C++ on utiliserait plutôt le passage par référence (qui n'existe pas en C). Voyons déjà le passage par pointeur qui a le mérite de marcher aussi bien en C qu'en C++.
L'idée c'est si on reprend le programme ci-dessus de passer l'adresse (= pointeur) de x (que je vais nommer px), et d'aller altérer l'entier qui se trouve à cette endroit.
- L'opérateur unaire & permet de récupérer l'adresse d'une variable. Si celle ci est de type toto_t, l'adresse correspondante sera de type "toto_t *". Une adresse générique est notée "void *". Bien entendu le type attaché à une adresse ne change rien sa nature d'adresse : une adresse fait toujours 32 bits sur une architecture 32 bits, 64 bits sur une architecture 64 bits etc, peu importe sur quoi elle "pointe".
- L'opérateur unaire * permet de considérer ce qui est à cette adresse. Si mon pointeur px est de type "int *", alors *px est vu comme un entier. Cet opérateur n'a de sens que si le pointeur n'est pas "void *" pour savoir combien d'octets il doit considérer à cet endroit (un int et un double ne font pas la même taille en mémoire !).
- De la même manière les opérateurs + et [] sur les pointeurs n'ont de sens que si ce pointeur est typé. En fait il faut comprendre que la notion de tableau n'existe juste pas en C, c'est juste un jeu syntaxique. Quand on parle de "px + 3" avec px de type "int *" on parle d'un entier qui est situé en mémoire 3 entiers après px. La notation px[3] est un raccourci de *(px +3). C'est d'ailleurs pour ça qu'un "tableau" en C est "indexé" à partir de 0.
Quand on parle des opérateurs * ou [] Il faut bien sûr que px désigne l'adresse d'une zone mémoire utilisée par mon programme (sinon j'aurai une erreur mémoire). Le programme précédent deviendrait :
#include <iostream> void incrementer(int *px) { std::cout << "incrementer: 1) x = " << *px << std::endl; (*px)++; std::cout << "incrementer: 2) x = " << *px << std::endl; } int main() { int x; std::cout << "main: 1) x = " << x << std::endl; incrementer(&x); std::cout << "main: 2) x = " << x << std::endl; return 0; }
Maintenant considérons notre tableau de 10000 éléments. Finalement la seule chose importante c'est de savoir où commence mon tableau et combien il contient d'éléments pour en déduire où il s'arrête. Donc supposons que je veuilles écrire un tableau d'entier, ma fonction serait du genre :
void ecrire_tableau(int * tableau, std::size_t size) { std::cout << '['; for(std::size_t i = 0; i < size; i++) { std::cout << tableau[i] << " "; } std::cout << ']'; }
C'est donc exactement le même principe pour ta fonction. Bon je n'ai pas très bien compris ce qu'elle était sensé faire car dans a tu initialises la valeur 0 et par la suite tu écris "a += a" ce qui signifie ajoute la valeur de a à l'ancienne valeur de a... et 0 + 0 ça fait toujours 0 :-)
int nbrSup(const int * t, int MC, size_t size) // size_t size à la place de int x { int a=0; for (size_t i = 0;i <= size; i++) { if (t[i]> MC) { a += a; } } return a; }
Vu que cette fonction me paraît fausse, je vais garder à titre d'exemple mes fonctions "ecrire_tableau" et "incrementer" pour illustrer le propos.
const ?
Tu noteras que j'ai écrit "const int * t" et non "int * t". C'est parce que la fonction nbrSup ne modifie pas les valeurs de mon tableau t (contrairement à ma fonction incrementer). Donc je peux annoncer qu'elle garantit la "constance" des données pointées par t. Je ne vais pas rentrer dans les détails mais globalement, quand une fonction garantit la constance de certains paramètres passé par référence ou par pointeur c'est mieux de l'indiquer car ça généralise leur domaine d'utilisation (pour un passage par recopie, ça n'a pas d'utilité).
Peut-on faire mieux ?
Ce qu'on a vu, c'est la manière de faire ça en C... mais on ne profite pas vraiment de la force du C++. Regardons ce qui est "moche" dans ce programme :
- on doit rappeler la taille du tableau
- la fonction dépend du type des données embarquées dans le tableau
- les pointeurs engendrent des notations souvent plus lourdes que les références (dont je n'ai pas encore parlé).
Tout cela, le C++ permet de le faire.
Les vecteurs
Ici on veut manipuler un tableau d'éléments non triés et pas forcément deux à deux distincts, le type adéquat est donc std::vector.
https://forums.commentcamarche.net/forum/affich-37604421-introduction-a-la-stl-en-c-standard-template-library
Note que la STL n'est pas forcément installée de base sur ton PC, mais si tu utilises un environnement du genre code::blocks ou devcpp tu devrais l'avoir. Et vu que ça simplifie grandement la vie, je te conseille de t'y habituer !
#include <vector> #include <iostream> void ecrire_tableau(std::vector<int> tableau) { std::cout << '['; for(std::size_t i = 0; i < tableau.size(); i++) { std::cout << tableau[i] << " "; } std::cout << ']'; }
Bon mais ici on fait une recopie du tableau, donc c'est mauvais. Donc il faudrait faire un passage par pointeur pour éviter une recopie coûteuse, ou mieux un ...
Passage par référence (C++)
Déjà expliquons ce qu'est une référence. En gros c'est presque un pointeur, sauf que
- syntaxiquement parlant tout ce passe comme si je manipulais directement la variable, pas son adresse
- dans les faits, je ne fais pas de recopie
- la dernière nuance par rapport à un pointeur, c'est qu'une référence est toujours initialisée et référence forcément une variable existant dans mon programme.
Une référence sur une variable de type "toto_t" est noté "toto_t &" (de la même manière qu'une adresse sur une telle variable serait notée "toto_t *"). Mon programme devient alors :
#include <iostream> void incrementer(int & x) { std::cout << "incrementer: 1) x = " << x << std::endl; x++; std::cout << "incrementer: 2) x = " << x << std::endl; } void ecrire_tableau(const std::vector<int> & tableau) { std::cout << '['; for(std::size_t i = 0; i < tableau.size(); i++) { std::cout << tableau[i] << " "; } std::cout << ']'; }
Ok ok ça commence à être pas mal mais maintenant supposons que je veuilles que ma fonction marche aussi pour un tableau de double, je vais devoir réécrire une autre fonction qui fera exactement la même chose mais avec le bon type... Un peu dommage... à moins qu'on puisse écrire des fonctions qui fonctionnent avec des types génériques...
Les templates (C++)
Une fonction template doit être déclarée ET implémentée dans un header (.hpp) (contrairement aux fonctions classiques qui sont déclarées dans le .hpp et implémentée dans le .cpp). Le compilateur va générer automatiquement l'implémentation en fonction des besoins du programme. Ici comme mon programme est intégralement écrit dans le même fichier .cpp, je peux directement mettre le code dans ce fichier, mais ce n'est pas le cas en général.
Comme ma fonction "ecrire_tableau" marcherait pour n'importe quel tableau (du moment que les éléments qu'il embarque supporte l'opérateur <<). Ainsi je peux écrire :
template <typename T> void ecrire_tableau(const std::vector<T> & tableau) { std::cout << '['; for(std::size_t i = 0; i < tableau.size(); i++) { std::cout << tableau[i] << " "; } std::cout << ']'; } int main() { std::vector<int> v; v.push_back(1); v.push_back(5); v.push_back(3); afficher_tableau(v); return 0; }
Les iterators (C++)
Bon ensuite il faut bien voir qu'il existe plein d'autres containers en C++. Notamment des std::set, des std::list, etc. Et finalement un calcul de moyenne ou un affichage consiste toujours à itérer sur ces les éléments et faire le traitement adéquat. Pour itérer sur mon tableau j'ai utilisé un index (i) que j'ai incrémenter mais j'aurais pu utiliser ce qu'on appelle des iterators. Comme je ne modifie pas le contenu du vector, on peut même utiliser des const_iterator. Un (const_)iterator se manipule syntaxiquement comme un pointeur (donc en particulier, l'incrémenter permet d'avancer d'une case).
void ecrire_tableau(const std::vector<int> & tableau) { std::cout << '['; std::vector<int>::const_iterator tableau_it (tableau.begin()), tableau_end(tableau.end()); for(; tableau_it != tableau_end; tableau_it++) { std::cout << *tableau_it << " "; } std::cout << ']'; }
Pour la généralisation template, on est obligé d'utiliser le mot clé "typename" car le type std::vector<T> est générique.
template <typename T> void ecrire_tableau(const std::vector<T> & tableau) { std::cout << '['; typename std::vector<T>::const_iterator tableau_it (tableau.begin()), tableau_end(tableau.end()); for(; tableau_it != tableau_end; tableau_it++) { std::cout << *tableau_it << " "; } std::cout << ']'; }
Bon présenté comme ça, on a l'impression que les iterators on plus complexifié le code qu'autre chose. Mais en fait leur intérêt c'est qu'on peut les utiliser sur n'importe quels containers de la STL. Donc notre fonction d'affichage pourrait être généralisée pour des std::list ou des std::set par exemple. Du coup on serait générique sur le container et non sur les données embarquées par le containers.
Pour plus de détails :
http://www.cplusplus.com/reference/iterator/InputIterator/
Foreach (C++ boost)
Personnellement je ne me suis jamais trop intéressé à comment généraliser une fonction avec des iterators "génériques", d'ailleurs j'utilise de moins en moins souvent les iterators au profit de BOOST_FOREACH. Cette macro repose justement sur ces fameux iterators et les "masque" intelligemment pour rendre le code beaucoup plus lisible et simple.
Il faut au préalable installer boost (je ne sais pas comment on fait sous windows vu que je n'utilise pas windows). Tu peux voir boost comme une extension de la STL. Ici la fonctionnalité qui nous intéresse
c'est celle la :
https://www.boost.org/doc/libs/1_35_0/doc/html/foreach.html
#include <iostream> #include <vector> #include <boost/foreach.hpp> template <typename T> void ecrire_tableau(const std::vector<T> & tableau) { std::cout << '['; BOOST_FOREACH(const T & element, tableau) { std::cout << element << " "; } std::cout << ']'; }
Surcharge d'opérateurs (C++) et flux (streams) (hors sujet par rapport à ton problème)
Enfin pour rendre à César ce qui est à César, on n'écrirait pas une fonction d'affichage de cette manière. Je précise ici car cette réponse fait plus office de cours que de réponse directe à ton problème. Finalement écrire quelque chose dans un fichier ou sur la sortie standard (std::cout) ou la sortie d'erreur standard (std::cerr) c'est un peu le même combat.
Donc déjà notre fonction devrait être plutôt :
#include <ostream> #include <vector> #include <boost/foreach.hpp> template <typename T> void ecrire_tableau(std::ostream & out, const std::vector<T> & tableau) { out << '['; BOOST_FOREACH(const T & element, tableau) { out << element << " "; } out << ']'; }
Ensuite on aurait envie d'écrire directement "std::cout << tableau << std::endl". Pas de problème, le C++ (contrairement au C) permet de redéfinir les opérateurs. On va ici redéfinir l'opérateur "<<". Ici on veut qu'il prenne en opérande gauche un flux (par exemple std::cout) et à droite un const std::vector<T> &. On également lui faire retourner le flux modifié, ce qui permettra d'écrire des choses du genre "std::cout << tableau << "coucou" << ...".
On obtient donc :
#include <ostream> #include <vector> #include <boost/foreach.hpp> template <typename T> std::ostream & operator << (std::ostream & out, const std::vector<T> & tableau) { out << '['; BOOST_FOREACH(const T & element, tableau) { out << element << " "; } out << ']'; return out; }
Et voilà ;-)