Langage C

les pointeurs en C

Un pointeur est une variable spéciale qui peut contenir l'adresse d'une autre variable. Chaque pointeur est limité à un type de données. Il peut contenir l'adresse d'une variable de ce type.

Pour utiliser des pointeurs en C, nous devons comprendre les deux opérateurs ci-dessous.

  • Pour accéder à l'adresse d'une variable vers un pointeur, nous utilisons l'opérateur unaire & qui renvoie l'adresse de cette variable.
    Par exemple, &x nous donne l'adresse de la variable x.
    Exemple 1 :
                                            #include < stdio.h> 
                                            int main(void){
                                                int x;
                                                printf("adresse de x est : %p",&x);
    
                                                return 0;
                                            }
                                        
      adresse de x est : 0x7ffee6ffea28
  • Un autre opérateur est unary *, qui sert à deux choses :
    Pour déclarer une variable de pointeur

    Lorsqu'une variable de pointeur est déclarée en C / C ++, il doit précéder d'un *.

    Exemple 2 :
                                            #include < stdio.h> 
                                            int main(void){
                                                int x;
                                                // 1) Puisqu'il y a * dans la déclaration, 
                                                // ptr devient un pointeur (une variable
                                                // qui stocke l'adresse d'une autre variable)
                                                // 2) Puisqu'il y a int avant *, ptr est
                                                // pointeur sur une variable de type entier
                                                int *ptr; 
    
                                                // & opérateur avant que x est utilisé pour obtenir l'adresse de x
                                                // L'adresse de x est assignée à ptr.
                                                ptr = &x; 
        
                                                return 0;
                                            }
                                        
    Accéder à la valeur stockée dans l'adresse

    Pour accéder à la valeur stockée dans l'adresse, nous utilisons l'opérateur unaire (*) qui renvoie la valeur de la variable située à l'adresse spécifiée par son opérande.

    Exemple 3 :
                                            #include < stdio.h> 
                                            int main(void){
                                                int x=2;
                                                
                                                // pointeur contenant l'adresse de x.
                                                int *ptr=&x; 
    
                                                // La valeur à l'adresse est maintenant 5
                                                *ptr = 5;
    
                                                printf(" *ptr = %d \n",*ptr);
                                                printf(" x = %d",x);
        
                                                return 0;
                                            }
                                        
    *ptr = 5
    x = 5

Déclaration d'un pointeur

type * nom_du_pointeur;
  • type: Type de données stocké dans l'adresse.
  • nom_du_pointeur : nom du pointeur
Exemple 4 :
                                char *buffer;
                                int *x;
                                float *y;
                            
Très important :  C'est au programmeur d'initialiser le pointeur

Initialisation d'un pointeur

Il doit pointer vers une zone mémoire réservée par le compilateur comme étant une variable

Exemple 5 :
                                int f;
                                int *pf,*pg;
                                // création du lien entre le pointeur et la variable
                                pf = &f; 

                                // allocation dynamique d'une variablede type int
                                // création du lien entre  pg et l'espace mémoire réservé
                                pg =(int*)malloc(sizeof(int)); 
                                                                                                    
                                .
                                .
                                .
                                free(pg); // libération de l'espace réservé
                            

Utilisation des pointeurs

Les pointeurs ont un grand nombre d'intérêts :

  • Ils permettent de manipuler de façon simple des données pouvant être importantes (au lieu de passer à une fonction un élément très grand (en taille) on pourra par exemple lui fournir un pointeur vers cet élément...)
  • Les tableaux ne permettent de stocker qu'un nombre fixé d'éléments de même type. En stockant des pointeurs dans les cases d'un tableau, il sera possible de stocker des éléments de taille diverse, et même de rajouter des éléments au tableau en cours d'utilisation (la notion de tableau dynamique) ce qui n’est pas possible pour les tableaux statiques.
  • Il est possible de créer des structures chaînées.

Après (et seulement après) avoirdéclaré et initialisé un pointeur, il est possible d'accéder aucontenu de l'adresse mémoire pointée par le pointeur grâce à l'opérateur '*'

Si P un pointeur, on doit distinguer *P et &p : &P contient l’adresse de la variable dont le pointeur P pointe, et *P permet d’accéder à la valeur de la variable sur laquelle pointe P.

Exemple 6 :
                                int*P ;
                                int A=10 ;

                                // le pointeur P pointe sur A
                                P=&A ; 
                                
                                // équivalent à  A=A+1
                                *p=*p+1 ;    
                                
                                // équivalent à A=5
                                *p=5 ;
                            

