除了 Eric 的回答之外,还有其他几种方法可以处理 C 中的这种情况,其中包含不同数量的额外包袱和精神悲伤。也就是说:这些是表达一个值的方法,该值表示您的域中未定义或未设置的值。我们不是在谈论如果您分配一个数组而不初始化它然后访问它,您通常会从您的 C 实现中返回的实际不确定值。
方法 1: NaN(非数字)值,如 Eric 的解决方案中所示。在您操作浮点数的特定情况下工作,并且NaN 不可能在您的域中用作合法定义的值。如果您正在存储字符,空字符'\0'
也可能是未定义的合理选择。
方法2:指针。C 确实有 NULL,就像 C# 一样。但是,它仅适用于您正在操作指针类型的值。所以,而不是:
int a[3];
a[0] = 1;
a[1] = 2;
a[2] = 0; /* blech -- I really want this to mean 'undefined',
but what if I really needed 0 as a numeric value
in my array? In that case, this doesn't work! */
我可以这样做:
#include <stdlib.h>
int* integer_new(int i) {
int* result = (int*) malloc(sizeof(int));
*result = i;
return result;
}
int* a[3];
a[0] = integer_new(1);
a[1] = integer_new(2);
a[2] = NULL;
现在您有了一个可以轻松测试并与正常整数值区分开来的值。我相信这在理论上是 C# 代码幕后发生的事情。但是您很快就会看到缺点:您现在有一堆堆分配的对象,而以前没有,现在您必须管理它们,当您使用它们时适当地释放它们。
如果您正在处理诸如享元模式之类的东西,其中这些值是堆栈分配的或在其他地方预分配的,则可以对此进行改进。在这种情况下,您可以只获取这些值的地址,将它们粘贴到数组中,而不必自己进行堆分配。但是您仍然必须应对额外的间接层。
方法 3: maybe 模式(我从 Haskell 偷来的)。像这样的东西:
typedef struct maybe_int_ {
int has_value; /* 1 if yes, 0 if no */
int value;
} maybe_int;
maybe_int maybe_nothing(void) {
maybe_int result;
result.has_value = 0;
return result;
}
maybe_int maybe_just(int i) {
maybe_int result;
result.has_value = 1;
result.value = i;
return result;
}
maybe_int a[3];
a[0] = maybe_just(1);
a[1] = maybe_just(2);
a[2] = maybe_nothing();
这对堆栈更有效,因此通常以这种方式对指针进行改进,但您仍然需要处理更多的簿记工作。
方法4:工会。类似于方法 3,但如果您的数组中可以有多种值,您可能会这样做:
typedef enum {
BOXED_TYPE_UNDEFINED,
BOXED_TYPE_INT,
BOXED_TYPE_FLOAT
} boxed_type;
typedef struct boxed_ {
boxed_type type;
union {
int int_value;
float float_value;
} u;
} boxed;
boxed boxed_undefined(void) {
boxed result;
result.type = BOXED_TYPE_UNDEFINED;
return result;
}
boxed boxed_int(int i) {
boxed result;
result.type = BOXED_TYPE_INT;
result.u.int_value = i;
return result;
}
boxed boxed_float(float f) {
boxed result;
result.type = BOXED_TYPE_FLOAT;
result.u.float_value = f;
return result;
}
boxed a[3];
a[0] = boxed_int(1);
a[1] = boxed_float(2.0f);
a[2] = boxed_undefined();
如果您已经在使用联合加类型鉴别器,那么此解决方案可能特别容易实现。
所有这些解决方案有什么共同点?哨兵值的概念:您存储在数组中的某些值保证不会用于域中的其他任何内容,因此您可以自由地将其解释为未定义的值。如您所见,有很多方法可以将标记值注入您的域。
这是一种不涉及哨兵值的解决方案。方法五:跳出框框。
int a[3];
unsigned char adef[3];
a[0] = 1; adef[0] = 1;
a[1] = 2; adef[1] = 1;
adef[2] = 0; /* I would set a[2] = 0 here as well
because I dislike uninitialized
array values! */
如果您真的不能以任何允许您定义哨兵值的方式处理域中的值,那么只需将值的额外“定义性”存储在其他地方即可。我自己从来不用求助于这个,但我发现单独的相关数据表的一般技术确实有时会派上用场。