INFO-F-105 Langages 1 - Homepage de l'Université libre de ... · L-value •Définition: une...
-
Upload
trinhnguyet -
Category
Documents
-
view
214 -
download
0
Transcript of INFO-F-105 Langages 1 - Homepage de l'Université libre de ... · L-value •Définition: une...
Opérateurs
• Le C++ connaît énormément d’opérateurs:
• +, -, *, /, %
• <, >, <=, >=
• <<, >>
• ++, --, typeid, sizeof, [], new, delete
• ...
Priorité et associativité• Comme en mathématique les opérateurs
ont une priorité:
• ex: a+b*c équivaut a + (b*c)
• L’associativité est à droite pour les opérateurs unaires et l’assignation, et à gauche sinon
• ex: a+b+c équivaut (a+b)+c
• ex: *p++ équivaut *(p++)
• ex: a=b=c équivaut a=(b=c)
Type du résultat
• Quel est le type du résultat d’une expression arithmétique ?
• Cela dépend des types qui y apparaissent
• Règles générale: on va toujours vers le plus précis
• ex:long int i ;int j ;... = i+j ; // i+j est long int
L-value
• Définition: une l-value est une valeur qui a une adresse en mémoire
• exemples:
• ++x renvoie la nouvelle valeur de x. Cette valeur est donc stockée dans x, et a donc une adresse (celle de x)
• x++ renvoie l’ancienne valeur de x. Cette valeur n’est plus stockée en mémoire. Ce n’est pas une l-value.
Type de retour• De manière générale, les opérateurs qui
reçoivent une l-value renvoient une l-value. Cela permet de combiner les opérateurs
• exemples:
• x = y ; renvoie la nouvelle valeur de x. On peut donc écrire z = x = y ; La valeur de y sera d’abord copiée dans x, puis dans z.
• int *p = &++x ; fait pointer p vers x
• int * p = &x++ ; n’est pas correct.
Dépassements
• Aucune garantie n’est offerte quant au dépassement de capacité.
• Ceux-ci ne sont pas détectés
• Les effets ne sont pas garantis
• exemple: qu’imprime ?int i = 1;while (i > 0) i++ ;cout << i ;
Ordre d’évaluation
• L’ordre dans lequel les composants d’une expressions sont évalués n’est pas garanti !
• Exemple: on ne sait pas si f(1) sera évalué avant g(2) ou pas dans:int x = f(1) + g(2) ;
• Exemple: que fait ?int i = 1;V[i] = i++ ;
Ordre d’évaluation
• Il y a heureusement des exceptions: && et || évaluent de gauche à droite
• Utile dans un cas comme:while (p != NULL && p->info != i) {...}
Car si p=NULL et qu’on évalue d’abord p->info, on a une erreur de segmentation
Opérateurs && et ||
• De plus, ces opérateurs sont évalués de façon «paresseuse»:
• Dans e1 && e2, on évalue d’abord e1, et puis e2 seulement si e1 est vrai
• Dans e1 || e2, on évalue d’abord e1, et puis e2 seulement si e1 est faux
Incrément et décrément
• Le C++ connaît les opérateurs ++ et -- pour incrémenter et décrémenter
• Il en existe deux versions:
• Une version préfixe ++x, --x, qui renvoie la nouvelle valeur
• Une version postfixe x++, x--, qui renvoie l’ancienne valeur (et donc pas une l-value)
++ et -- sur les pointeurs
• Quand on applique ces opérateurs sur un pointeur T * p cela a pour effet d’incrémenter le pointeur de sizeof(T).
• Utilité: parcours de taleaux !
Exercice...
• Que fait le code ci-dessous ?
char S[10] = «Hello !» ;char T[10] ;char * p = S, *q = T ;while (*q++ = *p++) ;
new et delete
• new T alloue sur le heap un objet de type T et retourne son adresse
• delete p, où p est un pointeur, libère l’espace mémoire pointé par p, à condition que cet espace ait été alloué par new
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;p = q ;return 0 ;
}
stack heap
Codep q
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;p = q ;return 0 ;
}
stack heap
Codep q
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;p = q ;return 0 ;
}
stack heap
Codep q
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;p = q ;return 0 ;
}
stack heap
Codep q
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;p = q ;return 0 ;
}
stack heap
Codep q
La zone initialement pointée par p n’est plus
accessible par le programme mais reste
«réservée». Elle est donc perdue.
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;delete p ;p = q ;return 0 ;
}
stack heap
Codep q
Utilité de delete
int main() {int * p, *q;p = new int ;q = new int ;delete p ;p = q ;return 0 ;
}
stack heap
Codep q
delete
• Attention ! delete ne modifie pas le pointeur.
• delete p libère la zone mémoire pointée par p...
• ...mais le pointeur p contient toujours l’adresse de cette zone, qu’on ne peut plus utiliser
• ne pas déréférencer p après un delete !
new[] et delete[]
• new T[n] crée un tableau de taille n d’objets de type T, et renvoie l’adresse du premier élément du tableau
• De manière symétrique delete[] peut être utiliser pour libérer la mémoire occupée par un tableau alloué à l’aide de new[]
Fonctions
• Rappel: on peut séparer les déclaration et définition d’une fonction. Les deux sont nécessaires mais la définition doit être unique
• Rappel: prototype d’une fonction =type retour f(type arg1, type arg2,...)
Variables statiques
• Rappel: par défaut les variables déclarées dans une fonction sont détruites quand on quitte la fonction (elles sont allouées sur le stack)
• Peut-on contourner ce comportement ?
Variables statiques
• Par exemple, dans la fonction:void f(int x) { int i = 0 ; i+=x ; cout << i ;}
• On aimerait que i ne soit pas réinitialisée à chaque entrée dans la fonction, mais qu’elle stocke la somme des x
Variables statiques
• Solutionvoid f(int x) { static int i = 0 ; i+=x ; cout << i ;}
• En déclarant la variable static, l’initialisation ne sera effectuée qu’une seule fois !
Arguments
• Rappel: par défaut, quand on passe une valeur à une fonction, une copie de la valeur est effectuée dans une variable locale de la fonction.
• Ce comportement peut être contourné en utilisant des pointeurs ou des références
Arguments
• ex:void swap(int i, int j) { int c = i ; i = j ; j = c ;}
• Ne va pas avoir le comportement attendu car on manipule des copies locales
Arguments
• ex:void swap(int &i, int &j) { int c = i ; i = j ; j = c ;}
• Va fonctionner: on modifie les variables de l’appelant
Arguments• ex:void swap(int *i, int *j) { int c = *i ; *i = *j ; *j = c ;}
• Fonctionne aussi, mais est plus lourd: l’utilisateur doit passer des adresses
• Ce sont les adresses qui seront copiées dans i et j, pas les contenus
Arguments const
• Avantage des références: évite la copie
• Inconvénient: la fonction peut modifier la variable de l’appelant !
• Solution: pn peut aussi déclarer les arguments const, quand il s’agit de pointeurs ou de références
• Cela permet d’interdire à la fonction de modifier les objets pointés ou référencés (tout en évitant la copie qui est lourde)
Arguments const• ex:int strlen (const char* S) { int i = 0 ; while (S[i]) i++ ; return i ;}Cette fonction reçoit un pointeur vers un const, ce qui lui permet de consulter l’objet, mais pas de le modifier
• A utiliser absolument pour éviter les erreurs !
Arguments: tableaux
• Quand un tableau est passé à une fonction, la fonction reçoit un pointeur vers le premier élement
• Conséquences:
• La fonction peut modifier le tableau de l’appelant (les tableaux sont toujours passés par référence)
• La fonction ne connaît pas la taille du tableau
Arguments: tableaux
• Quand un tableau est passé à une fonction, la fonction reçoit un pointeur vers le premier élement
• Conséquences:
• Quand on passe un tableau à une dimension, on ne doit pas spécifier la taille dans le type du tableau
• ex:void f(int M[]) {...}
Arguments: tableaux• Quand un tableau est passé à une fonction,
la fonction reçoit un pointeur vers le premier élement
• Conséquences:
• Quand on passe un tableau à plusieurs dimensions, la taille de la première coordonnée peut être omise
• ex:void f(int M[][n]) {...}Pourquoi ?
Valeurs de retour
• La sémantique du return est de
• créer une nouvelle variable anonyme, du type de retour de la fonction
• y stocker la valeur à retourner
• passer cette variable à la fonction appelante
Valeurs de retourint f(int i) { int k ; k = i +1 ; return k ;}
int main(void) { int l ; l = f(5) ; return 0 ;}
l
Valeurs de retourint f(int i) { int k ; k = i +1 ; return k ;}
int main(void) { int l ; l = f(5) ; return 0 ;}
l
k6
Valeurs de retourint f(int i) { int k ; k = i +1 ; return k ;}
int main(void) { int l ; l = f(5) ; return 0 ;}
l
k6
6
Valeurs de retourint f(int i) { int k ; k = i +1 ; return k ;}
int main(void) { int l ; l = f(5) ; return 0 ;}
l6
k6
6
Quand on quitte la
fonction, k est désallouée
Valeurs de retour
• On ne peut donc pas retourner un pointeur ou une référence vers une variable locale !
• ex:int * f(void) { int i ; return &i ; // pas bien}
Surcharge• Principe général: Le compilateur tolère que
plusieurs fonctions différentes aient le même nom, à condition que les paramètres lui permettent de déterminer laquelle appeler en pratique
• ex:void print(int i) { cout << "Entier " << i ;}void print(char c) { cout << "Caractère " << c ;}
Surcharge• La surcharge peut être pratique, mais peut
donner lieu à des ambiguïtés, si une conversion doit avoir lieu:
• ex:void g(double d) { cout << "Double " << d ;}void g(float fl) { cout << "Float " << fl ;}int main() { g(1) ; }
Surcharge• La surcharge peut être pratique, mais peut
donner lieu à des ambiguïtés, si une conversion doit avoir lieu:
• ex:void g(double d) { cout << "Double " << d ;}void g(float fl) { cout << "Float " << fl ;}int main() { g(1) ; }
Ambigu !Doit-on convertir le
int en double ou en float ?
Surcharge
• Quand le compilateur ne sait «pas décider» il affiche une erreur
• Le mécanisme utilisé par le compilateur pour «prendre sa décision» (pour résoudre la surcharge) est complexe
Mécanisme de résolution• A-t-on une fonction avec le prototype exact ?
• Si non, peut-on utiliser des promotions ?
• bool ☞ int
• char ☞ int
• short ☞ int
• float ☞ double
• double ☞long double
Mécanisme de résolution• Si non, peut-on utiliser des conversion
standards ?
• int ☞ double
• double ☞ int
• int ☞ unsigned int
• Si non peut-on utiliser des conversions définies par l’utilisateur ?
• ...
Ambiguïté
• Les problèmes d'ambiguïté sont plus importants quand on a plusieurs paramètres
• Ce que le mécanisme de résolution peut résoudre pour un paramètre n’est pas toujours gérable pour plusieurs paramètres
Ambiguïté• Exemple:void e(double d, int i) { cout << "double " << d ; cout << "int " << i ;}
void e(int i, double d) { cout << "int " << i cout << "double " << d ;}
int main() { e(1, 1) ; }
Arguments par défaut
• Par moment, on aimerait permettre à l’utilisateur d’appeler une fonction sans devoir toujours donner une valeur à tous ses arguments, car certains sont «habituels»
• ex: une fonction qui affiche un nombre dans une certaine base, 10 par défaut:void f(int v, int base) {...}f(5) ; // affiche en base 10f(5, 16) ; // base 16f(5, 8) ; // base 8
Arguments par défaut
• Solution 1: surcharge:void f(int i) {...}void f(int i, int base) {...}
• Problème: on va dupliquer du code
• Il faudrait pouvoir dire que «10» est une valeur «par défaut» !
• Solution 2:void f(int i, int base=10) {..}
Arguments par défaut
• Pour que cela fonctionne, il faut que le compilateur puisse décider, quand il manque des arguments à l’appel, lesquels correspondent aux valeurs par défaut:
• Règle: seuls les derniers arguments peuvent être «par défaut» et on associe les valeurs aux arguments de gauche à droite
Arguments par défaut
• Exemples:int f(int, int =0, char* =0) ; OKint g(int =0, int =0, char*) ; KOint h(int =0, int, char* =0) ; KO
Arguments par défaut
• Exemple:int f(int i, int j= 0, int j= 0) ;
int main() { f(5) ; // f(5, 0, 0) f(5, 1) ; // f(5, 1, 0) // pas f(5, 0, 1) f(5, 0, 1) ;}
Arguments non spécifiés• Il est également possible de déclarer des
fonctions sans spécifier le nombre de paramètres
• ex: Une fonction qui affiche une chaîne de caractères suivie d’une liste de valeurs, chacune spécifiée comme un paramètreint i = 9 ;f("Liste", 1, i) ;f("Liste", 5, 9, 15, 20) ;f("Liste", 5+2) ;C’est toujours la même fonction qui est appelée !
Arguments non spécifiés
• Déclaration de la fonction:void f(char * n ...)
• Ensuite, on accède aux paramètres effectifs à l’aide de macros spécifiées dans cstdarg (à inclure)
Arguments non spécifiés• Exemple:void f(char * n ...) { va_list arguments ; va_start(arguments, n) ;
int i ; cout << n << " : " ; do { i = va_arg(arguments, int) ; if (i) cout << i << " " ; } while(i) ; cout << endl ;}
Pointeurs vers des fonctions
• On peut aussi définir des pointeurs vers des fonctions
• Tout comme un pointeur vers un objet permet d’indiquer à une fonction une zone de mémoire à traiter, un pointeur vers une fonction permet d’indiquer à une autre fonction un traitement à appliquer
Pointeurs vers des fonctions
• Syntaxe:Tr (*fp)(T1, T2,..., Tn)Déclare un pointeur fp vers une fonction qui retourne un élément de type Tr et prend n arguments de types T1, T2,... Tn
• Exemple:int (*fp)(char *)ne pas confondre avec:int * fp(char *)
Pointeurs vers des fonctions
• Utilisation: Comme un pointeur vers une variable:int f(char * S) {...}
int main() { int (*fp)(char *) ; fp = &f ; // adresse char * V = ... ; (*fp)(V) ; // déréf. return 0 ;}
Pointeurs vers des fonctions
• Mais en pratique, une fonction est un pointeur !
• Une fonction n’est jamais que l’adresse mémoire où se trouve la première instruction du code généré
• On n’est donc pas obligé d’écrire explicitement &f, ni *f
Pointeurs vers des fonctions
• Alternative: int f(char * S) {...}
int main() { int (*fp)(char *) ; fp = f ; // adresse char * V = ... ; fp(V) ; // déréf. return 0 ;}
Fonctions inline
• Par défaut, un appel de fonction donne lieu à un jump dans le code compilé
• Pour des petites fonctions appelées fréquemment, il peut être préférable d’éviter le jump, et de demander au compilateur de d’insérer le code de la fonction à l’endroit de l’appel