1. Introduction au C++



sommaire chapitre 1

1.2. Le langage C

Cette partie est une brève introduction au langage.

1.2.1. Types de données

Il existe en langage C des types dits scalaires (de base) :

On peut également combiner des préfixes :

Voici quelques exemples de combinaisons :

L'opérateur sizeof permet de connaître la taille en octets occupée par un type.

 type   taille
(octets) 
 signed
min 
  signed
max 
 unsigned
min 
 unsigned
max 
 char   1   -128   +127   0   255 
 short int   2   -32768   32767   0   65535 
 int   4   -2147483648   +2147483647   0   4294967295 
 long int   4   2147483648   +2147483647   0   4294967295 
 long lont int   8   -9 223 372 036 854 775 807   +9 223 372 036 854 775 807   0   +18 446 744 073 709 551 615 
Types entiers en langage C en architecture 32 bits
 type   taille
(octets) 
 min    max   décimales 
 float   4   1.2E-38   3.4E+38   6 
 double   8   2.3E-308   1.7E+308   15 
 long double   12 ou 16   3.4E-4932   1.1E+4932   19 
Types flottants en langage C
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main() {
  5.     cout << "sizeof(char) = " << sizeof(char) << endl;
  6.     cout << "sizeof(short int) = " << sizeof(short int) << endl;
  7.     cout << "sizeof(int) = " << sizeof(int) << endl;
  8.     cout << "sizeof(long int) = " << sizeof(long int) << endl;
  9.     cout << "sizeof(long long int) = " << sizeof(long long int) << endl;
  10.     cout << "sizeof(float) = " << sizeof(float) << endl;
  11.     cout << "sizeof(double) = " << sizeof(double) << endl;
  12.     cout << "sizeof(long double) = " << sizeof(long double) << endl;
  13.     return 0;
  14. }
  15.  
****************************
*** architecture 32 bits ***
****************************
sizeof(char) = 1
sizeof(short int) = 2
sizeof(int) = 4
sizeof(long int) = 4
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 12
****************************
*** architecture 64 bits ***
****************************
sizeof(char) = 1
sizeof(short int) = 2
sizeof(int) = 4
sizeof(long int) = 8
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 16

1.2.2. Types et types structurés

On utilise le mot clé struct combiné à typedef afin de définir de nouveau types :

  1. typedef short int int16_t;
  2. typedef unsigned short int uint16_t;
  3.  
  4. /**
  5.  * première forme pour définir une personne
  6.  */
  7. struct _Person1 {
  8.     int age;
  9.     char name[25];
  10. };
  11.  
  12. typedef struct _Person1 Person1;
  13.  
  14. /**
  15.  * deuxième forme pour définir une personne (condensé)
  16.  */
  17. typedef struct {
  18.     int age;
  19.     char name[25];
  20. } Person2;
  21.  
  22. /**
  23.  * structure auto-référencée pour liste chaînée
  24.  */
  25. struct _Link {
  26.     struct _Link *prev, *next;
  27.     int value;
  28. };
  29.  
  30. typedef struct _Link Link;
  31.  

1.2.3. Déclaration des variables

La déclaration de variable se fait en commençant par définir le type suivi de l'identificateur.

  1. // déclaration de x de type entier, non initialisée
  2. int x;
  3.  
  4. // déclaration de y de type entier, initialisée à 0
  5. int y = 0;
  6.  
  7. // plusieurs déclarations
  8. int z, t = 1, u = 2, k;
  9.  
  10. // attention cependant pour les pointeurs
  11. // ici x est un pointeur sur un entier
  12. // y et z sont des entiers
  13. int *x, y, z;
  14.  
  15. // par contre ici x, y ,z sont des pointeurs sur un entier
  16. int *x, *y, *z;
  17.  

1.2.4. Les tableaux

On peut déclarer les tableaux de deux manières différentes :

Attention, pour un tableau composé de N éléments :

  • le premier indice est toujours 0
  • le dernier indice est toujours N-1
  1. // tableau de 20 entiers
  2. int tab_int[20];
  3.  
  4. // initialisation
  5. for (int i=0; i<20; ++i) {
  6.     tab_int[i] = 0;
  7. }
  8.  
  9. // tableau de 100 Person
  10. Person tab_person[100];
  11.  
  12.  

Note : lorsque l'on déclare une variable ou un tableau localement (dans une fonction) celui-ci est alloué dans la pile. Sous Unix/Linux par exemple la taille de la pile est par défaut de 8192 ko (soit 8 Mo) (cf commande shell ulimit -a).

Si on déclare un tableau trop grand on risque de saturer la pile

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main() {
  5.     // note: 2097152 * 4 = 8 * 1024 * 1024
  6.     int tab[2097153];
  7.    
  8.     cout << "hello world !" << endl;
  9.     return 0;
  10. }
  11.  
./c1_segmentation_fault.exe 
Segmentation fault (core dumped)

Les chaînes de caractères

Il n'y a pas de type chaîne de caractères en langage C, une chaîne de caractères est représentée par un tableau de caractères. La fin de la chaine est représentée par le caractère '\0'.

Les chaines sont introduites pas le symbole double guillemet : "abc"

Les caractères sont introduits pas le symbole simple guillemet : 'a'

Une chaine de longueur 3 caractères comme "abc" occupe donc 4 caractères : 'a', 'b', 'c', '\0'.

Plusieurs fonctions issues de la librairire string.h permettent de manipuler les tableaux de caractères :

