Nested Klassen

Wenn innerhalb einer Klasse, eine andere Klasse gebraucht wird. Es entsteht dadurch eine Verschachtelung aus Klassen.

class C{
.     public:
.           struct M {
.                      //
.           };
};

 

C++ Geltungsbereich (Scope) von Variablen

lokale Variabeln überschreiben globale 8.4

Bjarne nennt in 8.4 unterschiedlichen Geltungsbereiche von Variablen
– global
– lokal
– in Klasse allein (siehe vektor unten)
– in Namensgebeit (z. B. struct, enum, …)
– in Aussagen (z. B. innerhalb eines for)

Der Geltungsbereich hat mit dem Datentyp etwas zu tun, und auf welchem RAM-Segment der Code abgelegt wird.

extern

extern int aktuelle_Version;

Die Variable wird nur deklariert und ist überall sichtbar.
Sie kann (theoretisch) in jedem File neu definiert und mit neuen Werten zugewiesen werden, das ist jedoch gefährlich. Besser ist es, nur in 1 File die Variable zu definieren und dann für alle anderen Files nur sichtbar zu machen.

Klasse

class MyVector{
.    // Klassenvariable. Gültigkeit in der Klasse 
.    vector <int> v      
.
.    public:
.          // öffentliche Funktion
.          int largest(){
.             int max = 0;
.             for (int i = 0; i < v.size(); i++) {
.               max = max( x, abs(v[i]) );
.             return max;  
.          }
.         // öffentliche Variable
.         int i;
.
};

Der Vektor v ist nur in der Klasse sichtbar. Ausserhalb exstiert er nicht und kann auch nicht angesprochen werden.

Argumente sind lokal

int funcition_b (int value_1){  
.  
.    //
}

Die Funktion ist global, aber der Parameter value_1  ist lokal bezüglich der function_b .

 

constexpr <function>
Lokale Variablen liegen FIFO im Stack. Man kann nicht auf sie zugreifen (Bjarne 8.5.9). Soll eine Funktion während dem Kompilieren definiert werden, braucht sie das Schlüsselwort constexpr bei all ihren Variablen und für die Funktion.

constexpr int scale_x = 3;
constexpr int scale_y = 2;
constexpr Point scale( Point P){
.
.     return { scale_x * P.x, scale_y * P.y };
}

Unterscheidung Deklaration <-> Definition

Bjarne Stromkamph behandelt diesen Unterschied im Kapitel 8.2.

Deklaration

extern variable_b;
void function_a();

Deklarationen reservieren Namen. Sie reservieren keinen Platz. Sie sind für den Linker wichtig.
Die „bekannteste“ Deklaration ist die forward declaration von Funktioen oder das setzen von globalen Variablen.
Deklarationen kommen in ein Header-File.

In Deklarationen müssen die Parameter nicht zwingend angegeben werden. Man macht dies jedoch, damit der Code lesbarer ist (Bjarne 8.5.1).

Definition
Mit der Definition wird Speicherplatz alloziert. Die Variable erhält einen Platz.

int var_2= 5;

void function_a(){
.     //code
.     return 0;
}

 

 

C++ Datenypen

Vektor
Es gibt den Datentyp Vektor in C++. Im <- Link ist einiges Hilfreiche notiert.

std::vector <type> <name>

 

let: Deklaration und nicht Zuweisung
Let ist ein altes Keyword und will den Vorgang der Deklaration von der Wertzuweisung unterscheiden

let value_1 = 3;

 

Globale Variable

extern int value_b;

Extern stehet in allen Files, die auf diese Variable zugreifen müssen.
Definiert wird die Variable nur in einem File. In diesem muss die Variable auch extern gesetzt werden.

extern  ist formal eine Deklaration (und darf keinen Wert erhalten). Ermöglicht dadurch, dass die Variable in vielen Files deklariert werden kann.

const
Hat in C++ eine vielseitige Bedeutung. Const als Argument  (Bjarne 8.5.4) verhindert, dass die Funktion print() die Werte versehentlich ändert.

void print( const vektor <int>& messungen){
.   // code
}

Dynamische Speicherzuweisung C: „Allocation“

Nicht dynamisch
Definiert man eine Variable, Konstante oder ein Array mit fixer Länge char array[5] , so ist die Länge des Speicherplatzes klar und die Speicherzuweisung ist nicht dynamisch.
Nicht dynamische Speicherzuweisungen werden im STACK abgelegt.

Grund für dynamisch
Die Grösse des Arrays ändert sich während der Verarbeitung. Man kann das Array nicht auf eine fixe Grösse initialisieren.
Eine dynmische Speicherzuweisung nennt man Allozierung. Die Daten werden im HEAP abgelegt.

Vorgehen
1. Speicherplatz reservieren
Dies geschieht über die Funktion malloc().  Es ist zwingend, dass man die Anzahl Bytes nennt.  int *buffer1 = malloc(n * sizeof(data));.  N bezeichnet die Anzahl daten, data bezeichnet die Grösse eines Datenpackets.
Die Funktion sizeof( )  gibt immer die Anzahl Bytes der Variable zurück.
2. Auf dynamische Daten zugreiffen
Die Funktion malloc() gibt als Returnwert die Adresse zurück. Auf dynamische Daten kann nur über den Pointer zugegriffen werden. Ein direkter Zugriff über den Variablennamen auf die Daten ist nicht möglich. data1 = buffer1[0];
3. Speicherplatz freigebe
free(buffer1); Wird Speicherplatz nicht mehr freigegeben, so entsteht ein Memory Leak. Ohne free() verkleinert sich die Speicherkapazität des RAMs.

Pointers in C

Durch das Setzen eines Asterisk vor einer Variable, definiert man, dass die Variable keinen Wert, sondern eine Adresse beinhaltet:

  • Zeigen auf eine Speicheradresse
  • Sie beinhalten eine Adresse
    <type> *<name>, *<name>;

    Adresse übergeben (referenzieren)

    int *pointer;
    pointer = &variable_a;
    

    Die Variable_a wird gelesen, aber nur ihre Adresse wird übergeben.

    Wert übergeben (dereferenzieren, per value)

    *pointer = 10;
       // oder
    var1 = *pointer_b;
    printf(" %d ",  *pointer)

    Der Wert wird ebenfalls über einen Stern zurückgegeben. Das ist verwirrlich, zeigt aber auch, dass es ein Wert von einem Pointer ist…

    —————————————————————————–

    Pointer a

    int *a;
    int b;

    Wohin zeigt a ?

    b = &a;  // b hat die Adresse

    a soll einen Wert erhalten

    *a = 10;

Keyword static in C

static lokale Variable
– Ziel:
Die Variable behält ihren Wert zwischen zwei lokalen Funktionsaufrufen.
Die Initialisierung gilt nur beim ersten Mal (Ablegen auf dem Datensegment).
– Sichtbarkeit:
Der Gültigkeitsbereich bleibt gleich. Der Ablageort der Variable ist auf dem Datensegment anstelle des Stacks.
– Bsp: Zählen von Interrupts

<type> function( ){
.       static int nr_of_calls = 0;
.
.       // do some code
.
       nr_of_calls ++;
.      return <type>;
}

 

static globale Variable
– Ziel:
Wie bei der lokalen statischen Variable. Zwischen den Aufrufen behält die Variable den Wert. Wenn sie global ist, dann behält sie den Wert während der ganzen Programmdauer.
– Sichtbarkeit:
Eine externe Variable mit static ist ausserhalb der Datei unsichtbar!

 

static Funktion
– Sichtbarkeit:
Wird eine Funktion extern und static gesetzt, dann ist sie ausserhalb der Datei nicht sichtbar.

Unit Testing with Catch

Es gibt CATCH, das Testing für C++ ermöglicht. Es gibt einen Blogeintrag, der den Einsatz von CATCH auf hohem Niveau beschreibt. Zum Starten ist das Tutorial sehr gut.

#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"

unsigned int Factorial( unsigned int number ) {
    return number <= 1 ? number : Factorial(number-1)*number;
}

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}

Catch innerhalb eines Files einbauen und ausführen

1. Catch als Header in File einbinden

#define CATCH_CONFIG_RUNNER  
#include "catch.hpp"

2. Funktion, die getetst werden will

double calculate(string calculation) {
    double result = 0.0;
    ts.set(calculation);
    result = expression();
    return result;
}

3. Testfälle aufstellen

TEST_CASE( "r", "[digit]" ) {
    REQUIRE(calculate("4") == 4);
    REQUIRE(calculate("6") == 6);
    REQUIRE(calculate("4+2") == 6);
    REQUIRE(calculate("4+2+2") == 8);
    REQUIRE(calculate("4+2+2+4+2+2+1") == 17);
    REQUIRE(calculate("4-2") == 2);
    REQUIRE(calculate("8-2") == 6);
    REQUIRE(calculate("8-2-2") == 4);
    REQUIRE(calculate("3*2") == 6);
    REQUIRE(calculate("3*3") == 9);
    REQUIRE(calculate("3*2*3") == 18);
    REQUIRE(calculate("3+2*4") == 11);
    REQUIRE(calculate("3*2+4") == 10);
    REQUIRE(calculate("4 + 2") == 6);
    REQUIRE(calculate("10 + 2") == 12);
    REQUIRE(calculate("10 + 123") == 133);
    REQUIRE(calculate("-4 + 5") == 1);
    REQUIRE(calculate("-100 + 5") == -95);
    REQUIRE(calculate("(4 + 5)*2") == 18);
    REQUIRE(calculate("3+(-4 * 5)") == -17);
    REQUIRE(calculate("1.5 * 2") == 3);
}

Am Ende des Files folgend die Testfälle.

4. Datei kompilieren
5. Ausführen über Konsole und Argumente mitgeben
Gibt man keine Argumente mit, werden alle Tests durchgelauften.

Struktur der Testfälle

REQUIRE(calculate("6") == 6);

Nach REQUIRE schreibt man die Funktion, die aufgerufen werden soll und gibt ihr die notwendigen Werte mit.
Nach dem == folgt das zu erwartende Ergebnis.

Basic Error-Library

#include <exception>

Der Standardteil dieser Bibliothek umfasst folgende Exceptions:
Logische Fehler                                 Runtime Fehler
– logic_error                                         – runtime_error
– domain_error                                  – range_error
– invalid_argument                          – overflow_error
– length_error                                    – underflow_error
– out_of_range

In der Funktion

void Token_stream::put_back(Token t) {
    if (full) {
        throw std::overflow_error("buffer already full");
    }
    full = true;
    buffer = t;
}

 

 

own Error-Class (Basics)

class neg_sum{};

int main(){
  try {
.     ...
.     
.     ...
  } catch(neg_sum const & e){
.     std::cout << "negative value"<<"\n";
  }   
}

In der Funktion

double solve_quadratic(int a, int b, int c) {
    double delta = pow(b, 2) - (4 * a * c);
    if (delta < 0) {
        throw Negative_delta{};
    }
.   .....
}