worstcase.cc

ERWARTE DAS SCHLIMMSTE

Browsing Posts tagged c++

1. Was ist ein Zeiger und welche Vorteile und Gefahren bringt die Anwendung mit sich?

Ein Zeiger ist ein Variablentyp, der die Adresse einer anderen Variable enthält. Der Vorteil besteht in der Flexibilität der Variable. Ihr kann jeder Zeit ein anderer Speicherblock zugewiesen werden. Die ist auch eines der Probleme die Zeiger mit sich bringen können. Beispielsweise vergessen einen zur Laufzeit zugewiesenen Speicherblock wieder freizugegeben. Dies nennt man dann Memory Leak. Auch ist die Benutzung von Pointern oft für Anfänger etwas verwirrend und kann so zu ungewollten Programmabstürzen bzw. Sicherheitslücken führen. Für fortgeschrittene Anwender ist die Benutzung von Zeigern zu empfehlen, da dies auch einer der Vorteile der Programmiersprache C/C++ ist. Anfänger sollten jedoch nur dann Zeiger verwenden, wenn sie müssen oder sich bei dem was sie tun sicher sind.

2. Wie werden Zeiger definiert und wie dereferenziert?

Ein Zeiger hat immer einen Datentyp. Die Definition unterscheidet sich von der normaler Variablen dadurch, das vor den Variablennamen ein * gestellt wird. Will man einen Zeiger dereferenzieren (auf den Wert zugreifen) muss dem Zeiger wieder ein * vorangestellt werden.

Beispiel:

#include <iostream>
using namespace std;
 
int main()
{
    int i = 5;
    int *prt_i = NULL;
 
    prt_i = &i; // prt_i zeigt jetzt auf die Speicheradresse von i
 
    cout << *prt_i << endl;  // Ausgabe 5
 
    // Klammern sind notwenig, sonst wird nur der
    // Zeiger eine Speicheradresse weitergeschoben
    (*prt_i)++;
 
    cout << *prt_i << endl; // Ausgabe 6
}

3. Erklären sie den Unterschied zwischen „const char* ptr“ und „char* const ptr“ .

Der Ausdruck const char* prt ist ein Pointer auf einen unveränderlichen Charakter. Der Pointer kann auf einen neuen Speicheradresse verschoben werden. Jedoch kann man über eine dereferenzierung den Wert nicht verändern.

Beispiel:

#include <iostream>
using namespace std;
 
int main()
{
    const char *ptr = NULL; // Zuweisung einer Adresse
    char a = 'a';
 
    ptr = &a; // Erneute Zuweisung einer Adresse
 
    cout << *ptr << endl;
 
    *ptr = 'c'; // Dies funktioniert nicht !!!
}

Analog dazu verbietet die Definition char * const ptr das ändern des Pointers. Man kann jedoch über eine dereferenzierung den Wert der Variable ändern.

Beispiel:

#include <iostream>
using namespace std;
 
int main()
{
    char a = 'a';
    char * const ptr = &a;
 
    cout << *ptr << endl;
 
    *ptr = 'b'; // Erneute Wertzuweisung
    cout << *ptr << endl;
 
    ptr = NULL; // Dies funktioniert nicht !!!
}

4. Gegeben ist die Funktion
float mittelwert (float x, float y) {return (x+y)/2;}.
Definieren sie einen Zeiger pf auf diese Funktion.
Rufen sie diese Funktion mit den Parameterwerten x=4, y=2 auf.

#include <iostream>
using namespace std;
 
float mittelwert (float x, float y)
{
    return (x+y)/2;
}
 
int main()
{
    float (*pf)(float, float) = mittelwert;
    float ret = (*pf)(4,2);
 
    cout << ret << endl;
}

5. Gegeben ist die Klasse Ort mit der Methode float Ort::XWert(). Auf ein Objekt von Ort verweist der Zeiger pOrt. Rufen sie die Methode XWert dieses Objektes mit Hilfe von pOrt auf.

Auf eine Methode wird mittels des Strukturoperator -> zugegriffen.

#include <iostream>
using namespace std;
 
class Ort
{
    public:
        // ...
        float XWert() { return 1.2; }
};
 
int main()
{
    Ort *pOrt = new Ort();
 
    float ret;
    ret = pOrt->XWert();
    cout << ret << endl;
}

6. Welche Bedeutung haben Basisklassenzeiger?

