Base de connaissances CCM
Programmation - Langages - Langage C++




Sujet 247 - [Optimisation] Polymorphisme “statique”

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]

* Le polymorphisme est l'une des pierres angulaires du paradigme objet

En C++, il s'exprime sous la forme de fonctions virtuelles : celles-ci permettent d'adapter dynamiquement (i.e. à l'exécution) leur code au type de l'objet auxquelles elles sont appliquées. Cela peut avoir un impact négatif sur les performances, dans le cas particulier où :

En effet, comme le compilateur ne connaît pas à l'avance le code à appliquer, il ne peut pas appliquer les optimisations usuelles. Par ailleurs, le coût en place mémoire est d'autant plus grand que les branchements possibles (pour chaque type d'objet) sont nombreux.

Elle consiste à déclarer la classe parente comme classe générique, recevant comme paramètre le type de la classe dérivée. Cette classe devra ensuite tout déléguer à ses héritiers (comme un classe abstraite qui n'a que des fonctions virtuelles pures), mais ainsi le code à appliquer est connu au moment de la compilation.

Voici un petit exemple :
// classe de base paramétrée par le type de la classe dérivée
template<class TypeEnfant>
class Matrice {
public:
  // fonction de délégation
  TypeEnfant& CommeUnEnfant()
  {
    return (static_cast<TypeEnfant&>(*this));
  }

  // surcharge par délégation
  double operator()(int i, int j)
  {
    return (CommeUnEnfant()(i,j));
  }
private:
  ...
};

// exemples de classes dérivées
class MatriceTriangulaire : public Matrice<MatriceTriangulaire> {
private:
  ...
};

class MatriceSymetrique : public Matrice<MatriceSymetrique> {
private:
  ...
};

// fonction "virtuelle" fonctionnant avec n'importe quel type de matrice
template<class TypeEnfant>
double norme(Matrice<TypeEnfant>&);

// exemple d'application
double nt, ns;
MatriceTriangulaire mt;
MatriceSymetrique   ms;
nt = norme(mt);
ns = norme(ms);


N'empéche que ce n'est pas totalement souple, car tout ajout se fera dans la plupart des cas dans au moins deux endroits dans le code, dans l'interface de la classe dérivée et dans celle de la classe de base. Je préconise donc que cette méthode est à utiliser lorsque l'interface est figée et que comme le titre le dit, on veut optimiser le code.

Lire la suite

Les templates en C++ »
Publié par Fu Xuen - Dernière mise à jour le 22 novembre 2009 à 17:11 par marlalapocket




Sujet 11194 - Les templates en C++

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]



Introduction


Nous allons présenter la notion de template (patron en français). Les templates font parties des grands apports du C++ au langage C.

Jusqu'ici on passait en paramètre des fonctions des variables. Grâce au concept de template, il est possible de passer en paramètre des types et ainsi de définir des fonctions génériques. Mais le concept de template ne s'arrête pas aux fonctions, on peut aussi l'utiliser pour les classes et les structures.

Avantages


On appelle dans ce qui suit symbole indifféremment une fonction, une structure ou une classe. L'intérêt des templates réside dans sa :
- généricité : du moment que le type paramètre fournit tout ce qui est utilisé dans le symbole template, on peut passer n'importe quel type
- simplicité : on ne code qu'un symbole quel que soit le(s) type(s) passé(s) en paramètre, ce qui rend le code d'autant plus facile à maintenir

Inconvénients


- Comme nous allons le voir par la suite, l'utilisation de template requiert quelques précautions d'usage (typename...)
- Le programme est plus long à compiler.

Quand utiliser des templates ?


L'usage des templates est particulièrement pertinent pour définir des containers, c'est-à-dire des structures qui servent à stocker une collection d'objets (une liste, un vecteur, un graphe...).

Les templates sont également adaptés pour définir des algorithmes génériques s'appliquant à une famille de classe. Par exemple, il est intéressant de coder un algorithme de plus courts chemins indépendamment de la structure de graphe. On retiendra que l'usage de foncteurs peut s'avérer pertinent pour accéder aux poids installés sur les arcs du graphe dans ce cas-là. La classe de graphe passée en paramètre doit alors vérifier un certain nombre de prérequis pour que l'algorithme puisse être appliqué. Si ce n'est pas le cas le programme ne compilera pas...

Que dois-je mettre dans les .hpp et dans les .cpp ?


Le C++ étant un langage compilé, on ne peut évidemment pas imaginer de compiler pour un symbole donné toutes ses versions. Par exemple, si on définit une classe template de vecteur, que je vais noter my_vector<T>, on ne peut pas imaginer de compiler my_vector<int>, my_vector<char>, my_vector<my_struct> ... sachant qu'il y a une infinité de types pouvant être passés en paramètres.

C'est pourquoi une classe template est (re)compilée pour chaque type d'instance présent dans le programme. Ainsi, si dans mon programme j'utilise my_vector<int> et my_vector<char>, seules ces versions seront compilées. Si dans un autre programme j'utilise my_vector<my_vector<double> >, je compilerai juste my_vector<float> et my_vector<my_vector<float> >. Ce qu'il faut retenir, c'est que le compilateur se débrouille tout seul pour savoir quelles versions il doit compiler.

De tout ce qui vient d'être dit, on déduit qu'un symbole template ne peut être "précompilé", car il est compilé pour chaque instance. On retiendra donc la règle suivante :

Si un symbole template est utilisé uniquement dans un .cpp (fichier source), il peut être implémenté dans ce .cpp
Sinon, il doit être implémenté dans un .hpp (header).


Remarque :

Il peut arriver qu'un fichier contenant une classe template porte une extension différente des headers (.h ou .hpp), par exemple .tcc. Ceci n'est qu'une convention de notations. Personnellement, je les considère comme des headers à part entière.

Convention de notations


Les paramètres templates sont généralement notés avec une majuscule (tandis que les autres types sont généralement écrits en minucules). Dans la pratique, on fait comme on veut. Personnellement je les note précédés d'un T (par exemple Tgraph pour désigner un paramètre template représentant un graphe).

Ceci peut sembler anodin mais nous verrons que c'est assez pratique pour s'y retrouver avec les typenames et que ça rend le code plus lisible ;-)

Quelques templates célèbres


STL


La STL (Standard Template Library) est livrée de base avec les compilateurs C++. Cette librairie fournit un jeu de containers génériques, notamment :
std::vector : les vecteurs (tableau d'éléments de type T adjacents en mémoire), accès en O(1)
std::set : les ensembles d'éléments de type T sans doublon et ordonnés selon l'opérateur <, accès en O(log(n))
std::list : les listes chaînées (accès en O(n), insertion en début et fin de liste en O(1))

On pourra se faire une idée du contenu de la STL ICI

BGL


La BGL (Boost Graph Library) fournit des classes de graphe génériques et les algorithmes qui vont avec (algorithmes de plus court chemin, algorithme de flot, parcours de graphe, ...). On pourra se faire une idée du contenu de la BGL ICI

Celle-ci n'est pas présente par défaut mais s'installe facilement. Par exemple sous debian :
aptitude install libboost-graph-dev

Premiers pas


Pour manipuler des templates, on a besoin de quatre choses :

- le mot clé typename : il indique que le type qui suit est abstrait (paramètre template ou dépend d'un paramètre template) et qu'il ne doit être pris en compte qu'au moment où l'on instancie.

- le mot clé template : il indique que le symbole (structure, classe, fonction) qui va suivre prend des paramètres templates. On écrit directement après le mot clé template les paramètres templates (précédés du mot clé typename, struct, class, ou type de base selon le type de paramètre template attendu) entre chevrons, suivis du symbole écrit normalement. Attention à bien séparer les chevrons (fermants) afin qu'il ne soient pas confondus avec l'opérateur >> .

Dans cet exemple nous allons voir
- comment coder une classe template
- comment coder une fonction template
- comment coder un opérateur template
Dans cet exemple les symboles paramètres prennent juste un paramètre template, mais la démarche reste similaire avec plusieurs paramètres templates.

Exemple :
template <typename T1, typename T2, ... >
type_retour_t ma_fonction(param1_t p1,param2_t p2, ...){
...
}

template <typename T1, typename T2, ... >
class ma_classe_t{
...
};

template <typename T1, typename T2, ... >
struct ma_struct_t{
...
};


- l'opérateur :: : il permet d'accéder aux champs (en particulier les types) et les méthodes statiques d'une classe ou d'une structure. Il n'est pas spécifique aux templates (il s'applique aux classes et structures en général et aux namespaces). On peut le voir un peu comme le '/' des répertoires. Ainsi std::vector<int>::const_iterator signifie que j'accède au type const_iterator, stocké dans la classe vector<int>, elle-même codée dans le namespace std.

Nous allons définir notre propre classe de vecteur afin d'illustrer ce qui vient d'être dit. Évidemment en pratique on utilisera directement la classe std::vector de la STL...
#include <iostream>
//----------------------- début my_vector_t.hpp
#include <cstdlib>
#include <ostream>

// Une classe template prenant un paramètre
template <typename T>
class my_vector_t{
	protected:
		unsigned taille; // stocke la taille du vecteur
		T *data;	// stocke les composantes du vecteur
	public:
		// Le constructeur
		my_vector_t(unsigned taille0 = 0,const T & x0 = T()):
			taille(taille0),
			data((T *)malloc(sizeof(T)*taille0))
		{
			for(unsigned i=0;i<taille;++i) data[i] = x0;
		}

		// Le destructeur
		~my_vector_t(){
			free(data);
		}

		// Retourne la taille du vecteur
		inline unsigned size() const{
			return taille;
		}

		// Un accesseur en lecture seule sur la ième case du vecteur
		inline const T & operator[](unsigned i) const{
			if(i >= size()) throw;
			return data[i];
		}

		// Un accesseur en lecture écriture sur la ième case du vecteur
		inline T & operator[](unsigned i){
			if(i >= size()) throw;
			return data[i];
		}
};

// Un opérateur template
template <typename T>
std::ostream & operator<<(std::ostream & out,const my_vector_t<T> & v){
	unsigned n = v.size();
	out << "[ ";
	for(unsigned i=0;i<n;++i) out << v[i] << ' '; 
	out << ']';	
	return out;
}

//----------------------- fin my_vector_t.hpp

// Une fonction template
template <typename T>
void ecrire(const my_vector_t<T> & v){
	unsigned n = v.size();
	std::cout << "[ ";
	for(unsigned i=0;i<n;++i) std::cout << v[i] << ' '; 
	std::cout << ']';
}

int main(){
	my_vector_t<int> v(5); // un vecteur de 5 entiers
	v[0] = 6; v[1] = 2; v[2] = 3; v[3] = 4; v[4] = 8;
	ecrire<int>(v); // appel de la fonction template
	std::cout << std::endl;
	ecrire(v); // appel implicite de ecrire<int>
	std::cout << std::endl;
	std::cout << v << std::endl; // appel de l'opérateur template
	return 0;
}

À l'exécution :
[ 6 2 3 4 8 ]
[ 6 2 3 4 8 ]
[ 6 2 3 4 8 ]

Tout ce qui est entre "début classe my_vector_t" et "fin classe my_vector_t" pourrait être déplacé dans un header (par exemple my_vector.hpp) puis être inclus par le programme principal.

Spécifications de templates


Rien n'empêche d'implémenter spécifiquement un symbole pour un jeu de paramètre template. Notons que rien n'oblige à spécifier tous les paramètres template. Dans ce cas le prototype le "moins template" est privilégié pour lever les ambiguïtés. Si on repart de l'exemple précédent :
#include "my_vector.hpp"

// Une spécification de template
void ecrire(const my_vector_t<int> & v){
    unsigned n = v.size();
    std::cout << "{ ";
    for(unsigned i=0;i<n;++i) std::cout << v[i] << ' ';
    std::cout << '}';
}

int main(){
    my_vector_t<int> v(5); // un vecteur de 5 entiers
    v[0] = 6; v[1] = 2; v[2] = 3; v[3] = 4; v[4] = 8;
    ecrire<int>(v); // appel de la fonction template
    std::cout << std::endl;
    ecrire(v); // appel à ecrire (prévaut sur l'appel implicite de ecrire<int>)
    std::cout << std::endl;
    std::cout << v << std::endl; // appel de l'opérateur template
    return 0;
}

À l'exécution :
[ 6 2 3 4 8 ]
{ 6 2 3 4 8 }
[ 6 2 3 4 8 ]

Template par défaut


Il est également possible de préciser un paramètre template par défaut de la même façon que pour un paramètre de fonction.

Par exemple :
template<typename T = int>
class my_vector_t{
   //...
};

int main(){
  my_vector<> v; // un vecteur d'int
  return 0;
}

Quelques exemples célèbres de templates par défaut : dans la STL le foncteur de comparaison, utilisé dans les std::set, est initialisé par défaut par std::less (foncteur de comparaison basé sur <). Ainsi on peut écrire indifféremment :
std::set<int> s;
std::set<int,std::less<int> > s_;

Récupérer des paramètres templates, des types et des méthodes statiques d'une classe template


Une bonne affaire avec les classes templates, c'est de mettre des typedef (en public) pour pouvoir récupérer facilement les paramètres templates. Exemple : j'ai une classe c1<T> et je veux récupérer le type T. Ceci va être possible grâce à typedef et typename.
template <typename T>
struct my_class_t{
	typedef T data_t;
};

int main(){
	typedef my_vector_t<int>::data_t data_t; // Ceci est un entier
}

Cependant on ne peut appliquer l'opérateur :: que si un membre de gauche n'est pas un type abstrait (ie dépendant d'un type template pas encore évalué). Par exemple, si je veux manipuler le typedef "const_iterator" de la classe std::vector fournie par la STL, si les paramètres templates de std::vector ne sont pas affectés le programme refusera de compiler :
void ecrire(const std::vector<int> & v){
	std::vector<int>::const_iterator vit(v.begin(),vend(v.end());
	for(;vit!=vend;++vit) std::cout << *vit << ' ';
}

template <typename T>
void ecrire(const std::vector<int> & v){
	std::vector<T>::const_iterator vit(v.begin(),vend(v.end()); // ERREUR !
	for(;vit!=vend;++vit) std::cout << *vit << ' ';
}

Ici le std::vector<T> est situé à gauche d'un :: et dépend d'un paramètre template. C'est la que typename entre en jeu.
template <typename T>
void ecrire(const std::vector<int> & v){
	typename std::vector<T>::const_iterator vit(v.begin(),vend(v.end());
	for(;vit!=vend;++vit) std::cout << *vit << ' ';
}

On retiendra que quand le type à gauche d'un :: dépend d'un paramètre template, il doit être précédé d'un typename. Etant donné que les types deviennent rapidement lourds à manipuler, il est judicieux de faire des typedef. Sur un autre exemple, plus compliqué, ça donne par exemple:
typedef typename std::vector<typename std::vector<T>::const_iterator>::const_iterator mon_type_bizarre_t

Templates récursifs


Il est possible de définir des templates récursifs (si si !). Un exemple :
#include <iostream>

template <int N>
int fact(){
	return N*fact<N-1>();
}

template <>
int fact<0>(){
	return 1;
}

int main(){
	std::cout << fact<5>() << std::endl;
	return 0;
}

Ici l'intérêt est assez modéré car concrètement on compile fact<5>, fact<4>... fact<0>, c'est vraiment juste pour donner un exemple simple de template récursif.

Quel intérêt d'un template récursif du coup ? Eh bien dans boost, le template récursif permet d'implémenter des tuples génériques ! Pour les petits curieux ça se tient dans /usr/include/boost/tuple/detail/tuple_basic.hpp, donc je ne m'étendrai pas davantage ;-)

Tester des valeurs de type template


Il est possible avec boost de vérifier si un type template correspond à un type attendu et de bloquer la compilation le cas échéant. Étant donné que ça utilise la librairie boost, je donne juste un bref exemple :
#include <boost/type_traits/is_same.hpp>
#include <boost/static_assert.hpp>

template <typename T>
struct ma_struct_qui_ne_doit_compiler_que_si_T_est_int{
	BOOST_STATIC_ASSERT((boost::is_same<T,int>::value));
	//...
};

Liens utiles


Cours sur le C++, la partie sur les templates est très bien expliquée et très détaillée :
http://www.nawouak.net/?doc=course.cpp+ch=templates+lang=fr

Présentation de la STL (Standard Template Library) : elle propose de nombreux containers templates) :
http://www.sgi.com/tech/stl/
http://www.commentcamarche.net/faq/sujet 11255 introduction a la stl en c standard template library

Présentation de la BGL (Boost Graph Library) : une sous-partie de la librairie boost, elle propose des classes et des algorithmes de graphes templates) :
http://www.boost.org/doc/libs/1_35_0/libs/graph/doc/table_of_contents.html

Lire la suite

Les inlines en C++ »
Publié par mamiemando - Dernière mise à jour le 13 novembre 2009 à 13:15 par marlalapocket




Sujet 11250 - Les inlines en C++

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]



Signification


Le mot-clé inline est utilisé en C++ et s'applique à une fonction. Il indique au compilateur que chaque appel à la fonction inline devra être remplacé par le corps de cette fonction. Afin de générer un exécutable de taille raisonnable, il est donc en pratique utilisé pour des fonctions "courtes" en terme de nombre d'instructions.

Le mot clé inline présente l'avantage d'accélérer un programme si celui-ci fait régulièrement appel à la fonction inline. Il permet de condenser sensiblement le code, en particulier pour les accesseurs d'une classe. Un accesseur de classe est typiquement une fonction d'une ligne

Exemple 1


main.cpp
#include <iostream>
inline void ma_fonction(){
  std::cout << "j'aime les tapirs" << std::endl;
}

class ma_structure_t{
  protected:
    int x;
  public:
    ma_structure_t(int x0):x(x0){}
    inline int get_x() const{
      return x;
    }
};

int main(){
  ma_fonction();
  ma_structure_t s(7);
  std::cout << s.get_x() << std::endl;
  return 0;
}

Inline et headers


Le inline permet de déclarer et d'implémenter des fonctions directement dans un header (.hpp) sans risque de multi-définition. En effet le symbole de la fonction inline n'apparaît jamais explicitement au moment du linkage puisque les appels à cette fonction ont tous été remplacés. Rappelons que si une fonction n'est pas inline, est implémentée dans un header, et que ce header est inclus à plusieurs endroits, le compilateur retournera une erreur de définition multiple.

Rappelons qu'hormis les fonctions templates, seules les fonctions inline peuvent être implémentées dans le header. Une fonction template peut tout à fait être inline.

Si l'on veut coder une fonction inline déclarée dans un header (.hpp) mais implémentée dans un fichier source (.cpp) seul le prototype du header doit comporter le mot clé inline.

Exemple 2


fichier.hpp
inline void f();

fichier.cpp :
#include <iostream>
#include "fichier.hpp"

void f(){
  std::cout << "plop !" << std::endl;
}

Lire la suite

Introduction à la STL en C++ (standard template library) »
Publié par mamiemando - Dernière mise à jour le 12 novembre 2009 à 12:48 par marlalapocket




Sujet 11255 - Introduction à la STL en C++ (standard template library)

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]



Introduction


L'une des difficultés du langage C consiste à implémenter des containers (vecteurs, listes chaînées, ensembles ordonnés) génériques, simple d'utilisation et efficaces. Afin d'être générique on est souvent contraint de faire appel à des pointeurs génériques (void *) et aux opérateurs de cast. De plus, quand ces containers sont imbriqués les uns dans les autres (par exemple un ensemble de vecteurs) le code devient rapidement complexe à utiliser.

Afin de répondre à ce besoin, la STL (standard template library) implémente un grand nombre de classe template décrivant des containers génériques pour le langage C++. La STL fournit en plus des algorithmes permettent de manipuler aisément ces containers (pour les initialiser, rechercher des valeurs, ...). La STL introduit également le concept d'iterator qui permet de parcourir très facilement un container en s'affranchissant complètement de la manière dont il est implémenté.

Les concepts développés dans la STL sont aujourd'hui étendus par la librairie boost qui permet de manipuler des structures de graphe générique.

L'objectif de cette introduction n'est pas de faire un inventaire exhaustif des possibilités offertes par la STL, mais de donner des exemples courants d'utilisation. On pourra trouver un aperçu très détaillé des classes de la STL ici :
http://www.sgi.com/tech/stl/table_of_contents.html

Par la suite, on présentera les classes de la STL avec des paramètres templates "simples". En pratique il faut voir les classes de la STL comme des legos (ou des duplos pour ne pas faire de jaloux) pouvant être imbriqués les uns dans les autres. Ainsi, on pourra tout à fait manipuler un vector d'ensemble de liste d'entier (std::vector<std::set<std::list<int> > >).

Principales classes de la STL


Il est particulièrement important de choisir une classe fournie par la STL cohérente avec son besoin. Certaines structures sont effectivement plus ou moins efficaces pour accéder à une mémoire ou en terme de réallocation mémoire lorsqu'elles deviennent importantes. L'enjeu de cette partie consiste à présenter les avantages et les inconvénients de chacune d'elle.

Il est au préalable nécessaire d'avoir quelques notions de complexité. Soit n la taille d'un container. Un algorithme est dit linéaire (en O(n)) si son temps de calcul est proportionnel à n. De la même façon, un algorithme peut être instantané (O(1)), logarithmique O(log(n)), polynomial O(n^k), exponentiel O(e(n)), etc...

Pour résumer, voici la liste des complexités dans l'ordre croissant des proportions de temps de calcul (plus on descend, plus c'est violent):

Ici on s'intéressera principalement à la complexité pour l'accès (recherche) à une donnée stockée dans un container et à la complexité pour insérer une donnée.

std::pair<T1,T2>


Une paire est une structure contenant deux éléments éventuellement de types différents. Certains algorithmes de la STL (find par exemple) retournent des paires (position de l'élément trouvé et un booléen indiquant s'il a été trouvé).

Complexité : insertion et accès en O(1)
#include <pair> // en pratique cet include est sous-entendu car implicitement fait lorsqu'on utilise <vector>, <set> ...
#include <iostream>
#include <string>

int main(){
  std::pair<int,std::string> p = std::make_pair(5,"pouet");
  std::cout << p.first << ' ' << p.second << std::endl;
  return 0;
}

std::list<T,...>


La classe list fournit une structure générique de listes chaînées pouvant éventuellement contenir des doublons.

Complexité
  • Insertion (en début ou fin de liste) : O(1)
  • Recherche : O(n) en général, O(1) pour le premier et le dernier maillon


Cet exemple montre comment insérer les valeurs 4,5,4,1 dans une liste et comment afficher son contenu :
#include <list>
#include <iostream>

int main(){
  std::list<int> ma_liste;
  ma_liste.push_back(4);
  ma_liste.push_back(5);
  ma_liste.push_back(4);
  ma_liste.push_back(1);
  std::list<int>::const_iterator
    lit (ma_liste.begin()),
    lend(ma_liste.end());
  for(;lit!=lend;++lit) std::cout << *lit << ' ';
  std::cout << std::endl; 
  return 0;
}

std::vector<T,...>


La classe vector est proche du tableau du C. Tous les éléments contenus dans le vector sont adjacents en mémoire, ce qui permet d'accéder immédiatement à n'importe quel élément. L'avantage du vector comparé au tableau du C est sa faculté à se réallouer automatiquement en cas de besoin lors d'un push_back par exemple. Cependant comme un tableau classique une case n'est accessible par l'opérateur [ ] que si elle a été allouée au préalable (sinon une erreur de segmentation se déclenche).

Complexité :
  • Accès O(1)
  • Insertion : O(n) en début de vector (pop_back), O(1) en fin de vector (push_back). Dans les deux cas des réallocations peuvent survenir.


ll ne faut pas perdre de vue qu'une réallocation mémoire est coûteuse en terme de temps de calcul. Ainsi si la taille d'un vecteur est connue par avance, il faut autant que possible le créer directement à cette taille.

Exemple :
#include <vector>
#include <iostream>

int main(){
  std::vector<int> mon_vecteur;
  mon_vecteur.push_back(4);
  mon_vecteur.push_back(2);
  mon_vecteur.push_back(5);
  // pour parcourir un vector (même const) on peut utiliser les iterators ou les index
  for(std::size_t i=0;i<mon_vecteur.size();++i) std::cout << mon_vecteur[i] << ' '
  std::cout << std::endl;

  std::vector<int> mon_vecteur(5,69); // crée le vecteur 69,69,69,69,69
  v[0] = 5;
  v[1] = 3;
  v[2] = 7;
  v[3] = 4;
  v[4] = 8;
  return 0;
}

std::set<T,...>


La classe set permet de décrire un ensemble ordonné et sans doublons d'éléments. Il faut a priori passer cet ordre en paramètre template (plus précisément d'un foncteur). Par défaut, le foncteur std::less (basé sur l'opérateur <) est utilisé, ce qui revient à avoir un ensemble d'éléments classés du plus petit au plus grand. Concrètement il suffit donc d'implémenter l'opérateur < d'une classe ou d'une structure de type T pour pouvoir définir un std::set<T>. De plus, le type T doit disposer d'un constructeur vide T().

Complexité :
  • O(log(n)) pour la recherche et l'insertion. En effet, la structure std::set tire partie de l'ordre sur T pour construire une structure d'arbre rouge noir, ce qui permet une recherche logarithmique d'un élément.

#include <set>
#include <iostream>

int main(){
  std::set<int> s; // équivaut à std::set<int,std::less<int> >
  s.insert(2); // s contient 2
  s.insert(5); // s contient 2 5
  s.insert(2); // le doublon n'est pas inséré
  s.insert(1); // s contient 1 2 5
  std::set<int>::const_iterator
    sit (s.begin()),
    send(s.end());
  for(;sit!=send;++sit) std::cout << *sit << ' ';
  std::cout << std::endl;
  return 0;
}


Attention : le fait de supprimer ou ajouter un élément dans un std::set remet invalide ses iterators. Il ne faut pas modifier un std::set dans une boucle for basée sur ses iterators

std::map<K,T,...>


Une map permet d'associer une clé (identifiant) à une donnée (table associative).

La map prend au moins deux paramètres templates :

À l'image du std::set, le type K doit être ordonné (cet ordre peut être passé en 3e paramètre template, std::less<K> par défaut) et . Le type T impose juste d'avoir un constructeur vide.

Complexité :
  • O(log(n)) pour la recherche et l'insertion. En effet, la structure std::map tire partie de l'ordre sur T pour construire une structure d'arbre rouge noir, ce qui permet une recherche logarithmique d'un élément.


Attention : le fait de supprimer ou ajouter un élément dans un std::map remet invalide ses iterators. Il ne faut pas modifier un std::map dans une boucle for basée sur ses iterators

Attention : le fait d'accéder à une clé via l'opérateur [ ] insère cette clé (avec la donnée T()) dans la map. Ainsi l'opérateur [ ] n'est pas adapté pour vérifier si une clé est présente dans la map, il faut utiliser la méthode find. De plus, il ne garantit pas la constance de la map (à cause des insertions potentielles) et ne peut donc pas être utilisé sur des const std::map.

Exemple :
#include <map>
#include <string>
#include <iostream>

int main(){
  std::map<std::string,unsigned> map_mois_idx;
  map_mois_idx["janvier"] = 1;
  map_mois_idx["février"] = 2;
  //...
  std::map<std::string,unsigned>::const_iterator
    mit (map_mois_idx.begin()),
    mend(map_mois_idx.end());
  for(;mit!=mend;++mit) std::cout << mit->first << '\t' << mit->second << std::endl;
  return 0;
}

Les iterators


Nous avons vu dans la section précédente que les iterators permettaient de parcourir aisément une structure de la STL d'un bout à l'autre. Un iterator rappelle un peu la notion de pointeur, mais ce n'est pas une adresse. Nous allons à présent voir quatre iterators classiques de la STL.

Ils sont définis pour toutes les classes de la STL évoquées ci-dessus (hormis bien sûr std::pair)

iterator et const_iterator


Un iterator (et un const_iterator) permet de parcourir un container du début à la fin. Un const_iterator, contrairement à un iterator, donne un accès uniquement en lecture à l'élément "pointé". Ainsi, un parcours avec des const_iterator maintient la constance du container. C'est pourquoi un container "const" peut être parcouru par des const_iterators mais pas des iterators.

De manière générale, quand on a le choix entre des iterators ou des const_iterator, il faut toujours privilégier les const_iterator car ils rendent la section de code à laquelle ils servent plus générique (applicable aux containers const ou non const).

Exemple :
#include <list>
#include <iostream>

const std::list<int> creer_liste(){
  std::list<int> l;
  l.push_back(3);
  l.push_back(4);
  return l;
}

int main(){
  const std::list<int> ma_liste(creer_liste());
  // ne compile pas car l est const
  // std::list<int>::iterator
  //  lit1 (l.begin()),
  //  lend(l.end());
  //for(;lit1!=lend1;++lit1) std::cout << *lit1 << ' ';
  //std::cout << std::endl;
  std::list<int>::const_iterator
    lit2 (l.begin()),
    lend2(l.end());
  for(;lit2!=lend2;++lit2) std::cout << *lit2 << ' ';
  std::cout << std::endl; 
  return 0;
}

reverse_iterator et const_reverse_iterator


Le principe des reverse_iterators et const_reverse_iterator est similaire aux iterators et const_iterator à ceci prês que le parcours se fait dans le sens opposé.

On utilise alors :
#include <set>
#include <iostream>

int main(){
  std::set<unsigned> s;
  s.insert(1); // s = {1}
  s.insert(4); // s = {1, 4}
  s.insert(3); // s = {1, 3, 4}
  std::set<unsigned>::const_reverse_iterator
    sit (s.rbegin()),
    send(s.rend());
  for(;sit!=send;++sit) std::cout << *sit << std::endl;
  return 0;
}

... affiche :
4
3
1

Les algorithmes


Si le concept d'iterator est maîtrisé, il suffit de se référer à
http://www.sgi.com/tech/stl/table_of_contents.html

On retiendra quelques méthodes bien pratiques comme find (pour les std::set et std::map) pour chercher un élément, std:fill pour (ré)initialiser un std::vector, des algorithmes de tri, de recherche de min ou de max.

Lire la suite

Entrées/sorties : Les flux en C++ »
Publié par mamiemando - Dernière mise à jour le 1 novembre 2009 à 15:14 par marlalapocket




Sujet 18530 - Entrées/sorties : Les flux en C++

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]

