Le langage C : partie 3

a marqué ce sujet comme résolu.

Ah ok d'accord! Toutes mes confuses. ^^

daimebag

Pas de soucis. ;)

J'ai une petite question, est-ce qu'il existe un pdf avec les 3 parties ? ou juste les 2 parties et la 3 éme ? car sur le tuto je vois archive mais pas le "pdf"

Drakop

Les PDFs ne sont produits que lors de la publication il me semble. Il faudra attendre sa fusion avec le cours principal pour cela. :/

+0 -0

C'est un peu space la partie sur le retour de fonction ! lol

Pourquoi retourner un pointeur de fonction est sans doute là d'une des syntaxes les plus dégoûtante du langage C ? Ça peut être pratique de retourner un pointeur de fonction en fonction d'un cas particulier. En général on fait l'inverse, on passe un pointeur de fonction a une fonction, mais je l'ai déjà utilise quelques fois et je trouve pas ça si 'dégeu'. :)

Je vois pas trop l’intérêt de la partie sur les définitions récursives. lol Tu aurais un exemple concret de ce genre d'utilisation ?

C'est un peu space la partie sur le retour de fonction ! lol

Pouet_forever

En effet. :p

Pourquoi retourner un pointeur de fonction est sans doute là d'une des syntaxes les plus dégoûtante du langage C ? Ça peut être pratique de retourner un pointeur de fonction en fonction d'un cas particulier.

Pouet_forever

Disons que, avec le retour d'un pointeur sur tableau, il s'agit pour moi des deux syntaxes les plus « laides » du C dans le sens où cela devient vite illisible. Le prototype de la fonction signal() en est un bon exemple.

1
void (*signal(int sig, void (*func)(int)))(int);

Je vois pas trop l’intérêt de la partie sur les définitions récursives. lol Tu aurais un exemple concret de ce genre d'utilisation ?

Pouet_forever

Le cas qui me vient à l'esprit est une machine à états où chaque état est représenté par une fonction qui renvoie vers l'état suivant (et donc vers une fonction de signature identique). L'exemple que je donne est une version très simpliste d'une telle machine à état. Après, c'est vrai que ce n'est pas hyper fréquent, mais je pense que cela vaut le coup d'en toucher un mot. :)

+0 -0

Ah oui ! Dans le cas de la lisibilité je suis entièrement d'accord ! ;) Cela dit, dès lors qu'on manipule des pointeurs de fonctions, en général on glisse un joli typedef. Ça évite les risques d'erreur liés a une mauvaise écriture du prototype ! Par rapport a signal(), d'ailleurs dans le man ils font le typedef : typedef void (*sighandler_t)(int);

Pour la machine a états, pas terrible l'exemple ! lol Un autre exemple ? :p

Cela dit, dès lors qu'on manipule des pointeurs de fonctions, en général on glisse un joli typedef. Ça évite les risques d'erreur liés a une mauvaise écriture du prototype !

Pouet_forever

Clairement, c'est tout de suite plus clair avec. ^^

Pour la machine a états, pas terrible l'exemple ! lol Un autre exemple ? :p

Pouet_forever

Comme ça, non. Mais je suis sûr qu'il y en a pléthore d'autres. :-°
Qui a dit « non » ?

+0 -0

Pour la machine a états, pas terrible l'exemple ! lol Un autre exemple ? :p

Pouet_forever

Ca veut dire quoi "pas terrible" ? :p

Parce que bon, y a plein de manieres de voir des automates un peu partout. Un exemple qui m'est venu en tete dernierement : ecrire un analyseur lexical simple en C. Une maniere assez elementaire de le voir consiste a penser le truc comme un automate. Exemple d'analyse lexicale d'un mini-langages avec des expressions arithmetiques et des commentaires imbriques "a la Caml" :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum { MAX_LINE_LENGTH = 1000 };

typedef enum {
    TEMPTY, TEOF, LPAR, RPAR, TADD, TSUB, TMUL, TDIV, TREM, TNUM, NB_TOKENS
} token_type;

static const char *string_of_token_type[NB_TOKENS] = {
    "",
    "",
    "lpar",
    "rpar",
    "add",
    "sub",
    "mul",
    "div",
    "rem"
};


typedef struct {
    token_type type;
    int value;
} token;

typedef struct next_token {
    token t;
    struct next_token (*next)(char **);
} next_token;

static token_type binops[UCHAR_MAX + 1] = {
    [(unsigned char)'+'] = TADD,
    [(unsigned char)'-'] = TSUB,
    [(unsigned char)'*'] = TMUL,
    [(unsigned char)'/'] = TDIV,
    [(unsigned char)'%'] = TREM
};

static int comment_depth;

next_token get_token(char **input);
next_token get_comment(char **input);

