【问题标题】:How to implement retrieve and update in a C struct如何在 C 结构中实现检索和更新
【发布时间】:2014-02-17 10:17:13
【问题描述】:

假设我们在 C 中有以下结构:

struct student {
    int AM;
    char name[50];
    char surname[50];
    char course[50];
};

有人如何实现retrieve(R, c, s) 和update(R, c, s)。

  • 在retrieve()中R是一个指向struct student的指针,s是struct的一个字段,c会有这个字段的值。
  • 在update() 中,R 又是一个指向struct student 的指针,s 是struct 的一个字段,最后struct 的字段将具有c 的值.

【问题讨论】:

  • 这是要做一些家庭作业或课程吗?你试过什么了?对此有多种可能的方法,但我们不知道您已经知道什么。此外,从某个地方复制和粘贴所有内容也不是那么好,所以你仍然应该尝试(并表明)你正在做你自己的一部分。在最坏的情况下,您的解决方案会出现问题,但它仍然比复制 100% 完美的解决方案要好。
  • 我遇到的主要问题是,在检索中我无法预定义变量 c 的类型,因为我不知道该字段将是什么。我尝试将 c 设置为 void,然后将其相应地投射到函数中,但它没有用
  • 那么AM 会是另一个可能返回的字段吗?
  • 是的,如果所有字段都属于同一类型,我可以将其视为数组。
  • @kir2:不,你不应该这样做,因为编译器会填充结构。成员的偏移量是不可预测的

标签: c data-structures struct


【解决方案1】:

由于您仅限于使用 C,因此您的选择非常有限。

基本上,有两种不同的选择(假设您不想深入研究内存/指针魔法):

  • 创建替代函数以根据实际类型设置/检索您的数据。
  • 将您自己的“变体”数据类型创建为struct。

使用不同的函数,你可以这样做:

void retrieveInteger(const struct student *dataset, const char *field, int *value) {
    if (!dataset || !field || !value) // some minimal error checking
        return;
    if (!strcmp(field, "AM")) {
        *value = dataset->AM;
        return;
    }
    printf("Tried to retrieve unknown integer field '%s'!\n", field);
}

检索字符串或设置整数将使用它们自己的函数以类似的方式工作。

使用自定义变体数据类型:

struct variant {
    char type;
    union {
        char string[50];
        int integer;
    };
};

void retrieve(const struct student *dataset, const char *field, struct variant *value) {
    if (!dataset || !field || !value) // some minimal error checking
        return;
    if (!strcmp(field, "AM")) {
        value->type = 0; // let's assume this represents an integer value
        value->integer = dataset->AM;
        return;
    }
    printf("Tried to retrieve unknown integer field '%s'!\n", field);
}

int varToInt(struct variant *value) {
    if (value->type == 0)
        return value->integer;
    else // Not an integer value
        return 0;
}

【讨论】:

  • "假设您不想深入研究内存/指针魔法" => OP 想要将结构成员的值分配给函数的第三个参数.我想说你必须假设指针算术是解决这个问题的唯一有效方法。您的答案可能有效,但它依赖于对结构成员进行硬编码。如果你一直远离指针魔法,那么也许你必须离开 C 直到你准备好学会喜欢指针......
  • 没错,但同时这个任务的基本承诺已经过时了(正如你所说:“我看不出尝试编写一个函数来更新/检索一个值您描述的结构。”)。我认为这是一些基本任务/任务,包括使用指针、结构、switch 语句等。虽然你的答案是完全有效的,但我认为你可能有点“过于复杂”以获得不依赖的通用实现硬编码成员名称等内容。
  • 是的,“指针魔法”可能有点过时,更像是“避免基于数据结构对齐的指针运算”。
  • 我认为解决方案与偏移量有关。我知道指针但不知道偏移量。
  • 如果您想使用偏移量,请查看 Elias 的回答。不要只是比较参数,然后手动计算偏移量。
【解决方案2】:

旁注:
我必须承认:我看不出尝试编写函数以按照您描述的方式从结构中更新/检索值的意义。如果您知道结构的类型,并且您知道要检索/更新的成员,str_var.member 或str_ptr->member 有什么问题?反正性能会更好...

基本答案:
我可能编写一个分配给给定结构成员的函数的基本示例如下:

void update(void *str_ptr, size_t member, void * value, size_t v_size)
{
    memcpy(
        (void *) (((char *)str_ptr) + member),
        value,
        v_size);
}

如何调用:

struct student your_struct;
const char *str = "string";
assign(
    (void *) &your_struct,
    offsetof(struct student, name),
    (void *) str,
    strlen(str)
);

retrieve 函数的工作原理类似,但应该更容易实现:

void retrieve( void *str_ptr, size_t member, void *target, size_t size)
{
    if (size == 0) size = sizeof(*target);//optional
    memcpy(
        target,
        (((char *) str_ptr) + member),
        size
    )
}

可以这样称呼:

int my_am;
retrieve ( (void *) &your_struct, offsetof(struct student, AM), &my_am, 0);
//or
retrieve ( (void *) &your_struct, offsetof(struct student, AM), &my_am, sizeof(my_am));

在检索字符串时,您可能要考虑使用strncpy,并检查target 的类型。但实施这类事情是你的工作。

更有效的替代方案
正如我在答案顶部解释的那样,除了常规的int foo = struct_var.member 之外,这是最好的选择,很可能您发现自己处于不可能的情况。我的回答适用于这些情况,但我刚刚提供了 2 个常规功能。有更好的方法:
但是,正如您所看到的,这些函数只是为您的代码添加了一些语法糖,因为它们包装了一个单独的 memcpy 调用,如果留在行内看起来会有点混乱:

memcpy(
    (void *) &my_am,
    (void *) (((char *) &str_ptr) + offsetof(struct student, am)),
    sizeof(my_am)
);

因此,您可以将这些函数转换为宏,或者(如果您正在编写 C99>= 代码),请使用 inline 函数。
这是你的选择。 inline 和宏都有各自的优缺点。

现在,这一切如何运作?
基本上,魔法发生在这里:

(void *) (((char *)str_ptr) + member)

这里发生了什么:

  • (char *)str_ptr 将 void 指针转换为 char 指针。鉴于char 的大小保证为 1 字节,我们现在可以使用指针算法访问结构成员。
  • + member:这个值是通过offsetof成员获得的,它的值是结构的给定成员相对于它的初始内存地址的偏移量
  • (void *):将批次重新转换为 memcpy 函数的空指针

就是这样,真的。如果您无法预测您将使用的类型,那么只需使用void *
可选的size = sizeof(*target); 位实际上是一种“猜测”您正在处理的类型的方法。如果 sizeof(*target); 为 1,则很有可能您正在处理 char 类型,但您不应该依赖此检查...

更多信息
有关我使用 offsetof 和结构成员指针算法的更多详细信息,check this question

检查working codepad here

来源,供将来参考这些功能的工作实现:

#include <stddef.h>
#include <stdio.h>

struct foo
{
    int bar;
    int foobar;
};

void retrieve( void *str_ptr, size_t member, void *target, size_t size);

void update( void *str_ptr, size_t member, void *value, size_t size);

int main ( void )
{
    struct foo test = {.bar = 123,.foobar = 345};
    int target = 0;
    printf("Initial values: %d\n%d\n", target, test.foobar);
    retrieve(
        (void *) &test,
        offsetof(struct foo, bar),
        (void *) &target,
        sizeof(target)
    );
    update (
        (void *) &test,
        offsetof(struct foo, foobar),
        (void *) &target,
        sizeof(target)
    );
    printf("After calls: %d\n%d\n", target, test.foobar);
    return 0;
}

void retrieve( void *str_ptr, size_t member, void *target, size_t size)
{
    //optional
    if (size == 0) size = sizeof *target;
    memcpy(
        target,
        (void *) (((char *) str_ptr) + member),
        size
    );
}

void update( void *str_ptr, size_t member, void *value, size_t size)
{
    //optional
    if (size == 0) size = sizeof(*target);
    memcpy(
        (void *) (((char *) str_ptr) + member),
        value,
        size
    );
}

【讨论】:

    【解决方案3】:

    您问错了问题,而不是试图找到严格的偏移量,您真正想要做的是使用映射数据类型(又名字典)。这可能是一个简单的数组,您可以使用常量访问其成员:

    #define STUDENT_ID 0
    #define STUDENT_NAME 1
    #define STUDENT_SURNAME 2
    #define STUDENT_COURSE 3
    
    typedef void** student;
    
    void update(student R, void* c, size_t s) {
        R[s] = c;
    }
    void* retrieve(student R, size_t s) {
        return R[s];
    }
    
    // you might want to define some helper macros
    #define update_dynamic(type, R, c, s) { \
        type* ptr = malloc(sizeof(type)); \
        *ptr = c; \
        update(R, ptr, s); \
    }
    #define retrieve_dynamic(type, R, c, s) { \
        type* ptr = (type*) retrieve((R), (s)); \
        c = *ptr; \
        free(ptr); \
    }
    
    
    struct student s1 = malloc(4 * sizeof(void*));
    int* id = malloc(sizeof(int));
    id = 12;
    update(s1, id, STUDENT_ID);
    

    或者您可以继续使用哈希表或基于树的映射数据结构,这样您就可以使用字符串作为字段名称。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-05
      • 1970-01-01
      • 1970-01-01
      • 2011-08-27
      • 2011-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多