Einführung in C++

Compiler-Chain & Task A - ChatBot

Prof. Dr. Malte Schilling

Autonomous Intelligent Systems Group

🚀 by Decker

Überblick heutige Termin

  • Rückblick
  • Task Chatbot

Compiler

C++ und C sind (wie Java) Programmiersprachen, die vor der Ausführung mithilfe eines Compilers in ein ausführbares Programm übersetzt werden müssen.

  • Hierfür existieren verschiedene Compiler: GNU Compiler Collection (GCC), Clang, Intel C/C++ Compiler (ICC), Microsoft Visual C++, IBM XL C/C++, uvm.
  • Wir verwenden GCC oder Clang.

Installation

  • Linux Installation eines Paketes (z. B. gcc) aus der Paketverwaltung, z.B. unter Ubuntu: sudo apt-get install gcc
  • Windows Installation:
    • Minimalist GNU for Windows (MinGW), http://nuwen.net/mingw.html
    • Cygwin bietet Terminal und weitere Funktionalität ähnlich zu Linux Distributionen, https://cygwin.com/install.html

“Hello World”

In C

#include "stdio.h"
#include "stdlib.h"

int main(int argc, char *argv) {
	printf("%s", "Hello, world!\n");
	// eine C-Funktion!
	return EXIT_SUCCESS;
}

In C++

#include <iostream>

int main() {
	std::cout << "Hello, world!" << std::endl;
	return 0 ;
}

  1. Kompilieren: \(\underbrace{\tt gcc }_{\text{compiler}} \overbrace{\tt -Wall }^{\text{Warnings aktivieren}} \underbrace{\tt -o \; hello }_{\text{Name der Zieldatei}} \overbrace{\tt hello.c }^{\text{Quellcode}}\)
  2. Ausführen: ./hello
  3. Ausgabe: Hello world!

Kompiler-Sequenz

