」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > C 中的不相交聯合

C 中的不相交聯合

發佈於2024-11-08
瀏覽:664

Disjoint Unions in C

目前还不清楚如何在 C:
中表达此 Haskell 类型

data Tree = Leaf Int | Inner Tree Tree

与 Haskell 和 Rust 等语言不同,C 缺乏对
的内置支持 不相交联合。然而,如果我们愿意做一些额外的输入,它确实提供了代表它们所需的所有成分。

首先要认识到的是,不相交的并集由以下部分组成:

  • 许多不同的变体
  • 每个都有一些与之关联的数据

在我们的二叉树示例中,我们有两个变体:“叶子”和“内部”。叶变量存储单个整数(其数据),内部变量存储两棵树(代表其左子树和右子树)。

我们可以使用具有两个字段的结构体在 C 中表示这样的动物:

  1. “类型标签”,通常是整数,指示所表示的变体。
  2. A data 字段,用于存储与变体相关的数据。

使用枚举定义不同的变体类型标签很方便:

enum tree_type {
        TREE_LEAF,
        TREE_INNER,
};

存储数据怎么样?这就是工会要解决的问题类型。

工会

联合只是一块能够存储多种不同类型数据的内存。例如,这是一个可以存储 32 位 int 或 5 个字符的数组的联合。

union int_or_chars {
        int num;
        char letters[5];
};

类型为 union int_or_chars 的变量可以在任何特定时间保存 int 或 5 个字符的数组(只是不能同时保存):

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;

像 union int_or_chars 这样的联合拥有足够大的内存块可供使用,可以容纳最大的成员。这是显示其工作原理的示意图:

  ----   ----   ----   ----   ----  
| byte |      |      |      |      |
  ----   ----   ----   ----   ----  
||
||

这有助于解释为什么在我们在 quux 中存储字符数组后打印 quux.num 会导致“垃圾”:它不是垃圾,而是字符串“abcd”被解释为整数。 (在我的机器上,quux.num 以十六进制打印为 64636261。字符“a”的 ASCII 值为 0x61,“b”的值为 0x62,“c”为 0x63,“d”为 0x64。由于我的处理器是小端字节序,所以顺序颠倒了。)

关于联合的最后一点,您可能会对 sizeof:
报告的大小感到惊讶

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

在我的机器上,类型 union int_or_chars 的大小为 8 个字节,而不是我们预期的 5 个字节。由于我的处理器架构规定的对齐要求,已添加一些填充。

返回二叉树

我们现在准备继续将二叉树类型从 Haskell 转换为 C。我们已经定义了一个枚举来表示变体的类型。现在我们需要一个联合来存储其数据:

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

其中 struct inner_data 是包含“内部”变体的左右子级的结构:

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

请注意,“内部”变体维护指向其左右子节点的指针。间接是必要的,因为否则结构树将没有固定的大小。

这些部分就位后,我们就可以定义我们的树类型了:

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;
};

和树玩耍

让我们编写一些函数来构造树:

// 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;
}

并打印它们:

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;
        }
}

这使我们能够翻译 Haskell 表达式:

Inner (Inner (Leaf 1) (Leaf 2)) (Leaf 3)

到 C 中为:

inner(inner(leaf(1), leaf(2)), leaf(3));

例如:

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

作为一个稍微有趣的例子,我们来翻译一下这个深度优先搜索函数:

-- 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

使用我们的树类型:

// 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)
                );
        }
}

这当然更冗长,但翻译过程很简单(编译器大概可以为我们做这种事情......)。

权衡

我们最后稍微离题了一些关于替代表示所涉及的权衡。具体来说,假设代替:

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

我们使用:

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

在第一种情况下,联合体包含一个结构体inner_data,而在第二种情况下,它存储一个指向该结构体的指针。因此,第一个联合体稍大一些,为 16 字节,而我的机器上的指针版本为 8 字节。不幸的是,受影响的不仅仅是内部节点:叶节点使用相同的 16 字节联合,但仅存储单个(4 字节)int。感觉有点浪费。

然而,这并不是故事的全部。每次访问内部节点的左子节点和右子节点时,我们都会为额外的间接寻址付出代价:读取不一定便宜,特别是如果指向的内存未缓存。

