首先,让我们忽略所有类型定义;这是它们遮蔽多于照亮的罕见情况之一。
这是来自 C 语言标准(草案n1256)的void 数据类型的定义:
6.2.5 类型
...
19 void 类型包含一组空值;它是一个不完整的类型,无法完成。
IOW,void 是一种“非上述”数据类型;它没有关联的值。类型为 void 的函数没有返回值,并且不应使用 void 表达式的(不存在的)结果。
与任何其他不完整类型一样,您可以创建指向void 的指针。它们用作一种通用指针类型; void 指针可用于指向任何其他类型的对象:
6.3.2.3 指针
1 指向void 的指针可以转换为指向任何不完整或对象类型的指针或从指针转换。指向任何不完整或对象类型的指针可以转换为指向void 的指针并再次返回;结果应与原始指针比较。
因此,例如,我可以使用 void 指针指向 int、float 或 char 数组等:
int x = 1;
double y = 2.0;
char *z = "three";
void *p[3];
p[0] = &x;
printf("address of x is %p\n", p[0]);
p[1] = &y;
printf("address of y is %p\n", p[1]);
p[2] = &z;
printf("address of z is %p\n", p[2]);
这是一个假设的内存映射,显示了每个的值(假设整数和浮点数为 32 位,字符数据为 ASCII):
Item Address 0x00 0x01 0x02 0x03
---- ------- ---- ---- ---- ----
"three" 0x00001000 0x74 0x68 0x72 0x65 // string literals are stored "somewhere else"
0x00001004 0x65 0x00 xxxx xxxx // xxxx indicates any random value
...
x 0x10008000 0x00 0x00 0x00 0x01
y 0x10008004 0x40 0x00 0x00 0x00 // Binary representation of 2.0 on my system
z 0x10008008 0x00 0x00 0x10 0x00 // z contains the address of the first element of the string literal
p 0x1000800C 0x10 0x00 0x80 0x00
0x10008010 0x10 0x00 0x80 0x04
0x10008014 0x10 0x00 0x80 0x08
如您所见,存储在p[0](地址0x10008010)的值是x(0x10008000)的地址。同样,p[1] 存储y 的地址,p[3] 存储z 的地址。 void 指针的这个属性为我们提供了一种将类型信息与代码分开的方法,尽管不像 C++ 模板或 Java 泛型那样简单。因此,我们可以使用void 指针数组来构建一个通用容器,该容器可以容纳任何类型的对象(技术上是指向对象的指针)。
注意:在 1989 年标准被采用之前,您会使用 char * 作为“通用”指针类型;但是,您必须将指针值显式转换为目标类型,这与 void 指针无关(在 C 中;这对于 C++ 来说不是这样,它确实需要显式转换)。这是您仍然看到人们投射 malloc/calloc/realloc; 的结果的部分原因。从前,你不得不。
让我们看看你的数据结构和代码,但首先没有 typedef:
struct Vector {
void **elements;
int top;
int max_size;
};
void **elements 将elements 声明为指向 void 的指针;在这种情况下,我们将动态分配void 指针向量。这类似于上面示例中的数组p。
void allocateVector(struct Vector *vector, int maxSize)
{
vector->elements = malloc(sizeof *vector->elements * maxSize);
if (vector->elements)
vector->maxSize = maxSize;
}
我们现在有一个指向void 的一维指针数组,能够保存maxSize 元素。现在我们可以编写你的Add_To_Vector 函数了:
void Add_To_Vector(struct Vector *vector, void *objectPtr, int index)
{
if (vector->elements)
{
if (index < vector->maxSize)
{
vector->elements[index] = objectPtr;
}
}
}
然而,有两个非常大的问题。首先,我们无法从指针本身判断被指向对象的类型;它只是一个地址,没有描述所指向对象的信息。例如,如果我们想打印向量中所有对象的值,我们将不知道使用哪个转换说明符。其次,如果我们的代码结构如下:
while (!done)
{
scanf("%d %d", &vector_value_int, &index);
Add_To_Vector(&vector, &vector_value_int, index);
...
}
我们会继续将相同的地址 (&vector_value_int) 添加到数组中,这不是我们想要的。我们想复制 vector_value_int 中的内容,但同样,我们不知道如何做到这一点,因为我们不知道 objectPtr 中的内容的类型指向。
解决这个问题的一种常见方法是通过指针信息传递所谓的回调函数;回调知道如何复制指向的值。例如:
void *copyIntValue(void *objectPtr)
{
int *copy = malloc(sizeof *copy);
if (copy)
*copy = *(int *) objectPtr;
return copy;
}
void Add_To_Vector(struct Vector *vector, void *objectPtr, void *(*cpyFunc)(void *), int index)
{
if (vector->elements)
{
if (index < vector->maxSize)
{
vector->elements[index] = cpyFunc(objectPtr);
}
}
}
...
while (!done)
{
scanf("%d %d", &vector_value_int, &index);
AddToVector(&vector, &vector_value_int, copyIntValue, index);
}
我们已经消除了第二个问题,但不是第一个;我们仍然不知道vector->elements[i] 指向什么类型。不知何故,这些信息需要作为数组的一部分保存。
我想我现在要破解论坛软件了,所以我将把它留作以后的练习。