Les caractères spéciaux sont préfixés par un caractère \ :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. int main(int argc, char *argv[]) {
  6.  
  7.     const char *str1 = "abrac";
  8.     char str2[] = "ada";
  9.    
  10.     char str[30];
  11.    
  12.     strcpy(str, str1);
  13.     strcat(str, str2);
  14.     strncat(str, &str[1], 3);
  15.    
  16.     printf("final string = %s of length %lu\n", str, strlen(str));
  17.    
  18.     int x = 1; int y = 2;
  19.    
  20.     int z = x++ + ++y;
  21.     printf("z = %d\n", z);
  22.     printf("x = %d\n", x);
  23.     printf("y = %d\n", y);
  24.     return 0;
  25. }
  26.  
final string = abracadabra of length 11

1.2.5. Les opérateurs

Attention, certain opérateurs possèdent plusieurs significations, notamment &, |, *.

 Symbole   Signification 
 [ ]   accès élément d'un tableau 
 !   négation logique (NOT) 
 ~   complément à 1 (NOT) 
 + - / *   opérateurs arithmétiques classiques 
 *   accès valeur d'un pointeur 
 ++   pré ou post incrémentation 
 --   pré ou post décrémentation 
 &   et binaire / adresse d'une variable 
 |   ou binaire 
 &&   et logique (AND) 
 ||   ou logique (OR) 
 < >   inférieur, supérieur 
 <= >=   inférieur ou égal, supérieur ou égal 
 =   opérateur d'affectation 
 ==   test d'égalité 
 !=   test de différence 
 .   accès à un attribut d'une structure 
 ->   accès à un attribut d'une structure par pointeur 
Opérateurs en langage C

1.2.5.a  post et pré incrémentation et décrémentation

Les opérateurs de post et pré incrémentation (ou décrémentation) combinés à l'opérateur d'affectation possèdent un comportement différent en fonction qu'ils sont positionnés avant ou après la valeur à modifier.

  • en pré-incrémentation, l'opérateur ++ commence par incrémenter la variable située à sa droite, puis retourne la nouvelle valeur
  • en post-incrémentation, l'opérateur ++ retourne la valeur actuelle de la variable puis l'incrémente
  1. int a = 1;
  2. int b = 1;
  3. int c, d;
  4.  
  5. c = ++a; // like a = a + 1; c = a;
  6. d = b++; // like d = b; b = b + 1;
  7.  
  8. // finally
  9. // a = 2, b = 2, c = 2, d = 1
  10.  

Comment interpréter alors l'expression suivante : z = x +++ y ?

int x = 1; 
int y = 2;

int z = x+++y; // x++ + y donc z = 3, x = 2, y = 2

1.2.5.b  l'opérateur () : ?

Il permet de réaliser un if then else sous forme condensée :

  1. int a = 1;
  2. int b = 2.
  3. int x;
  4.  
  5. if (a < b) {
  6.     x = 0
  7. } else {
  8.     x = 1;
  9. }
  10.  
  11. // ou en condensé
  12. x = (a < b) ? 0 : 1;
  13.  

1.2.6. Les structures de contrôle

1.2.6.a  for

for (initialisations; condition; incrémentations) { 
	instructions;
}
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i, sum, tab[100];
  6.  
  7.     for (i=0; i<100; ++i) {
  8.         tab[i] = i+1;
  9.     }
  10.  
  11.     for (i=0, sum = 0; (i<100) && (sum < 50); ++i) {
  12.         sum += tab[i];
  13.     }
  14.    
  15.     printf("sum = %d\n", sum);
  16.    
  17.     return 0;
  18. }
  19.  

Dans une boucle for / while on peut utiliser deux instructions particulières :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i, sum = 0;
  6.  
  7.     // on calcule la somme des i impairs
  8.     for (i=0; i<100; ++i) {
  9.         // on sort de la boucle lorsque i == 50
  10.         if (i == 50) {
  11.             break;
  12.         }
  13.         // si i est pair alors on passe à i+1
  14.         if ((i % 2) == 0) {
  15.             continue;
  16.         }
  17.        
  18.         sum += i;
  19.     }
  20.  
  21.     printf("sum = %d\n", sum);
  22.    
  23.     return 0;
  24. }
  25.  

1.2.6.b  if then else

if (condition) {
	instructions-then;
} else {
	instructions-else;
}

if (condition) 
	une-instruction-then;
else 
	une-instruction-else;
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i;
  6.    
  7.     srand(19702013);
  8.    
  9.     for (i = 1; i < 100; ++i) {
  10.         if ((i % 2) == 0) {
  11.             printf("%d is even\n", i);
  12.         }
  13.     }
  14.    
  15.     for (i = 1; i < 100; ++i) {
  16.         int x = rand() % 10;
  17.         if (x < 5) {
  18.             printf("%d is lower than 5\n", x);
  19.         } else {
  20.             printf("%d is greater or equal to 5\n", x);
  21.         }
  22.     }
  23.    
  24.     for (i = 1; i < 100; ++i) {
  25.         int x = rand() % 10;
  26.         if (x < 5) {
  27.             printf("%d is lower than 5\n", x);
  28.         } else if (x <= 7) {
  29.             printf("%d is in [5..7]\n", x);
  30.         } else {
  31.             printf("%d is greater than 7\n", x);
  32.         }
  33.     }
  34.    
  35.    
  36. }
  37.  
  38.  

1.2.6.c  while

while (condition) {
	instructions;
}

while (condition) une-instruction;
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i, sum, tab[100];
  6.  
  7.     i=0;
  8.     while (i<100) {
  9.         tab[i] = i+1;
  10.         ++i;
  11.     }
  12.  
  13.     i=0;
  14.     sum = 0;
  15.     while ((i<100) && (sum < 50)) {
  16.         sum += tab[i];
  17.         ++i;
  18.     }
  19.    
  20.     printf("sum = %d\n", sum);
  21.    
  22.     return 0;
  23. }
  24.  

