Recherche…


C itérateurs (pointeurs)

// This creates an array with 5 values.
const int array[] = { 1, 2, 3, 4, 5 };

#ifdef BEFORE_CPP11

// You can use `sizeof` to determine how many elements are in an array.
const int* first = array;
const int* afterLast = first + sizeof(array) / sizeof(array[0]);

// Then you can iterate over the array by incrementing a pointer until
// it reaches past the end of our array.
for (const int* i = first; i < afterLast; ++i) {
    std::cout << *i << std::endl;
}

#else

// With C++11, you can let the STL compute the start and end iterators:
for (auto i = std::begin(array); i != std::end(array); ++i) {
    std::cout << *i << std::endl;
}

#endif

Ce code afficherait les nombres 1 à 5, un sur chaque ligne comme ceci:

1
2
3
4
5

Le briser

const int array[] = { 1, 2, 3, 4, 5 };

Cette ligne crée un nouveau tableau d'entiers avec 5 valeurs. Les tableaux C ne sont que des pointeurs vers la mémoire où chaque valeur est stockée ensemble dans un bloc contigu.

const int* first = array;
const int* afterLast = first + sizeof(array) / sizeof(array[0]);

Ces lignes créent deux pointeurs. Le premier pointeur reçoit la valeur du pointeur de tableau, qui est l'adresse du premier élément du tableau. L'opérateur sizeof utilisé sur un tableau C renvoie la taille du tableau en octets. Divisé par la taille d'un élément, cela donne le nombre d'éléments dans le tableau. Nous pouvons l'utiliser pour trouver l'adresse du bloc après le tableau.

