Вместо блока if можно использовать структуру указателей функций в качестве виртуальной таблицы, включая to_string
.Следующее динамически создает Type
, который может быть Num
или Letter
.
#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;
}
Вероятно, излишним для вашей функции, но если у вас есть больше типов, это становится все более привлекательным.Можно рассмотреть union
схожих разнесенных типов, чтобы он мог быть полностью размещен в стеке.
$ 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.