Zeiger und Vektoren



  • Der Indexoperator a[n] ist äquivalent zu *(a + n).

    Alternativ kann man auch die Funktion so deklarieren:

    double* Summe(double a[], double b[], int length)
    

    Wie du siehst, kein Unterschied zwischen Array- und Zeigerzugriff.



  • Aber noch was: diese Funktion hat ein schlimmes Design, weil sie sich innerhalb einer Berechnung Speicher holt und diesen als non-owning Pointer zurückgibt. Das ist eine schlechte Idee, weil man dieser Funktion das nicht ansieht. Dinge wie Verschachteln von mehreren Aufrufen der Funktion Summe führen sofort zu Speicherlecks.

    Also dieses hier:
    auto gesamt = Summe(a, Summe(b, c, 10), 10);
    addiert zwar a+b+c korrekt, aber gibt Speicher nicht korrekt frei.

    In C++ würde ich eher 2 Iteratorpaare als Parameter erwarten, sowas in der Art von:

    std::transform(
        a.begin(), a.end(), // 1. Range
        b.begin(), // 2. Range, Länge wie 1
        outputIterator, 
        std::plus<double>());
    


  • @Th69 sagte in Zeiger und Vektoren:

    Der Indexoperator a[n] ist äquivalent zu *(a + n).

    Und aus a+n = n+a folgt *(a+n) = *(n+a) = n[a] (= ist Gleichheit, keine Zuweisung)



  • Vielen Dank für eure schnellen Antworten. Bei Verwendung der folgenden Klasse ist jetzt noch eine weitere kleinere
    Frage entstanden:

    class IntArr
    {
    	private:
    		int* ptrArr;		
    		int len;
    
    	public:		
    		IntArr(int len);
    		~IntArr();
    
    		int length() const { return len;}		
    		void compress();
    };
    
    IntArr::IntArr(int len)
    {
    	this->len = len;
    	ptrArr = new int[len];
    	for (int i = 0; i < len; i++)
    	{
    		ptrArr[i] = 0;
    	}
    }
    
    IntArr::~IntArr()
    {
    	delete [] ptrArr;
    }
    
    void IntArr::compress()
    {	
    	int count=0;
    	int index=0;
    
    	for (int i = 0; i < len; i++)
    	{
    		if (ptrArr[i] != 0)
    			count++;
    	}
    
    	int* ptrArr1 = new int[count];
    
    	for (int i = 0; i < len; i++)
    	{		
    		if (ptrArr[i] != 0)			
    		    ptrArr1[index] = ptrArr[i];		    
    			index += 1;
    	}
    
    	delete[] ptrArr;
    	ptrArr = ptrArr1;
    	len = count;
    }
    

    Aufruf:

    int main()
    {
        IntArr arr(100);
    
        srand((unsigned int)time(NULL));
    
        for (int i = 0; i < arr.legnth(); i++)
        {
            arr[i] = rand();
        }
    }
    

    Warum kann man den einzelnen Objekt arr[i] einfach so einen Wert zuweisen? Müsste dazu nicht ein geeigneter Konstruktor vorhanden sein, um zunächst ein passendes Objekt vom Typ der Klasse anzulegen, welches dem Objekt dann zugewiesen werden kann? Wenn das so klappt, wo wird der Wert von rand() dann reingeschrieben? Vielen Dank!!



  • @C-Sepp sagte in Zeiger und Vektoren:

    Warum kann man den einzelnen Objekt arr[i] einfach so einen Wert zuweisen?

    Das geht gar nicht - und zwar, weil dein Code nicht vollständig ist. Dazu müsste nämlich irgendwo Code für den operator[] vorhanden sein. In diesem müsste der entsprechende Code implementiert sein.

    Müsste dazu nicht ein geeigneter Konstruktor vorhanden sein, um zunächst ein passendes Objekt vom Typ der Klasse anzulegen, welches dem Objekt dann zugewiesen werden kann?

    Der Konstruktor existiert doch - in deinem IntArr::IntArr:
    ptrArr = new int[len];
    Damit werden alle len Integer default-initialisiert (tut nix). Wenn du geschwungene Klammern hinzufügst ptrArr = new int[len]{};, würdest du value-initialisieren (setzt auf 0) und könntest die folgende Schleife, die alle auf 0 Werte setzt, einsparen.

    PS: Verwende std::vector<int> statt IntArr. Verwende nullptr statt NULL. Verwende https://en.cppreference.com/w/cpp/numeric/random statt rand.



  • Das geht auch nicht ohne weiteres - dazu müßtest du schon den Index-Operator [] überladen.
    Bei deinem Code kommt auch eine entsprechende Fehlermeldung: Ideone-Code (den Schreibfehler bei length habe ich mal korrigiert):

    error: no match for ‘operator[]’ (operand types are ‘IntArr’ and ‘int’)
    arr[i] = rand();



  • Den Indexoperator [] habe ich weggelassen, weil ich dachte, dass dieser für die Frage nicht relevant ist.
    Er lautet:

    int& IntArr::operator[](int i)
    {
    	if (i <= 0 || i > len)
    		cerr << "Der Bereichsindex wurde überschritten";
    	return ptrArr[i];
    }
    

    Mir leuchtet trotzdem noch nicht ein, wie mit Hilfe des vorhandenen Konstruktors die Werte von rand() in ein Objekt vom Typ der Klasse geschrieben werden sollen. Der vorhandene Konstruktor erstellt doch lediglich dynamisch ein Array der Länge len auf welches die Zeiger ptrArr dann zeigen und initialisiert alle Werte mit 0?



  • @C-Sepp sagte in Zeiger und Vektoren:

    Mir leuchtet trotzdem noch nicht ein, wie mit Hilfe des vorhandenen Konstruktors die Werte von rand() in ein Objekt vom Typ der Klasse geschrieben werden sollen. Der vorhandene Konstruktor erstellt doch lediglich dynamisch ein Array der Länge len auf welches die Zeiger ptrArr dann zeigen und initialisiert alle Werte mit 0?

    Wieso mit Hilfe des Konstruktors?

    Der Konstruktor initialisiert alle Werte mit 0 (wie du auch geschrieben hast).

    In deiner Schleife schreibst du die Random-Werte dann in das Array. Das ist nicht im Konstruktor.

    Vielleicht hast du nicht richtig verstanden, wie die operator[]-Funktion funktioniert?



  • Der Operator gibt ja eigentlich nur den jeweiligen i-ten Wert des Arrays zurück. Ich vermute, dass das irgendwie mit der Referenz als Rückgabewert zusammenhängt? Meine, dass in dem Fall die Operatorfunktion wie ein Objekt vom Typ int behandelt werden kann und auch solche Operationen (sprich Zuweisung, Addition) zulässt. Wie das möglich ist keine Ahnung. Wahrscheinlich muss man sich das einfach nur merken?



  • @C-Sepp sagte in Zeiger und Vektoren:

    Der Operator gibt ja eigentlich nur den jeweiligen i-ten Wert des Arrays zurück.

    Nein, er gibt eine Referenz auf den i-ten Wert zurück. Großer Unterschied.

    Ich vermute, dass das irgendwie mit der Referenz als Rückgabewert zusammenhängt?

    Bingo!

    Wie das möglich ist keine Ahnung. Wahrscheinlich muss man sich das einfach nur merken?

    Eine mögliche Implementierung ist, dass der Compiler einfach einen Zeiger auf dieses Element zurückgibt und dann beim Zugriff auf den Wert automatisch dereferenziert - d.h. er versteckt dann für dich die Zeigeroperationen. Aber der Compiler kann das auch irgendwie anders machen, so wie er will, solange der Effekt am Ende derselbe ist.



  • Alles klar...aber muss der Rückgabewert (i.d.F. Zeiger ptrArr) dann nicht statisch sein? Bei Referenzen muss as Objekt auf welches der Rücgkabewert verweist doch nach Verlassen der Funktion noch existieren



  • Das Klassenobjekt (und damit das enthaltene Array) existieren doch weiterhin.
    Man darf nur nicht eine Referenz auf eine lokale Variable zurückgeben.



  • Ahh okay..stimmt. Danke!



  • @C-Sepp sagte in Zeiger und Vektoren:

    int& IntArr::operator[](int i)
    {
    	if (i <= 0 || i > len)
    		cerr << "Der Bereichsindex wurde überschritten";
    	return ptrArr[i];
    }
    

    Aua, das tut weh. Wenn man schon auf den Index prüft, dann bitte auch richtig darauf reagieren. Bei Dir gibt es trotzdem einen Zugriff auf das falsche Element.

    int& IntArr::operator[] (const int i) {
        if (i <= 0 || i > len) throw std::runtime_error("index out of bounds");
    
         return ptrArr[i];
     }
    

    Wenn man unbedingt die Resourcen selbst verwalten will muss man die Rule of five einhalten.

    P.S. int ist kein guter Datentyp für die Länge von Arrays.



  • @john-0 sagte in Zeiger und Vektoren:

    Aua, das tut weh. Wenn man schon auf den Index prüft, dann bitte auch richtig darauf reagieren. Bei Dir gibt es trotzdem einen Zugriff auf das falsche Element.
    int& IntArr::operator[] (const int i) {
    if (i <= 0 || i > len) throw std::runtime_error("index out of bounds");

    Ist ja schön und gut, dass du das cerr in die Exception umgewandelt hast, aber das hüpfende Komma war doch die if-Bedingung if (i <= 0 || i > len), die vielleicht besser if (i < 0 || i >= len) hätte heißen sollen, sofern man nicht einen size_t nimmt. Wobei ich mir angewöhnt habe, Längen von Containern grundsätzlich über test < container.size() zu vergleichen, d.h. immer "irgendwas kleiner als size" zu schreiben. Dann muss man nicht mehr nachdenken, ob <, <= oder welcher Operator auch immer richtig ist.

    ...wenn man diese Klasse behalten will, sollte man zusätzlich zu den schon genannten Dingen auch noch einen const-overload für diesen Operator anbieten - oder sich allgemein überhaupt mal um const-correctness Gedanken machen.


Anmelden zum Antworten