for (const int* i = first; i < afterLast; ++i) {

Nous créons ici un pointeur que nous utiliserons comme itérateur. Il est initialisé avec l'adresse du premier élément que nous voulons parcourir, et nous allons continuer à itérer tant que i est inférieur à afterLast , ce qui signifie aussi longtemps que i pointe vers une adresse dans array .

    std::cout << *i << std::endl;

Enfin, dans la boucle , nous pouvons accéder à la valeur de notre itérateur i pointe par déréférencement elle. Ici, l'opérateur de déréférence * renvoie la valeur à l'adresse dans i .

Vue d'ensemble

Les itérateurs sont des positions

Les itérateurs sont un moyen de naviguer et d'opérer sur une séquence d'éléments et constituent une extension généralisée des pointeurs. Conceptuellement, il est important de se rappeler que les itérateurs sont des positions et non des éléments. Par exemple, prenez la séquence suivante:

A B C

La séquence contient trois éléments et quatre positions

+---+---+---+---+
| A | B | C |   |
+---+---+---+---+

Les éléments sont des choses dans une séquence. Les positions sont des endroits où des opérations significatives peuvent avoir lieu dans la séquence. Par exemple, on insère dans une position, avant ou après l' élément A, pas dans un élément. Même la suppression d'un élément ( erase(A) ) se fait en trouvant d'abord sa position, puis en la supprimant.

Des itérateurs aux valeurs

Pour convertir une position en une valeur, un itérateur est déréférencé :

auto my_iterator = my_vector.begin(); // position
auto my_value = *my_iterator; // value

On peut penser à un itérateur en tant que déréférencement à la valeur à laquelle il fait référence dans la séquence. Ceci est particulièrement utile pour comprendre pourquoi vous ne devriez jamais déréférencer l'itérateur end() dans une séquence:

+---+---+---+---+
| A | B | C |   |
+---+---+---+---+
  ↑           ↑
  |           +-- An iterator here has no value. Do not dereference it!
  +-------------- An iterator here dereferences to the value A.

Dans toutes les séquences et tous les conteneurs trouvés dans la bibliothèque standard C ++, begin() renvoie un itérateur à la première position et end() renvoie un itérateur à un autre après la dernière position ( pas la dernière!). Par conséquent, les noms de ces itérateurs dans les algorithmes sont souvent étiquetés en first et en last :

+---+---+---+---+
| A | B | C |   |
+---+---+---+---+
  ↑           ↑
  |           |
  +- first    +- last

Il est également possible d'obtenir un itérateur à n'importe quelle séquence , car même une séquence vide contient au moins une position:

+---+
|   |
+---+

Dans une séquence vide, begin() et end() auront la même position et aucune des deux ne pourra être déréférencée:

+---+
|   |
+---+
  ↑
  |
  +- empty_sequence.begin()
  |
  +- empty_sequence.end()

La visualisation alternative des itérateurs est qu'ils marquent les positions entre les éléments:

+---+---+---+
| A | B | C |
+---+---+---+
↑   ^   ^   ↑
|           |
+- first    +- last

et déréférencer un itérateur renvoie une référence à l'élément venant après l'itérateur. Certaines situations où cette vue est particulièrement utile sont les suivantes:

  • insert opérations d'insertion insèrent des éléments dans la position indiquée par l'itérateur,
  • erase opérations d' erase renverront un itérateur correspondant à la même position que celle passée,
  • un itérateur et son itérateur inverse correspondant sont situés dans la même position entre les éléments

Itérateurs non valides

Un itérateur devient invalidé si (par exemple, au cours d'une opération) sa position ne fait plus partie d'une séquence. Un itérateur invalidé ne peut pas être déréférencé tant qu'il n'a pas été réaffecté à une position valide. Par exemple:

std::vector<int>::iterator first;
{
    std::vector<int> foo;
    first = foo.begin(); // first is now valid
} // foo falls out of scope and is destroyed
// At this point first is now invalid

Les nombreux algorithmes et fonctions de membre de la séquence dans la bibliothèque standard C ++ ont des règles régissant le moment où les itérateurs sont invalidés. Chaque algorithme est différent dans la manière dont il traite (et invalide) les itérateurs.

Comme nous le savons, les itérateurs servent à naviguer dans les séquences. Pour ce faire, un itérateur doit migrer sa position tout au long de la séquence. Les itérateurs peuvent avancer dans la séquence et certains peuvent reculer:

auto first = my_vector.begin();
++first;                                             // advance the iterator 1 position
std::advance(first, 1);                              // advance the iterator 1 position
first = std::next(first);                            // returns iterator to the next element
std::advance(first, -1);                             // advance the iterator 1 position backwards
first = std::next(first, 20);                        // returns iterator to the element 20 position forward
first = std::prev(first, 5);                         // returns iterator to the element 5 position backward
auto dist = std::distance(my_vector.begin(), first); // returns distance between two iterators.

Notez que le second argument de std :: distance devrait être accessible depuis le premier (ou, en d’autres termes, first devrait être inférieur ou égal à la second ).

Même si vous pouvez exécuter des opérateurs arithmétiques avec des itérateurs, toutes les opérations ne sont pas définies pour tous les types d'itérateurs. a = b + 3; fonctionnerait pour les itérateurs à accès aléatoire, mais ne fonctionnerait pas pour les itérateurs directs ou bidirectionnels, qui peuvent toujours être avancés de 3 positions avec quelque chose comme b = a; ++b; ++b; ++b; . Il est donc recommandé d'utiliser des fonctions spéciales si vous n'êtes pas sûr du type d'itérateur (par exemple, dans une fonction de modèle acceptant un itérateur).

Concepts d'itérateur

Le standard C ++ décrit plusieurs concepts d'itérateurs différents. Ceux-ci sont regroupés en fonction de leur comportement dans les séquences auxquelles ils se réfèrent. Si vous connaissez le concept d'un modèle d' itérateur (se comporte comme), vous pouvez être assuré du comportement de cet itérateur indépendamment de la séquence à laquelle il appartient . Ils sont souvent décrits dans l'ordre, du plus restrictif au moins restrictif (car le concept d'itérateur suivant est un pas meilleur que son prédécesseur):

  • Les itérateurs d'entrée: peuvent être déréférencés seulement une fois par position. Ne peut avancer que d'une seule position à la fois.
  • Itérateurs directs: un itérateur d'entrée pouvant être déréférencé un nombre illimité de fois.
  • Itérateurs: Un Bidirectionnel iterator avant qui peut également avancer en arrière une position à la fois.
  • Randator Access Iterators: Un itérateur bidirectionnel qui peut avancer ou reculer d'un nombre quelconque de positions à la fois.
  • Itérateurs contigus (depuis C ++ 17): itérateur à accès aléatoire garantissant que les données sous-jacentes sont contiguës en mémoire.

Les algorithmes peuvent varier en fonction du concept modélisé par les itérateurs. Par exemple, bien que random_shuffle puisse être implémenté pour les itérateurs avant, une variante plus efficace nécessitant des itérateurs à accès aléatoire pourrait être fournie.

Traits d'itérateur

Les traits d'itérateur fournissent une interface uniforme aux propriétés des itérateurs. Ils permettent de récupérer la valeur, la différence, le pointeur, les types de référence et également la catégorie d'itérateur:

template<class Iter>
Iter find(Iter first, Iter last, typename std::iterator_traits<Iter>::value_type val)  {
    while (first != last) {
        if (*first == val)
            return first;
        ++first;
    }
    return last;
}

La catégorie d'itérateur peut être utilisée pour spécialiser des algorithmes:

template<class BidirIt>
void test(BidirIt a, std::bidirectional_iterator_tag)  {
    std::cout << "Bidirectional iterator is used" << std::endl;
}
 
template<class ForwIt>
void test(ForwIt a, std::forward_iterator_tag)  {
    std::cout << "Forward iterator is used" << std::endl;
}
 
template<class Iter>
void test(Iter a)  {
    test(a, typename std::iterator_traits<Iter>::iterator_category());
}

Les catégories d'itérateurs sont essentiellement des concepts d'itérateurs, sauf que les itérateurs contigus ne disposent pas de leur propre balise, car il a été trouvé que le code est cassé.

Itérateurs inverses

Si nous voulons parcourir en arrière une liste ou un vecteur, nous pouvons utiliser un reverse_iterator . Un itérateur inversé est créé à partir d'un itérateur bidirectionnel ou à accès aléatoire qu'il conserve en tant que membre et accessible via base() .

Pour itérer en arrière, utilisez rbegin() et rend() comme itérateurs pour la fin de la collection et le début de la collection, respectivement.

Par exemple, pour itérer l'utilisation en arrière:

std::vector<int> v{1, 2, 3, 4, 5};
for (std::vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it)
{
    cout << *it;
} // prints 54321

Un itérateur inversé peut être converti en un itérateur avant via la fonction membre base() . La relation est que l'itérateur inversé référence un élément après l'itérateur base() :

std::vector<int>::reverse_iterator r = v.rbegin();
std::vector<int>::iterator i = r.base();
assert(&*r == &*(i-1)); // always true if r, (i-1) are dereferenceable
                        // and are not proxy iterators

 +---+---+---+---+---+---+---+
 |   | 1 | 2 | 3 | 4 | 5 |   |
 +---+---+---+---+---+---+---+
   ↑   ↑               ↑   ↑
   |   |               |   |
rend() |         rbegin()  end()
       |                   rbegin().base()
     begin()
     rend().base()

Dans la visualisation où les itérateurs marquent les positions entre les éléments, la relation est plus simple:

  +---+---+---+---+---+
  | 1 | 2 | 3 | 4 | 5 |
  +---+---+---+---+---+
  ↑                   ↑
  |                   |
  |                 end()
  |                 rbegin()
begin()             rbegin().base()
rend()
rend().base()

Itérateur de vecteur

begin renvoie un iterator au premier élément du conteneur de séquence.

end renvoie un iterator au premier élément après la fin.

Si l'objet vectoriel est const , les deux begin et end renvoient un const_iterator . Si vous souhaitez qu'un const_iterator soit renvoyé même si votre vecteur n'est pas const , vous pouvez utiliser cbegin et cend .

Exemple:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = { 1, 2, 3, 4, 5 };  //intialize vector using an initializer_list

    for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << " ";
    }

    return 0;
}

Sortie:

1 2 3 4 5

Itérateur de carte

Un itérateur sur le premier élément du conteneur.

Si un objet map est qualifié en const, la fonction renvoie un const_iterator . Sinon, il renvoie un iterator .

// Create a map and insert some values
std::map<char,int> mymap;
mymap['b'] = 100;
mymap['a'] = 200;
mymap['c'] = 300;

// Iterate over all tuples
for (std::map<char,int>::iterator it = mymap.begin(); it != mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

Sortie:

a => 200
b => 100
c => 300

Itérateurs de flux

Les itérateurs de flux sont utiles lorsque vous devez lire une séquence ou imprimer des données formatées à partir d'un conteneur:

// Data stream. Any number of various whitespace characters will be OK.
std::istringstream istr("1\t 2     3 4");
std::vector<int> v;

// Constructing stream iterators and copying data from stream into vector.
std::copy(
    // Iterator which will read stream data as integers.
    std::istream_iterator<int>(istr),
    // Default constructor produces end-of-stream iterator.
    std::istream_iterator<int>(),
    std::back_inserter(v));

// Print vector contents.
std::copy(v.begin(), v.end(),
    //Will print values to standard output as integers delimeted by " -- ".
    std::ostream_iterator<int>(std::cout, " -- "));

Le programme d'exemple imprimera 1 -- 2 -- 3 -- 4 -- sur la sortie standard.

Ecrivez votre propre itérateur avec générateur

Un modèle courant dans d'autres langages consiste à avoir une fonction qui produit un "flux" d'objets et à pouvoir utiliser le code de boucle pour la parcourir.

Nous pouvons modéliser ceci en C ++ comme

template<class T>
struct generator_iterator {
  using difference_type=std::ptrdiff_t;
  using value_type=T;
  using pointer=T*;
  using reference=T;
  using iterator_category=std::input_iterator_tag;
  std::optional<T> state;
  std::function< std::optional<T>() > operation;
  // we store the current element in "state" if we have one:
  T operator*() const {
    return *state;
  }
  // to advance, we invoke our operation.  If it returns a nullopt
  // we have reached the end:
  generator_iterator& operator++() {
    state = operation();
    return *this;        
  }
  generator_iterator operator++(int) {
    auto r = *this;
    ++(*this);
    return r;
  }
  // generator iterators are only equal if they are both in the "end" state:
  friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) {
    if (!lhs.state && !rhs.state) return true;
    return false;
  }
  friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) {
    return !(lhs==rhs);
  }
  // We implicitly construct from a std::function with the right signature:
  generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))
  {
    if (operation)
      state = operation();
  }
  // default all special member functions:
  generator_iterator( generator_iterator && ) =default;
  generator_iterator( generator_iterator const& ) =default;
  generator_iterator& operator=( generator_iterator && ) =default;
  generator_iterator& operator=( generator_iterator const& ) =default;
  generator_iterator() =default;
};

exemple vivant .

Nous stockons l'élément généré tôt afin de pouvoir détecter plus facilement si nous sommes déjà à la fin.

Comme la fonction d'un itérateur de générateur d'extrémité n'est jamais utilisée, nous pouvons créer une gamme d'itérateurs de générateur en ne copiant qu'une seule fois la std::function . Un générateur de génération construit par défaut se compare à lui-même et à tous les autres générateurs-finisseurs.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow