|
---|
La gestion des entrée/sortie avec les périphériques standards que sont le clavier et l'écran se fond, en C++, au moyen de 2 classes, istream et ostream. Ces classes sont définies dans le fichier iostream, qu'il faut inclure au début du programme. Nous reviendrons en détail plus tard sur ce qu'est une classe ; mais le but de cette partie est juste de vous monter comment on peut lire le clavier et écrire sur l'écran.
Il peut être intéressant que l'utilisateur puisse lui-même entrer les valeurs de a et b. Pour cela l'ordinateur doit attendre que l'utilisateur entre les données au clavier et les valide par la touche entrée.
Pour stocker une information dans un programme, on utilise un objet qui est symbolisée par une lettre ou un mot. (Comme a,b,c précédemment). On choisit la classe de cet objet, selon la nature de l'information que l'on veut stocker (nombre entier, ou nombre à virgule, nombre complexe, ou série de lettres, ou matrice,...).
Voici les différentes classes de base qui existent en C++:
Déclaration: classe objet; | signification | Limites |
int a; | entier | voir remarque 1 |
float c; | réel |
1038
|
double d; | réel double précision |
10308
|
char e; | caractère | 256 caractères |
char *f; | chaîne de caractère |
Il existe 2 grandes catégories d'objets, les objets globaux et les objets locaux. Les objets globaux se déclarent juste après les includes. Ils sont connus dans tous les blocs qui les suivent. Ils sont, comme nous l'avons déjà dit, à éviter. Les objets locaux ne vivent que dans le bloc ({...}) dans lequel ils ont été déclarés ; ils sont détruits dès la sortie du bloc.
On peut déclarer un objet et l'initialiser en même temps, de différentes manières :
(il faut inclure <iomanip>)
On peut affecter un objet à partir d'un objet d'une autre classe, lorsque cela a un sens (i.e. lorsque ça a été prédéfini) . On parle de conversion ou cast. Ces cast peuvent avoir lieu sur des objets ou des pointeurs (la conversion d'un pointeur d'une classe en un pointeur d'une autre classe est très utilisée).
Certaines conversions peuvent être faites de façon implicite, c'est-à-dire sans spécifier la classe. Cependant ceci ne peut se faire sans risque dans n'importe quelle situation. Essayer le programme ci-dessous :
Une condition est quelque chose qui est vrai ou faux. En C++, comme en C, faux est synonyme de 0 et toutes les autres valeurs numériques sont vraies. Souvent, pour des raisons de lisibilité, on utilise 1 pour vrai. Voici la syntaxe générale qui permet d'obtenir une expression vraie ou fausse.
Signification | symbole |
supérieur à | > |
inférieur à | < |
supérieur ou égal à | >= |
inférieur ou égal à | <= |
égal à | == |
différent de | != |
Attention à la confusion possible: a==2 sert à comparer l'objet a avec 2 (et ne change pas la valeur de a). Par contre a=2 met la valeur 2 dans l'objet a.
Signification | symbole |
et logique | && |
ou logique | || |
non logique | ! |
Lorsqu'une condition est évaluée comme i 30
La syntaxe de la boucle for dans l'exemple ci-dessous signifie
: Initialise i à 10 ; si i 30
10 100
12 144
14 196
etc...
30 900
signifie : Fait le bloc d'instructions tant que la condition est vraie.
1 1
2 4
etc...
10 100
signifie : Tant que la condition est vraie fait le bloc d'instructions.
1 1
2 4
etc...
10 100
On peut forcer la sortie d'une boucle avant que la condition de fin
soit réalisée en utilisant la commande break.
Nous en verrons un exemple au paragraphe suivant
Ecrire un petit programme qui
Faire un programme qui au départ choisit un nombre au hasard entre
0 et 1000 (se servir de la section suivante), puis demande à l'utilisateur
de le trouver, en répondant "trop grand"
ou "trop petit" à chaque essai. L'utilisateur
a droit a un nombre limité d'essais.
Il faut ces 2 fichiers:
L'instruction switch est un peu analogue au if ;
elle peut être remplacée par une succession de if...else...
mais elle présente l'avantage d'être plus légère à écrire et à relire.
La syntaxe générale est de la forme :
Un tableau est suite d'objets de même la classe.
La façon de déclarer un tableau est la suivante:
Pour mettre la valeur 3 dans la case numéro 0, on écrit naturellement:
Complétez les signes ``?'' dans le programme suivant
La notion de pointeur est importante dans le langage C et C++. Elle
est réputée comme étant difficile et technique; nous espérons que
vous aurez néanmoins les idées claires après la lecture de cette section.
Nous introduisons rapidement la notion de pointeur, et montrons
comme exemple, son intérêt pour créer des tableaux de taille
variable au cours du programme.
Rappelons déjà ce qu'est un objet. Par exemple:
Bien sûr, cette case se trouve quelque part dans la mémoire de l'ordinateur.
Elle a un certain emplacement, caractérisée par son adresse, appelée
pointeur.
Il faut donc retenir que pointeur d'un objet
signifie adresse d'un objet
dans la mémoire de l'ordinateur.
On peut avoir accès à l'adresse de la ``case'' i en faisant:
Le signe &i
signifie l'adresse de l'objet i.
Donc la troisième ligne met dans p l'adresse de l'objet i.
Voici une représentation de la mémoire de l'ordinateur:
Pour afficher le contenu de l'objet i on a maintenant
deux possibilités:
Pour modifier le contenu de l'objet i on a maintenant
deux possibilités:
avant d'effectuer l'opération d'écriture: (*p)=5.3 ; il
faut être sur que l'adresse du pointeur correspond à une ``case''
existante. Vous avez donc compris que avant d'écrire, il faut prendre
le soin de réserver de la place mémoire.
En fait vous avez déjà utilisé des pointeurs... En effet lors de la
déclaration du tableau tab
Il est possible de déclarer des tableaux de plusieurs dimensions :
Il est possible de réserver n cases mémoires, par l'instruction:
A près cela, pValeur pointe sur la première
case réservée : on peut légitimement écrire dans cette première case
par l'instruction:
A la fin de l'utilisation, n'oubliez pas
de libérer l'emplacement mémoire par l'instruction:
qui déclare chaine comme étant un pointeur sur un caractère,
pouvant pointer sur les 4 caractères ``toto''. Mais on préfère généralement
utiliser la classe string de la STL pour manipuler les chaînes
de caractères.
L'avantage de cette méthode est que votre matrice peut avoir un nombre
de colonnes différents pour chaque ligne.
Modifier le programme de la leçon ``tableaux'' sur le produit scalaire
de deux vecteurs, en créant maintenant ces deux vecteurs de façon
dynamique: le programme demande à l'utilisateur la taille n
des vecteurs, et les composantes des vecteurs.
Une fonction est un petit sous programme qui utilise (ou pas) certains
paramètres que lui passe la fonction qui l'appelle (cela peut être
la fonction principale ou programme principal (``main'')).
Une fonction peut modifier un objet, remplir un tableau, écrire une
information à l'écran...
Une fonction peut renvoyer une (et une seule)
valeur d'une certaine classe ou ne pas renvoyer de valeur du tout.
on met void (vide en anglais).
Il existe trois façons de transmettre un argument à une fonction :
par recopie, par pointeur et par référence.
Ce mode de transmission est le mode par défaut ; il est parfois appelé
transmission par valeur.
Exécuter ce programme ; que se passe-t-il ?
Ce mode de transmission est propre au C++.
Exécuter ce programme ; que se passe-t-il ?
Il fonctionne donc correctement. La seule différence par rapport à
la transmission par recopie est l'apparition de ``&''
devant les paramètres à la déclaration de la fonction Echange
signifiant que l'on passe la valeur et l'adresse de l'argument ;
il n'y a plus recopie. Cette méthode est donc
celle que nous utiliserons à chaque fois que nous aurons besoin de
modifier un argument.
Vous pouvez sauter cette section dans un premier temps.
L'exécution de ce programme donne:
Imaginer que nous ayons à écrire 2 fonctions F1 et F2.
Si la fonction F2 appelle la fonction F1, nous devrons
écrire
Un des aspects les plus puissants du C++ est que l'on peut ``surcharger''
les fonctions: c'est à dire que l'on peut donner le même nom à des
fonctions qui font des choses différentes. Ce mécanisme s'étend même
aux opérateurs (voir le la surdéfinition des opérateurs).
Ce qui permet au langage de distinguer qu'elle est la fonction à appeler,
c'est les paramètres demandés lors de l'appel de la fonction.
Les Boucles
for( initialisation ; condition ; incrémentation) {instructions}
Ce programme produira:
using namespace std;
int main( )
{
int i;
int j;
for(i=10;i<=30;i=i+2)
{
j=i*i;
cout<<i<<"\t"<<j<<endl; // le caractère \t est une tabulation
}
}
do { instructions} while ( condition);
produira:
using namespace std;
int main( )
{
int MAX=11;
int i=1,j;
do
{
j=i*i;
cout<<i<<"\t"<<j<<endl;
i=i+1; // Ne pas oublier l'incrémentation
}
while(i<MAX); // i < MAX est la condition
}
while (condition) { instructions }
Produira:
using namespace std;
int main ( )
{
int MAX=11;
int i=1, j;
while(i<MAX) // condition
{
j=i*i;
cout<<i<<"\t"<<j<<endl;
i=i+1; // ne pas oublier l'incrémentation
}
}
Remarque sur les boucles:
if (condition) {instructions} else {instructions}
using namespace std;
int main()
{
int i=5,j;
char test='N';
while(test!='O') // tant que la valeur de test est différente de 'O'
{
cout<<"entrer un nombre entre 1 et 10 "<<endl;
cin>>j;
if(j>10)
{
cout<<"Comme tu ne sais pas lire je refuse de jouer avec toi"<<endl;
break; //on sort du while
}
if(i==j)
{
cout<<"Bravo vous avez trouvé le nombre mystérieux!"<<endl;
test='O';
}
else
cout<<"Non ce n'est pas le bon nombre"<<endl;
} // fin du while
} // du programme
Exercice : Résolution d'une équation du second degré
Complément : Exercice sur les nombres aléatoires
Pour générer un nombre entier p aléatoire, entre 0 et N inclus.
Puis dans le programme, l'instruction suivante ne doit apparaître
qu'une seule fois; elle permet d'initialiser les tirages à partir
de la date.
#include <cstdlib>
Puis au moment de choisir un nombre au hasard:
Explications: rand() génère un nombre entier
aléatoire entre 0 et MAX_INT (le plus grand entier). Ensuite %
signifie modulo, a%b est
le reste de la division de a par b.
Ainsi rand()%(N+1) est le reste de la division du
nombre choisit par rand() par N+1. C'est
donc un entier compris entre 0 et N (inclus), ce que
l'on veut.
switch
où var est soit de type int soit de type char
et valueX est du type de var. Selon la valeur de var,
faire telles ou telles instructions. Dans l'exemple précédant, si
var=value1 on ne fera que les instruction1 ; idem pour
value2 et value4. Par contre si var=value3 on
effectue instruction3 et instruction4 car il n'y a pas
de break. Enfin si var n'est égale à aucune des valueX,
on fait instruction_defaut. Cette dernière
ligne est facultative.
{
case value1: instruction1; break;
case value2: instruction2; break;
case value3: instruction3;
case value4: instruction4; break;
default: instruction_defaut; break;
}
Les tableaux
déclaration
float tab2[20]; //tab2 est un tableau de 20 cases contenant chacune une objet de classe réel
char tab3[10]; //tab3 est un tableau de 10 cases contant chacune une objet de classe char. On dit aussi que c'est une chaîne de 10 caractères
Attention: la taille du tableau doit
être un nombre constant ; ça ne peut pas
être la valeur d'un objet (int). Si l'on veut créer un tableau
de taille variable, voir la section pointeurs.
Affectation
Attention:
si on déclare int tab1[N], les N cases sont numérotées
de 0 à N-1 . Si on se trompe, si on écrit dans une case hors de limites,
cela peut être la cause du plantage de votre programme. Sachez que
la plupart des bugs que vous risquez de créer proviendront de ce problème.
Exercice
#include<iostream>
using namespace std;
const int dim=3; //declaration d'une constante de type entier
int main()
{
double v1[dim], v2[dim],ps;
int i;
v1[?]=1; v1[?]=2; v1[?]=3;
v2[?]=3; v2[?]=-2; v2[?]=1;
ps=0.;
for (i= ? ;i< ? ;i=i+1)
ps=ps+v1[i]*v2[i];
cout<<"le produit scalaire est:"<<ps<<endl ;
}
Remarques
Les pointeurs
Déclaration et affectation d'un pointeur
Cette instruction a pour effet de réserver une ``case'' en mémoire
de l'ordinateur, permettant de stocker un nombre reél. Cette case
s'appelle i.
Grâce au signe *, la deuxième ligne déclare p comme
étant un pointeur sur un double (désignant une case mémoire
qui est sensée contenir un réel). Remarquons que p est en fait
un objet de type double*. On peut écrire double* p
ou double *p ; cependant par convention, on utilise
plutôt le seconde notation.
double *p; // déclaration du pointeur p sur un double
p=&i; // p devient l'adresse de i
adresse des cases
contenu de la case
nom de variable
10000
...
10004
10012
p
10008
...
10012
2.
i
10016
...
ou:
qui est équivalent car (*p)
signifie le contenu de la ``case'' où pointe p.
donne l'adresse pointée par p (i.e. celle de i).
ou:
qui est équivalent.
Attention:
Les Tableaux statiques
vous réservez 6 cases mémoires de type float. Comme
la dimension de ces tableaux est forcément constante, on parle de
tableau statique. La variable tab n'est rien d'autre
qu'un pointeur sur un float. Ainsi l'instruction
Ceci explique pourquoi les tableaux ont leurs cases numérotées de
0 à N-1 :*(tab+i) signifie ``je saute i cases''
(dont la taille dépend du type du pointeur).
tab[1]=2.5 est parfaitement équivalente à *(tab+1)=2.5
...
L'élément tab[i][j] correspond par exemple à la ligne
i et la colonne j d'une matrice.
Allocation dynamique de la mémoire
Cela s'appelle une allocation dynamique
de la mémoire, car elle est faite au cours de l'exécution du programme
et non par le compilateur comme dans les tableaux statiques. C'est
la commande new qui permet de faire ceci.
cout<<"entrez n"<<flush;
cin>>n;
float *pValeur; //on déclare le pointeur pValeur
pValeur=new float[n]; // on réserve n cases mémoire de la classe float
ou (ce qui est équivalent)
On peut de même écrire dans la case suivante par:
ou
etc... jusqu'à pValeur[n-1].
Remarques :
p=new int(); // on réserve une case mémoire de classe int.
*p=1;
delete p; // pour libérer la place mémoire
A= new float*[10]; //on crée 10 pointeurs de float
for (int i=0; i<10;i++) A[i]=new float[10]; //on crée 10 cases de float pour chaque A[i]
Exercice
Les fonctions
Fonction sans objet de retour
using namespace std;
//========declaration de la fonction carre=============
void carre(int i)
{
int j; // declaration d'un objet local
j=i*i;
cout<<"le carré de "<<i<<" est "<<j<<endl;
}
//====declaration de la fonction principale =============
int main()
{
int x;
cout<<" entrer x "<<endl;
cin>>x;
carre(x); // appel de la fonction carre
return 0;
}
Remarques:
Fonction avec objet de retour
using namespace std;
//======declaration de la fonction carre =================
int carre(int i)
{
int j;
j=i*i;
return j; //on renvoie le résultat au programme principal
}
//===declaration de la fonction principale ===============
int main()
{
int x;
cout<<"entrer x "<<endl;
cin>>x;
int y;
y=carre(x); // appel de la fonction carre
cout<<"Le carré de "<<x<<" est "<<y<<endl;
return 0;
}
Remarques
La seule différence avec l'exemple précédent est que la fonction carre
renvoie son résultat, par un objet de la classe int. Pour
cela on utilise l'instruction return.Et pour
appelé cette fonction, on utilise une la syntaxe y=carre(x)
si bien que le résultat est tout de suite stocké dans l'objet y.
Transmission des arguments d'une fonction.
Transmission par recopie
using namespace std;
//declaration de Echange (ne renvoie rien car void)
void Echange(int a, int b)
{
int c;
cout<<"Echange: Avant permutation n="<<a<<" p="<<b<<endl;
c=a; a=b; b=c;
cout<<"Echange: Apres permutation n="<<a<<" p="<<b<<endl;
}
int main()
{
int n=10, p=20;
cout<<"main: Avant appel a Echange n="<<n<<" p="<<p<<endl;
Echange(n,p);
cout<<"main: Apres appel a Echange n="<<n<<" p="<<p<<endl;
}
Exercice
Remarques :
Transmission par référence
using namespace std;
//declaration de Echange (ne renvoie rien car void)
void Echange(int &a, int &b)
{
int c;
cout<<"Echange: Avant permutation n="<<a<<" p="<<b<<endl;
c=a; a=b; b=c;
cout<<"Echange: Apres permutation n="<<a<<" p="<<b<<endl;
}
int main()
{
int n=10, p=20;
cout<<"main: Avant appel a Echange n="<<n<<" p="<<p<<endl;
Echange(n,p);
cout<<"main: Apres appel a Echange n="<<n<<" p="<<p<<endl;
}
Exercice
Complément : Transmission par pointeur
{
int c;
cout<<"Echange: Avant permutation n="<<*a<<" p="<<*b<<endl;
c=*a; *a=*b; *b=c;
cout<<"Echange: Apres permutation n="<<*a<<" p="<<*b<<endl;
}
int main()
{
int n=10, p=20;
cout<<"main: Avant appel a Echange n="<<n<<" p="<<p<<endl;
Echange(&n,&p);
cout<<"main: Apres appel a Echange n="<<n<<" p="<<p<<endl;
}
Remarque :
Ici on appelle Echange en donnant les adresses de n
et p ; celles-ci sont recopiées dans les pointeurs a
et b. Le contenu des cases pointées par ces pointeurs est échangé
mais a continu de pointé sur n et b sur p.
Cette fois la permutation a réussi dans le main(). Ceci est
donc une méthode si on veut modifier les arguments passés à une fonction.
Noter cependant que l'écriture comme l'appel de la fonction Echange
est très lourd. En C c'est la seule façon de procéder.
Echange: Avant permutation n=10 p=20
Echange: Apres permutation n=20 p=10
main: Apres appel a Echange n=20 p=10
Fonctions et Prototypes
En effet comme F2 appelle F1, F1 doit être déclaré
avant F2 pour que F1 soit connue dans F2. Maintenant
supposons que F2 appelle F1 et F1 appelle F2...On
doit alors utiliser un prototype, c'est-à-dire déclarer la
fonction F2, son type, le nombre de ces arguments et leur type
avant:
{
...
}
void F2(int x,int y)
{
int h;
...
h=F1(y);
...
}
int main()
{
int i,j;
...
F2(i,j);
...
}
Outre ce cas particulier où il est indispensable, le prototype est
utilisé dans tous le fichier ``.h'' que vous utilisez dans les includes.
Nous verrons leur utilité dans l'écriture des classes.
int F1(int x)
{
...
if (z<2) F2(x,z);
...
}
void F2(int x,int y)
{
int h;
...
h=F1(y);
...
}
int main()
{
int i,j;
...
F2(i,j);
...
}
La surcharge des fonctions
Exemple
using namespace std;
//----------------------
void func(int i)
{
cout<<"fonction 1 appelée"<<endl;
cout<<" paramètre = "<<i<<endl;
}
//----------------------
void func(float i)
{
cout<<"fonction 2 appelée"<<endl;
cout<<" paramètre = "<<i<<endl;
} //----------------------
void func(char *s,int i)
{
cout<<"fonction 3 appelée "<<endl;
cout<<" paramètre = "<<s<<endl;
cout<<" paramètre = "<<i<<endl;
}
//-----------------------
int main()
{
int j=10;
func(j);
float k=5.2;
func(k);
func("Chaîne",4);
}