1.2.6.d  repeat until = do while

do {
	instructions;
} while (condition);
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i, sum, tab[100];
  6.  
  7.     i=0;
  8.     while (i<100) {
  9.         tab[i] = i+1;
  10.         ++i;
  11.     }
  12.  
  13.     i=0;
  14.     sum = 0;
  15.     do {
  16.         sum += tab[i];
  17.         ++i;   
  18.     } while ((i<100) && (sum < 50));
  19.    
  20.     printf("sum = %d\n", sum);
  21.    
  22.     return 0;
  23. }
  24.  

1.2.6.e  case of = switch / case

switch (valeur-entière) {
	case VALUE_1: instructions; break;
	case VALUE_2: instructions; break;
	...
	default : instructions; break;
}
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main() {
  5.     int i, sum, tab[100], count[5];
  6.  
  7.     srand(19702013);
  8.    
  9.     for (i=0; i<100; ++i) {
  10.         tab[i] = rand() % 5;
  11.     }
  12.  
  13.     for (i=0; i<5; ++i) {
  14.         count[i] = 0;
  15.     }
  16.    
  17.     sum = 0;
  18.     for (i=0; i<100; ++i) {
  19.         int x = tab[i];
  20.         switch(x) {
  21.             case 0: ++count[x]; break;
  22.             case 1:
  23.             case 2: sum += x; break;
  24.             default: printf("%d not taken into account\n", x);
  25.         }
  26.     }
  27.    
  28.     printf("sum = %d\n", sum);
  29.    
  30.     return 0;
  31. }
  32.  

1.2.7. Les pointeurs et l'allocation mémoire

Un pointeur est une variable qui contient une adresse mémoire

En C, un pointeur possède deux fonctionnalités :

  • il permet de faire référence à une variable de même type.
  • il permet de créer de nouvelles variables / tableaux

Trois opérateurs sont utilisés avec les pointeurs :

Enfin la constante NULL permet d'identifier un pointeur vide, elle est renommée en nullptr en C++ 11.

1.2.7.a  Exemple

Dans l'exemple qui suit:

pointeur

Le symbole * a trois sémantiques différentes :

ce qui, d'un point de vue assembleur, se traduit par exemple par :

; on traduit :
;
; *p = *p + 1
;
; === p est représenté par le registre ebx ===
;
mov  eax, [ebx]  ; r-value en lecture (load)
add  eax, 1      ; ajouter 1 à *p
mov  [ebx], eax  ; l-value en écriture (store)

1.2.7.b  Manipulation de données existantes par l'intermédiaire d'un pointeur

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4.  
  5. int main() {
  6.  
  7.     // déclaration d'une variable entière
  8.     int x = 1;
  9.  
  10.     // déclaration d'un pointeur
  11.     int *p;
  12.  
  13.     // affectation du pointeur pour être positionné sur x
  14.     p = &x;
  15.  
  16.     printf("x = %d, *p = %d\n", x, *p);
  17.    
  18.     // modification de la valeur de x par l'intermédiaire du pointeur
  19.     *p = 2;
  20.  
  21.     // lecture de la valeur de x par l'intermédiaire du pointeur
  22.     int y = *p + 1; // y = 2 + 1 = 3
  23.    
  24.     printf("y = %d, *p = %d\n", y, *p);
  25.  
  26.     // déclaration de q qui pointe sur x puisqu'on lui affecte
  27.     // l'adresse de p
  28.     int *q = p;
  29.    
  30.     printf("*q = %d\n", *q);
  31.  
  32.     return 0;
  33. }
  34.  

1.2.7.c  Création de nouvelles données

Il existe plusieurs fonctions pour allouer et libérer la mémoire :



  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. // ----------------------------------------------
  5. // main function
  6. // ----------------------------------------------
  7. int main() {
  8.     // déclaration d'un pointeur sur un entier
  9.     int *x;
  10.  
  11.     // création de l'espace de stockage nécessaire
  12.     x = (int *) malloc(sizeof(int));
  13.  
  14.     // initialisation de l'entier pointé par x à la valeur 1
  15.     *x = 1;
  16.    
  17.     printf("x = %p, *x = %d\n", x, *x);
  18.    
  19.     // libération de l'espace alloué
  20.     free(x);
  21.    
  22.     return 0;
  23. }  
  24.  
  1. // compiler avec -msse2 car on utilise xmmintrin.h
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <xmmintrin.h>
  5.  
  6. /**
  7.  * classe contenant un entier utilisée pour cet exemple
  8.  */
  9. class MyClass {
  10. protected:
  11.     int value;
  12. public:
  13.     /**
  14.      * constructeur par défaut sans argument
  15.      */
  16.     MyClass() {
  17.         value = 0;
  18.     }
  19.    
  20.     /**
  21.      * constructeur avec valeur d'initialisation
  22.      */
  23.     MyClass(int v) {
  24.         value = v;
  25.     }
  26.    
  27.     /**
  28.      * getter
  29.      */
  30.     int get_value() {
  31.         return value;
  32.     }
  33. };
  34.  
  35. // ----------------------------------------------
  36. // main function
  37. // ----------------------------------------------
  38. int main() {
  39.  
  40.     // allocation d'un tableau de 20 entiers
  41.     int *tab_int;
  42.  
  43.     tab_int = (int *) malloc(20 * sizeof(int));
  44.     // ou
  45.     tab_int = static_cast<int *>(malloc(20 * sizeof(int)));
  46.     // ou
  47.     tab_int = (int *) calloc(20, sizeof(int));
  48.  
  49.     // libération de la mémoire allouée
  50.     free(tab_int);
  51.  
  52.  
  53.     // ==================================
  54.     // allocation avec alignement
  55.     // ==================================
  56.  
  57.     tab_int = (int *) _mm_malloc(20 * sizeof(int), 16);
  58.  
  59.     // libération de la mémoire allouée par _mm_malloc
  60.     _mm_free(tab_int);
  61.  
  62.  
  63.     // tableau de 100 Person
  64.     MyClass *tab_object;
  65.  
  66.     tab_object = (MyClass *) malloc(100 * sizeof(MyClass));
  67.     // ou
  68.     tab_object = static_cast<MyClass *>(malloc(100 * sizeof(MyClass)));
  69.     // ou, mais nécessite de déclarer le constructeur par défaut sans arguments
  70.     tab_object = new MyClass[ 100 ];
  71.  
  72.     for (int i=0; i<100; ++i) {
  73.         printf("%d ", tab_object[i].get_value() );
  74.     }
  75.     // libération
  76.     free(tab_object);
  77.      
  78.     return 0;
  79. }
  80.  
  1. struct _Link {
  2.     struct _Link *prev, *next;
  3.     int value;
  4. };
  5.  
  6. typedef struct _Link Link;
  7.  
  8. Link a = (Link *) malloc(sizeof(Link));
  9. a->prev = a->next = NULL;
  10. a->value = 1;
  11.  
  12.  
  13.  