Basisklassenzeiger werden für die Polymorphie benötigt. Also das dynamische Binden von Funktionen. Dies ist ein wichtiger Bestandteil von Objektorientierter Programmierung.

7. Worin besteht der Unterschied zwischen Referenzen und Zeiger?

Referenzen sind Aliase für Variablennamen. Sie müssen gleich bei der Definition initialisiert werden. Man erstellt diese indem man den Typ angibt und vor dem Variablennamen ein kaufmännisches Und voranstellt &. Dabei muss man beachten, dass dies nicht der Adressoperator ist, mit dem man an die Adresse einer Variable kommt!
Ein Zeiger wiederum ist ein Verweis auf den Speicherplatz einer anderen Variable.

#include <iostream>
using namespace std;
 
int main()
{
    int i = 0;       // Variable
    int &ref = i;    // Referenz auf Variable i
    int *ptr = NULL; // Zeiger
 
    ptr = &ref; // Benutzung Adressoperators
 
    ref++; // gleichwertig zu i++ oder (*ptr)++
 
    cout << ref << endl; // Ausgabe über Referenz
 
    (*ptr)++;
 
    cout << i << endl;       // Ausgabe über normale Variable
    cout << *ptr << endl;    // Ausgabe über Pointer
}
  • Share/Bookmark

11. Schreiben sie ein Programm zur Berechnung des Minimums von 2 Zahlen eines beliebigen Datentyps mit Hilfe eines Funktionentemplates.

#include <iostream>
using namespace std;
 
template <class Type>
void min( Type &x, Type &y )
{
    if( x > y )
        return y;
    else
        return x;
}
 
int  main()
{
    cout << min( 1, 2 ) << endl;
    cout << min( 1.123, 5.213 ) << endl;
}

12. In welchem Zusammenhang werden Funktionen überschrieben. Zeigen sie dies an Hand eines Beispiels.

Ich nehme jetzt mal an, dass damit virtuelle Methoden gemeint sind. Diese wird benutzt um Methoden in abgeleiteten Klassen zu “überschreiben”. Dabei wird in der Basisklasse der Funktion (=Methode) ein virtual vorangestellt. Um diese Methode dann in der abgeleiteten Klasse zu überschreiben wird sie einfach nocheinmal deklariert.
Dabei ist es üblich der erneuten deklaration wieder ein virtual voranzustellen (zur Kennzeichnung), dies ist jedoch keine Pflicht.
Es funktioniert auch ohne ein virtual in der Basisklasse. Dabei wird aber die ursprüngliche Funktion nur versteckt. Dies ist sehr schlechter Programmierstil und wird beim Aufruf mit dem Basisklassenzeiger Probleme bereiten.

Beispiel:

class Lebewesen
{
    ...
    virtual void bewege
    {
        cout << "Schritt" << endl;
    }
}
 
class Känguru : public Lebewesen
{
    ...
    virtual void bewege
    {
        cout << "Hüpf" << endl;
    }
 
}

13. Was versteht man unter direkter und indirekter Rekursion? Zeigen sie den Unterschied an Hand von zwei Programmbeispielen.

Bei der direkten Rekursion ruft sich die Funktion selbst wieder auf. Bei einer indirekten Rekursion wechseln sich quasi zwei Funktionen ab und rufen sich gegenseitig auf.

Beispiel direkte Rekursion:

#include <iostream>
using namespace std;
 
int potenz (int x, int n)
{
      if( n == 0 )
        return (1); // x^0 = 1
    else
        return ( potenz (x, n-1) * x );
}
 
 
int main()
{
    int pot = potenz( 2, 8 );
    cout << pot << endl;
}

BEISPIEL INDREKTE REKURSION

Mir fällt nichts ein, wenn ihr ein Bespiel habt, schickt es mir doch bitte.

14. Gegeben ist der folgende Programmcode:

#define mul(a,b) (a*b)
erg=mul(2+3,4+1);

Welcher Wert steht in erg?

Prinzipiell gar keiner, da in dem Programm keine main Funktion definiert wurde :D . Sieht man aber darüber hinweg, so ist es laut vielen Regeln immer noch eine sehr unsichere Implementierung einer Makrofunktion. (siehe hier)

Korrekt wäre eine Multiplikation in der folgenden Form:

#define mul(a,b) ((a)*(b))

