Parcours
/
C++
C++
/
Exercices
/
Fondation Speedywagon
Fondation Speedywagon

Fondation Speedywagon

Exercice d'apprentissage

Introduction

Comme beaucoup d'autres langages, le C++ a des pointeurs. Tu sais déjà que les références et les pointeurs se ressemblent, mais considère-les comme un niveau plus proche du fonctionnement interne de ton ordinateur. Les pointeurs sont des variables qui contiennent des adresses d'objets. Ils servent à interagir directement avec les objets, ce qui permet l'allocation dynamique de mémoire et une manipulation efficace des données en C++.

Si les pointeurs sont nouveaux pour toi, ils peuvent sembler un peu mystérieux, mais une fois que tu t'y es habitué, ils sont tout à fait simples.

Ils sont une partie cruciale du C++, alors prends le temps de bien les comprendre. La version minimale de ce concept est aussi appelée dumb pointer ou pointeur brut. Avec le C++ moderne, il existe aussi des pointeurs intelligents, mais le type de base n'est pas intelligent du tout et tu dois gérer tout le travail manuellement.

Avant d'entrer dans les détails, il vaut la peine de comprendre à quoi servent les pointeurs. Les pointeurs permettent de partager l'adresse d'un objet avec d'autres parties de notre programme, ce qui est utile pour deux raisons majeures :

  1. Comme les références, les pointeurs évitent les copies et aident à réduire l'empreinte en ressources de ton programme.
  2. Contrairement aux références, les pointeurs peuvent être réaffectés à d'autres objets.
  3. Les pointeurs peuvent aussi pointer vers une valeur nulle, pour indiquer qu'ils ne pointent actuellement vers aucun objet.

Syntaxe générale

En C++, déclarer un pointeur consiste à préciser le type de données vers lequel il pointe, puis un astérisque (*) et le nom du pointeur. Quand on déclare un pointeur, il n'est pas initialisé automatiquement. Sans affectation explicite, un pointeur contient généralement une valeur indéterminée, souvent appelée « adresse poubelle ». Même si certains compilateurs initialisent les pointeurs à nullptr, ce comportement n'est pas garanti pour tous les compilateurs ; il est donc essentiel de ne pas s'y fier. La bonne pratique est d'initialiser explicitement les pointeurs bruts et de vérifier qu'ils ne sont pas nuls avant de les utiliser, afin d'éviter d'éventuels problèmes.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Pour affecter l'adresse d'une variable à un pointeur, on utilise l'opérateur d'adresse (&). On déréférence un pointeur à l'aide de l'opérateur d'indirection (*).

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Attention : le déréférencement doit se faire explicitement, alors qu'une référence fonctionne simplement comme un alias.

Arithmétique des pointeurs

L'arithmétique des pointeurs permet d'effectuer des opérations arithmétiques sur des pointeurs, ce qui est particulièrement utile lorsqu'on travaille avec des tableaux. Ajouter un entier à un pointeur le fait pointer vers un autre élément.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

L'arithmétique des pointeurs en C++ peut facilement mener à un comportement indéfini si elle n'est pas manipulée avec précaution. Un comportement indéfini peut se manifester par des résultats de programme inattendus, des plantages, voire des failles de sécurité. Un exemple tristement célèbre des conséquences d'un comportement indéfini est la explosion de la fusée Ariane 5 en 1996, où une exception logicielle provoquée par la conversion d'un nombre à virgule flottante de 64 bits en un entier signé de 16 bits a entraîné une défaillance catastrophique.

Accéder aux variables membres

En C++, l'opérateur -> sert à accéder aux membres d'un objet à travers un pointeur vers cet objet. C'est une notation raccourcie qui simplifie l'accès aux membres des objets pointés par un pointeur. Par exemple, si ptr est un pointeur vers un objet qui a une variable membre x, au lieu d'écrire (*ptr).x, tu peux utiliser directement ptr->x. Cet opérateur améliore la lisibilité du code et réduit la verbosité lorsqu'on travaille avec des pointeurs vers des objets.

Voici un petit exemple, avec une struct Superhero qui possède une variable membre superpower. La fonction principale crée un pointeur dianaPrince vers un objet Superhero (qui représente Wonder Woman). L'opérateur -> sert à accéder à la variable membre superpower, ce qui met en valeur le célèbre « Lasso of Truth » de Wonder Woman.

