Crashkurs C++ Mit Hilfe von Java Fachvortrag IPhone Alexis Popovski Serdar Korkmaz Canan Ucan Tuan Nguyen Dominic Engel
Crashkurs C++ Mit Hilfe von Java Fachvortrag IPhone Alexis Popovski Serdar Korkmaz Canan Ucan Tuan Nguyen Dominic Engel
Installation und Grundaufbau Gliederung Speicher Vergleich und Fazit Konstruktoren/ Destruktoren Zeiger und Referenzen Einführung Installation und Grundaufbau
Vergleich und Fazit Gliederung
Einführung
Entstehung Bjarne Stroustrup C als prozedurale Sprache Klassen von SIMULA Erscheinungsjahr: 1985
Entstehung Bjarne Stroustrup C als prozedurale Sprache Klassen von SIMULA Erscheinungsjahr: 1985 Bjarne Stroustrup
Multiparadigmensprache Entstehung Generisch Prozedural Objektorientiert
Multiparadigmensprache Generisch Objektorientiert Prozedural
Prozedurale Programmierung (POP) Multiparadigmensprache Programme bestehen aus einer (endlichen) Folge von Anweisungen Zur Strukturierung werden inhaltlich zusammenhängende (Teil-)Folgen von Anweisungen in Prozeduren zusammengefasst Prozeduren: Folge imperativer Anweisungen: Zuweisungen, Tests, Schleifen, Aufrufe Unterprozeduren Prozeduren sind Funktionen, die Argumente entgegennehmen & abhängige Rückgabewerte liefern typische POP-Sprachen: Fortran, C, COBOL, Pascal
Prozedurale Programmierung (POP) Programme bestehen aus einer (endlichen) Folge von Anweisungen Zur Strukturierung werden inhaltlich zusammenhängende (Teil-)Folgen von Anweisungen in Prozeduren zusammengefasst Prozeduren: Folge imperativer Anweisungen: Zuweisungen, Tests, Schleifen, Aufrufe Unterprozeduren Prozeduren sind Funktionen, die Argumente entgegennehmen & abhängige Rückgabewerte liefern typische POP-Sprachen: Fortran, C, COBOL, Pascal
Prozedurale Programmierung (POP) vs. Prozedurale Programmierung (POP) vs. Objektorientierten Programmierung (OOP) Prozedurale Programmierung (POP) vs. Objektorientierte Programmierung (OOP) Prozedurale Programmierung (POP) Objektorientierte Programmierung soll Schwierigkeiten, die bei der prozeduralen Programmierung entstehen können, reduzieren Hauptmodule: Klassen und nicht Prozeduren Erstellung von Klassen und Objekten Modellierung von realen Objekten
Prozedurale Programmierung (POP) vs. Objektorientierte Programmierung (OOP) Objektorientierte Programmierung soll Schwierigkeiten, die bei der prozeduralen Programmierung entstehen können, reduzieren Hauptmodule: Klassen und nicht Prozeduren Erstellung von Klassen und Objekten Modellierung von realen Objekten
Objektorientierte Programmierung (OOP) Prozedurale Programmierung (POP) vs. Objektorientierte Programmierung (OOP) Kapselung Objekt kapselt seine Attribute und Methoden Vererbung Eine abgeleitete Klasse erbt alle Eigenschaften ihrer Basisklasse Polymorphie Objekte einer Instanz verändern zu Laufzeit ihr Verhalten
Objektorientierte Programmierung (OOP) Kapselung Objekt kapselt seine Attribute und Methoden Vererbung Eine abgeleitete Klasse erbt alle Eigenschaften ihrer Basisklasse Polymorphie Objekte einer Instanz verändern zu Laufzeit ihr Verhalten
Objektorientierte Programmierung (OOP) Generische Programmierung Verfahren zur Entwicklung wiederverwendbarer Software-Bibliotheken Funktionen werden für unterschiedliche Datentypen möglichst allgemein entworfen Implementierung erfolgt durch das Konzept generischer Typen bzw. Templates Für Template unerheblich, ob es Zahlen oder Männer vertauschen soll (Zeile 11 & 12) Selbst Typ-Parameter T (Zeile 1) muss nicht spezifiziert werden Compiler kann diesen aus Funktionsargumenten (Zeile 11 & 12) ableiten
Generische Programmierung Verfahren zur Entwicklung wiederverwendbarer Software-Bibliotheken Funktionen werden für unterschiedliche Datentypen möglichst allgemein entworfen Implementierung erfolgt durch das Konzept generischer Typen bzw. Templates Für Template unerheblich, ob es Zahlen oder Männer vertauschen soll (Zeile 11 & 12) Selbst Typ-Parameter T (Zeile 1) muss nicht spezifiziert werden Compiler kann diesen aus Funktionsargumenten (Zeile 11 & 12) ableiten
Generische Programmierung Wann wird C++ verwendet? Wann eignet sich Java mehr? Geeigneter für Software zur Manipulation auf Hardware-Ebene Maschinennähe: Schneller Zugriff auf RAM, Festplatte, CPU usw. -> Beliebt bei gewerblicher Softwareerstellung Systemprogramme „sichere Sprache“ Anwendungsprogramme Eigene Schnittstelle für grafische Benutzeroberfläche Grundlage für Android-Entwicklung Frage der Effizienz für hohe Effizienz nimmt C++-Programmierung erhebliche Umstände in Kauf Sofern Optimierung zweitrangig -> Java
Wann wird C++ verwendet? Wann eignet sich Java mehr? Geeigneter für Software zur Manipulation auf Hardware-Ebene Maschinennähe: Schneller Zugriff auf RAM, Festplatte, CPU usw. -> Beliebt bei gewerblicher Softwareerstellung Systemprogramme „sichere Sprache“ Anwendungsprogramme Eigene Schnittstelle für grafische Benutzeroberfläche Grundlage für Android-Entwicklung Frage der Effizienz für hohe Effizienz nimmt C++-Programmierung erhebliche Umstände in Kauf Sofern Optimierung zweitrangig -> Java
Installation und Grundaufbau
Compiler - Auswahl Clang GCC MSVC (MS Visual C++) Nicht nur ein Compiler wie bei Java Clang GCC MSVC (MS Visual C++)
Compiler - Auswahl Clang GCC MSVC (MS Visual C++) Nicht nur ein Compiler wie bei Java Clang GCC MSVC (MS Visual C++)
Editor - Auswahl Compiler - Auswahl CLion Visual Studio XCode Visual Studio Code
Editor - Auswahl CLion Visual Studio XCode Visual Studio Code
Aufteilung in .h und .cpp – Datein Editor - Auswahl Aufteilung in .h und .cpp – Datein Deklaration Implementierung
Aufteilung in .h und .cpp – Datein Deklaration Implementierung
Aufteilung in .h und .cpp – Datein Live Demo Aufteilung in .h und .cpp – Datein Beispiele im Anhang
Live Demo
Zeiger und Referenzen
Zeiger und Referenzen
Zeiger und Referenzen Sind ungefähr das Gleiche, aber wiederum auch nicht Sehr effizient zusammen, funktionieren jeweils aber auch alleine Abstrakt gesagt, sind Zeiger und Referenzen wie Bruder und Schwester Zeiger werden mit einem Asterisk (*) gekennzeichnet Referenzen werden mit einem Empersand (&) gekennzeichnet
Zeiger und Referenzen string a = "hi"; void doSomething(){} 0x00AZWE3… 0x014FVC… double a = 5.5; 0x12ADG56… Load Disc 0x014FVC… int a = 5; 0x00AZWE3… Instructions 0x00BFC…
Zeiger und Referenzen string a = "hi"; void doSomething(){} Load Disc int a = 5; 0x00AZWE3… Instructions 0x00BFC… Load Disc 0x014FVC… void doSomething(){} double a = 5.5; 0x12ADG56… string a = "hi";
Zeiger und Referenzen Straße repräsentiert einen Speicherblock, mit einem Start und einem Ende Ein Computer braucht, für alles was wir an einem Computer tun, Speicher - Denn alles, was wir an einem Computer tun, wird in den Speicher geschoben und dort ausgeführt Alles, was in den Speicher geschoben wird, bekommt eine Adresse, auf die wir mit dem Zeiger zugreifen können(um Speicher zu managen und zu manipulieren)
Ein Zeiger ist einfach ein Integer, der eine Speicheradresse enthält Was ist ein Zeiger? Ganz einfach: Ein Zeiger ist einfach ein Integer, der eine Speicheradresse enthält Mehr nicht!
Was ist ein Zeiger? Was sind Referenzen? Eine Art „Erweiterung“ für Zeiger Ein Verweis auf eine bereits existierende Variable Belegen keinen neuen Speicher Referenzen referenzieren auf ein bereits existierendes Objekt …und können nicht nur deklariert werden, sondern müssen sofort initialisiert werden
Live Demo Was sind Referenzen? Beispiele im Anhang
Live Demo
Konstruktoren/ Destruktoren
Konstruktor & Destruktor class Foo { public: Foo(); // Konstruktor (ctor) ~Foo(); // Destruktor (dtor) }; Aufgaben des Konstruktors Objekt in validen initialen Zustand überführen Benötigte Ressourcen akquirieren Aufgabe des Destruktors Ressourcen ggf. wieder freigeben
Konstruktor & Destruktor class Foo { public: Foo(); // Konstruktor (ctor) ~Foo(); // Destruktor (dtor) }; Aufgaben des Konstruktors Objekt in validen initialen Zustand überführen Benötigte Ressourcen akquirieren Aufgabe des Destruktors Ressourcen ggf. wieder freigeben
Konstruktor & Destruktor Scope und Lebenszeit von Objekten
Konstruktor & Destruktor Scope und Lebenszeit von Objekten
Konstruktor & Destruktor Scope und Lebenszeit von Objekten
Konstruktor & Destruktor Scope und Lebenszeit von Objekten
Konstruktor & Destruktor Das RAII-Prinzip RAII — Resource Acquisition Is Initialization Resource Ownership: Objekt besitzt Ressource Konstruktor holt / alloziert Ressource Destruktor gibt Ressource wieder frei Essentiell für Exception Safety Typische Ressourcen: Speicher, Datei, Thread, Hardware, Grafikpuffer- oder Texturobjekt etc.
Konstruktor & Destruktor RAII — Resource Acquisition Is Initialization Resource Ownership: Objekt besitzt Ressource Konstruktor holt / alloziert Ressource Destruktor gibt Ressource wieder frei Essentiell für Exception Safety Typische Ressourcen: Speicher, Datei, Thread, Hardware, Grafikpuffer- oder Texturobjekt etc. Das RAII-Prinzip
Konstruktor & Destruktor Das RAII-Prinzip: Einfaches Beispiel mit Speicher als Ressource
Konstruktor & Destruktor Das RAII-Prinzip: Einfaches Beispiel mit Speicher als Ressource
Speicher
Speicher
Speicher aus Sicht von Java (dieser Platz wurde absichtlich freigelassen)
Speicher aus Sicht von Java (dieser Platz wurde absichtlich freigelassen)
Speicher aus Sicht von Java Speicher aus Sicht von C++ Heap Stack Statischer Speicher Programmspeicher Dynamische Speicherverwaltung Funktionsaufrufe, lokale Variablen Globale Variablen Das eigentliche Programm (z.B. Anweisungen)
Speicher aus Sicht von C++ Heap Stack Statischer Speicher Programmspeicher Dynamische Speicherverwaltung Funktionsaufrufe, lokale Variablen Globale Variablen Das eigentliche Programm (z.B. Anweisungen)
Speicher aus Sicht von C++ Stack Speicher aus Sicht von C++ Für Speicher auf dem Stack muss man nichts explizit anfordern oder freigeben Automatisch LIFO- Datenstruktur: „Last In First Out“ effizient Festgelegte Größe, kann in einer IDE oder direkt am Betriebssystem verändert werden
Stack Für Speicher auf dem Stack muss man nichts explizit anfordern oder freigeben Automatisch LIFO- Datenstruktur: „Last In First Out“ effizient Festgelegte Größe, kann in einer IDE oder direkt am Betriebssystem verändert werden
Stack Beispiel Vec2 doSomething(Vec2 param) { Stack Pointer : FP SP Call Stack Stack Pointer : Beginn des freien Speichers Frame Pointer : Start des aktuellen Stack Frames Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a);
Beispiel (2,2) a Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . FP SP Call Stack (2,2) a Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a);
Beispiel FP SP Call Stack (2,2) a Reservierung von Speicher auf dem Call Stack für ein Vec2 Objekt und Initialisierung von a (2,2) SP wird um sizeOf(Vec2) erhöht. Da Vec2 nur zwei int- Werte verwaltet hat dieser die Größe von 8 bytes, also erhöht sich der SP um 8 bytes.
Beispiel (2,2) a b ? Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . FP SP Call Stack (2,2) a b ? Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a);
Beispiel FP SP Call Stack (2,2) a b ? Es wird für Vec2 b Speicher auf dem Stack reserviert aber noch nicht initialisiert Um b initialisieren zu können müssen wir die Methode doSomething(..) aufrufen SP wird wieder um die Größe von Vec2 erhöht
Beispiel (2,2) a b ? RV RP 0xFF… Vec2 doSomething(Vec2 param) { FP SP (2,2) a b ? RV RP 0xFF… Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a); Call Stack
Beispiel (2,2) a b ? RV RP 0xFF… FP SP (2,2) a b ? RV RP 0xFF… Vor dem Verlassen des Scopes wird folgendes vorbereitet: RV: Speicherplatz für den return Value wird reserviert RP: Rücksprungadresse wird gemerkt FP: Frame Pointer Adresse wird gemerkt FP wird auf den neuen Scope gesetzt SP wird ebenfalls erhöht auf den nächsten freien Speicherbereich gesetzt Call Stack
Beispiel (2,2) a b ? RV RP 0xFF… . Vec2 doSomething(Vec2 param) { FP SP (2,2) a b ? RV RP 0xFF… . Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a); Call Stack
Beispiel (2,2) a b ? RV RP 0xFF… . FP SP (2,2) a b ? RV RP 0xFF… . Für alle Parameter und lokale Variablen werden ebenfalls Speicher auf dem Stack reserviert (Hier für die Vereinfachung mit „…“ dargestellt) Der zurückgegebener Wert wird in RV gespeichert Call Stack
Beispiel (2,2) a b RV RP 0xFF… . Vec2 doSomething(Vec2 param) { FP SP (2,2) a b RV RP 0xFF… . Vec2 doSomething(Vec2 param) { Vec2 lokal(1,7); . return Vec2(...); } int main() Vec2 a(2,2); Vec2 b = doSomething(a); Call Stack
Beispiel (2,2) a b RV RP 0xFF… . der Stack räumt auf FP (2,2) a b RV RP 0xFF… . SP der Stack räumt auf beim nächsten Aufruf werden die Daten überschrieben b wird initialisiert mit RV SP und FP zeigen wieder auf die Adressen vor dem Methodenaufruf Call Stack
Beispiel (2,2) a b RV RP 0xFF… . der Stack räumt auf Call Stack der Stack räumt auf beim nächsten Aufruf werden die Daten überschrieben b wird initialisiert mit RV SP und FP zeigen wieder auf die Adressen vor dem Methodenaufruf FP (2,2) a b RV RP 0xFF… . SP
Beispiel Fazit Stack Begrenzte Größe LIFO Datenstruktur Wächst und schrumpft im Programmverlauf Kein explizites Freigeben des Speichers notwendig Sehr effizient
Fazit Stack Begrenzte Größe LIFO Datenstruktur Wächst und schrumpft im Programmverlauf Kein explizites Freigeben des Speichers notwendig Sehr effizient
Fazit Stack Frage an euch Keine Referenzen (oder Pointer) auf ein lokales Objekt aus der Funktion heraus exportieren Wenn Scope endet wird „result“ vom Stack geräumt und die übergebene Referenz zeigt auf einen ungültigen Speicher
Frage an euch Keine Referenzen (oder Pointer) auf ein lokales Objekt aus der Funktion heraus exportieren Wenn Scope endet wird „result“ vom Stack geräumt und die übergebene Referenz zeigt auf einen ungültigen Speicher
Frage an euch Heap Heap Stack Statischer Speicher Programmspeicher Dynamische Speicherverwaltung Dynamisch allozierte Objekte : Vec2 bsp = new Vec2(2,2); Alles was mit new erzeugt wird, wird auf dem Heap gespeichert Heap ist wie ein Haufen, nicht strukturiert wie ein Stack Heap kann während Laufzeit dynamisch wachsen
Heap Vec2* p = new Vec2(2,2); Heap ? Vec2* p = new Vec2(2,2); Zur Laufzeit wird nach einem freien Speicherplatz auf dem Heap angefragt In unserem Beispiel von einer Bytegröße von sizeOf(Vec2) Heap reserviert Speicherplatz und liefert Adresse zurück
Heap Vec2* p = new Vec2(2,2); Heap Zur Laufzeit wird nach einem freien Speicherplatz auf dem Heap angefragt In unserem Beispiel von einer Bytegröße von sizeOf(Vec2) Heap reserviert Speicherplatz und liefert Adresse zurück Konstruktor wird aufgerufen und Objekt wird initialisiert
Heap Heap Stack p * Vec2* p = new Vec2(2,2); Durch Zeiger haben wir Zugriff auf Objekt auf dem Heap p speichert die zurückgegebene Adresse auf dem Stack p ist vom Typ Vec2*, also ein Zeiger auf Vec2
Heap Vec2* p = new Vec2[n]; Heap Stack p * Reserviert zusammenhängenden Speicherblock für n Objekte vom Typ Vec2 (funktioniert wie ein Array) n muss nicht zur Compilezeit bekannt sein Kann nur Default-Konstruktor von Vec2 aufrufen p hat nur Speicheradresse des ersten Objekts Programmierer muss die Größe des Arrays selber verwalten, denn der Zeiger kann dies nicht Heap (0,0) Stack p *
Heap Vec2* p = new Vec2[n]; Heap Stack p * (0,0) Stack p * New erfordert immer ein delete, um den Speicherplatz wieder freizugeben Mit delete[ ] p geben wir den Speicher in unserem Beispielwieder frei ABER der Zeiger existiert nach wie vor Dangling Pointer : zeigt auf bereits freigegebenen Speicher
Heap Vec2* p = new Vec2[n]; Heap Stack p * (0,0) Stack p * New erfordert immer ein delete, um den Speicherplatz wieder freizugeben Mit delete[ ] p geben wir den Speicher wieder frei ABER der Zeiger existiert nach wie vor Den Zeiger auf nullptr setzen so zeigt er auf nichts
Heap Vec2* p = new Vec2[n]; Heap Stack p * (0,0) Stack p * (2,3) Vergessen wir das delete haben wir einen memory leak Der Zeiger p referenziert jetzt auf ein neues Objekt auf dem Heap Der Speicher von unserem Array kann nicht mehr freigegeben werden Erst beim Programmende wird der Speicher wieder freigegeben
Heap Vec2* p = new Vec2[n]; Heap Stack p * Vergessen wir das delete haben wir einen memory leak Der Zeiger p referenziert jetzt auf ein neues Objekt auf dem Heap Der Speicher von unserem Array kann nicht mehr freigegeben werden Erst beim Programmende wird der Speicher wieder freigegeben Heap (0,0) Stack p * (2,3)
Fazit Stack und Heap Heap Stack hat festgelegte Größe, Heap wächst dynamisch zur Laufzeit Stack ist selbstorganisierend und effizient, Heap erfordert dass der Programmierer an die Speicherfreigabe denkt Heap erfordert in C++ immer Zeiger Zugriff langsamer
Fazit Stack und Heap Stack hat festgelegte Größe, Heap wächst dynamisch zur Laufzeit Stack ist selbstorganisierend und effizient, Heap erfordert dass der Programmierer an die Speicherfreigabe denkt Heap erfordert in C++ immer Zeiger Zugriff langsamer
Allokation und Konstruktion Fazit Stack und Heap Allokation und Konstruktion Malloc() und placement new Was passiert hier? Obj* o = new Obj(1,2,3); Speicher für ein Objekt vom Typ Obj wird allokiert Konstruktor von Obj(1,2,3) wird aufgerufen
Allokation und Konstruktion Malloc() und placement new Was passiert hier? Obj* o = new Obj(1,2,3); Speicher für ein Objekt vom Typ Obj wird allokiert Konstruktor von Obj(1,2,3) wird aufgerufen
Allokation und Konstruktion Manchmal macht es Sinn diese beiden Schritte zu trennen Speicher für ein Objekt vom Typ Obj mit malloc() allokieren: Obj* speicher = static_cast<Obj*>(malloc(sizeOf(Obj))); 2. Konstruktoraufruf mit placement new: new(speicher) Obj(1,2,3);
Allokation und Konstruktion Manchmal macht es Sinn diese beiden Schritte zu trennen Speicher für ein Objekt vom Typ Obj mit malloc() allokieren: Obj* speicher = static_cast<Obj*>(malloc(sizeOf(Obj))); 2. Konstruktoraufruf mit placement new: new(speicher) Obj(1,2,3);
Vergleich und Fazit
C++ und Java Fazit ÄHNLICHKEITEN VEREINFACHUNGEN IN JAVA Primitive Datentypen Syntax: Kontrollstrukturen Klassen, Sichtbarkeit(public, private) Multiple Konstruktoren, this, new Packages in Java/namespaces in C++ Keine Pointer – nur Referenzen Keine Funktionen – static Methoden Keine globalen Variablen – public static Variablen nutzen Keine Destruktoren – garbage collection und finalize() Keine Header-Dateien – stattdessen Interfaces Keine Operatorenüberladung – nur Methodenüberladung Keine Mehrfachvererbung – dafür mehrfache Implementierung von Interfaces möglich
C++ und Java Fazit ÄHNLICHKEITEN VEREINFACHUNGEN IN JAVA Primitive Datentypen Syntax: Kontrollstrukturen Klassen, Sichtbarkeit(public, private) Multiple Konstruktoren, this, new Packages in Java/namespaces in C++ VEREINFACHUNGEN IN JAVA Keine Pointer – nur Referenzen Keine Funktionen – static Methoden Keine globalen Variablen – public static Variablen nutzen Keine Destruktoren – garbage collection und finalize() Keine Header-Dateien – stattdessen Interfaces Keine Operatorenüberladung – nur Methodenüberladung Keine Mehrfachvererbung – dafür mehrfache Implementierung von Interfaces möglich C++ und Java Fazit
Danke für Eure Aufmerksamkeit