【问题标题】:C method for iterating through a struct's members like an array?用于遍历结构成员(如数组)的 C 方法?
【发布时间】:2013-01-03 07:47:22
【问题描述】:

假设我有一个矢量类:

typedef struct vec3_s
{
    float x, y, z;
}
vec3;

但是,我希望能够在不将其转换为浮点数组的情况下对其进行迭代。虽然在这种情况下可以接受强制转换,但我很想知道在直接 C 中是否可以使用类似 C++ 的功能。例如,在 C++ 中,由于 std::vector< T > 的下标 [] 运算符重载,我可以将其第一个索引的地址传递给采用void* 的函数。

即,

void do_something_with_pointer_to_mem( void* mem )
{
    // do stuff
}

int main( void )
{
    std::vector< float > v;

    // fill v with values here

    // pass to do_something_with_pointer_to_mem

    do_some_with_pointer_to_mem( &v[ 0 ] );

    return;
}

另一个更具体的例子是在 OpenGL 中调用 glBufferData(...) 时(使用 C++ 时):

glBufferData( GL_ARRAY_BUFFER, sizeof( somevector ), &amp;somevector[ 0 ], GL_STREAM_DRAW );

那么,是否有可能在 C 中使用下标运算符完成类似的事情?如果不是,并且我必须编写一个函数(例如,float vec3_value_at( unsigned int i )),那么在定义它的头文件中只使用static inline 是否有意义?

【问题讨论】:

标签: c vector iteration memory-address subscript


【解决方案1】:

如果所有结构字段都属于同一类型,则可以使用联合,如下所示:

typedef union vec3_u
{
    struct vec3_s {
        float x, y, z;
    };
    float vect3_a[3];
}
vec3;

通过这种方式,您可以独立访问每个 x、y 或 z 字段,或者使用 vect3_a 数组对其进行迭代。这个解决方案在内存或计算方面没有任何成本,但我们可能距离类似 C++ 的解决方案有点远。

【讨论】:

  • 这很优雅。不过,您介意解释一下这是如何工作的吗?我以前从未真正发现自己使用过工会。虽然我对 C++ 相当了解,但我对 C 还是很陌生。
  • 未定义的行为? “6.2.6.1.7:当值存储在联合类型对象的成员中时,不对应于该成员但对应于其他成员的对象表示的字节采用未指定的值。”
  • 实际上如上面评论中所述,这可能取决于编译器。但是 AFAIK 大多数编译器都会在不同联合成员的字节之间进行简单的映射。所以在上面的例子中,vect3_a[0] 映射到 x,vect3_a[1] 映射到 y,vect3_a[2] 映射到 z。这只是访问结构数据的一种语法方式。整个 vec3 实例仍将是 3*sizeof(float)。另一种迭代解决方案是简单地使用指针增量。例如 &x + 1 将对应于 &y。这与使用数组基本相同。
  • 如果有人有兴趣查看代码,我已经在 GCC x64 (Linux) 上对此进行了测试。这很简单,我没有任何问题:)。这段代码在 C++ 中使用也没有实际的理由,因为 GLM 几乎可以处理所有这些。
【解决方案2】:

您没有获得 C++ 的语法糖,但很容易将您在 C++ 中编写的函数编写为 operator[]。

float get_vec3(v *vec3, int i) {
   switch(i) {
   case 0: return v->x;
   case 1: return v->y;
   case 2: return v->z;
   }
   assert(0);
 }

现在您可以遍历任何 vec3。

 for (int i = 0; i < 3; i++) {
     printf("%f\n", get_vec3(v, i));
 }

【讨论】:

  • 将其设为staticinline 或两者的组合是否有利?
  • static 会给它静态而不是全局范围(所以我不明白你为什么认为这会很好)。 inline 将是一个潜在的优化,但您需要进行分析。
【解决方案3】:

C 中的问题在于您需要知道如何在结构中移动(即您需要知道类型)。 std::vector&lt;T&gt; 之所以如此运作,是因为它使用了模板(C++ 概念)。现在,也就是说,你可以尝试一些与你建议的稍有不同的东西。如果您不想使用任何数组,则可以存储泛型类型。但是,在检索和使用数据时,用户必须知道他或她期待什么样的数据。下面避免了数组(尽管在使用它们时存在一个潜在的更清洁的解决方案)并且有一个链接列表实现,它为您提供几乎与std::vector&lt;T&gt; 相同的灵活性(除了性能优势,因为这是一个具有O(n) 操作的链接列表一切(你可以聪明地把列表倒过来实现,也许,O(1) 插入,但这只是举例)

#include <stdio.h>
#include <stdlib.h>
typedef struct _item3_t
{
  void *x, *y, *z;
  struct _item3_t* next;
} item3_t;

typedef struct
{
  item3_t* head;
} vec3_t;

void insert_vec3(vec3_t* vec, void* x, void* y, void* z)
{
  item3_t* item = NULL;
  item3_t* tmp  = NULL;
  int i = 0;
  if(vec == NULL)
    return;

  item = malloc(sizeof(item3_t));
  item->x = x;
  item->y = y;
  item->z = z;
  item->next = NULL;

  tmp = vec->head;
  if(tmp == NULL) { // First element
    vec->head = item;
  } else {
    while(tmp->next != NULL)
      tmp = item->next;
    tmp->next = item;
  }
}

// This is one method which simply relies on the generic method above
void insert_vec3_float(vec3_t* vec, float x, float y, float z)
{
  float* xv, *yv, *zv;
  if(vec == NULL)
    return;
  xv = malloc(sizeof(float));
  yv = malloc(sizeof(float));
  zv = malloc(sizeof(float));

  *xv = x;
  *yv = y;
  *zv = z;

  insert_vec3(vec, xv, yv, zv);
}

void init_vec3(vec3_t* vec)
{
  if(vec == NULL)
    return;
  vec->head = NULL;
}

void destroy_vec3(vec3_t* vec)
{
  item3_t* item = NULL, *next = NULL;
  if(vec == NULL)
    return;

  item = vec->head;
  while(item != NULL) {
    next = item->next;
    free(item->x);
    free(item->y);
    free(item->z);
    free(item);
    item = next;
  }
}

item3_t* vec3_get(vec3_t* vec, int idx)
{
  int i = 0;
  item3_t* item = NULL;
  if(vec == NULL)
    return NULL;
  item = vec->head;
  for(i = 0 ; i < idx && item != NULL ; ++i)
    item = item->next;
  return item;
}

void do_something(item3_t* item)
{
  if(item == NULL)
    return;
  float x = *((float*)item->x);
  float y = *((float*)item->y);
  float z = *((float*)item->z);

  // To do - something? Note, to manipulate the actual
  // values in the vector, you need to modify their values
  // at their mem addresses
}

int main()
{
  vec3_t vector;

  init_vec3(&vector);

  insert_vec3_float(&vector, 1.2, 2.3, 3.4);

  printf("%f %f %f\n", *((float*)vec3_get(&vector, 0)->x), *((float*)vec3_get(&vector, 0)->y), *((float*)vec3_get(&vector, 0)->z));

  do_something(vec3_get(&vector, 0));

  destroy_vec3(&vector);

  return 0;
}

此代码应该可以直接编译。您在这里拥有的是一个链表,它是您的“向量”(特别是 vec3 结构)。列表中的每个节点(即std::vector&lt;T&gt; 意义上的每个元素)都有 3 个元素,它们都是 void 指针。因此,您可以在此处存储您希望的任何数据类型。唯一的问题是您需要为这些指针分配内存以指向并且在删除元素时,您需要释放该内存(请参阅vec3_destroy 方法以获取示例)。希望这有助于更多地了解这些 void 指针如何在您的情况下工作。

要检索数据,您将无法使用[] 表示法,但您可以以相同的方式使用vec3_get 方法。 do_something 方法是一个示例存根,您可以通过某种方式完成与您在 OP 中提到的类似的事情。

【讨论】:

    猜你喜欢
    • 2021-12-27
    • 2013-10-04
    • 1970-01-01
    • 2021-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多