« PrécédentSuivant »
Sommaire

Entrées/sorties : les flux en C++




Pour pouvoir sauvegarder des données après fermeture de vos programmes, vous devez écrire ces données dans des fichiers. Cette astuce vous montre comment faire.

Pour ouvrir un fichier, que ce soit en lecture ou en écriture, il faut déclarer une instance des objets ofstream et/ou ifstream. Pour cela, pensez à inclure "fstream".

Attention : le fichier utilisé pour les exemples s'appelle "donnees.txt". Rien ne vous empêche de l'appeler comme vous voulez. Cela ne change en rien le résultat.

Ouvrir un fichier en lecture


Pour ouvrir un fichier en lecture, l'objet nécessaire est "ifstream", puis il faut fermer ce flux avec
la fonction membre close().

Ouverture du fichier "donnees.txt" en lecture


#include <iostream> 
#include <fstream> 

using namespace std; 

int main() 
{ 

    ifstream fichier("donnees.txt"); 

    fichier.close();                     //fermeture du flux 

    return 0; 
} 


Ce code ne fait rien en apparence, mais en vérité, il ouvre le fichier "donnees.txt" en lecture.
On instancie un objet de type ifstream en lui donnant en paramètre le nom de notre fichier.
Implicitement, cela appelle le constructeur de la classe ifstream qui va ouvrir ce fichier.