1.2.8. Entrées et sorties

Pour les entrées et sorties standard on utilise les flux suivants :

Pour la lecture on utilise scanf, fscanf, pour l'écriture printf ou fprintf :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. // ----------------------------------------------
  6. // main function
  7. // ----------------------------------------------
  8. int main() {
  9.     int x, y;
  10.     // str is NULL so it will be allocated by getline
  11.     char *str = NULL;
  12.     size_t size;
  13.    
  14.     // will read two integers followed by '\n'
  15.     fscanf(stdin, "%d%d\n", &x, &y);
  16.    
  17.     printf("x = %d, y = %d\n", x, y);
  18.    
  19.     fflush(stdin);
  20.     getline(&str, &size, stdin);
  21.    
  22.     fprintf(stderr, "str = [%s] of allocated size = %lu and real size %lu\n", str, size, strlen(str));
  23.     free(str);
  24.    
  25.     return 0;
  26. }
  27.  

Lorsqu'on désire ouvrir ou lire un fichier il faut utiliser un descripteur de fichier FILE *, ainsi que les fonctions :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. // ----------------------------------------------
  6. // main function
  7. // ----------------------------------------------
  8. int main() {
  9.     // input file
  10.     FILE *in;
  11.     // output file
  12.     FILE *out;
  13.    
  14.     int i, value;
  15.     const char *file_name = "data.txt";
  16.    
  17.     srand(19702013);
  18.    
  19.     // ------------------------------------------
  20.     // generate file
  21.     // ------------------------------------------
  22.     out = fopen(file_name, "w");
  23.     if (!out) {
  24.         fprintf(stderr, "error: could not open file for writing: %s", file_name);
  25.         exit(EXIT_FAILURE);
  26.     }
  27.    
  28.     for (i = 0; i<10; ++i) {
  29.         fprintf(out, "%d\n", rand() % 100);
  30.     }
  31.    
  32.     for (i = 0; i<5; ++i) {
  33.         int j, length = (rand() % 10) + 2;
  34.         for (j = 0; j<length; ++j) {
  35.             fprintf(out, "%c", (char) (rand() % 26) + 65);
  36.         }
  37.         fprintf(out, "\n");
  38.     }
  39.     fclose(out);
  40.    
  41.     // ------------------------------------------
  42.     // read file
  43.     // ------------------------------------------
  44.     in = fopen(file_name, "r");
  45.     if (!out) {
  46.         fprintf(stderr, "error: could not open file for reading: %s", file_name);
  47.         exit(EXIT_FAILURE);
  48.     }
  49.     for (i = 0; i<10; ++i) {
  50.         fscanf(in, "%d\n", &value);
  51.         printf("read %d\n", value);
  52.     }
  53.     for (i = 0; i<5; ++i) {
  54.         char *str = NULL;
  55.         size_t size;
  56.         getline(&str, &size, in);
  57.         printf("read %s", str);
  58.         free(str);
  59.     }
  60.     fclose(in);
  61.    
  62.     return 0;
  63. }
  64.  

1.2.9. Le préprocesseur et les macro-instructions

En langage C, on réalise la séparation entre l'interface et l'implémentation :

  • l'interface est placée dans un fichier entête (header) d'extension .h et contient les définitions de constantes, de types, le prototype des sous-programmes
  • l'implémentation est placée dans une fichier source d'extension .c qui contient le code des sous-programmes

1.2.9.a  Le fichier header

Il commence toujours par la définition d'un littéral qui permet d'éviter de le charger plusieurs fois ce qui conduirait à générer des erreurs par le compilateur:

#ifndef STACK_H
#define STACK_H
...
#endif

