Heim > Backend-Entwicklung > C++ > Disjunkte Gewerkschaften in C

Disjunkte Gewerkschaften in C

WBOY
Freigeben: 2024-09-10 10:44:15
Original
564 Leute haben es durchsucht

Disjoint Unions in C

Es ist nicht sofort klar, wie dieser Haskell-Typ in C: ausgedrückt werden soll

data Tree = Leaf Int | Inner Tree Tree
Nach dem Login kopieren

Im Gegensatz zu Sprachen wie Haskell und Rust fehlt in C die integrierte Unterstützung für
Disjunkte Gewerkschaften. Es liefert jedoch alle Zutaten, die wir für die Darstellung benötigen, wenn wir bereit sind, etwas mehr zu tippen.

Das erste, was man erkennen muss, ist, dass eine disjunkte Vereinigung aus Folgendem besteht:

  • Eine Reihe verschiedener Varianten
  • Jedem davon sind einige Daten zugeordnet.

In unserem Binärbaum-Beispiel haben wir zwei Varianten: „Blatt“ und „inner“. Die Blattvariante speichert eine einzelne Ganzzahl (ihre Daten) und die innere Variante speichert zwei Bäume (die ihre linken und rechten Kinder darstellen).

Wir können ein solches Tier in C darstellen, indem wir eine Struktur mit zwei Feldern verwenden:

  1. Ein „Typ-Tag“, normalerweise eine Ganzzahl, die angibt, welche Variante dargestellt wird.
  2. Ein Datenfeld, in dem die mit der Variante verknüpften Daten gespeichert werden.

Es ist praktisch, die verschiedenen Variantentyp-Tags mithilfe einer Aufzählung zu definieren:

enum tree_type {
        TREE_LEAF,
        TREE_INNER,
};
Nach dem Login kopieren

Was ist mit der Speicherung der Daten? Dies ist die Art von Problem, zu deren Lösung Gewerkschaften existieren.

Gewerkschaften

Eine Union ist nur ein Teil des Speichers, der eine Reihe unterschiedlicher Datentypen speichern kann. Hier ist zum Beispiel eine Union, die entweder einen 32-Bit-Int oder ein Array mit 5 Zeichen speichern kann.

union int_or_chars {
        int num;
        char letters[5];
};
Nach dem Login kopieren

Eine Variable vom Typ Union int_or_chars kann zu einem bestimmten Zeitpunkt entweder ein int oder ein Array mit 5 Zeichen enthalten (nur nicht beide gleichzeitig):

union int_or_chars quux;

// We can store an int:
quux.num = 42;
printf("quux.num = %d\n", quux.num);
// => quux.num = 42

// Or 5 chars:
quux.letters[0] = 'a';
quux.letters[1] = 'b';
quux.letters[2] = 'c';
quux.letters[3] = 'd';
quux.letters[4] = 0;
printf("quux.letters = %s\n", quux.letters);
// => quux.letters = abcd

// But not both. The memory is "shared", so the chars saved above are
// now being interpreted as an int:
printf("quux.num = %x\n", quux.num);
// quux.num = 64636261

return 0;
Nach dem Login kopieren

Eine Union wie Union int_or_chars verfügt über einen Speicherblock, der groß genug ist, um das größte seiner Mitglieder aufzunehmen. Hier ist ein Schema, das zeigt, wie das funktioniert:

+ ---- + ---- + ---- + ---- + ---- +
| byte |      |      |      |      |
+ ---- + ---- + ---- + ---- + ---- +
|<--   int uses these 4  -->|
|<--  array of chars uses all 5 -->|
Nach dem Login kopieren

Das erklärt, warum das Drucken von quux.num zu „Müll“ führte, nachdem wir ein Array von Zeichen in quux gespeichert hatten: Es war kein Müll, sondern die Zeichenfolge „abcd“, die als Ganzzahl interpretiert wurde. (Auf meinem Computer wird quux.num in Hex als 64636261 gedruckt. Das Zeichen „a“ hat einen ASCII-Wert von 0x61, „b“ hat einen Wert von 0x62, „c“ ist 0x63 und „d“ ist 0x64. Das Die Reihenfolge ist umgekehrt, da mein Prozessor Little-Endian ist.)

Als letzte Anmerkung zu Gewerkschaften: Sie könnten von der von sizeof gemeldeten Größe überrascht sein:

printf("%ld\n", sizeof(union int_or_chars));
// => 8
Nach dem Login kopieren

Auf meinem Rechner hat die Typunion int_or_chars eine Größe von 8 Bytes, nicht 5, wie wir vielleicht erwartet hätten. Aufgrund der Ausrichtungsanforderungen, die die Architektur meines Prozessors vorgibt, wurde etwas Polsterung hinzugefügt.

Zurück zu Binärbäumen

Wir sind jetzt bereit, mit der Übersetzung des binären Baumtyps von Haskell nach C fortzufahren. Wir haben bereits eine Aufzählung definiert, um den Typ der Variante darzustellen. Jetzt brauchen wir eine Union, um ihre Daten zu speichern:

union tree_data {
        int leaf;
        struct inner_data inner;
};
Nach dem Login kopieren
Nach dem Login kopieren

wobei struct inner_data eine Struktur ist, die die linken und rechten Kinder einer „inneren“ Variante enthält:

struct inner_data {
        struct tree *left;
        struct tree *right;
};
Nach dem Login kopieren

Beachten Sie, dass die „innere“ Variante Zeiger auf ihre linken und rechten untergeordneten Elemente beibehält. Die Indirektion ist notwendig, da der Strukturbaum sonst keine feste Größe hätte.

Sobald diese Teile vorhanden sind, können wir unseren Baumtyp definieren:

enum tree_type {
        TREE_LEAF,
        TREE_INNER,
};

struct tree;
struct inner_data {
        struct tree *left;
        struct tree *right;
};

union tree_data {
        int leaf;
        struct inner_data inner;
};

// A representation of a binary tree.
struct tree {
        enum tree_type type;
        union tree_data data;
};
Nach dem Login kopieren

Mit Bäumen spielen

Lassen Sie uns einige Funktionen schreiben, um Bäume zu erstellen:

// Construct a leaf node.
struct tree *leaf(int value) {
        struct tree *t = malloc(sizeof(*t));
        t->type = TREE_LEAF;
        t->data.leaf = value;
        return t;
}

// Construct an inner node.
struct tree *inner(struct tree *left, struct tree *right) {
        struct tree *t = malloc(sizeof(*t));
        t->type = TREE_INNER;
        t->data.inner.left = left;
        t->data.inner.right = right;
        return t;
}
Nach dem Login kopieren

und drucken Sie sie aus:

void print_tree(struct tree *t) {
        switch (t->type) {
        case TREE_LEAF:
                printf("%d", t->data.leaf);
                return;
        case TREE_INNER:
                printf("(");
                print_tree(t->data.inner.left);
                printf(" ");
                print_tree(t->data.inner.right);
                printf(")");
                return;
        }
}
Nach dem Login kopieren

Dadurch können wir den Haskell-Ausdruck übersetzen:

Inner (Inner (Leaf 1) (Leaf 2)) (Leaf 3)
Nach dem Login kopieren

in C als:

inner(inner(leaf(1), leaf(2)), leaf(3));
Nach dem Login kopieren

Zum Beispiel:

struct tree *t = inner(inner(leaf(1), leaf(2)), leaf(3));
print_tree(t);
// => ((1 2) 3)
Nach dem Login kopieren

Als etwas interessanteres Beispiel übersetzen wir diese Tiefensuchfunktion:

-- Check if a value is in a tree.
search :: Int -> Tree -> Bool
search v (Leaf w) = v == w
search v (Inner l r) = search v l || search v r
Nach dem Login kopieren

Verwendung unseres Baumtyps:

// Check if a value is in a tree.
int search(int value, struct tree *t) {
        switch (t->type) {
        case TREE_LEAF:
                return t->data.leaf == value;
        case TREE_INNER:
                return (
                        search(value, t->data.inner.left) ||
                        search(value, t->data.inner.right)
                );
        }
}
Nach dem Login kopieren

Es ist sicherlich ausführlicher, aber der Übersetzungsprozess ist unkompliziert (so weit, dass ein Compiler vermutlich so etwas für uns erledigen könnte ...).

Kompromisse

Wir schließen mit einem kleinen Exkurs über die Kompromisse, die eine alternative Darstellung mit sich bringt. Angenommen, anstelle von:

union tree_data {
        int leaf;
        struct inner_data inner;
};
Nach dem Login kopieren
Nach dem Login kopieren

wir haben verwendet:

union tree_data {
        int leaf;
        struct inner_data *inner;
        //                ^ The difference.
};
Nach dem Login kopieren

Im ersten Fall enthält die Union eine Struktur inner_data, während sie im zweiten Fall einen Zeiger auf diese Struktur speichert. Dadurch ist die erste Union mit 16 Bytes etwas größer, im Vergleich zu 8 Bytes bei der Zeigerversion auf meinem Rechner. Bedauerlicherweise sind nicht nur innere Knoten betroffen: Blattknoten verwenden dieselbe 16-Byte-Vereinigung, speichern aber nur einen einzelnen (4-Byte-)Int. Das fühlt sich etwas verschwenderisch an.

Das ist jedoch nicht die ganze Geschichte. Wir zahlen für die zusätzliche Indirektion jedes Mal, wenn wir auf die linken und rechten Kinder eines inneren Knotens zugreifen: Lesevorgänge sind nicht unbedingt billig, insbesondere wenn der Speicher, auf den verwiesen wird, nicht zwischengespeichert ist.

Ich vermute, dass der hier vorgestellte Hauptansatz in den meisten Fällen ein besserer Ausgangspunkt ist und dass der Versuch, ein paar Bytes einzusparen (weiß, was zusätzliche Lesevorgänge verursacht), sich einfach nicht lohnt, bis es soweit ist.

Das obige ist der detaillierte Inhalt vonDisjunkte Gewerkschaften in C. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage