Habituellement en C, nous devons indiquer à l'ordinateur le type de données dans la déclaration de variable. Par exemple, dans le programme suivant, je veux imprimer la somme de deux nombres à virgule flottante X et Y.
#include<stdio.h>
main()
{
float X=5.2;
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
J'ai dû dire au compilateur le type de variable X.
- Le compilateur ne peut-il pas déterminer le type de
X
lui-même?
Oui, je peux le faire:
#define X 5.2
Je peux maintenant écrire mon programme sans dire au compilateur le type de X
:
#include<stdio.h>
#define X 5.2
main()
{
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
Nous voyons donc que le langage C a une sorte de fonctionnalité, à l'aide de laquelle il peut déterminer lui-même le type de données. Dans mon cas, il a déterminé qu'il X
est de type float.
- Pourquoi devons-nous mentionner le type de données, lorsque nous déclarons quelque chose dans main ()? Pourquoi le compilateur ne peut-il pas déterminer seul le type de données d'une variable
main()
comme il le fait dans#define
.
c
variables
data-types
io
declarations
user106313
la source
la source
5.2
est undouble
, donc le premier programme arrondit les doubles littéraux à lafloat
précision, puis les ajoute comme des flottants, tandis que le second arrondit la double représentation de 5.1 àdouble
et l'ajoute à ladouble
valeur 5.2 en utilisant l'double
addition, puis arrondit le résultat de ce calcul à lafloat
précision . Étant donné que l'arrondi se produit à différents endroits, le résultat peut différer. Ceci n'est qu'un exemple pour les types de variables affectant le comportement d'un programme par ailleurs identique.#define X 5.2
,X
n'est pas une variable, mais une constante, il est donc littéralement remplacé par un préprocesseur avec5.2
n'importe où que vous avez mentionnéX
. Vous ne pouvez pas réaffecterX
.auto
fait en fait ce que vous voulez). D'un autre côté, si vous pensez savoir ce que fait votre code et que vous avez réellement tapé autre chose, une frappe statique comme celle-ci détectera une erreur plus tôt, avant qu'elle ne devienne un énorme problème. Chaque langue établit un équilibre: typage statique, inférence de typage, typage dynamique. Pour certaines tâches, la frappe supplémentaire en vaut la peine. Pour d'autres, c'est un gaspillage.Réponses:
Vous comparez des déclarations de variables à
#define
s, ce qui est incorrect. Avec a#define
, vous créez un mappage entre un identifiant et un extrait de code source. Le préprocesseur C remplacera alors littéralement toutes les occurrences de cet identifiant par l'extrait fourni. L'écriturefinit par être la même chose pour le compilateur que l'écriture
Considérez-le comme un copier-coller automatisé.
De plus, les variables normales peuvent être réassignées, alors qu'une macro créée avec
#define
ne peut pas (bien que vous puissiez la réutiliser#define
). L'expressionFOO = 7
serait une erreur de compilation, car nous ne pouvons pas attribuer à «rvalues»:40 + 2 = 7
est illégal.Alors, pourquoi avons-nous besoin de types? Certains langages se débarrassent apparemment des types, ceci est particulièrement courant dans les langages de script. Cependant, ils ont généralement quelque chose appelé «typage dynamique» où les variables n'ont pas de types fixes, mais les valeurs en ont. Bien que cela soit beaucoup plus flexible, il est également moins performant. C aime les performances, il a donc un concept de variables très simple et efficace:
Il y a une partie de la mémoire appelée «pile». Chaque variable locale correspond à une zone de la pile. Maintenant, la question est de combien d’octets cette zone doit-elle avoir? En C, chaque type a une taille bien définie que vous pouvez interroger via
sizeof(type)
. Le compilateur a besoin de connaître le type de chaque variable pour pouvoir réserver la bonne quantité d'espace sur la pile.Pourquoi les constantes créées avec n'ont-elles pas
#define
besoin d'une annotation de type? Ils ne sont pas stockés sur la pile. Au lieu de cela,#define
crée des extraits de code source réutilisables d'une manière légèrement plus maintenable que le copier-coller. Les littéraux dans le code source tels que"foo"
ou42.87
sont stockés par le compilateur soit en ligne sous forme d'instructions spéciales, soit dans une section de données distincte du binaire résultant.Cependant, les littéraux ont des types. Un littéral de chaîne est un
char *
.42
est unint
mais peut également être utilisé pour les types plus courts (rétrécissement de la conversion).42.8
serait undouble
. Si vous avez un littéral et que vous voulez qu'il ait un type différent (par exemple pour créer42.8
unfloat
ou42
ununsigned long int
), vous pouvez utiliser des suffixes - une lettre après le littéral qui change la façon dont le compilateur traite ce littéral. Dans notre cas, nous pourrions dire42.8f
ou42ul
.Certaines langues ont un typage statique comme en C, mais les annotations de type sont facultatives. Les exemples sont ML, Haskell, Scala, C #, C ++ 11 et Go. Comment ça marche? La magie? Non, cela s'appelle «inférence de type». En C # et Go, le compilateur examine le côté droit d'une affectation et en déduit le type. C'est assez simple si le côté droit est un littéral tel que
42ul
. Il est alors évident quel devrait être le type de la variable. D'autres langages ont également des algorithmes plus complexes qui prennent en compte la façon dont une variable est utilisée. Par exemple, si vous le faitesx/2
, alorsx
ne peut pas être une chaîne mais doit avoir un certain type numérique.la source
#define
nous avons une constante qui est directement convertie en code binaire - aussi longue soit-elle - et qui est stockée dans la mémoire telle quelle.#define X 5.2
?printf
vous avez invoqué un comportement non défini. Sur ma machine, cet extrait de code imprime une valeur différente à chaque fois, sur Ideone, il se bloque après avoir imprimé zéro.X*Y
n'est pas valide siX
etY
sont des pointeurs, mais c'est correct s'ils sontint
s;*X
n'est pas valide siX
c'est unint
, mais ça va si c'est un pointeur.X dans le deuxième exemple n'est jamais un flottant. Elle s'appelle une macro, elle remplace la valeur de macro définie «X» dans la source par la valeur. Un article lisible sur #define est ici .
Dans le cas du code fourni, avant la compilation, le préprocesseur change le code
à
et c'est ce qui est compilé.
Cela signifie que vous pouvez également remplacer ces «valeurs» par du code comme
ou même
la source
#define FOO(...) { __VA_ARGS__ }
.La réponse courte est que C a besoin de types en raison de l'historique / représentant le matériel.
Historique: C a été développé au début des années 1970 et a été conçu comme un langage pour la programmation de systèmes. Le code est idéalement rapide et tire le meilleur parti des capacités du matériel.
L'inférence de types au moment de la compilation aurait été possible, mais les temps de compilation déjà lents auraient augmenté (voir le dessin animé de compilation de XKCD. Cela s'appliquait au `` bonjour le monde '' pendant au moins 10 ans après la publication de C ). L'inférence de types lors de l'exécution n'aurait pas correspondu aux objectifs de la programmation des systèmes. L'inférence à l'exécution nécessite une bibliothèque d'exécution supplémentaire. C est venu bien avant le premier PC. Qui avait 256 RAM. Pas des gigaoctets ou mégaoctets mais des kilo-octets.
Dans votre exemple, si vous omettez les types
Ensuite, le compilateur aurait pu comprendre que X & Y sont des flottants et faire de Z la même chose. En fait, un compilateur moderne déterminerait également que X & Y ne sont pas nécessaires et définirait simplement Z à 10,3.
Supposons que le calcul soit intégré dans une fonction. L'auteur de la fonction peut vouloir utiliser ses connaissances du matériel ou résoudre le problème.
Un double serait-il plus approprié qu'un flotteur? Prend plus de mémoire et est plus lent mais la précision du résultat serait plus élevée.
Peut-être que la valeur de retour de la fonction pourrait être int (ou longue) car les décimales n'étaient pas importantes, bien que la conversion de float en int ne soit pas sans coût.
La valeur de retour pourrait également être rendue double en garantissant que flotteur + flotteur ne déborde pas.
Toutes ces questions semblent inutiles pour la grande majorité du code écrit aujourd'hui, mais étaient vitales lorsque C a été produit.
la source
C n'a pas d'inférence de type (c'est ce qu'on appelle quand un compilateur devine le type d'une variable pour vous) car il est ancien. Il a été développé au début des années 1970
De nombreux langages récents ont des systèmes qui vous permettent d'utiliser des variables sans spécifier leur type (ruby, javascript, python, etc.)
la source
true
estboolean
), pas des variables (par exemple,var x
peuvent contenir des valeurs de n'importe quel type). De plus, l'inférence de type pour des cas aussi simples que ceux de la question était probablement connue une décennie avant la publication de C.implicit none
dans ce cas, vous devez déclarer un type.