
Envoyé par
Basmor
Bonjour,
Salut, et bienvenue sur le forum
J'ai quelques questions théoriques à propos des tableaux de pointeurs de type char* ainsi que plus généralement les pointeurs char*.
Pour exemple dans cet énoncé simple:
1 2 3 4 5
| using namespace::std
int main()
{
char* pszArray; |
[/QUOTE]
Le plus facile pour bien comprendre consiste sans doute à lire le code de droite à gauche...
Ainsi, pszArray est le nom d'une variable qui est un pointeur ( * ) de type "char" (caractère)
Cette ligne de code est particulièrement dangereuse, mais j'en parlerai au moment de répondra à ta question qui concerne l'emplacement de Developpez 
cout << &(*pszArray) << endl;
A tu réfléchi à ce que fait ce & (*pszArray) 
Nous avons dit que pszArray est... un pointeur.
L'étoile que tu as écrite signifie "prend ce qui est pointé par le pointeur" (en fait "prend ce qui se trouve à l'adresse indiquée par le pointeur") et l'esperluette " & " signifie "prend l'adressse de..."
N'as tu pas l'étrange impression de te faire du mal en voulant prendre l'adresse de ce qui se trouve à l'adresse indiquée par pszArray 
Tout dabord je ne comprends pas comment cette chaîne de caractère est crée.
Cette chaine prend place dans le segment data du programme...
Il s'agit, simplement, d'une partie de la mémoire dans laquelle iront se placer toutes les constantes littérales utilisée.
C'est le compilateur qui se charge de placer toutes les constantes littérales dans cette partie de la mémoire 
Je veux dire, il n'y a pas eu d'allocation dynamique de mémoire,
Il n'y en a absolument pas besoin, parce que le compilateur sait, pour les constantes, calculer de lui-même le nombre de byte à utiliser 
on a absolument rien renseigné et pour en savoir un peu plus j'ai voulu donc voir où était stocké cette chaîne dans la mémoire (d'où
&(*pszArray)) mais ce qui en résulte est simplement l'affichage de la chaîne pointée par pszArray, à savoir "Developpez". A partir de là, je ne comprends plus rien! Où est stocké cette chaîne et, du coup, sur quoi le pointeur pointe-t-il?!
C'est normal, parce que toute donnée (y compris sur le disque dur ou n'importe quel système de stockage) est accessible par... une adresse qui lui est propre...
Le fait qu'elle se trouve dans le segment data n'y change rien, hormis le fait que la donnée ne peut pas être modifiée.
Mais c'est ce point particulier qui est dangereux: il t'est strictement interdit, au cours de l'exécution du programme, d'essayer de remplacer Developpez par Enveloppez 
Pour pouvoir envisager un tel changement, il faut veiller à ce que pszArray contienne une adresse mémoire... accessible en lecture/écriture, disposant de suffisamment d'espace contigu pour contenir les 9 caractères significatifs du mot Developpez plus un caractère nul '\0' qui représente la fin de la chaine.
En outre, il faut organiser la copie des 9 caractères significatifs du mot du segment data vers la mémoire qui aura été allouée pour les recevoir.
Cela prendrait la forme de
1 2 3
|
pszArray = new char[10];
strncpy("Developpez",pszArray,10); |
Et ce quand bien même, en quoi est-ce différent d'un type
string ?
La différence, c'est que les chaines de caractères "C style" qui ne sont que des tableaux de caractères terminés par un '\0' t'obligent à gérer toi même l'espace mémoire que tu prévois pour contenir la chaine, et à effectuer toi-même la copie de contenu, avec tous les risques inhérents au fait que tu risque toujours... de ne pas avoir prévu assez d'espace (si tant est que tu puisse décider d'en allouer plus) ou d"'oublier" de libérer un espace que tu n'utilise plus
Par contre, la classe string connait en permanence le nombre de caractères qu'elle utilise et le nombre qu'elle peut encore rajouter avant de devoir procéder à une modification de sa taille maximale.
Elle est capable de gérer la copie de son contenu, et le mieux de l'histoire, c'est qu'elle le fait de manière tout à fait transparente pour toi (tu n'as absolument pas à t'inquiéter de tout cela) 
Dans la continuité de ce questionnement, j'aimerais savoir comment il serait possible de remplir une chaîne pointée par un char*
via un flux d'entré?
Peut etre (en fait, non, j'en suis persuadé
) que le mieux est, tout simplement, d'utiliser la classe string partout où tu le peux 
De toutes manières, si tu as vraiment besoin d'une chaines de caractères "C style", tu peux toujours en récupérer une grâce à la fonction membre c_str() 
En effet, le code suivant plante:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| using namespace::std
int main()
{
char* pszString;
char* pszArray[0];
cin >> pszString;
cin >> *(pszArray + 0);
system("PAUSE");
return 0;
} |
Evidemment pour les deux cin. [/QUOTE]C'est normal que le code platnte:
pszString est un pointeur vers des variables de types char qui pointe sur... strictement tout ou n'importe quoi (l'adresse qu'il contient est le résultat des crasse laissée par la dernière utilisation de la mémoire).
Or, il y a de fortes chances que cette adresse renvoie à... une donnée qui n'est certainement pas prête à recevoir des données 
La première chose à faire pour pouvoir utiliser pszString est donc... d'indiquer très clairement le nombre de caractères contigus que tu veux utiliser, soit de manière statique (comprend: sans avoir recours à l'allocation dynamique de la mémoire), soit en ayant recours à l'allocation dynamique, et en étant toujours susceptible de te trouver dans une situation où tu aurais prévu de permettre la saisie de N caractères alors que l'utilisateur en introduira... N+1, ce qui risque de provoquer des problèmes sans nom 
La déclaration n'ayant pas recours à l'allocation dynamique pourrait prendre la forme de
qui se lirait sous la forme de "je déclare un tableau (les crochets permettant de représenter un tableau, ou, plus précisément, l'indice des éléments se trouvant dans un tableau) de type char et que je nomme pszString, et qui aurait pour résultat de permettre la saisie de 10 caractères significatifs en gardant la place pour le '\0' terminal.
Soit, tu peux avoir recours à l'allocation dynamique (qui te permet, le cas échéans, de décider de modifier la taille du tableau) sous la forme de
char *pszString=new char[11];
Quant à pszArray, c'est encore pire...
Tu déclares un tableau devant contenir des pointeurs vers des données de type char, mais qui doit contenir... 0 éléments 
Autrement dit, tu déclares un tableau de pointeurs vers des données de type char qui ne peut contenir... aucun pointeur... Cherchez l'erreur 
Et, même si tu modifie un peu le code en
char *pszArray[10]; /* je prévois de stocker 10 chaines de caractères */
il faudra encore... veiller à allouer l'espace suffisant pour contenir chacune de ces chaines, parce qu'une chaine de caractères "C style" n'est jamais... qu'un tableau de cararctères
(terminé par un '\0', est-il encore besoin de le rappeler)
Ou alors, la bérézina consiste à souhaiter être en mesure de disposer d'un nombre inconnu de chaines de caractères pouvant chacune contenir un nombre quelconque de caractères, et là, pour y arriver (à la manière C style), il faudrait commencer à jouer avec... des pointeurs de pointeurs...
En effet, lorsque tu déclares un tableau sous la forme de
tab (sans les crochets) est à considérer comme... un pointeur sur le premier élément du tableau (celui se trouvant à l'indice 0)
J'en conclu qu'il faut utiliser dans ce cas là une allocation dynamique, non?
Pas forcément avec de l'allocation dynamique, mais très certainement en comprenant la signification de chacun des symboles que tu utilise...
En effet, lorsque tu écris:
tu ne fait que déclarer un pointeur vers des données de type char (autrement dit, une variable dont la valeur représente l'adresse à laquelle tu devrais pouvoir trouver une variable de type char)
alors que quand tu écris:
tu déclare un tableau de dix caractères contigus en mémoire et str représente... l'adresse à laquelle se trouve le premier caractère (celui se trouvant à l'index 0)
Si tu veux gérer un tableau de chaines de caractères, tu peux très bien envisager d'écrire
qui va déclarer un tableau contenant dix tableaux eux même composés de 15 caractères (14 caractères significatifs), autrement dit, un tableau de dix chaines de 14 caractères chacune.
Si tu veux utiliser l'allocation dynamique, le code devient:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
/* soit on effectue directement l'allocation dynamique comme dans les
* exemples précédents, soit en prend la précaution d'initialiser
* le pointeur à NULL qui représente une adresse invalide et facile
* à tester
*/
char *str = NULL;
str=new char[10];
/*...*/
/* il ne faut pas oublier de libérer la mémoire allouée dynamiquement quand
* on n'en a plus besoin
*/
delete str;
/* et, par sécurité, de remettre le pointeur à NULL
*/
str = NULL; |
ou, pour un tableau de chaines de caractères "C style", sous la forme de
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
| /* il faut un tableau de tableau, autrement dit l'identifiant est... l'adresse à
* laquelle nous trouverons l'adresse du premier caractère de la première
* chaine (c'est à dire, un pointeur de pointeur)
*/
char **tabstr;
/* il faut commencer par allouer dynamiquement la mémoire pour le
* contenant (pour le tableau), il doit contenir... des pointeurs vers des
* caractères
*/
tabstr= new char*[10];
/* puis il faut allouer la mémoire pour chacune des chaines de caractères
*/
for(size_t i=0;i<10;++i)
tabstr[i]=new char[15];
/*...*/
/* et bien sur, il ne faut pas oublier de libérer la mémoire lorsque l'on n'en a
* plus besoin...
* d'abord le contenu (les différentes chaines)
*/
for(size_t i=0;i<10;++i)
delete tabstr[i];
/* puis le contenu */
delete tabstr;
/* et, par sécurité, nous faisons pointer tabstr vers une adresse connue pour
* être invalide
*/
tabstr = NULL; |
Et, quoi qu'il en soit, l'idéal est d'éviter comme la peste les possibilités qui sont strictement issues du C et de leur préférer les possibilités offertes par le C++ et qui lui sont propres.
Ainsi, plutôt que de t'amuser avec des char *, char[] pour représenter des chaines de caractères, l'idéal sera toujours de manipuler... des string, qui sont disponibles (comme tout ce qui est fourni par le standard, ou presque) dans l'espace de noms std par la seule inclusion de l'en-tête <string>
Et, plutôt que de t'amuser à gérer d'une manière ou d'une autre toi-même des tableaux d'éléments contigus en mémoire, l'idéal est de recourir à la classe fournie par le standard qui agit justement comme un tableau classique: la classe vector, également disponible dans l'espace de noms std, mais nécessitant cette fois-ci l'inclusion de l'en-tête <vector>.
Tu pourrais alors envisager un code proche de
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
| #include <iostream> /* nécessaire pour les entrées/sorties standard*/
#include <string> /* nécessaire pour les chaines de caractères */
#include <vector> /* nécessaire pour les tableaux d'éléments contigus */
/* on pourrait rajouter la directive
using namespace std;
* qui nous éviterait d'avoir à écrire sans cesse std::...
* mais on peut faire sans :D
*/
int main()
{
/* déclarons un tableau de chaines de caractères */
std::vector<std::string> strTab;
/* demandons dix fois à l'utilisateur d'introduire un mot
* nous aurions pu lui demander 10 000 fois... que cela aurait encore
* été pareil :D
* mais l'utilisateur se serait sans doute lassé avant l'ordinateur :D
*/
for(size_t i=0;i<10;++i)
{
/* il nous faut quand même une chaine pour récupérer l'entrée */
std::string str;
/* et soyons sympa, indiquons à l'utilisateur ce que l'on attend
* de lui :D
*
* le premier mot sera indiqué par le numéro 1... or on compte à
* partir de 0 ici :D
*/
std::cout<<"Veuillez introduire le mot "<<i+1<<":"<<std::endl;
std::cin>>str;
/* et introduisons cette chaine dans le tableau */
strTab.push_back(str);
}
/* il est temps d'afficher tous les mots... ici, un par ligne fera l'affaire :D
*/
std::cout<<"Vous avez introduit les mots :"<<std::endl;
/* il existe quantité de possibilités de provoquer l'affichage...
* mais une simple boucle peut très bien faire l'affaire :D
* Ici, je vais cependant utiliser la fonction size() dont dispose
* la classe vector pour savoir quand m'arrêter :D
*/
for(size_t i=0;i<strTab.size();++i)
std::cout<<strTab[i]<<std::endl;
/* ce qui est bien, c'est que l'on n'a rien à faire pour libérer la mémoire
* avant de quitter l'application :D
*/
return 0;
} |
Et je présumes que tu l'aura remarqué: Si le code semble énorme, c'est essentiellement à cause des commentaires que j'ai placés
Partager