Tracks
/
C++
C++
/
Übungen
/
Speedywagon-Stiftung
Speedywagon-Stiftung

Speedywagon-Stiftung

Lernübung

Einführung

Wie viele andere Sprachen hat C++ Zeiger. Du weißt schon, dass Referenzen und Zeiger sich ähneln, aber stell sie dir als eine Ebene näher an den inneren Abläufen deines Computers vor. Zeiger sind Variablen, die Objektadressen speichern. Man verwendet sie, um direkt mit Objekten zu interagieren, was dynamische Speicherverwaltung und effiziente Datenmanipulation in C++ ermöglicht.

Wenn Zeiger für dich neu sind, wirken sie vielleicht etwas mysteriös, aber sobald du dich an sie gewöhnt hast, sind sie ganz unkompliziert.

Sie sind ein entscheidender Teil von C++, also nimm dir etwas Zeit, um sie wirklich zu verstehen. Die einfache Variante in diesem Konzept wird auch dumb pointer oder raw pointer genannt. Im modernen C++ gibt es außerdem smart pointers; der einfache Typ ist überhaupt nicht intelligent und du musst die ganze Arbeit selbst erledigen.

Bevor wir in die Details eintauchen, lohnt es sich, den Nutzen von Zeigern zu verstehen. Zeiger sind eine Möglichkeit, die Adresse eines Objekts mit anderen Teilen unseres Programms zu teilen, was aus zwei wichtigen Gründen nützlich ist:

  1. Wie Referenzen vermeiden Zeiger Kopien und helfen, den Ressourcenverbrauch deines Programms zu verringern.
  2. Im Gegensatz zu Referenzen kannst du einen Zeiger auf ein anderes Objekt zeigen lassen.
  3. Zeiger können außerdem auf einen Nullwert zeigen, um anzuzeigen, dass sie gerade auf kein Objekt zeigen.

Allgemeine Syntax

Bei einer Zeigerdeklaration in C++ gibst du den Datentyp an, auf den der Zeiger zeigt, gefolgt von einem Sternchen (*) und dem Namen des Zeigers. Wenn Zeiger deklariert werden, sind sie nicht automatisch initialisiert. Ohne explizite Zuweisung enthält ein Zeiger typischerweise einen unbestimmten Wert, der oft als „Garbage-Adresse“ bezeichnet wird. Manche Compiler initialisieren Zeiger zwar mit nullptr, aber dieses Verhalten ist nicht bei allen Compilern garantiert, also solltest du dich nicht darauf verlassen. Es gilt als bewährte Praxis, rohe Zeiger explizit zu initialisieren und vor der Verwendung zu prüfen, dass sie nicht null sind, um mögliche Probleme zu vermeiden.

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

Um die Adresse einer Variable einem Zeiger zuzuweisen, verwendest du den Adressoperator (&). Zum Dereferenzieren eines Zeigers verwendest du den Dereferenzierungsoperator (*).

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};

Achtung: Das Dereferenzieren muss explizit erfolgen, während Referenzen einfach wie ein Alias funktionierten.

Zeigerarithmetik

Zeigerarithmetik ermöglicht es dir, Rechenoperationen mit Zeigern durchzuführen, was besonders nützlich ist, wenn du mit Arrays arbeitest. Wenn du eine Ganzzahl zu einem Zeiger addierst, zeigt er auf ein anderes Element.

// 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

Zeigerarithmetik in C++ kann bei unvorsichtiger Handhabung leicht zu undefiniertem Verhalten führen. Undefiniertes Verhalten kann sich in unerwarteten Programmergebnissen, Abstürzen oder sogar Sicherheitslücken äußern. Ein berüchtigtes Beispiel für die Folgen undefinierten Verhaltens war die Explosion der Ariane-5-Rakete im Jahr 1996, bei der eine Softwareausnahme, ausgelöst durch die Umwandlung einer 64-Bit-Gleitkommazahl in eine 16-Bit-Ganzzahl mit Vorzeichen, zu einem katastrophalen Fehlschlag führte.

Zugriff auf Membervariablen

In C++ wird der Operator -> verwendet, um über einen Zeiger auf ein Objekt auf dessen Member zuzugreifen. Er ist eine Kurzschreibweise, die den Zugriff auf Member von Objekten vereinfacht, auf die Zeiger zeigen. Wenn ptr zum Beispiel ein Zeiger auf ein Objekt mit der Membervariablen x ist, kannst du statt (*ptr).x direkt ptr->x verwenden. Dieser Operator verbessert die Lesbarkeit des Codes und verringert die Ausführlichkeit, wenn du mit Zeigern auf Objekte arbeitest.

Hier ist ein kurzes Beispiel mit einer struct Superhero, die eine Membervariable superpower hat. Die main-Funktion erstellt einen Zeiger dianaPrince auf ein Superhero-Objekt (das Wonder Woman darstellt). Mit dem Operator -> wird auf die Membervariable superpower zugegriffen, was Wonder Womans ikonisches „Lasso of Truth“ zeigt.

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;

Zeiger vs. Referenzen

