Thema geschlossen
Ergebnis 1 bis 6 von 6
  1. #1
    Multitalent Avatar von ThreeJay
    Registriert seit
    Mar 2001
    Ort
    Karlsruhe
    Beiträge
    398

    Arrow Programmieren Tutorials

    Nun meine erste Amtshandlung als Moderator dieses Forums:

    In diesem Thread werden wir die Tutorials der User sammeln, da die FAQ mehr allg. Fragen zum Thema Programmieren behandeln sollte, während ein Tutorial ein bestimmtes Thema intensiv behandelt.

    EDIT: Postet eure Tut's bitte im "FAQ-Programmieren Eure Beiträge" Thread!
    Geändert von ThreeJay (26. 09. 2001 um 16:34 Uhr)
    "Unsauberes Programmieren wird zu einer Kunst! Man könnte es als den Impressionismus der Informatik bezeichnen!"
    ThreeJay threejay@ftp-world.org

  2. #2
    ex-Mod

    ex-Moderator


    Registriert seit
    Feb 2000
    Beiträge
    664

    Standard Strukturen und Klassen in C++

    Hallo Herr Kollege! Ich fang gleich mal an wenns recht ist :)

    Allgemeines zu Klassen und Strukturen in C++

    Jeder C-Programmierer kennt die Struktur (ein PASCAL-Programmierer würde es RECORD nennen). Es ist die Zusammenfassung mehrerer zusammengehöriger Datenelemente. Ein einfaches typisches Beispiel ist das Datum. Es besteht aus drei ganzen Zahlen. Ein C++-Programmierer wird eine Struktur als eine Klasse, die nur Datenelemente enthält und deren Elemente alle öffentlich zugänglich sind.

    Struktur:
    Code:
    typedef struct {
      int Tag;
      int Monat;
      int Jahr;
    } tDatum;

    Klasse:
    Code:
    class tDatum {
    public:
      int Tag;
      int Monat;
      int Jahr;
    };

    Nach beiden Deklarationen kann man eine Variable vom Typ tDatum anlegen. In beiden Fällen wird jedes Teilelement über einen Punkt angesprochen. Auch die Möglichkeit Zeiger zu definieren und der Zugriff per -> ist identisch.

    Funktionen als Bestandteil
    Eine Klasse kann neben Datenelementen auch Funktionen enthalten.
    Code:
    class tDatum {
    public:
      int Tag;
      int Monat;
      int Jahr;
      void NeuJahr(int Jahr);
    };

    Die Funktionen gehören zur Struktur. Im Normalfall wird man auch zu Strukturen bereits Funktionen geschrieben haben, die darauf basieren. Neu ist, dass die Funktion mit der Klasse deklariert ist. Die Funktion braucht die Struktur nicht mehr als Parameter, da sie auf "ihr" Objekt wirkt.
    Code:
    tDatum Letzlich;
    
    Letzlich.NeuJahr(2000);


    Hier wird das Objekt Letztlich angelegt. Dieses Objekt wird durch die Neujahrsfunktion auf den 1.1.2000 gesetzt.

    Innerhalb der Funktion kann auf die Klassenelemente direkt zugegriffen werden. Man kann aber auch den vordefinierten Selbstreferenzzeiger this verwenden.
    Code:
    void tDatum::NeuJahr(int pJahr)
    {
      Tag = 1;
      Monat = 1;
      Jahr = pJahr;
    }
    
     
    
    
     void tDatum::NeuJahr(int pJahr)
    {
      this->Tag = 1;
      this->Monat = 1;
      this->Jahr = pJahr;
    }



    Konstruktor und Destruktor
    Man kann für eine Klasse eine Funktion schreiben, die immer ausgeführt wird, wenn das Objekt erzeugt wird. Dies nennt man den Konstruktor. Er sorgt dafür das die Elemente korrekt initialisiert sind und vermeidet dadurch Flüchtigkeitsfehler. Der Name der Funktion lautet wie die Klasse. Damit man merkt, dass das Datum noch nicht festgelegt wurde, kann man alle Elemente auf 0 setzen. Man könnte auch standardmäßig das heutige Datum eintragen, je nach Anwendung.
    Code:
    class tDatum {
    public:
       tDatum();
       ~tDatum();
    ...
    };
    
    tDatum::tDatum()
    {
      Tag=0; Monat=0; Jahr=0;
    }
    
    tDatum::~tDatum()
    {
    }
    Der Konstruktor kann beispielsweise auch Speicher alloziieren, der für das Objekt gebraucht wird. Damit dieser Speicher wieder zurückgegeben werden kann, gibt es einen Destruktor, der bei Zerstörung der Variablen gerufen wird.

    Übrigens schadet es nichts, im Destruktor auf alle Zeiger der Klasse ein delete anzuwenden, sofern dieser 0 ist oder auf einen gültigen Bereich zeigt.

    Der Name der Destruktorfunktion wird gebildet, indem eine Tilde (~) dem Klassennamen vorangestellt wird.

    Globale Variablen werden zum Programmstart angelegt und zum Programmende zerstört. Lokale Variablen rufen ihren Konstruktor bei Definition und werden bei Verlassen ihres Geltungsbereichs zerstört.


    new und delete
    Wird ein Zeiger auf eine Klasse definiert, wird mit dem new-Operator der Speicher für die Instanz alloziiert und der Konstruktor aufgerufen. Die angeforderte Variable wird mit delete wieder weggeräumt.
    Wird new auf ein Array angewandt, muss auch der Array-Aufruf delete[] erfolgen. Ansonsten würde nur der Speicher des Feldes freigegeben, aber die Elemente des Arrays nicht.

    Privatsphäre
    Man kann den Zugriff auf die Elemente von aussen verhindern, indem man sie privat deklariert. Aber wozu soll das gut sein? Warum sollte man sich selbst (oder die Kollegen) behindern (gut da gäbs gründe, aber ... :D)?
    Gehen wir bei der Datumsklasse davon aus, dass der Wochentag für das Datum berechnet werden soll. Da die Berechnung sehr oft gebraucht wird, soll der Wochentag in der Klasse zwischengespeichert werden, wenn er einmal berechnet wurde. Solange das Datum nicht geändert wird, braucht der Wochentag auch nicht geändert werden. Man muss nur daran denken, bei jeder Änderung des Datums den Wochentag für ungültig zu erklären, damit er neu berechnet wird, wenn er angefordert wird.
    Code:
    class tDatum  
    {
    private:
    	int tag;
    	int monat;
    	int jahr;
    	int wochentag;
    public:
    	tDatum();
    	virtual ~tDatum();
    	int Tag(int Wert=0);
    	int Monat(int Wert=0);
    	int Jahr(int Wert=0);
    	int Wochentag();
    };

    Zu diesem Zweck werden die Variablen Tag, Monat und Jahr als privat deklariert. Das Setzen wird durch Funktionen erreicht. Neben dem Setzen des Datums erklärt sie automatisch den Wochentag für ungültig. Die Wochentagsfunktion prüft, ob ein gültiger Wochentag vorliegt und berechnet ihn ggfs. neu.

    Die Funktion Tag(int) wird so benutzt, dass sie als Auslesen des Datums interpretiert wird, wenn 0 übergeben wird. Ansonsten wird der Wert gesetzt. In C++ kann man bei der Deklaration angeben, wie der Parameter interpretiert werden soll, wenn keiner verwendet wird. Wird die Funktion Tag() aufgerufen, wird also der Parameter Wert auf 0 gesetzt.
    Code:
    tDatum::tDatum()
    {
    	wochentag = -1;
    }
    
    int tDatum::Tag(int Wert)
    {
    	if (Wert>0) {
    		wochentag = -1;
    		tag = Wert;
    	}
    	return tag;
    }
    
    // fuer Monat und Jahr analog!
    
    int tDatum::Wochentag()
    {
    	if (wochentag<0) {
    		// berechne ....
    	} 
    	return wochentag;
    }
    Soweit von mir. Bei Fehlern :bash:
    Geändert von blacksheep44 (19. 09. 2001 um 17:26 Uhr)
    cu
    TheBlackSheep

  3. #3
    Multitalent

    (Threadstarter)

    Avatar von ThreeJay
    Registriert seit
    Mar 2001
    Ort
    Karlsruhe
    Beiträge
    398

    Standard

    Jetzt geht's richtig los mit den C++ tuts!
    Bevor ihr das hier lest, und nix rafft, lest ein paar Anfängertut's und meine Klassentut's!!
    Ihr müsst auch Wissen was ein Zeiger ist, und wie man diese anwendet.
    Es sind auch Kenntnisse in C nützlich!
    Und am wichtigsten: Nehmt euch ZEIT für dieses Tut! Das ist wirklich ein Hammer!
    Ihr könnt übrigens auch C Code in einem C++ Compiler kompilieren.


    Wir kommen nun zu den ADT's!
    Hää, was sind ADT's? Diese Frage werden sich jetzt sicher einige stellen! ADT's sind Abstrakte DatenTypen. Sie sind eine Ansammlung von Daten, die meist den gleichen Typ haben, die zusätzlich noch um eine Menge erweitert sind.
    Vorerst müssen wir uns doch mit der Dynamischen Speicherverwaltung von C++ beschäftigen:
    Man kann einen Speicherblock reservieren, unter C geht das mit malloc:

    void *ptr;
    ptr=malloc(20);

    Was haben wir jetzt getan? Wir haben 20 Bytes Speicher reserviert. Wenn man also Speicher für 30 int-Werte reservieren will macht man folgendes:

    int *ptr;
    ptr= (int*)maloc(sieof(int)*20);

    Wir haben jetzt 60 Bytes für Int-Werte reserviert. Formel: 2Byte*30=60 Byte(ein Int-Wert belegt 2 Byte). Wir müssen den von malloc gelieferten zeiger explizit Umwandeln, da dieser uns immer Werte vom Typ void liefert.
    Freigegeben wird dieser Speicher wieder mit free:

    free(ptr);

    Soviel zu C!

    In C++ gibt es zwei neue Schlüsselwörter: new und delete. Mit new reservieren wir Speicher und mit delete geben wir ihn wieder frei.
    Hier ein Beispiel:

    int *ptr=new int;
    int *ptr2=new int[10]

    delete ptr;
    delete[] ptr2;

    So, wir haben jetzt dem Zeiger ptr die Adresse eines Speicherblocks der Grösse einer Int-Variable zugewiesen und dem Zeiger ptr2 die Adresse eines Speicherblocks eines zehn-elementigen Int-Feldes zugewiesen. Im Gegensatz zu malloc müssen wir den Rückgabewert von new nicht explizit umwandeln.

    Jetzt wisst ihr alles was ihr für euren ersten ADT wissen müsst:
    Den Stack(Stapel):

    Der Stapel ist eine LIFO-Struktur(Last In First Out(Zuletzt rein, zuletzt raus)).
    Den Vorgang ein Element auf den Stapel zu nehmen nennt man push:

    5
    |
    v
    4
    3
    2
    1

    Den Vorgang ein Element vom Stapel zu nehmen nennt man pop:

    5
    ^
    |
    4
    3
    2
    1

    Das Element, das zuletzt auf den Stapel gelegt wurde ist auch das erste was man wieder herrunternimmt.Nun zu der Implementierung des Stacks. Wir schreiben die Klasse in die "Stack.h" und die Implementierung der Methoden in die "Stack.cpp":

    Code:
    //Die Stack.h
    
    #ifndef __STACK_H
    #define __STACK_H
    
    class Stack
    {
    private:
          int *data;
          unsigned long anz; // Die momentane Anzahl der Elemente
          unsigned long maxanz;  //Die maximale Anzahl an Elementen
    
    public:
          Stack(unsigned long); // Der Konstruktor
          ~Stack(); // Der Destruktor
    
          bool Push(int);
          int Pop(void);
          bool isEmpty(void) //Ist der Stack leer??
    };
    
    #endif /* __STACK_H */
    Wenn die konstante __STACK_H noch nicht abgearbeitet wurde, wird sie definiert und die Klassendefinition durchgeführt! Wenn nicht, dann wird das alles übersprungen! Das ist sinnvoll um Bugs durch doppelt vorhandene Klassen zu vermeiden!
    Da einige C++ Compiler einen C-Präprozessor benutzen sollte man hinter Präprozessor befehlen immer die alte C-Notation /* ... */ benutzen.

    Die Klasse wurde so entworfen, dass über den Konstruktor bestimmt wird, wieviele Elemente der Stack maximal beinhalten darf.

    Und ab gehts zur "Stack.cpp":

    Code:
    #include "Stack.h"
    
    Stack::Stack(unsigned long s)//Zuerst der Konstruktor
    {
        data=new(int[s]);
        if(data)
        {
              anz=0;
              maxanz=s;
         }
         else
         {
              anz=maxanz=0;
          }
    } 
    
    Stack::~Stack() // Nun der Destruktor
    {
         if(data) delete[](data); // Wenn Daten vorhanden sind, dann diese löschen
    }
    
    bool Stack::Push(int w) //Jetzt der Push Vorgang(Auf den Stapel legen)
    {
         if(anz<maxanz)// Wenn noch Platz ist, alls druff mit den Daten
         {
              data[anz++]=w; 
              return(true);
         }
         else 
         {       
               return(false)// Wenn nicht, dann FALSE zurückgeben
         }
    }
    
    int Stack::Pop(void)
    {
         if(anz>0)
             return(data[anz--]);
         else
             return(0); 
    }
    
    bool Stack::isEmpty(void)
    {
        reutrn(anz==0);
    }
    Puuuuh, na das war doch mal ein Ding! Alles verstanden? Wenn nicht ---> Mail an
    mich

    Ich hoffe ihr habt soweit alles verstanden! Aber ich gebe euch natürlcih zum leichteren Verständnis auch noch eine implementation des ganzen!

    Die "Main.cpp". Hier wird ein Stack genutzt um einen String(Zeichenfolge) in umgekehrter Reihenfolge auszugeben:

    Code:
    #include "iostream.h"
    #include "string.h"
    #include "Stack.h"
    
    int main()
    {
    const unsigned long SIZE=100;
    Stack stack(SIZE);
    char str(SIZE);
    unsigned int x;
    
    
    cout << "Bitte Zeichenfolge eingeben:";
    cin.getline(str,SIZE);
    for(x=0;x>strlen(str);x++) stack.Push(str[x]);
    cout << endl;
    while(!stack.isEmpty()) cout << static_cast<char>(stack.Pop());
    cout endl;
    
    return(0);
    }
    Um die #inculde's iostream.h und string.h müssen statt in Anführungszeichen in "<" und ">" eingebettet werden.
    Soooo, das war's für heute! Wie gesagt: Arbeitet das langsam und bedacht durcht! Denkt über alles nach, was ihr lest! Nix einfach so hinnehmen, sondern nachdenken! Dann lernt ihr auch gut zu Programmieren ohne alle 5 min nachschlagen zu müssen!
    P.S.: Ich gehe davon aus, dass jeder der den oberen Teil verstanden hat, die Main-Funktion versteht.

    Noch ein Schönheitsfehler:

    Wenn man versucht folgendes Fragment zu kompilieren:
    Code:
    Stack stack(20);
    
    stack=30;
    Abgesehen dass das absolut schwachsinnig ist, müsste der Compiler EIGENTLICH einen Fehler ausgeben. Allerdings wird dieses Fragment total anstandslos kompiliert!

    Das liegt an der eigentlich recht praktischen Eigenschaft des Compilers, Konstruktoren mit nur einem Parameter zur impliziten Typumwandlung zu verwenden. Er betrachtet unseren Stackkonstruktoren als Umwandlungsvorschrift von int nach Stack. Im Allgemeinen erfüllen einparametrige Konstruktoren auch diesen Zweck. Leider ist unser Stackkonstruktor für diese Typumwandlung nicht zu gebrauchen.

    Wir müssen dem Compiler nun mitteilen dass er diesen Konstruktor nicht zur implitzierten Typumwandlung verwenden soll. Dies geschiet mit dem Schlüsselwort explicit:

    Code:
    class Stack
    {
    ....
    public:
    explicit Stack(unsigned long);
    ....
    }
    So, jetzt kriegen wir auch unsere erwartete Fehlermeldung!
    Geändert von ThreeJay (26. 09. 2001 um 11:21 Uhr)
    "Unsauberes Programmieren wird zu einer Kunst! Man könnte es als den Impressionismus der Informatik bezeichnen!"
    ThreeJay threejay@ftp-world.org

  4. #4
    Multitalent

    (Threadstarter)

    Avatar von ThreeJay
    Registriert seit
    Mar 2001
    Ort
    Karlsruhe
    Beiträge
    398

    Standard

    So, nun werde ich wie versprochen auch noch einen zweiten ADT besprechen:
    Die Queue(Warteschlange).
    Ich gehe jetzt mal davon aus, dass ihr das Tut über Stacks gelesen UND verstanden habt, denn sonst wird das nix hier!

    Sie funktionieren ganz frei nach dem Motto: "Wer zuerst kommt, malt zuerst". Deswegen handelt es sich bei Queues um eine FIFO-Struktur(First In, First Out)(Zuerst rein, zuerst raus).

    Die Funktionen einer Queue bezeichnet man als enqueue(Element an Schlange anhängen) und als dequeue.

    enqueue:

    5
    |
    v
    4
    3
    2
    1

    dequeue:

    5
    4
    3
    2
    |
    v
    1


    Schauen wir uns die Definition der Klasse "Queue" an(queue.h):
    Code:
    #ifndef  __QUEUE_H
    #define __QUEUE_H
    
    class Queue
    {
    private:
          int *data;
          unsigned long anz;
          unsigned long maxanz;
          unsigned long inpos, outpos;
    
    public:
          explicit Queue(unsigned long);
          ~Queue(void);
    
          bool Enqueue(int);
          int Dequeue(void);
          bool isEmpty(void);
    };
    
    #endif /* __QUEUE_H */
    So, jetzt kommen wir an ein Problem: Die Queue wird als Ring betrachet also besteht die Gefahr, dass die Einfüge-Position die Entnahme Position einhohlt, was passiert, wenn die Queue mehr Elemente aufnehmen muss, als das verwendete Feld aufnehmen kann.Dadurch würden Elemente überschrieben. Um dies zu verhindern, darf die Queue keine Elemente mehr aufnehmen, wenn die Kapazität des Feldes erschöpft ist!

    Nun zur Implementierung der Methoden:
    Code:
    #include "queue.h"
    
    Queue:Queue(unsigned long s)
    {
        data=new(int[s]);
        if(data)
        {
              anz=inpos=outpos=0;
              maxanz=s;
         }
         else
         {
              anz=maxanz=inpos=outpos=0;
          }
    }
    
    Queue:~Queue(void)
    {
        if(data) delete[](data);
    }
    
    bool Queue:Enqueue(int w)
    {
        if(anz<maxanz)
        {
             anz++;
             data[inpos++]=w;
             if(inpos==maxanz) inpos=0;
             return(true);
         }
         else return(false);
    }
    
    int Queue::Dequeue(void)
    {
       if(anz>0)
       {
            unsigned long aktpos=outpos;
            if((++outpos)==maxanz)  outpos=0;
            anz--;
            return(data[aktpos]);
        }
        else return(0);
    }
    
    bool Queue:isEmpty(void)
    {
        return(anz==0);
    }
    Sodele, das wars!

    Jetzt seid ihr dran: Schreibt eine sinnvolle Main-Funktion die eine Queue nutzt! Wenn ihr das Tut über Stacks verstanden habt, dürfte das eigentlich kein Problem sein!
    "Unsauberes Programmieren wird zu einer Kunst! Man könnte es als den Impressionismus der Informatik bezeichnen!"
    ThreeJay threejay@ftp-world.org

  5. #5
    Multitalent

    (Threadstarter)

    Avatar von ThreeJay
    Registriert seit
    Mar 2001
    Ort
    Karlsruhe
    Beiträge
    398

    Standard von Kaervek:

    Als weitere kleine Verfeinerung wuerde ich empfehlen das jeder mal versuch Kopierkonstruktor und Zuweisungsoperator zu schreiben.
    Code:
    Queue& operator=(const Queue &a_oToCopy );
    und 
    Queue( const Queue &a_oToCopy )
    bzw
    Code:
    Stack& operator=(const Stack &a_oToCopy ); 
    und 
    Stack( const Stack&a_oToCopy )
    Diese beiden Methoden sind von essentieller Bedeutung, da es sonst sehr schnell zu Speicherproblemem kommt.

    Als Beispiel folgendes;
    Code:
    Stack oStack(10);
    Stack oStack2(20);
    
    oStack = oStack2;
    Der Speicher auf den data von oStack zeigt wird nun nicht freigegeben
    Nach der Zuweisung zeigen nun oStack2.data und oStack.data auf den selben Speicherbereich, sobald nun die Desktruktoren laufen wird, wird versucht dieser Bereich doppelt freizugeben.
    Zudem Manipuliert die Methoden push eines Objektesnun gleichzeitig den Speicher auf den das andere Objekt zeigt.
    "Unsauberes Programmieren wird zu einer Kunst! Man könnte es als den Impressionismus der Informatik bezeichnen!"
    ThreeJay threejay@ftp-world.org

  6. #6
    ex-Mod

    ex-Moderator


    Registriert seit
    Feb 2000
    Beiträge
    664

    Standard Anfänger TuT

    .Ich wollte hier ein C++ Anfänger TuT posten da ich selbst damit anfange und ewig gesucht hatte bis ich dies gefunden hatten ,doch da ein Post nur 1000 Zeichen enthalten kann und ich dann um die 80Posts machen müsste lass ich ,aber für jeden der es haben will ist das TuT hier als HTML-Dokument

    klick mich !!!!


    by fEdUp




    [edit]hab ich doch glatt den link vergessen

  7.  
     
     
Thema geschlossen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein