我想用它来实现一些东西,比如将一个 NULL 错误对象传递给一个函数,如果有错误,它将错误的指针设置为一些错误代码等。
从上面的引用和问题中的代码来看,您似乎正在寻找一个可以“保存”不同类型的变量,即有时您希望它是整数,有时是浮点数,有时是浮点数一个字符串等等。这在某些语言中称为变体,但在 C 中不存在 变体。 (有关变体的更多信息,请参见https://en.wikipedia.org/wiki/Variant_type)
因此,在 C 语言中,您必须编写自己的变体类型。有几种方法可以做到这一点。我将在下面给出示例。
但首先是关于 C 中指针的几句话,因为问题中的代码似乎揭示了一个误解,因为它直接将值分配给指针,例如pointer = somestruct; 这是非法的。
在 C 中理解“指针的值”和“指向对象的值”之间的区别非常重要。第一个,即指针的值,告诉指针指向的位置,即指针的值是指向对象的地址。对指针的赋值会改变指针指向的位置。要更改指向对象的值,必须首先取消引用该指针。示例(伪代码):
pointer = &some_int; // Make pointer point to some_int
*pointer = 10; // Change the value of the pointed to object, i.e. some_int
// Notice the * in front of pointer - it's the dereference
// that tells you want to operate on the "pointed to object"
pointer = 10; // Change the value of the pointer, i.e. where it points to
// In other words, pointer no longer points to some_int
现在回到“变体”实现。如前所述,有几种方法可以在 C 中编写代码。
从您的问题来看,您似乎想使用空指针。这是可行的,我将首先展示一个使用 void-pointer 的示例,然后再展示一个使用联合的示例。
在您的问题中不清楚 cond 是什么,所以在我的示例中,我将假设它是一个命令行参数,并且我只是添加了一些解释以便有一个运行示例。
示例的常见模式是使用“标签”。这是一个额外的变量,它告诉当前对象类型的值(也称为元数据)。所以一般变体数据类型看起来像:
struct my_variant
{
TagType tag; // Tells the current type of the value object
ValueType value; // The actual value. ValueType is a type that allows
// storing different object types, e.g. a void-pointer or a union
}
示例 1:空指针和强制类型转换
下面的示例将使用 void 指针指向包含实际值的对象。有时是整数,有时是浮点数或任何需要的值。使用 void 指针时,有必要在取消引用指针之前强制转换 void 指针(即在访问指向的对象之前)。 tag 字段告诉了指向对象的类型,从而也告诉了强制转换的方式。
#include <stdio.h>
#include <stdlib.h>
// This is the TAG type.
// To keep the example short it only has int and float but more can
// be added using the same pattern
typedef enum
{
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
// This is the variant type
typedef struct
{
error_type_e tag; // The tag tells the type of the object pointed to by value_ptr
void* value_ptr; // void pointer to error value
} error_object_t;
// This function evaluates the error and (if needed)
// creates an error object (i.e. the variant) and
// assigns appropriate values of different types
error_object_t* get_error_object(int err)
{
if (err >= 0)
{
// No error
return NULL;
}
// Allocate the variant
error_object_t* result_ptr = malloc(sizeof *result_ptr);
// Set tag value
// Allocate value object
// Set value of value object
if (err > -100) // -99 .. -1 is INT error type
{
result_ptr->tag = INT_ERROR_TYPE;
result_ptr->value_ptr = malloc(sizeof(int));
*(int*)result_ptr->value_ptr = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_ptr->tag = FLOAT_ERROR_TYPE;
result_ptr->value_ptr = malloc(sizeof(float));
*(float*)result_ptr->value_ptr = 42.42;
}
else
{
result_ptr->tag = UNKNOWN_ERROR_TYPE;
result_ptr->value_ptr = NULL;
}
return result_ptr;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t* err_ptr = get_error_object(err);
if (err_ptr == NULL)
{
// No error
// ... add "normal" code here - for now just print a message
printf("No error\n");
}
else
{
// Error
// ... add error handler here - for now just print a message
switch(err_ptr->tag)
{
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", *(int*)err_ptr->value_ptr);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", *(float*)err_ptr->value_ptr);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
free(err_ptr->value_ptr);
free(err_ptr);
}
return 0;
}
运行这个程序的一些例子:
> ./prog 5
No error
> ./prog -5
Error type INT, value 42
> ./prog -105
Error type FLOAT, value 42.419998
> ./prog -205
Error type UNKNOWN, no value to print
如上例所示,您可以使用 void-pointer 实现变体类型。但是,代码需要大量转换,这使得代码难以阅读。一般来说,我不会推荐这种方法,除非您有一些特殊要求强制使用 void-pointer。
示例 2:指向联合的指针
如前所述,C 没有其他语言中已知的变体。但是,C 有一些非常接近的东西。那是工会。一个联合可以在不同的时间持有不同的类型——它错过的只是tag。因此,您可以使用标签和联合,而不是使用标签和空指针。好处是 1) 不需要强制转换,并且 2) 避免了 malloc。示例:
#include <stdio.h>
#include <stdlib.h>
typedef enum
{
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
// The union that can hold an int or a float as needed
typedef union
{
int n;
float f;
} error_union_t;
typedef struct
{
error_type_e tag; // The tag tells the current union use
error_union_t value; // Union of error values
} error_object_t;
error_object_t* get_error_object(int err)
{
if (err >= 0)
{
// No error
return NULL;
}
error_object_t* result_ptr = malloc(sizeof *result_ptr);
if (err > -100) // -99 .. -1 is INT error type
{
result_ptr->tag = INT_ERROR_TYPE;
result_ptr->value.n = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_ptr->tag = FLOAT_ERROR_TYPE;
result_ptr->value.f = 42.42;
}
else
{
result_ptr->tag = UNKNOWN_ERROR_TYPE;
}
return result_ptr;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t* err_ptr = get_error_object(err);
if (err_ptr == NULL)
{
// No error
// ... add "normal" code here - for now just print a message
printf("No error\n");
}
else
{
// Error
// ... add error handler here - for now just print a message
switch(err_ptr->tag)
{
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", err_ptr->value.n);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", err_ptr->value.f);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
free(err_ptr);
}
return 0;
}
在我看来,这段代码比使用 void-pointer 的代码更容易阅读。
示例 3:联合 - 无指针 - 无 malloc
即使示例 2 比示例 1 更好,示例 2 中仍然存在动态内存分配。动态分配是大多数 C 程序的一部分,但只有在真正需要时才应使用它。换句话说 - 具有自动存储持续时间的对象(也称为局部变量)应在可能的情况下优先于动态分配的对象。
下面的例子展示了如何避免动态分配。
#include <stdio.h>
#include <stdlib.h>
typedef enum
{
NO_ERROR,
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
typedef union
{
int n;
float f;
} error_union_t;
typedef struct
{
error_type_e tag; // The tag tells the current union usevalue_ptr
error_union_t value; // Union of error values
} error_object_t;
error_object_t get_error_object(int err)
{
error_object_t result_obj;
if (err >= 0)
{
// No error
result_obj.tag = NO_ERROR;
}
else if (err > -100) // -99 .. -1 is INT error type
{
result_obj.tag = INT_ERROR_TYPE;
result_obj.value.n = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_obj.tag = FLOAT_ERROR_TYPE;
result_obj.value.f = 42.42;
}
else
{
result_obj.tag = UNKNOWN_ERROR_TYPE;
}
return result_obj;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t err_obj = get_error_object(err);
switch(err_obj.tag)
{
case NO_ERROR:
printf("No error\n");
break;
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", err_obj.value.n);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", err_obj.value.f);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
return 0;
}
总结
有很多方法可以解决 OP 解决的问题。这个答案给出了三个例子。在我看来,示例 3 是最好的方法,因为它避免了动态内存分配和指针,但在某些情况下示例 1 或 2 可能更好。