Structure des programmes

De ZbahWiki.

Un programme en NXC est composé de blocs de code et de variables. Il y a deux types distincts de blocs de code : les taches et les fonctions. Chaque type de bloc de code a ses propres caractéristiques, mais ils partagent une structure commune. Le nombre maximum de blocs est de 256.

Sommaire

L'ordre du code

L'ordre du code a deux aspects : l'ordre dans lequel le code apparaît dans le fichier de code source et l'ordre dans lequel il est exécuté. Le premier symbolisera l'ordre lexical et le second l'ordre d’exécution. L'ordre lexical est important pour le compilateur NXC, mais pas pour la brique NXT. Cela signifie que l'ordre dans lequel vous écrivez votre tâche et de définitions de fonction n'a aucun effet sur l'ordre d'exécution. Les règles de contrôle de l'ordre d'exécution sont les suivants:

  • Il doit toujours y avoir une tâche appelée main et cette tâche sera toujours la première appelée.
  • Une fonction démarrera à partir du moment ou elle est appelé par un autre bloc de code.

Cette dernière règle peut sembler inutile, mais elle peut avoir de grosses conséquence lorsque plusieurs tâches sont en cours d’exécution.
Si une tâche appelle une fonction qui est déjà en course, cela peut entraîner un comportement imprévisible.
Les tâches peuvent partager des fonctions en les considérant comme des ressources partagées et en utilisant les mutex pour éviter qu'une tâche appelle la fonction alors qu'une autre l'utilise.
Le mot-clé safecall peut être utilisé pour simplifier le code.

Les règles pour l'ordre lexical sont:

  • Tout identifiant nommant une tâche ou une fonction doit être connue par le compilateur avant qu'il ne soit utilisé dans un bloc de code (tâche ou fonction).
  • La définition d'une tâche ou d'une fonction rend connu du compilateur l'identifiant de cette fonction ou tâche.
  • Lorsqu'une tâche ou une fonction est définie elle ne peut pas être redéfinie.

Parfois, vous pouvez être dans une situation où il est impossible ou inopportun d'ordonnerla définition des tâches et des fonctions de sorte que le compilateur sache chaque nom de chaque tâches ou fonctions avant de voir que le nom utilisé dans un bloc de code. Vous pouvez contourner ce problème en insérant des déclarations de tâches ou de fonctions de la forme :

task nom ;
return_type nom( liste_arguments );

avant le bloc de code où se produit la première utilisation. Le liste_arguments doit correspondre à la liste des arguments formels donnés plus tard dans la définition même de la fonction.

Les tâches

Depuis que le NXT supporte le multi-threading, une tâche en NXC correspond directement à un thread en NXT.

Les tâches sont définies en utilisant le mot-clé avec la syntaxe présentée dans l'exemple de code ci-dessous.

task nom () {
    // corps de la tâche
}

Le nom d'une tâche peut être n'importe quel identifiant légal. La programme doit toujours avoir au moins une tâche ( nommée «main» ) qui est démarré lorsque le programme est exécuté. Le corps d'une tâche consiste en une liste de déclarations.

Vous pouvez démarrer et arrêter des tâches avec des déclarations de début et de fin, dont il est question en-dessous. Toutefois, le principal mécanisme pour le démarrage des tâches est la fonction Precedes.

La fonction StopAllTasks arrête toutes les tâches en cour de fonctionnement. Vous pouvez aussi arrêter une tâche en cours de fonctionnement avec la fonction Stop. Une tâche peut se stopper via la fonction ExitTo. Pour finir, une tâche s'arrêtera à la fin du code qu'elle contient.

Dans l'exemple de code qui suit, la tâche main programme les tâches de musique, de mouvement et de contrôle avant d'arriver à sa fin et permet à à ces trois taches d'être lancées. Le contrôleur des tâches attend dix secondes avant d'arrêter la tâche de musique, et attend encore cinq secondes avant d'arrêter le programme.

