Zeiger und Vektoren



  • Zu Testzwecken habe ich die Funktion test auch nochmal verändert:

    const char** test(const char* const s1, const char* s2)
    {    
        s1 = "ein anderer Test";   //Fehler
        s2 = "Test1";   
        s1 = s2; //Fehler  
       
        return &s1; //Fehler
    }
    

    Beide Zeiger sind jetzt konstant und zeigen auf ein Objekt, welches selbst konstant ist. Wie erwartet erscheinen in den Zeilen 3 und 5 jetzt Fehler. Warum Zeile 7 auch nicht passt, ist mir nicht klar. Ich hole mit & die Adresse des doppelt konstanten Zeigers s1. Also lese/hole nur die Adresse. Warum stimmt der Rückgabetyp nicht mit den Funktionstyp überein (Fehler)? Dank!!!



  • @C-Sepp sagte in Zeiger und Vektoren:

    Warum stimmt der Rückgabetyp nicht mit den Funktionstyp überein (Fehler)? Dank!!!

    Der genaue Wortlaut der Fehlermeldung wäre doch angebracht.
    Per Copy&Paste.

    Andererseits bringt das auch nichts, da s1 eine lokale Variable ist, die beim Beenden der Funktion (das macht return) nicht mehr existiert.
    Die Adresse davon ist nicht sinnvoll.



  • Die Fehlermeldung lautet:
    "const char *const *" kann nicht in "const char **" konvertiert werden



  • Dann ist const char *const * was du als Rückgabetyp angeben solltest.

    Ist aber nicht sinnvoll.



  • @C-Sepp sagte in Zeiger und Vektoren:

    Super...wieder etwas dazugelernt. Gut zu wissen auch, dass der Schreibschutz eines konstanten Zeigers im Gegensatz zu strings nicht unterlaufen werden kann. Sprich ich kann einen konstanten Zeiger keinen nicht konstanten Zeiger zuweisen:

    ???

    const string s1 ="Ein Stringstest";
    string s2 = s1;
    

    Das hier in Zeile 2 erzeugt eine KOPIE von s1! s1 wird nicht geändert und somit auch kein const-Schutz unterlaufen! Die Kopie in s2 kannst du natürlich ändern. Das änder aber nicht s1.

    In diesem Code:

    char* temp = s; // Fehler
    

    Hier machst du keine Kopie des Strings, sondern versuchst, den Pointer zu kopieren, aber nicht den String selbst. Wenn das erlaubt wäre, würdest du durch Ändern von temp[0] auch s[0] ändern.

    Auch mit char-Pointern darfst du natürlich Kopien anlegen. Dies muss aber mit strcpy oder verwandten Funktionen geschehen. Und dann darfst du auch const nach nicht-const kopieren.



  • Okay!
    Wenn bei char* temp = s nur der Zeiger kopiert wird, warum erscheint dann in dem Fall (Zeiger auf konstantes Objekt) eine Fehlermeldung, welche das const ankreidet??
    Das heißt wenn Pointer und Wert kopiert werden würden, könnte ich durch Ändern von temp[0] auch s[0] ändern, obwohl nur eine Kopie angelegt wird...wie geht denn das. Das entspricht doch eigentlich den Prinzip einer Referenz??



  • Nein, es wird keine Kopie des Strings angelegt, sondern nur der Zeiger (die Adresse) kopiert. Und weil ein String-Literal ("...") nicht verändert werden kann, mußt du einen const char * verwenden (also dessen Inhalt dann eben nicht per z.B. Indexoperator verändert werden kann).



  • @C-Sepp sagte in Zeiger und Vektoren:

    Das heißt wenn Pointer und Wert kopiert werden würden, könnte ich durch Ändern von temp[0] auch s[0] ändern, obwohl nur eine Kopie angelegt wird...wie geht denn das.

    Nein, wenn Du auch den Wert kopierst, ist

    temp[0] != s[0]
    

    kopierst Du nur den Zeiger, ist

    temp[0] == s[0]
    

    Der Wert ist ja dann nur einmal vorhanden, es wird aber von zwei verschiedenen Zeigervariablen drauf gezeigt.



  • Hey Leute,

    bin auf eine Funktion gestoßen, zu der sich eine Frage ergeben hat. Sie lautet:

    double* Summe(double* a, double* b, int length)
    {
        double* sum = new double[length];
        for (int i=0; i<length; i++)
        {
           sum[i] = a[i] + b[i];
        }
        return sum;
    }
    

    Addiert also Schritt für Schritt die Werte zweier an a und b übergebener Array's. Warum kann ich mit a[i] und b[i] die Werte zur Addition ansprechen? Eigentlich wurde bei Zeigern doch immer der *-Operator verwendet. Vielen Dank!



  • 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


Anmelden zum Antworten