Aber bei diesem Beispiel kommt bei mir 15 raus. Ich denke jedoch, dass dies von der Implementierung im Compiler abhängt. Ich werde mich aber noch mal informieren, was dabei genau passiert.

  • Share/Bookmark

6. Betrachtet wird die Klasse string der Standardbibliothek. Schreiben sie ein kleines Programm, das aus einer Datei zwei Textzeilen einliest, diese verkettet und wieder ausgibt.

#include <iostream>
#include <fstream>
using namespace std;
 
int main( int argc, char **argv)
{
    ifstream datei;
    datei.open("test.txt");
 
    string str1, str2;
 
    // Zwei Zeilen auslesen
    getline( datei, str1 );
    getline( datei, str2 );
 
    datei.close();
 
    cout << "Beide Zeilen zusammen:" << endl;
 
    str1 += str2; // Beide Zeilen zusammenhängen
    cout << str1 << endl;
}

7. Was versteht man unter einem Funktionsprototyp? Geben sie ein Beispiel für seine Verwendung.

Ein Funktionsprototyp ist eine Funktionsdefinition ohne Implementierung. Diese wird in der Regel in Header Dateien definiert. Dies dient dazu Funktionen in Programmteilen bekannt zu machen, ohne die komplette Implementierung offen zulegen. Dies wird beispielsweise bei kommerziellen Produkten gemacht um das “Wissen” also die Implementierung zu verbergen. Auch wird dies vom Compiler dazu benutzt, um zu überprüfen ob die richtigen Parameter- und Rückgabetypen benutzt werden. Bei der Benutzung von C++ kann man den Begriff Funktionsprototyp noch etwas erweitern, da man unter Umständen auch Klassen darunter verstehen kann.

8. Erklären sie die verschiedenen Bedeutungen des Bezeichners static.

Static hat verschiedene Bedeutungen, welche jeweils vom Kontext abhängen.

lokale Variable:
Die Variable bleibt auch nach Verlassen des Blockes in dem sie definiert wurde erhalten.

globale Variable:
Die Variable ist in der ganzen Datei sichtbar.

“member” Variable (Variable innherhalb einer Klassendefinition):
Alle Instanzen einer Klasse benutzen die gleiche Variable.

Funktion (außerhalb einer Klasse):
Funktion ist innerhalb der ganzen Datei sichtbar.

Funktion in einer Klasse:
Diese Funktionen können auch ohne Instanz der Klasse aufgerufen werden. Sie können jedoch keine “member” Variablen verändern.

9. Wie kann erreicht werden, dass beim Funktionsaufruf nicht alle deklarierten Parameter übergeben werden müssen?

In C++ ist es im Vergleich zu C möglich Parameter mit einem Wert vorzubesetzen. Wenn also das Parameter nicht übergeben wird, wird der vorher definierte Wert benutzt.

Beispiel:

#include <iostream>
using namespace std;
 
int test( int a = 1, int b = 2, int c = 3 )
{
    cout << "a:" << a;
    cout << " b:" << b;
    cout << " c:" << c;
    cout << endl;
    return 0;
}
 
int  main()
{
    test();
    test(4);
    test(5,6);
    test(7,8,9);
}

Ausgabe:

a: 1 b: 2 c: 3
a: 4 b: 2 c: 3
a: 5 b: 6 c: 3
a: 7 b: 8 c: 9

10. Warum ist es sinnvoll, dass Funktionen überladen werden können? Demonstrieren sie dies am Beispiel der Berechnung des Maximums von Zahlen.

#include <iostream>
using namespace std;
 
int max( int a , int b )
{
    if( a > b )
        return a;
    else
        return b;
}
 
char max( char a, char b )
{
    if( a > b )
        return a;
    else
        return b;
}
 
 
int  main()
{
    cout << "Zahlen :" << max(3,9) << endl;
    cout << "Buchst.:" << max('4','6') << endl;
 
}

Dieses Beispiel demonstriert, wie man mit einer überladenen Funktion mehrere Datentypen verarbeiten kann. Eine weitere Anwendung wäre beispielsweise eine Maximum Funktion, welche ein Charakter und ein Integer entgegen nimmt.

  • Share/Bookmark

1. Was gilt bezüglich der Sichtbarkeit von gleichen Namen in geschachtelten Blöcken?

Jede Variable hat einen Gültigkeitsbereich (engl. scope). Ein Gültigkeitsbereich wird vereinfacht gesagt durch zwei geschweifte Klammern definiert. Innerhalb eines Gültigkeitsbereichs kann man nur auf Variablen eines übergeordneten Gültigkeitsbereich bzw. den eigenen zugreifen. Sollten zwei Variablen in einem Übergeordneten und im lokalen Gültigkeitsbereich den gleichen Namen besitzen, so kann nur auf die Lokale Variable zugegriffen werden.

Beispiel:

#include <iostream>
using namespace std;
 
int main()
{
    int var1 = 1;
    cout << var1 << endl;       // Ausgabe: 1
    if(1)
    {
        cout << var1 << endl;   // Ausgabe: 1
        int var1 = 2;
         cout << var1 << endl;  // Ausgabe: 2
    }
    cout << var1 << endl;       // Ausgabe: 1
}

2. Was versteht man unter dem Namensraum?

Ein Namensraum (engl. namespace) ist eine Abstrakte Umgebung die Klassen, Funktionen und Variablen gruppiert. So ist beispielsweise die ganze C++-Standardbibliothek im Namensraum std definiert. In C++ werden Namensräume in der Regel dazu benutzt Namenskollisionen zu vermeiden. Die Namensraumauflösung ist hierarchisch. So kann innerhalb eines Namensraumes ein anderer definiert werden. Mit der Eingabe von using namespace kann ein Namensraum fest eingestellt werden. Ansonsten muss mittels des Bereichsoperators :: auf den Namensraum zugegriffen werden.

Beispiel:

#include <iostream>
 
namespace ns1
{
    int var1 = 0;
    namespace ns2
    {
        int var2 = 1;
    }
}
 
int main()
{
    std::cout << ns1::var1 << std::endl;
    std::cout << ns1::ns2::var2 << std::endl;
 
    using namespace std;
 
    cout << "hier ohne std..." << endl;
}

3. Schreiben sie ein Programm, das 3 ganze Zahlen verlangt und die größte ausgibt.

#include <iostream>
#include <cstdlib> // atoi
using namespace std;
 
int main( int argc, char** argv )
{
    if( argc != 4 ) // erstes Argument argv[0] ist Programmname
    {
        cout < < "Bitte mit drei Zahlen aufrufen" << endl;
        return 0;
    }
 
    int a,b,c,max;
    a = atoi(argv[1]);
    b = atoi(argv[2]);
    c = atoi(argv[3]);
 
    if( a < b ) max = b;
    else max = a;
    if( max < c ) max = c;
 
    cout << "Größte Zahl ist: " << max << endl;
}

4. Schreiben sie ein Programm zur Lösung der quadratische Gleichung ax² + bx + c = 0.

#include <iostream>
#include <cstdlib>
#include <cmath>
 
using namespace std;
 
int main( int argc, char **argv)
{
    if( argc != 4 )
    {
        cout << "Benutzung: Eingabe der Parameter in der Form:" << endl;
        cout << argv[0] << " a b c" << endl;
        return 0;
    }
 
    float a = atof( argv[1] );
    float b = atof( argv[2] );
    float c = atof( argv[3] );
 
    float dis = (pow(b,2)) - (4*a*c);
 
    if( dis < 0 )
    {
        cout << "Das Ergebnis kann nicht berechnet werden" << endl;
        return 0;
    }
    else if( dis == 0 )
    {
 
        float erg1 = (-b + sqrt(dis) ) / (2*a);
        cout << "Ergebnis = +/- " << erg1 << endl;
    }
    else
    {
        float erg1 = (-b + sqrt(dis) ) / (2*a);
        float erg2 = (-b - sqrt(dis) ) / (2*a);
        cout << "Ergebnis = " << erg1 << endl;
        cout << "Ergebnis = " << erg2 << endl;
    }
}

5. Betrachtet wird die Klasse vector der Standardbibliothek. Wie kann man eine Bereichsüberschreitung feststellen?

Man kann (und sollte) die ausgelöste Exception abfangen.

Beispiel:

#include <iostream>
#include <vector>
using namespace std;
 
int main( int argc, char **argv)
{
 
    vector<int> vec( 5, 1 ); // 5 Elemente mit Wert 1
 
    for( int i = 0; i < 10; i++ )
        try
        {
            cout << vec.at(i) << endl;
        }
        catch( exception e )
        {
            cerr << "Fehler: " << e.what() << endl;
        }
}
  • Share/Bookmark
Powered by WordPress Web Design by SRS Solutions © 2010 worstcase.cc Design by SRS Solutions