« Langage C++/Pointeur, Tableaux et références » : différence entre les versions
→Tableaux "Statiques" : selon discussion |
Dépeuplement de Catégorie:Pages utilisant des balises source obsolètes |
||
Ligne 40 : | Ligne 40 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type>* <NomPointeur>[ = <Valeur>];</syntaxhighlight>}} |
||
Où <Type> est le type de la donnée en mémoire, "*" signifie que l’on a affaire à un pointeur, <NomPointeur> est l'identifiant du pointeur et <Valeur> l'adresse mémoire (unsigned long int) où le pointeur doit aller chercher la donnée. |
Où <Type> est le type de la donnée en mémoire, "*" signifie que l’on a affaire à un pointeur, <NomPointeur> est l'identifiant du pointeur et <Valeur> l'adresse mémoire (unsigned long int) où le pointeur doit aller chercher la donnée. |
||
Ligne 52 : | Ligne 52 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
char x = 'A'; // création de la variable char en pile |
char x = 'A'; // création de la variable char en pile |
||
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse de la variable en pile. |
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse de la variable en pile. |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 68 : | Ligne 68 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
char x = 'A'; // création de la variable char en pile |
char x = 'A'; // création de la variable char en pile |
||
char* p1 = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse. |
char* p1 = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse. |
||
char* p2 = p1; // copie la valeur de l'adresse pointée par p1 dans le pointeur p2. (p2 == p1), (p2 == &x) |
char* p2 = p1; // copie la valeur de l'adresse pointée par p1 dans le pointeur p2. (p2 == p1), (p2 == &x) |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 85 : | Ligne 85 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
char x = 'A'; // création de la variable char en pile |
char x = 'A'; // création de la variable char en pile |
||
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse. |
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse. |
||
char y = *p; // Déréférencement du pointeur "p" pour obtenir la valeur pointée (y == 'A') |
char y = *p; // Déréférencement du pointeur "p" pour obtenir la valeur pointée (y == 'A') |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 101 : | Ligne 101 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
int a; // création de la variable int en pile (sizeof(int) == 4) |
int a; // création de la variable int en pile (sizeof(int) == 4) |
||
int* p = &a; // création de la variable pointeur sur int et affectation de l'adresse de a. |
int* p = &a; // création de la variable pointeur sur int et affectation de l'adresse de a. |
||
Ligne 117 : | Ligne 117 : | ||
y = p - 1; // (y == &a + 4 octets) |
y = p - 1; // (y == &a + 4 octets) |
||
z = p - 1; // (z == &a) |
z = p - 1; // (z == &a) |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 130 : | Ligne 130 : | ||
Il est inacceptable d’utiliser la forme calculée des pointeurs pour accéder à des tableaux. Seule la forme entre crochets est valide. Le jour où un développeur instanciera un tableau dynamique sur un pointeur utilisé sous forme calculée, le programme plantera car le compilateur sera incapable de faire les déréférencements. }} |
Il est inacceptable d’utiliser la forme calculée des pointeurs pour accéder à des tableaux. Seule la forme entre crochets est valide. Le jour où un développeur instanciera un tableau dynamique sur un pointeur utilisé sous forme calculée, le programme plantera car le compilateur sera incapable de faire les déréférencements. }} |
||
Préférez donc la forme entre crochets |
Préférez donc la forme entre crochets |
||
{{Exemple | contenu = < |
{{Exemple | contenu = <syntaxhighlight lang="cpp">a = t[2][3];</syntaxhighlight> }} |
||
à la forme calculée |
à la forme calculée |
||
{{Attention|Avec_fond = oui| |
{{Attention|Avec_fond = oui| |
||
{{Exemple | contenu = < |
{{Exemple | contenu = <syntaxhighlight lang="cpp"> a=*(t+2*3)); </syntaxhighlight> }} |
||
}} |
}} |
||
Ligne 159 : | Ligne 159 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type> <NomTableau> [<Constante1>](...)[<ConstanteN>];</syntaxhighlight> |
||
ou |
ou |
||
< |
<syntaxhighlight lang="cpp"><Type> <NomTableau> [<Constante1>](...)[<ConstanteN>] = {{<Variable11,...,Variable1M>},(...),{<VariableN1,...,VariableNM>}};</syntaxhighlight> |
||
ou |
ou |
||
< |
<syntaxhighlight lang="cpp"><Type> <NomTableau> [](...)[] = {{<Variable11,...,Variable1M>},(...),{<VariableN1,...,VariableNM>}};</syntaxhighlight> |
||
}} |
}} |
||
Ligne 179 : | Ligne 179 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Valeurs constantes |
// Valeurs constantes |
||
const int Grilles = 4; |
const int Grilles = 4; |
||
Ligne 234 : | Ligne 234 : | ||
} |
} |
||
}; |
}; |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 378 : | Ligne 378 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Valeur variable |
// Valeur variable |
||
int trois = 3; |
int trois = 3; |
||
Ligne 391 : | Ligne 391 : | ||
int c = pInt1[2]; // c == 3 |
int c = pInt1[2]; // c == 3 |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 406 : | Ligne 406 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type>* <NomVariable>[<Constante1>](...)[<ConstanteN>] = new <Type>[<Variable>][<Constante1>](...)[<ConstanteN>];</syntaxhighlight>}} |
||
Où <Type> est le type du tableau, <NomVariable> est le nom identifiant du tableau, <Variable> est une variable entière non signée définissant la première dimension du tableau, <ConstanteN> sont des constantes entières non signées définissant les autres dimensions du tableau. |
Où <Type> est le type du tableau, <NomVariable> est le nom identifiant du tableau, <Variable> est une variable entière non signée définissant la première dimension du tableau, <ConstanteN> sont des constantes entières non signées définissant les autres dimensions du tableau. |
||
Ligne 415 : | Ligne 415 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Valeurs constantes |
// Valeurs constantes |
||
const int vColonnes = 2; |
const int vColonnes = 2; |
||
Ligne 427 : | Ligne 427 : | ||
delete [] vTableau; |
delete [] vTableau; |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 446 : | Ligne 446 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type> <NomTableau> = new <Type>[<Variable>];</syntaxhighlight>}} |
||
Où <Type> est le type du tableau, <NomTableau> est le nom du tableau et <Variable> une variable entière non signée. |
Où <Type> est le type du tableau, <NomTableau> est le nom du tableau et <Variable> une variable entière non signée. |
||
Ligne 458 : | Ligne 458 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
#include <iostream> |
#include <iostream> |
||
Ligne 615 : | Ligne 615 : | ||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 624 : | Ligne 624 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><TypeRetour> (*<NomPointeur>)(<TypeParametre><NomParametres>);</syntaxhighlight>}} |
||
Où "<TypeRetour>" est le type de retour de la méthode à pointer, "<NomPointeur>" est le nom attribué au pointeur, "<TypeParametre>" et "<NomParametres>" sont respectivement les types et noms des paramètres de la méthode |
Où "<TypeRetour>" est le type de retour de la méthode à pointer, "<NomPointeur>" est le nom attribué au pointeur, "<TypeParametre>" et "<NomParametres>" sont respectivement les types et noms des paramètres de la méthode |
||
Ligne 631 : | Ligne 631 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Méthode |
// Méthode |
||
bool MaMethode(int MonParametre) |
bool MaMethode(int MonParametre) |
||
Ligne 642 : | Ligne 642 : | ||
// Appel de la méthode via le pointeur |
// Appel de la méthode via le pointeur |
||
bool a = LaMethode(8); |
bool a = LaMethode(8); |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Remarquez bien les parenthèses autour de "*LaMethode". Si on ne les avait pas mis comme dans cet exemple : |
Remarquez bien les parenthèses autour de "*LaMethode". Si on ne les avait pas mis comme dans cet exemple : |
||
Ligne 649 : | Ligne 649 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Méthode |
// Méthode |
||
bool MaMethode(int MonParametre) |
bool MaMethode(int MonParametre) |
||
Ligne 660 : | Ligne 660 : | ||
// Appel de la méthode via le pointeur |
// Appel de la méthode via le pointeur |
||
bool a = LaMethode(8); |
bool a = LaMethode(8); |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 676 : | Ligne 676 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
//Classe |
//Classe |
||
class MaClasse |
class MaClasse |
||
Ligne 691 : | Ligne 691 : | ||
// Appel de la méthode via le pointeur |
// Appel de la méthode via le pointeur |
||
bool a = LaMethode(8); |
bool a = LaMethode(8); |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 701 : | Ligne 701 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type>& <NomRéférence> = <NomVariable>;</syntaxhighlight>}} |
||
Où "<Type>" est le type de la référence "<NomRéférence>" est l'identifiant de la référence et <NomVariable> est l'identifiant de la variable que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre variable après initialisation. |
Où "<Type>" est le type de la référence "<NomRéférence>" est l'identifiant de la référence et <NomVariable> est l'identifiant de la variable que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre variable après initialisation. |
||
Ligne 710 : | Ligne 710 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
//Variable à référencer |
//Variable à référencer |
||
int MaVariable = 5; |
int MaVariable = 5; |
||
Ligne 717 : | Ligne 717 : | ||
// Modification de la variable via la référence. |
// Modification de la variable via la référence. |
||
LaVariable = 8; |
LaVariable = 8; |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 727 : | Ligne 727 : | ||
{{Définition |
{{Définition |
||
| contenu = |
| contenu = |
||
'''Syntaxe:''' < |
'''Syntaxe:''' <syntaxhighlight lang="cpp"><Type> (&<NomRéférence>)(<TypeParam> <NomParam>) = <NomMéthode>;</syntaxhighlight>}} |
||
Où "<Type>" est le type de retour de la méthode à référencer, "<NomRéférence>" est l'identifiant de la référence, <TypeParam> et <NomParam> sont respectivement le type et le nom de l'image des paramètres de la méthode à référencer et <NomMéthode> est l'identifiant de la méthode que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre méthode après initialisation. |
Où "<Type>" est le type de retour de la méthode à référencer, "<NomRéférence>" est l'identifiant de la référence, <TypeParam> et <NomParam> sont respectivement le type et le nom de l'image des paramètres de la méthode à référencer et <NomMéthode> est l'identifiant de la méthode que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre méthode après initialisation. |
||
Ligne 734 : | Ligne 734 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
bool MaMethode(int MonParametre) |
bool MaMethode(int MonParametre) |
||
{ |
{ |
||
Ligne 743 : | Ligne 743 : | ||
// Appel de la méthode via la référence. |
// Appel de la méthode via la référence. |
||
bool a = LaMethode(8); |
bool a = LaMethode(8); |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 773 : | Ligne 773 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Création d'une variable "c" de type "char" non initialisée. |
// Création d'une variable "c" de type "char" non initialisée. |
||
char c; |
char c; |
||
Ligne 779 : | Ligne 779 : | ||
// cast de la valeur constante 0x43 (0x43 = 'C') en char. |
// cast de la valeur constante 0x43 (0x43 = 'C') en char. |
||
c = (char)0x43; |
c = (char)0x43; |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Ligne 787 : | Ligne 787 : | ||
| contenu = |
| contenu = |
||
< |
<syntaxhighlight lang="cpp"> |
||
// Création d'une variable "c" de type "char" non initialisée. |
// Création d'une variable "c" de type "char" non initialisée. |
||
char c; |
char c; |
||
Ligne 797 : | Ligne 797 : | ||
// cast de la valeur de la variable ('A' = 0x41 = 65) en int. |
// cast de la valeur de la variable ('A' = 0x41 = 65) en int. |
||
i = (int)c; |
i = (int)c; |
||
</syntaxhighlight> |
|||
</source> |
|||
}} |
}} |
||
Version du 27 juin 2020 à 16:24
Les Pointeurs
Les pointeurs servent à accéder aux données en mémoire par le biais de leur adresse. Le type pointeur est un entier non signé dont la taille dépend du type de processeur et du mode en cours. En gros, et pour simplifier, il utilise le format du "unsigned long int" actuellement en cours, où il enregistre l'adresse mémoire de la donnée à pointer.
Adresse | Valeur | Nom de variable |
---|---|---|
... | ... | ... |
0x635 | 'P' | x |
... | ... | ... |
0x7B8 | 0x00000635 | p |
... | ... | ... |
Voici la syntaxe :
Où <Type> est le type de la donnée en mémoire, "*" signifie que l’on a affaire à un pointeur, <NomPointeur> est l'identifiant du pointeur et <Valeur> l'adresse mémoire (unsigned long int) où le pointeur doit aller chercher la donnée.
Affectation d'adresse
En C++, il est utile de récupérer l'adresse d'une variable ou d'une constante en utilisant l'opérateur "&" qui signifie "adresse de" et se place à gauche de l'identifiant.
char x = 'A'; // création de la variable char en pile
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse de la variable en pile.
Comprendre : création d'une variable nommée "p" de type pointeur sur char "char*" prenant pour valeur "=" l'adresse de "&" la variable "x"
Mécanique d'Adressage
En C++, il peut être utile de copier l'adresse d'une variable pointée par un pointeur sur un autre pointeur. Cela se fait ainsi :
char x = 'A'; // création de la variable char en pile
char* p1 = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse.
char* p2 = p1; // copie la valeur de l'adresse pointée par p1 dans le pointeur p2. (p2 == p1), (p2 == &x)
On n'utilise pas l'opérateur "adresse de" dans ce cas là car "p1" renvoie l'adresse de "x"
Déréférencement
En C++, il peut être utile de récupérer la valeur d'une variable pointée par un pointeur. Cela se fait ainsi :
char x = 'A'; // création de la variable char en pile
char* p = &x; // création de la variable pointeur sur char en pile et affectation de l'adresse.
char y = *p; // Déréférencement du pointeur "p" pour obtenir la valeur pointée (y == 'A')
Arithmétique des pointeurs
En C++, les pointeurs ont leur propre algorithmique. L'incrémentation et la décrémentation est particulière pour ce type. En fait, l'incrémentation (ainsi que la décrémentation) se fait en multipliant la taille du type du pointeur (obtenue avec sizeof()) par la valeur que l’on veut lui ajouter/retrancher.
L'arithmétique de pointeur est un concept particulier qu’il est possible d’éviter avec les notations liées au tableau. En particulier en C++, il est intéressant de s'interdire de recourir à l’arithmétique de pointeurs.
int a; // création de la variable int en pile (sizeof(int) == 4)
int* p = &a; // création de la variable pointeur sur int et affectation de l'adresse de a.
int* x = p++; // création de la variable pointeur sur int x (x == &a)
int* y = p; // création de la variable pointeur sur int y (y == &a + 4 octets)
int* z = ++p; // création de la variable pointeur sur int z (z == &a + 8 octets)
x = p--; // (x == &a + 8 octets)
y = p; // (y == &a + 4 octets)
z = --p; // (z == &a)
// plus explicitement
x = p; // (x == &a)
y = p + 1; // (y == &a + 4 octets)
z = p + 1; // (z == &a + 8 octets)
x = p; // (x == &a + 8 octets)
y = p - 1; // (y == &a + 4 octets)
z = p - 1; // (z == &a)
Ce comportement permet la création et l’utilisation des tableaux. Nous verrons cela un peu plus bas.
Lorsque les pointeurs sont créés, ils prennent la valeur d'adresse qu’il y avait à l'emplacement qu’ils occupent. C'est pour cela qu’il faut impérativement les initialiser avant de s'en servir. Dans le cas contraire on peut assister, parfois, à des comportements des plus étranges.
Le pire des cas est celui du pointeur non initialisé à qui l’on affecte une valeur. Dans certains cas rares, l’application fonctionne sur l'ordinateur de développement car la donnée que le pointeur va prendre comme adresse, correspond à une adresse valide et donc le programme se déroule normalement.
Mais après installation sur le poste client, l’application plante car la donnée en mémoire n'est plus la même et ne correspond plus à une adresse valide.
Il est inacceptable d’utiliser la forme calculée des pointeurs pour accéder à des tableaux. Seule la forme entre crochets est valide. Le jour où un développeur instanciera un tableau dynamique sur un pointeur utilisé sous forme calculée, le programme plantera car le compilateur sera incapable de faire les déréférencements. |
Préférez donc la forme entre crochets
à la forme calculée
|
Les Tableaux
En C++, les tableaux permettent de mémoriser des ensembles de données de même type. Il en existe 2 sortes, les tableaux statiques et les tableaux dynamiques.
La différence entre tableaux statiques et dynamiques est que les tableaux statiques sont dans tous les cas des groupes d'octets contigus monolithiques dans la pile alors que les tableaux dynamiques multidimensionnels sont, le plus souvent, des groupes d'octets contigus disséminés en plusieurs paquets dans le tas.
Tableaux "Statiques"
Le fait qu’ils soient enregistrés en pile fait que leur taille doit être connue dès la compilation et donc être constante. En effet, pour le processeur, il serait mal vu de pouvoir redimensionner le tableau après la création d'autres variables en pile. Dans le cas de l'agrandissement, on écraserait les nouvelles variables et dans le cas de la diminution, on ferait des "TROUS" dans la pile, qui alors s'effondrerait.(Rappelez vous qu’il faut considérer la pile comme une pile d'assiettes)
On peut calculer le volume que le tableau statique occupe en mémoire, en multipliant la taille du type utilisé par le nombre d'éléments.
Ainsi un tableau tridimensionnel de 4 * 3 * 2 valeurs int, fera :
4(grilles) * 3(lignes) * 2(colonnes) * 4(éléments du type int) = 24(éléments)* 4(Octets du type int) = 96 Octets contigus en pile.
Voici la syntaxe :
<Type> <NomTableau> [<Constante1>](...)[<ConstanteN>];
ou
<Type> <NomTableau> [<Constante1>](...)[<ConstanteN>] = {{<Variable11,...,Variable1M>},(...),{<VariableN1,...,VariableNM>}};
ou
<Type> <NomTableau> [](...)[] = {{<Variable11,...,Variable1M>},(...),{<VariableN1,...,VariableNM>}};
Où <Type> est le type des éléments du tableau, <NomTableau> est le nom de la variable tableau, [](...)[] indique que c’est un tableau à N dimensions, <ConstanteN> est le nombre d'éléments, constant, de la dimension N du tableau (facultatif si on a défini une liste de valeurs), = assigne des valeurs au tableau (facultatif si <ConstanteN> est présent), {{},(...),{}} est une liste de valeurs des dimensions (il y en a autant que <ConstanteN>, facultatif si <ConstanteN> est présent), et <VariableN1,...VariableNM> sont les différentes valeurs que le tableau doit affecter aux éléments de la ligne N (il doit y en avoir autant que <ConstanteN>, facultatif si <ConstanteN> est présent).
Concrètement, voyons ce que ça donne si on reprend notre exemple :
// Valeurs constantes
const int Grilles = 4;
const int Lignes = 3;
const int Colonnes = 2;
// Valeur variables
int deux = 2;
// Différentes syntaxes pour la création des tableaux statiques
int t1 [Grilles][Lignes][Colonnes]; // création d'un tableau d'entiers de 4 * 3 * 2 = 24 éléments non initialisés.
// Création d'un tableau d'entiers de 24 éléments initialisés.
int t2 [Grilles][Lignes][Colonnes] = {
{
{1, deux},
{3, 4},
{5, 6}
},
{
{7, 8},
{9, 10},
{11, 12}
},
{
{13, 14},
{15, 16},
{17, 18}
},
{
{19, 20},
{21, 22},
{23, 24}
}
};
// Création d'un tableau d'entiers de 24 éléments initialisés.
int t3 [][3][2] = {
{
{1, deux},
{3, 4},
{5, 6}
},
{
{7, 8},
{9, 10},
{11, 12}
},
{
{13, 14},
{15, 16},
{17, 18}
},
{
{19, 20},
{21, 22},
{23, 24}
}
};
Les tableaux statiques, sont organisés par lignes. Cela signifie que lorsque l’on incrémente l'indice du tableau, on change de colonne plus rapidement que ce que l’on change de ligne.
Pour reprendre notre exemple, cela donnerait (en supposant que l'adresse de base du tableau t1 soit 0x02C0) :
Adresse | Index | no Élément | Grille | Ligne | Colonne | Valeur |
---|---|---|---|---|---|---|
0x02C0 | t1[0][0][0] | 0 | 0 | 0 | 0 | 1 |
0x02C4 | t1[0][0][1] | 1 | 0 | 0 | 1 | 2 |
0x02C8 | t1[0][1][0] | 2 | 0 | 1 | 0 | 3 |
0x02CC | t1[0][1][1] | 3 | 0 | 1 | 1 | 4 |
0x02D0 | t1[0][2][0] | 4 | 0 | 2 | 0 | 5 |
0x02D4 | t1[0][2][1] | 5 | 0 | 2 | 1 | 6 |
0x02D8 | t1[1][0][0] | 6 | 1 | 0 | 0 | 7 |
... | ... | ... | ... | ... | ... | ... |
0x03A8 | t1[2][2][1] | 17 | 2 | 2 | 1 | 18 |
0x03AC | t1[3][0][0] | 18 | 3 | 0 | 0 | 19 |
0x03B0 | t1[3][0][1] | 19 | 3 | 0 | 1 | 20 |
0x03B4 | t1[3][1][0] | 20 | 3 | 1 | 0 | 21 |
0x03B8 | t1[3][1][1] | 21 | 3 | 1 | 1 | 22 |
0x03BC | t1[3][2][0] | 22 | 3 | 2 | 0 | 23 |
0x03C0 | t1[3][2][1] | 23 | 3 | 2 | 1 | 24 |
Pointeurs et Tableaux Statiques
Nous avons pu voir tout à l’heure que l'arithmétique des pointeurs permet de travailler sur plus d'une valeur voyons ce que cela donne :
// Valeur variable
int trois = 3;
// Différentes syntaxes pour la création des tableaux statiques
int t[4] = {1, 2, trois, 4}; // Création d'un tableau d'entier de 4 éléments initialisés.
trois = 0; // trois = 0
int* pInt1 = t; // Création d'un pointeur sur entiers auquel on affecte t
int* pInt2 = &t[0]; // Création d'un pointeur sur entiers auquel on affecte l'adresse du premier élément du tableau t (pInt1 == pInt2)
int a = *(pInt1+2); // a == 3
int b = *(t+2); // b == 3
int c = pInt1[2]; // c == 3
Tableaux "Dynamiques"
Pour un tableau dynamique, il existe deux méthodes.
Tableaux Compacts
On peut utiliser le format des tableaux statiques multidimensionnels que l’on reproduit en tas.
Voici la syntaxe de déclaration :
<Type>* <NomVariable>[<Constante1>](...)[<ConstanteN>] = new <Type>[<Variable>][<Constante1>](...)[<ConstanteN>];
Où <Type> est le type du tableau, <NomVariable> est le nom identifiant du tableau, <Variable> est une variable entière non signée définissant la première dimension du tableau, <ConstanteN> sont des constantes entières non signées définissant les autres dimensions du tableau.
Aucun tableau dynamique n'est assignable à la déclaration. On ne peut pas en C++ assigner des valeurs à un tableau dynamique lors de sa création.
// Valeurs constantes
const int vColonnes = 2;
const int vLignes = 3;
// Valeur variables
int vGrilles = 4;
int* vTableau[vLignes][vColonnes] = new int[vGrilles][vLignes][vColonnes]; // Création d'un tableau d'entiers de 24 éléments.
//...
delete [] vTableau;
Cela n’est pas très judicieux pour trois raisons :
- La première c’est que les tailles des dimensions du tableau sont fixées définitivement (à moins de recréer le tableau complet).
- La deuxième c’est que les différents éléments ne sont pas désolidarisables du tableau.
- La troisième pour redimensionner le tableau il faut le recopier intégralement.
Techniquement, on peut faire mieux.
La seule raison valable qui peut amener à utiliser ces types de tableaux dynamiques est leur forme compacte. Ceci dit ils ne font réellement gagner de l'espace que sur les tableaux dont on connaît à l'avance la longueur. En effet, la lourdeur du traitement pour ajouter ou soustraire des éléments est souvent rédhibitoire.
Il ne reste donc qu'une solution techniquement valable en environnement PC : L'indirection de pointeurs multiples.
Indirection de Pointeurs
Le but du jeu est de créer un tableau de pointeurs où chaque pointeur pointe vers un autre tableau de pointeurs... (ce récursivement pour chaque dimension)... où chaque pointeur pointe vers un élément.
Où <Type> est le type du tableau, <NomTableau> est le nom du tableau et <Variable> une variable entière non signée.
Voyons comment cela est représenté en mémoire :
Voici un exemple concret qui montre comment créer/détruire et manipuler un tableau utilisant l'indirection de pointeurs.
#include <iostream>
using namespace std;
// Création d'un tableau de pointeurs triples d'entier.
int**** mCreerTableauDynamiquePointeursTriplesEntiers(int pGrilles, int pLignes, int pColonnes)
{
int**** vTableau = new int***[pGrilles];
// Pour chaque pointeur triple du tableau
for(int vIndexGrille = 0; vIndexGrille < pGrilles; vIndexGrille ++)
{
// Créer un tableau de pointeurs doubles dans vTableau[vIndexGrille].
vTableau[vIndexGrille] = new int** [pLignes];
// Pour chaque pointeur double du tableau vTableau[vIndexGrille].
for(int vIndexLigne = 0; vIndexLigne < pLignes; vIndexLigne++)
{
// Créer un tableau de pointeurs dans vTableau[vIndexGrille][vIndexLigne].
vTableau[vIndexGrille][vIndexLigne] = new int* [pColonnes];
// Pour chaque pointeur du tableau vTableau[vIndexGrille][vIndexLigne]
for(int vIndexColonne = 0; vIndexColonne < pColonnes; vIndexColonne++)
{
// Créer un entier dans vTableau[vIndexGrille][vIndexLigne][vIndexColonne].
vTableau[vIndexGrille][vIndexLigne][vIndexColonne] = new int;
// Remplit l'entier avec son numéro d'élément
*(vTableau[vIndexGrille][vIndexLigne][vIndexColonne]) = (vIndexGrille * pLignes * pColonnes) + (vIndexLigne * pColonnes) + vIndexColonne + 1;
}
}
}
return vTableau;
}
void mDetruireTableauDynamiquePointeursTriplesEntiers(int**** pTableau, int pGrilles, int pLignes, int pColonnes)
{
// Pour chaque pointeur triple du tableau pTableau
for(int vIndexGrille = 0; vIndexGrille < pGrilles; vIndexGrille++)
{
// Pour chaque pointeur double du tableau pTableau[vIndexGrille]
for(int vIndexLigne = 0; vIndexLigne < pLignes; vIndexLigne++)
{
// Pour chaque pointeur du tableau pTableau[vIndexGrille][vIndexLigne]
for(int vIndexColonne = 0; vIndexColonne < pColonnes; vIndexColonne++)
{
// Détruire l'entier dans pTableau[vIndexGrille][vIndexLigne][vIndexColonne].
delete pTableau[vIndexGrille][vIndexLigne][vIndexColonne];
}
// Détruire le tableau de pointeurs dans pTableau[vIndexGrille][vIndexLigne].
delete [] pTableau[vIndexGrille][vIndexLigne];
}
// Détruire le tableau de pointeurs doubles dans pTableau[vIndexGrille].
delete [] pTableau[vIndexGrille];
}
// Destruction du tableau de pointeurs triples d'entiers.
delete [] pTableau;
}
void mAfficherTableau(int**** pTableau, int pGrilles, int pLignes, int pColonnes)
{
for(int vIndexGrille = 0; vIndexGrille < pGrilles; vIndexGrille++)
{
cout << "Grille : " << vIndexGrille + 1 << endl;
for(int vIndexLigne = 0; vIndexLigne < pLignes; vIndexLigne++)
{
cout << " Ligne : " << vIndexLigne + 1 << endl;
for(int vIndexColonne = 0; vIndexColonne < pColonnes; vIndexColonne++)
{
cout << " Colonne : " << vIndexColonne + 1 << " = " << *pTableau[vIndexGrille][vIndexLigne][vIndexColonne] << endl;
}
}
}
cout << endl << "Appuyez sur [Entree] pour continuer..." << endl;
getchar();
}
void mInverserColonnes(int**** pTableau, int pGrilles, int pLignes, int pColonnes)
{
for(int vIndexGrille = 0; vIndexGrille < pGrilles; vIndexGrille++)
{
for(int vIndexLigne = 0; vIndexLigne < pLignes; vIndexLigne++)
{
for(int vIndexColonne = 0; vIndexColonne < (pColonnes / 2); vIndexColonne++)
{
int* vTemporaire = pTableau[vIndexGrille][vIndexLigne][vIndexColonne];
pTableau[vIndexGrille][vIndexLigne][vIndexColonne] = pTableau[vIndexGrille][vIndexLigne][(pColonnes - 1) - vIndexColonne];
pTableau[vIndexGrille][vIndexLigne][(pColonnes - 1) - vIndexColonne] = vTemporaire;
}
}
}
}
void mInverserLignes(int**** pTableau, int pGrilles, int pLignes)
{
for(int vIndexGrille = 0; vIndexGrille < pGrilles; vIndexGrille++)
{
for(int vIndexLigne = 0; vIndexLigne < (pLignes / 2); vIndexLigne++)
{
int** vTemporaire = pTableau[vIndexGrille][vIndexLigne];
pTableau[vIndexGrille][vIndexLigne] = pTableau[vIndexGrille][(pLignes - 1) - vIndexLigne];
pTableau[vIndexGrille][(pLignes - 1) - vIndexLigne] = vTemporaire;
}
}
}
void mInverserGrilles(int**** pTableau, int pGrilles)
{
for(int vIndexGrille = 0; vIndexGrille < (pGrilles / 2); vIndexGrille++)
{
int*** vTemporaire = pTableau[vIndexGrille];
pTableau[vIndexGrille] = pTableau[(pGrilles - 1) - vIndexGrille];
pTableau[(pGrilles - 1) - vIndexGrille] = vTemporaire;
}
}
int main(int argc, char* argv[])
{
// Valeur variables
int vColonnes = 2;
int vLignes = 3;
int vGrilles = 4;
int**** vTableau = mCreerTableauDynamiquePointeursTriplesEntiers(vGrilles, vLignes, vColonnes);
cout << "Tableau a l'etat initial" << endl << endl;
// Affiche le tableau
mAfficherTableau(vTableau, vGrilles, vLignes, vColonnes);
// Le tableau est trié du plus petit au plus grand.
cout << "Inversion des Colonnes" << endl << endl;
// Intervertit les colonnes
mInverserColonnes(vTableau, vGrilles, vLignes, vColonnes);
// Affiche le résultat
mAfficherTableau(vTableau, vGrilles, vLignes, vColonnes);
cout << "Invertion des Lignes" << endl << endl;
// Intervertit les lignes
mInverserLignes(vTableau, vGrilles, vLignes);
// Affiche le résultat
mAfficherTableau(vTableau, vGrilles, vLignes, vColonnes);
cout << "Inversion des Grilles" << endl << endl;
// Intervertit les Grilles
mInverserGrilles(vTableau, vGrilles);
// Affiche le résultat
mAfficherTableau(vTableau, vGrilles, vLignes, vColonnes);
// Le tableau est trié du plus grand au plus petit.
mDetruireTableauDynamiquePointeursTriplesEntiers(vTableau, vGrilles, vLignes, vColonnes);
return 0;
}
Pointeurs sur Méthodes
En C++, il est possible de créer des pointeurs sur des méthodes. Cela est surtout utile pour lier les fonctions d'une bibliothèque de liens dynamiques ou, plus intéressant, pour appeler des méthodes que l’on ne connaît pas à l'avance mais qui auront un prototype que l’on a défini. Cela permet, par exemple, de créer une liste de tâches à accomplir suite à un évènement donné.
Où "<TypeRetour>" est le type de retour de la méthode à pointer, "<NomPointeur>" est le nom attribué au pointeur, "<TypeParametre>" et "<NomParametres>" sont respectivement les types et noms des paramètres de la méthode
// Méthode
bool MaMethode(int MonParametre)
{
return true;
}
//Pointeur correspondant à la méthode
bool (*LaMethode)(int Params);
LaMethode = MaMethode;
// Appel de la méthode via le pointeur
bool a = LaMethode(8);
Remarquez bien les parenthèses autour de "*LaMethode". Si on ne les avait pas mis comme dans cet exemple :
// Méthode
bool MaMethode(int MonParametre)
{
return true;
}
//Pointeur correspondant à la méthode
bool *LaMethode(int Params);
LaMethode = MaMethode;
// Appel de la méthode via le pointeur
bool a = LaMethode(8);
Le compilateur aurait compris "LaMethode" une méthode qui accepte un paramètre "Params" de type "int" et retourne un pointeur de type "bool";
Pointeurs sur Méthodes Encapsulées
Il est possible de créer des pointeurs sur des méthodes qui sont encapsulées dans des classes à deux conditions :
- la méthode doit être publique,
- la méthode doit être statique.
//Classe
class MaClasse
{
public:
// Méthode
static bool MaMethode(int MonParametre)
{
return true;
}
}
//Pointeur correspondant à la méthode
bool (*LaMethode)(int Params) = MaClasse::MaMethode;
// Appel de la méthode via le pointeur
bool a = LaMethode(8);
Les Références
Devant la difficulté qu'avaient la plupart des programmeurs de l'époque, à gérer convenablement la mémoire avec les pointeurs, le C++ a introduit la notion de référence. Une référence est un ALIAS, un autre nom, pour une variable DÉJÀ EXISTANTE. À l'inverse du pointeur, une référence n'occupe pas de place en mémoire. Le compilateur la remplace par le nom de la variable actuellement assignée dans le code après avoir déroulé les traitements mais avant de traduire ce code en binaire.
Où "<Type>" est le type de la référence "<NomRéférence>" est l'identifiant de la référence et <NomVariable> est l'identifiant de la variable que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre variable après initialisation.
Ou plus concrètement :
//Variable à référencer
int MaVariable = 5;
//Référence correspondant à la variable;
int& LaVariable = MaVariable;
// Modification de la variable via la référence.
LaVariable = 8;
Références sur Méthodes
À l'instar du pointeur, il est possible de référencer une méthode.
Où "<Type>" est le type de retour de la méthode à référencer, "<NomRéférence>" est l'identifiant de la référence, <TypeParam> et <NomParam> sont respectivement le type et le nom de l'image des paramètres de la méthode à référencer et <NomMéthode> est l'identifiant de la méthode que la référence doit désigner. Contrairement aux pointeurs, la référence doit impérativement être assignée dès la déclaration et il n’est pas possible de réassigner la référence à une autre méthode après initialisation.
bool MaMethode(int MonParametre)
{
return true;
}
//Référence correspondant à la méthode
bool (&LaMethode)(int Params) = MaMethode;
// Appel de la méthode via la référence.
bool a = LaMethode(8);
Le Casting :
L'une des fonctionnalités les plus appréciées dans la manipulation des variables est l'opérateur de casting, ou de refonte, que sont les parenthèses "()".
Syntaxe: |
---|
(<Type>)[<Constante>|<Variable>] |
Où <Type> est le type dans lequel la valeur <Constante> ou <Variable> doit être refondue.
En fait, on peut refondre des valeurs de types différents de manière sûre si au moins l'une de ces conditions est remplie :
- La valeur source peut être transcodée dans le type de destination.(ex. : (char)'A'; est équivalent à (char)41; "41" est un const int)
- Le type de la valeur source peut être approximée ou être un sous ensemble du type de la variable de destination (numérique, ex. : (double)int ou (int)double).
- Le type de la valeur source hérite du type de la variable de destination (upcasting).
- Le type de la valeur source est parente du type de la variable de destination (downcasting).
Attention : L'utilisation de l'opérateur de refonte est à utiliser avec précautions. Le compilateur ne vous avertira pas si un dépassement de capacité a lieu ou si vous faites des downcasting (refontes descendantes) avec des types de nature différente |
Exemples :
Exemple de cast pour une valeur constante
// Création d'une variable "c" de type "char" non initialisée.
char c;
// cast de la valeur constante 0x43 (0x43 = 'C') en char.
c = (char)0x43;
Autre exemple pour une variable :
// Création d'une variable "c" de type "char" non initialisée.
char c;
// Création d'une variable "i" de type "int" non initialisée.
int i;
// Affectation du caractère "A" à la variable c.
c = 'A';
// cast de la valeur de la variable ('A' = 0x41 = 65) en int.
i = (int)c;