int get_number(char **input) {
    int ans = 0;

    while (isdigit(**input)) {
        ans = (ans * 10) + **input - '0';   
        *input += 1;
    }

    return ans;
}

next_token get_token(char **input) {
    unsigned char c = **input;

    if (**input == '\0') {
        return (next_token) { { .type = TEOF }, NULL };
    } else if (isspace(**input)) {
        *input += 1;
        return (next_token) { { .type = TEMPTY }, get_token };
    } else if (isdigit(**input)) {
        return (next_token) { { .type = TNUM, .value = get_number(input) }, get_token };
    } else if (strncmp(*input, "(*", 2) == 0) {
        comment_depth = 1;
        *input += 2;
        return (next_token) { { .type = TEMPTY }, get_comment };
    } else if (**input == '(') {
        *input += 1;
        return (next_token) { { .type = LPAR }, get_token };
    } else if (**input == ')') {
        *input += 1;
        return (next_token) { { .type = RPAR }, get_token };
    } else if (binops[c]) {
        *input += 1;
        return (next_token) { { .type = binops[c] }, get_token };
    } else {
        fprintf(stderr, "Unexpected token: %c\n", **input); 
        exit(EXIT_FAILURE);
    }
}

next_token get_comment(char **input) {
    if (**input == '\0') {
        fprintf(stderr, "Non terminated comment\n");
        exit(EXIT_FAILURE);
    } else if (strncmp(*input, "*)", 2) == 0) {
        --comment_depth;
        *input += 2;
        return (next_token) { { .type = TEMPTY }, comment_depth == 0 ? get_token : get_comment };
    } else if (strncmp(*input, "(*", 2) == 0) {
        ++comment_depth;
        *input += 2;
        return (next_token) { { .type = TEMPTY }, get_comment };
    } else {
        *input += 1;
        return (next_token) { { .type = TEMPTY }, get_comment };
    }
}

void print_token(token t) {
    if (t.type == TNUM) 
        printf("%d\n", t.value);
    else if (t.type != TEMPTY)
        printf("%s\n", string_of_token_type[t.type]);
}

int main(void) {
    char s[MAX_LINE_LENGTH + 1];

    while (fgets(s, sizeof s, stdin) != NULL) {
        next_token p = (next_token) { { .type = TEMPTY }, get_token };
        char *q = s;

        while (1) {
            p = p.next(&q); 
            if (p.t.type == TEOF)
                break;
            print_token(p.t);
        }
    }

    return 0;
}

Si tu as une autre solution, qui, a l'instar de celle-ci :
(1) Se generalise a des expressions regulieres plus compliquees, avec pleins de regles qui se chevauchent.
(2) Peut etre generee "automatiquement" a partir d'un langage intermediaire de regles de ce type.
Ca peut etre interessant de comparer. ;)

Qu'est-ce qui t’empêche de faire une machine a états 'classique' ? :)

Pouet_forever

Foncièrement, rien. :-°
Mais c'est beau, non ? :p

Sinon, je viens de modifier la phrase concernant la syntaxe du renvoie d'un pointeur de fonction pour qu'elle dramatise un peu moins. Toutefois, elle ne sera visible que lors de la publication du cours (la validation est en court). Cela te semble-t-il mieux ? :)

Dans l'autre sens, il est possible de retourner un pointeur de fonction à l'aide d'une syntaxe… un peu lourde. :-°

+0 -0

Bah, une machine a états 'classique' c'est simplement une variable, une boucle et un switch/tableau. :p Un peu de pub pour moi, regarde la fonction mainLoop dans main.c ou game.c la fonction gameMain : https://github.com/pouet/tetris/

Pour moi c'est exactement le meme principe, du coup. Si tu as besoin de recuperer l'etat pour faire autre chose que juste appeler la fonction suivante, alors effectivement proceder comme ca est sans doute la meilleure idee, vu que comparer des pointeurs sur fonctions la j'avoue que c'est pas tres beau (et dans ton code du Tetris effectivement tu as l'air d'utiliser tes constantes un peu partout).

Mais dans le cas ou on fait pas grand chose dans l'appelant, creer toute une suite de constantes qui porte le meme nom que plein de fonctions, puis faire un gros switch (ou un gros tableau dont l'initialisation ressemble furieusement a celle du "tableau identite"), c'est juste compliquer le probleme pour rien, recopier du code partout, faire des erreurs, etc. Et pour le coup j'ai meme pas d'arguments contre (a part la syntaxe pas tres naturelle, mais c'est justement le but du passage de demystifier un peu la chose).

Le principe d'une machine a états restera toujours le même. ;)

Bah, le problème d'un pointeur de fonction, c'est aussi que tes appels de fonctions doivent toujours respecter le même prototype. Faire un switch, ça permet de faire autre chose et éventuellement appeler d'autres fonctions qui n'ont pas le même prototype.

Bonjour-soir !

