conversion des types, tableaux des pointeurs, fonction main

Transcription

conversion des types, tableaux des pointeurs, fonction main
Cours 5. Types avancés en langage C. Conversions des types.
Dimitri Galayko
1
Conversion des types en langage C
1.1
Conversion des types arithmétiques
Considérez le code suivant:
int a =4;
double b ;
b=a ;
Ici la variable b est de type double. On l’affecte avec la valeur de la variable a qui est de type entier. Intrinsèquement, une telle opération est incorrecte, car les deux objets ne sont pas de même type. Cependant, en
langage C, il y a un mécanisme de conversion de types, qui autorise cette affectation.
Le mécanisme de conversion de type s’enclenche de deux manières:
– Conversion de types implicite, c.a.d., sans mention spéciale: dans l’exemple précédent, le type de la valeur
de la variable a sera converti du int vers le double, sans que l’utilisateur n’ait à l’indiquer explicitement. Cette
conversion ne sera pas toujours correcte sémantiquement, comme on le verra plus loin.
– Conversion de type explicites : cela se fait à l’aide de l’opérateur de conversion de type. Cet opérateur s’écrit
ainsi :
(<type vers lequel on veut convertir>)<expression>.
Par exemple, l’exemple ci-dessus pourrait s’écrire ainsi:
int a =4;
double b ;
b=(double ) a ;
Ici, le type de la valeur de la variable a est explicitement converti vers un type double. Notez que la variable a
garde son type int, cette conversion n’est valable qu’au niveau de l’expression.
1.2
Conversions sémantiquement correctes
En général, on peut convertir des types à faible capacité vers les types de grande capacité (e.g., char vers int, float
vers double, etc.)
Les conversions suivantes sont sémantiquement correctes, c.a.d., ne donnent pas lieu à une perte d’infrormation
ou à des résultats erronnés :
char->int->long->long long->float->double->long double.
Notez qu’en convertissant le type long long vers float on peut avoir une perte de précision (un arrondi), bien
que la valeur approximative va rester correcte.
Les conversions dans les sens inverses peuvent donner, en général, des résultats incorrect, sauf les cas où les
valeurs stoquées dans un type plus ”grand” sont compatibles avec le type plus ”petit”, comme dans l’exemple
suivant:
int a =4;
char b ;
b=(char ) a ; // l e r é s u l t a t s e r a c o r r e c t
// mais
a =1000;
b=(char ) a ; // l e r é s u l t a t e s t i n c o r r e c t , c a r
// char a l a c a p a c i t é − 1 2 8 . . . 1 2 7
1.3
Conversion entre les expressions entières signées et non-signées
La conversion d’un type signé vers un type non-signé se font sans erreur, tant que la valeur signée est non-négative.
La converion d’un type non-signé vers un type signé se fait tant que la valeur non-signée est dans la plage
acceptable des valeurs positives du type signée.
Par exemple :
char a =3;
unsigned char b=4;
a=b ; // c o r r e c t
b=a ; // c o r r e c t
b=200;
a=b ; // non−c o r r e c t : l a p l a g e de a e s t − 1 2 8 . . . 1 2 7
a=−5;
b=a ; // non−c o r r e c t : b n ’ a c c e p t e que d e s v a l e u r s non−né g a t i v e s
1.4
Conversion des pointeurs
Tous les types des pointeurs sont compatibles. Ainsi, on peut toujours convertir un type de pointeur vers un autre:
char ∗ a ;
int tab [ 1 0 ] ;
a=(char ∗ ) ( tab +2) ;
a [1]=3;
Dans ce code, un pointeur de type char (une donnée à 1 octet) pointe vers le premier élément du tableau tab
de type int. Ainsi, le pointeur pointe vers le premier octet du troisième élément du tableau. La dernière ligne
modifie le deuxième octet du troisième élément du tableau tab.
Cet exemple, un peu tordu, permet de montrer la souplesse du mécanisme des pointeurs du langage C – ce qui
fait de ce langage un outil très intéressant pour la programmation à très bas niveau proche du matériel.
1.5
Pointeur non-typé
Un pointeur peut être non-typé. Dans ce cas il est déclaré comme étant de type void :
void ∗p ;
int b ;
p=(void ∗ )&b ;
Un tel pointeur peut être converti en pointeur de n’importe quel type:
// l a s u i t e du code pr é c é d e n t
∗ ( ( int ∗ ) p ) =5;
Ici le pointeur p est converti en type ”pointeur vers int”, et ensuite par l’opération indirection l’objet vers
lequel il pointe (un entier int) est affecté de 5.
1.6
Tableaux de pointeurs et tableaux de chaı̂nes de caractères
Les tableaux de pointeurs permettent de créer un tableau dont les éléments sont des tableaux de tailles variables
et sont dispersés dans la mémoire. A noter la différence avec un tableau à 2 dimensions, où tous les sous-tableaux
sont de même taille et sont stoqués l’un suite à l’autre.
Un tableau de pointeur se déclare ainsi:
int ∗p [ 1 0 ] ; // p e s t un t a b l e a u de 10 é l é ment , chaque é l é ment e s t un p o i n t e u r v e r s i n t
Pour l’initialiser, on va créer des tableaux d’entiers, et on va affecter les éléments du tableau p par les adresses des
tableaux a, b et c:
int a [ 1 0 ] ;
int b [ 2 0 ] ;
int c [ 5 ] ;
p[0]= a ;
p[1]=b ;
p[3]= c ;
L’inconvénient d’un tel tableau de pointeurs est que l’on ne connaı̂t pas implicitement la taille de chaque
sous-tableau. La situation est différente avec un tableau de chaı̂nes de caractères:
char ∗ c [ 1 0 ] ;
c [0]= ” abcdaire ” ;
c [1]= ” t e s t ” ;
// . . . e t c
Dans ce cas, chaque élément du tableau c est une chaı̂ne de caractère dont la longueur est définie par la position
du caractère ’\0’.
2
2.1
Arguments de la fonction main
Passage des arguments à la fonction main
La fonction main peut prendre 2 arguments: argv et argc, de la manière suivante:
main ( int argc , char ∗ argv [ ] ) {
...
}
La variable argv est un tableau de pointeur de type char, c.a.d., un tableau de chaı̂nes de caractères. argc et
le nombre d’éléments de ce tableau. Chaque chaı̂ne du tableau argv est un argument que l’utilisateur a donné en
ligne de commande. Par exemple, considérez le programme suivant:
#include <s t d i o . h>
int main ( int argc , char ∗ argv [ ] ) {
int i ;
f o r ( i =0; i <a r g c ; i ++)
p r i n t f ( ” l ’ argument %d e s t %s \n” , argc , argv [ i ] ) ;
return 0 ;
}
Ce programme affiche à l’écran chaque argument en ligne de commande. Vous voyez que la variable argc sert
de compteur.
2.2
Interprétation des arguments ”chaı̂nes de caractères” en tant que nombres
On peut passer tout genre d’information en ligne de commande, mais cette information est forcement de type
”chaı̂ne de caractère”. Si on souhaite transmettre une valeur numérique, la chaı̂ne de caractère qui correspond à
l’écriture numérique de la valeur doit être convertie en type ”nombre” avec les fonctions suivantes:
long int
float
double
long double
strtol(char *str, char **endptr, int base)
strtof(char *str, char **str_end );
strtod( char *str, char **str_end );
strtold(char *str, char **str_end );
Chacune de ces fonction convertit le début de la chaı̂ne de caractère stoquée dans str vers un type long int,
float, double ou long double. Le pointeur str end stoque le pointeur vers le premier caractère non-converti (par ex.,
si le nombre est suivi par un autre texte). Pour la fonction strtol il est possible de préciser la base de numération
du nombre entier entre 2 et 36, ou bien 0 (cf. la documentation de la fonction pour plus de détail).
Considérez le code suivant:
#include <s t d i o . h>
#include < s t d l i b . h>
int main ( int argc , char ∗ argv [ ] ) {
char t e x t [ ] = ” 3 0 . 5cm” ;
char ∗ t ;
float x ;
x=s t r t o f ( t e x t , &t ) ;
p r i n t f ( ” Chaine i n i t i a l e : %s \n” , t e x t ) ;
p r i n t f ( ”x=%f , t p o i n t e v e r s %s \n” , x , t ) ;
}
Ici on convertit la chaı̂ne de caractères ”30.5cm” en nombre à virgule flottante. La variable x contiendra la
valeur 30.5, alors que le pointeur t pointe vers le caractère c de la chaı̂ne text, si bien que la seconde command
printf affichera:
x=30.500000, t pointe vers cm
En effet, seuls les quatres caractères 30.5 peuvent être converties en nombre, les caractères cm ne le peuvent
pas.
2.3
Exercice
Ecrivez une calculette qui calcule les expressions arithmétiques binaires, avec les nombres entiers, et avec les
quatres opérations arithmétiques. L’expression est à saisir en ligne de commande. Par exemple, si le programme
s’appelle calculette, l’appel en ligne de commande :
calculette 0.5+3.2
doit conduire à l’impression à l’écran du résultat
3.7
Pour se simplifier la tâche, on n’autorisera pas d’espaces entre les opérandes et le caractère de l’opérateur.
Nous donnons ici le listing du code:
#include <s t d i o . h>
#include < s t d l i b . h>
// ce programme r é a l i s e une c a l c u l e t t e q u i
// c a l c u l e l a v a l e u r de l ’ e x p r e s s i o n donné e en
// argument de l a l i g n e de commande
// L ’ e x p r e s s i o n e s t de t y p e a? b ou ? p e u t ê t r e + , − ,∗ ,/
// a e t b s o n t d e s e n t i e r s , s i g n é , ou non
// Le programme a f f i c h e l e r é s u l t a t .
// l e s e s p a c e s e n t r e l e s op é r a n d e s e t l e c a r a c t è r e d ’ op é r a t i o n
// s o n t i n t e r d i t s
int main ( int argc , char ∗ argv [ ] ) {
// pour s t o q u e r l e s op é r a n d e s e t
// l e c a r a c t è r e d ’ op é r a t i o n
int operande1 , operande2 ;
char o p e r a t i o n ;
// pour f a i r e f o n c t i o n n e r l a f o n c t i o n s t r t o l
char ∗ t , ∗ t 1 ;
//
//
//
if
on v é r i f i e d ’ abord l e nombre d ’ argument
p a s s é s en l i g n e de commande : i l d o i t y a v o i r e x a c t e m e n t
deux
( a r g c !=2) {
p r i n t f ( ” Mauvais nombre d ’ arguments , au r e v o i r \n” ) ;
return 1 ;
}
// On l i t l a premi è r e op é rande dans l e d e u x i ème argument
// de l a l i g n e de commande
operande1=s t r t o l ( argv [ 1 ] , &t , 1 0 ) ;
// s i l e p o i n t e u r t p o i n t e v e r s l a dé b u t de l a cha ı̂ ne ,
// ce que l e f o r m a t de l a premi è r e op é rande e s t i n c o r r e c t
i f ( t==argv [ 1 ] ) {
p r i n t f ( ” Mauvais format de l a premi è r e opé rande , au r e v o i r \n” ) ;
return 1 ;
}
// S i l a premi è r e op é rande a é t é c o r r e c t e m e n t l u e , l e p o i n t e u r t p o i n t e v e r s
// l e c a r a c t è r e s u i v a n t , donc , v e r s l e c a r a c t è r e de l ’ op é r a t i o n .
// On l e l i t t e l q u e l
o p e r a t i o n =∗t ;
// e t on v é r i f i e s ’ i l e s t c o r r e c t .
i f ( ! ( ( o p e r a t i o n==’+ ’ ) | | ( o p e r a t i o n==’− ’ ) | | ( o p e r a t i o n==’ ∗ ’ ) | | ( o p e r a t i o n==’ / ’ ) ) ) {
p r i n t f ( ” Mauvais format de l ’ opé r a t e u r , au r e v o i r \n” ) ;
return 1 ;
}
// On l i t l a s e c o n d e op é rande . Maintenant , l a cha ı̂ ne de c a r a c t è r e qu ’ i l f a u t
// donner à l a f o n c t i o n s t r t o l a l ’ a d r e s s e t +1, c a r l a s e c o n d e op é rande s u i t immé
diatement
// l e s i g n e de l ’ op é r a t i o n .
// On donne l e p o i n t e u r t 1 pour s t o q u e r l e s y m b o l e s u r l e q u e l l a l e c t u r e s ’ a r r ê t e , e t
pour
// p o u v o i r dé t e c t e r l ’ e r r e u r .
operande2=s t r t o l ( t +1, &t1 , 1 0 ) ;
i f ( t 1==t +1){
p r i n t f ( ” Mauvais format de l a s e c o n d e opé rande , au r e v o i r \n” ) ;
return 1 ;
}
// Maintenant , quand l a l e c t u r e a r é u s s i , on f a i t l e c a l c u l . C ’ e s t t r i v i a l
// s i on comprend l a commande c a s e
switch ( o p e r a t i o n ) {
case ’+ ’ :
p r i n t f ( ”%d\n” , operande1+operande2 ) ;
break ;
case ’− ’ :
p r i n t f ( ”%d\n” , operande1−operande2 ) ;
break ;
case ’ ∗ ’ :
p r i n t f ( ”%d\n” , operande1 ∗ operande2 ) ;
break ;
case ’ / ’ :
p r i n t f ( ”%d\n” , operande1 / operande2 ) ;
break ;
}
return ( 1 ) ;
}