Lecture du contenu de "donnees.txt"


#include <iostream> 
#include <fstream> 

using namespace std; 

int main() 
{ 

    ifstream fichier("donnees.txt"); 
    char caractere; 

    while(fichier.get(caractere))  
        cout << caractere; 

    cout << endl << endl; 
    fichier.close();                     //fermeture du flux 

    return 0; 
} 

Ouvrir un fichier en écriture


Pour ouvrir un fichier en lecture, l'objet nécessaire est "ofstream", puis il faut fermer ce flux avec la fonction membre close().

Ouverture du fichier "donnees.txt" en écriture


#include <iostream> 
#include <fstream> 

using namespace std; 

int main() 
{ 

    ofstream fichier("donnees.txt"); 

    fichier.close();                     //fermeture du flux 

    return 0; 
} 


Ce code ne fait rien en apparence, mais en vérité, il ouvre le fichier "donnees.txt" en écriture.

Écrire dans "donnees.txt"


#include <iostream> 
#include <fstream> 

using namespace std; 

int main() 
{ 

    char bonjour[10] = "bonjour !" 
    ofstream fichier("donnees.txt"); 

    fichier << bonjour;              //écriture de la chaine bonjour dans donnees.txt 
    fichier.close();                     //fermeture du flux 

    return 0; 
} 


