Maîtrise approfondie du conteneur std::vector en C++

Fondamentaux et Initialisation

Le conteneur std::vector fait partie intégrante de la STL en C++. Il s'agit d'un tableau dynamique capable de stocker des éléments de n'importe quel type. Pour l'utiliser, l'inclsuion de l'en-tête dédié est requise :

#include <vector>

Constructeurs et Méthodes d'Initialisation

La création d'un vecteur peut se faire de plusieurs manières selon les besoins :

std::vector<T> default_vec;                          // Constructeur par défaut
std::vector<T> range_vec(src.begin(), src.end());    // Copie à partir d'une plage d'itérateurs
std::vector<T> fill_vec(count, value);               // Remplissage avec 'count' copies de 'value'
std::vector<T> copy_vec(original_vec);               // Constructeur par copie

Exemples pratiques de déclaration :

std::vector<int> seq_numeric(15);                    // 15 entiers initialisés par défaut
std::vector<int> seq_zeroed(15, 0);                  // 15 entiers valant 0

std::vector<int> cloned(seq_numeric);                // Copie intégrale
std::vector<int> partial(seq_zeroed.begin(), seq_zeroed.begin() + 5); // 5 premiers éléments

std::set<std::string> unique_names;
std::vector<std::string> names_vec(unique_names.begin(), unique_names.end());

int raw_data[] = {10, 20, 30};
std::vector<int> data_from_arr(raw_data, raw_data + 3);

std::vector<int> literal_vec{10, 20, 30};            // Initialisation par liste

Opérations Courantes sur les Vecteurs

Assignation

std::vector<int> primary = {5, 10, 15};
std::vector<int> secondary = primary;                // Surcharge de l'opérateur =

std::vector<int> tertiary;
tertiary.assign(primary.begin(), primary.end());     // Assignation par plage
tertiary.assign(4, 99);                              // Assignation par remplissage (quatre 99)

Insertion d'Éléments

std::vector<int> buffer;
buffer.push_back(42);                                // Ajout en fin de séquence
buffer.insert(buffer.begin(), 100);                  // Insertion en première position
buffer.insert(buffer.begin() + 1, 3, 7);             // Insertion de trois '7' à l'index 1

Suppression d'Éléments

std::vector<int> data = {1, 2, 3, 4, 5, 6};
data.pop_back();                                     // Retire le dernier élément
data.erase(data.begin() + 1);                        // Retire l'élément à l'index 1
data.erase(data.begin(), data.begin() + 2);          // Retire une plage d'éléments

Accès aux Données

std::vector<int> metrics = {10, 20, 30, 40, 50};

int val1 = metrics.at(2);                            // Accès avec vérification des bornes
int val2 = metrics[3];                               // Accès direct sans vérification
int first = metrics.front();                         // Premier élément
int last = metrics.back();                           // Dernier élément

Gestion de la Taille et de la Capacité

bool is_empty = metrics.empty();                     // Vérifie si le conteneur est vide
size_t current_size = metrics.size();                // Nombre d'éléments actuels
size_t allocated_cap = metrics.capacity();           // Capacité totale allouée

metrics.resize(8);                                   // Ajuste la taille (remplit avec 0 si agrandi)
metrics.resize(10, 99);                              // Ajuste la taille (remplit avec 99 si agrandi)

Permutation de Conteneurs

std::vector<int> alpha = {1, 2};
std::vector<int> beta = {3, 4, 5};
alpha.swap(beta);                                    // Échange les pointeurs internes en O(1)

Structurse Multidimensionnelles

Pour créer une matrice ou un tableau 2D, l'initialisation nécessite de spécifier les dimensions :

int rows = 5;
int cols = 4;

// Méthode 1 : Initialisation via constructeur
std::vector<std::vector<int>> matrix_2d(rows, std::vector<int>(cols, 0));

// Méthode 2 : Initialisation par défaut
std::vector<std::vector<int>> grid(rows, std::vector<int>(cols));

// Méthode 3 : Redimensionnement dynamique
std::vector<std::vector<int>> dynamic_grid;
dynamic_grid.resize(rows);
for (int r = 0; r < rows; ++r) {
    dynamic_grid[r].resize(cols);
}

Idiomes Avancés et Pièges Fréquents

Retourner un Vecteur Directement

Il est possible de retourner un vecteur en utilisant une liste d'initialisation directement dans l'instruction return :

#include <vector>

class ArraySolver {
public:
    std::vector<int> findPairIndices(const std::vector<int>& sorted_arr, int goal) {
        int left_ptr = 0;
        int right_ptr = static_cast<int>(sorted_arr.size()) - 1;

        while (left_ptr < right_ptr) {
            int current_sum = sorted_arr[left_ptr] + sorted_arr[right_ptr];
            if (current_sum == goal) {
                return {left_ptr + 1, right_ptr + 1}; // Retour direct
            } else if (current_sum < goal) {
                ++left_ptr;
            } else {
                --right_ptr;
            }
        }
        return {};
    }
};

Piège d'Initialisation : Parenthèses vs Accolades

La syntaxe utilisée pour l'initialisation change radicalement le comportement :

int count = 5;
int value = 8;

// Crée un vecteur de 5 éléments, tous initialisés à 8
std::vector<int> filled_vec(count, value);

// Crée un vecteur de 2 éléments contenant les valeurs 5 et 8
std::vector<int> literal_vec{count, value};

Piège d'Accès par Index sur un Vecteur Vide

Il est impossible d'utiliser l'opérateur [] pour assigner une valeur à un index qui n'existe pas encore. Le vecteur doit d'abord être dimensionné ou utiliser push_back :

std::vector<int> empty_vec;
// empty_vec[0] = 10; // Comportement indéfini / Segfault

// Approche correcte 1 : Ajout dynamique
empty_vec.push_back(10);

// Approche correcte 2 : Redimensionnement préalable
empty_vec.resize(1);
empty_vec[0] = 10;

Publié le 15 juin à 23h33