Wie benutze ich einen Iterator?

72

Ich versuche den Abstand zwischen zwei Punkten zu berechnen. Die zwei Punkte, die ich in einem Vektor in C ++ gespeichert habe: (0,0) und (1,1).

Ich soll Ergebnisse erzielen als

0
1.4
1.4
0

Aber das tatsächliche Ergebnis, das ich bekam, ist

0
1
-1
0

Ich denke, es stimmt etwas nicht mit der Art und Weise, wie ich Iterator in Vektor verwende. Wie kann ich dieses Problem beheben?

Ich habe den Code unten gepostet.

typedef struct point {
    float x;
    float y;
} point;

float distance(point *p1, point *p2)
{
    return sqrt((p1->x - p2->x)*(p1->x - p2->x) +
                (p1->y - p2->y)*(p1->y - p2->y));
}

int main()
{
    vector <point> po;
    point p1; p1.x = 0; p1.y = 0;
    point p2; p2.x = 1; p2.y = 1;
    po.push_back(p1);
    po.push_back(p2);

    vector <point>::iterator ii;
    vector <point>::iterator jj;
    for (ii = po.begin(); ii != po.end(); ii++)
    {
        for (jj = po.begin(); jj != po.end(); jj++)
        {
            cout << distance(ii,jj) << " ";
        }
    }
    return 0;
}
Peter Mortensen
quelle

Antworten:

192

Dass Ihr Code überhaupt kompiliert wird, liegt wahrscheinlich daran, dass Sie using namespace stdirgendwo einen haben. (Sonst vectormüsste es sein std::vector.) Davon würde ich abraten, und Sie haben gerade einen guten Fall angeführt, warum:
Ihr Anruf wird versehentlich entgegengenommen std::distance(), was zwei Iteratoren erfordert und die Entfernung zwischen ihnen berechnet. Entfernen Sie die using-Direktive und stellen Sie allen Standardbibliothekstypen das Präfix voran, std::und der Compiler teilt Ihnen mit, dass Sie versucht haben, a zu übergeben, vector <point>::iteratorwo a point*erforderlich war.

Um einen Zeiger auf ein Objekt zu erhalten, auf das ein Iterator zeigt, müssen Sie den Iterator dereferenzieren - der einen Verweis auf das Objekt gibt - und die Adresse des Ergebnisses übernehmen : &*ii.
(Beachten Sie, dass ein Zeiger alle Anforderungen für einen std::vectorIterator perfekt erfüllen würde und einige frühere Implementierungen der Standardbibliothek tatsächlich Zeiger dafür verwendeten, wodurch Sie std::vectorIteratoren als Zeiger behandeln konnten. Moderne Implementierungen verwenden jedoch eine spezielle Iteratorklasse dafür Der Grund dafür ist, dass die Verwendung einer Klasse das Überladen von Funktionen für Zeiger und Iteratoren ermöglicht. Die Verwendung von Zeigern als std::vectorIteratoren fördert außerdem das Mischen von Zeigern und Iteratoren, wodurch verhindert wird, dass der Code kompiliert wird, wenn Sie Ihren Container ändern.)

Aber anstatt dies zu tun, schlage ich vor, dass Sie Ihre Funktion so ändern, dass stattdessen Referenzen verwendet werden (siehe diese Antwort, warum das sowieso eine gute Idee ist.):

float distance(const point& p1, const point& p2)
{
    return sqrt((p1.x - p2.x)*(p1.x - p2.x) +
                (p1.y - p2.y)*(p1.y - p2.y));
}

Beachten Sie, dass die Punkte von constReferenzen übernommen werden . Dies zeigt dem Anrufer an, dass die Funktion die übergebenen Punkte nicht ändert.

Dann können Sie es so nennen : distance(*ii,*jj).


Nebenbei bemerkt, dies

typedef struct point {
    float x;
    float y;
} point;

ist ein C-Ismus in C ++ unnötig. Buchstabiere es einfach

struct point {
    float x;
    float y;
};

Das würde Probleme structbereiten , wenn diese Definition jemals von einem C-Compiler analysiert werden sollte (der Code müsste struct pointdann nicht einfach darauf verweisen point), aber ich denke, std::vectorund dergleichen wäre für einen C-Compiler sowieso eine weitaus größere Herausforderung.

sbi
quelle
14
Diese Antwort ist falsch. std :: distance kann von ADL auf einem std :: iterator erfasst werden, sodass es Teil des Kandidatensatzes sein kann, unabhängig davon, ob es verwendet wird oder nicht std.
Welpe
4
@ Welpe: Das ist in der Tat wahr (und seit 2,5 Jahren hat es niemand bemerkt), aber das ist nicht alles, was meine Antwort gesagt hat. Das Übergeben von Punkten pro const point& p1löst auch dieses Problem.
sbi
5
@sbi: Nein, es wird das Problem nicht lösen. Es wird immer noch möglich sein, fälschlicherweise zu schreiben distance(ii, jj)und zu bekommen std::distance.
Ben Voigt
10
Obwohl niemand sagt, dass der offensichtlichste Weg, Ihre Version der Distanz zu erzwingen, das Schreiben ist, ::distance(...)anstatt zu schreiben, distance(...)wann Sie die Funktion aufrufen. Ihre distanceFunktion ist im globalen Namespace definiert, sodass Sie den Namen Ihrer Funktion mit einem leeren Präfix qualifizieren können ::distance(und natürlich müssen Sie den Iterator dereferenzieren, um ihn ordnungsgemäß aufzurufen).
Peregring-lk
4
Verwenden des Namespace std; ist eine extrem schlechte Praxis. Das ist hier zu denken. Verbreiten Sie das Wort!
amanuel2
21

Zufällig verwenden Sie tatsächlich eine integrierte STL-Funktion "distance" , die die Entfernung zwischen Iteratoren berechnet, anstatt Ihre eigene Entfernungsfunktion aufzurufen. Sie müssen Ihre Iteratoren "dereferenzieren", um das enthaltene Objekt zu erhalten.

cout << distance(&(*ii), &(*jj)) << " ";

Wie Sie der obigen Syntax entnehmen können, ähnelt ein "Iterator" einem verallgemeinerten "Zeiger". Der Iterator kann nicht direkt als "Ihr" Objekttyp verwendet werden. Tatsächlich sind Iteratoren Zeigern so ähnlich, dass viele Standardalgorithmen, die mit Iteratoren arbeiten, auch mit Zeigern gut funktionieren.

Wie Sbi bemerkte: Ihre Distanzfunktion nimmt Zeiger. Es wäre besser, stattdessen const-Referenzen zu verwenden, was die Funktion "kanonischer" c ++ machen und die Iterator-Dereferenzierungssyntax weniger schmerzhaft machen würde.

float distance(const point& i_p1, const point& i_p2)
{
    return sqrt((p1.x - p2.x)*(p1.x - p2.x) +
                (p1.y - p2.y)*(p1.y - p2.y));
}

cout << distance(*ii, *jj) << " ";
Joris Timmermans
quelle
6

Sie könnten ein paar Dinge tun:

  1. Lassen Sie die distance()Funktion auf pointObjekte verweisen . Dies dient nur dazu, die Lesbarkeit der distance()Funktion zu verbessern:
    float distance(const point& p1, const point& p2)
    {
        return sqrt((p1.x - p2.x)*(p1.x - p2.x) +
                    (p1.y - p2.y)*(p1.y - p2.y));
    }
    
  2. Dereferenzieren Sie Ihre Iteratoren beim Aufrufen, distance()damit Sie die pointObjekte übergeben:
    distance( *ii, *jj)
    
    Wenn Sie die Schnittstelle der distance()Funktion nicht ändern , müssen Sie sie möglicherweise wie folgt aufrufen, um geeignete Zeiger zu erhalten:
    distance( &*ii, &*jj)
    
Michael Burr
quelle