我怀疑这里提出的主要方法在大多数情况下是一个更好的起点,并且尝试削减一些字节(白色导致额外的读取)是不值得的,直到它是。

版本聲明 本文轉載於:https://dev.to/wjlewis/disjoint-unions-in-c-4i9i如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何在 Go 中解密 AES ECB 模式加密?
    如何在 Go 中解密 AES ECB 模式加密?
    Go 中的AES ECB 加密Go 中的AES ECB 加密package main import ( "crypto/aes" "fmt" ) func decryptAes128Ecb(data, key []byte) []byte { ...
    程式設計 發佈於2024-11-08
  • 在 GitHub-echo 中實現 TOML 配置支持
    在 GitHub-echo 中實現 TOML 配置支持
    介绍 最近,我有机会通过添加对 TOML 配置文件的支持来增强 github-echo 命令行工具。此功能允许用户在 .github-echo-config.toml 文件中设置持久默认选项,从而减少每次使用该工具时手动输入重复配置的需要。在这篇文章中,我将向您介绍我在该功能上的经...
    程式設計 發佈於2024-11-08
  • 如何使用 SimpleXML 和 DOMDocument 刪除 XPath 節點?
    如何使用 SimpleXML 和 DOMDocument 刪除 XPath 節點?
    SimpleXML:刪除XPath 節點在本文中,我們將探討如何使用以下方法有效地從XML 文件中刪除父節點: SimpleXML 和XPath。 了解 SimpleXML限制提供的程式碼嘗試使用 SimpleXML 在透過 XPath 找到父節點後刪除它。然而,SimpleXML 的 unset(...
    程式設計 發佈於2024-11-08
  • 建立一個 React Hook 以任意角度旋轉影像
    建立一個 React Hook 以任意角度旋轉影像
    在Web開發中,您可能需要旋轉影像,這在CSS中很容易做到。像這樣的簡單程式碼變換:rotate(90deg);。但是如果我們想用 JS 來做呢? TLDR 將圖像繪製到瀏覽器環境中的畫布上並旋轉它。但在此之前,我們需要做一些數學運算來保持原始影像的長寬比。 核 ...
    程式設計 發佈於2024-11-08
  • Lithe 中間件:它是如何運作的以及如何創建自己的中間件
    Lithe 中間件:它是如何運作的以及如何創建自己的中間件
    中间件提供了一种方便的机制来检查和过滤进入应用程序的 HTTP 请求。 例如,Lithe 包含检查用户是否经过身份验证的中间件。如果没有,中间件会将用户重定向到登录屏幕。如果用户通过身份验证,中间件将允许请求继续。 中间件如何在 Lithe 中工作 在 Lithe 中,中间件是能够访...
    程式設計 發佈於2024-11-08
  • 如何在 JavaScript 中建立具有重複元素的陣列?
    如何在 JavaScript 中建立具有重複元素的陣列?
    JavaScript 中重複元素的數組創建具有多次重複的相同元素的數組在各種編程場景中至關重要。在 Python 中,這可以透過列表乘法來實現,如 [2] * 5 所示。但是,此功能在 JavaScript 陣列中不能直接使用。 自訂函數方法為了滿足這種需求,一種方法是建立自訂函數,例如問題中提供的...
    程式設計 發佈於2024-11-08
  • ## MySQL 中的 LIKE 與 LOCATE:哪個運算子是效能之王?
    ## MySQL 中的 LIKE 與 LOCATE:哪個運算子是效能之王?
    MySQL LIKE 與LOCATE 效能比較在MySQL 中查找資料時,你可能會想知道LIKE 和LOCATE 哪個運算子效率更高?本文探討了這兩個運算子之間的效能差異。 在典型的使用場景中,LIKE 比 LOCATE 稍快。這主要是因為 LIKE 不像 LOCATE 那樣執行與 0 的額外比較。...
    程式設計 發佈於2024-11-08
  • 如何使用 PHP 更新多個 MySQL 行的表單資料?
    如何使用 PHP 更新多個 MySQL 行的表單資料?
    使用表單資料更新多個MySQL 行在Web 開發中,通常有一個表單,使用者可以在其中編輯資料庫中的記錄。常見的情況是使用修改後的資料更新同一個表中的多行。這可以使用 PHP 和 MySQL 來實作。 表單結構與資料擷取初始表單負責呈現要編輯的資料。在此範例中,表單從資料庫中擷取具有特定 GALLER...
    程式設計 發佈於2024-11-08
  • 為什麼我不能在 Go 中將 []byte 分配給字串?
    為什麼我不能在 Go 中將 []byte 分配給字串?
    了解位元組分配錯誤:無法將[]byte 指派給字串在嘗試讀取資料夾中的檔案時,遇到了錯誤嘗試讀取檔案內容時,「無法在多重賦值中將[]byte 指派給z(字串類型)」。讓我們深入研究這個錯誤背後的原因。 理解多重賦值當在一行中為多個變數賦值時,如程式碼所示:z, err := ioutil.ReadF...
    程式設計 發佈於2024-11-08
  • 如何使用 React 和 Typescript 建立自訂表格元件(第 2 部分)
    如何使用 React 和 Typescript 建立自訂表格元件(第 2 部分)
    介绍 耶! ?您已经完成了这个由两部分组成的系列的最后一部分!如果您还没有查看第 1 部分,请先停在此处并完成第 1 部分。别担心,我们会等你回来! ? 在第 1 部分中,我们构建了 CustomTable 组件。您可以在这里看到它的实际效果。 在第二部分中,我们将扩展该组件以添加...
    程式設計 發佈於2024-11-08
  • 使用 TypeScript 和 ioredis 在 Node.js 中建立高效能快取管理器
    使用 TypeScript 和 ioredis 在 Node.js 中建立高效能快取管理器
    使用基於 ioredis 建構的多功能、易於使用的快取管理器來提升 Node.js 應用程式的效能。簡化快取、優化效率並簡化操作。 我根據自己的需求開發了一個基於 ioredis 的類,重點關注易用性和性能。它包括 TypeScript 支持,旨在實現簡單使用和高效操作。它仍然可以進一步改進和優化...
    程式設計 發佈於2024-11-08
  • 超類別引用和子類別對象
    超類別引用和子類別對象
    Java 是一種強型別語言。 標準轉換和自動升級適用於原始型別。 嚴格執行型別相容性。 通常,一個類別的引用變數不能引用另一個類別的物件。 即使類別 X 和 Y 在結構上相同,也不可能將 X 的引用分配給 Y 的對象,因為類型不同。 一般來說,物件引用變數只能引用其類型的物件。 型別強...
    程式設計 發佈於2024-11-08
  • Flexbox 中的 flex-grow 和 width 有何不同?
    Flexbox 中的 flex-grow 和 width 有何不同?
    Flexbox中flex-grow和width的區別Flexbox提供了兩種在元素之間分配空間的主要方法:flex- grow和width。了解這些屬性之間的差異對於有效使用 Flexbox 至關重要。 Flex-grow 與 widthFlex-grow 是一個無量綱屬性,定義元素的大小擴展以填充...
    程式設計 發佈於2024-11-08
  • 如何將表單標籤和輸入水平對齊在同一行?
    如何將表單標籤和輸入水平對齊在同一行?
    實現表單標籤與輸入在同一行水平放置在網頁開發中,表單的美觀對於使用者體驗至關重要。將標籤和輸入欄位排列在同一行可以增強表單的可讀性和可用性。本文探討如何將輸入元素與其標籤無縫對齊,無論其長度為何。 初始嘗試在單一元素上對齊標籤和輸入的常見方法行涉及將輸入的寬度設為自動。然而,這通常會導致輸入寬度固定...
    程式設計 發佈於2024-11-08
  • 遞迴-1
    遞迴-1
    簡介1 函數呼叫自身的過程稱為遞歸, 對應的函數稱為遞歸函數. 由於電腦程式設計是數學的基本應用,因此讓 我們首先嘗試理解遞歸背後的數學推理。 一般來說,我們都知道函數的概念。簡而言之,函數是 提供輸入時產生輸出的數學方程式。例如: 假設函數 F(x) 是由下式定義的函數: F(...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3