task music() {
    while (true) {
        PlayTone(TONE_A4, MS_500);
        Wait(MS_600);
    }
}
task movement() {
    while (true) {
        OnFwd(OUT_A, Random(100));
        Wait(Random(SEC_1));
    }
}
task controller() {
    Wait(SEC_10);
    stop music;
    Wait(SEC_5);
    StopAllTasks();
}
task main() {
    Precedes(music, movement, controller);
}

Les fonctions

Il est souvent utile de regrouper un ensemble de déclarations dans une seule fonction, que votre code peut alors appeler au besoin.

Le NXC supporte les fonctions avec des arguments et des valeurs de retour. Les fonctions sont définies en utilisant la syntaxe ci-dessous.

[safecall] [inline] return_type name(argument_list)
{
    // corps de la fonction
}

Le type de retour est le type de données renvoyées. Dans le langage de programmation C, les fonctions doivent spécifier le type de données de retour. Les fonctions qui ne retournent pas de données sont de type void.

Des détails supplémentaires sur les mots-clés safecall, en ligne, et non avenue se trouve ci-dessous :

La liste des arguments d'une fonction peut être vide, ou peuvent contenir un ou plusieurs arguments. Un argument est défini par un type suivi d'un nom. Virgules séparent plusieurs arguments. Toutes les valeurs sont représentées comme des int, bool, char, byte, int, short, long, non signé, non signé chaîne de long, float, les types struct, ou des tableaux de tout type.

Le NXC supporte la spécification d'une valeur par défaut pour les arguments de la fonction qui ne sont pas une structure ou un type tableau. Il suffit d'ajouter un signe égal suivi de la valeur par défaut. Spécification d'une valeur par défaut rend l'argument optionnel lorsque vous appelez la fonction. Tous les arguments optionnels doivent être à la fin de la liste des arguments.

int foo(int x, int y = 20)
{
       return x*y;
}
task main()
{
       NumOut(0, LCD_LINE1, foo(10));
       NumOut(0, LCD_LINE2, foo(10, 5)); 
       Wait(SEC_10); 
}

Le NXC supporte également des arguments passés par valeur, en valeur constante, par référence, et par référence constante. Ces quatre modes pour passer des paramètres dans une fonction sont expliquées ci-dessous.

Lorsque les arguments sont passés par valeur à la fonction que l'on appel, le compilateur doit allouer une variable temporaire pour contenir l'argument. Il n'y a aucune restriction sur le type de valeur qui peuvent être utilisés. Toutefois, puisque la fonction utilise une copie de l'argument réel, l'appelant ne verra pas de changements fait sur la valeur passée en paramètre. Dans l'exemple ci-dessous, la fonction foo tente de définir la valeur de son argument à 2. C'est parfaitement légal, mais puisuqe foo travaille sur une copie de l'argument original, la variable de la tâche principale reste inchangée.

void foo(int x)
{
       x = 2;
}
task main()
{
       int y = 1;      // y vaut 1
       foo(y); // y vaut toujours 1!
}

Le deuxième type d'argument, le type constant, est également passé par valeur. Si la fonction est une fonction inline, puis les arguments de ce genre peuvent parfois être traités par le compilateur comme de véritables valeurs constantes et peuvent être évaluées à la compilation. Si la fonction n'est pas inline, le compilateur traite l'argument comme s'il s'agissait d'une référence constante, vous permettant de passer des constantes ou variables. Être en mesure d'évaluer pleinement les arguments de la fonction au moment de la compilation peut être importante puisque certaines fonctions de l'API NXC travaillent uniquement avec de vrais constantes.

void foo(const int x)
{
       PlayTone(x, MS_500);
       x = 1; // error - cannot modify argument
       Wait(SEC_1);
 
}
task main()
{
       int x = TONE_A4;
       foo(TONE_A5);    // ok
       foo(4*TONE_A3); // expression is still constant
       foo(x); // x is not a constant but is okay
}

