Tutoriel C 0 - Introduction

Avec ce tutoriel j'ai l'espérance qu'un débutant en programmation puisse comprendre les principes essentiels du langage C.
Pour introduire la programmation C je dois clarifier les points suivants:

Il est possible d'apprendre ce langage de manière académique, en ne présentant aucun des aspects liés à la pratique.
Dans mon tutoriel, pratique et théorie seront plus ou moins entremêlés.
Cependant il ne s'agit pas de confondre les logiciels de développement, avec le langage en lui même.
Pour développer en C il faut des outils de compilation, et d'édition de programmes.
Ceci sera détaillé par la suite.

1 - C, langage informatique


Comme dans tout tutoriel de programmation qui se respecte vous avez dû entendre parler dans les introduction d'un petit historique de l'informatique, car en effet pour comprendre l'existence d'un langage il faut se reposer sur l'histoire. Depuis que les machines programmables existent (voir Turing...) plusieurs moyens d'expression des ordres ont existés, les bandes perforées, puis plus tard lors de l'apparition des ASIC (chips semi conducteurs intégrés sur une plaquette de silice) la programmation numérique est apparue. Chaque nombre ordonnait une action (instruction). Mais ce langage dit machine, est peu convivial et difficilement lisible. En 1955 une femme eu l'idée d'encapsuler le langage machine dans un langage plus facile pour nous les humains, le langage d'assemblage, où chaque inscrution correspond à quelques lettres.
Par exemple au lieu de
0x080A0B on écrirait add eax, ebx ce qui se comprend plus facilement (additionner deux registres).
Encore plus tard sont apparus d'autres langages dit de "haut niveau" encore plus expressifs que l'assembleur. Puis en 1976, le langage C fut créé et immédiatement employé au développement d'Unix.
Depuis des compilateurs pour le langage C existent sur toutes les plateforme, ce qui en fait le langage de plus bas niveau portable à ce jour. Son expressivité est proche d'un pseudo code de description algorithmique, il est cependant de bas niveau donc très rapide à l'execution. En revanche il ne possède aucun mécanisme de sécurité inéhrents aux langages de plus haut niveau comme BASIC ou Java, notamment pour la gestion de la mémoire, l'accès dans les tableaux...
Le C bénéficie de plusieurs normes, celle des années 70, la norme ISO-1989 et la norme ISO/IEC/ANSI-1999.
Les compilateurs ne sont jamais totalement compatibles avec une norme, en général le compilateur référence est le GNU C COMPILER (GCC), si on lui spécifie les options de compilation -std=c99 -ansi.
La norme utilisée pour l'enseignement dans ce tutoriel est contre indication contraire, ISO-C99.
Depuis 1991 un autre langage compatible avec le C mais d'un paradigme tout à fait différent (orienté objet) appellé le C++ a été développé. Ce langage n'a à ce jour qu'une normalisation, appellée ISO-C++98.
Il est possible de compiler du code C89 à l'aide d'un compilateur C++.
Pour ceux qui seraient familier à la norme C89 courament enseignée, je conseille une visite sur ce site:
http://www.comeaucomputing.com/techtalk/c99/


1 - 2 - les moyens à mettre en oeuvre

pour faire du C il faut:

- un ordinateur pour compiler et executer le code
cela peut être votre PC, votre Mac ou autre.
ou bien un serveur, dans ce cas vous devez avoir accès à un terminal.
- un minimum de volonté (ah oui parce que ce n'est pas forcément facile)
- un système d'exploitation
- un logiciel capable d'editer du texte
- un compilateur C permettant la création d'executables sur votre système d'exploitation


voici les élements que j'utilise personnellement:
- ordinateur type PC
- systèmes d'exploitation Windows NT ou Linux
- sous windows: microsoft visual studio
sous linux: emacs et gcc (ceux-ci doivent fonctionner aussi sous MacOS X)

sous windows il existe aussi mingw (cygwin), borland dev, etc..

Il existe un très grand nombre de combinaisons, à vous de choisir la plus adaptée, mon tutoriel étant consacré au langage et non pas a la technique, si vous n'avez pas encore tous ces éléments cherchez comment les avoir sur un autre site.
par exemple:
http://c.developpez.com/compilateurs/


2 - l'Apprentissage

Quand vous apprennez une langue étrangère vous pouvez commencer directement par apprendre à dire certains mots de base.
Comme "oui" "non". (s'ils existent bien sur (voir le cas du latin !))
C'est ce que l'on fait aussi quand on apprend un langage informatique: on apprend à dire "bonjour" !
Mais d'abord il faut préciser comment mettre en oeuvre un programme et comment le faire fonctionner, voici une explication:

2 - 2 - Procédure

Un programme consiste en une suite d'instructions et de fonctions que vous tapez dans un éditeur de texte classique, cet éditeur peut parfaitement être intégré à l'environnement de programmation (IDE), facilitant ainsi les tâches de compilation.

Étant donné qu'il existe un très grand nombre de compilateurs et d'environnements je ne pourrais pas vous aider à les utiliser avec précision.
Je peux vous aider vaguement en expliquant pour la manière MS VC et la manière emacs/gcc

En général un programme informatique C est composé d'un fichier contenant le code source (extension .c souvent) c'est le minimum vital.
Il peut être accompagné d'un fichier d'en-tête (extension .h).
Les programmes plus compliqués sont composés de plusieurs de ces fichiers source et en-têtes, on les nommes modules.
Il peut aussi être joint des fichiers de ressources comme les images ou le texte etc...
tout ceci est compris dans le nom: projet.

Dans un environnement de programmation, créer un nouveau projet signife généralement acceder au menu "fichier" puis l'option "nouveau". Il est ensuite demandé des choix à l'utilisateur dans des boîtes de dialogue à propos du type de document qu'il veut créer.
Dans le cas de MS-VC6 il faut créer un workspace qui contiendra le projet. (Une solution dans VStudio >= 2001)

Pour ce, faites: File->New puis dans l'onglet Projects choisissez Win32 Console Application. Si l'option Create new workspace est selectionnée tout ira bien. Vous devez donner un nom à votre projet, une fois le projet créé il sera placé dans un dossier du nom du projet.

Il est très recommandé de créer un nouveau dossier pour chaque projet. Car un projet devient souvent toute une collection de fichiers !
(ici l'IDE s'en charge)

Dans la boîte de dialogue suivante choisissez empty project.
Une fois que le projet est créé il faut créer le fichier source principal (si vous avez choisi empty project, il n'y en a pas, sinon ce fichier est déja créé, mais il n'y a pas d'aprentissage si on ne part pas de zéro !). Pour ce, faites: file->new puis dans l'onglet files choisissez C source file et donnez lui un nom.
une fois ce fichier créé il se placera dans le répertoire Source Files du workspace, pour y accedez servez-vous de la vue par défaut à gauche de l'écran pour naviguer dans le workspace.
Si vous avez une autre IDE, la manoeuvre doit être décrite dans les fichiers d'aide.

Une fois tout ceci
préparé vous pouvez editer le code source et commencer a programmer.

Sous linux/mac os vous pouvez simplement créer un répoertoire à l'endroit où vous le souhaitez, par exemple à l'aide de la commande:
$ mkdir rep_de_mon_projet
et lancer un éditeur de texte (gedit, nedit, kwrite, vim, emacs..)
sur un nouveau nom de fichier, par exemple
$ cd
rep_de_mon_projet
$ emacs tutoriel.cpp
Vous êtes alors prêts pour taper votre code !
vous pouvez aussi activer "syntax highlighting" sous emacs pour colorer le code.

sous windows il est aussi tout à fait possible de taper son code dans des éditeurs qui ne permettent que de taper son code mais pas de les compiler, il s'agit déditeurs simples, non d'IDE.
Bloc note peut faire office d'éditeur de code mais cet éditeur est très désagréable car les indentations mesurent l'équivalent de 8 caractères d'espacement, il n'y a pas de couleurs, et les polices utilisées ne sont pas forcément proportionnelles.

2 - 3 - alors ce "bonjour" ?

suivez mon exemple:

#include <stdio.h>

int main(void)
{
    printf("bonjour!");
    return 0;
}


ceci constitue un premier programme executant une action, celle d'afficher bonjour dans la console (sortie standard).
Sous windows l'execution d'un tel programme ouvrira une console, fera l'affichage, et fermera immédiatement la console.
Certaines IDE permettent d'attendre l'appui sur une touche avant de quitter la console.
Sinon la solution peut être de lancer le programme par ligne de commande.
Ou bien d'utiliser la fonction système pause:
(ajouter
#include <windows.h> sous la ligne de stdio.h puis system("pause"); sous la ligne de printf(..))

Etant donné qu'il s'agit du programme classique d'un premier cours de programmation, il est tellement devenu anthologique qu'il porte un nom le caractérisant bien: le "hello world" !!

Vous pouvez recopier ce programme texto dans votre éditeur.
Pour lancer ce programme il faut d'abord le compiler, dans MS-VC++ il suffit de taper Ctrl+F5.
dans une auter IDE vous devez chercher une fonction build, ou compile (construit, compile) ou encore launch, execute ou Go! (lance, execute, aller!), ce sont les mots généralement utilisés.
cette action aura plusieurs effets que je décrirai plus loin.

Sous linux/mac vous pouvez utiliser le programme gcc
en faisant: $ gcc tutoriel.cpp
Cette action aura pour effet de générer un fichier binaire a.out dans le répertoire courant.
pour choisir son fichier de sortie utilisez l'option
-o fichier_sortie

Si vous constatez des erreurs lors de la compilation, il est fort probable qu'il s'agisse d'une erreur de syntaxe, avez vous correctement recopié le programme exemple ? :)
Si le linker (ld) donne une alerte, peut-être que la biliothèque standard C n'a pas été trouvée, dans ce cas revoyez la configuration de votre IDE. Sous linux toutes les distributions l'incluent. Sinon, réinstallez gcc.
Il peut aussi avoir des problèmes pour trouver les fichiers d'en-tête (.h), normallement les chemins doivent lui être fournis par la ligne de commande.
Lors de l'execution de ce programme sous linux, la sortie se fera dans le terminal qui a lancé l'application. Ce terminal représente la sortie standard de l'application.

Pour l'instant interessons-nous au programme lui même:
en voici les explications détaillées:

-1ère ligne:
l'instruction include ne fait pas partie du coeur du langage C elle est ici pour indiquer au préprocesseur de prendre en compte la liste des fonctions incluses dans la librairie stdio (standard Input-Output).
ceci va copier le contenu du fichier d'en-tête stdio.h dans le fichier source pour ensuite être compilé.
ceci nous permet d'utiliser la fonction printf. (voir la suite)
en fait pour bien imager les effets de include, le préprocesseur fait un grand copier/coller d'un programme comportant plein de petites choses de base bien utiles, et qui de ce fait, est devenu standard.
Il n'est pas forcément nécessaire de voir ce programme, ou bien de le copier nous meme à chaque fois, c'est pourquoi l'instruction include a été inventée.
printf n'est pas une fonction du langage C !
En fait le langage C est extrèmement dépouillé, il ne comporte pas des fonctions d'affichage aussi complexe que printf.

-2eme ligne:
c'est un vide, cela permet d'aérer le programme et de le rendre plus lisible, le langage C accepte des espaces et des retours à la ligne n'importe où dans le code source, sauf au milieu des mots-clés.
Cependant, je conseille d'employer la présentation que j'ai utilisé ici.

-3ème ligne:
ceci est une déclaration de fonction, ici la fonction main (principale), cette fonction est le point de démarrage de tout programme C/C++.
int est le type de retour de la fonction, void indique (vide) qu'elle ne prend pas d'arguments.
Vous saurez plus en détails ce qu'est une fonction dans le chapitre dédié.

-4ème ligne:
une accolade ouvrante, suivit d'une indentation, ceci permet de créer et de délimiter un bloc, ce bloc symbolise le corps de la fonction main.
les indentations ne sont pas obligatoires, il s'agit en général d'un caractere tabulation devant chaque ligne du code dans un bloc. Elle est décrite plus en détail dans le chapitre vocabulaire.

-5ème ligne:
on appelle la fonction printf, elle ne fait pas partie du langage non plus, mais on l'a ajouté grâce a l'inclusion de fonctions supplémentaires à l'aide de la directive préprocesseur décrite a la 1ère ligne (include).

-6ème ligne:
on ordone à la fonction main de passer le fil d'execution à la fonction supérieure. dans ce cas, la fonction au dessus de main est le système d'exploitation lui même. cette ligne a donc pour effet d'arreter le programme car c'est l'effet que produit toute sortie de la fonction main.
(remarque on renvoie la valeur '0' à l'OS pour lui dire que tout s'est bien passé)

-7ème ligne:
accolade fermante, fin du bloc de la fonction main.

j'éspère que ça commence a se clarifier dans vos esprits :)

2.5 - Bibliothèques:

Traduis directement de l'anglais "library", une bibliothèque est un objet comportant des fonctions utiles.
Par exemple ici nous nous servons de la bibliothèque standard d'entées et sorties du langage C: stdio
Dans notre programme nous avons inclu un fichier d'en-tête, stdio.h
lors de la compilation, l'effet de la directive "include" est de recopier le contenu du fichier à cet emplacement.
Le fichier .h est un fichier texte éditable comme le code source que vous venez de taper.
Généralement il comporte les prototypes des fonctions de la bibliothèque, ceci permet au compilateur de savoir comment elles doivent être employées.
La définition même des fonctions n'est pas accessible, le code des fonctions est déjà compilé dans des fichiers .lib (static library).
Le programme de liaison (linker) aura pour tâche de recopier ces fonctions dans votre executable final.

3 - Procédés

3 - 1 construction

La construction du programme se fait en plusieurs étapes comme vous avez déjà du le comprendre, voici ces étapes:

1 - préprocesseur:

Le préprocesseur n'agit que sur le texte du code source il ne compile rien, il remplace des éléments (constantes, inclusions de fonctions, etc...)
Il complète les tâches pour rendre le programme compilable. Il fait seulement du bête remplacement syntaxique.

2 - la compilation:

Elle aussi peut se décomposer en plusieurs parties, mais je ne suis pas expert en la matière alors je vais rester plus ou moins vague pour ne pas me risquer.
je sais qu'elle se fait en plusieurs passes, le code textuel comme vous l'avez lu dans le premier exemple est modifié et devient méconnaissable, en tout cas pour nous, mais de plus en plus familier pour la machine.
La dernière étape est la transformation en langage machine à partir du code objet. Le code objet est notre programme transformé dans le langage le plus bas niveau après le langage machine: l'assembleur. En langage machine à chaque instruction texte correspond une OPCODE c'est à dire une insctruction comprise dans la table d'instructions du microprocesseur. Cette table est le mode d'emploi même du processeur et est livrée par le constructeur aux développeurs qui la demandent.
Nous utilisons le plus courament sur nos PC des processeurs de type x86 executant la table d'instructions complexes d'INTEL.
Une fois le code objet généré, passe le linker.
Le linker est un programme très important qui copie le corps des fonctions à la place des déclarations.
Par exemple le linker va inclure dans le programme la définition de la fonction "printf" à partir du fichier stdio.lib
Bref au final notre programme texte devient EXECUTABLE. (on parle aussi de binaire)

Sous unix printf n'est généralement pas liée statiquement mais plutot dynamiquement à la libc.

3 - 2 syntaxe

La syntaxe du langage C est stricte, toute erreur sera signalée par le compilateur, et le programme ne pourras pas être construit.
Cette syntaxe a pour règles générales les suivantes:

- toujours mettre un point-virgule à la fin des lignes de bloc (une ligne de bloc est l'inverse d'une ligne de contrôle)
les lignes de contrôle introduisent les blocs, par exemple le bloc d'une fonction est introduit par le type de la fonction, le nom de la fonction et la liste des arguments.
un bloc de condition est introduit par l'instruction if et la liste des conditions (voir chapitre "structures de controle").
- Un bloc doit être délimité par des accolades comme dans l'exemple.
- Les arguments d'une fonction sont entourés de parenthèses et délimités par des virgules (comme en math, par exemple max(x, y)).
- une chaîne de caractère est entourée de guillemets
- un caractère unique est entouré d'apostrophes

4 - Vocabulaire

pour savoir parler un langage il faut savoir comment nommer les élements de ce langage.

- on désigne un élément primaire du code source comme étant un mot-clé.
par exemple le type que nous avons donné a notre fonction main était int, int est un mot-clé, et plus précisément un type de donnée.

- un type de donnée: est la caractéristique qui sera adopté par un élément, c'est à dire, il sera un nombre pouvant être plus ou moins gros/précis.
J'expliquerais ça plus en détail dans le chapitre "variables".
Tout est numérique dans un ordinateur, et le langage C etant relativement proche de la machine, tous les types de données sont des nombres. Certains langages offrent parfois des types génériques, comme les derniers java (generics), Visual Basic (variant), C++ (template). Le C ne le permet pas, car il fait partie des langages dits fortement typés. En revanche il existe le type void* qui permet de pointer vers n'importe quel type, mais sans aide de la part du compilateur quand à l'utilisation des objets référencés par un tel pointeur.

- une instruction: est un mot clé compris dans le langage de base, on ne peut faire autre que les apprendre par coeur.
par exemple il existe for, do, while, if, switch, continue, etc...

- une fonction: est une notion déja plus complexe que je décrirait dans le chapitres "fonctions"
en gros c'est un mot-clé qui permet d'executer une action plus ou moins compliqué, en mathématique la fonction cosinus, prend un angle en argument et retourne un nombre entre -1 et 1 qui à la particularité d'être la projection sur l'axe horizontal du vecteur de norme 1 orienté directement -par rapport a l'axe- de l'angle donné. En programmation une fonction est en rapport avec ce shéma mathématique, un nom, des paramètres et une valeur en retour.

- une indentation: est un espace fait de tabulation qui grandit au fur et a mesure que les bloc s'enchainent, par exemple:
for (;;)
{
    for (;;)
    {
        if (true)
        {
            // niveau 3 d'indentation
        }
    }
}

les indents ne sont pas obligatoires mais améliorent largement la lisibilité du programme.

- Un argument: est une valeur que l'on passe à une fonction, une fonction peut accepter de aucun à un nombre indéterminé d'arguments.
Remarque: l'abscence d'argument peut etre explicitement signalée par le mot-clé void.
Rem2: En mathématique on préfère parler de paramètres.

- Une déclaration: est le fait de présenter un élément comme une variable ou une fonction (dans le cas de la fonction, sa déclaration s'appelle un prototype)
exemple:
long SuperFonction(int arg1);    // ceci est une déclaration de fonction, autrement dit, un prototype.
ex2:
short toto;   // ceci est la déclaration de la variable toto
c'est grace à la déclaration que l'on connait la nature de l'objet. (son type, son nom, etc...)

- Une définition: est le code complet d'une fonction.
Dans le cas d'une variable, elle est définie lorsqu'elle est déclarée. Sauf dans le cas de l'utilisation du mot clé extern qui déclare une variable mais ne la définit pas.

- Un commentaire: est un morceau de texte que le compilateur ne prendra pas en compte, mais qui existe dans le code source, il permet souvent une meilleur explication du programme.
on l'introduit par //, dans ce cas le commentaire se termine à la fin de la ligne.
Sinon il existe /* comment */  cette notation permet des commentaires sur plusieurs lignes.
note: la notation du commentaire ligne // est nouvelle à la norme C99 et certains compilateurs peuvent ne pas l'accepter.

- Une directive: est l'appellation réservée aux "mots-clés" du préprocesseur.
par exemple #include, ou #define.

- Un opérateur: (voir le chapitre) c'est une application au sens mathématique.

- Une opérande: c'est sur quoi l'opérateur va agir.

- Un qualificateur: c'est un mot clé qui va apporter une précision à un type de donnée.
par exemple le qualificateur const devant un type permet de préciser qu'une donnée sera constante, inline qu'une fonction doit être recopiée plutôt qu'appellée dans un contexte (C99 seulement).

- Une instance/instanciation: une instance est l'objet réellement créé à partir d'une déclaration de variable d'un type donné. Par exemple après la déclaration
int b, b est une instance du type int. Ce vocabulaire est hérité du paradigme objet.

le reste du vocabulaire sera acquis dans les chapitres référant à leurs objets respectifs.


5 - Variables

Une variable n'est pas un mot-clé, c'est un nom inventé par vous, elle réference un espace en mémoire. Le contenu de cet espace est variable, d'où le nom.
La taille de l'espace mémoire auquel réfere votre nom est fonction du type de la variable.
En effet, le langage C fait parti des langages fortement typés, tout a un type et il ne peut pas changer.

A la création, la variable ne comporte que ce qu'il y avait en mémoire avant elle, c'est à dire un contenu imprévisible.
Il est possible de déclarer une variable n'importe où dans le code du programme. (sauf en C89 ou inférieur)
Un nom de variable ne doit pas commencer par un chiffre, cependant il peut en contenir. Il ne peut pas non plus contenir d'espaces, et sa taille est limité
(Je crois que la norme prévoit que seuls les 30 premiers caractères sont significatifs, les suivants ne seront pas refusés mais ne permettrons pas de différencier deux variables ayant même nom pendant leurs 30 premières lettres).
en C les noms de variable sont case-sensitive c'est à dire sensibles à la casse, les majuscules et les minuscules sont importantes !
Il est impossible de mettre des accents dans les noms de variable ni aucun caractère spécial.
(excepté le tiret souligné)

5 - 1 types

Comme je l'ai déja dit tout les types sont des nombres, pourquoi ? parce que dans la mémoire d'un ordinateur tout est stocké sous forme de nombres (dans la base 2 d'ailleur, ce qui ne simplifie pas la tache)
Et le langage C est un langage de bas niveau !
ce chapitre mérite une explication en détail car c'est une des bases les plus importantes de la programmation.
cette image sera surement explicite:

variable

dans ce cas précis, j'ai choisi le type int pour ma variable et le nom MaVar.
le nom MaVar est une sorte de réference pour l'espace mémoire choisi par le compilateur pour cette variable.
il existe les types suivants:

désignation du type
nom du type
taille occupée
int
integer
4 octets
long
long integer
4 octets
long long
long long integer
8 octets
float
floating point single precision
4 octets
double floating point double precision 8 octets
short
short integer
2 octets
char
character
1 octet
long double
floating point very large precision
10 octets
bool
booléen
1 octet

Il reste des commentaires à apporter sur ce tableau, en effet le type int est variable en fonction du type de machine, nos x86 sont 32 bits depuis le 386, le type int prend donc 32 bits, mais sur des systèmes plus vieux il prenait 16 bits (2 octets) c'est la raison de l'existence du type long.
le type long double n'existe sous aucun des OS de type Windows.
(il existe mais il ne fait que 80 bits)
le type char semble faire exception au caractère numérique de tout les types, mais pourtant ce n'en est pas une, le type char est bien un type numerique de 1 octet donc entre -128 et +127 en complément à 2 (les subtilités du binaire et des complément à 1 et à 2 doivent être expliquées sur mon manuel).
je vais quand même redonner les bases.
1 octet = 8 bits
1 bit (contraction de binary digit) est un chiffre
(un digit) dans la base 2 (le binaire) donc contenant ou 0 ou 1.
pour calculer le nombre de possibilités dans une base il suffit de faire: (base)^(nombre de digits)
(le chapeu indique l'exposant)
donc dans 8 bits (1 octet) on peu contenir 2^8 = 256 possibilités.
en mode signé (c'est à dire avec nombres négatif) complément à 2 (méthode de signature) le champ va de -128 à +127
vous suivez ?
on a donné le nom char à ce type puisque à la création du C tous les caractères se stockaient en 1 octet (voir table ASCII dans le manuel de ce site, partie 3)
Le type long long (entier sur 64 bits) est introduit à partir de la norme C99. Utilisez le suffixe LL sur vos constantes de type long long (comme le sufixe f pour les float).
ex:
unsigned long long grosseVariable = 584410012234000ULL;  // valide en C99 (pas en C++98 ni en C89)

floating

les types flottants sont les seuls types à ne pas stocker des nombres entiers, mais des nombres à virgule, grâce a des procédés relativements complexes compris dans la norme IEEE-754.
Le principe est de stocker ceci: (+-) 1,xxxxxx * 10 ^ eeee
on enregistre le signe sur un bit.
on enregistre les xxx qui composent l'"après virgule" du nombre dans ce que l'on appelle la mantisse.
on enregistre l'exposant eee dans la partie exposant. La base (ici 10) est spécifiée dans la norme, mais si ma mémoire est bonne, intel utilise une base 2, IBM utilisait une base 16, et les calculatrices non programmables une base 10. Pour des raisons de stabilité numérique, la base 2 apparait être plus adaptée.
Pour plus d'information google est votre ami.
Dans l'écriture de constantes du code, un nombre comportant un point sera de type double.
ex:
double d1 = 56.3;  // valide
float f1 = 89.4;  // warning, risque de perte de précision
float f2 = 51.98f  // valide

signé ou non signé:

pour chaque type de variable ormis les types flotants (ceux-ci sont constament signés) il est possible de choisir si votre variable sera signée ou pas, c'est à dire acceptera les nombres négatifs ou non.
pour ce, utilisez les mots-clé unsigned et signed par défaut le type est signé, le mot clé signed est donc pratiquement inutile, il permet seulement de voir explicitement que le type est signé si cela est très important à la bonne réalisation de vos algorithmes (but de compréhension donc).

exemple:

signed int truc;
unsigned short variable;

Ce code à pour effet de créer une variable truc qui peut stocker des nombres de -2 147 483 648 à +2147483647.
Et une variable nomée variable stockant de 0 à 32767.

exemple numero 2 complet:

#include <stdio.h>

int main(void)
{
    int nombre;
    double nb2;


    nombre = 55;
    nb2 = nombre;
    nb2 = nb2 + nombre;

    printf("nombre vaut: %d\nnb2 vaut: %f\n", nombre, nb2);

    nb2 = nb2 + 0.5;

    printf("maintenant nombre vaut: %d\net nb2 vaut: %f\n", nombre, nb2);

    return 0;
}


le résultat dans la sortie standard (la console) devrait être ceci:

nombre vaut: 55
nb2 vaut: 110.000000
maintenant nombre vaut: 55
et nb2 vaut: 110.500000

il y a plusieurs choses à expliquer dans ce programme, je vais seulement évoquer celles qui me semblent les plus importantes.
comme vous pouvez le constater les opérateurs = et + fonctionnent très bien en programmation C, leur fonctionnement sera expliqué dans un des chapitres suivant.
la fonction printf comporte la particularité de transformer les nombres compris dans les variables, en chaine de caractere affichable à l'ecran.
on signale une variable grace au caractère d'echappement '%' suivit de la définition du type présumé de la variable. cette suite s'appelle une séquence d'échappement.
par exemple
int var;
var = 4500;
printf("%d", var);
va afficher le contenu de la variable nomée var (ici 4500), si cette variable est de type int, cela marchera sans surprises.
si c'est un type double cela risque de donner des nombres pas du tout en rapport avec la valeur attendue.
au cas ou il y aurait plusieurs variables il suffit d'agrandir la liste des arguments, par exemple:
printf("%d%d%d", var1, var2, var3);
Il existe aussi la possibilité de passer à la ligne avec la séqence d'échappement \n.
l'exemple numero 2 est un bon exemple du mélange que l'on peut faire de tout ceci.
notez aussi le %f pour float, et %d comme decimal, consultez la documentation de la fonction pour plus de détails.
si vous n'avez pas de documentations, consultez la MSDN:
http://msdn.microsoft.com/library/default.asp
ou la commande
man printf sous unix.

il est possible de créer des alias de type grace au mot-clé typedef.
par exemple pour éviter de taper unsigned char à chaque fois que vous voulez une variable de 1 octet allant de 0 a 255, faites un alias de cette maniere:

typedef unsigned char byte;
byte supervar;


ceci fonctionne parfaitement !

5 - 2 pointeurs

Les pointeurs sont une notion importante du langage C, ils sont très utiles.
définition:
un pointeur est une variable comme une autre mais qui prend forcément 4 octets en mémoire (en tout cas sur les systèmes 32 bits)
Il sert à désigner (pointer) une variable quelconque en stockant directement son adresse en mémoire.
4 octets pourquoi ? parce que l'espace mémoire occupe 4 Go et pour addresser 4 Go il faut exactement 4 294 967 296 possibilités, c'est exactement ce que permet une variable de 32 bits.
les pointeurs prennent tout leur interet dans l'utilisation de tableau, et dans le passage d'arguments aux fonctions, tout ceci est développé dans les chapitres suivants.

note sur la mémoire des architectures x86:
sous windows, 2Go seulement sont adressables dans l'espace utilisateur, et 2Go pour l'espace superviseur (le système d'exploitation lui même).
Sur certains OS de serveurs (NT Advanced Server Edition ou bien 2003 server...) il est possible d'utiliser 36 bits d'adressage si l'architecture le permet (comme sur les Xeons).
Une architecture 64 bits comme l'IA64, UltraSPARC, ou même AMD64 ISA et EM64T ne signifie pas 64 bits d'adresse. Par exemple le contrôleur mémoire de l'Opteron utilise 48 bits d'adresse.

pour déclarer un pointeur il faut utiliser l'étoile * après le nom du type de la variable vers laquelle le pointeur pointe.

programme d'essai:

char var;
char* PointeurVersChar;
PointeurVersChar = &var;

ce bout de programme créer 2 variables, une de type char, et un pointeur vers le type char.
à leur création les 2 variables contiennent du débris de mémoire (elles ne sont pas initialisées)
il est possible d'initialiser une variable dès sa création en écrivant:

char var = 0;
char* Pointeur = 0;

par exemple.

la dernière ligne du programme (bleu) d'essai change la valeur non-initialisée contenue dans le pointeur, par l'addresse de la variable "var".
on appelle ça une assignation (voir le chapitre des opérateurs).
l'opérateur & permet de connaître l'adresse d'une variable, c'est l'opérateur de réferencement appellé parfois address-of.

Il est possible d'acceder au contenu de la variable en déréferençant un pointeur grâce à l'opérateur *, appelé opérateur d'indirection.
par exemple:

long var;
var = 0;


à le meme effet que:

long var;
long* ptr;
ptr = &var;   // assignement de l'adresse de la variable 'var'
*ptr = 0;   // indirection


ce qui fait de
*ptr et de var des alias (synonymes) !
autrement dit, 2 manières d'appeller le même espace mémoire.

5 - 3 - constantes

Une constante c'est comme une variable, mais que l'on ne peut pas modifier, il en existe plusieurs sortes, la constante se définit grace au mot clé const.
voici un exemple de constante:

const unsigned char c = 2;

une constante est OBLIGATOIREMENT initialisée, car par la suite elle est inaccessible en écriture, donc elle serait inutile non-initialisée.
une constante est une valeur accessible uniquement en lecture.
il existe aussi les constantes préprocesseur, on les créer grace à la directive #define.
par exemple:

#define SUPERCONSTANTE 500

cette ligne aura pour effet de remplacer toutes les occurences du texte SUPERCONSTANTE par 500 dans le code avant la compilation (au passage du préprocesseur)
sinon tout ce qui est définit dans le code (par exemple les valeurs) s'appellent des constantes aussi, par exemple:

int var = 0;
var = var + 9;


0 et 9 sont des constantes. (parce que elles ont été tapées dans le code, elles ont donc une valeur fixe)


6 - Tableaux

Un tableau est simplement un grand nombre de variables contenues dans une "variable" appellée tableau.
Les tableaux C-style comportent tous leurs éléments à la suite en mémoire.
Il est possible de faire des tableaux à plusieurs dimensions. Certains les appellent alors comme en mathématiques, des matrices (dans le cas de la dimension 2).
En anglais vous pourrez rencontrer le terme array ou vector (dans le cas à une dimension).
Dans des études de plus bas niveau sur la structure des objets, on entend parfois parler de memory pool ou bloc de mémoire.

exemples:

1 - tableau à une dimension de 50 éléments de type long:
int MonTablo[50];

2 - tableau à une dimension de 22 éléments de type unsigned char:
unsigned char MonTabloDeChars[22];

3 - tableau à 2 dimensions de 20 colonnes et à 40 lignes par colonnes où chaque élément est un type double
double GrosTablo[20][40];

un tableau dans l'espace de stockage automatique ne peut pas contenir des nombres de lignes différents par colonne, le tableau est forcément rectangulaire. (ou parrallélepipèdique en dimension 3, ou hypercubique après)
pour accéder aux élements d'un tableau, par exemple du tableau 1, il faut utiliser des crochets et un indice:
MonTableau[0] = 85;
ce bout de code assigne la valeur 85 à la première variable du tableau 'MonTablo', 85 est un nombre acceptable puisque ce tableau est de type int.
si l'on avait mis 85.45 ce nombre aurait été tronqué à 85 en raison de la limitation du type int (nombre entiers seulement).

NOTE IMPORTANTE: on accède aux éléments d'un tableau de 50 éléments grâce a des indices allant de 0 à 49 ! l'élément 50 n'existe pas.

information: l'accès a des élements hors-tableau existe, et en général c'est un bug, on l'appelle buffer-overrun.
Le C étant un langage d'assez bas niveau, aucune vérification sur la légalité des indices n'est faite. Les dépassements de tableaux sont la source la plus fréquente de failles de sécurités et aussi la source de plantages d'applications (segmentation faults).

considérez le tableau suivant:

type table[2][3];

la figure suivant représente tous les éléments de ce tableau:

array

les tableaux sont très utiles dans des programmes où il serait colossal d'avoir un nom de variable pour chaque élément.
voila un exemple d'utilisation des tableaux:

#include <stdio.h>
#include <time.h>

int main(void)
{
    float table[5];
    int var = 0;

    table[var] = time(0) % 80 + 0.5f;
    table[1] = table[var];
    table[2] = table[1] - 89.23f;
    var = 4;
    table[3] = table[var] = table[2] + 11.1f;  // ligne n°13

    printf("voici un affichage de la liste:\n%f %f %f %f %f\n", table[0], table[1], table[2], table[3], table[4]);


    return 0;
}


en voila un exemple qu'il est interressant :)
en effet il fait appel a plusieurs notions de programmation bonnes à voir.
au début on déclare les fichiers d'en-tête pour pouvoir utiliser les fonctions printf et time.
ensuite on déclare et on définit la fonction main.
ensuite on créer un vecteur de 5 float.
ensuite on déclare une variable de type int.

ensuite on assigne à l'élément d'indice var (ici var contient 0) le résultat d'un calcul que je vais expliquer ici:
time est une fonction de la librairie time.lib (déclarée dans time.h) qui retourne le temps en seconde écoulé depuis minuit.
pour ne pas récupérer un nombre toujours croissant avec le temps, et trop grand non plus, je fait un modulo 80 grace a l'opérateur %.
le calcul modulo se définit comme étant le reste de la division entière de la valeur à gauche par la valeur à droite de l'opérateur.
on obitent donc toujours un nombre entre 0 et 79, cet opérateur ne fonctionne qu'avec des nombres entiers.
ensuite j'ajoute 0.5, a quoi sert donc le f apres 0.5 ? par defaut un nombre à virgule dans le code est présumé comme étant un double, si on définit explicitement f apres ce nombre, ce sera un float, et ne créera pas d'avertissement à la compilation (l'avertissement est légitime car un nombre double stocké dans un float perdra de la précision).
l'expression est souvent évaluée de droite à gauche dans les compilateurs C, c'est pourquoi cela pourrait être dangereux d'ecrire mon calcul comme je l'ai fait, en effet il pourrait croire que je veut faire time(0) modulo 80.5, mais heureusement l'opérateur modulo est prioritaire par rapport à l'addition, mon calcul ne produit donc pas d'erreur. (en cas de doute utilisez des parenthèses, par exemple (time(0) % 80) + 0.5f)
ensuite j'assigne à tous les éléments de la liste des valeurs en fonction des éléments précedents, par exemple table[1] = table[var]; assigne dans la variable numéro 1 (c'est à dire le 2ème élément du tableau) la valeur comprise dans l'élément numero 'var' (ici 0) du tableau.
etc pour les suivants...

dans la  ligne N°13 j'utilise le fait que table[var] est lisible et inscriptible. il est donc possible de lui mettre un égal, a gauche, et un a droite !

pour l'affichage vous êtes maintenant en mesure de comprendre la finalité de la fonction printf n'est-ce pas :) ?

note: Il est interdit d'utiliser des variables dans les dimensions d'un tableau à la déclaration. Dans ce cas utilisez la fonction malloc().
En C99 la création de tableaux de taille fixe mais indiqués par une variable à la déclaration est quand même autorisé. (sauf si le tableau est static, global ou extern).
On appelle ces tableaux, des VLA (variable length array)
Dans ce cas pour le passer en paramètre à une fonction, utilisez la terminologie type identifiant[*] pour l'argument dans le prototype.
Les membres de structures ne peuvent pas être des VLA, sauf le dernier, on créé alors une structure flexible, mais je ne recommande pas l'usage de ces extensions bizare qui ne sont passées je ne sais pourquoi dans la norme 99.

Dimensions

Il est possible de créer des tableaux avec autant de dimensions que cela nous chante, mais on utilise rarement plus de 2 dimensions.
un tableau à 3 dimensions pourrait se représenter comme un parrallélépipède de prodondeur la taille de la 1ère dimension, le largeur la taille de la 2ème et de hauteur la 3ème.
exemple:

int Tablo3Dims[10][20][40];

un tableaux comme celui ci créer en mémoire 10*20*40 = 8000 variables de type int, ce qui représente 31.25 kilo octets (32 000 octets).
c'est relativement énorme comme tableau ! c'est pourquoi les tableaux supérieurs à cette dimension sont dangereux pour l'espace mémoire.

Arithmétique des pointeurs

Le nom d'un tableau est automatiquement un pointeur vers le premier élément de lui même.
par exemple "Tablo3Dims" est un pointeur vers Tablo3Dims[0], ce qui fait des 2 écritures:

Tablo3Dims;
&Tablo3Dims[0];

des alias !
ces alias donnent les adresses, mais il est possible de déréferencer les adresses pour acceder au contenu des ces adresses, ce qui nous donne encore 2 alias:

*Tablo3Dims;
Tablo3Dims[0];

en réalité l'opérateur [x] déréférence le pointeur pour acceder à la variable contenue à l'adresse "débuttableau + x * tailledutype".
il est possible de connaître la taille en octet d'un type grâce à la fonction sizeof.
par exemple:

long vardetypelong;
sizeof(long);
sizeof(vardetypelong);


les 2 appels à sizeof de cet exemple retournent le même nombre, ici 4.
en effet un type long occupe 4 octets.
le calcul x * tailledutype est automatiquement réalisé grâce à ce que l'on appelle l'arithmétique des pointeurs.
par exemple ajouter une valeur x à une adresse pointera forcément à l'objet en mémoire x * tailletutypedupointeur fois plus loin:

double tablo[5];

tablo + 4;    // ceci pointe sur le dernier élément du tableau
&tablo[4];    // cette ligne est un alias de la ligne du dessus !


c'est pourquoi:
si tab est par exemple déclaré comme suit: int tab[5];
alors *(tab + 2) est l'équivalent de tab[2]

6 - 2 - chaînes de caractères

la chaîne de caractère est une notion importante en programmation.
elle représente un morceau de texte, un mot une phrase n'importe quoi qui soit une suite de plusieurs caractères de texte.
on a déjà utilisé plusieurs chaines dans nos programmes jusqu'a maintenant, sans s'en rendre compte !
en effet dans la fonction printf, le premier argument est une chaine de caractere !
exemple:

printf("je suis un humain\n");

ceci est l'appel le plus simple de la fonction printf, on ne lui envoie qu'un seul argument.
le premier argument de printf est obligatoire et c'est une chaine de caractère, c'est pour cela qu'on la délimite par des guillemets.

mais aussi jusqu'a maintenant nous n'avons utilisé que des chaines constantes, il est temps d'en utiliser des variables:

- Chaines variables:

une chaine variable est en fait un tableau à une dimension de char (une liste de char).
cela peut aussi être des unisgned char (c'est plus pratique de commencer à 0 qu'a -128), il existe une option de compilation permettant de transformer le type char par defaut signed, en type char par defaut unsigned pour faciliter la gestion des chaines.
voici un exemple cela sera plus explicite qu'un long discours:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char chaine[400];

    strcpy(chaine, "super voila ma chaine");
    printf("%s", chaine);
    printf("\nlongueur du texte: %d caracteres\n", strlen(chaine));

    strcat(chaine, ", de plus etant donne sa taille on peut l agrandir");

    printf("\n\n%s\n\n", chaine);

    return 0;
}


Explications détaillées:
-au début on inclue les en-têtes pour pouvoir utiliser les fonctions strcpy, strcat et printf.
-on définit la fonction main
-on créer une liste de char de 400 caracteres (une chaine)
cette opération aura pour effet d'allouer 400 octets en mémoire à la suite pour nous permettre d'y stocker nos chaines.
-la fonction strcpy permet de copier les chaines
ici je copie la chaine constante "super voila ma chaine" dans la chaine variable 'chaine'.
précisons qu'une chaine en mémoire est toujours terminée par un caractère NULL (valeur 0) pour savoir ou est la fin de la chaine.
la fonction strcpy à le bon gout de placer ce char NULL pour nous.
-on affiche cette chaine grâce à la fonction printf, le premier argument de la fonction doit être constant, on ne peut donc pas écrire directement:
printf(chaine); cela créer une erreur de compilation.
il suffit de signaler à la fonction que l'on veut afficher une chaine, pour ce, on utilise la séquence d'échappement %s. (s signifie string, soit chaine en anglais)
le 2ème argument alors attendu par la fonction printf est un type char*, c'est l'adresse du début de la chaine que l'on veut afficher, il suffit pour cela de lui passer chaine puisque chaine est un pointeur (comme vu dans le chapitre tableaux), il aurait aussi été possible de lui passer &chaine[0].
-à la ligne suivante on affiche la longeur de la chaine 'chaine', pour ce, on compte le nombre de caracteres grace à la fonction strlen (string length) et on donne l'adresse de la chaine en argument.
Il est possible comme vous le voyez d'appeler des fonctions dans les arguments des fonctions !
Ceci aura pour effet d'executer en 1er les fonctions de droite à gauche remplacant ainsi les fonctions par les valeurs retournées. ici 21 !
il faut noter que cela signifie que la chaine occupe 21 caracteres et un 22eme caractère 0 à sa fin. donc en tout 22 octets aurait été suffisant.
-ensuite j'opère une opération dite de concaténation grâce à la fonction strcat (string concatenate), cela signifie que l'on ajoute une chaine après une autre, où le premier caractere de la 2eme chaine passe par dessus le caractère NULL de la premiere chaine.
cette fonction a aussi le bon gout de nous placer un caractère NULL à la fin de la chaine finalement formée.
-pour le reste vous devez pouvoir comprendre à ce niveau :) !


7 - opérateurs

7 - 1 - l'op '='

L'assignement est l'opération créée par l'opérateur "=", on assigne la valeur X à la variable.
par exemple:

short toto = 50;

ceci est une initialisation, mais aussi un assignement, on assigne 50 à la variable toto.
Une left value (l-value) est une valeur que l'on peut modifier, une right value (r-value) est une valeur non-modifiable comme la valeur de retour d'une fonction, ou bien une constante.
ici la variable toto est une l-value.

7 - 2 opérateurs de calcul

- le +

L'opérateur + que nous avons déjà utilisé plusieurs fois dans ce tutoriel est assez transparent d'utilisation, je ne ferais pas d'exemple.

- le -

même chose que le + !

- le *

même chose que le +, sauf qu'il multiplie bien sur.

- le /

la division faites par cet opérateur est entière si il s'agit de type int, long, short etc... et a virgule si dans le calcul figure au moins une variable de type float.
si vous voulez diviser 2 nombres entier et obtenir un résultat à virgule, vous devez caster vos variables en type float.
un cast consiste à changer momentanément le type de la variable.
par exemple:

int var1 = 20, var2 = 3;   // il est possible de déclarer plusieurs variable de type int en séparant par des virgules
float resultat;

resultat = var 1 / var2;   // resultat va contenir la division entière de 20 par 3, soit 6.
printf("sans cast: %f\n", resultat);
/* tandis que dans un cas comme celui la: */
resultat = (float)var1 / var2;
printf("avec cast: %f\n", resultat);

- le %

nous avons déjà eu affaire à cet opérateur dans ce tutoriel, il s'agit de l'opérateur modulo, j'ai déja donné ca définition plus haut.
voyons un exemple interressant:

int var1 = 20, var2 = 3, resultat, reste;
resultat = var1 / var2;
reste = var1 % var2;

printf("division de %d par %d, quotient: %d, reste: %d\n", var1, var2, resultat, reste);

- le ++ et le --

ces 2 opérateurs sont appellés respectivement opérateur d'incrément et de décrément (ou d'incrémentation...)
exmeple:

int var 0;
var++;
// maintenant var vaut 1
var--;
maintenant var vaut de nouveau 0

en fait ce sont les équivalents de var = var + 1 ou var = var - 1 mais avouez que c'est plus rapide à écrire ! :)

- l'ajout de = après un opérateur

si vous ajoutez = devant un opérateur cela représente l'equivalence avec la recopie de la variable après l'opérateur, voici un exemple:

int var;
var = var + 50;   //  est équivalent à:
var += 50;

ou:

int var;
var = var / 2;   //  est équivalent à:
var /= 2;

ou encore:

int var;
var = var * 5;   //  est équivalent à:
var *= 5;



- 2 - 2 - les opérateurs de bit

ces opérateurs agissent de manière booléenne, ou de manière bit-à-bit.

- le !

celui ci est un booléen, il retourne 1 ou 0 uniquement, pour s'en servrir il faut l'accoler à une variable.
si cette variable est nulle, il retournera 1, sinon 0.
exemple:

bool b, c;
b = true;
c = !b;


que voila quelque chose d'interressant, on créer 2 variables b et c de type bool.
ensuite on initialise b à true (vrai, c-a-d 1)
puis c à l'inverse booléen de b, dans ce cas précis, ce sera false, donc 0.
cet opérateur est très utilisé dans les évaluations d'expression dans les blocs conditionnels, voir le chapitre structures de contrôle.

- le &&

le double & est l'opérateur booléen qui execute la porte logique AND (ET)
exemple:

bool b, c, d;

b = false;
c = true;
d = b && c;   // b est l'opérande de gauche, c celle de droite


d contient false.

le && retourne true (1) si et seulement si les 2 opérandes (une opérande est la variable sur laquelle travaille l'opérateur) sont true.
sinon il retourne false.

- le ||

le double | (barre) est l'opérateur INCLUSIVE OR (OU INCLUSIF) il retourne true si AU MOINS une des opérande est true.

- le ^

ceci est un opérateur BITWISE c'est à dire BIT A BIT, il ne renvoie pas de valeur booléenne mais le résultat d'un calcul.
ce calcul c'est l'opération EXCLUSIVE OR, ou encore XOR (OU EXCLUSIF)
pour savoir comment fonctionne cet opérateur il faut prendre un exemple sur 1 bit:
si a et b sont 2 variables de 1 bit de long, a XOR b sera true (1) si et seulement si a et b sont differents.
si on procede ceci sur tout les bits des variables classique on obtient l'effet de ^.
shéma:
xor
- le & et le |

ce sont les mêmes opérateur que ^ sauf qu'ils executent les tables de vérité du AND et du OR (respectivement) en bit-a-bit.

- le >> et le <<

ces 2 opérateurs sont appellés opérateurs de shifting, ou de décalage, ils décalent les bits vers la gauche ou vers la droite de la manière présentée ci-dessous:

prenons le contenu d'une variable:
x = 101101000

si on fait ceci:

x = x << 2;

le résultat sera:

x = 110100000

si maintenant on fait ceci:

x >>= 1;

le résultat sera:

x = 011010000

compris ?

- le ~

le tilde est un opérateur effectuant un BITWISE NOT c'est à dire un complément à 1.
en gros il inverse tous les bits d'une variable.

si x = 00000010

alors x = ~x;
aura pour effet de rendre x comme ceci:

x = 11111101

opérateurs de test:

- > et <

supérieur, cet opérateur retourne un booléen, si une expression est supérieur à une autre

int a = 1, b = 2;

les expresions:
a < b est true
a > b est false


en revanche

int a = 1, b = 1;
a < b est false
a > b est false

- >= et <=

supérieur ou égal, et inférieur ou égal:

si a = 5 et b = 5;
a <= b est true
a >= b est true

si a = 4 et b = 5
a >= b est false
a <= b est true

- == et !=

== détermine si 2 expressions sont égales
!= détermine si 2 expressions sont différentes

voir des exemples dans le chapitre structures de controle


8 - Fonctions

la fonction main:

On la voit partout cette fonction, oui c'est normal, il s'agit d'un mot-clé inhérent au langage C, c'est le point de départ du programme !
Sans fonction main le linker ne serait pas capable de créer un executable.
La fonction main doit toujours retourner int !
Notez que en C99 si vous n'indiquez pas de valeur de retour explicitement, le compilateur insérera de manière cachée un
return 0; dans le code au lieu de générer une erreur. Je conseille cependant de toujours expliciter ce return par principe de non discrimination envers les autres fonctions, et pour garder la compatbilité C++ et C89.
Elle peut prendre les arguments de ligne de commande de la manière ANSI:

int main(int argc, char* argv[])

Il s'agit d'une déclaration/définition classique de la fonction main.
Le deuxième argument argv est un tableau (donc en fait un pointeur) contenant des objets de type "pointeur vers char".
argc est le nombre d'élements compris dans ce tableau.
Les objets pointés par les éléments de argv sont des chaines, comportant le texte qui à été entré par l'utilisateur lors du lancement du programme sur la ligne de commande.
Attention argv[0] est le nom du programme lui meme !
Notez que sous Unix, un troisième argument contenant un pointeur vers la chaine des variables d'environnement peut parfois être passé.

fonctions:

Vous connaissez déjà en gros l'effet d'une fonction mais nous allons développer ca pour tout mettre au net.
une fonction pour exister doit être déclarée (prototypée) et définie (programmée, écrite).
(à l'exception de main)
par exemple faisons une fonction qui ajoute 2 nombres:

#include <stdio.h>

int ajoute(int nombre1, int nombre2);  // prototype de la fonction "ajoute" (on parle souvent de déclaration)

int main(void)
{
   
    printf("ajout de 500 avec 400: %d\n", ajoute(500, 400));  /* c'est grace au prototype que le compilateur sais ce que veut dire "ajoute" à cet endroit du programme */

    return 0;
}

int ajoute(int nombre1, int nombre2)    // ceci est la définition = l'endroit où a été codée la dite fonction
{
    return  nombre1 + nombre2;
}

comme vous le voyez cette fois il y a 2 blocs de fonctions dans notre programme, le 2eme bloc est la definition de notre fonction.
et au dessus de main, le prototype est la déclaration de cette fonction.
il était aussi possible de ne pas faire de prototype et alors d'écrire le programme comme cela:

#include <stdio.h>

int ajoute(int nombre1, int nombre2)
{
    return  nombre1 + nombre2;
}


int main(void)
{
   
    printf("ajout de 500 avec 400: %d\n", ajoute(500, 400));  /* c'est parce que la fonction a été définie au dessus que le compilateur connais le mot 'ajoute' */
    return 0;
}

Les arguments ici sont nombre1 et nombre2. la valeur de retour de la fonction est de type int. les 2 arguments sont du meme type.
une fonction ne peut pas retourner plus d'une valeur.

Sans prototypage, les fonctions qui sont appellées doivent avoir étés définies au dessus dans le code.
C'est pour cela que je vous propose d'utiliser
exclusivement la technique du premier programme.

void:

Le type void permet de définir une abscence de quelquechose, par exemple d'argument ou de valeur de retour.
voyez cet exemple:

#include <stdio.h>

void retour(void);

int main(void)
{
   
    printf("salut");
    retour();
    printf("c'est moi");
    retour();

    return 0;
}

void retour(void)
{
    printf("\n");
}

Il est impossible de déclarer une variable du type void, par contre void* oui car c'est un type pointeur donc de taille connue, cependant le compilateur ne pourra pas vous aider lors des déréférencements sur les prédicats des types à respecter.

portée des variables

Chaque variable a une portée, c'est à dire des endroits dans le programme où elle peut être utilisée, et d'autre non.
une variable déclarée dans la fonction main ne peut être utilisée que dans la fonction main.
vous ne pouvez pas faire ca:

#include <stdio.h>

void changevar(int valeur);

int main(void)
{
    int var;
    changevar(56);
    printf("%d", var);

    return 0;
}

void changevar(int valeur)
{
    var = valeur;
}

si vous essayez de compiler ceci vous vous apperçeverez que votre compilateur râle.
moi j'ai ce message d'erreur: error C2065: 'var' : undeclared identifier
c'est tout à fait normal, 'var' dans la fonction changevar n'a pas été déclarée, var appartient a main elle n'existe donc pas dans la fonction changevar.

vous pouvez corriger le tir en passant la variable var comme globale, mais ceci est une méthode un peu trafiquée qui risque d'être embrouillante dans les programmes de plus grosse taille.

exemple:

#include <stdio.h>

void changevar(int valeur);
int var;  // var étant déclarée à l'exterieur de main, elle est maintenant globale

int main(void)
{

    changevar(56);
    printf("%d\n", var);

    return 0;
}

void changevar(int valeur)
{
    var = valeur;
}

vous pourrez constater que ceci compile et s'execute parfaitement.

fonctions d'allocation:

Les fonctions malloc(), realloc(), calloc() et free() de la bilbiothèque standard C inclues dans stdlib.h ou malloc.h permettent l'allocation dynamique de mémoire, pour les tableaux notamment.

exemple:

int taille = 568;
int* tablo;
tablo = malloc(sizeof(int) * taille);
tablo[420] = -8000;
free
(tablo);

Dans cet exemple, on remarque l'utilité de l'allocation dynamique: il est possible de donner une variable comme argument pour la taille ! Ce qui avec l'allocation automatique est impossible.
Attention à ne jamais oublier l'appel à free() sur les objets alloués par malloc, sinon la mémoire "fuit" (memory leak).

note sur les espaces de stockage:

L'allocation dynamique est parfois appellée allocation sur le tas (on the heap) du fait du nom de l'espace mémoire où se retrouvent les variables allouées avec ces opérateurs.
L'allocation automatique est l'allcoation par defaut du langage lors de déclarations classiques de variables, ces variables vont dans un espace appellé la pile (stack), la pile est spécifique au contexte de la fonction actuellement active, et à la fin de cette fonction la pile est dépilée, donc les variables locales sont détruites.
Lors de l'utilisation de variables dynamiques, seul l'opération free() peut les désallouer, elles peuvent donc se transmettre par pointeurs entre les fonctions.

note sur les performances:

Des tableaux alloués à l'aide de malloc() sur le tas (dynamiquement) seront long à allouer car il faut le temps au système de trouver une place contigue suffisament grand où mettre le bloc.
Les tableaux automatiques stockés dans la pile sont alloués en temps de complexité asymptotique constante (O(1)).
C'est à dire quasi-instantanément.

passage d'arguments par valeur et par adresse:

Pour ceux qui connaissent le BASIC les mots-clé associés au sujet sont ByVal et ByRef.
Alors qu'est-ce que ca veut bien pouvoir dire que tout ceci ?
quand vous faites une fonction, vous ne pouvez pas changer les variables d'une autre fonction, en tout cas vous pouvez toujours passer des arguments.
ce que vous connaissez jusqu'a maintenant est le passage par valeur
la fonction ajoute que nous avons faites ci-dessus acceptait deux arguments en passage par valeur.
nombre1 et nombre2 dans la fonction ajoute sont des copies des valeurs que vous avez donné en argument pendant l'appel de la fonction.
vous pouvez donc passer des constantes sans problème.
mais dans un cas comme celui la:

#include <stdio.h>

void ajoute(int nb1, int nb2, int resultat);

int main(void)
{
    int res = 0;

    ajoute(2, 3, res);
    printf("%d\n", res);

    return 0;
}

void ajoute(int nb1, int nb2, int resultat)
{
    resultat = nb1 + nb2;

    return;  // ce return est facultatif dans un cas ou la fonction est de type void.
}


Si vous essayez ce programme vous verrez que le résultat est 0.
c'est très logique, voila l'explication:
la fonction ajoute, ajoute bien les 2 nombres comme prévu, mais l'argument resultat est passé par copie, donc la variable résultat est totalement différente dans la fonction ajoute, que dans la fonction main, resultat pred bien la bonne valeur, mais res elle va rester à 0 !
pour changer la valeur de res, il faut utiliser les pointeurs, et passer l'adresse par copie et modifier le contenu de l'adresse dans la fonction.
cette méthode s'appelle un passage par adresse.
voici un programme qui cette fois-ci fonctionne très bien:

#include <stdio.h>

void ajoute(int nb1, int nb2, int* resultat);

int main(void)
{
    int res = 0;

    ajoute(2, 3, &res);

    printf("%d\n", res);

    return 0;
}

void ajoute(int nb1, int nb2, int* resultat)
{
    *resultat = nb1 + nb2;

    return;  // ce return est facultatif dans un cas ou la fonction est de type void.
}


vous saisissez l'astuce ? :)
on passe l'adresse de res (res de la fonction main) dans un argument de la fonction ajoute, puis dans la fonction ajoute on réference le pointeur grace à l'opérateur d'indirection étoile, et on modifie le contenu de l'adresse (dans ce cas, la variable res de la fonction main).

note de vocabulaire: on dit qu'une variable est passée par référence si on passe son adresse mais en réalité dans le langage C seul un passage par copie existe. On passe seulement la valeur de l'adresse d'une variable.
Ceci peut être une considération de performance importante lors du passage de structures, ou pire de tableaux.
Préférez les passages de pointeurs qui ne font que 4 octets généralement (une taille constante et faible du moins).

9 - Structures de contrôles

Enfin arrivé au chapitre le plus important, les structures de contrôles !
pour l'instant sans ça la programmation n'a rien de très puissant et ne permet de faire que peu de choses, des programmes linéaires relativement limités.
les structures de contrôles vont permettre des aiguillage dans le déroulement du programme, et infiniment augmenter le champ d'influence de vos programmes.

9 - 1 - La condition

La structure de contrôle condition est la plus importante, elle permet d'executer une partie du code seulement si une certaine condition est respectée.
le mieux c'est de donner un exemple:

#include <stdio.h>
#include <time.h>

int main(void)
{
    int pif;

    pif = time(0) % 2;  // pif est pseudo-aléatoire entre 0 et 1
    if (pif == 0)
    {
        printf("vaut 0\n");
    }
    else
    {
        printf("vaut 1\n");
    }

    return 0;
}

voici un programme qui affiche une phrase différente selon la valeur de pif, si pif vaut 0, ou si pif vaut autre chose.

if, else, else if:

le bloc de controle
if se créer comme ceci:

if (condition booléenne)
{
    // code à executer dans ce cas
}
else if (autre condition)
{
    // code a executer dans ce cas
    /*
    cette condition peut etre vraie, et pourtant ce bloc ne sera jamais executé dans le cas ou la condition du dessus est vraie aussi

    si vous voulez pourtant que ce bloc soit aussi executé, il ne faut pas utiliser else if, mais bel et bien un nouveau bloc if à part
    entière
    */

}
else
{
    // code executé au cas ou les 2 conditions au dessus n'aient pas été vérifiées
}

les parenthèses autours des conditions sont obligatoires.

exemple plus poussé:

#include <stdio.h>

int main(void)
{
    int var;

    scanf("%d", &var);

    if (var > 0)
    {
        printf("var positive\n");
    }
    else if (var == 0)    // c'est possible car > 0 n'inclu pas 0
    {
        printf("la var vaut précisement 0\n");
    }
    else
    {
        printf("var négative\n");   // si ce n'est pas supérieur à 0 ou égal à 0.
    }

    return 0;
}

Dans ce programme j'utilise la fonction
scanf qui permet de récupérer une entrée au clavier de l'utilisateur. Ici un nombre entier décimal que l'on stockera dans var, c'est pour cela que scanf demande un pointeur; j'ai donc déréferencé var grâce à l'opérateur adress-of (&).
Ensuite le programme parle de lui même !
Il faut tout de même préciser quelque chose d'important, chaque expression constituant les conditions, sont booléennes, c'est à dire qu'elles sont vraie ou fausse, point final.
Par exemple est-ce que var est supérieur à 0 ? oui ou non, il n'y a pas d'autre possibilités.
Vous avez le droit de mettre des expressions non booléennes dans les conditions, comme par exemple une variable, dans ce cas elle sera considérée comme vraie si differente de 0, fausse autrement.
autrement dit, booléennement:

var == 0 équivaut à !var
et
var != 0 équivaut à var

switch

le switch est une instruction d'embranchement, en fonction de 'cas' il execute un code, par exemple:

#include <stdio.h>

int main(void)
{
    int var;

    scanf("%d", &var);

    switch (var)
    {
    case 0:
        printf("var vaut 0\n");
        break;
    case 1:
        printf("var vaut 1\n");
        break;
    default:
        printf("var vaut autre chose que 1 ou 0\n");
        break;
    }

    return 0;
}

break est une instruction du langage qui permet les saut dans le code, un break dans le cas d'un switch sort du bloc du switch.
Si je n'avais pas mis break avant "case 1:" si l'utilisateur rentrait 1, les 2 phrases des cas 0 et 1 auraient été affichées.
Le tout dernier break est inutile.
L'instruction default est l'équivalent d'un else.

9 - 2 - la boucle

La boucle est la 2ème structure de contrôle, il y en a plusieurs type aussi, je vais présenter le plus important en premier:

for

la boucle for est la plus utilisée, il s'agit d'une boucle avec compteur, voici un exemple:

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < 50; i++)
    {
        printf("%d\n", i);
    }

    return 0;
}

Il est necessaire de commenter l'instruction for utilisée ici.
Elle est composée de 3 sorte "d'arguments", le premier est l'initialisation d'une variable, qui pourra servir de compteur.
Le 2ème est la condition de continuation, tant que i est inférieur à 50 on continue
le 3ème est executée à chaque boucle de manière aveugle, on y place le plus souvent l'incrémentation du compteur de la boucle.
Ces 3 arguments sont séparés de point-virgules.

do, while

la do while est le 2eme type de boucle, le compteur n'est pas gérée intrinsèquement, on s'en sert quand il n'y a pas besoin de compteur, ou seule une condition suffit pour en sortir.
voici un exemple:

#include <stdio.h>
#include <time.h>

int main(void)
{
    int limite;

    if (time(0) % 20 < 10)
    {
        limite = 10;
    }
    else
    {
        limite = 19;
    }

    do
    {
    } while (time(0) % 20 < limite);

    return 0;
}


ce programme attend au plus 10 secondes avant de sortir de la boucle.
remarquez que le même effet peut être réalisé par un programme bien plus simple:

#include <stdio.h>
#include <time.h>

int main(void)
{
    do
    {
    } while (time(0) % 10 < 9);

    return 0;
}


Ceci est un exemple de ce que l'on appelle l'optimisation, un code plus court et plus efficace à l'execution, pour la meme fonction.
c'est une bonne démonstration pour expliquer que un programme peut s'écrire d'une inifinité de manières différentes, programmez comme vous le sentez !

Il y a un deuxieme placement de while possible:

#include <conio.h>

int main(void)
{
    while (!_kbhit())
    {
    }

    return 0;
}

Dans ce programme le while est placé au dessus, dans ce cas, le do ne doit pas être écrit.
la fonction _kbhit comprise dans la librairie
conio vérifie si il y a frappe au clavier, dans ce cas elle retourne une valeur differente de 0 (c-a-d true), sinon elle retourne 0 (c-a-d false).
ce qui a permis dans ce programme de faire une fonction d'attendre d'appuis sur touche.

maintenant que vous êtes familier à tout ceci on peut dire que vous avez de bonne bases pour vous lancer dans des programmes pouvant atteindre une relative complexité !
en revanche pour se faire il faudra que vous appreniez plus de fonctions, car ce tutoriel n'a utilisé que peu de fonctions, dans la réalité il est nécéssaire d'en connaître toute une botte, histoire de ne pas trop restreindre les champs d'activité.
En particulier pour l'affichage d'objets à l'écran, dans ce cas il est préférable de réfléchir à la librairie graphique en fonction du système d'exploitation.
Par exemple GDI ou DirectGraphics sous windows.
Ou encore OpenGL et SDL qui tout deux peuvent-être utilisés sous linux, et sous d'autres OS, voire même d'autres type de machines. (Mac, PDA, consoles...)
le C est un langage portable qui existe sur plusieurs types de machines. Si toutefois on ne fait pas usage de bliblothèques non portables. Dans l'exemple ci dessous, la bibliothèque "conio" est utilisée, or seul microsoft l'a fournie, ceci abrège toute possibilité de compilation sur d'autres plateforme que windows. Sous linux il faudrait utiliser "ncurses", ceci est un problème de portabilité classique, l'écriture pseudo graphique dans une console est généralement dépendante du système.

Voici un programme plus complet, qui pourra vous servir de ressource, ce programme fonctionne sous windows:

#include <conio.h>
#include <stdio.h>
#include <windows.h>

void attentouche(void);
void cls(HANDLE h);
void posi(HANDLE h, short x, short y);
void couleur(HANDLE h, WORD coul);

int main(void)
{
    // change le titre de la console:
    SetConsoleTitle("tutoriel de Lightness1024!");
    // affiche une phrase de bienvenue:
    printf("Bienvenue au programme du tutoriel de Lightness1024!\n");
    printf("appuyez sur une touche");
    // attend l'appuis sur une touche, cette fonction à été définie plus bas
    attentouche();

    HANDLE cons; // un handle est une "poignée" en anglais, ce handle va permettre la gestion de la console
    cons = GetStdHandle(STD_OUTPUT_HANDLE);  // récupère le handle de la sortie standard
    // vous pouvez remarquer que la valeur STD_OUTPUT_HANDLE est une constante, elle a été définie dans le header windows.h


    bool exitboucle = false;  // ce type de variable de controle est un flag (drapeau)

    for(;;)  // boucle infinie sans condition, je préfère ceci a 'do {..} while(true)' car ca ne génère pas de warnings
    {
        cls(cons);  // efface la console
        couleur(cons, 11);  // bleu clair
        posi(cons, 20, 0);  // change la position du curseur   
        printf("---Menu---");
        couleur(cons, 10);  // vert clair
        posi(cons, 5, 7);
        printf("1 - c'est parti !");
        posi(cons, 5, 8);
        printf("2 - a propos");
        posi(cons, 5, 9);
        printf("3 - quitter");
        int clavier;
        clavier = _getch();  // récupération d'une touche
        switch (clavier)  // quoi faire en fonction de la touche appuyée
        {
        case '1':
            exitboucle = true;
            break;  // le break positionné ici sort du switch
        case '2':
            cls(cons);
            printf("Lightness1024! ProgrammatO E.U.R.L.\n");
            printf("programme associe au tutoriel C++\n");
            attentouche();
            break;
        case '3':
            return 0;  // sort de la fonction main, donc arrête le programme
        }
        // pour une seule instruction, les {} ne sont pas necessaires
        if (exitboucle)
            break;  // le break positionné ici sort de la boucle for
    }
    cls(cons);
    for (int coul = 0; coul < 32; coul++)
    {
        char ch;

        couleur(cons, coul);

        for (ch = 33; ch < 127; ch++)
        {
            printf("%c", ch);
        }
        printf("\n");
    }
    attentouche();
    return 0;
}

void attentouche(void)
{
    while (!_kbhit())
    {
    }
    _getch();
}

void cls(HANDLE h)
{
    COORD c;  // structure contenant un x et un y
    c.X = 0;
    c.Y = 0;

    FillConsoleOutputCharacter(h, ' ', 1000, c, NULL);  // pour effacer on remplis de bcp de caractere espace
    SetConsoleCursorPosition(h, c);  // ensuite on remet le curseur en haut
}

void posi(HANDLE h, short x, short y)
{
    COORD pos;
    pos.X = x;
    pos.Y = y;

    SetConsoleCursorPosition(h, pos);
}

void couleur(HANDLE h, WORD coul)
{
    SetConsoleTextAttribute(h, coul);  // les couleurs sont les 16 couleurs classiques
    // 0 = noir
    // 1 = bleu foncé
    // 2 = vert foncé
    // 3 = bleu gris foncé
    // 4 = rouge foncé
    // 5 = majenta foncé
    // 6 = marrons foncé
    // 7 = gris clair
    // 8 = gris foncé
    // ajoutez 8 aux valeurs pour avoir du clair
    // ajoutez 16 pour que ca soit le fond du texte
}


Il est marrant, il ne sert à rien, mais c'est une ressource comme une autre.
faites en ce que vous voulez c'est aussi en regardant les programmes des autres qu'on apprend.

10 - structures

Une structure c'est ce qu'un BASIC-ien appelerait un TYPE.
En fait c'est un type de "variable" dans lequel on peut mettre plusieurs variables, pas comme un tableau, puisque les variables peuvent être différentes.
Il est possible d'inclure des structures dans les structures.
la syntaxe est la suivante:

struct NomDeLaStructure
{
    TypeDeDonnee VariableQuelquonque;
    TypeAutre AutreVariable;
    ....
    ....
};


ensuite on peut déclarer des variables comme ceci:

struct NomDeLaStructure NomDeLaVar;

Acceder aux éléments comme ceci:

NomDeLaVar.VariableQuelquonque = MachinDeTypeCompatible;


On note que devoir placer l'indentification struct devant le type semble être une lourdeur non nécessaire.
On détourne cette lourdeur du C en ajoutant un typedef devant struct. (comme dans le programme suivant).

prenons un exemple:

#include <stdio.h>

typedef struct JOUEUR_
{
    bool humain;
    char* nom;
    long points;
    bool actif;
} JOUEUR;  // ceci est la différence entre un tag et un nom de structure, ici le type JOUEUR existe désormais

int main(void)
{
    JOUEUR joueur[5];  // on créer 5 joueurs
    int nb;

    printf("combien voulez vous de joueurs ? (entre 2 et 5):\n");
    scanf("%d", &nb);
    if (nb > 5)
        nb = 5;  // sécurité au cas ou l'utilisateur ait tapé plus de 5
    if (nb < 2)
        nb = 2;

    char noms[4][30];  // la zone de mémoire ou sont réelement stockés les noms !
    int i;

    for (i = 1; i < nb; i++)
    {
        joueur[i].actif = true;
        joueur[i].humain = true;
        joueur[i].points = 0;
        printf("quel est le nom du joueur %d ?:", i);
        scanf("%29s", noms[i - 1]);  // le 29 permet de ne copier qu'un maximum de 29 caractères dans l'espace mémoire
        joueur[i].nom = noms[i - 1];   // ceci n'est qu'un pointeur vers le nom !
    }
    for (i = nb; i < 5; i++)  // on initialise tout de meme le reste
    {
        joueur[i].actif = false;
    }
    joueur[0].humain = false;  // le premier joueur c'est l'ordinateur
    joueur[0].nom = "ordinateur";

    printf("\nnous avons donc:\n");
    for (i = 0; i < nb; i++)
    {
        printf("%s, joueur %d\n", joueur[i].nom, i);
    }

    return 0;
}


Dans le cas où l'on ait à traiter avec un pointeur vers une structure, le moyen logique pour acceder a ses membres serait:

MegaStruct maStruct;
MegaStruct* p_maStruct = &maStruct;

(*maStruct).uneVariableMembre = ceci;


mais il existe un opérateur plus rapide à écrire pour faire la même chose: ->

MegaStruct maStruct;
MegaStruct* p_maStruct = &maStruct;

maStruct->uneVariableMembre = ceci;


ces programmes sont équivalents !

11 - macros

une macro, c'est comme une toute petite fonction, mais qui serait recopiée à la place de son nom chaque fois qu'elle apparait.
ça à l'avantage d'être rapide à l'execution, mais de faire prendre plus de place au programme compilé.
on créer une macro grâce à la directive préprocesseur #define.
voici un exemple:

#include <stdio.h>

#define PI 3.14159  // constante
#define Radian(degres) ((degres) * (PI / 180.0f))   // macro

int main(void)
{
    float angle;

    angle = 360;
    angle = Radian(360);
    printf("%f\n", angle);
    return 0;
}

Il existe presque le même principe avec les fonctions, ce sont les fonctions déclarées inline.
(la notion de fonction inline existe pour le C depuis la norme C99, pour copier le C++)

Attention avec les macros ! car il s'agit d'un simple replacement syntaxique, considérez cette macro:

#define MAX(x, y) (x > y ? x : y)

si vous décidez d'appeller cette macro en faisant comme ceci: MAX(var1++, var2);
l'effet escompté ne sera surement pas atteind, car le code réellement compilé sera en fait !
var1++ > var2 ? var1++ : var2
ce qui aura pour effet d'incrémenter deux fois var1.

De manière générale l'utilisation de macros est rare et c'est tant mieux, préférez lui toujours une fonction classique à passage de paramètres sur la pile. Ou préférez lui une fonction inline, ce qui aura pour effet de recopier le contenu de la fonction à chaque positions où elle est appellée (attention une fonction inline n'a pas de déclaration externe, donc définissez les dans les .h).
Et ce, sans conflit de noms. Notez cependant que déclarer une fonction inline peut ne pas avoir d'effet par rapport à une fonction normale, de la même manière que le qualificateur register, le compilateur se réserve le droit de ne pas en tenir compte si il trouve que la qualification n'est pas appliquable.

13 - conclusion


If you want it code it !



Copyleft 2003-2005 Lightness1024! ProgrammatO E.U.R.L.
retour à l'index