C ist eine einfache Sprache, fast ein portabler Assembler, daher befinden sich die Datenstrukturen und Sprachkonstrukte in der Nähe des Metalls (Datenstrukturen verursachen keine versteckten Kosten - mit Ausnahme der durch Hardware und ABI auferlegten Einschränkungen hinsichtlich Auffüllen, Ausrichtung und Größe ). C hat also in der Tat keine dynamische Typisierung von Haus aus. Wenn Sie es jedoch benötigen, können Sie eine Konvention festlegen, dass alle Ihre Werte Aggregate sind, beginnend mit bestimmten Typinformationen (z. B. einigen ...). Verwendung -s und (für arrayartige Dinge) flexible Anordnungs - Element in sich auch die Größe des Arrays enthält.enum
union
struct
(Wenn Sie in C programmieren, liegt es in Ihrer Verantwortung, nützliche Konventionen zu definieren, zu dokumentieren und zu befolgen - insbesondere Vor- und Nachbedingungen und Invarianten. Auch die dynamische Speicherzuweisung von C erfordert explizite Konventionen darüber, wer free
eine gehäufte malloc
Speicherzone verwenden soll.)
Also, Werte zu repräsentieren , die boxed ganze Zahlen sind, oder Strings, oder irgendeine Art von Schema -ähnlichen Symbol oder Vektoren von Werten, werden Sie das Konzept eine verwenden getaggten Vereinigung (als Vereinigung von Zeigern implementiert) -Immer vom Typ Art Start -, z.B:
enum value_kind_en {V_NONE, V_INT, V_STRING, V_SYMBOL, V_VECTOR};
union value_en { // this union takes a word in memory
const void* vptr; // generic pointer, e.g. to free it
enum value_kind_en* vkind; // the value of *vkind decides which member to use
struct intvalue_st* vint;
struct strvalue_st* vstr;
struct symbvalue_st* vsymb;
struct vectvalue_st* vvect;
};
typedef union value_en value_t;
#define NULL_VALUE ((value_t){NULL})
struct intvalue_st {
enum value_kind_en kind; // always V_INT for intvalue_st
int num;
};
struct strvalue_st {
enum value_kind_en kind; // always V_STRING for strvalue_st
const char*str;
};
struct symbvalue_st {
enum value_kind_en kind; // V_SYMBOL
struct strvalue_st* symbname;
value_t symbvalue;
};
struct vectvalue_st {
enum value_kind_en kind; // V_VECTOR;
unsigned veclength;
value_t veccomp[]; // flexible array of veclength components.
};
Um den dynamischen Typ eines Wertes zu erhalten
enum value_kind_en value_type(value_t v) {
if (v.vptr != NULL) return *(v.vkind);
else return V_NONE;
}
Hier ist eine "dynamische Umwandlung" in Vektoren:
struct vectvalue_st* dyncast_vector (value_t v) {
if (value_type(v) == V_VECTOR) return v->vvect;
else return NULL;
}
und ein "sicherer Accessor" innerhalb von Vektoren:
value_t vector_nth(value_t v, unsigned rk) {
struct vectvalue_st* vecp = dyncast_vector(v);
if (vecp && rk < vecp->veclength) return vecp->veccomp[rk];
else return NULL_VALUE;
}
Normalerweise definieren Sie die meisten der oben genannten static inline
Kurzfunktionen wie in einer Header-Datei.
Übrigens, wenn Sie den Garbage Collector von Boehm verwenden können, können Sie ganz einfach in einem übergeordneten (aber unsicheren) Stil codieren, und mehrere Scheme-Interpreter werden auf diese Weise ausgeführt. Ein variadischer Vektorkonstruktor könnte sein
value_t make_vector(unsigned size, ... /*value_t arguments*/) {
struct vectvalue_st* vec = GC_MALLOC(sizeof(*vec)+size*sizeof(value));
vec->kind = V_VECTOR;
va_args args;
va_start (args, size);
for (unsigned ix=0; ix<size; ix++)
vec->veccomp[ix] = va_arg(args,value_t);
va_end (args);
return (value_t){vec};
}
und wenn Sie drei Variablen haben
value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;
Sie können mit ihnen einen Vektor erstellen make_vector(3,v1,v2,v3)
Wenn Sie Böhms Garbage Collector nicht verwenden (oder Ihren eigenen entwerfen) möchten, sollten Sie sehr vorsichtig sein, um Destruktoren zu definieren und zu dokumentieren, wer, wie und wann Speicher free
-d sein sollte. siehe dieses Beispiel. Sie könnten also malloc
anstelle von GC_MALLOC
oben verwenden (aber dann auf seinen Fehler testen), aber Sie müssen einige Destruktorfunktionen sorgfältig definieren und verwendenvoid destroy_value(value_t)
Die Stärke von C besteht darin, niedrig genug zu sein, um Code wie oben zu ermöglichen und Ihre eigenen Konventionen (insbesondere für Ihre Software) zu definieren.