Vous avez certainement déjà rencontré des codes du type:

cout << "chaine à afficher";

Et vous aurez remarqué son utilisation dans le code ci-dessus.
Rencontrer ainsi l'opérateur << peut paraître troublant puisque qu'il est normalement destiné à déplacer
des bits.

En fait cet opérateur est utilisé sur les objets de gestion de flux (ifstream, ofstream, etc..) pour écrire ou lire dans le flux. Celà est programmé par le biais d'une surcharge d'opérateur. Sans besoin de se perdre dans des détails hors sujets, il est utile de noter que surcharger un opérateur signifie "pour tel opérateur (+, -, <<, etc...) appliqué à tel type d'objet (ifstream, ofstream), je veux qu'il se passe tel truc".

Ici ifstream utilise >> pour la lecture depuis le flux, et ofstream utilise << pour l'écriture vers le flux. Ces opérateurs ont été choisis dans les bibliothèques standards car ils sont très visuels, et expriment bien le mouvement et sa direction.

Différents types d'ouverture de flux


Par défaut, ofstream crée automatiquement un fichier si celui précisé n'existe pas. Mais vous pouvez rajouter des paramètres à ce constructeur, pour en changer le comportement :
Publié par HACKER 712 - Dernière mise à jour le 23 juin 2011 à 17:06 par @ntoine
Ce document intitulé « Entrées/sorties : Les flux en C++ » issu de CommentCaMarche.net (CCM) (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.




Sujet 18546 - La saisie sécurisée en C++

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]


