Introduction au langage C
Sommaire
- Introduction
- Un langage compilé
- Les langages compilés
- Les langages interprétés
- Compilation d’un programme
- Détails des options
- Les bases du langage
- Les mots clés
- Les commentaires
- Structure générale d’un programme C
- Notion d’identification
- Notion de variable
- Les types primitifs
- Les entiers
- Représentation des valeurs
- Les caractères
- Les flottants
- Le type void
- La syntaxe du langage
- Notion d’expression
- Opérateurs arithmétiques
- Opérateurs d’affectation
- Opérateurs relationnels
- Opérateurs logiques
- Opérateurs bit à bit
- Les structures décisionnelles
I- Introduction
Premier cours d’apprentissage au langage C.
Relation entre langage, programme et environnement d’exécution.
Informations :
- Difficulté : Moyenne
II- Un langage compilé
Dans le domaine de l’information moderne, on utilise aujourd’hui principalement des langages de haut niveau, c’est à dire plus facilement compréhensible par l’homme, en opposition avec des langages de bas niveau, comme l’assembleur, plus facilement compréhensible par la machine.
Parmi ces langages, on distingue 2 grandes familles : les langages compilés et les langages interprétés.
III- Les langages compilés
Il s’agit des langages qui nécessites un étape de compilation unique pour pouvoir être ensuite exécutés et interprétés par le système. Parmi ces langages on peut citer le C, C++, Java, Pascal...
Étant donnée que la phase de compilation n’est réalisée qu’une seule fois, ce type de langage est plus adapté pour la réalisation d’applications plus efficaces ou de plus grande envergure. Par ailleurs, ils permettent un accès plus précis aux ressources du systèmes.
IV- Les langages interprétés
Dans ce cas, le programme est compilé et interprété à la volé à chaque fois qu’il est exécuté. Ces langages sont très adaptés pour le développement rapide d’applications, de prototypes, ou pour l’extension de programmes sous forme de plug-in. En revanche puisqu’ils doivent être compilés à la volé, puis interprétés, leur exécution est plus lente. Parmi ces langages on peut citer : Php, JavaScript, Perl ou encore Lua, Python...
Bien que très pratique, ces langages ne sont pas conseillés là ou les exigences non fonctionnelles comptent (rapidité, sécurité, fiabilité, robustesse...).
Le langage C est un langage compilé. Ainsi un programme C est décrit par un ensemble de fichiers textes, portant des extensions .C et .H, et nommés sources. La compilation se réalise en 4 phases successives :
- Le traitement par le pré-processeur : les fichiers sources sont analysés par le pré-processeur, qui effectue des transformations purement textuelles selon des directives (remplacement de chaines de caractères, inclusion de fichiers, compilation conditionnelle...).
- La compilation : la compilation proprement dite, traduit le fichier généré par le pré-processeur en code assembleur, c’est-à-dire une suite d’instructions formées de mnémoniques propre à l’architecture du processeur concerné.
- L’assemblage : cette opération transforme le code assembleur en un binaire, c’est-à-dire en instructions directement compréhensibles par le processeur.
- L’édition de liens : un programme est généralement séparé en plusieurs fichiers sources. Une fois chaque fichier source assemblé, il faut les lier entre eux pour former l’exécutable final (le fichier qui servira à lancer le programme).
V- Compilation d'un programme
Il existe un grand nombre de compilateurs C pour chaque systèmes d’exploitations (Windows, Linux, Mac). Dans le cadre de nos exercices, nous utiliserons le compilateur libre Linux GCC, porté sous Windows sous le nom de minGW.
Nous vous présentons ici les commandes de bases pour transformer un fichier source en programme executable. Elle s’effectue généralement en deux étapes :
- Transformation du code source en fichier binaire (.o)
- Code : Tout sélectionner
- gcc -O3 -Wall -c prog.c
- Regroupement des différents fichiers binaires constituant le programme (édition de liens)
- Code : Tout sélectionner
- gcc -o prog prog.o
Lorsque tous les fichiers sources sont présents dans un même répertoire, l’étape de compilation peut-être unique et simplifiée :
- Code : Tout sélectionner
- gcc prog.c
VI- Détails des options
- -O: effectue des optimisations sur le code généré. La compilation peut alors prendre plus de temps et utiliser plus de mémoire.
- -Wall: active des messages d’avertissement supplémentaires.
- -c: demande au compilateur de n’effectuer que la première étape de compilation .
- -o: permet de préciser un nom de fichier à l’exécutable final.
VII- Les bases du langage
A- Les mots clés
Le C est un langage défini par un certain nombre de mots clés et de symboles réservés. Vous ne pourrez pas utiliser ces identifiants en tant qu’identifiant de variable ou de fonction. En voici la liste, par ordre alphabétique :
- Code : Tout sélectionner
- auto
- break
- case
- char
- const
- continue
- default
- do
- double
- else
- enum
- extern
- float
- for
- goto
- if
- int
- long
- register
- return
- short
- signed
- sizeof
- static
- struct
- switch
- typedef
- union
- unsigned
- void
- volatile
- while
B- Les commentaires
Les commentaires sont indispensables à la compréhension de n’importe quel programme informatique non triviale. Ils permettent le travail collaboratif et la maintenance.
Le langage C propose deux types de commentaires :
- Les commentaires mono-lignes (introduit dans la norme C99) :
- Code : Tout sélectionner
- // Ceci est un commentaire mono-ligne
- Les commentaires multi-lignes :
- Code : Tout sélectionner
- /* Ceci est un
- commentaire
- multi-lignes
- */
Il existe plusieurs normes de formalisation de commentaires, nous vous recommandons de vous reporter à l’outil doxygen, qui est un générateur de documentation automatique référent.
C- Structure générale d’un programme C
D’une manière générale, un programme consiste en un ensemble organisé de sous programmes appelés fonctions organisés entre eux. La fonction principale d’un programme appelée main,constitue le point d’entrée (et de sortie) d’un programme.
Voici à quoi ressemble un programme C minimaliste qui affichera sur la console le fameux : « Hello World »
- Code : Tout sélectionner
- #include <stdio.h> // Directive de preprocesseur
- int main(int argc, char** argv) {
- printf("Hello world!");
- }
Un programme est donc constitué d’un ensemble de fonctions, mais aussi de variables qui représentes des données ainsi que des structures de contrôles qui organisent le déroulement du programme (du flot d’exécution).
D- Notion d’identification
Un identificateur permet de donner un nom à une entité d’un programme. Qu’il s’agisse d’une fonction ou d’une variable, ils sont sujets aux règles suivantes :
- Ils sont formés d’une suite de lettres (’a’ à ’z’ et ’A’ à ’Z’), de chiffres (0 à 9) et du signe ’_’. En particulier, les lettres accentuées sont interdites.
- Le premier caractère de cette suite ne peut pas être un chiffre.
- Les identificateurs sont sensibles à la casse : abc et Abc sont 2 identificateurs distincts.
Etant donné que l’identification d’une entité est laissée libre au développeur, il est fortement conseillé d’utiliser des noms significatifs !
- Code : Tout sélectionner
- int _is_connected = 0; // Valide.
- int 2_good_4_me = 12; // Invalide.
- char* message = 12; // Valide.
E- Notion de variable
Une variable est un espace mémoire situé en mémoire et pouvant stocker, selon son type, des données.
Une variable est définie(déclarée) par un un identifiant, précédée d’un type et éventuellement succédée d’un opérateur d’affectation et d’une valeur pour en effectuer son initialisation.
- Code : Tout sélectionner
- int _is_connected = 0;
Dans l’exemple ci-dessus, on déclare une variable identifiée sous le nom de "_is_connected" de type int (entière) et initialisée à la valeur 0.
Cette variable sera donc accessible, dans la limite de sa portée, sous cet identifiant.
- Code : Tout sélectionner
- int _is_connected = 0; // Valide.
- _is_connected = 1;
- int connection;
- connection = is_connected;
Dans l’exemple ci-dessus, la variable est accédée à la ligne 2 en mode écriture, et à la ligne 4 en mode lecture.
Remarque : Un identifiant est unique à l’intérieur de sa portée.
F- Les types primitifs
Il s’agit des types de bases définis en standard par le langage. Ils permettent de définir la nature d’une variable. Le langage C est très faiblements typé, il ne propose que des types numériques.
G- Les entiers
Les types entiers sont les plus utilisés et représentes des valeurs entières. Le langage C permet de préciser leur taille en octet et donc l’espace de valeur qu’ils peuvent prendre.
- Code : Tout sélectionner
- short int mask;
- int i;
- long int id;
- unsigned int count;
Le mot clé unsigned indique l’espace de valeur prit par count et sur l’ensemble des entiers naturels(>=0). Par défaut les entiers sont signés.
Le type int est calé sur la longueur du mot machine, en d’autres termes, si le processeur est un 16 bits, ce sera sur 2 octets, un 32 bits sur 4 octets. Les autres types sont fixes. Le mot machine étant le plus rapide à traiter (puisqu’il ne nécessite pas de conversion).
Tableau récapitulatif des types :
Type | Taille mémoire | Intervalle |
---|
char | 1 octet | [-128 ;127] |
unsigned char | 1 octet | [0; 255] |
int | 2,4,8 octets | [-2¹⁵; 2¹⁵-1] ou [-2³¹; 2³¹-1] ... |
unsigned int | 2,4,8 octets | [0; 2¹⁶-1] ou [0; 2³²-1] ... |
short int | 2 octets | [-2¹⁵; 2¹⁵-1] |
unsigned short int | 2 octets | [0; 2¹⁶-1] |
long | 4 octets | [-2³¹; 2³¹-1] |
unsigned long | 4 octets | [0; 2³²-1] |
long long * | 8 octets | [-2⁶³; 2⁶³-1] |
unsigned long long * | 8 octets | [0; 2⁶⁴-1] |
* Dépend du compilateur utilisé.
Les valeurs limites des différents types sont définies dans le fichier header <limits.h>
H- Représentation des valeurs
Le langage C permet de représenter les valeurs entières selon 3 bases :
Base 10, Décimale
c’est la base par défaut.
- Code : Tout sélectionner
- short int mask = 372;
Base 8, Octale
On commence par un 0 suivi de chiffres octaux
- Code : Tout sélectionner
- short int mask = 0477;
Base 16, Hexadécimale
On commence par un 0x ou 0X suivi de chiffres héxadécimaux
- Code : Tout sélectionner
- short int mask = 0x5A2b;
I- Les caractères
Le C propose un type de variable pour représenté la notion de caractère, c’est-à-dire une donnée devant être affichée, typiquement une lettre, un chiffre, un espace, un retour à la ligne...
Il s’agit en fait d’un entier codé sur 8 bits interprété comme un caractère utilisé sur la machine (il s’agit en général du code ASCII de ce caractère).
- Code : Tout sélectionner
- /* Declaration d’une variable c1 de type char a laquelle on affecte la valeur ’a’.
- Important : Noter l’utilisation du simple quote
- */
- char c1 = 'a';
- char c2 = 97; //c2 correspond egalement au caractere ’a’, puisque dans la table ASCII, la lettre 'a' représente la valeur 97.
Table des principaux code ASCII Caractères particuliers
Il existe un certain nombre de caractères particuliers utilisant le caractère d’échappement \ dont les principaux sont les suivants
Caractère | '\n' | '\t' | '\b' | '\'' | '\"' |
---|
Sémantique | retour à la ligne | tabulation | backspace | ' | " |
J- Les flottants
On distingue 3 types de flottants. La différence entre eux est leur précision. Du plus faible à la plus forte, on a float, double, long double.
- Code : Tout sélectionner
- double Pi = 3.14159;
Type | Taille mémoire | Intervalle | Précision |
---|
float | 4 octets | [1,2∗10⁻³⁸; 3,4∗10³⁸] | 6 chiffres décimaux |
double | 8 octets | [2,3∗10⁻³⁰⁸; 1,7∗10^³⁰⁸] | 15 chiffres décimaux |
long double | 10 octets | [3,4∗10⁻⁴⁹³²; 1,1∗10⁴⁹³²] | 19 chiffres décimaux |
K- Le type void
On a vu que toute variable C était typée, de même que toute valeur de retour d’une fonction. Mais il peut arriver qu’aucune valeur ne soit disponible.
Pour exprimer l’idée de ”aucune valeur”, on utilise le mot-clé void. Ce type est utilisé dans trois situations :
Les expressions de type void : on les rencontre principalement dans la déclaration de fonctions qui n’ont pas de valeur de retour.
- Code : Tout sélectionner
- void exit (int status);
Le prototype de fonctions sans paramètres
- Code : Tout sélectionner
- int rand(void);
A noter qu’en pratique, on écrira plus simplement :
- Code : Tout sélectionner
- int rand();
Les pointeurs vers le type void : le type void * est le type pointeur générique, c’est à dire qu’il est capable de pointer vers n’importe quel type d’objet. Il représente donc l’adresse de l’objet et non son type.
VIII- La syntaxe du langage
A- Notion d’expression
- Code : Tout sélectionner
- 4 * 512 // Type: int
- 4 + 6 * 512 // Type: int; equivalent to 4 + (6 * 512)
- 1.0 + sin(x) // Type: double
- srand((unsigned)time(NULL)) // Appel de fonction; type: void
- (int*)malloc(count*sizeof(int)) // Appel de fonction; type: int *
Une expression est une combination d’opérateurs et d’opérandes.
Dans les cas les plus simples, une expression se résume à une constante, une variable ou un appel de fonction.
Des expressions peuvent également être utilisées comme opérandes et être jointes ensembles par des opérateurs, pour obtenir des expressions plus complexes.
Toute expression a un type et si ce type n’est pas void, c'est une valeur.
B- Opérateurs arithmétiques
Les principaux opérateurs arithmétiques sont résumés dans le tableau. Les opérandes de ces opérateurs peuvent appartenir a tout type arithmétique, à l’exception du % qui requière des opérandes de type entier.
On peut effectuer une conversion de type aux opérandes. Le résultat de l’opération prend le type de cette conversion. Ainsi ; 2.0/3 est équivalent à 2.0/3.0 et le résultat est de type float.
Le résultat d’une division d’entiers est aussi un entier !
- Code : Tout sélectionner
- 6 / 4 // Resultat: 1
- 6 % 4 // Resultat: 2
- 6.0 / 4.0 // Resultat: 1.5
- 6.0 / 4 // Resultat: 1.5
Opérateur | Traduction | Exemple | Résultat |
---|
+ | Addition | x + y | l'addition de x et y |
- | Soustraction | x - y | la soustraction de x et y |
* | Multiplication | x * y | la multiplication de x et y |
/ | Division | x / y | la division de x et y |
% | Reste | x % y | Reste de la division euclidienne de x par y |
+(unaire) | signe positif | +x | valeur de x |
-(unaire) | signe négatif | -x | négation arithmétique de x |
++(unaire) | Incrément | x++ ou ++x | x = x+1, ++x : préincrément; incrément, puis évaluation. x++ : postincrément; évaluation puis incrément. |
--(unaire) | Décrément | x-- ou --x | x = x-1, --x : prédécrément; décrément, puis évaluation. x-- : postdécrément; évaluation puis décrément. |
- Code : Tout sélectionner
- int x = 0;
- int y = x++; // y vaut 0;
- int z = x; // z vaut 1;
- int b = ++x; // b vaut 2;
C- Opérateurs d’affectation
Les opérateurs d’affectation permettent d’écrire dans une variable. On en distingue 2 types
Opérateur | Traduction | Exemple | Résultat |
---|
= | Affectation simple | x = y | place la value de y dans x |
(op)= | Affectation composée | x += y | place la valeur de x + y dans x (x=x+y) |
Exemple d’opérateurs composés : += -= *= /= %= &= ^= |= <<= >>=
D- Opérateurs relationnels
Toute comparaison est une expression de type int qui renvoie la valeur 0 (false) ou 1 (true). Il faut que les opérandes soient du même type arithmétique (ou des pointeurs sur des objets de même type).
Opérateur | Traduction | Exemple |
---|
< | strictement inférieur | x < y |
<= | inférieur ou égal | x <= y |
> | strictement supérieur | x > y |
>= | supérieur ou égal | x >= y |
== | égalité | x == y |
!= | inégalité | x != y |
Attention : Une erreur classique est de confondre l’opérateur d’égalité (==) avec celui d’affectation (=)
- Code : Tout sélectionner
- int i = 2;
- if (x = 4) printf("valeur de x : %d", x);
- if (x == 4) printf("valeur de x : %d", x);
Dans l’exemple ci-dessus, le code est correcte, mais le résultat n’est sans doute pas celui qu’on attend : les deux conditions sont évaluées à vrai !
Dans la première condition : on affecte 4 dans x, puis on évalue x (qui est différent de 0 donc vrai) et on affiche.
Dans la seconde condition, on compare x qui vaut désormais 4 à 4 donc vrai, et on affiche.
Pour éviter ce genre d’étourderie qui peut coûter des heures en debuggage, une bonne bratique simple consiste à mettre systématique les valeurs littéraires (ou en lecture seule à gauche)
- Code : Tout sélectionner
- int i = 2;
- if (4 = x) printf("valeur de x : %d", x);
- if (4 == x) printf("valeur de x : %d", x);
Ainsi la première condition provoquera une erreur de compilation, et vous pourrez corriger immédiatement l’erreur.
E- Opérateurs logiques
Les opérateurs logiques permettent de combiner le résultat de plusieurs expressions de comparaison en une seule expression logique.
Les opérandes des opérateurs logiques peuvent être n’importe quel scalaire (i.e arithmétique ou pointeur).
Toute valeur différente de 0 est interprétée comme vraie (et 0 correspond `a ’faux’).
Comme pour les expressions relationnelles, les expressions logiques renvoient une valeur entière (0=false ; 1=true).
Remarque : les opérateurs && et || évaluent les opérandes de gauche à droite et le résultat est connu dès l’opérande de gauche. Ainsi, l’opérande de droite n’est évaluée que si celle de gauche est vraie dans le cas de l'opérateur && ou fausse dans le cas de l'opérateur ||
- Code : Tout sélectionner
- if (0 && mask) printf("Jamais de la vie"); // mask n'est jamais évalué.
- if (1 || mask) printf("Jamais de la vie"); // mask n'est jamais évalué.
Opérateur | Traduction | Exemple |
---|
&& | ET Logique | x && y |
|| | OU Logique | x ||= y |
! | NON Logique | !x |
F- Opérateurs bit à bit
Les opérateurs bits à bits n’op��rent que sur des entiers. Les opérandes sont interprétées bits par bits (le bit 1 correspondant à une valeur vraie, 0 est considérée comme une valeur fausse).
Opérateur | Traduction | Exemple | Résultat |
---|
& | ET bit à bit | x & y | 1 si les bits de x et y valent 1 |
| | OU bit à bit | x | y | 1 si le bit de x et/ou de y vaut 1 |
^ | XOR bit à bit | x ^ y | 1 si le bit de x ou de y vaut 1 |
~ | NON bit à bit | x ~ y | 1 si le bit de x est 0 |
<< | décalage à gauche | x << y | décale chaque bit de x de y positions vers la gauche |
>> | décalage à droite | x >> y | décale chaque bit de x de y positions vers la droite |
Voici quelques exemples représentatifs :
x | y | Opération | Résultat |
---|
14 | 1110 9 | 1001 x & y 8 | 1000 |
14 | 1110 9 | 1001 x | y 15 | 1111 |
14 | 1110 9 | 1001 x ^ y 7 | 0111 |
14 | 1110 | ~x | 10001 |
14 | 1110 2 | 0010 x << y56 | 111000 |
14 | 1110 2 | 0010 x >> y3 | 11 |
IX- Les structures décisionnelles
Les structures décisionnelles sont des structures de controlles du flot d’exécuté qui permet d’orienté le déroulement du programme en fonction de l’évaluation du expression booléenne.
A- Instruction if
Cette première instruction de la famille des if permet d’exécuter une instruction ou un bloc d’instruction si une condition est remplie
Syntaxe
if ( expression )
Instruction ou Bloc d'instruction
Exemple :
- Code : Tout sélectionner
- int a = 5;
- int b = 10;
- if (! b % a )
- printf("b est un multiple de a");
Instruction if avec alternative simple
Cette seconde instruction de la famille des if permet d’exécuter une instruction ou un bloc d’instruction si une condition est remplie ou une autre instruction ou bloc d’instruction sinon.
Syntaxe
if ( expression )
Instruction ou Bloc d'instruction
else
Instruction2 ou Bloc d'instruction2
Exemple
- Code : Tout sélectionner
- int a = 5;
- int b = 10;
- if ( a>b )
- printf("a est strictement supérieur à b);
- else
- printf("b est supérieur ou égal à a);
Instruction if avec alternative multiple
Cette dernière instruction de la famille des if permet de proposer des alternatives multiples par des couplages des instructions précédentes.
Syntaxe
if ( expression )
Instruction ou Bloc d'instruction
else if ( expression2)
Instruction2 ou Bloc d'instruction2
else ...
Exemple
- Code : Tout sélectionner
- int a = 5;
- int b = 10;
- if ( a>b )
- printf("a est strictement supérieur à b);
- else if (a<b)
- printf("b est strictement supérieur à a);
- else
- printf("a est égal à b);
B- Instruction Switch
L’instruction d’aiguillage switch permet de tester une variable de type entière parmis un ensemble de constantes de type entière. La définition syntaxique est la suivante:
Syntaxe
- Code : Tout sélectionner
- switch ( valeur_entiere ) {
- case '2' : instruction1;
- case 2 : instruction2; instruction3;
- case '5' : instruction4;break;
- case 7 : instruction5;
- default : instruction6;
- }
Cette structure est à utiliser avec précaution, car elle n’est pas débranchante. En effet, lorsque la valeur entière est égal à l’un des cas, toutes les instructions qui suivent sont executés jusqu’à rencontrer l’instruction break. Cet type de comportement induit de nombreuses erreurs aux développeurs débutants. C’est pourquoi nous déconseillons fortement l’utilisation de switch lorsqu’ils sont utilisés en substitue de structure if.
Dans l’exemple précédent si la valeur_entiere vaut 2, les instructions 2,3,4 seront executés.
Dans l’exemple précédent si la valeur_entiere vaut 5, l’instruction 6 sera executé.
Dans l’exemple précédent si la valeur_entiere vaut 7, les instructions 5 et 6 seront executés.
L’instruction default est exécuté si aucun cas n’a été levé ont si aucun break n’est présent entre le case levé et l’instruction default.
C- L’opérateur Ternaire
L’opérateur ternaire est une forme condensée de l’instruction if/else qui renvoi une valeur.
Syntaxe
valeur = (expression)? valeur_si_vrai : valeur_si_faux;
Exemple
- Code : Tout sélectionner
- int a = 5;
- int b = 10;
- int max;
- // Avec la structure if
- if (a>b)
- max = a;
- else
- max = b;
- // forme condensée
- max = (a>b)?a:b;
Dans de nombreux cas, l’utilisation de cet opérateur permet d’alléger la lecture du code et surtout d’éviter de déclarer inutilement des variables.
Cas d’application Exemple
- Code : Tout sélectionner
- int max1(int a, int b) {
- int max;
- if (a>b)
- max = a;
- else
- max = b;
- return max;
- }
- int max(int a, int b) {
- return (a>b)?a:b;
- }
DJ Kin-C