Pourquoi utiliser le double pointeur? ou pourquoi utiliser des pointeurs vers des pointeurs?

Quand un double pointeur doit-il être utilisé dans C? Quelqu’un peut-il expliquer avec un exemple?

Ce que je sais, c’est qu’un double pointeur est un pointeur sur un pointeur. Pourquoi aurais-je besoin d’un pointeur sur un pointeur?

Si vous voulez avoir une liste de caractères (un mot), vous pouvez utiliser le char *word

Si vous voulez une liste de mots (une phrase), vous pouvez utiliser char **sentence

Si vous voulez une liste de phrases (un monologue), vous pouvez utiliser le char ***monologue

Si vous voulez une liste de monologues (une biographie), vous pouvez utiliser char ****biography

Si vous voulez une liste de biographies (une bio-bibliothèque), vous pouvez utiliser char *****biolibrary

Si vous voulez une liste de bio-bibliothèques (a ?? lol), vous pouvez utiliser char ******lol

… …

oui, je sais que ce ne sont peut-être pas les meilleures structures de données

L’une des raisons est que vous souhaitez modifier la valeur du pointeur transmis à une fonction en tant qu’argument de la fonction. Pour ce faire, vous avez besoin d’un pointeur vers un pointeur.

En termes simples, utilisez ** lorsque vous souhaitez conserver (OU conserver la modification) l’allocation de mémoire ou l’affectation, même en dehors d’un appel de fonction. (Donc, passer une telle fonction avec un argument à double pointeur.)

Cela peut ne pas être un très bon exemple, mais vous montrera l’utilisation de base:

 void allocate(int** p) { *p = (int*)malloc(sizeof(int)); } int main() { int* p = NULL; allocate(&p); *p = 42; free(p); } 

Voici une réponse SIMPLE !!!!

  • Disons que vous avez un pointeur que sa valeur est une adresse.
  • mais maintenant vous voulez changer cette adresse.
  • vous pourriez, en faisant pointer1 = pointer2, et pointer1 aurait maintenant l’adresse de pointeur2.
  • MAIS! Si vous voulez qu’une fonction le fasse pour vous, et que le résultat persiste après la fin de la fonction, vous devez faire un travail supplémentaire, vous avez besoin d’un nouveau pointeur3 pour pointer sur le pointeur1 et transmettre le pointeur3 à la fonction.

  • Voici un exemple amusant (jetez un oeil à la sortie ci-dessous, pour comprendre!):

 #include  int main() { int c = 1; int d = 2; int e = 3; int * a = &c; int * b = &d; int * f = &e; int ** pp = &a; // pointer to pointer 'a' printf("\na's value: %x \n", a); printf("\nb's value: %x \n", b); printf("\nf's value: %x \n", f); printf("\n can we change a?, lets see \n"); printf("\na = b \n"); a = b; printf("\na's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a); printf("\n cant_change(a, f); \n"); cant_change(a, f); printf("\na's value is now: %x, Doh! same as 'b'... that function sortingcked us. \n", a); printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n"); printf("\n change(pp, f); \n"); change(pp, f); printf("\na's value is now: %x, YEAH! same as 'f'... that function ROCKS!!!. \n", a); return 0; } void cant_change(int * x, int * z){ x = z; printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x); } void change(int ** x, int * z){ *x = z; printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x); } 
  • et voici le résultat:
  a's value: bf94c204 b's value: bf94c208 f's value: bf94c20c can we change a?, lets see a = ba's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... cant_change(a, f); ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see a's value is now: bf94c208, Doh! same as 'b'... that function sortingcked us. NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' change(pp, f); ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see a's value is now: bf94c20c, YEAH! same as 'f'... that function ROCKS!!!. 

En plus de la réponse d’ Asha , si vous utilisez un pointeur unique dans l’exemple ci-dessous (par exemple, alloc1 ()), vous perdrez la référence à la mémoire allouée à l’intérieur de la fonction.

 void alloc2(int** p) { *p = (int*)malloc(sizeof(int)); **p = 10; } void alloc1(int* p) { p = (int*)malloc(sizeof(int)); *p = 10; } int main(){ int *p; alloc1(p); //printf("%d ",*p);//value is undefined alloc2(&p); printf("%d ",*p);//will print 10 free(p); return 0; } 

La raison pour laquelle cela se produit est que, dans alloc1 le pointeur est passé en valeur. Ainsi, lorsqu’il est réaffecté au résultat de l’appel malloc dans alloc1 , la modification ne concerne pas le code d’une scope différente.

1. Concept de base –

Lorsque vous déclarez comme suit: –

1. char * ch – (appelé pointeur de caractère)
– ch contient l’adresse d’un seul caractère.
– (* ch) déréférencera à la valeur du personnage.

2. char ** ch –
‘ch’ contient l’adresse d’un tableau de pointeurs de caractères. (comme dans 1)
‘* ch’ contient l’adresse d’un seul caractère. (Notez que c’est différent de 1, à cause de la différence de déclaration).
(** ch) déréférencera à la valeur exacte du personnage.

L’ajout de plus de pointeurs étend la dimension d’un type de données, du caractère à la chaîne, au tableau de chaînes, etc. Vous pouvez l’associer à une masortingce 1d, 2d, 3d.

Ainsi, l’utilisation du pointeur dépend de la façon dont vous le déclarez.

Voici un code simple.

 int main() { char **p; p = (char **)malloc(100); p[0] = (char *)"Apple"; // or write *p, points to location of 'A' p[1] = (char *)"Banana"; // or write *(p+1), points to location of 'B' cout << *p << endl; //Prints the first pointer location until it finds '\0' cout << **p << endl; //Prints the exact character which is being pointed *p++; //Increments for the next string cout << *p; } 

2. Une autre application des doubles pointeurs -
(cela couvrirait aussi le passage par référence)

Supposons que vous souhaitiez mettre à jour un caractère d'une fonction. Si vous essayez ce qui suit: -

 void func(char ch) { ch = 'B'; } int main() { char ptr; ptr = 'A'; printf("%c", ptr); func(ptr); printf("%c\n", ptr); } 

La sortie sera AA. Cela ne fonctionne pas, car vous avez "transmis par valeur" à la fonction.

La bonne façon de le faire serait -

 void func( char *ptr) //Passed by Reference { *ptr = 'B'; } int main() { char *ptr; ptr = (char *)malloc(sizeof(char) * 1); *ptr = 'A'; printf("%c\n", *ptr); func(ptr); printf("%c\n", *ptr); } 

Etendez maintenant cette exigence pour la mise à jour d'une chaîne plutôt que d'un caractère.
Pour cela, vous devez recevoir le paramètre dans la fonction sous forme de double pointeur.

 void func(char **str) { strcpy(str, "Second"); } int main() { char **str; // printf("%d\n", sizeof(char)); *str = (char **)malloc(sizeof(char) * 10); //Can hold 10 character pointers int i = 0; for(i=0;i<10;i++) { str = (char *)malloc(sizeof(char) * 1); //Each pointer can point to a memory of 1 character. } strcpy(str, "First"); printf("%s\n", str); func(str); printf("%s\n", str); } 

Dans cet exemple, la méthode attend un double pointeur comme paramètre pour mettre à jour la valeur d'une chaîne.

J’ai vu un très bon exemple aujourd’hui, de cet article de blog , comme je le résume ci-dessous.

Imaginez que vous avez une structure pour les nœuds dans une liste chaînée, ce qui est probablement

 typedef struct node { struct node * next; .... } node; 

Maintenant, vous voulez implémenter une fonction remove_if , qui accepte un critère de suppression rm comme l’un des arguments et traverse la liste liée: si une entrée satisfait au critère (quelque chose comme rm(entry)==true ), son noeud sera supprimé de la liste. À la fin, remove_if renvoie la tête (qui peut être différente de la tête d’origine) de la liste liée.

Vous pouvez écrire

 for (node * prev = NULL, * curr = head; curr != NULL; ) { node * const next = curr->next; if (rm(curr)) { if (prev) // the node to be removed is not the head prev->next = next; else // remove the head head = next; free(curr); } else prev = curr; curr = next; } 

en tant que votre boucle. Le message est que, sans double pointeur, vous devez conserver une variable prev pour réorganiser les pointeurs et gérer les deux cas différents.

Mais avec les doubles pointeurs, vous pouvez réellement écrire

 // now head is a double pointer for (node** curr = head; *curr; ) { node * entry = *curr; if (rm(entry)) { *curr = entry->next; free(entry); } else curr = &entry->next; } 

Vous n’avez pas besoin d’un prev maintenant, car vous pouvez directement modifier ce que vous avez indiqué .

Pour rendre les choses plus claires, suivons un peu le code. Au cours de l’enlèvement:

  1. if entry == *head : ce sera *head (==*curr) = *head->next – la head pointe maintenant vers le pointeur du nouveau nœud de titre. Vous faites cela en changeant directement le contenu de la head en un nouveau pointeur.
  2. if entry != *head : de même, *curr est ce que prev->next pointe vers, et pointe maintenant vers entry->next .

Dans tous les cas, vous pouvez réorganiser les pointeurs de manière unifiée avec des pointeurs doubles.

Les pointeurs vers les pointeurs sont également utiles en tant que “descripteurs” dans la mémoire où vous voulez faire circuler un “handle” entre les fonctions vers la mémoire repérable. Cela signifie essentiellement que la fonction peut modifier la mémoire pointée par le pointeur à l’intérieur de la variable handle, et que chaque fonction ou object utilisant le handle pointe correctement vers la mémoire nouvellement déplacée (ou allouée). Les bibliothèques aiment faire ceci avec des types de données “opaques”, c’est-à-dire que les types de données n’ont pas à se soucier de ce qu’ils font avec la mémoire pointée, vous faites simplement passer le “handle” entre les fonctions de la bibliothèque pour effectuer certaines opérations sur cette mémoire … les fonctions de la bibliothèque peuvent allouer et désallouer la mémoire sous le capot sans que vous ayez à vous soucier explicitement du processus de gestion de la mémoire ou du pointage du handle.

Par exemple:

 #include  typedef unsigned char** handle_type; //some data_structure that the library functions would work with typedef struct { int data_a; int data_b; int data_c; } LIB_OBJECT; handle_type lib_create_handle() { //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs handle_type handle = malloc(sizeof(handle_type)); *handle = malloc(sizeof(LIB_OBJECT) * 10); return handle; } void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ } void lib_func_b(handle_type handle) { //does something that takes input LIB_OBJECTs and makes more of them, so has to //reallocate memory for the new objects that will be created //first re-allocate the memory somewhere else with more slots, but don't destroy the //currently allocated slots *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20); //...do some operation on the new memory and return } void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ } void lib_free_handle(handle_type handle) { free(*handle); free(handle); } int main() { //create a "handle" to some memory that the library functions can use handle_type my_handle = lib_create_handle(); //do something with that memory lib_func_a(my_handle); //do something else with the handle that will make it point somewhere else //but that's invisible to us from the standpoint of the calling the function and //working with the handle lib_func_b(my_handle); //do something with new memory chunk, but you don't have to think about the fact //that the memory has moved under the hood ... it's still pointed to by the "handle" lib_func_c(my_handle); //deallocate the handle lib_free_handle(my_handle); return 0; } 

J’espère que cela t’aides,

Jason

Les chaînes sont un excellent exemple d’utilisation de doubles pointeurs. La chaîne elle-même est un pointeur, donc chaque fois que vous avez besoin de pointer une chaîne, vous aurez besoin d’un double pointeur.

Voici un exemple très simple de C ++ qui montre que si vous souhaitez utiliser une fonction pour définir un pointeur sur un object, vous devez disposer d’un pointeur sur un pointeur . Sinon, le pointeur continuera à revenir à la valeur null .

(Une réponse en C ++, mais je crois que c’est la même chose en C.)

(En outre, pour référence: Google (“pass by value c ++”) = “Par défaut, les arguments en C ++ sont passés par valeur. Lorsqu’un argument est passé par valeur, la valeur de l’argument est copiée dans le paramètre de la fonction.”)

Donc, nous voulons définir le pointeur b égal à la chaîne a .

 #include  #include  void Function_1(std::ssortingng* a, std::ssortingng* b) { b = a; std::cout << (b == nullptr); // False } void Function_2(std::string* a, std::string** b) { *b = a; std::cout << (b == nullptr); // False } int main() { std::string a("Hello!"); std::string* b(nullptr); std::cout << (b == nullptr); // True Function_1(&a, b); std::cout << (b == nullptr); // True Function_2(&a, &b); std::cout << (b == nullptr); // False } // Output: 10100 

Que se passe-t-il à la ligne Function_1(&a, b); ?

  • La "valeur" de &main::a (une adresse) est copiée dans le paramètre std::ssortingng* Function_1::a . Par conséquent, Function_1::a est un pointeur sur (c'est-à-dire l'adresse mémoire de) la chaîne main::a .

  • La "valeur" de main::b (une adresse en mémoire) est copiée dans le paramètre std::ssortingng* Function_1::b . Par conséquent, il y a maintenant deux de ces adresses en mémoire, les deux étant des pointeurs nuls. À la ligne b = a; , la variable locale Function_1::b est alors remplacée par la Function_1::a (= &main::a ), mais la variable main::b est inchangée. Après l'appel à Function_1 , main::b est toujours un pointeur nul.

Que se passe-t-il à la ligne Function_2(&a, &b); ?

  • Le traitement de la variable a est le même: dans la fonction, Function_2::a est l'adresse de la chaîne main::a .

  • Mais la variable b est maintenant passée en tant que pointeur sur un pointeur. La "valeur" de &main::b (l' adresse du pointeur main::b ) est copiée dans std::ssortingng** Function_2::b . Par conséquent, dans Function_2, le fait de déréférencer ceci en tant que *Function_2::b permettra d'accéder et de modifier main::b . Donc la ligne *b = a; est en train de mettre main::b (une adresse) égale à Function_2::a (= adresse de main::a ) qui est ce que nous voulons.

Si vous voulez utiliser une fonction pour modifier une chose, que ce soit un object ou une adresse (pointeur), vous devez passer un pointeur sur cette chose. La chose que vous avez réellement transmise ne peut pas être modifiée (dans la scope de l'appel) car une copie locale est effectuée.

(Une exception est si le paramètre est une référence, comme std::ssortingng& a . Mais généralement, ils sont const . Généralement, si vous appelez f(x) , si x est un object, vous devriez pouvoir supposer que f won ' t modifier x mais si x est un pointeur, vous devriez supposer que f pourrait modifier l'object pointé par x .

Exemple simple que vous avez probablement vu plusieurs fois auparavant

 int main(int argc, char **argv) 

Dans le second paramètre, vous l’avez: un pointeur sur un pointeur vers un caractère.

Notez que la notation du pointeur ( char* c ) et la notation du tableau ( char c[] ) sont interchangeables dans les arguments de fonction. Vous pouvez donc aussi écrire char *argv[] . En d’autres termes, les caractères char *argv[] et char **argv sont interchangeables.

Ce que représente ce qui précède est en fait un tableau de séquences de caractères (les arguments de ligne de commande donnés à un programme au démarrage).

Voir aussi cette réponse pour plus de détails sur la signature de la fonction ci-dessus.

Par exemple, vous voudrez peut-être vous assurer que lorsque vous libérez la mémoire de quelque chose, vous définissez le pointeur sur NULL après.

 void safeFree(void** memory) { if (*memory) { free(*memory); *memory = NULL; } } 

Lorsque vous appelez cette fonction, vous l’appelez avec l’adresse d’un pointeur

 void* myMemory = someCrazyFunctionThatAllocatesMemory(); safeFree(&myMemory); 

Maintenant, myMemory est défini sur NULL et toute tentative de réutilisation sera manifestement incorrecte.

Par exemple, si vous souhaitez un access aléatoire à des données non contiguës.

 p -> [p0, p1, p2, ...] p0 -> data1 p1 -> data2 

– en C

 T ** p = (T **) malloc(sizeof(T*) * n); p[0] = (T*) malloc(sizeof(T)); p[1] = (T*) malloc(sizeof(T)); 

Vous stockez un pointeur p qui pointe vers un tableau de pointeurs. Chaque pointeur pointe sur une donnée.

Si sizeof(T) est grand, il ne sera peut-être pas possible d’allouer un bloc contigu (c’est-à-dire utilisant malloc) de sizeof(T) * n octets.

Une chose que je les utilise constamment, c’est lorsque j’ai un tableau d’objects et que je dois y effectuer des recherches (recherche binary) par différents champs.
Je garde le tableau original …

 int num_objects; OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects); 

Faites ensuite un tableau de pointeurs sortingés vers les objects.

 int compare_object_by_name( const void *v1, const void *v2 ) { OBJECT *o1 = *(OBJECT **)v1; OBJECT *o2 = *(OBJECT **)v2; return (strcmp(o1->name, o2->name); } OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects); int i = 0; for( ; i 

Vous pouvez créer autant de tableaux de pointeurs que vous le souhaitez, puis utiliser une recherche binary sur le tableau de pointeurs sortingés pour accéder à l'object dont vous avez besoin. Le tableau d'objects d'origine peut restr non sortingé, mais chaque tableau de pointeurs sera sortingé selon le champ spécifié.

J’ai utilisé des doubles pointeurs aujourd’hui alors que je programmais quelque chose pour le travail, donc je peux répondre à la raison pour laquelle nous avons dû les utiliser (c’est la première fois que je dois utiliser des doubles pointeurs). Nous avons dû gérer l’encodage en temps réel des images contenues dans les tampons membres de certaines structures. Dans l’encodeur, nous devions utiliser un pointeur vers l’une de ces structures. Le problème était que notre pointeur était changé pour pointer vers d’autres structures d’un autre thread. Pour utiliser la structure actuelle dans l’encodeur, j’ai dû utiliser un double pointeur, afin de pointer sur le pointeur en cours de modification dans un autre thread. Au début, du moins pour nous, il n’était pas évident que nous devions adopter cette approche. Beaucoup d’adresses ont été imprimées dans le processus :)).

Vous devez utiliser des doubles pointeurs lorsque vous travaillez sur des pointeurs modifiés dans d’autres endroits de votre application. Vous pouvez également trouver des doubles pointeurs pour être un must lorsque vous traitez avec le matériel qui vous retourne et vous adresse.

Pourquoi doubler les pointeurs?

L’objective est de changer ce que l’élève indique, en utilisant une fonction.

 #include  #include  typedef struct Person{ char * name; } Person; /** * we need a ponter to a pointer, example: &studentA */ void change(Person ** x, Person * y){ *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct. } void dontChange(Person * x, Person * y){ x = y; } int main() { Person * studentA = (Person *)malloc(sizeof(Person)); studentA->name = "brian"; Person * studentB = (Person *)malloc(sizeof(Person)); studentB->name = "erich"; /** * we could have done the job as simple as this! * but we need more work if we want to use a function to do the job! */ // studentA = studentB; printf("1. studentA = %s (not changed)\n", studentA->name); dontChange(studentA, studentB); printf("2. studentA = %s (not changed)\n", studentA->name); change(&studentA, studentB); printf("3. studentA = %s (changed!)\n", studentA->name); return 0; } /** * OUTPUT: * 1. studentA = brian (not changed) * 2. studentA = brian (not changed) * 3. studentA = erich (changed!) */ 

Comme cela a été dit, l’application du double pointeur consiste à mettre à jour la chaîne pour que les modifications apscopes soient renvoyées.

 #include  #include  // for using strcpy using namespace std; void change(char **temp) { strcpy(temp[0],"new"); strcpy(temp[1],"value"); } int main() { char **str; str = (char **)malloc(sizeof(char *)*3); str[0]=(char *)malloc(10); str[1]=(char *)malloc(10); strcpy(str[0],"old"); strcpy(str[1],"name"); char **temp = str; // always use the temporary variable while(*temp!=NULL) { cout<<*temp< 

Espérons que l’exemple suivant efface certains concepts concernant les pointeurs et les doubles pointeurs, leurs différences et leur utilisation dans des scénarios courants.

  int* setptr(int *x) { printf("%u\n",&x); x=malloc(sizeof(int)); *x=1; return x; } In the above function setptr we can manipulate x either 1. by taking fn arg as int *x , doing malloc and setting value of x and return x Or 2. By taking arg as int ** and malloc and then set **x value to some value. Note: we cant set any general pointer directly without doing malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the pointer and in the case pointer initially points to NULL or it is defined like int *x where it points to any random address then we need to assign a valid memory address to pointer 1. either we need to allocate memory to it by malloc int *x=NULL means its address is 0. Now we need to either o following 1. void main() { int *x; x=malloc *x=some_val; } Or void main() { int *x Fn(x); } void Fn(int **x) { *x=malloc; **x=5; } OR int * Fn(int *x) { x=malloc(); *x=4; Return x; } 2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined. OR int main() { int a; int *x=&a; Fn(x); printf("%d",*x); } void Fn(int *x) { *x=2; } in both cases value pointed by x is changed inside fn But suppose if we do like int main() { int *x=NULL; printf("%u\n",sizeof(x)); printf("%u\n",&x); x=setptr(x); //*x=2; printf("%d\n",*x); return 0; } /* output 4 1 */ #include void setptr(int *x) { printf("inside setptr\n"); printf("x=%u\n",x); printf("&x=%u\n",&x); //x=malloc(sizeof(int)); *x=1; //return x; } int main() { int *x=NULL; printf("x=%u\n",x); printf("&x=%u\n",&x); int a; x=&a; printf("x=%u\n",x); printf("&a=%u\n",&a); printf("&x=%u\n",&x); setptr(x); printf("inside main again\n"); //*x=2; printf("x=%u\n",x); printf("&x=%u\n",&x); printf("*x=%d\n",*x); printf("a=%d\n",a); return 0; } 

L’application du double pointeur, comme le montre Bhavuk Mathur, semble être erronée. L’exemple suivant est celui qui est valide

 void func(char **str) { strcpy(str[0],"second"); } int main(){ char **str; str = (char **)malloc(sizeof(char*)*1); // allocate 1 char* or ssortingng str[0] = (char *)malloc(sizeof(char)*10); // allocate 10 character strcpy(str[0],"first"); // assign the ssortingng printf("%s\n",*str); func(str); printf("%s\n",*str); free(str[0]); free(str); } 

L’exemple suivant, que je vais donner donnera un aperçu ou une intuition sur le fonctionnement des doubles pointeurs, je passerai par les étapes

 1) try to understand the following statements char **str ; a) str is of type char ** whose value is an address of another pointer. b) *str is of type char * whose value is an address of variable or (it is a ssortingng itself). c) **str is of type char ,gives the value stored, in this case a character. 

Vous trouverez ci-dessous le code auquel vous pouvez vous référer aux points ci-dessus (a, b, c) pour comprendre

 str = (char **)malloc(sizeof(char *) *2); // here i am assigning the mem for two char * str[0]=(char *)"abcdefghij"; // assignin the value str[1]=(char *)"xyzlmnopqr"; 

maintenant, pour imprimer la valeur, c’est-à-dire les chaînes dans le tableau, regardez simplement le point b. Dans le cas d’une chaîne, la valeur et l’adresse sont les mêmes.

 cout<<*str< 

Maintenant, pour imprimer la chaîne suivante, sortez d'un déréférencement, c'est-à-dire (*) de * str en str, puis incrémentez, comme indiqué ci-dessous

 str++; 

maintenant imprimer la chaîne

 cout<<*str< 

maintenant, pour imprimer uniquement les caractères d'une chaîne, reportez-vous au point c)

 cout<<**str< 

maintenant, pour imprimer le caractère suivant d'une chaîne, c'est-à-dire que "b" sort de l'opérateur de déréférencement et l'incrémente, c'est-à-dire qu'il va de ** str à * str et do * str ++

 *str++; 

imprimer maintenant le caractère

 cout<<**str< 

comme les deux tableaux ("abcdefghij", "xylmnopqr") sont stockés dans un bloc de mémoire continu si la même chose est faite pour incrémenter l'adresse, tous les caractères de deux chaînes seront imprimés