La saisie sécurisée en C++










Dans vos programmes, vous avez inévitablement besoin de demander des informations à l'utilisateur. Ces informations sont recueillies, en C++ grâce à l'objet "cin". Si vous ne sécurisez pas ces entrées,
alors votre programme est susceptible d'être victime d'un "buffer overflow".

En effet, si le nombre de caractères entrés dépasse la taille du buffer initialement prévue, alors les dernières données écrasent d'autres données sur la pile, et affectent de fausses données aux registres ESP et EBP.

Sans rentrer dans les détails, le cracker va pouvoir injecter un "shell code" dans votre programme, et
ainsi en faire ce qu'il veut.

1. Sécuriser ses saisies grâce à la méthode "get()"


Vous pouvez sécuriser ces saisies de différentes façons. Par exemple, utiliser la méthode membre "get()" de l'objet "cin" peut être une solution.

#include <iostream>

int main() {

char texte[100];
cin.get(texte, 100);  //premier paramètre : la où va la saisie  second : taille max de saisie

return 0;

}


Cet exemple illustre l'utilisation de cin.get.
Si le texte entré dépasse la taille allouée, les caractères en trop seront ignorés.

2. Sécuriser ses saisies grâce à la méthode "getline()"


Cette méthode fonctionne comme get(), mais elle supprime le caractère de fin de tampon.
En effet, pour valider un texte, l'utilisateur appuie sur la touche "Entrée", qui correspond au caractère '\n'.
getline() supprime ce caractère de fin, ce qui est pratique si l'on ne veut pas retourner à la ligne
après une saisie.

