我正在尝试实现一个struct person,我需要隐藏一些字段或使其恒定。 创建私有字段的技巧。
标题:
samplerCube
来源
#pragma once
#define NAME_MAX_LEN 20
typedef struct _person {
float wage;
int groupid;
} Person;
const char const *getName (Person *p);
int getId (Person *p);
/// OTHER FUNCTIONS
海湾合作委员会说#include "person.h"
struct _person
{
int id;
float wage;
int groupid;
char name[NAME_MAX_LEN];
};
/// FUNCTIONS
我可以将其写在标题中,但是之后,我将无法使用结构的字段。
person.c:7:8: error: redefinition a 'struct _person' struct _person
答案 0 :(得分:20)
一个结构不能有多个冲突的定义。因此,您无法创建隐藏某些字段的结构。
您可以执行的操作会声明该结构存在于标头中,而没有对其进行定义。然后,调用者将被限制为仅使用指向该结构的指针,并在实现中使用函数对其进行修改。
例如,您可以按以下方式定义标题:
typedef struct _person Person;
Person *init(const char *name, int id, float wage, int groupid);
const char *getName (const Person *p);
int getId (const Person *p);
float getWage (const Person *p);
int getGroupid (const Person *p);
您的实现将包含:
#include "person.h"
struct _person
{
int id;
float wage;
int groupid;
char name[NAME_MAX_LEN];
};
Person *init(const char *name, int id, float wage, int groupid)
{
Person *p = malloc(sizeof *p);
strcpy(p->name, name);
p->id = id;
p->wage= wage;
p->groupid= groupid;
return p;
}
...
答案 1 :(得分:10)
C没有用于隐藏结构类型的各个成员的机制。但是,通过仅根据此类的 pointers 指针进行操作,而不提供定义,可以使整个类型不透明。然后,用户将不得不使用您提供的功能以任何方式操纵实例。这是有时要做的事情。
在某种程度上,您可以实现类似于您在隐藏上下文中描述的内容。例如,考虑一下:
header.h
typedef struct _person {
float wage;
int groupid;
} Person;
实施。c
struct _person_real {
Person person; // must be first, and is a structure, not a pointer.
int id;
char name[NAME_MAX_LEN];
};
现在您可以执行以下操作:
Person *create_person(char name[]) {
struct _person_real *pr = malloc(sizeof(*pr));
if (pr) {
pr->person.wage = DEFAULT_WAGE;
pr->person.groupid = DEFAULT_GROUPID;
pr->id = generate_id();
strncpy(pr->name, name, sizeof(pr->name));
pr->name[sizeof(pr->name) - 1] = '\0';
return &pr->person; // <-- NOTE WELL
} else {
return NULL;
}
}
指向结构的第一个成员的指针也始终也指向整个结构,因此,如果客户端将从该函数获得的指针传递回给您,则可以
struct _person_real *pr = (struct _person_real *) Person_pointer;
并从更大的上下文中处理成员。
但是,请注意,这种方案是有风险的。没有什么可以阻止用户在没有较大上下文的情况下创建Person
,并将指向它的指针传递给期望存在上下文对象的函数。还有其他问题。
总体而言,C API通常采用不透明的结构方法,或者只是仔细地记录允许客户使用其访问的数据做什么,甚至只是记录一切如何工作,以便用户可以做出自己的选择。这些,尤其是后者,与整体C方法和习惯用法完全吻合-C不能握住您的手,或保护您免受伤害。它让您知道自己在做什么,并且只做自己打算做的事。
答案 2 :(得分:2)
您可以使用混合样式;例如在标题中写:
struct person {
float wage;
int groupid;
};
struct person *person_new(void);
char const *getName (struct person const *p);
int getId (struct person const *p);
和来源
struct person_impl {
struct person p;
char name[NAME_MAX_LEN];
int id;
}
struct person *person_new(void)
{
struct person_impl *p;
p = malloc(sizeof *p);
...
return &p->p;
}
chra const *getName(struct person const *p_)
{
struct person_impl *p =
container_of(p_, struct person_impl, p);
return p->name;
}
例如参见https://en.wikipedia.org/wiki/Offsetof了解container_of()
的详细信息。
答案 3 :(得分:2)
约翰·波林格答案的附录:
尽管恕我直言,带有访问器功能(init / get / set / destroy)的不透明指针类型是最安全的方法,但是还有另一个选项允许用户将对象放置在堆栈上。
可以在struct
中分配一个“无类型”的内存块,并显式地(逐位/逐字节)使用该内存,而不使用其他类型。
即:
// public
typedef struct {
float wage;
int groupid;
/* explanation: 1 for ID and NAME_MAX_LEN + 1 bytes for name... */
unsigned long private__[1 + ((NAME_MAX_LEN + 1 + (sizeof(long) - 1)) / sizeof(long))];
} person_s;
// in .c file (private)
#define PERSON_ID(p) ((p)->private__[0])
#define PERSON_NAME(p) ((char*)((p)->private__ + 1))
这是一个非常有力的指示,应避免访问private__
成员中的数据。无法访问实现文件的开发人员甚至都不知道其中包含什么。
话虽如此,最好的方法是使用pthread_t
API(POSIX)时可能遇到的不透明类型。
typedef struct person_s person_s;
person_s * person_new(const char * name, size_t len);
const char * person_name(const person_s * person);
float person_wage_get(const person_s * person);
void person_wage_set(person_s * person, float wage);
// ...
void person_free(person_s * person);
注释:
避免使用指针typedef
。它只会使开发人员感到困惑。
最好将指针保持明确,这样所有开发人员都可以知道他们使用的类型是动态分配的。
编辑:此外,通过避免“类型定义”指针类型,API承诺将来的/替代实现也将在其API中使用指针,从而使开发人员可以信任并依赖此行为(请参见注释)。
使用不透明类型时,可以避免使用NAME_MAX_LEN
,允许使用任意长度的名称(假设重命名需要一个新对象)。这是偏爱不透明指针方法的一个额外诱因。
尽可能避免将_
放在标识符的开头(即_name
)。以_
开头的名称被认为具有特殊含义,并且其中一些保留。对于以_t
结尾的类型(POSIX保留)也是如此。
请注意,我如何使用_s
将类型标记为结构,而不使用_t
(保留)。
C更经常是snake_case(至少从历史上来说)。最为人所知的API和大多数C标准是snake_case(从C ++导入的东西除外)。
此外,保持一致更好。当开发人员尝试记住您的API时,在某些情况下使用CamelCase(或smallCamelCase)在将snake_case用于其他事情时会造成混淆。
答案 4 :(得分:0)
John Bollinger写的是一种利用结构和内存工作方式的巧妙方法,但是它也是获取段错误的一种简便方法(想象一下分配Person
的数组,然后再传递最后一个元素更改为访问ID或名称的“方法”,或破坏您的数据(在Person
数组中,下一个Person
将覆盖前一个Person
的“私有”变量) 。您必须记住,必须创建指向Person
的指针数组,而不是Person
的数组(听起来很明显,直到您决定优化某些内容并认为可以分配和初始化结构更多为止)比初始化函数更有效)。
不要误会我的意思,这是解决问题的好方法,但是在使用时必须小心。
我建议(尽管每个Person
使用4/8字节以上的内存)是创建一个结构Person
,该结构具有指向另一个仅在.c文件中定义并保存私人数据。这样一来,在某个地方犯错误就更困难了(如果这是一个更大的项目,那就相信我-您迟早会这样做的。)
.h文件:
#pragma once
#define NAME_MAX_LEN 20
typedef struct _person {
float wage;
int groupid;
__personPriv *const priv;
} Person;
void personInit(Person *p, const char *name);
Person* personNew(const char *name);
const char const *getName (Person *p);
int getId (Person *p);
.c文件:
typedef struct {
int id;
char name[NAME_MAX_LEN];
} __personPriv;
const char const *getName (Person *p) {
return p->priv->name;
}
int getId (Person *p) {
return p->priv->id;
}
__personPriv* __personPrivNew(const char *name) {
__personPriv *ret = memcpy(
malloc(sizeof(*ret->priv)),
&(__personPriv) {
.id = generateId();
},
sizeof(*ret->priv)
);
// if(strlen(name) >= NAME_MAX_LEN) {
// raise an error or something?
// return NULL;
// }
strncpy(ret->name, name, strlen(name));
return ret;
}
void personInit(Person *p, const char *name) {
if(p == NULL)
return;
p->priv = memcpy(
malloc(sizeof(*p->priv)),
&(__personPriv) {
.id = generateId();
},
sizeof(*p->priv)
);
ret->priv = __personPrivNew(name);
if(ret->priv == NULL) {
// raise an error or something
}
}
Person* personNew(const char *name) {
Person *ret = malloc(sizeof(*ret));
ret->priv = __personPrivNew(name);
if(ret->priv == NULL) {
free(ret);
return NULL;
}
return ret;
}
侧面说明:可以实施此版本,以便在结构的“公共”部分之后/之前分配私有块,以改善局部性。只需分配sizeof(Person) + sizeof(__personPriv)
并将一部分初始化为Person
,将第二部分初始化为__personPriv
。