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) :
- char : charactère (octet)
- int : entier (32 ou 64 bits)
- float : réel 32 bits format IEEE 754
- double : réel 64 bits format IEEE 754
On peut également combiner des préfixes :
- unsigned pour spécifier une donnée non signée (entier naturel)
- short ou long pour modifier le nombres d'octets utilisés pour le codage
Voici quelques exemples de combinaisons :
- unsigned char
- short int (16 bits)
- long int
- unsigned int
- unsigned short int
- unsigned long int
- long long int
- long double
L'opérateur sizeof permet de connaître la taille en octets occupée par un type.
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
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
#include <iostream>
using namespace std;
int main() {
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(short int) = " << sizeof(short int) << endl;
cout << "sizeof(int) = " << sizeof(int) << endl;
cout << "sizeof(long int) = " << sizeof(long int) << endl;
cout << "sizeof(long long int) = " << sizeof(long long int) << endl;
cout << "sizeof(float) = " << sizeof(float) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << "sizeof(long double) = " << sizeof(long double) << endl;
return 0;
}
****************************
*** 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 :
typedef short int int16_t;
typedef unsigned short int uint16_t;
/**
* première forme pour définir une personne
*/
struct _Person1 {
int age;
char name[25];
};
typedef struct _Person1 Person1;
/**
* deuxième forme pour définir une personne (condensé)
*/
typedef struct {
int age;
char name[25];
} Person2;
/**
* structure auto-référencée pour liste chaînée
*/
struct _Link {
struct _Link *prev, *next;
int value;
};
typedef struct _Link Link;
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.
// déclaration de x de type entier, non initialisée
int x;
// déclaration de y de type entier, initialisée à 0
int y = 0;
// plusieurs déclarations
int z, t = 1, u = 2, k;
// attention cependant pour les pointeurs
// ici x est un pointeur sur un entier
// y et z sont des entiers
int *x, y, z;
// par contre ici x, y ,z sont des pointeurs sur un entier
int *x, *y, *z;
1.2.4. Les tableaux
On peut déclarer les tableaux de deux manières différentes :
- statique : la taille est connue, le tableau est défini dans le segment de données du programme (si variable globale), ou la pile (si variable locale ! Attention !)
- dynamique : la taille n'est pas forcément connue à la compilation, le tableau est alloué dans le tas (heap), il faut réaliser l'allocation mémoire du tableau
Attention, pour un tableau composé de N éléments :
- le premier indice est toujours 0
- le dernier indice est toujours N-1
// tableau de 20 entiers
int tab_int[20];
// initialisation
for (int i=0; i<20; ++i) {
tab_int[i] = 0;
}
// tableau de 100 Person
Person tab_person[100];
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
#include <iostream>
using namespace std;
int main() {
// note: 2097152 * 4 = 8 * 1024 * 1024
int tab[2097153];
cout << "hello world !" << endl;
return 0;
}
./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 :
- size_t strlen(const char *s); : donne la longueur d'une chaine
- char *strcpy(char *dest, const char *src); : copie une chaine source dans une chaine destination
- char *strncpy(char *dest, const char *src, size_t n); idem mais au maximum n caractères
- char *strcat(char *dest, const char *src); : concaténation
- char *strncat(char *dest, const char *src, size_t n); : concaténation avec au maximum n caractères
Les caractères spéciaux sont préfixés par un caractère \ :
- \n : new line
- \r : retour chariot
- \t : tabulation horizontale
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
const char *str1 = "abrac";
char str2[] = "ada";
char str[30];
printf("final string = %s of length %lu\n", str
, strlen(str
));
int x = 1; int y = 2;
int z = x++ + ++y;
return 0;
}
final string = abracadabra of length 11
1.2.5. Les opérateurs
Attention, certain opérateurs possèdent plusieurs significations, notamment
&, |, *.
[ ] |
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
int a = 1;
int b = 1;
int c, d;
c = ++a; // like a = a + 1; c = a;
d = b++; // like d = b; b = b + 1;
// finally
// a = 2, b = 2, c = 2, d = 1
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 :
int a = 1;
int b = 2.
int x;
if (a < b) {
x = 0
} else {
x = 1;
}
// ou en condensé
x = (a < b) ? 0 : 1;
1.2.6. Les structures de contrôle
1.2.6.a for
for (initialisations; condition; incrémentations) {
instructions;
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, sum, tab[100];
for (i=0; i<100; ++i) {
tab[i] = i+1;
}
for (i=0, sum = 0; (i<100) && (sum < 50); ++i) {
sum += tab[i];
}
return 0;
}
Dans une boucle for / while on peut utiliser deux instructions particulières :
- break : permet de sortir de la boucle
- continue : permet de sauter les instructions qui suivent
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, sum = 0;
// on calcule la somme des i impairs
for (i=0; i<100; ++i) {
// on sort de la boucle lorsque i == 50
if (i == 50) {
break;
}
// si i est pair alors on passe à i+1
if ((i % 2) == 0) {
continue;
}
sum += i;
}
return 0;
}
1.2.6.b if then else
if (condition) {
instructions-then;
} else {
instructions-else;
}
if (condition)
une-instruction-then;
else
une-instruction-else;
#include <stdio.h>
#include <stdlib.h>
int main() {
int i;
for (i = 1; i < 100; ++i) {
if ((i % 2) == 0) {
}
}
for (i = 1; i < 100; ++i) {
if (x < 5) {
printf("%d is lower than 5\n", x
);
} else {
printf("%d is greater or equal to 5\n", x
);
}
}
for (i = 1; i < 100; ++i) {
if (x < 5) {
printf("%d is lower than 5\n", x
);
} else if (x <= 7) {
printf("%d is in [5..7]\n", x
);
} else {
printf("%d is greater than 7\n", x
);
}
}
}
1.2.6.c while
while (condition) {
instructions;
}
while (condition) une-instruction;
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, sum, tab[100];
i=0;
while (i<100) {
tab[i] = i+1;
++i;
}
i=0;
sum = 0;
while ((i<100) && (sum < 50)) {
sum += tab[i];
++i;
}
return 0;
}
1.2.6.d repeat until = do while
do {
instructions;
} while (condition);
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, sum, tab[100];
i=0;
while (i<100) {
tab[i] = i+1;
++i;
}
i=0;
sum = 0;
do {
sum += tab[i];
++i;
} while ((i<100) && (sum < 50));
return 0;
}
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;
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, sum, tab[100], count[5];
for (i=0; i<100; ++i) {
}
for (i=0; i<5; ++i) {
count[i] = 0;
}
sum = 0;
for (i=0; i<100; ++i) {
int x = tab[i];
switch(x) {
case 0: ++count[x]; break;
case 1:
case 2: sum += x; break;
default: printf("%d not taken into account\n", x
);
}
}
return 0;
}
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 :
- * qui permet à la fois
- de déclarer le pointeur
- ou de faire référence à la valeur sur laquelle il pointe
- & qui donne l'adresse d'une variable
- -> qui donne accès à un attribut d'une structure de données
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:
- p est un pointeur sur un entier et occupe 4 octets = sizeof(int *), il est situé à l'adresse 64 en mémoire.
- la variable entière x est située à l'adresse mémoire 32 et contient la valeur -1 codée sur 4 octets = sizeof(int)
- du fait de la dualité pointeur / tableau, p peut être vu comme un tableau tel que:
- p[0] à pour adresse celle de la variable x, c'est à dire 32
- p[1] à pour adresse les 4 octets consécutifs à x, soit 36
- p[i] à pour adresse 32 + i * sizeof(int)
- en particulier p[8] à pour adresse celle du pointeur p, soit 64
Le symbole * a trois sémantiques différentes :
- dans la déclaration int *p; il signifie pointeur sur un entier
- dans l'affectation *p = *p + 1;
- situé à droite du symbole = (right-value, valeur en lecture), il signifie la valeur à l'adresse p
- situé à gauche su symbole = (left-value, valeur en écriture), il signifie affecter à l'adresse contenue dans p
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
#include <stdlib.h>
#include <stdio.h>
int main() {
// déclaration d'une variable entière
int x = 1;
// déclaration d'un pointeur
int *p;
// affectation du pointeur pour être positionné sur x
p = &x;
printf("x = %d, *p = %d\n", x
, *p
);
// modification de la valeur de x par l'intermédiaire du pointeur
*p = 2;
// lecture de la valeur de x par l'intermédiaire du pointeur
int y = *p + 1; // y = 2 + 1 = 3
printf("y = %d, *p = %d\n", y
, *p
);
// déclaration de q qui pointe sur x puisqu'on lui affecte
// l'adresse de p
int *q = p;
return 0;
}
1.2.7.c Création de nouvelles données
Il existe plusieurs fonctions pour allouer et libérer la mémoire :
- void *malloc(size_t size) : alloue size octets dans le tas (heap)
- void *calloc(size_t nmemb, size_t size) : alloue nmemb*size octets
- free(void *ptr) : libère la mémoire allouée
- void *_mm_malloc(size_t size, size_t align) : allocation avec alignement de l'adresse sur une valeur multiple de align (choisir 16 ou 32)
- void _mm_free(void *ptr) : libère la mémoire allouée par mm_malloc
#include <stdlib.h>
#include <stdio.h>
// ----------------------------------------------
// main function
// ----------------------------------------------
int main() {
// déclaration d'un pointeur sur un entier
int *x;
// création de l'espace de stockage nécessaire
x
= (int *) malloc(sizeof(int));
// initialisation de l'entier pointé par x à la valeur 1
*x = 1;
printf("x = %p, *x = %d\n", x
, *x
);
// libération de l'espace alloué
return 0;
}
// compiler avec -msse2 car on utilise xmmintrin.h
#include <stdlib.h>
#include <stdio.h>
#include <xmmintrin.h>
/**
* classe contenant un entier utilisée pour cet exemple
*/
class MyClass {
protected:
int value;
public:
/**
* constructeur par défaut sans argument
*/
MyClass() {
value = 0;
}
/**
* constructeur avec valeur d'initialisation
*/
MyClass(int v) {
value = v;
}
/**
* getter
*/
int get_value() {
return value;
}
};
// ----------------------------------------------
// main function
// ----------------------------------------------
int main() {
// allocation d'un tableau de 20 entiers
int *tab_int;
tab_int
= (int *) malloc(20 * sizeof(int));
// ou
tab_int
= static_cast
<int *>(malloc(20 * sizeof(int)));
// ou
tab_int
= (int *) calloc(20, sizeof(int));
// libération de la mémoire allouée
// ==================================
// allocation avec alignement
// ==================================
tab_int = (int *) _mm_malloc(20 * sizeof(int), 16);
// libération de la mémoire allouée par _mm_malloc
_mm_free(tab_int);
// tableau de 100 Person
MyClass *tab_object;
tab_object
= (MyClass
*) malloc(100 * sizeof(MyClass
));
// ou
tab_object
= static_cast
<MyClass
*>(malloc(100 * sizeof(MyClass
)));
// ou, mais nécessite de déclarer le constructeur par défaut sans arguments
tab_object = new MyClass[ 100 ];
for (int i=0; i<100; ++i) {
printf("%d ", tab_object
[i
].
get_value() );
}
// libération
return 0;
}
struct _Link {
struct _Link *prev, *next;
int value;
};
typedef struct _Link Link;
Link a
= (Link
*) malloc(sizeof(Link
));
a->prev = a->next = NULL;
a->value = 1;
1.2.8. Entrées et sorties
Pour les entrées et sorties standard on utilise les flux suivants :
- stdout : affichage standard écran/console, bufferisé
- stderr : affichade des erreurs écran/console, non bufferisé
- stdin : entrée standard, clavier
Pour la lecture on utilise scanf, fscanf, pour l'écriture printf ou fprintf :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ----------------------------------------------
// main function
// ----------------------------------------------
int main() {
int x, y;
// str is NULL so it will be allocated by getline
char *str = NULL;
size_t size;
// will read two integers followed by '\n'
fscanf(stdin
, "%d%d\n", &x
, &y
);
printf("x = %d, y = %d\n", x
, y
);
getline(&str, &size, stdin);
fprintf(stderr
, "str = [%s] of allocated size = %lu and real size %lu\n", str
, size
, strlen(str
));
return 0;
}
Lorsqu'on désire ouvrir ou lire un fichier il faut utiliser un descripteur de fichier
FILE *, ainsi que les fonctions :
- FILE *fopen(const char *path, const char *mode);
- int fclose(FILE *fp);
- pour les fichiers textes on utilise :
- int fscanf(FILE *stream, const char *format, ...)
- int fprintf(FILE *stream, const char *format, ...)
- pour les fichier binaires, on utilise :
- size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ----------------------------------------------
// main function
// ----------------------------------------------
int main() {
// input file
FILE *in;
// output file
FILE *out;
int i, value;
const char *file_name = "data.txt";
// ------------------------------------------
// generate file
// ------------------------------------------
out
= fopen(file_name
, "w");
if (!out) {
fprintf(stderr
, "error: could not open file for writing: %s", file_name
);
}
for (i = 0; i<10; ++i) {
}
for (i = 0; i<5; ++i) {
int j
, length
= (rand() % 10) + 2;
for (j = 0; j<length; ++j) {
}
}
// ------------------------------------------
// read file
// ------------------------------------------
in
= fopen(file_name
, "r");
if (!out) {
fprintf(stderr
, "error: could not open file for reading: %s", file_name
);
}
for (i = 0; i<10; ++i) {
}
for (i = 0; i<5; ++i) {
char *str = NULL;
size_t size;
getline(&str, &size, in);
}
return 0;
}
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
#ifndef STACK_H
#define STACK_H
#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENTS 100
typedef struct {
int index;
int *elements
} Stack;
// initialize data structure
void stack_create(Stack *s);
// remove data allocated by stack_create
void stack_delete(Stack *s);
// push
void stack_push(Stack *s, int v);
// pop
void stack_pop(Stack *s);
// return element on top
int stack_top(Stack *s);
// return 1 if stack is empty, 0 otherwise
int stack_is_empty(Stack *s);
#endif
#include "c1_stack.h"
// initialize data structure
void stack_create(Stack *s) {
s->index = -1;
s
->elements
= (int *) malloc(MAX_ELEMENTS
* sizeof(int));
}
// remove data allocated by stack_create
void stack_delete(Stack *s) {
}
// push
void stack_push(Stack *s, int v) {
++s->index;
if (s->index == MAX_ELEMENTS) {
}
s->elements[s->index] = v;
}
// pop
void stack_pop(Stack *s) {
if (s->index == -1) {
}
--s->index;
}
// return element on top
int stack_top(Stack *s) {
if (s->index == -1) {
}
return s->elements[s->index];
}
// return 1 if stack is empty, 0 otherwise
int stack_is_empty(Stack *s) {
return (s->index == -1) ? 1 : 0;
}
// main
int main() {
int i, sum = 0;
Stack s;
stack_create(&s);
for (i = 1; i<=10; ++i) {
stack_push(&s, i);
}
while (!stack_is_empty(&s)) {
sum += stack_top(&s);
stack_pop(&s);
}
stack_delete(&s);
return 0;
}
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);
#define SQUARE(X) (X)*(X)
#define JOIN(X,Y) X##Y
#define JOIN_STRING(X,Y) X#Y
#include <stdio.h>
int main() {
int x1 = 2, y1;
float x2 = 3.14, y2;
const char *str_x1 = JOIN_STRING("x",1);
const char *str_x2 = JOIN_STRING("x",2);
y1 = SQUARE(x1);
y2 = SQUARE(x2);
y2 = JOIN(x,1) + JOIN(x,2);
printf("%s + %s = %f\n", str_x1
, str_x2
, y2
);
return 0;
}
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.
// different version of string copy
// ----------------------------------------------
// version 1
// ----------------------------------------------
void copy_str(char *dst, char *src) {
int i = 0;
while (src[i] != '\0') {
dst[i] = src[i];
++i;
}
// copy last character '\0'
dst[i] = src[i];
}
// ----------------------------------------------
// version 2
// stop condition can be reduced to : src[i]
// assignment dst[i] = src[i] has a value of src[i]
// ----------------------------------------------
void copy_str(char *dst, char *src) {
int i = 0;
// while ((dst[i] = src[i]) != '\0') ++i;
while (dst[i] = src[i]) ++i;
}
// ----------------------------------------------
// version 3
// use of pointers and post incrementation
// ----------------------------------------------
void copy_str(char *dst, char *src) {
while (*dst++ = *src++) ;
}
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 :
- par valeur : on crée une copie d'une valeur existante, on peut modifier la valeur
à l'intérieur du sous-programme mais elle ne sera pas modifiée à l'extérieur
- par adresse : on travaille directement sur la valeur, si on la modifie à l'intérieur
du sous-programme elle sera modifiée à l'extérieur
- par reference : introduit en C++, grosso modo, on peut dire que c'est un passage par adresse mais
qui utilise la syntaxe d'un passage par valeur
#include <stdlib.h>
#include <stdio.h>
int a = 1;
int b = 2;
/**
* compute (x+1) + (y+1)
*/
int sum_plus(int x, int *y) {
x = x + 1; // or ++x
*y = *y + 1; // or ++*y;
return x + *y;
}
/**
* structure that is used to compute z = x @ y
* where @ = +, -, *, /, ...
*/
typedef struct {
int x, y, z;
} Operation;
/**
* execute sum on operation but call by value
*/
void operation_plus_by_value(Operation op) {
op.z = op.x + op.y;
}
/**
* execute sum on operation but call by address
*/
void operation_plus_by_address(Operation *op) {
op->z = op->x + op->y;
}
/**
* execute sum on operation but call by reference
*/
void operation_plus_by_reference(Operation& op) {
// use the syntax of call by value
op.z = op.x + op.y;
}
/**
* main function
*/
int main() {
int c;
c = sum_plus(a, &b);
// example with structure
Operation op1;
op1.x = 1;
op1.y = 2;
op1.z = 0;
Operation op2 = op1;
// call by value : KO, result won't be registered
operation_plus_by_value(op1);
printf("result of by value z = %d\n", op1.
z);
// call by address : OK
operation_plus_by_address(&op1);
printf("result of by address z = %d\n", op1.
z);
// call by reference : OK
operation_plus_by_reference(op2);
printf("result of by reference z = %d\n", op2.
z);
return 0;
}
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 :
- argc = 1
- argv[0] = nom du programme
Si on fournit $x$ arguments au programme alors :
- argc = x + 1
- argv[0] = nom du programme
- argv[1] = premier argument
- argv[x] = dernier argument
#include <iostream>
#include <cstdlib>
using namespace std;
// ----------------------------------------------
// main function
// ----------------------------------------------
int main(int argc, char *argv[]) {
// check that program as at least three arguments
if (argc <= 3) {
cerr << "the program needs at least 3 arguments" << endl;
cerr << argv[0] << " method alpha message" << endl;
}
cout << "program's name = " << argv[0] << endl;
for (int i=1; i<argc; ++i) {
cout << "argument " << i << " = " << argv[i] << endl;
}
// first argument should be an integer
int method = 1;
if (argc > 1) {
// convert into integer
cout << "method = " << method << endl;
}
// second argument should be a float
float alpha = 0.2;
if (argc > 2) {
// convert into float
cout << "alpha = " << alpha << endl;
}
// second argument should be a string
char *message = NULL;
if (argc > 3) {
message = argv[3];
cout << "message = " << message << endl;
}
return EXIT_SUCCESS;
}
> ./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
#include <iostream>
#include <cstdlib>
using namespace std;
#include <getopt.h>
// ----------------------------------------------
// description of possible arguments
// that can be used in
// - short format : -m 1
// - or long format : --method=1
// ----------------------------------------------
static struct option long_options[] = {
{"method", required_argument, 0, 'm'},
{"alpha", required_argument, 0, 'a'},
{"message", required_argument, 0, 's'},
{"verbose", no_argument, 0, 'v'},
{0,0,0,0}
};
// ----------------------------------------------
// Definition of a structure that will gather the
// parameters of the program
// ----------------------------------------------
typedef struct {
int method;
float alpha;
char *message;
bool verbose;
} Parameters;
Parameters params;
// ----------------------------------------------
// main function
// ----------------------------------------------
int main(int argc, char *argv[]) {
params.verbose = false;
if (argc <= 3) {
cerr << "the program needs at least 3 arguments" << endl;
cerr << argv[0] << " method alpha message" << endl;
}
cout << "program's name = " << argv[0] << endl;
for (int i=1; i<argc; ++i) {
cout << "argument " << i << " = " << argv[i] << endl;
}
int option_index;
while (true) {
option_index = 0;
// the format m: means that the 'm' option needs one argument
// the option 'v' does not need argument so it is listed as 'v'
int c = getopt_long(argc, argv, "m:a:s:v", long_options, &option_index);
if (c == -1) break;
switch(c) {
case 'm':
params.
method = atoi(optarg
);
break;
case 'a':
params.
alpha = atof(optarg
);
break;
case 's':
params.message = optarg;
break;
case 'v':
params.verbose = true;
break;
}
}
if (params.verbose) {
cout << "method = " << params.method << endl;
cout << "alpha = " << params.alpha << endl;
cout << "message = " << params.message << endl;
}
return EXIT_SUCCESS;
}
> ./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 :
/**
* program that will convert temperatures from Celsius to Fahrenheit
* or from Fahrenheit to Celsius
*/
#include "command_line_argument_parser.h"
#include <fstream>
#include <cmath>
// --------------------------------------------------------
// variables that the programmer must define
// --------------------------------------------------------
// name of program
string program_name = "temperature.exe";
// short description of what the program does
string program_desc = "convert temperatures between celsius and fahrenheit";
// --------------------------------------------------------
// variables related to command line arguments
// --------------------------------------------------------
// verbose level
u32 verbose = 1;
float initial_temperature = 0;
float final_temperature = 100.0;
float incr_temperature = 5.0;
// unit of initial to final temperatures
u32 unit_id = 0;
vector<string> unit_options = {
"celsius",
"fahrenheit"
};
/**
* trigger function for command line argument used to
* check that temperatures are set correctly
*/
void trigger_temperature() {
if (initial_temperature >= final_temperature) {
ostringstream oss;
oss << "initial temperature (=" << initial_temperature << ") ";
oss << "should be inferior to final temperature (=" << final_temperature << ")";
throw std::logic_error(oss.str());
}
}
/**
* convert from celsius to fahrenheit
*/
void c2f() {
float temperature = initial_temperature;
while (temperature <= final_temperature) {
float f = 32.0 + (temperature * 5.0) / 9.0;
if (verbose > 0) {
cout << temperature << "°C = " << f << "°F" << endl;
}
temperature += incr_temperature;
}
}
/**
* convert from fahrenheit to celsius
*/
void f2c() {
float temperature = initial_temperature;
while (temperature <= final_temperature) {
float c = (temperature -32.0) * 5.0 / 9.0;
if (verbose > 0) {
cout << temperature << "°F = " << c << "°C" << endl;
}
temperature += incr_temperature;
}
}
/**
* main function
*/
int main(int argc, char *argv[]) {
// declare command line parser
clap::CommandLineParser cl(program_name, program_desc,
argc, const_cast<char **>( argv ));
try {
// --------------------------------------------------------------
// add options
// --------------------------------------------------------------
// verbose level (0 = silent)
cl.add_natural("verbose", 'v', &verbose,
"verbose level, chose between 0 (silent) or 1, default is 1");
// unit of initial and final temperatures
cl.add_options("unit", 'u', &unit_id,
&unit_options, "unit of initial and final temperature");
// option with trigger, increment
cl.add_float("incr-temperature", 'c', &incr_temperature,
"temperature incrementation", &trigger_temperature);
// options that need to be set and use trigger
// set initial temperature
cl.add_float("initial-temperature", 'i', &initial_temperature,
"initial temperature", &trigger_temperature)->set_needed();
// set final temperature
cl.add_float("final-temperature", 'f', &final_temperature,
"final temperature", &trigger_temperature)->set_needed();
// --------------------------------------------------------------
// parse command line arguments
// --------------------------------------------------------------
cl.parse();
// --------------------------------------------------------------
// your code here
// --------------------------------------------------------------
if (unit_id == 0) {
c2f();
} else {
f2c();
}
} catch(exception& e) {
cl.report_error(e);
}
return 0;
}
./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.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#define BUFFER_MAX_SIZE 200
#define SIZE(x) ((x) > BUFFER_MAX_SIZE) ? BUFFER_MAX_SIZE : x
const char *MYLIB_PATH = "MYLIB_PATH=/home/richer";
/**
* list files in directory identified by its name
*/
void find_files(char *dir_name) {
DIR *dp;
struct dirent *ep;
dp = opendir (dir_name);
if (dp != NULL) {
while (ep = readdir (dp)) {
if (!((strcmp(ep
->d_name
, ".") == 0) ||
(strcmp(ep
->d_name
, "..") == 0))) {
}
}
(void) closedir (dp);
} else {
perror ("Couldn't open the directory");
}
}
/**
* main function
*/
int main(int argc, char *argv[]) {
char *path, *pbegin, *pend;
char buffer[BUFFER_MAX_SIZE + 1];
// get all paths and find files
/*
pbegin = path;
pend = strchr(pbegin, ':');
while (*pbegin != '\0') {
if (pend == NULL) {
strcpy(buffer, pbegin);
} else {
int size = pend-pbegin;
strncpy(buffer, pbegin, SIZE(size));
buffer[pend-pbegin] = '\0';
}
printf("===========================\n");
printf("directory %s\n", buffer);
printf("===========================\n");
find_files(buffer);
if (pend == NULL) break;
pbegin = ++pend;
pend = strchr(pbegin, ':');
}
*/
// or simplified version using strtok
while (p != NULL) {
printf("===========================\n");
printf("===========================\n");
find_files(p);
}
// play with setenv a
if (!setenv("MYLIB_PATH", "/home/richer", 1) < 0) {
fprintf(stderr
, "could not set MYLIB_PATH with setenv !!!!\n");
}
if (putenv(const_cast<char *>(MYLIB_PATH))!=0) {
fprintf(stderr
, "could not set MYLIB_PATH with putenv => %d\n%s !!!!\n",
}
system("/home/richer/public_html/ens/inra/test_mylib.sh");
return EXIT_SUCCESS;
}
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 =