J'ai repris très récemment votre cours sur le langage C. J'ai commencé la partie 3 et j'ai pris quelques notes, qui reflètent mon avis de débutant, néanmoins je pense que ça peut-être intéressant. Il faut garder à l'esprit que je peux dire des bêtises, peut-être qu'il y a des choses que je pense comprendre alors que ce n'est en réalité pas le cas … mais j'aime beaucoup vos tutos et si je peux apporter un avis constructif je souhaite essayer.

https://zestedesavoir.com/contenus/beta/833/le-langage-c-partie-3/notions-avancees/la-representation-des-types/

Au début de page dans le tableau, il est noté 32 au lieu de 42.

https://zestedesavoir.com/contenus/beta/833/le-langage-c-partie-3/notions-avancees/manipulation-des-bits/

Je n'ai pas réussi à comprendre ces notions du premier coup avec votre page, je suis allé consulter d'autres tutos sur le web qui m'ont éclairé. J'ai pu revenir sur le votre après avoir compris … ailleurs ^^ Du coup je pense qu'il est intéressant que je partage mon ressenti. Mais bien entendu je ne prentends pas du tout être un exemple, je fais seulement quelques propositions. Cela reste donc très subjectif.

En fait, j'ai eu beaucoup de mal à comprendre de quoi cette page parle, ce que signifiait "bit à bit", pourquoi les exemples donnés faisaient ci ou ça … du coup après avoir passé un peu de temps sur ces notions, je tente quelques propositions.

Tableau en début de page placé sous le sous-titre "Les opérateurs « et », « ou inclusif » et « ou exclusif »" Je trouve que le tableau n'est pas clair, ou pas complet j'sais pas trop … je ne le saisis pas bien. Les opérateurs ne sont pas bien présentés je trouve. J'aurais personnellement préféré :

| : renvoie 1 si au moins l'un des deux bits est 1

^ : renvoie 1 si les deux bits sont différents

& : renvoie 1 si les deux bits sont 1

ce qui donne ce tableau :

1
2
3
4
5
6
7
8
9
2 bits comparés  | | | ^ | & 
-----------------+---+---+----
    0 0          | 0 | 0 | 0
-----------------+---+---+----
    0 1          | 1 | 1 | 0
-----------------+---+---+----
    1 0          | 1 | 1 | 0
-----------------+---+---+----
    1 1          | 1 | 0 | 1

Premier code présenté : Je trouve les exemples compliqués (ils ne m'ont pas aidé à comprendre, je les ai compris après avoir appris sur d'autres sites).

1
2
/* 0111 1111 & 0011 1100 == 0011 1100 */
    printf("%2X\n", a & b);

Donc "a & b" c'est égal à b … je lisais ça comme ça, je ne comprenais rien. Il faut en fait comparer les chiffres 2 par 2, pour en ressortir un nouveau chiffre (on compare deux bits qui ont la même "position", donc le même poids si j'ai bien compris), ben ça je ne l'avais pas du tout compris. Il manque peut-être un exemple plus simple pour faire une transition … quelque chose comme ça :

(avec en plus un schéma qui démontre que l'on compare 2 bits de même poids pour obtenir un nouveau bit)

1
2
3
4
5
6
67 = 1000011
98 = 1100010

67 | 98 = 1100011  -> 99
67 ^ 98 = 0100001  -> 33
67 & 98 = 1000010  -> 66

J'ai mieux compris le reste de la page après avoir assimilé ces notions là. Personnellement j'ai eu beaucoup de mal avec cette page dés le début, c'est surtout ça que je souhaite vous dire. Les explications manquent de clarté, ça va trop vite et j'ai l'impression qu'il faut déjà avoir quelques notions sur le sujet pour pouvoir suivre. Bien évidemment je me répète, tout cela concerne MON point de vue, ça ne veut pas dire qu'il faut m'écouter. Peut-être que d'autres personnes pensent le contraire de ce que j'ai raconté. Voilà voilà, j'espère que je n'ai pas (trop) dit de bêtises, que mon ressenti était intéressant … Merci pour les tutos, je continue très vite avec grand plaisir :)

Après avoir lu rapidement le début de ce chapitre, je t'accorde que ce n'est pas très explicite. Je pense qu'une clarification au niveau des opérateurs de manipulation de bits est souhaitable, au moins en mettant les tables. :)

Je ne sais pas si le tuto a pour but de présenter la notation binaire ou c'est considéré comme quelque chose d'acquis au moment de lire le cours ?

J'ai repris très récemment votre cours sur le langage C. J'ai commencé la partie 3 et j'ai pris quelques notes, qui reflètent mon avis de débutant, néanmoins je pense que ça peut-être intéressant.

Gam

Mais des avis de débutants nous intéressent fortement. ;)

