Bonsoir @Cric,
En effet, le code est lu, compilé et executé de manière séquentielle. Avant d’utiliser une variable, un type, une structure de données, une classe, un objet, etc. ou d’invoquer une fonction ou une méthode, tu dois nécessairement la (ou le) déclarer au préalable.
Par exemple, le code suivant :
#include <Gamebuino-Meta.h>
struct Ball {
uint8_t x, y;
};
struct Paddle {
uint8_t x, y;
void handleCollision() {
if (ball.x < x) { }
}
};
Ball ball;
Paddle player, computer;
void setup() { gb.begin(); }
void loop() {
gb.waitForUpdate();
player.handleCollision();
}
… provoquera une erreur à la compilation :
/Users/steph/.../pong/pong.ino: In member function 'void Paddle::handleCollision()':
pong:10:13: error: 'ball' was not declared in this scope
if (ball.x < x) { }
^~~~
… puisque la variable globale ball
n’est déclarée qu’après l’instruction qui en fait usage.
Pour résoudre ceci, tu peux donc déclarer l’existence de la variable ball
avant de l’utiliser :
#include <Gamebuino-Meta.h>
struct Ball {
uint8_t x, y;
};
Ball ball;
struct Paddle {
uint8_t x, y;
void handleCollision() {
if (ball.x < x) { }
}
};
Paddle player, computer;
void setup() { gb.begin(); }
void loop() {
gb.waitForUpdate();
player.handleCollision();
}
Et là ça marche beaucoup mieux…
Mais plutôt que d’utiliser directement la variable globale ball
dans la méthode handleCollision()
, tu peux la passer comme argument à la méthode :
#include <Gamebuino-Meta.h>
struct Ball {
uint8_t x, y;
};
struct Paddle {
uint8_t x, y;
void handleCollision(Ball b) {
if (b.x < x) { }
}
};
Ball ball;
Paddle player, computer;
void setup() { gb.begin(); }
void loop() {
gb.waitForUpdate();
player.handleCollision(ball);
}
Ici, l’instruction :
player.handleCollision(ball);
… passe la variable ball
par valeur à la méthode handleCollision()
:
void handleCollision(Ball b) { ... }
Autrement dit, la variable b
qui désigne cette valeur reçoit une copie de l’objet ball
, et non l’objet ball
en tant que tel. Par conséquent, si la méthode handleCollision()
modifie la valeur d’un attribut de la variable b
, par exemple en écrivant :
void handleCollision(Ball b) {
b.x = 0;
}
C’est la valeur de l’attribut x
de la copie qui sera modifiée !.. et ça n’aura aucun effet sur la valeur de l’attribut x
de l’objet ball
original.
Pour résoudre cette problématique, C++ te permet d’invoquer une fonction ou une méthode en lui passant comme argument, non pas la copie de l’objet, mais une référence à l’objet lui-même, c’est à dire une variable qui désigne son adresse mémoire. De cette manière, la fonction ou la méthode peut avoir directement accès à l’objet original (et modifier éventuellement la valeur de ses attributs).
Dans ce cas, on écrira les choses ainsi :
#include <Gamebuino-Meta.h>
struct Ball {
uint8_t x, y;
};
struct Paddle {
uint8_t x, y;
void handleCollision(Ball &b) {
if (b.x < x) { }
}
};
Ball ball;
Paddle player, computer;
void setup() { gb.begin(); }
void loop() {
gb.waitForUpdate();
player.handleCollision(ball);
}
La fonction déclare accepter une référence à un objet de type Ball
(remarque bien l’usage de l’opérateur &
qui préfixe le nom de la variable b
) :
void handleCollision(Ball &b) { ... }
Et peut alors modifier directement l’objet original par le biais de sa référence, puisqu’il s’agit en réalité de son adresse mémoire. Par exemple, si on écrit :
void handleCollision(Ball &b) {
b.x = 0;
}
Et qu’on invoque la méthode ainsi :
player.handleCollision(ball);
… alors la valeur de l’attribut x
de la variable globale ball
sera modifiée et vaudra 0
après l’invocation de la méthode handleCollision()
.
Néanmoins, la possibilité qu’une méthode puisse modifier la valeur d’une variable qui lui est passée par référence peut avoir des effets de bord indésirables. On peut donc empêcher cela en déclarant que l’objet passé par référence doit rester constant (ne doit pas être modifié) :
void handleCollision(const Ball &b) { ... }
Par conséquent, si tu écris :
void handleCollision(const Ball &b) {
b.x = 0;
}
Tu obtiendras l’erreur de compilation suivante :
/Users/steph/.../pong/pong.ino: In member function 'void Paddle::handleCollision(const Ball&)':
pong:10:18: error: assignment of member 'Ball::x' in read-only object
b.x = 0;
^
… te signifiant que tu ne peux pas affecter une valeur à l’attribut d’un objet en lecture seule (imposée par le mot-clé const
).
Mais alors, tu vas me dire : “Ben il suffit de lui passer la copie et le tour est joué !”
Pas tout à fait => une copie implique de réserver un espace mémoire supplémentaire pour recevoir les données de la copie justement ! Alors qu’en passant l’objet par référence, tu n’effectues pas de copie de l’objet original en mémoire. Et c’est une énorme différence, surtout si ton objet comporte de nombreux attributs, ou des attributs qui pèsent lourd ! Ça te permet donc d’économiser de l’espace mémoire, dans tous les cas, que tu modifies ou non la valeur des attributs de l’objet passé par référence.
Il existe des notions très voisines à tout ce que je viens de t’expliquer, je veux parler des pointeurs. Mais je préfère ne pas les aborder tout de suite pour ne pas t’embrouiller la tête et te laisser le temps de digérer ma réponse…
Est-ce que j’ai été suffisamment clair ?
Pour résumer, voilà le code qui me paraît le plus approprié par-rapport à ton problème initial :
#include <Gamebuino-Meta.h>
struct Ball {
uint8_t x, y;
};
struct Paddle {
uint8_t x, y;
void handleCollision(Ball &b) {
if (b.x < x) { }
}
};
Ball ball;
Paddle player, computer;
void setup() { gb.begin(); }
void loop() {
gb.waitForUpdate();
player.handleCollision(ball);
}
Je n’ai pas déclaré void handleCollision(const Ball &b)
parce-que j’imagine que ta gestion des collisions est susceptible de modifier les attributs de la balle. Néanmoins, de manière générale, pose-toi toujours la question de savoir de quel objet relève la responsabilité d’exécuter telle ou telle action, surtout quand il perturbe son entourage…
Pour finir, @Jicehel t’avait répondu avec 3 messages soulevant, par erreur, des points essentiels quant aux notions qui caractérisent les objets en C++. Mais il les a malheureusement retirés, et c’est bien dommage… Je n’aborderai donc pas ces points ici pour ne pas faire dériver la discussion en-dehors de ton propre questionnement. Je trouverai bien l’occasion d’y revenir à un moment clef