includes

  1. #ifndef STACK_H
  2. #define STACK_H
  3.  
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6.  
  7. #define MAX_ELEMENTS 100
  8.  
  9. typedef struct {
  10.     int index;
  11.     int *elements
  12. } Stack;
  13.  
  14. // initialize data structure
  15. void stack_create(Stack *s);
  16.  
  17. // remove data allocated by stack_create
  18. void stack_delete(Stack *s);
  19.  
  20. // push
  21. void stack_push(Stack *s, int v);
  22.  
  23. // pop
  24. void stack_pop(Stack *s);
  25.  
  26. // return element on top
  27. int stack_top(Stack *s);
  28.  
  29. // return 1 if stack is empty, 0 otherwise
  30. int stack_is_empty(Stack *s);
  31.  
  32. #endif
  33.  
  34.  
  1. #include "c1_stack.h"
  2.  
  3. // initialize data structure
  4. void stack_create(Stack *s) {
  5.     s->index = -1;
  6.     s->elements = (int *) malloc(MAX_ELEMENTS * sizeof(int));
  7. }
  8.  
  9. // remove data allocated by stack_create
  10. void stack_delete(Stack *s) {
  11.     free(s->elements);
  12. }
  13.  
  14. // push
  15. void stack_push(Stack *s, int v) {
  16.     ++s->index;
  17.     if (s->index == MAX_ELEMENTS) {
  18.         printf("stack is full\n");
  19.         exit(1);
  20.     }
  21.     s->elements[s->index] = v;
  22. }
  23.  
  24. // pop
  25. void stack_pop(Stack *s) {
  26.     if (s->index == -1) {
  27.         printf("stack is empty\n");
  28.         exit(1);
  29.     }
  30.     --s->index;
  31. }
  32.  
  33. // return element on top
  34. int stack_top(Stack *s) {
  35.     if (s->index == -1) {
  36.         printf("stack is empty\n");
  37.         exit(1);
  38.     }
  39.     return s->elements[s->index];
  40. }
  41.  
  42. // return 1 if stack is empty, 0 otherwise
  43. int stack_is_empty(Stack *s) {
  44.     return (s->index == -1) ? 1 : 0;
  45. }
  46.  
  47. // main
  48. int main() {
  49.     int i, sum = 0;
  50.     Stack s;
  51.    
  52.     stack_create(&s);
  53.     for (i = 1; i<=10; ++i) {
  54.         stack_push(&s, i);
  55.     }
  56.    
  57.     while  (!stack_is_empty(&s)) {
  58.         sum += stack_top(&s);
  59.         stack_pop(&s);
  60.     }
  61.    
  62.     printf("sum = %d\n", sum);
  63.    
  64.     stack_delete(&s);
  65.     return 0;
  66. }  
  67.  

1.2.9.b  Définition de constantes

Les constantes sont déclarées grâce au mot clé #define identifier code :

#define MAX_ELEMENTS 100
#define MAX_PLUS_1 (MAX_ELEMENTS + 1) 

1.2.9.c  Définition de macro-instructions

Les macro-instructions sont déclarées grâce au mot clé #define identifier(parameters) code et permettent de réutiliser du code :

#define SQUARE(X) (X)*(X)
int x = 2;
int y = SQUARE(x); 
  1. #define SQUARE(X) (X)*(X)
  2. #define JOIN(X,Y) X##Y
  3. #define JOIN_STRING(X,Y) X#Y
  4.  
  5. #include <stdio.h>
  6.  
  7. int main() {
  8.     int x1 = 2, y1;
  9.     float x2 = 3.14, y2;
  10.     const char *str_x1 = JOIN_STRING("x",1);
  11.     const char *str_x2 = JOIN_STRING("x",2);
  12.    
  13.     y1 = SQUARE(x1);
  14.     y2 = SQUARE(x2);
  15.    
  16.     y2 = JOIN(x,1) + JOIN(x,2);
  17.    
  18.     printf("%s + %s  = %f\n", str_x1, str_x2, y2);
  19.     return 0;
  20. }
  21.  
x1 + x2 = 5.140000

1.2.10. Subtilités du langage C

Les règles d'associativité des opérateurs (gauche ou droite) permettent d'écrire de manière concise certains traitements, cependant l'interprétation est difficile pour le novice.

  1. // different version of string copy
  2.  
  3. // ----------------------------------------------
  4. // version 1
  5. // ----------------------------------------------
  6. void copy_str(char *dst, char *src) {
  7.     int i = 0;
  8.     while (src[i] != '\0') {
  9.         dst[i] = src[i];
  10.         ++i;
  11.     }
  12.     // copy last character '\0'
  13.     dst[i] = src[i];
  14. }
  15.  
  16. // ----------------------------------------------
  17. // version 2
  18. // stop condition can be reduced to : src[i]
  19. // assignment dst[i] = src[i] has a value of src[i]
  20. // ----------------------------------------------
  21. void copy_str(char *dst, char *src) {
  22.     int i = 0;
  23.     // while ((dst[i] = src[i]) != '\0') ++i;
  24.     while (dst[i] = src[i]) ++i;
  25. }
  26.  
  27. // ----------------------------------------------
  28. // version 3
  29. // use of pointers and post incrementation
  30. // ----------------------------------------------
  31. void copy_str(char *dst, char *src) {
  32.     while (*dst++ = *src++) ;
  33. }
  34.  
  35.  
  36.  

1.2.11. Passage de paramètres aux sous-programmes : par valeur ou par adresse

Lors de l'appel d'un sous-programme on peut passer les paramètres de trois manières différentes :

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. int a = 1;
  5. int b = 2;
  6.  
  7.  
  8. /**
  9.  * compute (x+1) + (y+1)
  10.  */
  11. int sum_plus(int x, int *y) {
  12.     x = x + 1; // or ++x
  13.     *y = *y + 1; // or ++*y;
  14.        
  15.     return x + *y;
  16. }
  17.  
  18.  
  19. /**
  20.  * structure that is used to compute z = x @ y
  21.  * where @ = +, -, *, /, ...
  22.  */
  23. typedef struct {
  24.     int x, y, z;
  25. } Operation;
  26.  
  27. /**
  28.  * execute sum on operation but call by value
  29.  */
  30. void operation_plus_by_value(Operation op) {
  31.     op.z = op.x + op.y;
  32. }
  33.  
  34. /**
  35.  * execute sum on operation but call by address
  36.  */
  37. void operation_plus_by_address(Operation *op) {
  38.     op->z = op->x + op->y;
  39. }
  40.  
  41. /**
  42.  * execute sum on operation but call by reference
  43.  */
  44. void operation_plus_by_reference(Operation& op) {
  45.     // use the syntax of call by value
  46.     op.z = op.x + op.y;
  47. }
  48.  
  49.  
  50. /**
  51.  * main function
  52.  */
  53. int main() {
  54.     int c;
  55.    
  56.     printf("before sum_plus\n");
  57.     printf("a = %d\n", a);
  58.     printf("b = %d\n", b);
  59.    
  60.     c = sum_plus(a, &b);
  61.    
  62.     printf("-------------\n");
  63.     printf("after sum_plus\n");
  64.     printf("a = %d\n", a);
  65.     printf("b = %d\n", b);
  66.     printf("c = %d\n", c);
  67.  
  68.     // example with structure
  69.     Operation op1;
  70.     op1.x = 1; 
  71.     op1.y = 2;
  72.     op1.z = 0;
  73.     Operation op2 = op1;
  74.        
  75.     // call by value : KO, result won't be registered
  76.     operation_plus_by_value(op1);
  77.     printf("result of by value z = %d\n", op1.z);
  78.    
  79.     // call by address : OK
  80.     operation_plus_by_address(&op1);
  81.     printf("result of by address z = %d\n", op1.z);
  82.  
  83.     // call by reference : OK
  84.     operation_plus_by_reference(op2);
  85.     printf("result of by reference z = %d\n", op2.z);
  86.  
  87.     return 0;
  88. }
  89.  
  90.  
before sum_plus
a = 1
b = 2
-------------
after sum_plus
a = 1
b = 3
c = 5
result of by value z = 0
result of by address z = 3
result of by reference z = 3

paramètres par défaut

Notons qu'en C++, il est possible de passer des paramètres par défaut aux sous-programmes.

1.2.12. Arguments en ligne de commande d'un programme

On propose ici 3 méthodes différentes.

1.2.12.a  méthode argc, argv

Les arguments en ligne de commande peuvent être récupérés à partir de la fonction main qui est la fonction principale appelée après initialisation du programme.

Si aucun argument n'est fourni au programme alors :

Si on fournit $x$ arguments au programme alors :

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4.  
  5. // ----------------------------------------------
  6. // main function
  7. // ----------------------------------------------
  8. int main(int argc, char *argv[]) {
  9.  
  10.     // check that program as at least three arguments
  11.     if (argc <= 3) {
  12.         cerr << "the program needs at least 3 arguments" << endl;
  13.         cerr << argv[0] << " method alpha message" << endl;
  14.         exit(EXIT_FAILURE);
  15.     }
  16.    
  17.     cout << "program's name = " << argv[0] << endl;
  18.     for (int i=1; i<argc; ++i) {
  19.         cout << "argument " << i << " = " << argv[i] << endl;
  20.     }
  21.    
  22.    
  23.     // first argument should be an integer
  24.     int method = 1;
  25.     if (argc > 1) {
  26.         // convert into integer
  27.         method = atoi(argv[1]);
  28.         cout << "method = " << method << endl;
  29.     }
  30.    
  31.     // second argument should be a float
  32.     float alpha = 0.2;
  33.     if (argc > 2) {
  34.         // convert into float
  35.         alpha = atof(argv[2]);
  36.         cout << "alpha = " << alpha << endl;
  37.     }
  38.    
  39.     // second argument should be a string
  40.     char *message = NULL;
  41.     if (argc > 3) {
  42.         message = argv[3];
  43.         cout << "message = " << message << endl;
  44.     }
  45.    
  46.    
  47.     return EXIT_SUCCESS;   
  48. }
  49.  
  50.  
> ./main_args.exe 1 2.15 "hello world" def ghij
program's name = ./main_args.exe
argument 1 = 1
argument 2 = 2.15
argument 3 = hello world
argument 4 = def
argument 5 = ghij
method = 1
alpha = 2.15
message = hello world

1.2.12.b  méthode getopt

