Die Funktionsweise der Musterlösung sollte aus den Kommentaren hervorgehen; im Anschluß findet sich ein kleines Testprogramm.
//=============================================-*-c++-*-============== // // Time-stamp: "20.06.03 14:15 vararr.h klaus@wachtler.de" // //==================================================================== // // Die Klasse stellt eine template-Klasse für variable Felder // zur Verfügung. // //==================================================================// // // // Copyright: // // ========== // // // // ///| |||\ || |||| // // // ////| |||\\ || |||| // // // ////|| ||||\\ || |||| // // // //// || |||| \\ || |||| // // // ////==|| |||| \\|| ||||// // // //// || |||| \\| |||// // // //// || |||| \| ||// // // |// // // |||| /| // // ///| ||||=====// ////======= // // |||| /|| // // ////| |||| // //// // // |||| //|| // // ////|| |||| // //// // // |||| // || // // //// || |||| // //// // // ||||// ||// // ////==|| ||||=(( ((((======= // // |||// |// // //// || |||| \\ \\\\ // // |||/ |/ // //// || |||| \\ \\\\======= // // // // (C) 1997-2003 AnyWare // // // // Klaus Wachtler // // Breidingstr. 17 // // 29614 Soltau // // // // Tel. (49)-5191-70 2 71 // // eMail AnyWare@Wachtler.de // // // //==================================================================// // // Geschichte: // =========== // // Datum: Name: Vers. Änderung: // // 16.05.1997 kw 0.1 erster Entwurf aus [1] intarr.cc entnommen // // 12.06.1997 kw Fehlerbehandlung auf CError umgestellt // // 03.07.1997 kw 0.1b freigegeben // // 15.07.1997 kw 0.1c freigegeben // // 17.07.1997 kw Fehlerkorrektur: // Die Übergabe eines VarArr-Feldes an eine // Funktion erzeugte ein neues (temporäres) // Objekt, dabei wird ein default-Konstruktor // (statt: // VarArr<T>( const VarArr<T> & init ) // ) aufgerufen. // Dieser allokiert das Feld (p) nicht neu, // sondern kopiert nur den Zeiger. // beim Verlassen der Funktion wird der // Destruktor aufgerufen, und das allokierte // Feld (p) freigegeben (obwohl das zugehörige // ursprüngliche Objekt noch existiert!) // Beim Freigeben des ursprünglichen Objekts // wird der zugehörige Destruktor nochmals // (p) freigeben. // Lösung: // VarArr<T>( const VarArr<T> & init ) // korrekt definiert. // // Bei dieser Gelegenheit auch gleich: // VarArr<T> & operator=( const VarArr<T> ) // definiert. // // 18.07.1997 kw - Erweiterung auf negative Indizes // - Allokieren grösserer Blöcke statt // Verlängerung auf gerade benötigte Länge. // // 19.07.1997 kw Fehlerbehandlung mit CError wieder entfernt: // ohne feste untere Grenze gibt es auch keine // Benutzerfehler mehr (Setzen von l(), ug(), // und og() auf unsinnige Werte erklärt das Feld // einfach für freigegeben). // // 22.07.1997 kw Speicherfehler geknackt (Bei Adreßberechnung // von p0_neu, wenn Elementgröße kein Vielfaches // von 4 ist) // // 23.07.1997 kw 0.1d freigegeben // // 24.07.1997 kw - Kommentare korrigiert // - copy-Konstruktor: Kopieren mit "=" ersetzt // durch entsprechenden Teil aus operator=(), // da sonst eine Endlosschleife beim Zuweisen // entsteht. // // 28.01.1998 kw 0.2 einige vorhandene Funktionen als "... const" // definiert, sowie die Funktion: // T & operator[]( int index ) const // neu definiert. // Damit kann man mit einem Feldzugriff (mit []) // auch in Objekte fassen, die als const ver- // einbart sind. Bei einer Feldgrenzenüber- // schreitung wird dann nicht das Feld // vergrößert, sondern eine Ausnahme ausgeworfen // (CErrorVarArrFeldgrenzenConst). // Damit ist jetzt auch ein #include "cerror.h" // nötig. // // 30.01.1998 kw Feldgrenzenüberprüfung korrigiert; // die Überprüfung kann mit // #define VARARR_FELDGRENZENKONTROLLIEREN // eingeschaltet werden. // // 08.05.1998 kw Bei DEBUG ist etwas mehr public // // 23.05.1998 kw 0.3 Neuerung: // Wenn VOR dem #includen von vararr.h // ein VARARR_NULL_POINTER #definiert // wird, dann wird beim Verlängern eines // Feldes kontrolliert, ob der Name des // zugrundeliegenden Typs der Feldelemente // auf "*" endet. // Wenn ja, scheint es ein Zeigertyp zu sein, // und der gesamte neu allokierte Speicher wird // mit Nullen vorbelegt. // // 01.03.1999 kw Einbau von Kleinst- und Größtwerten, // bei deren Überschreitung eine assert()- // Verletzung generiert wird (MINASSERT, // MAXASSERT) // // 14.03.1999 kw So eingestellt, daß im Normalfall keine // weiteren Dateien nötig sind // (also VARARR_NULL_POINTER und // VARARR_FELDGRENZENKONTROLLIEREN aus) // 20.10.1999 kw // 27.05.2000 kw Anpassungen an Linux/gcc // Fehler mit Defaultwerten eliminiert; // in dieser Form jetzt für Formelinterpreter // unter Linux geeignet // // 08.09.2002 kw Mechanismus mit MINASSERT/MAXASSERT entfernt // (war nur zum Debuggen interessant), // sowie die Nullpointer (mit // VARARR_NULL_POINTER, nicht portabel) // // 09.09.2002 kw in operator[] einen Schnelltest eingefügt, // ob resize() überhaupt aufgerufen werden muß // //==================================================================== // // Prinzipiell können damit alle Objekte in dynamischen Feldern // verwaltet werden. // Voraussetzungen: // - bei Basisdatentypen: keine // - bei Klassen: // 1) die Objekte müssen mit new und new[] allokierbar sein, // mit delete und delete[] wieder gelöscht werden können. // Also: Saubere Konstruktoren (ohne Parameter, und mit // const&-Initialisierung) und Destruktor // 2) die Objekte müssen mit operator= kopierbar sein. // Der Standardoperator = kopiert nur byteweise. Wenn die // Objekte also beispielsweise mit malloc() oder new beschafften // Speicher haben, dann braucht man auch einen sauberen // Kopieroperator = (der ggf. ein Objekt sauber aufsetzt). // //==================================================================== // // Verwendung: // =========== // // Der Anwender vereinbart ein Feld von beispielsweise int-Werten // etwa so: // #include <vararr.h> // VarArr<int> feld; // // Dann kann er auf beliebige Feldelemente zugreifen, z.B.: // feld[5] = 10; // feld[-1] = 3; // feld[1] = feld[5]+feld[-1]; // cout << feld[1]; // // Dabei wird die Länge des Feldes dynamisch so angepaßt, daß // die angesprochenen Werte auch existieren. // // // Abfrage der aktuellen Feldgröße: // // Mit den Elementfunktionen ug(), og(), und l() kann man die // untere bzw. obere maximale Feldgrenze und die aktuelle Länge // erfragen. // Bei einem frisch vereinbarten Feld liefert ug() den Wert INT_MAX, // og() den Wert INT_MIN, und l() den Wert 0. // // // Setzen der Feldgröße: // // Mit l( int lneu ) kann man die aktuelle Gesamtlänge setzen. // Ein positives lneu setzt dabei die untere Grenze auf 0, und die // obere Grenze auf lneu-1, so daß nachfolgende Aufrufe von l() wieder // og()-ug()+1 liefern. // Negative Werte oder 0 setzen das Feld zurück, sodaß die Länge // wieder 0 ist. // Dabei wird kein Speicher freigegeben, siehe [Funktionsweise]. // // Mit ug( int ugneu ) und og( int ogneu ) kann man die jeweilige // Feldgrenze von Hand setzen. Nachfolgende Aufrufe von l() liefern // dann eine entsprechend korrigierte Länge. // Ist ugneu größer als die bisherige obere Grenze, dann wird das Feld // als leer betrachtet (wie mit l(0)). // Ebenso, wenn ogneu kleiner ist als die bisherige untere Grenze. // Bei einem vorher leeren Feld (neu vereinbart, oder mit l(0) // geleert) setzt wird mit der oberen Grenze auch gleichzeitig die // untere gesetzt, und umgekehrt. Bei einem leeren Feld setzt also // og(10) auch gleichzeitig die Untergrenze auf 10. Das Element [10] // ist dann das einzige Element im Feld. ug() und og() liefern dann // den Wert 10, l() liefert 1. // // ug(int), og(int), und l(int) liefern jeweils den neuen Wert des // entsprechenden Parameters zurück (ihr Argument, oder 0 für l(int), // INT_MAX für ug(int), und INT_MIN für og(int)). // // // Unbenutzte Lücken im Feld: // // Es wird intern NICHT kontrolliert, welche Elemente belegt sind. // Nur die Grenzen werden mitgeführt und gegebenenfalls angepaßt. // Das heißt, daß Elemente, die nie belegt wurden, gelesen werden // können, aber ihr Inhalt ist undefiniert (es sei denn, es ist // für die Elemente ein Konstruktor definiert, der eine definierte // Vorbelegung vornimmt). // // Bei Bedarf kann man mit SetDefault() einen Wert für zukünftig // entstehende Lücken setzen; bis an diese Elemente etwas anderes // zugewiesen wird, haben sie dann wenigstens einen definierten Inhalt // (beispielsweise macht es Sinn, für ein Feld von Zeigern NULL als // Standardwert zu verwenden). // // Bereits bestehende Lücken werden von SetDefault() nicht // beeinflußt. // // // Debug-Ausgabe: // // Durch #definieren von DEBUGVARARR vor dem #includen von vararr.h // ereicht man eine ausführliche Ausgabe der internen Vorgänge. // //==================================================================== // // Funktionsweise: // =============== // // Es wird beim ersten Zugriff auf ein Element ein Stück Speicher // nur für dieses Feldelement allokiert. // Bei jedem weiteren Zugriff (lesend oder schreibend) wird // geprüft, ob der Zugriff in dem allokierten Bereich erfolgt, oder // außerhalb. Gegebenenfalls wird das Feld in die richtige Richtung // vergrößert. Dabei wird aber nicht nur bis zum aktuell benötigten // Element vergrößert, sondern um REALLOC_MEHR Elemente mehr. // Dadurch werden die Fälle, in denen man neu allokieren und alle // alten Elemente umkopieren muß, wesentlich seltener. // Nachteil ist natürlich, daß immer ein paar unbenutzte Elemente // in der Gegend herumliegen. // // Es gibt zwei Feldbereiche mit eigenen Ober- und Untergrenzen: // - Den Bereich, den der Benutzer sieht. Er reicht vom untersten // benutzten Element, bis zum höchsten benutzten Element. // Dieser Bereich wird von _ug, _og, und _l beschrieben. // - Der allokierte Bereich ist meist größer, und reicht von // _pallok bis (_pallok+_lallok-1). // // Es werden intern zwei Zeiger auf das allokierte Feld gepflegt, // nämlich _p0 und_pallok. // _p0 zeigt auf das Element [0] (auch wenn die untere Grenze // positiv ist, oder die obere Grenze negativ; dann ist _p0[0] nicht // gültig; _p0 selbst aber wohl). // _pallok zeigt auf das allokierte Feld. // // Anhand von _pallok==NULL wird erkannt, daß das Feld noch nicht // allokiert wurde. // // Bei jeder Feldvergrößerung wird nicht nur bis zur aktuell // geforderten Länge vergrößert, sondern gleich ein ordentliches Stück // mehr genommen. Aber wieviel? Die Verlängerung ist der größere // der beiden Werte (REALLOC_MIN1) und (REALLOC_MIN2). // Um einerseits kleine Felder effektiv verwenden zu können, sollte // REALLOC_MIN1 ein kleiner konstanter Wert sein, z.B. (5). // Um andererseits bei großen Feldern das aufwendige Neuallokieren // zu begrenzen, sollte REALLOC_MIN2 längenabhängig sein, also mit // der Feldlänge mitwachsen, z.B.: (l_allok/2). // Einzige Ausnahme: der erste Zugriff mit beliebigem Index läßt nur // das geforderte Element allokieren, da VarArr nicht wissen kann, ob // das Feld in Zukunft nach oben oder nach unten wachsen wird. // // Durch dieses großzügige Allokieren muß _pallok nun nicht identisch // sein mit _p0[ug()]. // Der Benutzer sieht Feldelemente von [_ug] bis [_og], der // allokierte Bereich reicht von _pallok bis (_pallok+_lallok-1). // Diese beiden Bereiche müssen nicht identisch sein, aber: // Der Benutzerbereich liegt immer innerhalb des allokierten Bereichs. // // Beispiel: // REALLOC_MIN1 sei 3, und REALLOC_MIN2 sei (_lallok/2). // // Mit der Definition: // VarArr<int> feld; // wird ein neues Feld geschaffen. // feld sieht dann so aus: // // _l == 0 // _lallok == 0 // _og == INT_MIN // _ug == INT_MAX // _p0 == NULL // _pallok == NULL // // Durch die Zuweisung: // feld[0] = 12; // wird nur Platz für feld[0] allokiert. _p0 und _pallok zeigen auf // diesen Wert. // // feld hat dann beispielsweise | Der Speicher könnte so aussehen: // folgenden Inhalt: | // // _l == 1 // _lallok == 1 | | // 5004 +-------------------------+ // _og == 0 | (int)feld[0] | // _ug == 0 | == | // | (int)12 | // _p0 == 5000 -----+ | | // _pallok == 5000 -----+------> 5000 +-------------------------+ // | | // Durch den Zugriff: | | // feld[-2] = 13; // passiert folgendes: // - das Feld muß nach unten verlängert werden. Da REALLOC_MIN1 aber // 3 ist, müssen über das ohnehin nötige feld[-2] hinaus noch zwei // Elemente mehr allokiert werden (insgesamt also [-4] bis [-1]). // Es wird also ein Feld mit fünf Elementen allokiert, und das // alte Feld freigegeben (nach dem Kopieren des einzigen Elements) // - _pallok zeigt auf das niedrigste allokierte Element feld[-4] // - _p0 zeigt auf die neue Position von feld[0] // - _ug wird auf -2 gesetzt // - _l wird auf 3 gesetzt (weil für den Benutzer die Feldlänge // immer _og-_ug+1 ist) // - _lallok wird auf 5 gesetzt, da das Feld von einem auf // fünf Elemente verlängert wird. // // Die Situation könnte dann so aussehen: // // | | // 6016 +-------------------------+ // | (int)feld[0] | // | == | // | (int)12 | // | | // +---------> 6012 +-------------------------+ // | | (int)feld[-1] | // | | == | // | | ?? | // | | | // | 6008 +-------------------------+ // | | (int)feld[-2] | // | | == | // | | (int)13 | // | | | // | 6004 +-------------------------+ // | | (int)feld[-3] | // | | == | // _l == 3 | | ?? | // _lallok == 4 | | | // | 6000 +-------------------------+ // _og == 0 | | (int)feld[-4] | // _ug == -2 | | == | // | | ?? | // _p0 == 6012 --+ | | // _pallok == 5996 ------------> 5996 +-------------------------+ // // Aus Effizienzgründen wird allokierter Speicher für das eigentliche // Feld nie mehr freigegeben, // bis der Destruktor des gesamten Feldes aufgerufen wird. // Also wird auch beim Aufruf von l(0) nichts freigegeben, sondern es // werden nur _l, _ug, und _og gesetzt. _p0 bleibt auf das bisherige // Element [0] gerichtet. // // Ausnahme: wenn das Feld für leer erklärt wird (z.B. durch l(0)), // und danach als erstes auf ein Element außerhalb des allokierten // Bereichs zugegriffen wird, dann wird der gesamte allokierte Bereich // freigegeben und nur Platz für das aktuell nötige Element allokiert. // // Wenn _pallok!=NULL, dann gilt: // - ( _pallok ) zeigt auf das unterste allok. Element // - ( _pallok + _lallok ) zeigt HINTER das allokierte Feld // - ( _pallok + _lallok - 1 ) zeigt auf das letzte allokierte Element // - ( _p0 ) zeigt auf das Element [0] // - ( _p0 + _ug ) zeigt auf das unterste Benutzerelement // - ( _p0 + _og ) zeigt auf das oberste Benutzerelement // // Dazu sind private member-Funktionen definiert: // - _p_letzt() liefert Zeiger auf das letzte allokierte Element // - _p_ug() liefert Zeiger auf das unterste Benutzerelement // - _p_og() liefert Zeiger auf das oberste Benutzerelement // Achtung: // Diese darf man nur aufrufen, wenn _pallok!=NULL ist!!! // //==================================================================== // // Benötigt: // ========= // // --- // //==================================================================== // // Umgebung: // ========= // // getestet mit: // // + gcc/Linux // + Visual C++ 4.2 - 6.0, Windows NT 4.0/SP3 // //==================================================================== // // Literatur: // ========== // // [1] Klaus Wachtler: C++ // // [2] Gedruckte Dokumentation duh.ps, und // Online-Hilfe duh.hlp // //====================================================================
#ifndef _VARARR
#define _VARARR
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <climits>
// Mit VARARR_FELDGRENZENKONTROLLIEREN kann man eine
// Feldgrenzenüberprüfung einschalten.
// Diese wird in
// T & operator[]( int index ) const
// wirksam, also beim Zugriff auf ein Element eines const-Objekts.
// In diesem Fall darf das Feld nicht implizit verlängert werden,
// weil ja "const". Eine Referenz auf ein Element darf dann
// auch nicht geliefert werden, also wird eine exception geworfen.
// Wenn VARARR_FELDGRENZENKONTROLLIEREN nicht definiert ist,
// dann findet keine Kontrolle statt. Vielmehr wird dann eine
// unbrauchbare Referenz auf irgendwas im Speicher geliefert.
// Viel Spaß damit!
// Seit 08.09.2002 wird in diesem Fall eine Ausnahme vom Typ
// std::out_of_range geworfen (vorher war es eine Ausnahme von einem
// eigenen Typ CError).
#define VARARR_FELDGRENZENKONTROLLIEREN
#define nDEBUGVARARR
#ifdef VARARR_FELDGRENZENKONTROLLIEREN
#include <stdexcept>
#endif /* ifdef VARARR_FELDGRENZENKONTROLLIEREN */
#ifndef REALLOC_MIN1
#define REALLOC_MIN1 (5)
#endif /* ndef REALLOC_MIN1 */
#ifndef REALLOC_MIN2
#define REALLOC_MIN2 (_lallok/2)
#endif /* ndef REALLOC_MIN2 */
// Es werden REALLOC_MEHR Elemente allokiert, als mindestens nötig.
// Mindestens nötig wäre immer 1 (für das aktuelle).
#ifndef REALLOC_MEHR
#define REALLOC_MEHR \
( ( REALLOC_MIN1>REALLOC_MIN2 ? REALLOC_MIN1 : REALLOC_MIN2 ) - 1 )
#endif /* ndef REALLOC_MEHR */
#ifndef assert
#include <assert.h>
#endif /* ifndef assert */
template<class T> class VarArr
{
private:
int _l; // momentane Länge (für Benutzer)
int _lallok; // allokierte Länge
int _ug; // untere Feldgrenze (für Benutzer)
int _og; // obere Feldgrenze (für Benutzer)
T * _p0; // Zeiger auf Element [0] (auch dann, wenn
// [0] nicht im Feld enthalten ist)
T * _pallok; // Zeiger auf das eigentliche Feld
T * _default; // NULL oder Zeiger auf default-Wert
// Vorbelegung für leeres Feld;
void reset()
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::reset()" << endl;
#endif /* DEBUGVARARR */
_l
= _lallok
= 0;
_og = INT_MIN;
_ug = INT_MAX;
_p0
= _pallok
= _default
= NULL;
}
// gibt ein belegtes Feld frei (es wird nicht tatsächlich
// freigegeben; vielmehr werden _ug, _og und _l gesetzt):
void loesche()
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::loesche()" << endl;
#endif /* DEBUGVARARR */
if( _default && _lallok>0 )
{
for( int i=0; i<_lallok; i++ )
{
_pallok[i] = *_default;
}
}
else
{
}
_l = 0;
_og = INT_MIN;
_ug = INT_MAX;
// Testweise geben wir alles frei:
delete[] _pallok; _pallok = NULL; // Speicher freigeben
reset(); // Initialisieren
//.
}
// liefert Zeiger auf das letzte allokierte Element
// (_pallok muß !=NULL sein!!!!!!!)
T *_p_letzt() const
{
#ifdef DEBUGVARARR
cerr << "[-] T * VarArr<T>::_p_letzt()" << endl;
#endif /* DEBUGVARARR */
assert( _pallok!=NULL );
return ( _pallok + _lallok - 1 );
}
// liefert Zeiger auf das unterste Benutzerelement
// (_pallok muß !=NULL sein!!!!!!!)
T *_p_ug() const
{
#ifdef DEBUGVARARR
cerr << "[-] T * VarArr<T>::_p_ug()" << endl;
#endif /* DEBUGVARARR */
assert( _pallok!=NULL );
return ( _p0 + _ug );
}
// liefert Zeiger auf das oberste Benutzerelement
// (_pallok muß !=NULL sein!!!!!!!)
T *_p_og() const
{
#ifdef DEBUGVARARR
cerr << "[-] T * VarArr<T>::_p_og()" << endl;
#endif /* DEBUGVARARR */
assert( _pallok!=NULL );
return ( _p0 + _og );
}
// sorgt dafür, daß das Element [index_neu] vorhanden ist.
void resize( int index_neu )
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::resize( int index_neu = "
<< index_neu << ")" << endl;
cerr << "Default "
<< ( _default==NULL ? "nicht " : "" )
<< "gesetzt!\n";
#endif /* DEBUGVARARR */
if( !_pallok )
{
// noch gar kein Speicher allokiert!
// Aber das kann man nachholen:
#ifdef DEBUGVARARR
cerr << "[-] - bisher noch kein Speicher allokiert!" << endl;
#endif /* DEBUGVARARR */
_l = 1;
_lallok = 1;
_pallok = new T[1];
if( _default )
{
*_pallok = *_default;
}
else
{
}
_p0 = _pallok - index_neu;
_ug
= _og
= index_neu;
return;
}
else
{ // Feld ist schon allokiert.
#ifdef DEBUGVARARR
cerr << "[-] - Feld ist schon allokiert." << endl;
#endif /* DEBUGVARARR */
if( _l<=0 )
{ // Feld ist zwar allokiert, aber wieder freigegeben.
// Wenn der verlangte Wert feld[index_neu] im allokierten
// Bereich liegt, dann wird dieses eine Element als neues
// _ug und _og betrachtet; die Länge ist dann 1.
#ifdef DEBUGVARARR
cerr << "[-] - _l<=0, also Feld freigegeben." << endl;
#endif /* DEBUGVARARR */
if( ( _p0 + index_neu ) >= _pallok
&& ( _p0 + index_neu ) <= _p_letzt()
)
{ // feld[index_neu] liegt im bereits vorhandenen Feld.
#ifdef DEBUGVARARR
cerr << "[-] - index_neu liegt im allokierten "
<< "Bereich." << endl;
#endif /* DEBUGVARARR */
_l = 1;
_ug
= _og
= index_neu;
if( _default )
{
for( int i=0; i<_lallok; i++ )
{
_pallok[i] = *_default;
}
}
else
{
}
return ;
} // feld[index_neu] liegt im bereits vorhandenen Feld.
else
{
// Feld ist allokiert, aber wieder freigegeben,
// und das erste neue Element [index_neu] liegt
// außerhalb des allokierten Bereichs.
// Dann das Feld freigeben, und neu anfangen:
#ifdef DEBUGVARARR
cerr << "[-] - index_neu liegt ausse"
<< "rhalb des allokierten Bereichs.\n"
<< "[-] Also delete[], und neu "
<< "allokieren!" << endl;
#endif /* DEBUGVARARR */
delete[] _pallok; _pallok = NULL;
reset(); // Initialisieren
resize( index_neu ); // (rekursiv) allokieren
return;
}
} // Feld ist zwar allokiert, aber wieder freigegeben.
// Kontrollieren, ob die Grenzen zur Länge passen:
assert( (_l==( _og - _ug + 1 )) );
// Hier ist ein Feld vorhanden, und die Länge ist
// mindestens 1.
// reicht die allokierte Länge?
if( &_p0[index_neu] > _p_letzt() )
{ // Feld muß nach oben verlängert werden!
#ifdef DEBUGVARARR
cerr << "[-] - Feld muss nach oben verlaengert werden!"
<< endl;
#endif /* DEBUGVARARR */
// neue Länge:
int lallok_neu
// Adresse des letzten allokierten Elements, wenn
// das Feld an der gleichen Stelle bleiben würde:
= ( &_p0[index_neu + REALLOC_MEHR]
// minus Basisadresse ergibt (neue Länge-1)
- _pallok
// plus 1 ergibt neue Länge (Zaunlatten!)
+ 1
);
#ifdef DEBUGVARARR
cerr << "[-] - REALLOC_MIN1 = " << REALLOC_MIN1;
cerr << ", REALLOC_MIN2 = " << REALLOC_MIN2 << endl;
cerr << "[-] REALLOC_MEHR = " << REALLOC_MEHR
<< ", lallok_neu = " << lallok_neu << endl;
#endif /* DEBUGVARARR */
assert( lallok_neu>_lallok );
// neues Feld:
T * pallok_neu = new T[lallok_neu];
if( _default )
{
for( int i=_lallok; i<lallok_neu; i++ )
{
pallok_neu[i] = *_default;
}
}
else
{
}
// Zeiger auf neues Element [0]:
// Das neue Element [0] soll im neuen Feld den gleichen
// Offset haben, wie das alte [0] im alten Feld.
// Das heißt: ( _p0 - _pallok ) == ( p0_neu - pallok_neu )
// Oder: p0_neu == ( _p0 - _pallok + pallok_neu )
// Auch hier muß man sicherstellen, daß nur Differenzen
// von Zeigern gebildet werden, die sich auf EINEN
// allokierten Speicherblock beziehen (siehe unten):
// T * p0_neu = ( _p0 - _pallok + pallok_neu );
T * p0_neu = &pallok_neu[(_p0 - _pallok)];
// Änderung 27.05.2000 kw:
// dies war der alte Code:
// // Damit kann man die Elemente kopieren:
// for( int i=_ug; i<=_og; i++ )
// {
//#ifdef DEBUGVARARR
// cerr << "[-] - Kopiere Element " << i << endl;
//#endif /* DEBUGVARARR */
// p0_neu[i] = _p0[i];
// }
// Fehler darin ist, daß nur die Elemente des Bereichs
// umkopiert werden, die der Benutzer sieht
// ((_ug)...(_og)).
// Wenn jedoch (_default) gesetzt ist, dann muß man
// alle bisher allokierten Elemente umkopieren, weil
// sonst die Elemente, die durch Mehrallokieren
// außerhalb von (_ug)...(_og) liegen, verlorengehen.
// Diese "Vorratselemente" müssen ja auch mit in das
// neu allokierte Feld übernommen werden.
// Weil dies aber wiederum überflüssig ist, wenn
// (_default) nicht gesetzt ist, gibt es hier eine
// Fallunterscheidung:
if( _default )
{ // _default, also alle allokierten Elemente kopieren
for( int i=0; i<_lallok; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere all. Element " << i << endl;
#endif /* DEBUGVARARR */
pallok_neu[i] = _pallok[i];
}
} // _default, also alle allokierten Elemente kopieren
else
{ // KEIN _default, also nur von _ug bis _og kopieren
for( int i=_ug; i<=_og; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere Element " << i << endl;
#endif /* DEBUGVARARR */
p0_neu[i] = _p0[i];
}
} // KEIN _default, also nur von _ug bis _og kopieren
// Das alte Feld kann weg:
delete[] _pallok;
_pallok = pallok_neu;
_og = index_neu;
_p0 = p0_neu;
_lallok = lallok_neu;
_l = _og - _ug + 1;
#ifdef DEBUGVARARR
cerr << "[-] - _ug = " << _ug << endl;
cerr << "[-] _og = " << _og << endl;
cerr << "[-] _l = " << _l << endl;
#endif /* DEBUGVARARR */
return;
} // Feld muß nach oben verlängert werden!
else if( &_p0[index_neu] < _pallok )
{ // Feld muß nach unten verlängert werden!
#ifdef DEBUGVARARR
cerr << "[-] - Feld muss nach unten verlaengert werden!" << endl;
#endif /* DEBUGVARARR */
// neue Länge:
int lallok_neu =
// erste Adresse hinter dem allokierten Feld:
&_pallok[_lallok]
// minus Adresse des ersten allokierten Elements, wenn
// das Feld nach unten verlängert werden könnte:
- &_p0[index_neu-REALLOC_MEHR];
#ifdef DEBUGVARARR
cerr << "[-] - REALLOC_MIN1 = " << REALLOC_MIN1;
cerr << ", REALLOC_MIN2 = " << REALLOC_MIN2 << endl;
cerr << "[-] REALLOC_MEHR = " << REALLOC_MEHR
<< ", lallok_neu = " << lallok_neu << endl;
#endif /* DEBUGVARARR */
assert( lallok_neu>_lallok );
// neues Feld:
T * pallok_neu = new T[lallok_neu];
if( _default )
{
for( int i=0; i<(lallok_neu-_lallok); i++ )
{
pallok_neu[i] = *_default;
}
}
else
{
}
// Zeiger auf neues Element [0]:
// Das neue Element [0] soll im neuen Feld den gleichen
// Abstand vom Ende haben, wie das alte [0] im alten Feld.
// Das heißt:
// ( _pallok + _lallok - _p0 )
// ==
// ( pallok_neu + lallok_neu - p0_neu )
// Oder:
// p0_neu
// ==
// ( pallok_neu+lallok_neu-( _pallok+_lallok-_p0 ) )
// Oder:
// p0_neu
// ==
// ( pallok_neu+lallok_neu+( -_pallok-_lallok+_p0 ) )
// Oder:
// p0_neu
// ==
// ( pallok_neu+lallok_neu-_pallok-_lallok+_p0 )
//
//T * p0_neu =
// (&pallok_neu[lallok_neu]
// - &_pallok[_lallok]
// )
// + _p0;
// Diese Version geht schief (bei sizeof(T)!=n*8 kommen
// Adressen heraus, die nicht Vielfache von sizeof(T)
// sind!!!!!!!!!!!!!!!!!!!!!!!
//T * p0_neu = pallok_neu+lallok_neu-_pallok-_lallok+_p0;
// Vielmehr muß man den Ausdruck so zusammenfassen, daß
// nur Differenzen von Adressen gebildet werden, die in
// einem allokierten Block liegen.
// Haben die allokierten Blöcke nämlich einen Abstand, der
// ungleich einem Vielfachen der Elementgröße ist, dann
// kommen Rundungsfehler ins Spiel (weil die Differenzen
// von Adressen immer durch sizeof(...) geteilt werden.
T * p0_neu =
&pallok_neu[lallok_neu] - ( &_pallok[_lallok] - _p0 );
#ifdef DEBUGVARARR
cerr << "[-] - p0_neu, pallok_neu = "
<< p0_neu << ", " << pallok_neu << endl;
#endif /* DEBUGVARARR */
// Änderung 27.05.2000 kw:
// dies war der alte Code:
// // Damit kann man die Elemente kopieren:
// for( int i=_ug; i<=_og; i++ )
// {
//
//#ifdef DEBUGVARARR
// cerr << "[-] - Kopiere Element " << i << endl;
//#endif /* DEBUGVARARR */
//
// p0_neu[i] = _p0[i];
// }
// Fehler darin ist, daß nur die Elemente des Bereichs
// umkopiert werden, die der Benutzer sieht
// ((_ug)...(_og)).
// Wenn jedoch (_default) gesetzt ist, dann muß man
// alle bisher allokierten Elemente umkopieren, weil
// sonst die Elemente, die durch Mehrallokieren
// außerhalb von (_ug)...(_og) liegen, verlorengehen.
// Diese "Vorratselemente" müssen ja auch mit in das
// neu allokierte Feld übernommen werden.
// Weil dies aber wiederum überflüssig ist, wenn
// (_default) nicht gesetzt ist, gibt es hier eine
// Fallunterscheidung:
if( _default )
{ // _default, also alle allokierten Elemente kopieren
for( int i=0; i<_lallok; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere all. Element " << i << endl;
#endif /* DEBUGVARARR */
pallok_neu[i+lallok_neu-_lallok] = _pallok[i];
}
} // _default, also alle allokierten Elemente kopieren
else
{ // KEIN _default, also nur von _ug bis _og kopieren
for( int i=_ug; i<=_og; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere Element " << i << endl;
#endif /* DEBUGVARARR */
p0_neu[i] = _p0[i];
}
} // KEIN _default, also nur von _ug bis _og kopieren
// Das alte Feld kann weg:
delete[] _pallok;
_pallok = pallok_neu;
_ug = index_neu;
_p0 = p0_neu;
_lallok = lallok_neu;
_l = _og - _ug + 1;
#ifdef DEBUGVARARR
cerr << "[-] - _ug = " << _ug << endl;
cerr << "[-] _og = " << _og << endl;
cerr << "[-] _l = " << _l << endl;
#endif /* DEBUGVARARR */
return;
} // Feld muß nach unten verlängert werden!
else
{ // Feld ist vorhanden, Länge ist größer 0,
// keine Verlängerung nötig, &Element[index_neu] liegt im
// Bereich [_pallok,_p_letzt()]
// Dann muß man nur sehen, ob index_neu im Bereich
// _ug..._og liegt; gegebenenfalls _ug oder _og anpassen:
#ifdef DEBUGVARARR
cerr << "[-] - Feld ist vorhanden, " << endl;
cerr << "[-] Laenge ist groesser 0," << endl;
cerr << "[-] keine Verlaengerung noetig." << endl;
cerr << "[-] " << endl;
#endif /* DEBUGVARARR */
if( index_neu<_ug )
{
_ug = index_neu;
_l = _og - _ug + 1;
#ifdef DEBUGVARARR
cerr << "[-] - _ug auf " << _ug
<< " gesetzt." << endl;
cerr << "[-] _l auf " << _l
<< " gesetzt." << endl;
#endif /* DEBUGVARARR */
}
if( index_neu>_og )
{
_og = index_neu;
_l = _og - _ug + 1;
#ifdef DEBUGVARARR
cerr << "[-] - _og auf " << _og
<< " gesetzt." << endl;
cerr << "[-] _l auf " << _l
<< " gesetzt." << endl;
#endif /* DEBUGVARARR */
}
return;
} // Feld ist vorhanden, Länge ist größer 0,
} // Feld ist schon allokiert.
} // void resize( int index_neu )
public:
// Konstruktor:
VarArr()
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>:: VarArr()" << endl;
#endif /* DEBUGVARARR */
_default = (T *)NULL;
reset();
}
// 17. 7.97 kw: der fehlte noch, um temporäre Objekte schaffen zu
// können:
// Initialisieren mit Referenz auf const-Objekt:
VarArr<T>( const VarArr<T> & init )
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::VarArr<T>( const VarArr<T> & init )"
<< endl;
#endif /* DEBUGVARARR */
// alles löschen:
reset();
if( init._default )
{
_default = new T( *(init._default) );
}
// Initialisierer zuweisen:
// 24. 7.97 kw:
// mit operator= zuweisen klappt, nicht, weil zu dessen Aufruf
// ein temporäres Objekt mit VarArr<T>( const VarArr<T> & init )
// angelegt, wird; darin wird wieder operator= aufgerufen, dazu
// wird...
// Klassische Rekursion; man lernt nie aus.
// hat das Feld einen Inhalt?
if( init._pallok && init._l>0 )
{
// JA!
#ifdef DEBUGVARARR
cerr << "[-] - Feld hat Inhalt." << endl;
#endif /* DEBUGVARARR */
// Kontrollieren, ob die Grenzen zur Länge passen:
assert( (init._l==( init._og - init._ug + 1 )) );
// Diese Werte kann man direkt übernehmen:
_l = init._l;
_ug = init._ug;
_og = init._og;
// Platz allokieren (nur soviel, wie nötig):
_lallok = _l;
_pallok = new T[_lallok];
// _p0 auf Element [0] zeigen lassen:
_p0 = _pallok - _ug;
// Die belegten Elemente von der rechten Seite nach links
// kopieren;
for( int i=_ug; i<=_og; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere Element " << i << endl;
#endif /* DEBUGVARARR */
_p0[i] = (init._p0)[i];
}
}
}
// Der Operator [] wird mit einer Funktion überladen, die
// a) ein Unter- oder Überschreiten des Feldes durch Verlängern
// erlaubt,
// b) eine Referenz auf das (index)-te Objekt liefert.
T & operator[]( int index )
{
#ifdef DEBUGVARARR
cerr << "[-] T & VarArr<T>::operator[]( int index = "
<< index << " )" << endl;
#endif /* DEBUGVARARR */
// 09.09.2002 kw: hier ein Schnelltest, ob resize() überhaupt
// nötig ist. Diese Tests werden zwar in resize()
// nochmals gemacht, aber in den meisten Fällen
// wird die Funktion gar nicht aufgerufen werden
// müssen (solange die meisten Zugriffe im
// existierenden Feld stattfinden).
// Damit sollte es so im Schnitt etwas schneller
// gehen:
if( index>_og || index<_ug || !_pallok )
{
resize( index ); // Ggf. das Feld verlängern lassen
}
return _p0[index]; // Referenz auf (index)-tes Objekt liefern
}
// Der Operator [] wird mit einer Funktion überladen, die
// a) ein Unter- oder Überschreiten des Feldes überwacht,
// und notfalls eine exception schmeißt,
// b) eine Referenz auf das (index)-te Objekt liefert.
// Im Gegensatz zum "normalen" Feldzugriff
// T & operator[]( int index ) wird das Feld nicht manipuliert!
// Deshalb ist diese Version auch für const-Objekte geeignet (und
// eventuell schneller).
T & operator[]( int index ) const
{
#ifdef DEBUGVARARR
cerr << "[-] T & VarArr<T>::operator[]( int index = "
<< index << " ) const" << endl;
#endif /* DEBUGVARARR */
#ifdef VARARR_FELDGRENZENKONTROLLIEREN
if( index < _ug || index > _og )
{
throw( std::out_of_range( "VarArr<>::operator[](int index) const" ) );
}
#endif // VARARR_FELDGRENZENKONTROLLIEREN
return _p0[index]; // Referenz auf (index)-tes Objekt liefern
}
// Die Funktionen l(), ug(), und og() liefern die Anzahl der
// belegten Elemente, den kleinsten belegten Index, und den
// größten belegten Index.
long int l() const
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::l()" << endl;
#endif /* DEBUGVARARR */
return _l;
}
long int ug() const
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::ug()" << endl;
#endif /* DEBUGVARARR */
return _ug;
}
// Destruktor:
~VarArr()
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::~ VarArr()" << endl;
#endif /* DEBUGVARARR */
if( _pallok )
{
delete[] _pallok; _pallok = NULL;
}
reset();
if( _default )
{
delete _default;
}
}
// 17. 7.97 kw
// Zuweisung
VarArr<T> & operator=( const VarArr<T> init )
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::operator=( const VarArr<T> init )"
<< endl;
#endif /* DEBUGVARARR */
// Ggf. altes Feld freigeben:
if( _pallok )
{
delete[] _pallok; _pallok = NULL;
}
reset();
// hat das Feld einen Inhalt?
if( init._pallok && init._l>0 )
{
// JA!
#ifdef DEBUGVARARR
cerr << "[-] - Feld hat Inhalt." << endl;
#endif /* DEBUGVARARR */
// Kontrollieren, ob die Grenzen zur Länge passen:
assert( (init._l==( init._og - init._ug + 1 )) );
// Diese Werte kann man direkt übernehmen:
_l = init._l;
_ug = init._ug;
_og = init._og;
// Platz allokieren (nur soviel, wie nötig):
_lallok = _l;
_pallok = new T[_lallok];
// _p0 auf Element [0] zeigen lassen:
_p0 = _pallok - _ug;
// Die belegten Elemente von der rechten Seite nach links
// kopieren;
for( int i=_ug; i<=_og; i++ )
{
#ifdef DEBUGVARARR
cerr << "[-] - Kopiere Element " << i << endl;
#endif /* DEBUGVARARR */
_p0[i] = (init._p0)[i];
}
}
return *this;
}
long int og() const
{
#ifdef DEBUGVARARR
cerr << "[-] VarArr<T>::og()" << endl;
#endif /* DEBUGVARARR */
return _og;
}
// Mit einem Parameter setzen die Funktionen l(int), ug(int),
// und og(int) die Anzahl der
// belegten Elemente (ab 0), resp. den kleinsten belegten Index,
// resp. den größten belegten Index.
long int l( int lneu )
{
#ifdef DEBUGVARARR
cerr << "[-] long int VarArr<T>::l( int lneu = "
<< lneu << " )" << endl;
#endif /* DEBUGVARARR */
if( lneu>0 )
{
// neuer Bereich ist [0,(lneu-1)].
// Dazu muß (lneu-1) existieren:
#ifdef DEBUGVARARR
cerr << "[-] - lneu>0" << endl;
#endif /* DEBUGVARARR */
resize( lneu - 1 );
resize( 0 );
_ug = 0;
_og = lneu - 1;
_l = lneu;
}
else
{
#ifdef DEBUGVARARR
cerr << "[-] - lneu<=0" << endl;
#endif /* DEBUGVARARR */
// Feld soll gelöscht werden.
loesche();
}
return _l;
}
long int ug( int ugneu )
{
#ifdef DEBUGVARARR
cerr << "[-] long int VarArr<T>::ug( int ugneu = "
<< ugneu << " )" << endl;
#endif /* DEBUGVARARR */
if( _l<=0 )
{
#ifdef DEBUGVARARR
cerr << "[-] - Feld ist leer, neu anlegen: [ugneu]"
<< endl;
#endif /* DEBUGVARARR */
// Feld ist bisher leer!
// Dann das Element [ugneu] schaffen:
resize( ugneu );
_og = _ug = ugneu;
_l = 1;
}
else if( ugneu>_og )
{
#ifdef DEBUGVARARR
cerr << "[-] - ugneu>_og, Feld soll freigegeben werden"
<< endl;
#endif /* DEBUGVARARR */
// Feld soll freigegeben werden:
loesche();
}
else
{
#ifdef DEBUGVARARR
cerr << "[-] - ugneu<=_og, F"
<< "eld soll am unteren Ende angepaßt werden"
<< endl;
#endif /* DEBUGVARARR */
// Feld soll am unteren Ende angepaßt werden:
resize( ugneu );
_ug = ugneu;
_l = _og - _ug + 1;
}
#ifdef DEBUGVARARR
cerr << "[-] - _ug ist jetzt " << _ug << endl;
cerr << "[-] - _og ist jetzt " << _og << endl;
cerr << "[-] _l ist jetzt " << _l << endl;
#endif /* DEBUGVARARR */
return _ug;
}
long int og( int ogneu )
{
#ifdef DEBUGVARARR
cerr << "[-] long int VarArr<T>::og( int ogneu = "
<< ogneu << " )" << endl;
#endif /* DEBUGVARARR */
if( _l<=0 )
{
#ifdef DEBUGVARARR
cerr << "[-] - Feld ist leer, neu anlegen: [ogneu]"
<< endl;
#endif /* DEBUGVARARR */
// Feld ist bisher leer!
// Dann das Element [ogneu] schaffen:
resize( ogneu );
_og = _ug = ogneu;
_l = 1;
}
else if( ogneu<_ug )
{
#ifdef DEBUGVARARR
cerr << "[-] - ogneu<_ug, Feld soll freigegeben werden"
<< endl;
#endif /* DEBUGVARARR */
// Feld soll freigegeben werden:
loesche();
}
else
{
#ifdef DEBUGVARARR
cerr << "[-] - ogneu>=_ug, Feld soll am oberen"
<< " Ende angepasst werden" << endl;
#endif /* DEBUGVARARR */
// Feld soll am oberen Ende angepaßt werden:
resize( ogneu );
_og = ogneu;
_l = _og - _ug + 1;
}
#ifdef DEBUGVARARR
cerr << "[-] - _ug ist jetzt " << _ug << endl;
cerr << "[-] - _og ist jetzt " << _og << endl;
cerr << "[-] _l ist jetzt " << _l << endl;
#endif /* DEBUGVARARR */
return _og;
}
// setzt den Defaultwert, mit dem neue Feldbereiche gefüllt
// werden sollen
void
SetDefault( const T &Default )
{
if( _default )
{
delete _default;
}
_default = new T( Default );
}
void
SetNoDefault()
{
if( _default )
{
delete _default;
_default = (T*)NULL;
}
}
}; // { Ende von template<class T> class VarArr }
// vergleicht zwei VarArr<T> nach Länge und Inhalt.
// true, wenn beide gleich sind.
// Benötigt für die Klasse T den operator==().
template <class T> static bool
CmpVarArr( VarArr<T> &a1, VarArr<T> &a2 )
{
if( a1.ug()!=a2.ug() || a1.og()!=a2.og() )
{
return false;
}
else
{
for( int i=a1.ug(); i<=a1.og(); i++ )
{
if( !(a1[i]==a2[i]) )
{
return false;
}
}
}
return true;
}
#endif /* _VARARR */
Hier das zugehörige Testprogramm:
// Time-stamp: "09.09.02 19:43 test.cpp klaus@wachtler.de"
//
// Kleines Demoprogramm für VarArr
#define NDEBUG // um diverse assert() in vararr.h stillzulegen
#include "vararr.h" // oder <vararr.h>
#include <iostream>
#include <string>
/////////////////////////////////////////////////////////////////////////////
// demonstriert ein einfaches Feld:
void demo_simple_int()
{
// Feld vereinbaren:
VarArr<int> feld;
// Beliebige Elemente initialisieren und verwenden:
feld[1] = 11;
feld[0] = 10;
feld[-5] = 15;
std::cout << "\ndemo_simple_int:" << std::endl;
std::cout << "feld[-5] = " << feld[-5] <<std::endl;
std::cout << "feld[0] = " << feld[0] <<std::endl;
std::cout << "feld[1] = " << feld[1] << std::endl;
}
/////////////////////////////////////////////////////////////////////////////
// demonstriert ein einfaches Feld:
void demo_simple_double()
{
// Feld vereinbaren:
VarArr<double> feld;
// Beliebige Elemente initialisieren und verwenden:
feld[1] = 11.11;
feld[0] = 10.00;
feld[-5] = 15.55;
std::cout << "\ndemo_simple_double:" << std::endl;
std::cout << "feld[-5] = " << feld[-5] <<std::endl;
std::cout << "feld[0] = " << feld[0] <<std::endl;
std::cout << "feld[1] = " << feld[1] << std::endl;
}
/////////////////////////////////////////////////////////////////////////////
// demonstriert ein einfaches Feld mit einem default-Wert für nicht
// definierte Elemente:
void demo_default_int()
{
// Feld vereinbaren:
VarArr<int> feld;
// einen Wert als Standardwert setzen:
feld.SetDefault( 12 );
// Beliebige Elemente initialisieren und verwenden:
feld[1] = 11;
feld[0] = 10;
feld[-5] = 15;
std::cout << "\ndemo_default_int:" << std::endl;
// Alle Elemente vom kleinsten benutzten bis zum höchsten benutzten
// ausgeben (obwohl nicht alle explizit initialisiert wurden!):
for( int i=feld.ug(); i<=feld.og(); i++ )
{
std::cout << "feld[" << i << "] = " << feld[i] <<std::endl;
}
}
/////////////////////////////////////////////////////////////////////////////
// demonstriert ein Feld einer eigenen Klasse:
class Student
{
friend std::ostream & operator<<( std::ostream &os, const Student &obj );
public:
Student( std::string name = "", int iq = 30 )
: name(name), iq(iq) {}
std::string getName() { return name; }
int getIQ() { return iq; }
private:
std::string name;
int iq;
};
std::ostream & operator<<( std::ostream &os, const Student &obj )
{
return (os<<obj.name<<","<<obj.iq);
}
void demo_student()
{
VarArr<Student> feld;
std::cout << "\ndemo_student:" << std::endl;
feld[3] = Student( "Hans-Peter", 12 );
feld[5] = Student( "Birgit", 103 );
// Alle Elemente vom kleinsten benutzten bis zum höchsten benutzten
// ausgeben (obwohl nicht alle explizit initialisiert wurden!):
for( int i=feld.ug(); i<=feld.og(); i++ )
{
std::cout << "feld[" << i << "] = " << feld[i] <<std::endl;
}
}
/////////////////////////////////////////////////////////////////////////////
// vergleicht anhand einer Mischung aus lesenden und schreibenden
// Zugriffen die Laufzeiten für echte Felder, VarArr<> und vector<>
// aus der STL.
//
// Auf meinem Laptop (PIII, 1.1GHz) ergibt das mit dem g++ 2.95.3 (Linux):
// klaus@lap2:~/vararr > g++ -Wall test.cpp -O0 && a.out
// demo_zeit:
// belegen echtes Feld: 0.03500000 usec/Element
// verwenden echtes Feld: 0.05725000 usec/Zugriff
// belegen var. Feld: 0.20700000 usec/Element
// verwenden var. Feld: 0.06125000 usec/Zugriff
// verwenden var. Feld: 0.06700000 usec/Zugriff (const)
// belegen vector-Feld: 0.13700000 usec/Element
// verwenden vector-Feld: 0.06800000 usec/Zugriff (ungeprüft)
// klaus@lap2:~/vararr > g++ -Wall test.cpp -O3 && a.out
// demo_zeit:
// belegen echtes Feld: 0.03000000 usec/Element
// verwenden echtes Feld: 0.01530000 usec/Zugriff
// belegen var. Feld: 0.13200000 usec/Element
// verwenden var. Feld: 0.02635000 usec/Zugriff
// verwenden var. Feld: 0.02335000 usec/Zugriff (const)
// belegen vector-Feld: 0.08700000 usec/Element
// verwenden vector-Feld: 0.01555000 usec/Zugriff (ungeprüft)
// bzw. unter W2k mit VC++ 6.0:
// Y:\>cl /GX test.cpp & .\test
// demo_zeit:
// belegen echtes Feld: 0.02800000 usec/Element
// verwenden echtes Feld: 0.04226000 usec/Zugriff
// belegen var. Feld: 0.18930000 usec/Element
// verwenden var. Feld: 0.05382500 usec/Zugriff
// verwenden var. Feld: 0.05308000 usec/Zugriff (const)
// belegen vector-Feld: 0.37950000 usec/Element
// verwenden vector-Feld: 0.05518000 usec/Zugriff (ungeprüft)
// Y:\>cl /Ox /GX test.cpp & .\test
// demo_zeit:
// belegen echtes Feld: 0.02800000 usec/Element
// verwenden echtes Feld: 0.03675500 usec/Zugriff
// belegen var. Feld: 0.15120000 usec/Element
// verwenden var. Feld: 0.03560000 usec/Zugriff
// verwenden var. Feld: 0.03900500 usec/Zugriff (const)
// belegen vector-Feld: 0.16730000 usec/Element
// verwenden vector-Feld: 0.03675000 usec/Zugriff (ungeprüft)
// Anzahl der Durchläufe der verschiedenen Varianten (um die
// Genauigkeit von clock() zu erhöhen); je nach Rechner müssen die
// Werte ggf. angepaßt werden:
#define NDURCHLAUF_ARRAY 100000000
#define NDURCHLAUF_VARARR 100000000
#define NDURCHLAUF_VECTOR 100000000
// gewünschte Feldlänge:
#define N_ELEMENTE 10000000
#include <ctime>
#include <vector>
void demo_zeit()
{
clock_t start, ende;
size_t i;
std::cout << "\ndemo_zeit:" << std::endl;
//
// "echtes" Feld:
//
// erstmals belegen (static, weil unter VC++ sonst der Stack nicht
// reicht):
static int echtes_feld[N_ELEMENTE];
start = clock();
for( i=0; i<N_ELEMENTE; i++ )
{
echtes_feld[i] = (int)i;
}
ende = clock();
printf( "belegen echtes Feld: %12.8f usec/Element\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/N_ELEMENTE // Zeit je Element
*1.E6 // Umrechnen auf mikro sec
);
// verwenden:
start = clock();
for( i=0; i<NDURCHLAUF_ARRAY; i++ )
{
echtes_feld[i%N_ELEMENTE] = (int)i + echtes_feld[(i-55)%N_ELEMENTE];
}
ende = clock();
printf( "verwenden echtes Feld: %12.8f usec/Zugriff\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/NDURCHLAUF_ARRAY // Zeit je Element
/2.0 // zwei Feldzugriffe je Durchlauf
*1.E6 // Umrechnen auf mikro sec
);
//
// VarArr:
//
// erstmals belegen:
VarArr<int> vararr_feld;
start = clock();
for( i=0; i<N_ELEMENTE; i++ )
{
vararr_feld[i] = (int)i;
}
ende = clock();
printf( "belegen var. Feld: %12.8f usec/Element\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/N_ELEMENTE // Zeit je Element
*1.E6 // Umrechnen auf mikro sec
);
// verwenden (normales VarArr):
start = clock();
for( i=0; i<NDURCHLAUF_VARARR; i++ )
{
vararr_feld[i%N_ELEMENTE] = (int)i + vararr_feld[(i-55)%N_ELEMENTE];
}
ende = clock();
printf( "verwenden var. Feld: %12.8f usec/Zugriff\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/NDURCHLAUF_VARARR // Zeit je Element
/2.0 // zwei Feldzugriffe je Durchlauf
*1.E6 // Umrechnen auf mikro sec
);
// verwenden (const VarArr):
const VarArr<int> &vararr_feld_const = vararr_feld;
start = clock();
for( i=0; i<NDURCHLAUF_VARARR; i++ )
{
vararr_feld_const[i%N_ELEMENTE]
= (int)i + vararr_feld_const[(i-55)%N_ELEMENTE];
}
ende = clock();
printf( "verwenden var. Feld: %12.8f usec/Zugriff (const)\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/NDURCHLAUF_VARARR // Zeit je Element
/2.0 // zwei Feldzugriffe je Durchlauf
*1.E6 // Umrechnen auf mikro sec
);
//
// vector aus der STL:
//
// erstmals belegen:
std::vector<int> vector_feld; // Anm.: Man könnte hier auch gleich die
// vermutliche Länge angeben, um
// späteres teures Allokieren vermeiden
// zu helfen.
// Um aber wirklich dynamisches
// Verhalten zu simulieren, stellen wir
// uns dumm und wissen die Länge nicht.
start = clock();
for( i=0; i<N_ELEMENTE; i++ )
{
vector_feld.push_back( (int)i );
}
ende = clock();
printf( "belegen vector-Feld: %12.8f usec/Element\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/N_ELEMENTE // Zeit je Element
*1.E6 // Umrechnen auf mikro sec
);
// verwenden (ohne Feldgrenzenüberprüfung):
start = clock();
for( i=0; i<NDURCHLAUF_VECTOR; i++ )
{
vector_feld[i%N_ELEMENTE] = (int)i + vector_feld[(i-55)%N_ELEMENTE];
}
ende = clock();
printf( "verwenden vector-Feld: %12.8f usec/Zugriff (ungeprüft)\n",
(double)( ende - start ) // clock ticks
/CLOCKS_PER_SEC // Umrechnen auf sec
/NDURCHLAUF_VECTOR // Zeit je Element
/2.0 // zwei Feldzugriffe je Durchlauf
*1.E6 // Umrechnen auf mikro sec
);
}
/////////////////////////////////////////////////////////////////////////////
// demonstriert ein zweidimensionales Feld:
void demo_2dim_int()
{
// Feld von Feld vereinbaren:
VarArr< VarArr<int> > feld;
// Leerzeichen hier^ wichtig!
// Beliebige Elemente initialisieren und verwenden:
feld[1][-1] = 11;
feld[1][0] = 10;
feld[1][1] = 11;
feld[0][3] = 3;
feld[0][4] = 4;
feld[0][5] = 5;
feld[0][6] = 6;
feld[0][8] = 8;
feld[0][9] = 9;
feld[-5][10] = -510;
feld[-5][11] = -511;
feld[-5][12] = -512;
feld[-5][13] = -513;
// alle Werte ausgeben:
std::cout << "\ndemo_2dim_int:" << std::endl;
for( int i=feld.ug(); i<=feld.og(); i++ )
{
for( int j=feld[i].ug(); j<=feld[i].og(); j++ )
{
std::cout << "feld[" << i << "][" << j << "] = "
<< feld[i][j]
<< std::endl;
}
}
}
/////////////////////////////////////////////////////////////////////////////
int main( int nargs, char **args )
{
demo_simple_int(); // einfaches Feld
demo_simple_double(); // einfaches Feld
demo_default_int(); // Feld mit default-Wert
demo_student(); // Feld eigene class für Elemente
demo_zeit(); // Zeitvergleich C-Array, VarArr, vector
demo_2dim_int(); // 2-dimensionales Feld
return 0;
}
/////////////////////////////////////////////////////////////////////////////