假设我有struct
(偶然包含位字段,但您不应该关心):
struct Element {
unsigned int a1 : 1;
unsigned int a2 : 1;
...
unsigned int an : 1;
};
我希望以方便的方式访问 i'th 成员。让我们检查一下检索解决方案 我想出了这个功能:
int getval(struct Element *ep, int n)
{
int val;
switch(n) {
case 1: val = ep->a1; break;
case 2: val = ep->a2; break;
...
case n: val = ep->an; break;
}
return val;
}
但我怀疑有一个更简单的解决方案。可能是数组访问风格之类的东西。
我试着这样做:
#define getval(s,n) s.a##n
但是预计它不起作用 有更好的解决方案吗?
答案 0 :(得分:13)
除非你对结构的底层结构有特定的了解,否则无法在C中实现这样的方法。会遇到各种各样的问题,包括
你最好亲自为你的结构实现一个方法,它对结构的内部成员有深刻的理解。
答案 1 :(得分:7)
如果结构中的每个字段都是int
,那么您基本上应该能够说
int getval(struct Element *ep, int n)
{
return *(((int*)ep) + n);
}
如果是整数,则将指向结构的指针转换为指向数组的指针,然后访问该数组的第n个元素。由于结构中的所有内容似乎都是整数,因此这是完全有效的。请注意,如果你有一个非int成员,这将失败。
更通用的解决方案是维护一组字段偏移:
int offsets[3];
void initOffsets()
{
struct Element e;
offsets[0] = (int)&e.x - (int)&e;
offsets[1] = (int)&e.y - (int)&e;
offsets[2] = (int)&e.z - (int)&e;
}
int getval(struct Element *ep, int n)
{
return *((int*)((int)ep+offsets[n]));
}
这将起到这样的意义,即您可以为结构的任何int字段调用getval
,即使结构中有其他非int字段,因为偏移将全部是对的。但是,如果您尝试在其中一个非int字段上调用getval
,则会返回完全错误的值。
当然,您可以为每种数据类型编写不同的函数,例如
double getDoubleVal(struct Element *ep, int n)
{
return *((double*)((int)ep+offsets[n]));
}
然后只需为您想要的任何数据类型调用正确的函数。顺便说一句,如果你使用C ++,你可以说像
template<typename T>
T getval(struct Element *ep, int n)
{
return *((T*)((int)ep+offsets[n]));
}
然后它适用于您想要的任何数据类型。
答案 2 :(得分:6)
如果你的结构是除了位域之外的任何东西,你可以只使用数组访问,如果我正确地记住C保证结构的一系列成员都是相同类型,具有与数组相同的布局。如果您知道编译器将位域按顺序存储到整数类型中的哪些位,那么您可以使用shift / mask ops,但那是依赖于实现的。
如果要通过变量索引访问位,那么最好用包含标志位的整数替换位域。变量访问确实不是bitfields的用途:a1 ... an基本上是独立的成员,而不是位数组。
你可以这样做:
struct Element {
unsigned int a1 : 1;
unsigned int a2 : 1;
...
unsigned int an : 1;
};
typedef unsigned int (*get_fn)(const struct Element*);
#define DEFINE_GETTER(ARG) \
unsigned int getter_##ARG (const struct Element *ep) { \
return ep-> a##ARG ; \
}
DEFINE_GETTER(1);
DEFINE_GETTER(2);
...
DEFINE_GETTER(N);
get_fn jump_table[n] = { getter_1, getter_2, ... getter_n};
int getval(struct Element *ep, int n) {
return jump_table[n-1](ep);
}
有些重复可以通过多次包含相同标头的技巧来避免,每次都以不同方式定义宏。标题会为每个1 ... N扩展该宏一次。
但我不相信这是值得的。
它确实处理了JaredPar的观点,即如果你的结构混合了不同的类型你会遇到麻烦 - 这里所有通过特定跳转表访问的成员当然必须是相同的类型,但它们之间可能有任何旧的垃圾他们。尽管如此,这仍然留下了JaredPar的其余部分,与开关相比,这是一个很大的代码膨胀。
答案 3 :(得分:2)
不,没有简单的方法可以更轻松地做到这一点。特别是对于难以通过指针间接访问的位域(你不能获取位域的地址)。
您当然可以将此功能简化为以下内容:
int getval(const struct Element *ep, int n)
{
switch(n)
{
case 1: return ep->a1;
case 2: return ep->a2;
/* And so on ... */
}
return -1; /* Indicates illegal field index. */
}
通过使用扩展到case
- 行的预处理器宏,可以进一步简化实现,但这只是sugar。
答案 4 :(得分:0)
如果结构真的像描述的那样简单,你可以使用带数组的联合(或对数组的强制转换)和一些位访问魔法(如How do you set, clear and toggle a single bit in C?中所示)。
正如Jared所说,一般情况是 hard 。
答案 5 :(得分:0)
为什么不在结构中构建getval()
?
struct Whang {
int a1;
int a2;
int getIth(int i) {
int rval;
switch (i) {
case 1: rval = a1; break;
case 2: rval = a2; break;
default : rval = -1; break;
}
return rval;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Whang w;
w.a1 = 1;
w.a2 = 200;
int r = w.getIth(1);
r = w.getIth(2);
return 0;
}
getIth()
会了解Whang
的内部结构,并且可以处理它所包含的内容。
答案 6 :(得分:0)
我认为你的真正解决方案是不在结构中使用位域,而是定义一个集合类型或一个位数组。
答案 7 :(得分:0)
我建议代码生成。如果您的结构不包含大量字段,则可以为每个字段或一系列字段自动生成例程 并使用它们:
val = getfield_aN( myobject, n );
或
val = getfield_foo( myobject );
答案 8 :(得分:0)
如果要使用元素索引访问您的结构:
int getval(struct Element *ep, int n)
并按名称:
ep->a1
然后你会遇到一些难以维护的开关,就像每个人都建议的方法一样。
但是,如果您想要做的只是按索引访问而不是按名称访问,那么您可以更有创意。
首先,定义一个字段类型:
typedef struct _FieldType
{
int size_in_bits;
} FieldType;
然后创建一个结构定义:
FieldType structure_def [] = { {1}, {1}, {1}, {4}, {1}, {0} };
上面定义了一个结构,其中包含五个大小为1,1,1,4和1位的元素。最后的{0}标志着定义的结束。
现在创建一个元素类型:
typedef struct _Element
{
FieldType *fields;
} Element;
创建Element
的实例:
Element *CreateElement (FieldType *field_defs)
{
/* calculate number of bits defined by field_defs */
int size = ?;
/* allocate memory */
Element *element = malloc (sizeof (Element) + (size + 7) / 8); /* replace 7 and 8 with bits per char */
element->fields = field_defs;
return element;
}
然后访问一个元素:
int GetValue (Element *element, int field)
{
/* get number of bits in fields 0..(field - 1) */
int bit_offset = ?;
/* get char offset */
int byte_offset = sizeof (Element) + bit_offset / 8;
/* get pointer to byte containing start of data */
char *ptr = ((char *) element) + byte_offset;
/* extract bits of interest */
int value = ?;
return value;
}
设置值与获取值类似,只有最终部分需要更改。
您可以通过扩展FieldType
结构来增强上述内容,以包含有关存储的值类型的信息:char,int,float等,然后为每种类型写入访问器,根据定义检查所需类型类型。
答案 9 :(得分:0)
如果你有
然后这个解决方案适合你。
#include <stdio.h>
#include <stdint.h>
struct Element {
unsigned int a1 : 1;
unsigned int a2 : 1;
unsigned int a3 : 1;
unsigned int a4 : 1;
};
#define ELEMENT_COUNT 4 /* the number of bit fields in the struct */
/* returns the bit at position N, or -1 on error (n out of bounds) */
int getval(struct Element* ep, int n)
{
if(n > ELEMENT_COUNT || n < 1)
return -1;
/* this union makes it possible to access bit fields at the beginning of
the struct Element as if they were a number.
*/
union {
struct Element el;
uint32_t bits;
} comb;
comb.el = *ep;
/* check if nth bit is set */
if(comb.bits & (1<<(n-1))) {
return 1;
} else {
return 0;
}
}
int main(int argc, char** argv)
{
int i;
struct Element el;
el.a1 = 0;
el.a2 = 1;
el.a3 = 1;
el.a4 = 0;
for(i = 1; i <= ELEMENT_COUNT; ++i) {
printf("el.a%d = %d\n", i, getval(&el, i));
}
printf("el.a%d = %d\n", 8, getval(&el, 8));
return 0;
}
答案 10 :(得分:0)
基于eli-courtwright解决方案,但不使用场偏移数组 ...... 如果你有一个包含像这样的指针字段的结构,也许你可以写:
struct int_pointers
{
int *ptr1;
int *ptr2;
long *ptr3;
double *ptr4;
std::string * strDescrPtr;
};
然后你知道每个指针与指向结构的指针有4个字节的偏移量,所以你可以写:
struct int_pointers ptrs;
int i1 = 154;
int i2 = -97;
long i3 = 100000;
double i4 = (double)i1/i2;
std::string strDescr = "sample-string";
ptrs.ptr1 = &i1;
ptrs.ptr2 = &i2;
ptrs.ptr3 = &i3;
ptrs.ptr4 = &i4;
ptrs.strDescrPtr = &strDescr;
然后,例如,对于int值,您可以写:
int GetIntVal (struct int_pointers *ep, int intByteOffset)
{
int * intValuePtr = (int *)(*(int*)((int)ep + intByteOffset));
return *intValuePtr;
}
通过以下方式致电:
int intResult = GetIntVal(&ptrs,0) //to retrieve the first int value in ptrs structure variable
int intResult = GetIntVal(&ptrs,4) //to retrieve the second int value in ptrs structure variable
以及其他结构字段值(写入其他特定函数并使用正确的字节偏移值(4的倍数))。
答案 11 :(得分:0)
尽管OP指定我们不应该关心结构的内容,因为它们只是位域,所以可以使用char或int(或任何具有所需大小的数据类型)来创建n-在这种情况下位“数组”?
void writebit(char *array, int n)
{
char mask = (1 << n);
*array = *array & mask;
}
如果需要更长的“数组”,则将char类型替换为更大的类型。不确定这是否是其他结构中的确定解决方案,但它应该在这里工作,具有类似的readbit函数。