Zeiger und Referenzen ermöglichen beide den indirekten Zugriff auf Objekte, unterscheiden sich aber in ihren Möglichkeiten und in puncto Sicherheit. Zeiger bieten die Flexibilität, ihr Zielobjekt zu ändern, und können auf null gesetzt werden. Diese Flexibilität bringt jedoch Risiken mit sich, etwa das Dereferenzieren von Nullzeigern oder das Erzeugen hängender Zeiger. Referenzen hingegen können nicht null sein und sind bei ihrer Erstellung an gültige Objekte gebunden, wodurch diese Risiken vermieden werden. Aufgrund ihrer sichereren Natur solltest du Referenzen Zeigern vorziehen, es sei denn, du brauchst die zusätzlichen Möglichkeiten, die Zeiger bieten.

Anleitung

Willkommen, Ingenieur! Du bist einer der letzten Veteranen der Speedywagon Foundation, einer geheimen Organisation, die seit Jahrzehnten gegen uralte Bedrohungen wie die Pillar Men kämpft. Im Zuge dieser Arbeit hast du jahrelang die technischen Systeme der Foundation gewartet, die aus einer Mischung aus modernster Technik und in die Jahre gekommenen Bibliotheken bestehen.

Doch in letzter Zeit funktionieren die Sensoren, die die Aktivitäten der Pillar Men verfolgen, nicht mehr richtig. Die Systeme der Foundation sind alt, und der Code arbeitet mit einer Legacy-C++-Bibliothek zusammen, die nicht aktualisiert werden kann. Deine Aufgabe ist es, vier Kernfunktionen zu implementieren, die die Sensoraktivität der Pillar Men über eine altmodische, zeigerbasierte Bibliothek überwachen.

Als moderner C++-Ingenieur würdest du am liebsten Smart Pointer verwenden, doch leider verlangt Legacy-Code Respekt vor den alten Methoden. Das Schicksal der Menschheit ruht vielleicht auf diesen Zeigern, also geh sorgsam vor, und möge die Hamon-Energie dich leiten.

Note

Da Sensorwerte riesig sein können, stellen wir ein Mockup-struct bereit, das in der tatsächlichen Bibliothek verwendet wird. Der Code wurde bereits in der Header-Datei für dich implementiert.

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

1. Sensorverbindung prüfen (connection_check)

Deine erste Aufgabe ist sicherzustellen, dass der Pillar-Men-Sensor richtig verbunden ist. Fehlalarme, die von nicht verbundenen Sensoren ausgelöst werden, können wir nicht gebrauchen. Du schreibst eine Funktion connection_check, die prüft, ob der Zeiger des Sensors gültig ist, indem sie auf nullptr testet.

  • Definiere eine Funktion, die einen Zeiger auf ein pillar_men_sensor-struct entgegennimmt.
  • Die Funktion soll true zurückgeben, wenn der Sensorzeiger nicht null ist, andernfalls false.
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. Aktivität der Sensoren zählen (activity_counter)

Die Pillar Men lauern im Schatten, und wir müssen wissen, ob die Sensoren Aktivität erkannt haben. Du schreibst die Funktion activity_counter, die ein Array von Sensoren und eine Kapazität entgegennimmt, welche die Anzahl der Sensoren im Array angibt.

  • Definiere eine Funktion, die einen Zeiger auf das erste Element eines Arrays und die Kapazität des Arrays entgegennimmt.
  • Verwende Zeigerarithmetik, um das Sensor-Array zu durchlaufen und die Aktivitätswerte zu summieren.
  • Gib die aufsummierte Aktivität zurück.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. Alarmsteuerung (alarm_control)

Nicht jeder Sensor sollte einen Alarm auslösen, es sei denn, es besteht echte Gefahr. Die Funktion alarm_control stellt sicher, dass ein Sensor nur dann einen Alarm auslöst, wenn sein Aktivitätswert größer als 0 ist. Diese Funktion sollte außerdem prüfen, ob ein Sensor null ist, um Systemabstürze zu verhindern.

  • Definiere eine Funktion, die den Zeiger auf ein pillar_men_sensor entgegennimmt.
  • Die Funktion sollte zunächst prüfen, ob der Sensor nullptr ist. Ist der Sensor nullptr, gib false zurück.
  • Wenn der Sensor gültig ist und seine Aktivität größer als 0 ist, gib true zurück, andernfalls false.
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. Die Daten mit der Funktion uv_alarm auf Anomalien prüfen

In dieser Aufgabe implementierst du die Funktion uv_alarm, um anhand der UV-Lichtwerte und der Sensoraktivität zu bestimmen, ob ein Alarm ausgelöst werden soll. Die Funktion uv_alarm soll die bereitgestellte Funktion uv_light_heuristic verwenden, die auf einem Vektor von Daten arbeitet und einen Wert anhand bestimmter Schwellenwerte zurückgibt. Dies ist eine Mockup-Version des komplexen Codes, der im Produktivbetrieb läuft; bitte ändere die Schnittstelle nicht.

Definiere die Funktion uv_alarm im Namespace speedywagon. Sie soll:

  • einen Zeiger auf ein pillar_men_sensor-struct als Parameter entgegennehmen.
  • false zurückgeben, wenn der Sensorzeiger null ist.
  • die Funktion uv_light_heuristic aufrufen und dabei die Adresse des data-Arrays des Sensors übergeben.
  • true zurückgeben, wenn der von uv_light_heuristic zurückgegebene Wert größer als der Wert von sensor->activity ist, andernfalls false.
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
C++ Exercism

Bereit, mit Speedywagon-Stiftung zu starten?

Melde dich bei Exercism an, um C++ mit 19 Konzepte100 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.