[social buttons]

C++

Die bekannnte objektorientierte Programmiersprache

C/C++ und der GNU Compiler auf Linux für Hartz IV Empfänger

logo.js MDN

Einleitung

Mit JavaScript für Hartz IV Empfänger ist für mich der Groschen gefallen, wieder zu dokumentieren, wie man programmiert. Es ist nicht das erste mal, dass ich sowas schreibe. In der Wayback Machine kann man linux-swt.de nach 2002 zurückverfolgen. Damals habe ich probiert, Linux/POSIX/C/C++ zu dokumentieren. Die Seiten sind irgendwann wieder untergegangen. Ein paar Jahre spaeter schrieb ich bereits generierende Systeme. Auch die Entwuerfe und Codes sind wieder untergegangen. Auf der alten Festplatte von dem alten Computer, den ich seit dem einen Wohnheim nicht mehr habe.

Hallo Welt

Einmal nur Ausgabe. C++ hat einen Einstiegspunkt ueber eine definierte Funktion main, die einen int Returncode hat sowie mögliche Parameter, die mit dem Argument Count und dem Argument Vector (es gab auch noch einen Environment Pointer) angesteuert werden.


#include <iostream>

int main () {
    std::cout << "Hallo Welt!" << std::endl;
}
    
	    

Ausgabe aller Argumente der main Funktion (int argc, char **argv). Man kann auch char *argv[] schreiben.


#include <iostream>
int main (int argc, char **argv) {
    std::cout << "Programmname: " << argv[0] << std::endl;
    if (argc > 1) {
	for (int i = 1; i < argc; i++) {
	    std::cout << "Parameter #" << i << " " << argv[i] << std::endl;
	}
    } else {
	std::cout << "Hallo Welt #1" << std::endl;
	std::cout << "Dieses Programm gibt alle Argumente auf der Konsole aus." << std::endl;
	std::cout << "Bitte mit Parametern rufen." << std::endl;
    }
}

> g++ hallowelt1.cpp -o h1
> ./h1
Programmname: ./h1
Parameter #1 dgdf
Parameter #2 dfg
Parameter #3 dfg
Parameter #4 dfg
Parameter #5 dfg
	    
	    
	    

Und nochmal mit Eingabe (mit std::cin)

	    

#include <iostream>

int main () {
    
    char str[32]; // Zu Buffer Overflows und Strings mehr spaeter.
    int i = 0;
    
    std::cout << "Lese bis Enter: ";
    
    do {
	str[i++] = std::cin.get(); // Hier funktioniert Post-Increment, nach der Zuweisung wird i erst erhoeht (nach der Zuweisung der rechten Seite der Gleichung erst)
    } while (str[i-1] != '\n' && i < 32);  

    std::cout << "Du schriebst "<< i <<" Zeichen: ";    
    std::cout << str << std::endl;
    
}
	    
	    

Da fallen mir gleich mehrere Varianten mit char c und char *str und malloc und und und ein...


#include <iostream>
#include <string>

using namespace std;

char next () {
    return cin.get();
}

int main () {
    char c;
    string s;
    while (!cin.eof()) {
	c = next();
	if (c == ' ') {
	    if (s != "") {
	        cout << "got word: " << s << endl;
		s = "";
	    }
	} else {
	    s += c;
	}
    }
}

// jkdflskjdfl sldkfjslk jskldfjl      skdjflk sdf      nn  sdfjsh
// got word: jkdflskjdfl
// got word: sldkfjslk
// got word: jskldfjl
// got word: skdjflk
// got word: sdf
// got word: nn
// got word: sdfjsh

Einen Parser zu schreiben, dafuer braucht man ein paar Minuten. Aber genau das werde ich muessen. Zum Ende des Dokuments muss ein ES-262 AST gebaut werden koennen. Und der ist etwas umfangreicher :-).

Ich verzichte in diesem Beispiel auf strtok. Aber ich bin auch nicht abgeneigt, fuer die naechsten Zeilen vielleicht nochmal in der C++ Referenz zu blaettern, damit einige Funktionen wieder aufleuchten. Zugeben muss ich, die schon gelesen und benutzt zu haben.

typeinfo, typeid