Il est également possible d'utiliser une fonctionnalité de la librairie C : getopt

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4. #include <getopt.h>
  5.  
  6. // ----------------------------------------------
  7. // description of possible arguments
  8. // that can be used in
  9. // - short format : -m 1
  10. // - or long format : --method=1
  11. // ----------------------------------------------
  12. static struct option long_options[] = {
  13.     {"method",  required_argument, 0, 'm'},
  14.     {"alpha",   required_argument, 0, 'a'},
  15.     {"message", required_argument, 0, 's'},
  16.     {"verbose", no_argument,       0, 'v'},
  17.     {0,0,0,0}
  18.    
  19. };
  20.  
  21. // ----------------------------------------------
  22. // Definition of a structure that will gather the
  23. // parameters of the program
  24. // ----------------------------------------------
  25. typedef struct {
  26.     int method;
  27.     float alpha;
  28.     char *message;
  29.     bool verbose;
  30. } Parameters;
  31.  
  32. Parameters params;
  33.  
  34. // ----------------------------------------------
  35. // main function
  36. // ----------------------------------------------
  37. int main(int argc, char *argv[]) {
  38.  
  39.     params.verbose = false;
  40.    
  41.     if (argc <= 3) {
  42.         cerr << "the program needs at least 3 arguments" << endl;
  43.         cerr << argv[0] << " method alpha message" << endl;
  44.         exit(EXIT_FAILURE);
  45.     }
  46.    
  47.     cout << "program's name = " << argv[0] << endl;
  48.     for (int i=1; i<argc; ++i) {
  49.         cout << "argument " << i << " = " << argv[i] << endl;
  50.     }
  51.    
  52.     int option_index;
  53.     while (true) {
  54.         option_index = 0;
  55.         // the format m: means that the 'm' option needs one argument
  56.         // the option 'v' does not need argument so it is listed as 'v'
  57.         int c = getopt_long(argc, argv, "m:a:s:v", long_options, &option_index);
  58.         if (c == -1) break;
  59.  
  60.         switch(c) {
  61.             case 'm':
  62.                 params.method = atoi(optarg);
  63.                 break;
  64.             case 'a':
  65.                 params.alpha = atof(optarg);
  66.                 break;
  67.             case 's':
  68.                 params.message = optarg;   
  69.                 break;
  70.             case 'v':
  71.                 params.verbose = true;
  72.                 break; 
  73.         }
  74.     }
  75.    
  76.     if (params.verbose) {
  77.         cout << "method = " << params.method << endl;
  78.         cout << "alpha = " << params.alpha << endl;
  79.         cout << "message = " << params.message << endl;
  80.     }
  81.    
  82.     return EXIT_SUCCESS;   
  83. }
  84.  
  85.  
> ./main_args_getopt.exe --alpha=2.15 --method=1 --message="hello world" def ghij --verbose
program's name = ./main_args_getopt.exe
argument 1 = --alpha=2.15
argument 2 = --method=1
argument 3 = --message=hello world
argument 4 = def
argument 5 = ghij
argument 6 = --verbose
method = 1
alpha = 2.15
message = hello world

1.2.12.c  méthode CLAP (Command Line Argument Parser)

Voici enfin un système un peu plus complexe : cmdlarg.tgz et sa mise en application :

  1. /**
  2.  * program that will convert temperatures from Celsius to Fahrenheit
  3.  * or from Fahrenheit to Celsius
  4.  */
  5. #include "command_line_argument_parser.h"
  6. #include <fstream>
  7. #include <cmath>
  8.  
  9. // --------------------------------------------------------
  10. // variables that the programmer must define
  11. // --------------------------------------------------------
  12. // name of program
  13. string program_name = "temperature.exe";
  14. // short description of what the program does
  15. string program_desc = "convert temperatures between celsius and fahrenheit";
  16.  
  17. // --------------------------------------------------------
  18. // variables related to command line arguments
  19. // --------------------------------------------------------
  20. // verbose level
  21. u32    verbose = 1;
  22.  
  23. float  initial_temperature = 0;
  24. float  final_temperature = 100.0;
  25. float  incr_temperature = 5.0;
  26.  
  27. // unit of initial to final temperatures
  28. u32    unit_id = 0;
  29. vector<string> unit_options = {
  30.     "celsius",
  31.     "fahrenheit"
  32. };
  33.  
  34. /**
  35.  * trigger function for command line argument used to
  36.  * check that temperatures are set correctly
  37.  */
  38. void trigger_temperature() {
  39.     if (initial_temperature >= final_temperature) {
  40.         ostringstream oss;
  41.         oss << "initial temperature (=" << initial_temperature << ") ";
  42.         oss << "should be inferior to final temperature (=" << final_temperature << ")";
  43.         throw std::logic_error(oss.str());
  44.     }
  45. }
  46.  
  47. /**
  48.  * convert from celsius to fahrenheit
  49.  */    
  50. void c2f() {
  51.     float temperature = initial_temperature;
  52.     while (temperature <= final_temperature) {
  53.         float f = 32.0 + (temperature * 5.0) / 9.0;
  54.         if (verbose > 0) { 
  55.             cout << temperature << "°C = " << f << "°F" << endl;
  56.         }
  57.         temperature += incr_temperature;
  58.     }
  59. }
  60.  
  61. /**
  62.  * convert from fahrenheit to celsius
  63.  */
  64. void f2c() {
  65.     float temperature = initial_temperature;
  66.     while (temperature <= final_temperature) {
  67.         float c = (temperature -32.0) * 5.0 / 9.0;
  68.         if (verbose > 0) { 
  69.             cout << temperature << "°F = " << c << "°C" << endl;
  70.         }  
  71.         temperature += incr_temperature;
  72.     }
  73. }
  74.  
  75. /**
  76.  * main function
  77.  */
  78. int main(int argc, char *argv[]) {
  79.     // declare command line parser
  80.     clap::CommandLineParser cl(program_name, program_desc,
  81.         argc, const_cast<char **>( argv ));
  82.        
  83.     try {
  84.        
  85.         // --------------------------------------------------------------
  86.         // add options 
  87.         // --------------------------------------------------------------
  88.        
  89.         // verbose level (0 = silent)
  90.         cl.add_natural("verbose", 'v', &verbose,
  91.                 "verbose level, chose between 0 (silent) or 1, default is 1");
  92.                
  93.         // unit of initial and final temperatures      
  94.         cl.add_options("unit", 'u', &unit_id,
  95.                 &unit_options, "unit of initial and final temperature");
  96.        
  97.         // option with trigger, increment
  98.         cl.add_float("incr-temperature", 'c', &incr_temperature,
  99.                 "temperature incrementation", &trigger_temperature);               
  100.  
  101.         // options that need to be set and use trigger
  102.        
  103.         // set initial temperature                     
  104.         cl.add_float("initial-temperature", 'i', &initial_temperature,
  105.                 "initial temperature", &trigger_temperature)->set_needed();
  106.                
  107.         // set final temperature       
  108.         cl.add_float("final-temperature", 'f', &final_temperature,
  109.                 "final temperature", &trigger_temperature)->set_needed();
  110.                                
  111.         // --------------------------------------------------------------
  112.         // parse command line arguments
  113.         // --------------------------------------------------------------
  114.         cl.parse();
  115.            
  116.         // --------------------------------------------------------------
  117.         // your code here
  118.         // --------------------------------------------------------------
  119.         if (unit_id == 0) {
  120.             c2f();
  121.         } else {
  122.             f2c();
  123.         }      
  124.        
  125.     } catch(exception& e) {
  126.         cl.report_error(e);
  127.     }
  128.  
  129.     return 0;
  130. }
  131.  
