Minimales Element einer Reihe
-
Hi,
ich muss in einer endlichen Reihe (N = 16 )von reelen Zahlen nach dem minimalen Element suchen. Ich habe folgendes gemacht:#include <vector> #include <algorithm> int main(int argc, char *argv[]) { const int size = 3; if(argc - 1 != size) return 1; std::vector<double> a; a.reserve(16); std::transform(argv + 1, argv + argc, std::inserter(a, a.end()), /*...*/); /*...*/ return 0; }Bei der Funktion transform versuche ich die Argumente von der Kommandozeile nach "double" umwandeln, und dann in die Reihe verschieben. Aber wie genau soll diese Umwandlung aussehen? Ich glaube, dass es mit sstream zu machen ist, oder?
-
Schreib' Dir eine kleine Funktiion die eine char* in einen double umwandelt - etwa so
double text2dbl( const char* txt ) { std::istringstream buf( txt ); double x; buf >> x; assert( !buf.fail() ); // evt. andere Fehlerbehandlung return x; }Es benötigt die Includes <sstream> und <cassert>. Der Aufruf ist dann
std::transform( argv + 1, argv + argc, std::inserter(a, a.end()), &text2dbl);Den kleinsten Wert liefert Dir übrigens der Algorithmus std::min_element.
Gruß
Werner
-
Übrigens solltest du statt des inserter(...) lieber einen back_inserter() verwenden - spätestens wenn mehr als 16 Eingabewerte kommen, hast du ein Problem mit ungültigen Iteratoren.
(oder du gibst gleich die korrekte Größe bei reserve() an)
-
CStoll schrieb:
(oder du gibst gleich die korrekte Größe bei reserve() an)
reserve ist für dieses Problem irrelevant, da es nur die Kapazität des Vektors ändert. Die Gültigkeit des Inserter-Iterators hängt hingegen einzig von der Größe des Vektors ab. Insofern muss der Rat heißen:
"Übrigens solltest du statt des inserter(...) lieber einen back_inserter() verwenden - oder du gibst gleich die endgültige Größe bei <b>resize()</b> an"
-
Falsch - der Inserter vergrößert den Vektor schon passend, wenn es sein muß. Allerdings kann die Einfügeposition, die er sich gemerkt hat, ungültig werden, wenn er seine Kapazität überschritten hat und die Daten umkopiert - deshalb muß die Kapazität so groß gewählt werden, daß er zwischendurch nichts umkopieren muß.
(und das machst du mit reserve())Wenn du unbedingt resize() verwenden willst, brauchst du überhaupt keinen Inserter mehr - da hast du schon genug Elemente und kannst sie mit transform() füllen.
-
@CStoll
Hast natürlich grundsätzlich recht, war ein Denkfehler meinerseits. In einem Punkt irrst du hingegen: der inserter ist unabhängig von reserve und resize immer gültig (unter der Annahme, dass er bei seiner Konstruktion gültig war). Das liegt daran, dass der inserter den Iterator beim Einfügen aktualisiert - er verwendet den Rückgabewert von Container::insert. Siehe dazu: 24.4.2.6.2
-
CStoll schrieb:
der Inserter vergrößert den Vektor schon passend, wenn es sein muß. Allerdings kann die Einfügeposition, die er sich gemerkt hat, ungültig werden, wenn er seine Kapazität überschritten hat und die Daten umkopiert ..
werden.
es wäre doch ein Armutszeugnis für die Iterator-Libary des C++-Standards, wenn CStoll recht hätte. Laut Standard (Kapitel 24.4.2.6.2) ist der Effekt einer Zuweisung an den insert_iterator:
iter = container->insert(iter, value); ++iter;wobei 'container' und 'iter' aus dem Aufruf
inserter( container, iter )stammen. D.h. auch wenn ein vector intern einen alloc aufruf und die Daten umkopiert und seine (bisherigen) Iteratoren ungültig werden, so liefert er mit der Methode 'insert' schön brav einen gültigen Iterator auf das soeben eingefügte Element zurück. Und erst dieser wird vom insert_iterator inkrementiert. Zeigte der Iterator vorher auf end() so wird auch der inkrementierte Iterator auf (das neue) end() zeigen.
Probiert's aus; es geht.
Gruß
Werner
-
Hm, ich habe folgendes gemacht:
#include <sstream> namespace zparse { template <class T> T convert(const char *txt); template <class T> bool convert(const char *txt, T &obj); } template <class T> T zparse::convert(const char *txt) { std::istringstream buf(txt); T x; buf >> x; return x; } template <class T> bool zparse::convert(const char *txt, T &obj) { std::istringstream buf(txt); buf >> obj; return buf.fail(); }Und die main.cpp Datei sieht so aus:
#include <vector> #include <algorithm> #include "zparse.hpp" int main(int argc, char *argv[]) { std::vector<double> a; std::transform(argv + 1, argv + argc, std::inserter(a, a.end()), &zparse::convert<double>); return 0; }Es lässt sich nicht kompilieren! Warum?
-
weil du hier zwei überladene templates hast, und der compiler nicht weiss, welches er nehmen soll. das kannst du ihm per static_cast sagen (wir wollen die funktion mit einem parameter):
std::transform(argv + 1, argv + argc, std::inserter(a, a.end()), static_cast<double (*)(const char*)>(&zparse::convert<double>));
-
Aber die Template Funktionen haben verschiedene Signaturen. Ich verstehe nicht warum der Kompilator die richtige Funktion nicht finden kann.
-
Weil er in dem Moment, wo er entscheidet wie transform zu expandieren ist, nicht in transform reingucken kann wie der Templateparameter genutzt werden könnte um zu bestimmen welcher genommen werden soll.
-
Ok, ok - jetzt verstehe ich. Dann brauche ich eine Template Funktion, die dieses CAST macht.
template <class T, class I> void parse(I &iter, const int argc, const char *argv[]);So etwas, oder?