Le troisième type, types_argument &, passe les arguments par référence plutôt que par valeur. Cela permet à la fonction appelée de modifier la valeur et que ces changements soient disponibles dans la fonction appelante après le retour de la fonction appelée. Toutefois, seules les variables peuvent être utilisées lors de l'appel d'une fonction à l'aide types_argument & arguments :

void foo(int &x)
{
       x = 2;
}
task main()
{
       int y = 1; // y vaut 1
       foo(y); // y vaut 2
       foo(2); // erreur - seuls les variables sont autorisées
}

Le quatrième type, const types_d_arguments &, est intéressant. Il est également passé par référence, mais avec la restriction que la fonction appelée n'est pas autorisé à modifier la valeur. En raison de cette restriction, le compilateur est capable de passer quelque chose, pas seulement des variables, aux fonctions en utilisant ce type d'argument. En raison de restrictions NXT firmware, le passage d'un argument par référence en NXC n'est pas aussi optimale que dans C. Une copie de l'argument est encore faite, mais le compilateur applique la restriction que la valeur ne peut être modifié à l'intérieur de la fonction appelée.

Les fonctions doivent être invoquée avec le bon nombre et le type d'arguments. L'exemple de code ci-dessous montre plusieurs différents appels légaux et illégaux à la fonction foo.

void foo(int bar, const int baz)
{
       // faire quelque chose
}
task main()
{
       int x; // on déclare la variable
       foo(1, 2);      // ok
       foo(x, 2);      // ok
       foo(2); // erreur - mauvais nombre d'arguments
}

Les variables

Toutes les variables en NXC sont définies en utilisant l'un des types énumérés ci-dessous:

Les variables sont déclarées en utilisant le mot-clé(s) pour le type désiré, suivie d'une liste séparée par des virgules de noms de variables et se termine par un point-virgule (';'). En option, une valeur initiale pour chaque variable peut être spécifié en utilisant un égal ('=') après le nom de la variable. Plusieurs exemples figurent ci-dessous :

int x;         // declare x
bool y,z;      // declare y et z
long a=1,b;    // declare a et b, initialise a à 1
float f=1.15, g; // declare f er g, initialise f
int data[10]; // un tableau de 10 zeros dans data
bool flags[] = {true, true, false, false};
string msg = "hello world";

Les variables globales sont déclarées à la portée du programme (en dehors de tout bloc de code). Une fois déclarée, ils peuvent être utilisés dans toutes les tâches, les fonctions et sous-routines. Leur champ d'application commence par la déclaration et se termine à la fin du programme. Les variables locales peuvent être déclarées dans les tâches et fonctions. Ces variables ne sont accessibles que dans le bloc de code dans lequel ils sont définis. Plus précisément, leur champ d'application commence par leur déclaration et se termine à la fin de leur bloc de code. Dans le cas des variables locales, une instruction composée (un groupe d'instructions entre crochets par '{' et '} ') est considéré comme un bloc:

int x;  // x is global
 
task main()
{
       int y;
       x = y;
       {
               int z;
               y = z;
       }
       y = z; // erreur - z n'est pas défini ici
}
 
task foo()
{
       x = 1; // ok
       y = 2; // erreur - y n'est pas une variable globale
}

Les structures

Le NXC supporte les types définis par l'utilisateur connu sous le nom structures. Ces sont déclarés très semblable à vous déclarez les structures dans un programme C.

struct car
{
   string car_type;
   int manu_year;
};
struct person
{
   string name;
   int age;
   car vehicle;
};
person myPerson;

Après avoir défini le type de structure, vous pouvez utiliser le nouveau type en déclarant une variable. Les membres au sein de la structure sont accessibles à l'aide d'un point.

myPerson.age = 40;
anotherPerson = myPerson;
fooBar.car_type = "honda";
fooBar.manu_year = anotherPerson.age;

Vous pouvez affecter les structures du même type, mais le compilateur se plaindra si les types ne correspondent pas.

Outils personnels