Laufzeitinformationen zu Datentyp und Variabelname

		#include<typeinfo>
		
		int main () {
		    float f = 2;
		    cout << typeid(f).name() << endl;
		}
		// f
	    

Speicher allozieren

In C muss man den benoetigten Speicher selbst erfragen und selbst freigeben.

	    
		#include <malloc.h>
		char *str = (char *)malloc(32 * sizeof(char)); // in dem Fall und bei int geht auch nur 32, ansonsten n * sizeof(data_t)
		
	    

Man kann aber auch feste Groessen angeben.

	    
		char str[32]; // Ein 32 Byte Array of Characters
		
		float vec[3];	// Ein 3 Komponenten Vector
		
	    

Allocator

Casting

Wann und wozu, die Regeln werde ich mir nochmal durchlesen muessen, dann kann ich sie wiedergeben und anwenden.

Statische Variablen

Anders als Zeiger sind das bereits allozierte Objekte, die sind da und anstelle mit Pfeil werden Attribute und Methoden mit dem . Punkt-Operator angesprochen.

	    #include <iostream>
	    
	    struct Klasse { // struct oder class ist in C++ egal
		private:
		    int a;
		    char b;
		public:
		    int getA () {
			return a;
		    }
		    char getB () {
			return b;
		    }
		    Klasse(int _a = 1, char _b = 'b') {
			a = _a;
			b = _b;
		    }
	    } meineKlasse;
	    
	    int main () {
	    
		std::cout << std::endl << meineKlasse.getA();	// mit dem Punktoperator
		std::cout << std::endl << meineKlasse.getB();
		
		Klasse c(2,'d'); // statische Variable, GENAU 1 OBJEKT GROSS
		std::cout << std::endl << c.getA();
		std::cout << std::endl << c.getB();
		
		
		// d Das hier ist keine statisch Variable
		Klasse *d = new Klasse(3, 'e'); // hinter d stand noch kein Objekt.
		std::cout << std::endl << d->getA(); // Zeiger mit Pfeiloperator
		std::cout << std::endl << d->getB();
		
		// e das ist auch keine statische Variable, sondern ein Zeiger auf eine solche
		Klasse *e = &meineKlasse;	// Zeiger auf meineKlasse einrichten
		std::cout << std::endl << e->getA(); // Mit PfeilOperator
		std::cout << std::endl << e->getB();
		std::cout << std::endl;

	    }
/*	    
	    
1
b
2
d
3
e
1
b

*/	    
	    

*Adresszeiger (Pointer)

Der gibt nur die Adresse eines Speicherbereichs an. Da hinter kann jeder beliebige Typ stehen, nimmt man z.B. einen void* Pointer. Anderseits wird fuer die ++Inkrementierung bei typisierten Pointern die entsprechende Groesse zum Fortzaehlen der Adresse vom Compiler (nicht vom Programmierer) benutzt .

		#include <malloc.h> // C
		char *str;
		str = (char *)malloc(256); // 256 * sizeof(char) Bytes
		
		// str[0] bis str[255] sind jetzt sicher vor Buffer Overflows.
		// In C++ kann man allerdings die std::string Klasse verwenden
		
		
		Klasse *k;		// Zeiger deklarieren
		k = new Klasse();	// Zeiger initialisieren, mit Adresse des Objekts
					// Der new Operator liefert eine Adresse zurueck.
					
	    

Objekte hinter Pointern.

Wenn ein Objekt von einem Pointer referenziert wird, muss man den Pfeil -> Operator nutzen um auf Attribute oder Methoden zuzugreifen.

#include <iostream>

struct Object {
    public:
	Object() {
	    // Da attr const ist, braucht man keinen Speicher allozieren
	    // und man kann attr nur einmal initialisieren
	    // Der Zeiger wird auf den Code im Constructor zeigen
	    attr = "Zeiger auf eine konstante Kette";
	}
	~Object() {}
	const char *attr; 	// Der Syntaxhighlighter hat ein > hinzugefuegt, sorry		
	const char *method () {			// ebenfalls. muss ich mal nachgucken.
	    return attr;
	}
};
int main () {
    Object *o = new Object();
    std::cout << o->attr << std::endl;
    std::cout << o->method() << std::endl;
}

// g++ p.cpp
// ./a.out
// Zeiger auf eine konstante Kette
// Zeiger auf eine konstante Kette
	    
	    