Beim Übersetzen in ein ausführbares Programm werden folgende Schritte durchgeführt:

  1. Der Präprozessor ersetzt Makros (z. B. #include).
  2. Der Compiler übersetzt den Quellcode in ein Assemblerprogramm.
  3. Der Assembler erzeugt Maschinencode.
  4. Der Linker verbindet die vers. Dateien zu einer ausführbaren Datei.
code-9c78fbe6.tex.svg
code-b2cb7882.tex.svg
code-22fc2d15.tex.svg
code-472b1d24.tex.svg
code-472b1d24.tex.svg

Übersetzung umfangreicher Projekte

Problem: Kompilieren „per Hand“ von umfangreichen Projekten mit vielen Headern, Quelltextdateien und Abhängigkeiten zu externen Bibliotheken kann sehr umständlich und fehleranfällig sein.

Mögliche Lösungen:

  • Nutzung einer integrierten Entwicklungsumgebung
    → Nachteil: Projekte können oft nur schwer ausgetauscht werden.
  • Nutzung von make, liest Makefile ein, in dem die Abhängigkeiten des Übersetzungsprozesses erfasst sind → Nachteil: auch Makefiles sind nicht portabel.
  • Für die zu bearbeitende Aufgaben wird make benötigt. Wie gcc/g++ ist make nicht nativ für Windows vorhanden. Daher MinGW oder Cygwin.
  • Die Ausführung von make in einem Ordner mit Makefile kompiliert/führt aus wie spezifiziert (falls Makefile mehrere Aufgaben definiert: make aufgabe).

Programmaufteilung auf verschiedene Dateien

Bei größeren Projekten trennt man meist Deklarationen und Definitionen in - Header-Dateien und - Implementierungs-Dateien

→ Erlaubt separate Kompilierung/Verwendung von Prebuilt-Libraries.

// max.h
// Deklaration
int max( int , int );
// max.c
// Definition
int max( int a, int b ) {
	return a>b? a : b; }


// foo.c
#include <stdio.h>
#include "max.h"

int main( void ) {
	printf( "%i\n" , max( 37, 91 ) );
}

Beim Kompilieren müssen alle Implementierungsdateien angegeben werden:
gcc -Wall -o foo max.c foo.c

Wie teile ich meinen Quellcode in Header und Implementierung auf?

Die Aufeilung in Header und Implementierung ist nicht immer einfach. Generell gilt:

  • Das Interface (wie wird die Bibliothek verwendet) gehört in einen oder mehrere Header.
  • Die Implementierung (was macht die Bibliothek) gehört in Quelltextdateien.

Implementierung

#include "Header.h"

void println( int x ) {
  std::cout << x << std::endl; 
}

Funktionsdeklaration

  • Eine Funktion kann zunächst deklariert und später definiert werden.
  • Beispiel: int max( int , int ); – hier dürfen die Namen der Parameter entfallen.

Warum gibt es die Unterscheidung zwischen Definition und Deklaration?

  • Die Deklaration enthält Informationen wie die Funktion zu benutzen ist.
  • Die Definition legt fest, was die Funktion tut.

Funktionsdeklaration

  • Beim Übersetzten überprüft der Compiler bei jedem Funktionsaufruf:
    • Gibt es eine Funktion mit dem Namen?
    • Passen die angegeben Argumente mit den Typen der Parametern zusammen?
    • Passt der Rückgabetyp?
  • Dazu reichen dem Compiler die Informationen aus der Deklaration aus.
  • Die Definition kann in einer anderen Datei stehen und wird dann erst vom Linker mit dem Funktionsaufruf verbunden (mehr dazu später).

Include Guards

Header-Dateien können versehentlich mehrfach eingebunden werden.

Beispiel: A bindet H ein, B bindet H und A ein. Dadurch wird H in B zweimal eingebunden.

→ Fehler, falls in H z.B. eine Funktion definiert wird.

  • Header sollten gegen mehrfaches Einbinden mit #ifndef geschützt werden.
// max.h
#ifndef __MAX_H
#define __MAX_H

int max( int, int );

#endif

Pointer und Referenzen

Wiederholung: Pointer

  • Ein Zeiger (engl. Pointer) enthält die Adresse einer Variable im Speicher.
  • Die Speicheradresse einer Variable liefert der Adressoperator &.
  • Spezielle Adresse: NULL – „Zeiger ins Nichts“.
  • Die Variable zu einem Pointer liefert der Dereferenzierungsoperator *.
  • Der Typ eines Pointers auf eine Variable vom Typ T ist T*.
int a = 42;
int* b = &a; // Adressoperator angewendet auf a
*b = 12; // Dereferenzierungsoperator angewendet auf b; a == 12

Visualisierung Pointer

code-32ad05cf.tex.svg
code-7b590b4c.tex.svg
code-53e300e0.tex.svg
code-df106a01.tex.svg
code-b45419f5.tex.svg
code-3368d916.tex.svg
code-b20ec228.tex.svg

Array-Variable und Pointer

  • Eine Array-Variable verhält sich wie ein Pointer, der auf das erste Element des Arrays zeigt.
  • Auf einen Pointer kann – wie auf ein Array – mit dem []-Operator zugegriffen werden. Dabei gilt: ptr[i] == *(ptr+i)
  • Übergibt man einen Array-Typ T[] als Parameter an eine Funktion, „verfällt“ der Typ zum entsprechenden Pointer T* (Array Decaying).

Referenzen

  • Der Umgang mit Pointern ist besonders am Anfang schwierig.
  • C++ führt mit Referenzen ein ähnliches Sprachmittel ein.
  • Wie Pointer sind Referenzen Variablen, die Verweise auf andere Variablen speichern.
  • Der Typ einer Referenz auf eine Variable vom Typ T ist T&.

Beispiel Referenzen

code-cd871d23.tex.svg
code-ee5b7b38.tex.svg
code-6ec1d950.tex.svg
code-c04868e3.tex.svg
code-981c9b42.tex.svg
code-a6b74c46.tex.svg
code-2342280b.tex.svg

Referenzen - Unterschied zu Pointern

  • Zur Initialisierung wird nicht der Adressoperator verwendet!
  • Zum Zugriff auf das gespeicherte Datum wird nicht der Dereferenzierungsoperator verwendet werden!
int a = 42;
int& b = a;
std::cout << b << std::endl; // gibt 42 aus
b = 5; // a == 5
std::cout << b << std::endl; // gibt 5 aus
std::cout << a << std::endl; // gibt 5 aus

Referenzen

  • Referenzen müssen immer sofort initialisiert werden:
  • Eine Referenz kann nicht neu zugewiesen werden!
  • Bei einer Zuweisung wird der referenzierte Wert überschrieben!
int i1 = 5;
int i2 = 6;
int& ir = i1; // ir zeigt auf i1
ir = i2; // ir zeigt immer noch auf i1 // i1 == i2

Unterschiede zwischen Pointern & Referenzen

Pointer Referenzen
Initialisierung int* ip = &i; int& ir = i;
Zugriff lesend *ip ir
Zugriff schreibend *ip = 5; ir = 5;
Kann ins „Nichts“ zeigen Ja Nein
Kann neu zugewiesen werden Ja Nein

Call by Reference

  • Erinnerung: Die Parameterübergabe an Funktionen erfolgt in C (und C++) immer by value.
  • Mit Pointern können wir jedoch call by reference simulieren.

Beispiel:

void swap( int* x, int* y) {
	int h = *x;
	*x = *y;
	*y = h;
}

int main( void ) {
	int i = 5;
	int j = 42;
	swap(&i, &j);// Achtung: Adressen auf i und j übergeben!
	printf( _"i = %i, j = %i\n"_ , i, j);
}

Call by Reference

  • Faustregel: In C++ sollten (wenn möglich) Referenzen statt Pointer verwendet werden.
  • Falls doch Pointer benötigt werden, sollte man auf Smart Pointer (siehe spätere Vorlesung) zurückgreifen.

Generative Modelle

References