3. Mince, ça marche pas...


Voici un code qui ne fonctionne pas correctement :

#include <iostream>

using namespace std;

int main() {

char entree[100];
int choix;
cout << "Entrez un nombre :" ;
cin >> choix;
cout << "Entrez un texte : ";
cin.get(texte, 100);
cout << "Saisies terminées !";
return 0;
}
En effet, la deuxième saisie n'est pas exécutée, et le message "Saisies terminées !" s'affiche, alors que nous n'avons pas été invité à rentrer du texte.

Comment palier à ce problème ?

Il faut utiliser la méthode "ignore()" de l'objet "cin".

4. La méthode "ignore()"


Cette méthode permet d'ignorer certains caractères d'une chaîne. Voici son utilisation dans le code source précédant :

#include <iostream>

using namespace std;

int main() {

char entree[100];
int choix;
cout << "Entrez un nombre :" ;
cin >> choix;
cout << "Entrez un texte : ";
cin.ignore(1, '\n');    //ignore le caractère d'entrée, qui validait auparavant la saisie.
cin.get(texte, 100);
cout << "Saisies terminées !";
return 0;
}
Avec cette méthode, le caractère d'entrée est ignoré, et le problème résolu.

Lire la suite

Les fonctions en C++ : surcharge et paramètres par défaut. »
Publié par HACKER 712 - Dernière mise à jour le 30 juillet 2009 à 10:06 par HACKER 712




Sujet 18561 - Les fonctions en C++ : surcharge et paramètres par défaut.

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]

« PrécédentSuivant »
Sommaire

Les fonctions en C++ : surcharge et paramètres par défaut.



En C++, une même fonction ou méthode de classe peut être surchargée, et avoir ce que l'on appelle des paramètres par défauts. Cette qualité est aussi valable pour les constructeurs de classes.

La surcharge de fonctions et méthodes de classes, dont le constructeur



Lorsqu'on surcharge une fonction, on la déclare deux ou plus de fois, en gardant le même nom, et
le même type de retour. Modifier le type de retour correspondrait alors au polymorphisme.

Exemple : Polygone.hpp

#include <iostream> 

using namespace std; 