Ich werde dann im folgenden mal ein Beispiel nach dem anderen machen, um die verschiedenen Moeglichkeiten, wann Adresse rauskommt, und wann das Zeichen dann mal herausarbeiten.

#include <iostream>
using namespace std;

const char *name = "Edward";

int main (int argc, char **argv) {
    cout << "sizeof argc: " << sizeof(argc) << endl;
    cout << "sizeof argv: " << sizeof(argv) << endl;
    cout << name << endl;
    cout << "sizeof name: " << sizeof(name) << endl;
    cout << *name << endl; // E: Zeigt auf den ersten Buchstaben
    cout << name[0] << endl;
    cout << ((*name == name[0]) ? "yes" : "no") << endl;
    cout << name[1] << endl;
}

// 4
// 4
// Edward
// 4
// E
// E
// yes
// d	    

Funktionszeiger

	    #include <iostream>
	    
	    struct Struktur {
		int (*add)(int, int);	// Zeiger auf Funktion mit 2 int Parametern, die int zurueckgibt, sein Name in der struct Struktur ist add.
	    };
	    
	    Struktur s;
	    
	    int _add (int x, int y) { // Funktion mit fester Adresse
		return x + y;
	    }
	    
	    int main () {
		s.add = &_add; // Adressoperator, gebe Adresse an Zeiger
		std::cout <<s.add(1,2) << std::endl;
	    }
	    
	    // 3
	

Und das ist noch gar nichts. Es kommen noch ein paar Zeiger dran.

&Referenzen (Alias)

Das & kann auf zwei Arten und Weisen bei einer Variable stehen. Einmal zur Definition eines Aliases, und einmal zur Wiedergabe der Adresse einer Variable (damit das Gegenstueck zum Pointer, dem man damit Adressen zuweisen kann).

	    
		// & Alias
	    
		int a;
		int &b = a;		// Initialisiere die Referenz b fuer a
		
		b = 12;
		std::cout << a << std::endl; 
		// 12;
	    

& als Adressoperator

Wenn man einen Typen mit & deklariert (also zum ersten mal definiert), handelt es sich um eine Referenz, einen Alias. Wenn man das & aber an Stelle des * verwendet, kann man einem Object o (nicht Object *o) mit &o seine Adresse abfragen.

		
		// & als Adressoperator
		
		char *str;				// Zeiger
		const char hw[10] = "Hallo Welt";	// Konstante

		// Variante 1 (Zuweisung)
		str = &hw;				// Gebe dem Zeiger die Adresse der Konstante
		std::cout << str << std::endl;
		
		// Variante 2 (Parameter)
		void print(char *str) {
		    std::cout << str << std::endl;
		}
		print(&hw);
	    

typedef

typedef ist ein Kuerzel um eigene Datentypen zu definieren, die man aus den Vorhandenen zusammenbaut.

#include <map>
#include <iostream>
#include <string>

typedef std::map<std::string, void> Properties;

int main (int argc, char **argv) {

    Properties p;

}
	

Templates

template <class T> struct Name { /* verwende T als Datentypbezeichner */ };

Wird benutzt, um sich wiederholende Operationen mit unterschiedlichen Datentypen zu vereinfachen. Nehmen wir an, die Funktionen sind fuer zwei Verschiedene Container gleich, aber die Typen passen nicht "uebereinander" (so gesagt), mit einem Template kann man hingegen sauber einen Algorithmus fuer mehrere Typen definieren. Weil ich den Doc gerade beginne, fange ich erstmal mit char und int an.


#include <iostream>

template <class T> class Doppel {
private:
    T value;
public:    
    T set (T v) {
	value = v;
    }
    T get () {
	return value;
    }
};

Doppel<int> a;
Doppel<char> b;

int main () {
    a.set(12);
    b.set('c');
    std::cout << a.get() << std::endl;
    std::cout << b.get() << std::endl;
}	

// ./a.out
// 12
// c
	    
	    

Operatoren überladen

Man kann sich in C++ Operatoren selbst schreiben. Das bedeutet, man kann selbst bestimmen, was Objekt + Objekt ergibt.


#include <iostream>

