搜索
首页后端开发C++C 中的不相交联合

Disjoint Unions in C

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

data Tree = Leaf Int | Inner Tree Tree

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

首先要认识到的是,不相交的并集包括:

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

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

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

  1. “类型标签”,通常是整数,指示所表示的变体。
  2. 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。感觉有点浪费。

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

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

以上是C 中的不相交联合的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
C#和C:探索不同的范例C#和C:探索不同的范例May 08, 2025 am 12:06 AM

C#和C 的主要区别在于内存管理、多态性实现和性能优化。1)C#使用垃圾回收器自动管理内存,C 则需要手动管理。2)C#通过接口和虚方法实现多态性,C 使用虚函数和纯虚函数。3)C#的性能优化依赖于结构体和并行编程,C 则通过内联函数和多线程实现。

C XML解析:技术和最佳实践C XML解析:技术和最佳实践May 07, 2025 am 12:06 AM

C 中解析XML数据可以使用DOM和SAX方法。1)DOM解析将XML加载到内存,适合小文件,但可能占用大量内存。2)SAX解析基于事件驱动,适用于大文件,但无法随机访问。选择合适的方法并优化代码可提高效率。

c在特定领域:探索其据点c在特定领域:探索其据点May 06, 2025 am 12:08 AM

C 在游戏开发、嵌入式系统、金融交易和科学计算等领域中的应用广泛,原因在于其高性能和灵活性。1)在游戏开发中,C 用于高效图形渲染和实时计算。2)嵌入式系统中,C 的内存管理和硬件控制能力使其成为首选。3)金融交易领域,C 的高性能满足实时计算需求。4)科学计算中,C 的高效算法实现和数据处理能力得到充分体现。

揭穿神话:C真的是一种死语吗?揭穿神话:C真的是一种死语吗?May 05, 2025 am 12:11 AM

C 没有死,反而在许多关键领域蓬勃发展:1)游戏开发,2)系统编程,3)高性能计算,4)浏览器和网络应用,C 依然是主流选择,展现了其强大的生命力和应用场景。

C#vs. C:编程语言的比较分析C#vs. C:编程语言的比较分析May 04, 2025 am 12:03 AM

C#和C 的主要区别在于语法、内存管理和性能:1)C#语法现代,支持lambda和LINQ,C 保留C特性并支持模板。2)C#自动内存管理,C 需要手动管理。3)C 性能优于C#,但C#性能也在优化中。

用C构建XML应用程序:实例用C构建XML应用程序:实例May 03, 2025 am 12:16 AM

在C 中处理XML数据可以使用TinyXML、Pugixml或libxml2库。1)解析XML文件:使用DOM或SAX方法,DOM适合小文件,SAX适合大文件。2)生成XML文件:将数据结构转换为XML格式并写入文件。通过这些步骤,可以有效地管理和操作XML数据。

C中的XML:处理复杂的数据结构C中的XML:处理复杂的数据结构May 02, 2025 am 12:04 AM

在C 中处理XML数据结构可以使用TinyXML或pugixml库。1)使用pugixml库解析和生成XML文件。2)处理复杂的嵌套XML元素,如书籍信息。3)优化XML处理代码,建议使用高效库和流式解析。通过这些步骤,可以高效处理XML数据。

C和性能:它仍然主导C和性能:它仍然主导May 01, 2025 am 12:14 AM

C 在性能优化方面仍然占据主导地位,因为其低级内存管理和高效执行能力使其在游戏开发、金融交易系统和嵌入式系统中不可或缺。具体表现为:1)在游戏开发中,C 的低级内存管理和高效执行能力使得它成为游戏引擎开发的首选语言;2)在金融交易系统中,C 的性能优势确保了极低的延迟和高吞吐量;3)在嵌入式系统中,C 的低级内存管理和高效执行能力使得它在资源有限的环境中非常受欢迎。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。