有没有办法在循环中打印struct成员而不在C中命名每个成员?

时间:2014-12-16 01:45:59

标签: c loops struct

每次我想要打印或初始化结构时,我都必须通过每个成员使代码不能重复使用。有没有办法在for,while或do while循环中执行此操作?

typedef struct Client
{
   char* Name;
   char* Address;
   char* Password;
   char* Privilege;
}Client;

1 个答案:

答案 0 :(得分:3)

以下是使用offsetof()的示例。代码是不完整的,但假设您使用C99或C11编译器,那么现在编译的是什么。代码在某些地方并不是最小的 - 特别是val_integer()fmt_integer()函数。您可以通过编写复杂表达式来避免每个变量中的baseptr变量。

它所基于的场景是“从文本文件中读取配置到结构中”。文件中的行可以为空或包含注释(#到行尾)或表单中的配置参数:

NAME_OF_PARAMETER   value-for-parameter

名称后跟空格和值。不同的配置元素具有不同的类型。代码读取行,将非注释行拆分为键(参数名称)和值,然后调用代码将值转换为键的正确类型。在这个方案的配置文件的基础上,有大约300个配置参数,不同的参数可以有不同的有效范围,默认值等等,所以类型集相当大,因此,这是足够好的用于说明目的。真实的'方案比这要复杂得多,因为它有大约30对验证和格式化功能 - 但它取代了一个单独的功能,超过6,000行,每个包含300个单位,每行20个,每个都有一个小于千的源文件。总共行,没有大于约20行的函数(所有大小的数字近似值)。

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

/* #include "xxconfig.h" // would define struct XX_Config */
typedef struct XX_Config
{
    int     xx_version_major;
    int     xx_version_minor;
    char   *xx_product_name;
    int     xx_max_size;
    int     xx_min_size;
    double  xx_ratio;
    /* And so on for several hundred configuration elements */
} XX_Config;

typedef enum TypeCode
{
    T_INT,
    T_DOUBLE,
    T_CHARPTR,
    T_CHARARR,
    /* ...other types as needed */
} TypeCode;
typedef struct Descriptor Descriptor;
typedef struct TypeInfo TypeInfo;

typedef int (*Validator)(char *buffer, const Descriptor *descr, void *data);
typedef int (*Formatter)(char *buffer, size_t buflen, const Descriptor *descr, void *data);

struct TypeInfo
{
    TypeCode    type;
    Validator   valid;
    Formatter   format;
};

struct Descriptor 
{
    TypeCode    type;
    size_t      offset;
    char       *name;
};

extern int val_integer(char *buffer, const Descriptor *descr, void *data);
extern int val_double(char *buffer, const Descriptor *descr, void *data);
extern int val_charptr(char *buffer, const Descriptor *descr, void *data);

extern int fmt_integer(char *buffer, size_t buflen, const Descriptor *descr, void *data);
extern int fmt_double(char *buffer, size_t buflen, const Descriptor *descr, void *data);
extern int fmt_charptr(char *buffer, size_t buflen, const Descriptor *descr, void *data);

extern void err_report(const char *fmt, ...);
extern int read_config(const char *file, XX_Config *config);
extern int print_config(FILE *fp, const XX_Config *config);

/* Would be static - but they're not defined so they need to be extern */
extern int is_comment_line(char *line);
extern Descriptor *lookup(char *key);
extern int split(char *line, char **key, char **value);

static TypeInfo info[] =
{
    [T_CHARPTR] = { T_CHARPTR, val_charptr, fmt_charptr },
    [T_DOUBLE]  = { T_DOUBLE,  val_double,  fmt_double  },
    [T_INT]     = { T_INT,     val_integer, fmt_integer },
    // ...other types as needed
};

static Descriptor xx_config[] =
{
    { T_INT,     offsetof(XX_Config, xx_version_major), "xx_version_major" },
    { T_INT,     offsetof(XX_Config, xx_version_minor), "xx_version_minor" },
    { T_CHARPTR, offsetof(XX_Config, xx_product_name),  "xx_product_name"  },
    { T_INT,     offsetof(XX_Config, xx_max_size),      "xx_max_size"      },
    { T_INT,     offsetof(XX_Config, xx_min_size),      "xx_min_size"      },
    { T_DOUBLE,  offsetof(XX_Config, xx_ratio),         "xx_ratio"         },
};

enum { NUM_CONFIG = sizeof(xx_config) / sizeof(xx_config[0]) };

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_offsetof_c[];
const char jlss_id_offsetof_c[] = "@(#)$Id$";
#endif /* lint */

int read_config(const char *file, XX_Config *config)
{
    FILE *fp = fopen(file, "r");
    if (fp == 0)
        return -1;
    char line[4096];

    while (fgets(line, sizeof(line), fp) != 0)
    {
        if (is_comment_line(line))
            continue;
        char *key;
        char *value;
        if (split(line, &key, &value) == 0)
        {
            Descriptor *desc = lookup(key);
            if (desc == 0)
            {
                err_report("Do not recognize key <<%s>>\n", key);
                continue;
            }
            TypeCode t = desc->type;
            if ((*info[t].valid)(value, desc, config) != 0)
            {
                err_report("Failed to convert <<%s>>\n", value);
            }
        }
    }

    fclose(fp);
    return 0;
}

int print_config(FILE *fp, const XX_Config *config)
{
    for (int i = 0; i < NUM_CONFIG; i++)
    {
        char value[256];
        TypeCode t = xx_config[i].type;
        if ((*info[t].format)(value, sizeof(value), &xx_config[i], (void *)config) == 0)
            fprintf(fp, "%-20s  %s\n", xx_config[i].name, value);
    }
    return 0;
}

int val_integer(char *buffer, const Descriptor *descr, void *data)
{
    int value;
    if (sscanf(buffer, "%d", &value) != 1)
    {
        err_report("Failed to convert <<%s>> to integer for %s\n", buffer, descr->name);
        return -1;
    }
    char *base = data;
    int *ptr = (int *)(base + descr->offset);
    *ptr = value;
    return 0;
}

int fmt_integer(char *buffer, size_t buflen, const Descriptor *descr, void *data)
{
    char *base = data;
    int *ptr = (int *)(base + descr->offset);
    int  nbytes;
    if ((nbytes = snprintf(buffer, buflen, "%d", *ptr)) < 0 || (size_t)nbytes >= buflen)
    {
        err_report("Failed to format %d into buffer of size %zu\n", *ptr, buflen);
        return -1;
    }
    return 0;
}

就像我在评论中所说:

  

出于实际目的,没有[没有一种简单的方法可以使用for循环来逐步浏览结构的元素以打印或初始化它们]。你可以这样做(在offsetof()中查找<stddef.h>),但将其设置为正常工作是一项艰苦的工作 - 比接受你需要轮流打印每个成员要困难得多,并且通常是为了最低限度的利益。

在原始场景的背景下,修改后的代码有一个好处 - 5000个删除的优势线!