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:
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 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);
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.
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 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.
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.
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{};
};
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.
pillar_men_sensor-struct entgegennimmt.true zurückgeben, wenn der Sensorzeiger nicht null ist, andernfalls false.pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
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.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
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.
pillar_men_sensor entgegennimmt.nullptr ist. Ist der Sensor nullptr, gib false zurück.true zurück, andernfalls false.pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
uv_alarm auf Anomalien prüfenIn 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:
pillar_men_sensor-struct als Parameter entgegennehmen.false zurückgeben, wenn der Sensorzeiger null ist.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.Melde dich bei Exercism an, um C++ mit 19 Konzepte100 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.