如何在C语言中定义一个可以接受不同类型的变量?

3
我想在C语言中实现一个字典数据结构,希望它尽可能地通用。也就是说,它可以接受任何类型的值对。
如何初始化一个可接受任何类型的变量?
并且,如何将该类型转换回我想要的类型?(类型转换)
谢谢。

我记得它与void有关,但具体是什么我不太记得了 :( - nubela
我认为在C++中,空指针可以做到这一点,但是在严格的C语言中我从未以这种方式使用过它们,因此我不确定它们是否对此有所帮助。 - FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner:实际上,在严格的C语言中它们可以更顺畅地工作。在C++中,你不能将一个void指针转换为某个类型的指针(或者反过来)而不使用强制转换。在C中,强制转换是不必要的。 - David Thornley
虽然很难搜索到,但这个问题以前已经讨论过几次了...我会试着找出一两个链接。 - dmckee --- ex-moderator kitten
这里有一个讨论相同基本问题的链接:https://dev59.com/l3NA5IYBdhLWcg3wAIxP 和 http://stackoverflow.com/questions/1662229/c-all-type-parameter 也相关。然而,发帖人并没有受到任何不光彩的影响:那些都很难找到。 - dmckee --- ex-moderator kitten
10个回答

8

定义一个可以保存多种类型值的变量的方法是使用联合:

union uu {
  int i;
  float f;
  char c;
  char *s;
} x;

x.i是一个整数,x.s是一个字符指针等等。x的大小是成员类型中最大的。

不幸的是,记住变量的类型的唯一方法是将其存储在其他地方。常见的解决方案是使用这样的结构:

 struct ss {
   int type;
    union {
      int i;
      float f;
      char c;
      char *s;
    } val;
  } x;

然后进行如下操作:

 #define FLOAT 1
 #define INT   2
 .... 

 x.val.i = 12;
 x.type = INT;
 ....
 if (x.type = INT) printf("%d\n",x.val.i);
 ....

真的很丑陋。

还有其他可能性可以通过宏处理器来使其更加美观,但本质是你必须事先知道联合中存储的值类型并访问正确的字段。


你在我写一个不那么冗长的同样想法的时候发了帖子 :) - tloach
顺便说一下,我选择使用宏来隐藏丑陋的代码,如果你想看一下:http://code.google.com/p/c-libutl/source/browse/trunk/src/tbl.h - Remo.D
@tloach,我猜只有很少的选择 :) - Remo.D

7
这并不是一件简单的事,但以下是最简单的方法:
您需要知道所有支持的类型以及在插入它们时它们的类型。
首先,您将实际拥有一个类似于结构体的数据结构: struct { void * data; enum Type tag } 并定义一个enum Type { int, char*,... etc } void *是指向没有类型的数据的指针,因此您可以使用它来存储包含要存储的数据的内存块。
Type标签存储数据的内容,使得使用该数据结构的代码可以知道从数据结构返回的是什么。
如果您不需要存储类型,并且可以在从数据结构中取出时将其强制转换为正确的类型,则可以省略Type标签,只需存储void *即可。

4
在C语言中,没有简单的方法来实现这个。你可以使用void*来传递类型的指针,但是没有模板、泛型或变体的概念存在。
要使用void*,你需要将所有东西都视为指针,因此必须在堆上分配它。然后,在发送到此数据结构时,您需要将那些指针转换为void*,然后在另一侧进行转换。这可能很棘手,因为你必须记住最初的类型是什么。
如果你恰好在Windows上编程,你可以使用Variants来实现这一点,但是与此相关的开销比较大。

你如何在C语言中使用变量(Variants)? - Remo.D
1
+1 提到变体。这是微软试图解决这个问题的方式。并不优雅,但实际上没有优雅的解决方案。 - Randolpho
@Remo.D:变量是Win32构造,它是C API。所以正如Steve所提到的,你必须在/针对Windows上进行编程。 - Randolpho
嗯,我的老脑袋对于仅限于Win32的概念并不那么舒适 :) - Remo.D
@Randolpho:针对Windows编程 哈哈!我喜欢这个! - pmg
@Remo.D: 你可以从 ::VariantInit(&myVariant); 开始。 - Andomar

2

你有两个选择:

void* foo;

它可以指向任何类型的数据,或者:

union my_types {
   int* i;
   char* s;
   double* d;
   bool* b;
   void* other;
}

这个功能使您可以进行“自动”指针转换(您可以将类型为“my_types”的变量引用,就像它是上述任何一种类型一样)。有关union的更多信息,请参见此链接

以上两种选项都不是很好 - 考虑使用C ++来实现您想要做的事情。


当他试图将结构体存储在字典中时,这会导致接口不一致。 - Cory Petosky

2
您可能需要使用void *

1

你的解决方案将需要使用指向void的指针。 指向void的指针可以保存任何对象类型(不包括函数),并且可以在不丢失信息的情况下转换回来。

最好使用“父结构体”,以便您知道指针所指向的对象类型:

enum MyType { INTEGER, DOUBLE_STAR };
struct anytype {
    enum MyType mytype;
    size_t mysize;
    void *myaddress;
};

然后

struct anytype any_1, any_2;
int num_chars;
double array[100];

any_1.mytype = INTEGER;
any_1.myaddress = &num_chars;
any_1.mysize = sizeof num_chars;

any_2.mytype = DOUBLE_STAR;
any_2.myaddress = array;
any_2.size = sizeof array;

还有,要与之一起工作

foo(any_1);
foo(any_2);

其中foo被定义为

void foo(struct anytype thing) {
    if (thing.mytype == INTEGER) {
        int *x = thing.myaddress;
        printf("the integer is %d\n", *x);
    }
    if (thing.mytype == DOUBLE_STAR) {
        size_t k;
        double *x = thing.myaddress;
        double sum = 0;
        for (k = 0; k < thing.mysize; k++) {
            sum += thing.myaddress[k];
        }
        printf("sum of array: %f\n", sum);
    }
}

代码未经测试


1

使用 void * ?

你可以将其强制转换成任何你想要的类型,当然前提是你必须自己进行检查。


0

enum _myType { CHAR, INT, FLOAT, ... }myType;
struct MyNewType
{
   void * val;
   myType type;
}

然后,您可以将类型为MyNewType的元素传递给您的函数。检查类型并执行适当的转换。


0

您可以使用 void * 指针来接受几乎任何指针。关键在于将其强制转换回所需类型:您必须在某个地方存储有关指针的类型信息。

您可以使用 struct 来存储 void * 指针和类型信息,并将其添加到数据结构中。主要问题在于类型信息本身;C 不包含任何类型反射,因此您可能需要创建一组类型枚举或存储描述该类型的字符串。然后,您必须通过查询类型信息来强制 struct 的用户将 void * 转换回原始类型。

这并不是理想的情况。一个更好的解决方案可能是转移到 C++ 或甚至 C# 或 Java。


0

C语言提供了联合体,可能适合您的需求。在编译之前,您需要定义所有类型,但是同一变量可以指向联合体中包含的所有类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接