./bin/temperature.exe -h
NAME
	temperature.exe - convert temperatures between celsius and 
	fahrenheit 

SYNOPSIS
	temperature.exe [OPTIONS] ... 

DESCRIPTION

	--final-temperature=FLOAT or -f FLOAT
		final temperature 

	--help or -h
		help flag, print synopsis 

	--incr-temperature=FLOAT or -c FLOAT
		temperature incrementation 

	--initial-temperature=FLOAT or -i FLOAT
		initial temperature 

	--output=STRING or -o STRING
		output file, used to redirect output 

	--unit=VALUE or -u VALUE
	  where value=celsius, fahrenheit
		unit of initial and final temperature 

	--verbose=NATURAL or -v NATURAL
		verbose level, chose between 0 (silent) or 1, default is 1 

1.2.13. Variables d'environnement

On peut récupérer le contenu des variables d'environnement grâce à la fonction getenv et utiliser setenv ou putenv pour définir une variable :

#include <stdlib.h>
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite); // overwrite=1 pour écrire
int putenv(char *string); // ici string est de la forme name=value

Voici un exemple qui extrait les répertoires depuis le PATH, puis liste les fichiers de chaque répertoire.

Note : les modifications de l'environnement sont locales au programme et donc perdues après arrêt du programme.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <dirent.h>
  6. #include <errno.h>
  7.  
  8. #define BUFFER_MAX_SIZE 200
  9. #define SIZE(x) ((x) > BUFFER_MAX_SIZE) ? BUFFER_MAX_SIZE : x
  10.  
  11. const char *MYLIB_PATH = "MYLIB_PATH=/home/richer";
  12.  
  13. /**
  14.  * list files in directory identified by its name
  15.  */
  16. void find_files(char *dir_name) {
  17.     DIR *dp;
  18.     struct dirent *ep;
  19.  
  20.     dp = opendir (dir_name);
  21.     if (dp != NULL) {
  22.         while (ep = readdir (dp)) {
  23.             if (!((strcmp(ep->d_name, ".") == 0) ||
  24.                   (strcmp(ep->d_name, "..") == 0))) {
  25.                 printf("- %s\n", ep->d_name);
  26.             }
  27.         }
  28.         (void) closedir (dp);
  29.     } else {
  30.         perror ("Couldn't open the directory");
  31.     }
  32. }
  33.  
  34. /**
  35.  * main function
  36.  */
  37. int main(int argc, char *argv[]) {
  38.     char *path, *pbegin, *pend;
  39.     char buffer[BUFFER_MAX_SIZE + 1];
  40.    
  41.     path = getenv("PATH");
  42.    
  43.     // get all paths and find files
  44.     /*
  45.     pbegin = path;
  46.     pend = strchr(pbegin, ':');
  47.    
  48.     while (*pbegin != '\0') {
  49.         if (pend == NULL) {
  50.             strcpy(buffer, pbegin);
  51.         } else {
  52.             int size = pend-pbegin;
  53.             strncpy(buffer, pbegin, SIZE(size));
  54.             buffer[pend-pbegin] = '\0';
  55.         }  
  56.        
  57.         printf("===========================\n");
  58.         printf("directory %s\n", buffer);
  59.         printf("===========================\n");
  60.        
  61.         find_files(buffer);
  62.        
  63.         if (pend == NULL) break;
  64.        
  65.         pbegin = ++pend;
  66.         pend = strchr(pbegin, ':');
  67.        
  68.     }  
  69.     */
  70.    
  71.     // or simplified version using strtok
  72.     char *p = strtok(path, ":");
  73.     while (p != NULL) {
  74.         printf("===========================\n");
  75.         printf("directory %s\n", p);
  76.         printf("===========================\n");
  77.        
  78.         find_files(p);
  79.        
  80.         p = strtok(NULL, ":");
  81.     }
  82.  
  83.     // play with setenv a
  84.        
  85.     if (!setenv("MYLIB_PATH", "/home/richer", 1) < 0) {
  86.         fprintf(stderr, "could not set MYLIB_PATH with setenv !!!!\n");
  87.     }
  88.    
  89.     if (putenv(const_cast<char *>(MYLIB_PATH))!=0) {
  90.         fprintf(stderr, "could not set MYLIB_PATH with putenv => %d\n%s !!!!\n",
  91.         errno, strerror(errno));
  92.     }
  93.    
  94.     system("echo $MYLIB_PATH");
  95.     system("/home/richer/public_html/ens/inra/test_mylib.sh");
  96.    
  97.     return EXIT_SUCCESS;
  98. }
  99.  
could not set MYLIB_PATH with setenv !!!!
could not set MYLIB_PATH with putenv => 2
No such file or directory !!!!
/home/richer
/home/richer/public_html/ens/inra/test_mylib.sh: 2: /home/richer/public_html/ens/inra/test_mylib.sh: MYLIB_PATH: not found
MYLIB_PATH =