int main() {

    struct Object {
    public:

	int a;
	int b;

	Object(int _a, int _b) {
	    a = _a;
	    b = _b;
	}

	Object operator += (Object o) {
	    a += o.a;
	    b += o.b;
	}

    } o1(1,1), o2(25,50);
    
    
    o1 += o2; // 1+25 sowie 1+50
    
    std::cout << o1.a << std::endl;
    std::cout << o1.b << std::endl;
}

// g++ o.cpp
// ./a.out
// 26
// 51	    
	    
	    

Überladen

Ueberladen heisst, die gleiche Signatur mehrmals zu verwenden. Das ermoeglicht Dynamik bei den Parametern, um saubere Schnittstellen zu definieren, sonst braeuchte man hier f1, f2, f3, f4, f5...

	    void f (int c) { }
	    void f (char c) { }
	    void f (Object *o) { }
	    void f (Object &o) { }
	    

Überschreiben

Bei Ueberschreiben wird die Methode ersetzt.


		class C {
		    public:
		    C(char d) {}
		    ~C() {}
		    // Abstraktes Interface
		    virtual void f () {
		    }
		};
		
		class D : C {
		    public:
		    D(char f) : C(f) {}
		    ~D() {}
		    // Ueberschreiben der Methode
		    void f () {
			// Ueberschrieben
		    }
		};
		
		int main () {
		    D d('a');
		}
	    
	    

"" und ''

'c' steht fuer ein Zeichen 'c' und char

"c" steht fuer eine "Zeichenkette" und const char *

Container, Iteratoren, Algorithmen

Iteratoren, Container, Algorithmen sind drei Grundkonzepte der STL. So bekam man vor zehn Jahren jede Referenz und auch heute ist das so.

Container

Iteratoren

Algorithmen

Namensräume

C++ kennt das Konzept der Namensräume, wodurch man den Code besser gliedern kann, ohne mit gleichnamigen Funktionen zu kollabieren.

	    
		namespace foo {	// Namensraum
		    void method () {
		    }
		}	
		
		void f () {
		    using namespace foo; // Laesst das Prefix entfallen
		    method();
		}
		
		int main () {
		    f();		
		    foo::method();	// Mit Prefix aufrufen
		}
		
	    

string

Anders als C mit den str-Funktionen, die auf char * arbeiten, bringt C++ eine Stringklasse mit, mit automatischer Speicherallozierung, den str-Funktionen, und überladenen Operatoren, um Stringoperationen durchzufuehren.

		#include <string>
		
		int main () {
		    std::string s("Hallo Welt");
		    std::cout << s << std::endl;
		}
	    
	    

vector

Vector ist in etwa sowas wie ein Array, nur als richtiger Container mit vielen Methoden.

map

Maps sind nuetzliche Container

pair

Eine sehr nuetzliche Klasse ist die pair Klasse, die ueber zwei Properties, .first und .second verfügt, eben ein paar ist.

	    
		#include <pair>
		#include <string>
		int main () {
		    pair<std::string, std::string> p;
		    p.first = "key";
		    p.second = "value";
		}
		
	    

Modularität

Man trennt Header (.h) und Implementation (.cpp) explizit, um einen uebersichtlichen, leicht wartbaren Codebaum zu erzeugen.

    
    // klasse.h
    class Klasse {
    public:
	Klasse();
	~Klasse();
	void rufeMethode();
    };
    
    // klasse.cpp
    Klasse::Klasse () {
	// constructor impl.
    }
    
    Klasse::~Klasse () {
	// destructor impl.
    }
    
    Klasse::rufeMethode () {
	// code the methode    
    }
    
    // main.cpp
    #include <klasse.h>
    
    int main () {
	Klasse k();
	k.rufeMethode();
    }
    
    // Kompilieren.
    
    g++ main.cpp klasse.cpp -o klasse
    
    

make

Das Urtool unter den Buildtools :)

Zur besseren Organisation kann man das alles in Makefiles organisieren und mit make kompilieren.

Wichtig im Makefile ist die korrekte Einrueckung mit Tabs.

Man kann Labels benennen, die eine Aktion durchfuehren.

Man kann Module einzeln erzeugen, und ihnen eine eigene Make Regel zuweisen.

all: 
        g++ main.cpp klasse.cpp -o klasse    
    
    make all

Referenz: man make