2
votes

renvoyer void * pour un retour de fonction / méthode

Est-il possible qu'une fonction en C renvoie un type de retour 'dynamique'

exemple

printResult (NumOrChar ());

void* NumOrChar(void) {
   // return int  or char 
}

void printResult (void* input) {
  if (isdigit(input)) {
     printf("It's a number");
}
  else {
     printf("It's not a number");
}

c

5 commentaires

Vous pouvez renvoyer un void * mais vous devez toujours savoir dans quel type le caster par la suite


cout << "foo"; vous ne pouvez pas changer de bits par une chaîne littérale en C


Les pointeurs ne sont pas des entiers. return 7 et input> 5 et isdigit (input) sont toutes des erreurs de type.


@UnholySheep pourriez-vous me donner un exemple? J'ai apporté des modifications à la question pour plus de clarté.


en C, les chaînes consistent en une série de caractères pouvant contenir des chiffres ou des caractères. isdigit vérifie un caractère individuel pour voir si est 0..9, sinon, il renvoie 0. vous ne pouvez pas utiliser isdigit sur un void *


4 Réponses :


2
votes

Vous pouvez utiliser _Generic dans certaines circonstances

int func_int(int *p)
{
    printf("%s\n", __FUNCTION__);
    return 5;  /* does not make too much sense */
}

float func_float(float *p)
{
    printf("%s\n", __FUNCTION__);
    return 5.0f;  /* does not make too much sense */
}

double func_double(double *p)
{
    printf("%s\n", __FUNCTION__);
    return 5.0;  /* does not make too much sense */
}


#define func(p) _Generic((p), \
              int *: func_int, \
              float *: func_float, \
              double *: func_double)(p) \


12 commentaires

Est-ce C pour de vrai?


@ machine_1 que pensez-vous que c'est.


Eh bien, je n'ai jamais vu cette notation auparavant. int *: func_int


C11 Standard - 6.5.1.1 Sélection générique


il est temps d'apprendre la norme de presque 10 ans. Beaucoup de choses ont changé depuis K&R :)


Bien qu'il fasse partie de la norme C11, les compilateurs ne l'ont pas mis en œuvre pendant plusieurs années. Il n'était pas pris en charge dans GCC jusqu'à la version 4.9 en 2014 (de nombreuses distributions Linux ne sont passées à 4.9 qu'un an ou deux plus tard)


@ DavidC.Rankin c'était une blague amicale avec le sourire, pas éducative :)


Oh oui, je l'ai compris, j'ai été vraiment surpris du temps qu'il a fallu à certaines distributions pour passer à gcc 4.9. Ou bien sûr, les distributions comme Arch étaient immédiates, mais les distributions comme SuSE ont pris jusqu'à Leap. Lord sait ce que les anciennes distributions comme Stack ou RedHat ont. Je sais que Debian Jessie est arrivée à 4.9, mais c'est là. Donc, si @ machine_1 a une ancienne boîte, je voulais m'assurer qu'il essayait et que cela ne fonctionnait pas - il avait un peu d'informations sur lesquelles continuer.


@ DavidC.Rankin Je suis un programmeur uC. Et je ne me souviens même pas du 4.9 :) 6.x me semble préhistorique. En fait, souvent je n'installe jamais des versions que les versions disro "standard", même si je ne mets pas à jour les bibliothèques.


Pour ce que ça vaut, RHEL 7 utilise GCC 4.8, l'a toujours fait et le fera toujours. Je suis sûr que RHEL 8 utilise quelque chose de plus récent, mais je ne vais pas y prêter beaucoup d'attention avant de sortir de la version bêta.


@JohnBollinger vous n'êtes pas obligé d'utiliser le "système"


Non, bien sûr que non, mais David parlait du moment où diverses distributions sont passées à GCC 4.9 ou version ultérieure, il est donc intéressant d'observer que RHEL ne le fait que pour le moment .



2
votes

Les fonctions peuvent certainement renvoyer void * . Mais c'est un type de pointeur spécifique avec des propriétés qui le rendent approprié pour acheminer des pointeurs vers des objets de tout type. Ce n'est pas un type générique à usage général. De plus, il ne contient aucun type d'informations sur le type réel, le cas échéant, de l'objet vers lequel il pointe, il n'y a donc aucun moyen de déterminer ce type de manière dynamique. Un programmeur C ++ pourrait décrire cette situation comme C ne fournissant aucun RTTI.

Au lieu de cela, vous pouvez renvoyer un type qui peut véhiculer des objets de divers types, connus à l'avance, avec un mécanisme pour les distinguer. Par exemple,

union num_or_string {
    struct { _Bool is_num; };
    struct { _Bool _x1; int num; };
    struct { _Bool _x2; char *string; };
};

union num_or_string NumOrChar(void) {
    // return a union num_or_string containing an int or a char *
}

void printResult (union num_or_string) {
    if (num_or_string.is_num) {
        printf("It's a number: %d\n", num_or_string.num);
    } else {
        printf("It's a string: %s\n", num_or_string.string);
    }
}


1 commentaires

Idiomatiquement connu sous le nom de " tagged union "



-2
votes

Je suppose que vous parlez de la fonctionnalité C # (selon mon Recherche Google ).

En C, ce n'est possible que si vous le faites vous-même (d'autres réponses montrent des exemples). Cela peut être facile ou difficile selon vos besoins. Vous devriez penser à passer à une autre langue si vous le souhaitez vraiment (on les appelle parfois variantes ).


0 commentaires

0
votes

Au lieu d'avoir un bloc de ifs, on peut utiliser une structure de pointeurs de fonction comme une table virtuelle, y compris to_string . Ce qui suit crée dynamiquement le Type , qui peut être Num ou Letter.

$ bin/numorchar 
524645 3456542563456
"524645" is of type num.
$ bin/numorchar 
6245635724564357652654245634576
Failure: Result too large
$ bin/numorchar 
ata gfddsgsdg
"a" is of type char.
$ bin/numorchar 

"
" is of type char.

Probablement exagéré pour votre fonction, mais comme on a plus de types, cela devient de plus en plus attrayant. On peut envisager une union de types espacés similaires afin qu'elle puisse être allouée entièrement sur la pile.

#include <stddef.h> /* offsetof */
#include <stdio.h>  /* [|s|sn]printf, fgets, stdin */
#include <stdlib.h> /* malloc, free, strtol */
#include <ctype.h>  /* isdigit */
#include <errno.h>
#include <assert.h>

struct Type;

typedef void (*TypeToString)(const struct Type *const, char (*const)[32]);
typedef void (*TypeAction)(struct Type *const);

struct Type {
    const struct TypeVt *vt;
};



/* Num extends Type. */

struct Num {
    struct Type base;
    int value;
};

static struct Num *num_upcast(struct Type *const type) {
    return (struct Num *)(void *)((char *)type - offsetof(struct Num, base));
}

static const struct Num *const_num_upcast(const struct Type *const type) {
    return (const struct Num *)(const void *)((const char *)type
        - offsetof(struct Num, base));
}

static void num_to_string(const struct Type *const type, char (*const a)[32]) {
    const struct Num *const num = const_num_upcast(type);
    snprintf(*a, sizeof *a, "%d", num->value); /* C99. */
}

static void num_delete(struct Type *const type) {
    struct Num *const num = num_upcast(type);
    free(num);
}



/* Letter extends Type. */

struct Letter {
    struct Type base;
    char letter;
};

static struct Letter *letter_upcast(struct Type *const type) {
    return (struct Letter *)(void *)((char *)type
        - offsetof(struct Letter, base));
}

static const struct Letter *const_letter_upcast(const struct Type *const type) {
    return (const struct Letter *)(const void *)((const char *)type
        - offsetof(struct Letter, base));
}

static void letter_to_string(const struct Type *const t, char (*const a)[32]) {
    const struct Letter *const letter = const_letter_upcast(t);
    sprintf(*a, "%c", letter->letter);
}

static void letter_delete(struct Type *const type) {
    struct Letter *const letter = letter_upcast(type);
    free(letter);
}



static const struct TypeVt {
    const char *name;
    const TypeToString to_string;
    const TypeAction delete;
} num_vt = { "num", &num_to_string, &num_delete },
    letter_vt = { "char", &letter_to_string, &letter_delete };

static void type_to_string(const struct Type *const t, char (*const a)[32]) {
    assert(t);
    t->vt->to_string(t, a);
}

static void type_delete(struct Type *const t) {
    assert(t);
    t->vt->delete(t);
}

static struct Type *num(const int value) {
    struct Num *num = malloc(sizeof *num);
    if(!num) return 0;
    num->base.vt = &num_vt;
    num->value = value;
    return &num->base;
}

static struct Type *letter(const char letter) {
    struct Letter *l = malloc(sizeof *l);
    if(!l) return 0;
    l->base.vt = &letter_vt;
    l->letter = letter;
    return &l->base;
}



static struct Type *read_type(void) {
    struct Type *type;
    char buffer[64];
    if(!fgets(buffer, sizeof buffer, stdin)) return 0;
    if(isdigit(buffer[0])) {
        long n;
        errno = 0;
        n = strtol(buffer, 0, 0);
        if(errno) return 0;
        type = num(n);
    } else {
        type = letter(buffer[0]);
    }
    return type;
}

int main(void) {
    char a[32];
    struct Type *type = 0;
    int is_success = 0;
    do {
        if(!(type = read_type())) break;
        type_to_string(type, &a);
        printf("\"%s\" is of type %s.\n", a, type->vt->name);
        is_success = 1;
    } while(0); {
        if(type) type_delete(type);
    }
    if(!is_success) return perror("Failure"), EXIT_FAILURE;
    return EXIT_SUCCESS;
}


0 commentaires