https://zestedesavoir.com/contenus/beta/833/le-langage-c-partie-3/notions-avancees/la-representation-des-types/

Au début de page dans le tableau, il est noté 32 au lieu de 42.

Gam

Merci, c'est corrigé. :)
Maintenant, cela ne sera visible que lors de la publication.

Tableau en début de page placé sous le sous-titre "Les opérateurs « et », « ou inclusif » et « ou exclusif »" Je trouve que le tableau n'est pas clair, ou pas complet j'sais pas trop … je ne le saisis pas bien. Les opérateurs ne sont pas bien présentés je trouve. J'aurais personnellement préféré […]

Gam

Cela te paraît-il plus clair comme ceci ?

Les opérateurs « et », « ou inclusif » et « ou exclusif »

Chacun de ces trois opérateurs attend deux opérandes entiers et produit un nouvel entier en appliquant une table de vérité à chaque paire de bits formée à partir des bits des deux nombres de départs. Plus précisémment :

  • L'opérateur « et » (&) donnera 1 si les deux bits de la paire sont à 1 ;
  • L'opérateur « ou inclusif » (|) donnera 1 si au moins un des deux bits de la paire est à 1 ;
  • L'opérateur « ou exclusif » (^) donnera 1 si un seul des deux bits de la paire est à 1.

Ceci est résumé dans le tableau ci-dessous, donnant le résultat des trois opérateurs pour chaque paires de bits possibles.

Bit 1 Bit 2 Opérateur « et » Opérateur « ou inclusif » Opérateur « ou exclusif »
0 0 0 0 0
1 0 0 1 1
0 1 0 1 1
1 1 1 1 0

Premier code présenté : Je trouve les exemples compliqués (ils ne m'ont pas aidé à comprendre, je les ai compris après avoir appris sur d'autres sites).

1
2
/* 0111 1111 & 0011 1100 == 0011 1100 */
    printf("%2X\n", a & b);

Donc "a & b" c'est égal à b … je lisais ça comme ça, je ne comprenais rien. Il faut en fait comparer les chiffres 2 par 2, pour en ressortir un nouveau chiffre (on compare deux bits qui ont la même "position", donc le même poids si j'ai bien compris), ben ça je ne l'avais pas du tout compris.

Gam

Mmm… Le choix de 0x7F comme valeur n'était peut-être pas pertinent effectivement puisqu'il produit des valeurs particulières. Par ailleurs, deux variables suffisent probablement pour un premier exemple. Cela te semble-t-il mieux comme cela ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>


int main(void)
{
    int a = 0x63; /* 0x63 == 99 == 0110 0011 */
    int b = 0x2A; /* 0x2A == 42 == 0010 1010 */

    /* 0110 0011 & 0010 1010 == 0010 0010 == 0x22 == 34 */
    printf("%2X\n", a & b);
    /* 0110 0011 | 0010 1010 == 0110 1011 == 0x6B == 107 */
    printf("%2X\n", a | b);
    /* 0110 0011 ^ 0010 1010 == 0100 1001 == 0x49 == 73 */
    printf("%2X\n", a ^ b);
    return 0;
}

Voilà voilà, j'espère que je n'ai pas (trop) dit de bêtises, que mon ressenti était intéressant…

Gam

Du tout, un grand merci pour ton retour. :)

Je ne sais pas si le tuto a pour but de présenter la notation binaire ou c'est considéré comme quelque chose d'acquis au moment de lire le cours ?

Pouet_forever

Elle est présentée au chapitre précédent. ;)

+0 -0

Pour la partie sur les pointeurs de fonctions, ça peut être une bonne idée de faire implémenter une structure de listes avec des fonctions membres. Avec quelques fonctions simples (par exemple append, insert, pop, pop_index, get, empty, etc.). Ça donnerait alors un code d’exemple du genre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct List *list = newList();
list->append(list, 10);
list->append(list, 5);
list->get(1); => 5
list->append(18);
list->insert(2, 8);
list->pop() /* => 18 */
list->pop() /* => 8 */
list->empty();
freeList(list); /* Ou list->destroy(list) ? */ 
+0 -0

Pour la partie sur les pointeurs de fonctions, ça peut être une bonne idée de faire implémenter une structure de listes avec des fonctions membres.

Karnaj

Mmm… Personnellement, je n’aime pas trop cette pratique ou, plus précisémment, je ne l’apprécie pas quand elle est employée sans autre but que de pouvoir utiliser une syntaxe de la forme pointeur->fonction(). À mon sens, les pointeurs de fonction sont particulièrement utiles comme champ d’une structure dans le cas où il permettent une généralisation du code. La SDL2 en emploi par exemple lors de la création du renderer afin de stocker l’adresse des fonctions de dessins qui seront, suivant le cas, des fonctions reposant sur OpenGL, Direct3D, etc.

+0 -0
Ce sujet est verrouillé.