class Polygone { 

public :  

Polygone(); 
Polygone(int nombreCote); 
Polygone(int nombreCote, bool regulier); 
Polygone(bool regulier); 

void setRegulier()  { cout << "Chouette ! Je suis regulier !" << endl; 
                      regulier = true; } 
                       
void setPolygone(int nbCote) { sonNombreCote = nbCote; } 

void setTaille(int tailleCote1) { cout << "Je connait la taille de mon premier coté : il mesure " << tailleCote1 << " cm." << endl; 
                                  saTailleCote1 = tailleCote1; } 
                                   
void setTaille(int tailleCote1, int tailleCote2)  
{ cout << "Je connait la taille de mon premier cote : il mesure " << tailleCote1 << " cm." << endl; 
  cout << "Je connait la taille de mon deuxieme cote : il mesure " << tailleCote2 << " cm." << endl; 

  saTailleCote1 = tailleCote1; 
  saTailleCote2 = tailleCote2; } 
                                                    
void setTaille(int tailleCote1, int tailleCote2, int tailleCote3)  
{ cout << "Je connait la taille de mon premier cote : il mesure " << tailleCote1 << " cm." << endl;                                                                 
  cout << "Je connait la taille de mon deuxieme cote : il mesure " << tailleCote2 << " cm." << endl; 
  cout << "Je connait la taille de mon troisieme cote : il mesure " << tailleCote3 << " cm." << endl; 

  saTailleCote1 = tailleCote1; 
  saTailleCote2 = tailleCote2; 
  saTailleCote3 = tailleCote3; } 

private : 

bool regulier; 
int sonNombreCote; 
int saTailleCote1; 
int saTailleCote2; 
int saTailleCote3; 

};


Cette exemple illustre la déclaration de la classe Polygone, qui utilise la surcharge du constructeur, et
de la fonction setTaille();

Voyons maintenant Polygone.cpp :

#include "polygone.hpp" 

using namespace std; 

Polygone::Polygone() { 

cout << endl << "Je suis un polygone, mais je ne sais pas si je suis regulier, ni combien j'ai de cotes, ni leur taille." << endl; 

} 

Polygone::Polygone(int nombreCote) { 

cout<< endl << "Je suis un polygone a " << nombreCote << " cotes mais je ne connait pas leur taille, et je ne sais pas si je suis regulier." << endl; 
sonNombreCote = nombreCote; 

} 

Polygone::Polygone(int nombreCote, bool regulier) { 

cout << endl << "Je suis un polygone a " << nombreCote << " cotes mais je ne connait pas leur taille."; 
if(regulier == true) 
     cout << " Je suis regulier !" << endl; 
else cout << " Je ne suis pas regulier." << endl; 
sonNombreCote = nombreCote; 
regulier = true; 

} 

Dans cet exemple, nous voyons l'implémentation de méthodes surchargées.
Cela se passe exactement de la même façon pour les fonctions non membres.

Vous pouvez tester ces deux fichiers avec ce fichier main.cpp :
#include "polygone.hpp" 
#include <iostream> 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    cout << endl << "Polygone simple : "; 
    Polygone polygoneSimple; 
     
    cout << endl << "Polygone a trois cotes : "; 
    Polygone polygoneATroisCotes(3); 
     
    cout << endl << "Polygone a 7 cotes : "; 
    Polygone polygoneASeptCotesRegulier(7, true); 
     
    cout << endl << "Polygone simple : "; 
    polygoneSimple.setRegulier(); 
     
    cout << endl << "Polygone a trois cotes : " << endl; 
    polygoneATroisCotes.setTaille(5, 7, 1); 
     
    cout << endl << "Polygone a 7 cotes : " << endl; 
    polygoneASeptCotesRegulier.setTaille(1, 90); 
     
    cout << endl << endl; 
    system("PAUSE"); 
    return EXIT_SUCCESS; 
}


Vous pouvez voir que le code est satisfaisant.

Utilisation des paramètres par défaut


Utiliser les paramètres par défauts peut s'avérer utile lorsque vous ne connaissez pas la valeur
de certains paramètres, alors qu'une fonction les attend.

Lors de la déclaration, les paramètres par défaut doivent se trouver au plus à droite.

exemple : redéfinition de la fonction setTaille :

void setTaille(int tailleCote1 = 5, int tailleCote2 = 8, int tailleCote3 = 4)  
{ cout << "Je connait la taille de mon premier cote : il mesure " << tailleCote1 << " cm." << endl;                                                                 
  cout << "Je connait la taille de mon deuxieme cote : il mesure " << tailleCote2 << " cm." << endl; 
  cout << "Je connait la taille de mon troisieme cote : il mesure " << tailleCote3 << " cm." << endl; 

  saTailleCote1 = tailleCote1; 
  saTailleCote2 = tailleCote2; 
  saTailleCote3 = tailleCote3; } 


Cela est d'une simplicité remarquable. Regardez maintenant l'utilisation avec main.cpp, lui aussi
changé pour plus d'utilité :

#include "polygone.hpp" 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    cout << endl << "Polygone simple : "; 
    Polygone polygoneSimple; 
     
    cout << endl << "Polygone a trois cotes : "; 
    Polygone polygoneATroisCotes(3); 
     
    cout << endl << "Polygone a 7 cotes : "; 
    Polygone polygoneASeptCotesRegulier(7, true); 
     
    cout << endl << "Polygone simple : "; 
    polygoneSimple.setTaille(); 
     
    cout << endl << "Polygone a trois cotes : " << endl; 
    polygoneATroisCotes.setTaille(1, 90); 
     
    cout << endl << "Polygone a 7 cotes : " << endl; 
    polygoneASeptCotesRegulier.setTaille(); 
     
    cout << endl << endl; 
    system("PAUSE"); 
    return EXIT_SUCCESS; 
}


Vous pouvez tester ce code.
Publié par HACKER 712 - Dernière mise à jour le 23 juin 2011 à 17:04 par @ntoine
Ce document intitulé « Les fonctions en C++ : surcharge et paramètres par défaut. » issu de CommentCaMarche.net (CCM) (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.




Sujet 18570 - [Windows] Compiler un projet Qt simplement

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]


[Windows] Compiler un projet Qt simplement


Cette astuce va vous montrer comment compiler simplement un projet Qt, grâce à un programme et un script batch.





1. La variable Path


Pour pouvoir utiliser l'invite de commandes et non le Qt Command Prompt, il va falloir modifier votre variable Path. En effet, il est impossible de lancer des programmes ou scripts depuis cette invite de commandes.

1.1 Accéder à cette variable


Cette variable est accessible depuis le Panneau de configuration.

Il faut que l'affichage de ce panneau de configuration soit en affichage classique, mais si par hasard, il est en affichage par catégories, comme ci-dessous, cliquez sur "basculer vers l'affichage classique".



Vous devriez arriver en affichage classique. Maintenant, vous avez un raccourci "système" : cliquez
dessus :