Les opérations élémentaires sur les pointeurs

Priorité de * et &

Les opérateurs * et & ont la même priorité que les autres opérateurs unaires (la négation !, l'incrémentation ++, la décrémentation --). Dans une même expression, les opérateurs unaires *, &, !, ++, -- sont évalués de droite à gauche.

Arithmétique de pointeur

Un ensemble limité d'opérations arithmétiques peut être effectué sur des pointeurs.
Un pointeur peut être:

  • incrémenté (++)
  • décrémenté (--)
  • un entier peut être ajouté à un pointeur (+ ou + =)
  • un entier peut être soustrait d'un pointeur (- ou - =)

L'arithmétique de pointeur n'a pas de sens si elle n'est pas effectuée sur un tableau.

Remarque ! Les pointeurs contiennent des adresses. Ajouter deux adresses n’a aucun sens car il n’a aucune idée de ce que cela signifierait. La soustraction de deux adresses vous permet de calculer le décalage entre ces deux adresses.
Exemple 7 :
                                int x,y;
                                int *ptr;

                                ptr=&x;

                                // y = x + 1
                                Y = *P+1;

                                // X = X + 10 
                                *P = *P+10

                                // X += 2 
                                *P += 2

                                // X++
                                (*P)++
                            
Remarque ! Dans le dernier cas, les parenthèses sont nécessaires
On peut uniquement affecter des adresses à un pointeur Seule exception La valeur numérique 0(zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part'.
Exemple 8 :
                                int *P;
                                P = 0;
                            
Exemple 9 :

Soit p1 et p2 deux pointeurs sur int.

                                #include < stdio.h>
                                int main(void){
                                    int x=5;
                                    int *p1, *p2;

                                    p2=&x;

                                    // Copie le contenu de p2 vers p1 
                                    // p1 pointe alors sur le même objet que p2.
                                    p1=p2;

                                    printf("*p1 = %d \n", *p1);
                                    printf("*p2 = %d \n", *p2);

                                    return 0;
                                }
                            
*p1 = 5
*p2 = 5

Pointeurs et les tableaux

 Lien entre le nom d'un tableau à 1 dimension et les pointeurs

Les pointeurs et les tableaux sontconceptuellement très similaires en C
Nom du tableau = adresse du premier élément du tableau.

En simplifiant, nous pouvons retenir que le nom d’un tableau est un pointeur constant sur le premier élément du tableau.

Exemple 10 :

En déclarant un tableau A de type int et un pointeur P sur int,

                                #include < stdio.h>
                                int main(void){
                                    int A[10];
                                    int *P;

                                    // equivalente à P =&A[0];
                                    P = A;

                                    return 0;
                                }
                            

Si P pointe sur une composante quelconque d'un tableau, alors P+1 pointe sur la composante suivante.
Généralement P+i pointe sur la i-ième composant devant P.

Exemple 11 :
                                int main(void){
                                    int A[10];
                                    int *P;

                                    int x, i=6;

                                    // Le pointeur P pointe sur A[0] (P =&A[0])
                                    P = A;

                                    // x = A[1] 
                                    x = *(P+1);

                                    // x = A[2] 
                                    x = *(P+2); 


                                    // x = A[i] 
                                    x = *(P+i); 

                                    return 0;
                                }
                            

Puisque le nom tableau est un pointeur constant sur le premier élément on peut écrire :

Exemple 12 :
                                int main(void){
                                    int A[10];

                                    int x, i=6;

                                    // x = A[0]
                                    x = A;

                                    // x = A[1] 
                                    x = *(A+1);

                                    // x = A[2] 
                                    x = *(A+2); 


                                    // x = A[i] 
                                    x = *(A+i); 

                                    return 0;
                                }
                            
Remarque !Il existe toujours une différence essentielle entre un pointeur et le nom d'un tableau:
  • Un pointeur est une variable, donc des opérations comme P = A ou P++ sont permises.
  • Le nom d'un tableau est une constante, donc des opérations comme A = P ou A++ sont impossibles.
Exemple 12 :

Initialiser les éléments du tableau Tab à 1 (version sans pointeur)

                                int main(void){
                                    int Tab[10];
                                    int i;

                                    for(i=0 ; i < 10 ; i++){
                                        Tab[i]=1;
                                    }
                                    return 0;
                                }
                            
Exemple 14 :

Initialiser les éléments du tableau Tab à 1 avec les pointeurs (V1)

                                int main(void){
                                    int Tab[10], i;
                                    int *P;

                                    P=Tab;
                                    for(i=0 ; i < 10 ; i++){
                                        *(P+i) = 1;
                                    }
                                    return 0;
                                }
                            
Exemple 15 :

Initialiser les éléments du tableau Tab à 1 avec les pointeurs (V2)

                                int main(void){
                                    int Tab[10];
                                    int *P;

                                    P=Tab;
                                    for(P=Tab ; P < (Tab+10) ; P++){
                                        *P = 1;
                                    }
                                    return 0;
                                }
                            

 Lien entre le nom d'un tableau à 2 dimension et les pointeurs

un tableau 2d est un tableau de tableau de dimension 1

  • On peut donc dire que le nom d'un tableau 2d est l'adresse d'un tableau d'adresse de tableaux de dimension 1
  • On dit aussi qu'il s'agit d'un pointeur de pointeurs
Exemple 16 :
                                int TAB[6][7];
                            

On suppose Tab a deux dimensions défini comme suit:

                                int Tab[3][4] = {{ 0, 1, 2, 3}, {10,11,12,13}, {20,21,22,23}};
                            
  • Le nom du tableau Tab représente l'adresse du premier élément du tableau et pointe sur le tableau Tab[0] qui a les valeurs: {0,1,2,3}
  • L'expression (Tab+1) est l'adresse du deuxième élément dutableau et pointe sur Tab[1] qui a les valeurs: {10,11,12,13}
Au sens strict du terme,  un tableau à deux dimensions est un tableau unidimensionnel dont chaque composante est un tableau unidimensionnel. Ainsi, le premier élément de la matrice Tab est le vecteur {0,1,2,3}, le deuxième élément est {10,11,12,13} et ainsi de suite.
En général, Tab[i][j] est équivalent à *(*(Tab + i) + j)

La mémoire d’un ordinateur étant organisée de manière linéaire, il n’est pas possible de stocker le tableau à deux dimensions en lignes et en colonnes. Le concept de lignes et de colonnes n’est que théorique; en fait, un tableau à deux dimensions est stocké dans un ordre de rang majeur, c’est-à-dire que les rangées sont placées les unes à côté des autres. La figure suivante montre comment le tableau 2D ci-dessus sera stocké en mémoire.

Exemple 17 :

Initialiser les éléments d'un tableau Tab à deux dimensions à 1 (version sans pointeur)

                                int main(void){
                                    int Tab[5][4];
                                    int i, j;

                                    for(i=0 ; i < 5 ; i++){
                                        for(j=0 ; j < 4 ; j++){
                                            Tab[i][j]=1;
                                        }
                                    }
                                    return 0;
                                }
                            
Exemple 18 :

Initialiser les éléments du tableau Tab à 1 avec les pointeurs

                                int main(void){
                                    int Tab[5][4];
                                    // créer un pointeur de 5 éléments (nombre de ligne de Tab)
                                    int *P[5];
                                    int i, j;
                                    
                                    // pointer chaque élément (i) de P vers le premier élément
                                    // de chaque ligne i de Tab
                                    for (i = 0; i < 5; i++)
                                    {
                                        P[i] = Tab[i];
                                    }

                                    // remplir le tableau par des 1
                                    for (i = 0; i < 5; i++)
                                    {
                                        for (j = 0; j < 4; j++)
                                        {
                                            *(*(P + i) + j) = 1;
                                        }
                                    }
                                    return 0;
                                }
                            

Pour cet exemple nous avons utilisé un tableau des pointeurs *P[5] chaque pointeur P[i] pointe sur une ligne Tab[i] .

Exemple 19 :

Dans la mémoire les éléments d’un tableau à deux dimensions sont adjacents, on peut utiliser un pointeur qui pointe sur le premier élément du tableau et ensuite déplacer ce pointeur sur les autres éléments du tableau.

                                int main(void){
                                    int Tab[5][4];

                                    int *P;
                                    int i, j;
                                    
                                    P=Tab[0];

                                    // remplir le tableau par des 1
                                    for (i = 0; i < (5*4); i++)
                                    {
                                        *(P+i)=1;
                                    }
                                    return 0;
                                }
                            

Partager ce cours avec tes amis :

Rédigé par M. ESSADDOUKI

Learning a new programming language is an easy thing, but the most difficult thing is how to design efficient algorithms for real-world problems, so don't be a programmer, be a problems solver.

Cours Similaires :