C++ Lernen - Verständnisfragen


  • Mod

    @Schlangenmensch sagte in C++ Lernen - Verständnisfragen:

    Aber, dass wäre ja zu einfach. Gerade nochmal nachgelesen, wenn es nicht passt und der Zieltyp signed ist, war das Ergebnis bis c++20 tatsächlich Implementation defined.
    Seit c++ 20 ist das fest definiert als der Wert modulo 2^(Anzahl Bits des Zieltypes)

    Das haben sie jetzt wirklich fest definiert? Das war doch immer ein Paradebeispiel für einen Fall wo undefiniertes Verhalten Optimierungen ermöglicht.



  • @SeppJ

    A prvalue of an integer type or of an unscoped (since C++11) enumeration type can be converted to any other integer type. If the conversion is listed under integral promotions, it is a promotion and not a conversion.
    If the destination type is signed, the value does not change if the source integer can be represented in the destination type. Otherwise the result is implementation-defined (until C++20)the unique value of the destination type equal to the source value modulo 2n
    where n is the number of bits used to represent the destination type. (since C++20). (Note that this is different from signed integer arithmetic overflow, which is undefined).

    https://en.cppreference.com/w/cpp/language/implicit_conversion

    Wenn ich das richtig lese, sind Overflows aufgrund von Berechnungen explizit davon ausgenommen. Das heißt im Fall von for loops kann da immer noch optimiert werden.


  • Mod

    Ah ja, das klingt nach einer guten Anpassung.



  • Auch hier wieder vielen Dank für eure Erläuterungen!

    Kommen wir zu meinen nächsten Problem:

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    int main ()
    {
    vector<string> ausgabe;
    string eingabe;
    int z=0;
    
    cout << "Wie viele Elemente soll der String haben? ";
    cin >> z;
    cout << endl;
    cout << "Sie wollen " << z << " Elemente eingeben." << endl;
    cout << endl;
    
        do {
            cout << "Etwas eingeben: ";
            getline (cin, eingabe);
            cout << endl;
    
            ausgabe.push_back(eingabe);
    
            cout << "Elemente im String: " << ausgabe.size() << endl;
            cout << endl;
    
            for (int k=0; k < ausgabe.size(); k++){
                cout << ausgabe.at(k) << " ";
                }
            cout << endl;
        }
        while (z-1 >=ausgabe.size());
    }
    

    Kann mir jemand erklären warum die do-while Schleife die Eingabe-Taste von cin >> z; mit zählt und den ersten Loop quasi durchläuft?



  • @Drgreentom sagte in C++ Lernen - Verständnisfragen:

    Kann mir jemand erklären warum die do-while Schleife die Eingabe-Taste von cin >> z; mit zählt und den ersten Loop quasi durchläuft?

    Weil das Zeichen der Eingabetaste ('\n') noch im Eingabestrom steht.
    Bei cin werden nur Zeichen gelesen, die zu dem Typ passen.
    Dann wird aufgehört und das nichtpassende Zeichen zurück gestellt.

    getline() jedoch, liest das '\n' und hört dann auf.

    Mit

    cin.clear(); 
    cin.ignore(1000,'\n');
    

    kannst du es überlesen



  • @Dirk: Warum 1000?
    Ich denke, das ist, gerade für einen Anfänger, sehr verwirrend.

    Sauber wäre

    cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // mit #include <limits>
    


  • Danke, das funktioniert mit dem clear und ignore!

    Aber so richtig erschließt sich mir das noch nicht.
    Wenn ich also z.B. bei einem Datenyp int oder double die Taste "Enter" drücke, muss ich damit rechnen, dass es mir das "Enter" in den nächsten String mit hineinschreiben wird/kann, wenn ich dort cin verwende?



  • @Drgreentom sagte in C++ Lernen - Verständnisfragen:

    …, muss ich damit rechnen, dass es mir das "Enter" in den nächsten String mit hineinschreiben wird/kann, wenn ich dort cin verwende?

    Nein.
    cin betrachtet Whitespace als Trennzeichen und überliest diese auch am Anfang.

    Das Problem tritt bei der Mischung von cin und getline() auf



  • Da ich aber z.B. eine int Variable nicht mit getline() beschreiben kann/darf, wird diese Mischung mit cin und getline() immer wieder mal auftreten?



  • @Drgreentom Wenn du zeilenweise einlesen möchtest, kannst du auch den int aus einem string wandeln.



  • Hallo zusammen, ich bins mal wieder...

    In der ersten for-Schleife steht X : v als Bedingung. Nur um sicher zugehen, was bedeutet der ":" hier?
    Ich verstehe was die Funktion macht: Für jedes Element in Vecor v erfolgt die zuweisung an X bis zu letzten Element in v. Im Buch wird das zwar benutzt aber nicht erklärt. Im Netz habe ich auch bisher keine befriedigende Erklärung gefunden. Der ":" Operator? hat doch bestimmt einen Namen?

    
    #include <iostream>
    #include <vector>
      
    using namespace std;
      
    int main()
    {
        typedef std::vector<int> vInt;
      
        vInt v;
      
        v.push_back(190);
        v.push_back(180);
        v.push_back(10);
        v.push_back(10);
        v.push_back(27);
      
        for (auto X : v) {			// < wie kann ich den ":" hier verstehen? 
            cout << X << " ";		        
        }
    
        for (int i=0; i<v.size(); i++){	// Andere Schreibweise, gleiche Ausgabe
    	cout << v[i] << " ";
        }
        return 0;
    }
    


  • @Drgreentom

    In der ersten for-Schleife steht X : v als Bedingung. Nur um sicher zugehen, was bedeutet der ":" hier?

    Das ist ein sog. "range based for loop".
    Der Teil vor dem : gibt die Laufvariable an und der Teil hinter dem : ist der Container über den iteriert werden soll. Der : ist einfach nur das Trennzeichen.



  • Hallo zusammen,
    ich habe mal wieder eine Frage:

    Ich möchte mir eine debug Funktion schreiben. Dazu möchte ich unterschiedliche Arrays, die jeweils mit Strings gefüllt sind, an ein Funktion übergeben. Zur Übersichtlichkeit habe ich mich mal das #include, namspace und std:: blabla weg gelassen.

    
    void funktion(array<string> fc_array) \\<- Funktioniert so nicht, da ein Argument fehlt 
    {
    \\mach mit fc_array etwas
    }
    
    int main()
    {
    array<string,3> arr = {string1,string2,string3};
    funktion(arr);
    }
    

    Wie muss ich das Array an die Funktion übergeben? Ich habe bereits gelesen, dass das u.a. mit Pointer funktioniert könnte. Aber ich bekomme das leider nicht hin und den Anwendungsfall finde ich so im Internet nicht. Könnt ihr mir das bitte an dem Bsp. zeigen?
    Vielen Dank vorab



  • @Drgreentom sagte in C++ Lernen - Verständnisfragen:

    Wie muss ich das Array an die Funktion übergeben?

    void funktion(array<string> fc_array) \\<- Funktioniert so nicht, da ein Argument fehlt 
    

    std::array ist ein Array mit fester Größe. Das heisst die Größe muss zur Compile-Zeit bekannt sein, also im Allgemeinen im Code mitgegeben werden. Daher hat std::array auch noch einen zweiten Template-Parameter, mit dem die Länge des Arrays angegeben wird. Die Fehlermeldung bekommst du, weil dieser fehlt - so sollte es funktionieren:

    void funktion(array<string, 3> fc_array)
    

    Damit legst du dich allerdings auf String-Arrays der Länge 3 fest. Vielleicht möchtest du aber Arrays beliebiger Länge verarbeiten.

    Eine Möglichkeit ist, ein Array dynamischer Größe zu verwenden: std::vector. Bei diesen muss die Länge nicht bereits beim Kompilieren bekannt sein und sie können ihre Größe auch ändern, während das Programm läuft. std::vector hat daher keinen Template-Parameter für die Länge:

    #include <vector>
    ...
    void funktion(std::vector<string> fc_array)
    ...
    std::vector<string> arr = {string1,string2,string3};
    funktion(arr);
    

    Falls du dennoch ein std::array mit fixer Größe verwenden willst, gibt es auch die Möglichkeit, die Länge des Arrays automatisch vom Compiler bestimmen zu lassen. Dazu macht man funktion zu einer Template-Funktion und kann die Array-Länge deduzieren lassen (d.h. vom Compiler aus dem jeweils übergebenen Argument herleiten lassen):

    #include <cstdint>
    ...
    template <std::size_t N>
    void funktion(array<string, N> fc_array)
    ...
    array<string, 3> arr = {string1,string2,string3};
    funktion(arr);
    

    Hierbei erzeugt der Compiler für jede Array-Länge, mit der du die Funktion in deinem Programm aufrufst, eine Template-Instanz der Funktion mit jeweils der passenden Länge im Funktionsparameter. Das ist, als würdest du für jede Länge, die du brauchst, eine individuelle funktion schreiben.

    Noch ein Hinweis: Du übergibst das Array "by Value", d.h. es wird bei jedem Funktionsaufruf eine Kopie des Array angelegt. Bei einem array<int, 3> wäre das völlig in Ordnung, bei einem array<string, 3> fängt das jedoch an, etwas teurer zu werden, da hier jeder String kopiert werden muss (dynamische Speicher-Reservierung und Kopie des unter Umständen recht langen Strings). Wenn also keine guten Gründe für die Übergabe "by Value" sprechen, empfielt es sich, das Array z.B. als Referenz entgegenzunehmen:

    void funktion(const std::array<string, 3>& fc_array)
    

    oder

    void funktion(const std::vector<string>& fc_array)
    


  • Danke für die Antwort. Wie das bei <vector> funktioniert war mir bereits bekannt. Da ist das wirklich unkompliziert.
    Es ging nur darum das ich der Funktion void funktion(array<string, N> fc_array) unterschiedliche Arrays mit jeweils unbekannter Größe übergeben will.
    Also z.B. mal ein array<string,4> und dann mal array<string,14> oder so. Funktioniert das dann mit der template Variante und wenn ich ein template template <std::size_t N> erstelle, wird dann die Variable N global?

    Ich gebe dir vollkommen recht, das Array ode Vector sollte meiner Ansicht call by reference (&) in der Funktion sein, da ich unter Umständen den Inhalt des Array bearbeiten will. Ich habe festgestellt, dass wenn ich ich sie als Value übergebe, der bearbeitete Wert nicht an den Parameter zurückgegeben wird. Ich müsste den Wert sonst in ein Variable übergeben, die vor der Funtkion bereits definiert ist, womit ich ja noch eine Kopie erstelle?!

    Macht es Sinn einen Parameter in einer Funktion so zu definieren: void funktion (const vector<string> &parameter){} wenn der Inhalt nur gelesen werden soll?
    Nach meinem aktuellen Verständnis spare ich mir so die Kopie des Vectors und somit Laufzeit und const verhindert ja sowieso die Veränderung des Parameters ? Richtig?



  • @Drgreentom sagte in C++ Lernen - Verständnisfragen:

    Funktioniert das dann mit der template Variante und wenn ich ein template template <std::size_t N> erstelle, wird dann die Variable N global?

    Nein, N ist nur innerhalb der Template-Definition gültig und auch keine richtige Variable, sondern mehr wie ein Literal, also so, als würdest du die Zahl direkt in den Code schreiben. Ich glaube ab C++20 sind das dann sogar richtige (konstante) Objekte mit statischer Lebenszeit, von denen man die Adresse nehmen und mit denen man Referenzen initalisieren kann, aber das geht denke ich zu sehr ins Detail.

    Ich gebe dir vollkommen recht, das Array ode Vector sollte meiner Ansicht call by reference (&) in der Funktion sein, da ich unter Umständen den Inhalt des Array bearbeiten will. Ich habe festgestellt, dass wenn ich ich sie als Value übergebe, der bearbeitete Wert nicht an den Parameter zurückgegeben wird. Ich müsste den Wert sonst in ein Variable übergeben, die vor der Funtkion bereits definiert ist, womit ich ja noch eine Kopie erstelle?!

    Das hier wäre ein gar nicht mal so verkehrtes Pattern, das würde ich sogar erstmal per Default empfehlen, wenn man keine guten Gründe hat, es anders zu machen:

    #include <utility>
    ...
    std::vector<string> funktion(std::vector<string> fc_array)
    {
        // modifiziere fc_array
        ...
        return std::move(fc_array);
    }
    

    Das hat den Vorteil, dass das übergebene Array eben nicht modifiziert wird (weniger überraschend: "wer hat mein schönes Array verwurstelt?" und angenehm in einem Multithreaded-Kontext zu verwenden wenn die Funktion auf ihrer persönlichen Kopie arbeitet). Man kann so die Funktion auch mit einem const-Array verwenden und profitiert durch die "by Value"-Übergabe von den vorhandenen Move-Konstruktoren des std::vector (falls ein Rvalue übergeben wird).

    Natürlich wird auch hier eine Kopie erzeugt, aber das ist ein Preis, der sich durchaus relativiert, je nachdem wie aufwändig die Modifikationen des Array ohnehin schon sind.

    Ansonsten tuts durchaus auch ein void funktion(std::vector<string>& fc_array), falls das für den Anwendungsfall sinnvoller erscheint.

    Macht es Sinn einen Parameter in einer Funktion so zu definieren: void funktion (const vector<string> &parameter){} wenn der Inhalt nur gelesen werden soll?
    Nach meinem aktuellen Verständnis spare ich mir so die Kopie des Vectors und somit Laufzeit und const verhindert ja sowieso die Veränderung des Parameters ? Richtig?

    Ja, das ist ziemlicher Standard, wenn nur gelesen werden soll. Das, was ich oben mit "by Value"-Übergabe geschrieben habe, bezieht sich nur darauf, wenn die Funktion das Array auch tatsächlich modifizieren soll.



  • Mit dem std::vector als Parameter funktioniert deine Funktion natürlich nicht mehr mit std::array. Und umgekehrt. Eigentlich sind diese Objekte aber sehr ähnlich. Daher ist es häufig sinnvoller, es so wie die stl zu machen, d.h. du schreibst eine Funktion, die zwei Iteratoren als Parameter hat. Dann kannst du auch z.B. mal das "halbe" Array verarbeiten. Häufig ist so eine Funktion mit einem ganzen vector als Parameter ganz nett zu haben, wenn du sowieso nur mit vector und immer mit dem ganzen vector arbeitest. Aber generell würde ich dir empfehlen, dir mal den algorithm-Header anzuschauen und wie die Funktionen dort Parameter behandeln. Es gibt ja z.B. auch noch std::deque usw.



  • @Finnegan ich würde generell (es sei denn es gibt im Spezialfall einen guten Grund) das std::move bei dem return weg lassen. Damit verhinderst du Copy elision und erzwingst den move. Wenn Copy elision nicht möglich ist, sollte jeder Compiler von sich aus moven.



  • @Schlangenmensch sagte in C++ Lernen - Verständnisfragen:

    @Finnegan ich würde generell (es sei denn es gibt im Spezialfall einen guten Grund) das std::move bei dem return weg lassen. Damit verhinderst du Copy elision und erzwingst den move. Wenn Copy elision nicht möglich ist, sollte jeder Compiler von sich aus moven.

    Ja, gute Idee, nicht zuletzt auch weil es noch simpler und geradliniger ist:

    #include <utility>
    ...
    std::vector<string> funktion(std::vector<string> fc_array)
    {
        // modifiziere fc_array
        ...
        return fc_array;
    }
    

    Das ist genau das, was C++ immer gebraucht hat: Dass der Code, der einem als Anfänger als erstes in den Sinn kommt, bereits eine ziemlich gute Lösung ist.



  • @wob sagte in C++ Lernen - Verständnisfragen:

    d.h. du schreibst eine Funktion, die zwei Iteratoren als Parameter hat.

    Alternativ auch ein Range-Concept aus C++20:

    #include <ranges>
    ...
    void funktion(std::ranges::input_range auto range)
    

    Leider scheint es da aber irgendwie kein fertiges Concept zu geben, mit dem man den Element-Typen z.B. auf string einschränken kann, so dass man sich selbst eins definieren müsste:

    #include <concepts>
    #include <ranges>
    #include <string>
    ...
    template <typename R, typename T>
    concept input_range_of = std::ranges::input_range<R> && std::same_as<std::ranges::range_value_t<R>, T>;
    ...
    void funktion(input_range_of<std::string> auto range)
    

    Aber das geht eventuell alles etwas zu weit für einen Anfänger, der sich gerade noch mit Arrays und Vektoren befasst. Das sei mal ein "sneak peek" auf das, was C++ noch alles zu bieten hat, um das "Wie übergebe ich ein Array?"-Problem zu lösen 😉


Anmelden zum Antworten