1. Vecteurs
Objectif
Implémenter une classe Vecteur
pour manipuler des vecteurs.
dense.cpp
.
Fichiers
La déclaration de la classe et de toute fonction associée aux vecteurs se fera dans include/vecteur.hpp
et la définition des fonctions et méthodes dans src/vecteur.cpp
. Vous êtes bien entendu libre de choisir un autre nom de fichier que vecteur.*
mais vous devez respecter l’emplacement de ces fichiers.
Stockage
Informatiquement, un vecteur est stocké sous la forme d’un tableau de nombres. La classe Vecteur
qui représentera un tel vecteur possèdera comme données privées (au moins) :
int N_
: Le nombre d’éléments (= la dimension)std::vector<double> coef_
: Les coefficients, stockés dans un tableau à une dimension de la bibliothèque standard
Notre fichier d’en-tête ressemble donc à
// Fichier include/vecteur.hpp
#pragma once
#include<vector> // pour les std::vector
class Vecteur{
private:
int N_;
std::vector<double> coef_;
public:
// Les méthodes et constructeurs à venir
// ...
};
Constructeurs et destructeur
Notre classe comporte (au moins) les trois constructeurs suivants
Vecteur (); // constructeur vide
Vecteur (int N); // constructeur créant un vecteur de taille N et rempli de zéros
Vecteur (const Vecteur &v); // constructeur par recopie
ainsi qu’un destructeur par défaut :
~Vecteur()=default; // destructeur par défaut
Le fichier d’en-tête est alors modifié ainsi
// Fichier include/vecteur.hpp
#pragma once
#include<vector> // pour les std::vector
class Vecteur{
private:
int N_;
std::vector<double> coef_;
public:
Vecteur (); // constructeur vide
Vecteur (int N); // constructeur créant un vecteur de taille N et rempli de zéros
Vecteur (const Vecteur &v); // constructeur par recopie
~Vecteur()=default; // destructeur par défaut
};
Implémentez la définition des constructeurs dans le fichier src/vecteur.cpp
. Ce fichier ressemble à ceci
#include "vecteur.hpp"
#include <vector>
Vecteur::Vecteur(){
//Constructeur vide
}
Vecteur::Vecteur(int N){
//Constructeur du vecteur nul de taille N
}
Vecteur::Vecteur(const Vecteur &v){
//Constructeur par recopie
}
Quelques méthodes
const
.
Voici deux méthodes qui nous seront utiles
- Méthode constante qui renvoie la taille du
Vecteur
, par exemple
int size() const;
- Accesseurs :
double & operator() (int i); // Accès à la référence
double operator() (int i) const; // Accès à la valeur (recopie)
Le premier permet d’accéder au coefficient de la matrice par référence (permettant une modification ultérieure) tandis que le second ne fait que renvoyer (une copie de) la valeur du coefficient.
C’est parti :
- Ajoutez leurs déclarations dans la classe (dans le fichier header)
- Implémentez leurs définitions (dans le fichier source)
Vecteur
via sa donnée coef_
(e.g. coef_[i]
) mais uniquement via ses accesseurs (e.g. v(i)
).
Affichage
Afficher un Vecteur
(ses coefficients et/ou toute autre info utile) dans le terminal nous sera utile, ne serait-ce que pour l’étape de vérification. Vous disposez de plusieurs choix pour se faire, la méthode la plus adaptée au C++
est la surcharge de l’opérateur de flux sortant <<
.
class Vecteur{
[...]
};
// En dehors de la classe Vecteur (mais dans le même fichier)
std::ostream & operator<<(std::ostream &os, const Vecteur& v);
Le fait de retourner un std::ostream
(typiquement std::cout
) présente l’avantage de pouvoir chaîner les opérations :
Vecteur v;
[...]
std::cout << "Mon vecteur : " << v << std::endl;
La surcharge de l’opérateur de flux sortant permet aussi de choisr le flux : affichage sur le terminal, écriture sur un fichier, …
const Vecteur &v
permet d’envoyer le Vecteur
v
par référence (plutôt que par copie), le mot clé const
nous garanti qu’il ne sera pas modifié par la fonction appelante.
To be friend
or not to be ? Parfois cet surcharge d’opérateur est déclarée à l’intérieur de la classe Vecteur
auquel on adjoint le mot clé friend
:
class Vecteur{
[...]
friend std::ostream & operator<<(std::ostream &os, const Vecteur& v);
};
Une fonction (et non méthode) friend
est une fonction qui a le droit d’accéder aux données privées de la classe “amie” (ici Vecteur
). En pratique, ce n’est pas forcément une bonne idée comme expliqué sur StackOverflow car cela brise l’encapsulation (la privatisation des données).
La règle est la suivante : utilisez le mot clé friend
si et seulement si vous avez un besoin impérieux d’accéder aux données privées (et en général cela se fait via une méthode d’accès aux données comme notre fonction size()
décrite plus haut).
Opérations arithmétiques
Surchargeons maintenant les opérations arithmétiques habituelles, c’est à dire :
- L’addition entre deux
Vecteur
:operator+
- La soustraction entre deux
Vecteur
:operator-
- Le produit entre un scalaire (
double
) et unVecteur
:operator*
- Le produit scalaire entre deux
Vecteur
:operator*
Mathématiquement, ces opérations sont des opérations binaires, qui nécessitent deux arguments. Informatiquement, nous avons le choix de définir certaines d’entre elles de manière unaire ou binaire, c’est à dire sous forme de méthodes ou de fonctions.
Pour rester proche des mathématiques, nous conseillons plutôt de définir ces opérateurs sous forme binaire. Comme nous avons définit un operator()
qui permet d’accéder aux coefficients d’un Vecteur
, nous n’avons aucune raison d’utiliser le mot clé friend
et conservons ainsi le principe d’encapsulation cher au C++
. Plus d’infos sont disponibles sur cette discussion :
class Vecteur{
[...]
};
// En dehors de la classe Vecteur
Vecteur operator+(const Vecteur &v, const Vecteur &w);
Vecteur
. Pour le produit avec un scalaire, regardez le warning ci-dessous.
operator
avant de passer à la suite.
Remarque sur la Multiplication par un Scalaire.
En C++, v*alpha
et alpha*v
sont deux opérations différentes et doivent toutes deux être définies correctement ! Autrement dit, vous devez définir un operator*
correspondant à ces deux opérations, bien que le code soit identique.
Pourquoi ? Supposons que vous ne définissez que l’un des deux, par exemple :
Vecteur operator*(const Vecteur &v, double alpha);
Supposons maintenant que votre code comporte :
Vecteur v;
double alpha;
[...]
Vecteur x = alpha * v;
Le compilateur devrait déclencher une erreur car l’opérateur n’existe pas. Cependant, il existe un risque que le compilateur ne dise rien mais qu’à l’exécution, le résultat soit complètement farfelu ! Vous devez donc définir les deux :
Vecteur operator*(const Vecteur &v, double alpha);
Vecteur operator*(double alpha, const Vecteur &v);