每次我想打印或初始化一个结构时,我都必须遍历每个成员,这使得代码的可重用性不高。有没有办法在 for、while 或 do while 循环中执行此操作?
typedef struct Client
{
char* Name;
char* Address;
char* Password;
char* Privilege;
}Client;
最佳答案
这是一个使用 offsetof()
的例子.代码不完整,但假设您使用 C99 或 C11 编译器,则存在的代码可以编译。代码在某些地方不是最小的——特别是 val_integer()
和 fmt_integer()
职能。你可以避免 base
和 ptr
通过编写一个复杂的表达式在每个变量中。
它基于的场景是“将配置从文本文件读取到结构中”。文件中的行可以是空白或包含注释(#
到行尾),或以下形式的配置参数:
NAME_OF_PARAMETER value-for-parameter
名称后跟空格和值。不同的配置元素具有不同的类型。代码读取该行,将非注释行拆分为键(参数名称)和值,然后调用代码将值转换为键的正确类型。在这个方案所基于的配置文件中,大约有三百个配置参数,不同的参数可以有不同的有效范围,默认值等等,所以类型集相当大,因此,但这已经足够了出于说明目的。 “真正的”方案比这复杂得多,因为大约有 30 对验证和格式化函数——但它用 6,000 多行代码替换了一个函数,在 300 个单元的程式化集合中,每个单元 20 行,每个单元都有一个小得多的源文件总共少于 1000 行,并且没有函数超过 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 practical purposes, no [there isn't an easy way to use a
for
loop to step through the elements of a structure to print or initialize them]. You can do it (look upoffsetof()
in<stddef.h>
), but setting it up to work properly is hard work — much harder work than accepting that you'll need print each member in turn, and usually for minimal benefit.
在原始场景的上下文中,修改后的代码有一个好处——删除了 5000 行的好处!
关于c - 有没有办法在循环中打印结构成员而无需在 C 中命名每个成员?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27496245/