struct Superhero {
    std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

Pointeurs et références

Les pointeurs et les références permettent tous deux un accès indirect aux objets, mais ils diffèrent par leurs capacités et par les questions de sûreté qu'ils posent. Les pointeurs offrent la souplesse de changer l'objet ciblé et peuvent recevoir la valeur nulle. Cependant, cette souplesse introduit des risques, comme déréférencer un pointeur nul ou créer des pointeurs suspendus. Les références, en revanche, ne peuvent pas être nulles et sont liées à des objets valides dès leur création, ce qui évite ces risques. Comme elles sont plus sûres, il faut préférer les références aux pointeurs, sauf si les fonctionnalités supplémentaires offertes par les pointeurs sont nécessaires.

Instructions

Bienvenue, ingénieur ! Tu es l'un des derniers vétérans de la Fondation Speedywagon, une organisation secrète qui, depuis des décennies, lutte contre d'anciennes menaces comme les Pillar Men. Dans le cadre de cet effort, tu as passé des années à maintenir les systèmes technologiques de la Fondation, construits à partir d'un mélange de technologies de pointe et de bibliothèques vieillissantes.

Cependant, ces derniers temps, les capteurs qui détectent l'activité des Pillar Men fonctionnent mal. Les systèmes de la Fondation sont anciens, et le code interagit avec une bibliothèque C++ héritée qui ne peut pas être mise à jour. Ta tâche est d'implémenter quatre fonctions essentielles qui surveillent l'activité des capteurs des Pillar Men à l'aide d'une vieille bibliothèque à base de pointeurs.

En tant qu'ingénieur C++ moderne, tu préférerais utiliser des pointeurs intelligents, mais hélas, le code hérité exige le respect des anciennes méthodes. Le sort de l'humanité repose peut-être sur ces pointeurs, alors avance avec prudence, et que l'énergie Hamon te guide.

Note

Comme les relevés des capteurs peuvent être énormes, on fournit une maquette de struct qui est utilisée dans la bibliothèque réelle. Le code a déjà été implémenté dans le fichier d'en-tête pour toi.

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. Vérifie la connexion du capteur (connection_check)

Ta première tâche consiste à t'assurer que le capteur des Pillar Men est bien connecté. On ne peut pas se permettre de fausses alertes déclenchées par des capteurs déconnectés. Tu vas écrire une fonction connection_check, qui vérifie si le pointeur du capteur est valide en testant nullptr.

  • Définis une fonction qui accepte un pointeur vers une struct pillar_men_sensor.
  • La fonction doit renvoyer true si le pointeur du capteur n'est pas nul, et false sinon.
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. Compte l'activité des capteurs (activity_counter)

Les Pillar Men rôdent dans l'ombre, et on doit savoir si les capteurs ont détecté la moindre activité. Tu vas écrire la fonction activity_counter, qui prend un tableau de capteurs et une capacité indiquant le nombre de capteurs dans le tableau.

  • Définis une fonction qui accepte un pointeur vers le premier élément d'un tableau ainsi que la capacité de ce tableau.
  • Utilise l'arithmétique des pointeurs pour parcourir le tableau de capteurs et accumuler les relevés d'activité.
  • Renvoie l'activité accumulée.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. Contrôle de l'alarme (alarm_control)

Un capteur ne doit déclencher une alarme que s'il y a un vrai danger. La fonction alarm_control fait en sorte qu'un capteur ne déclenche une alarme que si son niveau d'activité est supérieur à 0. Cette fonction doit aussi vérifier que les capteurs ne sont pas nuls pour éviter que le système plante.

  • Définis une fonction qui accepte le pointeur vers un pillar_men_sensor.
  • La fonction doit d'abord vérifier que le capteur n'est pas nullptr. Si le capteur est nullptr, renvoie false.
  • Si le capteur est valide et que son activité est supérieure à 0, renvoie true ; sinon, renvoie false.
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. Vérifie les données pour détecter des anomalies avec la fonction uv_alarm

Dans cette tâche, tu vas implémenter la fonction uv_alarm pour déterminer si une alarme doit être déclenchée en fonction des niveaux d'exposition aux UV et de l'activité des capteurs. La fonction uv_alarm doit utiliser la fonction uv_light_heuristic fournie, qui opère sur un vecteur de données et renvoie une valeur en fonction de certains seuils. Il s'agit d'une version maquette du code complexe qui s'exécutera en production, ne modifie pas l'interface.

Définis la fonction uv_alarm dans l'espace de noms speedywagon. Elle doit :

  • Prendre en paramètre un pointeur vers une struct pillar_men_sensor.
  • Renvoyer false si le pointeur du capteur est nul.
  • Appeler la fonction uv_light_heuristic en lui passant l'adresse du tableau data du capteur.
  • Renvoyer true si la valeur renvoyée par uv_light_heuristic est supérieure au niveau sensor->activity, sinon renvoyer false.
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
C++ Exercism

Prêt à commencer Fondation Speedywagon ?

Inscris-toi sur Exercism pour apprendre et maîtriser C++ avec 19 concepts100 exercices, et un vrai mentorat humain, le tout gratuitement.