Dans la fenêtre qui s'ouvre, allez dans l'onglet "avancé" puis cliquez en bas sur "variables d'environnement". Une autre fenêtre s'ouvre, cliquez sur la variable intitulée "Path" dans le cadre du bas :


1.2 Modifier la variable Path


Encore une fenêtre s'ouvre, (cette fois-ci c'est la dernière !), et vous voyez le contenu de cette variable. Si vous ne l'avez pas modifiée au préalable, elle doit contenir ceci :

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem

Il va falloir rajouter deux chemins :


Exemple des chemins pour le pac Qt 2009.3 :

Pour rajouter les chemins du dessus, il faut compléter

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem

par un ';' qui signifie la fin d'un chemin, puis le chemin absolu de chaque dossier.

Exemple :

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Qt\2009.03\Qt\bin\;C:\Qt\2009.03\mingw\bin\;

tout cela sans espace, puis finir par un antislash suivi du ';'.

Cliquez autant de fois que nécessaire sur OK, votre variable est prête !

2. Le script batch


Maintenant que Path est correctement complétée, il va falloir créer le script batch qui va compiler notre projet.

voici le mien :
cd ../../../Dev-Cpp/Projets/Qt/VTest
qmake
mingw32-make release
mingw32-make clean         //supprime tous les fichiers inutiles.
exit



► analyse de ce code :

Placez ce script dans un des dossiers que vous avez indiqué à Path, il faut qu'il soit accessible partout, comme le programme que nous allons tout de suite créer.

3. Le programme


Nous allons faire le programme que nous appellerons depuis l'invite de commande. Ce programme est facultatif, nous pourrions très bien appeler notre script, mais je préfère appeler un programme. Sachez que c'est facultatif.

Ce programme est simple, en langage C :

#include <stdio.h>
#include <stdlib.h>

int main() {

printf("\n\tCompilateur de projets Qt\n\n");
system("start script.bat");
system("PAUSE");              //si vous compilez sous Dev-CPP
return 0;

}

Attention ! Pensez à vérifier que le chemin contenu dans "script.bat" mène quelque part !

Une fois compilé, placez le lui aussi dans un dossier connu par Path.

4. Compiler un projet


► Pour compiler, ouvrez un invite de commande, et tapez juste le nom :

Cela va compiler les fichiers à l'emplacement défini dans le script batch, et placer l'exécutable dans un sous-dossier release.

Lire la suite

Les variables en C++ »
Publié par HACKER 712 - Dernière mise à jour le 3 août 2009 à 00:16 par HACKER 712




Sujet 18602 - Les variables en C++

[ Voir ce sujet en ligne ] - [ Catégorie: Programmation - Langages - Langage C++ ]


Les variables en C++







Tout programme a besoin, à un moment où un autre, de stocker un nombre saisi par l'utilisateur, ou un caractère...
Ces données sont stockées dans des variables, et, en C++ comme dans tout autre langage, ces variables doivent avoir un type.

1. Les différents types de variables


Il existe plusieurs types de variables, et chacun correspond à une taille maximale de nombre, ou un caractère, ou encore une vérité. Plus le type peut contenir de gros nombres, plus il utilise de place en mémoire.

1.1 bool


Utilise généralement 1 octet en mémoire, valeurs : true ou false.

1.2 char


Utilise généralement 1 octet en mémoire, permet de stocker un caractère, valeurs : 256 caractères.

1.3 unsigned short int


Utilise généralement 2 octets en mémoire, valeurs : de 0 à 65 535.

1.4 short int


Utilise généralement 2 octets en mémoire, valeurs : de -32768 à 32 767.

1.5 unsigned long int


Utilise généralement 4 octets en mémoire, valeurs : de 0 à 4 294 967 295.

1.6 long int


Utilise généralement 4 octets en mémoire, valeurs : de -2 147 483 648 à 2 147 483 647.

1.7 int (16 bits)


Utilise généralement 2 octets en mémoire, valeurs : de -32 768 à 32 767.

1.8 int (32 bits)


Utilise généralement 4 octets en mémoire, valeurs : de -2 147 483 648 à 2 147 483 647.

1.9 unsigned int (16 bits)


Utilise généralement 2 octets en mémoire, valeurs : de 0 à 65 535.

1.10 unsigned int (32 bits)


Utilise généralement 2 octets en mémoire, valeurs : de 0 à 4 294 967 295.

1.11 double


Utilise généralement 8 octets en mémoire, valeurs : de 2.2e-308 à 3.4e-38.

1.12 float


Utilise généralement 4 octets en mémoire, valeurs : de 1.2e-308 à 3.4e-38.

Attention !
La taille des variables en mémoire peut varier d'un ordinateur à un autre ! Vous pouvez vérifier la taille des variables en utilisant la fonction siezof()

2. Déclaration et affectation de variables


2.1 Déclaration


Pour déclarer une variable, il suffit d'indiquer son type suivi de son nom. Il existe plusieurs conventions quant au nom des variables. Certains préfèrent séparer les plusieurs parties du nom par des '_', d'autres préfèrent écrire une majuscule pour les séparer. Exemple :

int recetteDuMois;

ou
int recette_du_mois;


L'important est de rester cohérent, et d'utiliser toujours la même convention pour ses propres programmes.

2.2 Affectation de valeur


Vous pouvez affecter une valeur à votre variable en même temps que sa déclaration :
int recetteDuMois = 12301;


Vous pouvez aussi réaliser plusieurs déclarations sur une même ligne, mais dans ce cas, toutes les variables de la ligne auront le même type.
int recetteDuMois = 12301, recetteDeLAnnee = 45644545;

3. Rebouclage des entiers signés


Mais que nous arrive-t-il si le type que nous avons choisi est trop petit ? Il se passe un rebouclage : si le type atteint sa valeur maximale, et qu'il est incrémenté, par exemple, sa valeur deviendra la valeur minimale acceptée par ce type.


unsigned short int nombre = 65535;
cout << nombre << endl;
nombre++;
cout << nombre << endl;


Si vous exécutez ce code, la deuxième ligne n'écrit pas 65536, mais 0.
Cela est pareil pour tous les types.

4. Rebouclage des entiers non signés


Pour les non signés, cela se passe pareil, une fois qu'il a atteint sa taille maximale, il revient à sa
valeur minimale.


short int nombre = 32767;
cout << nombre << endl;
nombre++;
cout << nombre << endl;


Si vous exécutez ce code, la deuxième ligne n'écrit pas 32768, mais -32768.
Publié par HACKER 712 - Dernière mise à jour le 29 octobre 2009 à 17:32 par marlalapocket





© Tous droits réservés 2010 Jean-François Pillou