2. INTRODUCTION AU LANGAGE C++ ... 3
2.1. Historique ... 3
2.2. Implantation de modules en C++... 3
2.3. Structure d’un programme C++... 3
2.4. Commentaires ... 3
2.5. Types de base... 3
2.6. Définition de variables... 4
2.7. Les constantes... 4
2.8. Opérateurs et expressions ... 4
2.8.1 Opérateurs arithmétiques... 4
2.8.2. Opérateurs relationnels... 4
2.8.3. Opérateurs logiques... 4
2.8.4. Affectation ordinaire ... 5
2.8.5. Opérateurs d’incrémentation / décrémentation... 5
2.8.6. Opérateurs d’affectation élargie... 5
2.8.7. Opérateurs de décalage et logique bit à bit ... 5
2.8.8. Priorité des opérateurs... 6
2.9. Les instructions de contrôle ... 6
2.9.1. Notion de bloc ... 6
2.9.2. Instruction if... 6
2.9.3. Instruction switch ... 7
2.9.4. do … while... 7
2.9.5. while... 8
2.9.6. for... 8
2.10. Les fonctions ... 8
2.10.1. Déclaration ... 8
2.10.2. Définition... 8
2.10.3. Appel d’une fonction ... 9
2.10.4. Variables locales et variables globales ... 9
2.10.5. Passage de paramètres ... 10
2.10.6. Fonction inline... 11
2.10.7. Surcharge de fonction ... 11
2.10.8. Type du résultat d’une fonction ... 11
2.10.9. Exemple ... 12
2.11. Les tableaux et les pointeurs ... 13
2.11.1. Les tableaux à un indice... 13
2.11.2. Les tableaux à plusieurs indices ... 14
2.11.3. Notion de pointeur – les opérateurs * et &... 14
2.11.4. Un nom de tableau est un pointeur constant... 17
2.11.5. Les tableaux transmis en argument ... 17
2.12. Les références ... 18
2.13. Allocation dynamique de mémoire... 19
Version C... 19
Version C++ ... 19
Exemple d’utilisation des pointeurs : ... 20
2.14. Les chaînes de caractères ... 21
2.14.1. Représentation ... 21
2.14.2. Généralités sur les fonctions portant sur des chaînes... 22
2.15.2. Entrées/sorties en C... 22
2.16. Les fichiers... 22
2.16.1. Les fichiers en C... 22
2.16.2. Quelques bases sur les fichiers en C++ ... 24
2.17. Espaces de noms... 25
2.17.1. Définition... 25
2.17.2. Utilisation ... 25
2.18. Directives de prétraitement... 26
2.18.1. Forme d’une directive ... 26
2.18.2. #define... 26
2.18.3. #undef... 26
2.18.4. #include... 26
2.18.5. Compilation conditionnelle... 26
2.19. Structure ... 27
2.19.1. Déclaration ... 27
2.19.2. Accès aux membres ... 27
2.19.3. Utilisation ... 27
C C++
2. INTRODUCTION AU LANGAGE C++
2.1. Historique
C (pour Unix) C++ (Bjane Stroustrup) (pg° procédurale) (pg° orientée objet)
2.2. Implantation de modules en C++
1 pgm simple 1 fichier interface (spécification) .h
1 fichier d’implémentation .c ou .cpp 2.3. Structure d’un programme C++
1 programme simple main() Ex de pgm :
int main() entête {
; Bloc d’instructions (entre {})
}
Une fonction ne peut jamais contenir d’autres fonctions.
2.4. Commentaires /*_____
______
______ */
int main() // _____
{…
2.5. Types de base
char caractères int entiers float réels
double réels grande précision
void type « rien » (fonction qui ne retourne rien procédure) bool booléens
2.6. Définition de variables int main()
{ // transtypage : ex : (int) C
int A,B ; // 9 C sera vu comme int double C ;
B=A ; C=A+B ; }
2.7. Les constantes
const int A=2 ; 2.8. Opérateurs et expressions
2.8.1 Opérateurs arithmétiques
+, -, *, /, % (modulo), - (- unaire) char D=’b’ ;
D=D+1 ; // code ASCII suivant => c 2.8.2. Opérateurs relationnels
== identique
!= différent
<
>
<=
>=
if (A==B) {
… }
A = (B==2)*4 ;
∃
booléen =1 si VRAI =0 si FAUX 2.8.3. Opérateurs logiques
et &&
ou ||
non !
if (A<1 && B>3) {
… }
2.8.4. Affectation ordinaire
=
ex : A=B+3 ; B+C=A ;
2.8.5. Opérateurs d’incrémentation / décrémentation i=i+1 ; i++ ;
++i ; int i=3, n ;
n=++i-3 ; // n vaut 1, i vaut 4 n=i++-3 ; // i vaut 4, n vaut 0 2.8.6. Opérateurs d’affectation élargie
i=i+2 ; i+=2 ;
+= -= *= /= %=
A=2*A ; A*=3 ;
2.8.7. Opérateurs de décalage et logique bit à bit
<< décalage
A=A<<1 ; // si A=0010110101
// alors => 0101101010 (tjs 0 en fin)
<<1 *21
>>
>>1 /21
~ complément à 1
& ET logique
| OU logique
^ OU EXCLUSIF
int A=9, B=8, C ; A … 1001
B … 1000 C … 1000 C=A&B ;
2.8.8. Priorité des opérateurs
Catégorie d’opérateurs Opérateurs Assoc.
Fonction, tableau, membre de structure,
pointeur sur un membre de structure () [] . -> G=>D
Opérateurs unaires - ++ -- !
~ * & sizeof (type) D=>G
Multiplication, division, modulo * / % G=>D
Addition, soustraction + - G=>D
Opérateurs binaires de décalage << >> G=>D Opérateurs relationnels < <= >= > G=>D
Opérateurs de comparaison == != G=>D
Et binaire & G=>D
Ou exclusif binaire ^ G=>D
Ou binaire | G=>D
Et logique && G=>D
Ou logique || G=>D
Opérateur conditionnel ?: D=>G
Opérateurs d’affectation = += -= *= /= %=
&= ^= |= <<= >>= D=>G
Opérateur virgule , G=>D
2.9. Les instructions de contrôle 2.9.1. Notion de bloc
C’est une suite d’instructions délimitée par { … }.
Toutes les instructions simples se terminent par ; 2.9.2. Instruction if
if (Condition) // parenthèses obligatoires {
____
____
}
else // facultatif (lorsque rien à faire) {
____
____
}
Un else se rapporte toujours au dernier if rencontré auquel un else n’a pas encore été attribué.
if (i==0)
if (A>10) {
… }
else // correspond au 2ème if (malgré la mise en page) {
… }
-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
if (i==0) {
if (A>10) {
… }
}
else // correspond au 1er if {
… }
2.9.3. Instruction switch int i=2 ;
switch (i) // switch (qqch) où qqch est un entier
{ // ou un caractère
case 0 : ____
____
break ; // si pas de break, ça case 2 : ____ // continue (ici, si pas de break ; // break, ça exécute case 2) case 1 : ____
____
____
break ; default : ____
____
break ; }
2.9.4. do … while p=i=1 ; do {
p=p*i ; On y passe forcément au moins une fois i++ ;
} while (i!=10) ;
2.9.5. while p=i=1 ;
while (i!=10) {
p*=i ; i++ ; }
2.9.6. for
int i, Somme=0 ;
// n°1 n°2 n°3 for (i=1 ; i<=10 ; i++) // init condit°
// de continuité {
// n°4
Somme+=i ; }
1 2 4 3 2 4 3 2 4 … {
int Somme=0 ;
for (int i=1 ; i<=10 ; Somme+=i, i++) ;
A=0 ; // 9 n°4
2.10. Les fonctions
2.10.1. Déclaration
Toute fonction doit être déclarée avant de pouvoir être utilisée. Cette déclaration a pour but d’indiquer au compilateur le nom de la fonction, le type du résultat retourné, ainsi que le nombre et le type de ses paramètres.
float F1 (int, char, int, float) ; 2.10.2. Définition
La définition d’une fonction est la description de celle-ci comprenant l’entête et le corps de la fonction. L’entête est identique à la déclaration. Le corps est un bloc d’instructions.
float F1 (int A, char B, int C, float D) {
____
____
return A ; }
ex : fonction Max
int Max (int A, int B, int C) {
int M = (A > B) ? A : B ; return (M > C) ? M : C ; }
if (A>B) M=A ; else
M=B ;
Les définitions de fonctions ne peuvent pas être imbriquées.
2.10.3. Appel d’une fonction
Une fonction ne peut être appelée que si sa déclaration est accessible.
2.10.4. Variables locales et variables globales
Les variables déclarées à l’intérieur d’un bloc sont appelées variables locales.
Leur portée est limitée au bloc.
Les variables globales sont celles qui sont déclarées en dehors d’une fonction ou d’une classe. Elles sont utilisables jusqu’à la fin du fichier.
void F() ;
int A=0 ; // variable globale main ()
{
int B=0 ; // variable locale à main float A ; // variable locale à main F() ;
{
char C ; // C visible jusque
… // …
… // …
} // là, et c’est tout !
A++ ; // le A du main (pas le premier !) }
void F() {
int C ; ____
}
Variables statiques
int A ; // globale main ()
{
… F() ;
F() ; // récupère le C de l’appel précédent F() ;
… }
void F(void) {
int B=1 ; // locale
static int C=1 ; // statique (reste locale) B++ ;
C++ ; }
2.10.5. Passage de paramètres
2.10.5.1. Passage par valeur (IN) (recopie)
A l’appel d’une fonction, les paramètres formels sont initialisés avec les valeurs des paramètres réels correspondant.
A la fin de la fonction : aucune recopie de valeur de valeur de formel vers réel.
void Echange (int A, int B) {
int C ; C=A ; A=B ; B=C ; }
int main() {
int X1=1, X2=2 ;
Echange (X1, X2) ; // X1 et X2 inchangés !!!
… }
2.10.5.2. Passage par référence (n’existe pas en C ; plus rapide : pas de recopie) void Echange2 (int &A, int &B)
{
inc C ; C=A ; A=B ; B=C ; }
int main () {
int X1=1, X2=2 ;
Echange2 (X1, X2) ; // X1 et X2 changés
… // grâce aux &
}
main () {
TImage Im1 ; Calcul (Im1) ;
… }
void Calcul (const TImage &A) // image non modifiée
{ // grâce à const
…
… }
Passage par référence constante utilisée pour de gros objets à traiter.
On a le droit de donner des valeurs par défaut à des paramètres. Elles ne peuvent être mentionnées qu’une seule fois parmi les définition/déclaration d’une fonction.
Par convention, ces valeurs sont généralement mentionnées dans la déclaration de la fonction (le .h).
test.cpp lib.h lib.cpp
#include <lib.h>
main () {
int A=1, B=2, C ; …
C=F (A, B) ; …
}
C=F(A) ;
ça marche puisque valeur par défaut du 2ème paramètre = 0 (c’est défini)
int F (int, int) ;
int F (int, int=0) ;
#include <lib.h>
int F (int X1, int X2) ; {
… }
2.10.5.3. Passage par adresse On verra cela plus tard…
2.10.6. Fonction inline
inline int Min (int A, int B) // pour remplacer l’appel de
{ //la fonction par ce qu’elle fait
return A<B ? A : B ; }
Les définitions de fonctions inline sont dans le .h 2.10.7. Surcharge de fonction
Même nom de fonction différenciées par nombre et/ou type des paramètres (mais pas par le type de retour !!!).
2.10.8. Type du résultat d’une fonction
On retourne ce qu’on veut mais il faut veiller à certaines choses : Ex : ne pas retourner une référence à une variable locale
int F() {
int C ; C=1 ;
return &C ; // problème !!!
}
2.10.9. Exemple
unit1.cpp unit2.cpp
extern int A ; // décl. var. qui // vient d’ailleurs void F1 () ; // déclaration
static void F2 () ; // déclaration /* F2 ne peut être utilisée que par unit1.cpp */
void F3 () ; // déclaration int main (…)
{ … }
void F1 () // définition {
static int X=1 ; // var. locale // statique int Y=1 ; // var. locale // classique …
}
static void F2 () // définition {
… }
int A ; // définition var. globale static int B ; // définition
// utilisable que dans unit2.cpp int C ; // var. globale classique void F3 () ;
static void F4 () ; // déclaration // utilisable que dans unit2.cpp void F3 () // définition
{ … }
static void F4() // définition {
… }
extern int A ; void F1 () ;
static void F2 () ; void F3 () ;
int main (…) {
… }
void F1 () {
static int X=1 ; int Y=1 ;
… }
static void F2 () {
… }
int A ;
static int B ; int C ;
void F3 () ;
static void F4 () ; void F3 ()
{ … }
static void F4() {
… }
2.11. Les tableaux et les pointeurs
Comme tous les langages, C permet d’utiliser des « tableaux ». On nomme ainsi un ensemble d’éléments de même type désignés par un identificateur unique ; chaque élément est repéré par un « indice » précisant sa position au sein de l’ensemble.
Par ailleurs, comme certains langages tels que le Pascal, le langage C dispose de
« pointeurs », c’est-à-dire de variables destinées à contenir des adresses d’autres « objets » (variables, fonctions…).
A priori, ces deux notions de tableaux et de pointeurs peuvent paraître fort éloignées l’une de l’autre. Toutefois, il se trouve qu’en C un lien indirect existe entre ces deux notions, à savoir qu’un identificateur de tableau est une « constante pointeur ». Cela peut se répercuter dans le traitement des tableaux, notamment lorsque ceux-ci sont transmis en argument de l’appel d’une fonction.
C’est ce qui justifie que ces deux notions soient regroupées dans un seul chapitre.
2.11.1. Les tableaux à un indice
2.11.1.1. Exemple d’utilisation d’un tableau en C
Supposons que nous souhaitions déterminer, à partir de vingt notes d’élèves (fournies en données), combien d’entre elles sont supérieures à la moyenne de la classe. S’il ne s’agissait que de calculer simplement la moyenne de ces notes, il nous suffirait d’en calculer la somme, en les cumulant dans une variable, au fur et à mesure de leur lecture.
Mais ici, il nous faut à nouveau pouvoir consulter les notes pour déterminer combien d’entre elles son supérieures à la moyenne ainsi obtenue. Il est donc nécessaire de pouvoir
« mémoriser » ces vingt notes.
Pour ce faire, il paraît peu raisonnable de prévoir vingt variables scalaires différentes (méthode qui, de toute manière, serait difficilement transposable à un nombre important de notes). Le tableau va nous offrir une solution convenable à ce problème, comme le montre le programme suivant :
#include <stdio.h>
int main(void) {
int i, som, nbm ; double moy ;
int t[20] ;
for (i=0 ; i<20 ; i++) {
printf("donnez la note numéro %d : ", i+1) ; scanf ("%d", &t[i]) ;
}
for (i=0 ; som=0 ; i<20 ; i++) som+=t[i] ;
moy=som/20 ;
printf("\n\n moyenne de la classe : %f\n", moy) ; for (i=0 ; nbm=0 ; i<20 ; I++)
if (t[i]>moy) nbm++ ;
printf("%d élèves ont plus de cette moyenne", nbm) ; return 0 ;
}
La déclaration : int t[20] réserve l’emplacement pour 20 éléments de type int. Chaque élément est repéré par sa « position » dans le tableau, nommée
« indice ». Conventionnellement, en langage C, la première position porte le numéro 0. Ici, donc, nos indices vont de 0 à 19. Le premier élément du tableau sera désigné par t[0], le troisième par t[2] le dernier par t[19].
Plus généralement, une notation telle que t[i] désigne un élément dont la position dans le tableau est fournie par la valeur i. Elle joue le même rôle qu’une variable scalaire de type int.
2.11.1.2. Quelques règles
a) Les éléments de tableau
Il n’est pas possible, si t1 et t2 sont des tableaux d’entiers, d’écrire t1=t2 ; en fait, le langage C n’offre aucune possibilité d’affectations globales de tableaux, comme c’était le cas, par exemple en Pascal.
b) Les indices
Un indice peut prendre la forme de n’importe quelle expression arithmétique de type entier (ou caractère, compte tenu des règles de conversion systématique).
c) La dimension d’un tableau
La dimension d’un tableau (son nombre d’éléments) ne peut être qu’une constant ou une expression constante.
d) Débordement d’indice
Aucun contrôle de « débordement d’indice » n’est mis en place par la plupart des compilateurs. Pour en comprendre les conséquences, il faut savoir que, lorsque le compilateur rencontre une lvalue telle que t[i], il en détermine l’adresse en ajoutant à l’adresse de début du tableau t, un « décalage » proportionnel à la valeur de i (et aussi proportionnel à la taille de chaque élément du tableau) de sorte qu’il est très facile (s’il on peut dire !) de désigner et, partant, de modifier un emplacement situé avant ou après le tableau.
2.11.2. Les tableaux à plusieurs indices
Comme tous les langages, C autorise les tableaux à plusieurs indices (on dit aussi à plusieurs dimensions).
Par exemple, la déclaration int t[5][3] réserve un tableau de 15 (5*3) éléments. Un élément de ce tableau se trouve alors repéré par deux indices comme dans ces notations : t[3][21] t[i][j] t[i-3][i+j]
Notez bien que, là encore, la notation désignant un élément d’un tel tableau est une lvalue. Il n’en ira toutefois pas de même de notations telles que t[3] ou t[j] bien que, comme nous le verrons plus tard, de telles notations aient un sens en C.
Aucune limitation ne pèse sur le nombre d’indices que peut comporter un tableau. Seules les limitations de taille mémoire liées à un environnement donné risquent de se faire sentir.
2.11.3. Notion de pointeur – les opérateurs * et &
2.11.3.1. Introduction
Nous avons déjà été amené à utiliser l’opérateur & pour désigner l’adresse d’une lvalue. D’une manière générale, le langage C permet de manipuler des
adresses par l’intermédiaire de variables nommées « pointeurs ». en guise d’introduction à cette nouvelle notion, considérons les instructions :
int *ad ; int n ; n=20 ; ad=&n ;
*ad=30 ;
La première réserve une variable nommée ad comme étant un
« pointeur » sur des entiers. Nous verrons que * est un opérateur qui désigne le contenu de l’adresse qui le suit. Ainsi, à titre « mnémotechnique », on peut dire que cette déclaration signifie que *ad, c'est-à-dire l’objet d’adresse ad, est de type int, ce qui signifie bien que ad est l’adresse d’un entier.
L’instruction ad=&n ;
affecte à la variable ad la valeur de l’expression &n. L’opérateur
& est un opérateur unaire qui fournit comme résultat l’adresse de son opérande. Ainsi, cette instruction place dans la variable ad l’adresse de la variable n.
L’instruction suivante *ad=30 ;
signifie : affecter à la lvalue *ad la valeur 30. Or *ad représente l’entier ayant pour adresse ad (notez bien que nous disons l’entier et pas simplement la valeur car, ne l’oubliez pas, ad est un pointeur sur des entiers).
Bien entendu, ici, nous aurions obtenu le même résultat avec n=30 ;.
2.11.3.2. Quelques exemples
Voici quelques exemples d’utilisation de ces deux opérateurs. Supposez que nous ayons effectué ces déclarations :
int *ad1, *ad2, *ad ; int n=10, p=20 ;
Les variables ad1, ad2 et ad sont donc des pointeurs sur des entiers.
Considérons maintenant ces instructions :
ad1=&n ; ad2=&p ;
*ad1=*ad2+2 ;
Les deux premières placent dans ad1 et ad2 les adresses de n et p. La troisième affecte à *ad1 la valeur de l’expression *ad2+2.
Autrement dit, elle place à l’adresse désignée par ad1 la valeur (entière) d’adresse ad2, augmentée de 2. Cette instruction joue donc ici le même rôle que n=p+2 ;
De manière comparable, l’expression : *ad1+=3 jouerait le même rôle que n=n+3 et l’expression (*ad1)++ jouerait le même rôle que n++.
Remarque : une déclaration telle que int *ad réserve un emplacement pour un pointeur sur un entier. Elle ne réserve pas en plus un emplacement pour un tel entier. Cette remarque prendra encore plus d’acuité lorsque les « objets pointés » seront des chaînes ou des tableaux.
2.11.3.3. Incrémentation de pointeurs
Jusqu’ici, nous nous sommes contenté de manipuler, non pas les variables pointeurs elles mêmes, mais les valeurs pointées. Or si une variable pointeur ad a
En effet, ad est censée contenir l’adresse d’un entier et, pour C, l’expression ci-dessus représente l’adresse de l’entier suivant. Certes, dans notre exemple, cela n’a guère d’intérêt car nous ne savons pas avec certitude ce qui se trouve à cet endroit.
Mais nous verrons que cela s’avérera fort utile dans le traitement de tableaux ou de chaînes.
Notez bien qu’il ne faut pas confondre un pointeur avec un nombre entier. En effet, l’expression ci-dessus ne représente pas l’adresse de ad augmentée de un (octet). Plus précisément, la différence entre ad+1 et ad est ici de sizeof(int) octets. Si ad avait été déclarée par double *ad ;, cette différence serait de sizeof(double) octets.
De manière comparable, l’expression ad++ incrémente l’adresse contenue dans ad de manière qu’elle désigne l’objet suivant.
2.11.3.4. Comme simuler une transmission par adresse avec un pointeur Nous avons vu que le mode de transmission par valeur semblait interdire à une fonction de modifier la valeur de ses arguments effectifs et nous avions mentionné que les pointeurs fourniraient une solution à ce problème.
Nous sommes maintenant en mesure d’écrire une fonction effectuant la permutation des valeurs de deux variables. Voici un programme qui réalise cette opération avec des valeurs entières :
#include <stdio.h>
void echange(int *ad1, int *ad2) int main(void)
{
int a=10, b=20 ; echange (&a, &b) ; }
void echange(int *ad1, int*ad2) {
int x ; x=*ad1 ;
*ad1=*ad2 ;
*ad2=x ; }
Les arguments effectifs de l’appel de echange sont, cette fois, les adresses des variables n et p (et non plus leurs valeurs). Notez bien que la transmission se fait toujours par valeur, à savoir que l’on transmet à la fonction echange les valeurs des expressions &n et &p.
float B=3.14 ; int A ;
A=1 ;
int *Ptr ; // Ptr pointeur vers un entier
Ptr=&A ; // Ptr reçoit l’adresse de A (Ptr pointe vers A)
*Ptr=2 ; // modifie la zone pointée (pas le pointeur) float *Ptr2 ;
Ptr2=&B ;
*Ptr2=2.4 ;
2.11.4. Un nom de tableau est un pointeur constant int Tab [10] ;
int *Ptr ; Ptr=Tab ;
Tab correspond à l’adresse du premier entier du tableau.
Ptr aussi.
2.11.4.1. Cas des tableaux à un indice
Ex : rédiger un programme qui définit et remplit un tableau de 10 entiers avec la valeur 1.
int Tab[10] ;
for (int i=0 ; i<10 ; i++) Tab[i]=1 ;
int Tab[10], *Ptr ; Ptr=Tab ;
for (int i=0 ; i<10 ; i++, Ptr++)
*Ptr=1 ; int Tab[10] ;
for (int i=0 ; i<10 ; i++)
*(Tab+i)=1 ; // Tab++ interdit ! 2.11.4.2. Cas des tableaux à plusieurs indices
C’est compliqué donc on passe ! ^^
Rappel : Tab[3][4] crée un tableau de 3 lignes et 4 colonnes.
2.11.5. Les tableaux transmis en argument 2.11.5.1. Cas des tableaux à un indice
a) Tableau de taille fixe int main (…) {
int Tab[10] ; Remplir(Tab) ;
… }
void Remplir (int T[]) // ou void Remplir (int T[10])
{ // ou void Remplir(int *T)
for (int i=0 ; i<10 ; i++) T[i]=5 ; // ou *(T+i)=5 ; }
b) Tableau dont le nombre d’éléments est variable int main (…)
{
int Tab[10] ;
… }
void Remplir (int *T, int N) {
for (int i=0 ; i<N, i++) T[i]=5 ;
}
2.11.5.2. Cas des tableaux à plusieurs indices int main (…)
{
bool Tab[10][15] ; Toto(Tab) ;
… }
void Toto(bool T[][15]) // Toto (bool **T, int C) {
T[3][4]=true ; // T+1*C+4=true ; }
2.12. Les références
Une référence est un mécanisme d’alias (uniquement C++).
Pointeur, référence : même combat ; sauf que les références ne peuvent pas être vides.
Elles sont toujours initialisées lors de leur déclaration.
int Val=10 ; int *pVal ; pVal=&Val ;
int &RefVal=Val ;
*pVal=12 ; RefVal=13 ;
int main(…) {
int A=1, B=1, C=1 ; F(A, B, &C) ;
//passage ref val adr
void F(int X, int &Y, int *Z) {
X=2 ; Y=2 ;
*Z=2 ; }
Au retour dans main : A=1, B=2, C=2
10 Val pVal
RefVal
12 13
2.13. Allocation dynamique de mémoire
Allocation d’espace mémoire au moment de l’exécution et non au moment de la compilation.
int main (…) {
int N, *Tab ;
cout << "Nb d’étudiants :" ; cin >> N ;
// alloc. dynamique d’un tableau de N entiers
… }
Version C
int *Tab, *pi ;
pi=(int*)malloc(sizeof(int)) ; Tab=(int*)malloc(N*sizeof(int)) ; if ((pi != NULL) && (Tab != NULL)) {
…
free (pi) ; free (Tab) ; }
Version C++
int *pi ; int *Tab ; pi = new int ;
Tab = new int [N] ;
if ((pi != NULL) && (Tab != NULL)) {
…
delete pi ; delete []Tab ; }
Exemple d’utilisation des pointeurs :
#pragma hdrstop
#pragma argsused
#include <iostream>
#define AffChaine(Ch) cout << Ref++ << "--" << Ch << endl
#define AffCarac(Car) cout << Ref++ << "--" << Car << endl ; // cout << Ref++ affiche Ref puis l’incrémente de 1
using namespace std ;
int main (in argc, char* argv[]) {
char Ref = 'A' ;
char *Chaine = "merci" ; char Mot[] = "beaucoup" ; char *p = Mot ;
char *Tab[3] = {"ZERO", "UN", "DEUX"} ; AffChaine (Chaine) ;
AffCarac (Chaine[2]) ; AffChaine (Mot+3) ; AffCarac (*++p) ; AffCarac (++*++p) ;
// 3 2 1
AffChaine (Tab[1]) ; AffChaine (Tab[2]+1) ;
Tab[0][2] = '\0' ; // <=> *((*Tab+0)+2) = '\0' ; AffChaine (*Tab) ;
cin.sync() ; cin.get() ; return 0 ; }
Ref 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' Chaine
Mot p
Tab
m e r c i \0 b e a u c o u p \0
Z E R O \0 U N \0 D E U X \0 'A'
Affichage : A--merci B--r C--ucoup D--e E--b F--UN G--EUX H--ZE
2.14. Les chaînes de caractères 2.14.1. Représentation
2.14.1.1. Convention
La chaîne de caractères doit se terminer par le caractère \0 (de code 0).
Chaque caractère est stocké dans un octet.
Si la chaîne peut faire jusqu’à 20 caractères : char Tab[21]
2.14.1.2. Cas des chaînes constantes Ex : char *p ;
p="Toto" ; while (*p)
cout << *p++ ;
=> affichage : T o t o
2.14.1.3. Initialisation de tableaux de caractères char p[20] = "toto" ;
// /!\ p="toto" INTERDIT !!!!
p : t o t o \0 ou
char p[20]={'t', 'o', 't', 'o', '\0'} ; char p[]="toto" ; // <=> p[5]
2.14.1.4. Initialisation des tableaux de pointeurs sur des chaînes char *Tab[7]={"lundi", "mardi", …} ; Tab :
Tab :
=> char Tab[7][9] ;
l u n d i \0 m a r d i \0
l u n d i \0 m a r d i \0 m e r c r e d i \0 j e u d i \0
… … …
2.14.2. Généralités sur les fonctions portant sur des chaînes char Ch[]="toto" ;
int lg = strlen(Ch) ;
strcat(Ch1, Ch2) ; // concatène et place le résultat ds Ch1 strncat(Ch1, Ch2, lgmax) ; // n’ajoute pas + que lgmax car lg=strcmp(Ch1, Ch2) ; // -> =0 si Ch1 et Ch2 identiques
// <0 si Ch1 < Ch2 // >0 sinon
// /!\ Ch1=Ch2 strcpy(Ch1, Ch2) ; 2.15. Les entrées/sorties
2.15.1. Entrées/sorties en C++
cout << … ; // << et >> définis dans iostream cin >> … ;
int main (…) {
int A=12 ;
float B=123.4567f ; // f pr être vu comme float cout << setw(5) << 5 ; // -> ___12
}
2.15.2. Entrées/sorties en C int A ;
float B ; char C ; char D[10] ;
printf ("bonjour, %d, %f toto%c %s\n", A, B, C, D) ; //affichage 1 2 3 4 1 2 3 4
scanf ("%d%f%c%s", &A, &B, &C, D) ;
//saisie D est déjà une adresse ! 2.16. Les fichiers
2.16.1. Les fichiers en C 2.16.1.1. Introduction
Fichier = quelquechose sur le disque 2.16.1.2. Ouverture / fermeture
FILE *Fich ; //nom logique cstdio (C++) stdio.h (C) Fich=fopen("Nom_Phy", mode) ;
Mode :
"r" en lecture seulement (fichier doit exister)
"w" création et ouverture du fichier en écriture seulement (si le fichier existe, il est détruit)
"a" ouverture d’un fichier existant pour lui ajouter des enregistrements à la suite des enregistrements existants (si le fichier n’existe pas, il est créé)
"r+" ouverture en lecture et écriture d’un fichier qui doit exister
entier réel caractère chaîne de
caractères à la ligne
"w+" création d’un fichier et ouverture en lecture et écriture (si le fichier existe, il est détruit et remplacé par le fichier créé)
/!\ La fonction fopen retourne NULL si le fichier n’a pas pu être ouvert.
Fermeture : fclose (Fich) ; // Fich nom logique 2.16.1.3. Lecture / écriture
int fgetc(FILE *Flux) ; // Flux nom logique int fputc (int c, FILE *Flux) ;
// c = caractère, donc code ASCII d’où le "int"
char *fgets (char *Ch, int n, FILE *Flux) ; // permet de lire au max n-1 caractères
// arrêt de lire s’il on rencontre le caractère '\n' int fputs (const char *Ch, FILE *Flux) ;
fscanf (FILE *Flux, ______) ;
// idem scanf
fprintf (FILE *Flux, _____) ;
// idem printf
fwrite (&Client, sizeof(enregistrement), n, FICH) ; fread (_______, ______________________, _, ____) ; // adr du taille d’1 élémt nb nom logique // 1er élémt d’élémt du fichier
int A=1 ;
int Tab[10]={1, 2, 3, …, 10} ; FILE *Fich ;
Fich=fopen ("___","w") ;
if (Fich) // si pas NULL -> fichier bien ouvert {
fwrite (Tab, sizeof(int), 7, Fich) ; fwrite (&A, sizeof(int), 1, Fich) ;
// comme fprintf (Fich, "%d", A) ; fclose (Fich) ;
}
fseek (Fich, Dplct, Mode) ; // fonct° de positionnet // type long int
// (nb d’octets)
Mode : 0 depuis le début du fichier 1 depuis la position courante 2 depuis la fin du fichier
rewind(Fich) ;//pr se repositionner au déb du fichier ftell (Fich) ; // retourne un entier qui correspond
2.16.2. Quelques bases sur les fichiers en C++
Ce dernier paragraphe va nous permettre de voir en détail les possibilités de la librairie iostream. Nous savons déjà qu’il existe deux classes de bases : la classe istream et la classe ostream. Nous connaissons aussi, trois objets prédéfinis : cin, cout et cerr. Mais nous pouvons aller encore plus loin : nous pouvons notamment gérer les accès aux fichiers.
Les entrées-sorties sur les fichiers sont également réalisées avec des flots en C++. Ce type d’opérations nécessite l’inclusion de l’en-tête fstreamen plus de l’en-tête iostream. Les deux grandes classes permettant de réaliser ces opérations sont :
- La classe ofstream : cette classe est dédiée aux écritures réalisées dans des fichiers. La classes ofstream est dérivée de la classe ostream et bénéficie donc de toutes les méthodes définies dans cette classe.
#include <iostream>
#include <fstream>
int main() {
// Deux modes d’ouverture sont possibles :
// - ios::out -> créat°, fichier écrasé si existant // - ios::app -> ajout en fin de fichier
ofstream fichierSortie("donnees.txt", ios::out) ; // Test d’ouverture du fichier
if (!fichierSortie)
cerr << "Pb d’ouverture de fichier" << endl ; exit (1) ;
}
fichierSortie << "J’écris des caractères dans le fichier "
<< "et des nb : " << 10 << " " << 20 << endl ;
// Fermeture
fichierSortie.close() ; }
- La classe ifstream : cette classe est dédiée aux lectures réalisées dans des fichiers. La classe ifstream est dérivée de la classe istream et bénéficie donc de toutes les méthodes définies dans cette classe.
#include <iostream>
#include <fstream>
int main() {
// Ouverture du fichier
ifstream fichierEntree("donnees.txt", ios::in) ; // Test d’ouverture du fichier
if (!fichierEntree)
cerr << "Pb d’ouverture de fichier" << endl ; exit (1) ;
}
char buf[1024] ;
// Tant qu’il y a des lignes ds le fichier, on les // lit et on les affiche à l’écran
while (!fichierEntree.eof()) {
fichierEntree.getline(buf, 1024) ; cout << buf << endl ;
}
// Fermeture du fichier fichierEntree.close() ; }
Il existe bien entendu d’autres fonctionnalités utilisables sur ces classes : open (idem au contructeur), seek, tell, flush, eof, …
2.17. Espaces de noms 2.17.1. Définition
namespace Projet A { int i ;
int j=10 ; void f() {…} ; void g() ; }
2.17.2. Utilisation int main () {
using namespace Projet A ; j++ ;
}
2.18. Directives de prétraitement 2.18.1. Forme d’une directive
#_________
2.18.2. #define
#define B 10
// remplace B par 10 dans tout le programme
#define LONG 100
#define LARG (LONG-10) // /!\
…
main() {
int A = 2*LARG ; // si pas de () : 2*100-10 !!!
#define MAX(a,b) ((a)>(b)?(a):(b)) 2.18.3. #undef
#undef LONG // annule le #define LONG … 2.18.4. #include
#include <…> // C/C++ standard
#include "…" // si c’est perso 2.18.5. Compilation conditionnelle
#if … | #if LANGUE == 1
… | #define ERREUR "Fehlermeldung"
#endif | #elif LANGUE == 2
ou | #define ERREUR "Error"
#ifdef … | #elif …
… | #define …
#endif | #endif
ou
#ifndef …
…
#endif
File.h File.cpp Test.cpp Pile.cpp
#include "Pile.h"
void G() ;
…
#include "File.h"
void G() {
… F() ; }
#include "Pile.h"
#include "File.h"
main() {
F() ;
#include "Pile.h"
void F() {
… … } Pile.h
#ifndef PILE_H // si non défini #define PILE_H // on le définit void F() ;
…
#endif
2.19. Structure
2.19.1. Déclaration main()
{
struct Client {
char Nom[25] ; int Code ; float ChAff ; } ;
Client UnCl ; // UnCl de type Client 2.19.2. Accès aux membres
UnCl.Code=25 ; UnCl.ChAff=1000 ;
Strcpy (UnCl.Nom, "Dupont") ; 2.19.3. Utilisation
struct Client {
char Nom[25] ; int Age ;
} ;
Client UnClient, *PClient ; UnClient.Age=20 ; PClient=&UnClient ;
(*PClient).Age=30 ; //PClient -> Age = 30 ; struct Elt
{
Client Donnees ; Elt *pSuivant ; }
nouveau type