在C ++和Java中,数据结构可以包含private
,public
和protected
区域。我想将这个概念移植到我正在编写的C语言程序中。
是否有任何习惯用法在C struct
中实现私有或受保护的函数指针和数据字段?
我知道C struct
是公开的,我正在寻找一个成语来帮助隐藏一些实现细节并强迫用户使用公共接口。
注意:该商店选择了该语言,因此我无法将面向对象的概念应用到C中。
感谢。
答案 0 :(得分:26)
如你所知,你不能这样做。然而,有些习语会产生类似的效果。
C将允许你做类似于面向对象设计中所谓的“pimpl”习语。您的结构可以有一个指向另一个前向声明的结构的不透明指针,该结构充当结构的私有数据。在struct上运行的函数代替了成员函数,可以拥有私有成员的完整定义,并且可以使用它,而代码的其他部分则不能。例如:
在标题中,foo.h:
struct FooPrivate;
struct Foo {
/* public: */
int x;
double y;
/* private: */
struct FooPrivate* p;
};
extern struct Foo* Foo_Create(); /* "constructor" */
extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */
在实现中,foo.c:
struct FooPrivate {
int z;
};
struct Foo* Foo_Create()
{
struct Foo* foo = malloc(sizeof(Foo));
foo->p = malloc(sizeof(FooPrivate));
foo->x = 0;
foo->y = 0;
foo->p->z = 0;
return foo;
}
void Foo_DoWhatever(struct Foo* foo)
{
foo->p->z = 4; /* Can access "private" parts of foo */
}
在一个程序中:
#include "foo.h"
int main()
{
struct Foo* foo = Foo_Create();
foo->x = 100; /* Can access "public" parts of foo */
foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */
Foo_DoWhatever(foo); /* Can call "member" function */
return 0;
}
请注意,需要使用“构造函数”函数来为私有数据分配内存。显然,你需要将它与一个特殊的“析构函数”函数配对,以便正确地释放私有数据。
或者,或者,如果您希望您的结构没有任何公共字段,您可以使整个结构不透明,并且只需要标题
struct Foo;
extern struct Foo* Foo_Create(); /* "constructor" */
extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */
foo.c中struct Foo
的实际定义,以及您希望直接访问的任何属性的getter和setter函数。
答案 1 :(得分:9)
有时在C中使用的概念是
// lib.h
typedef struct {
int publicInt;
//...
char * publicStr;
} Public;
Public * getPublic();
int function(Public * public);
// lib.c
typedef struct {
Public public;
int privateInt;
// ...
char * privateStr
} Private;
static Private * getPrivate();
Public * getPublic() { return (Public*) getPrivate(); }
int function(Public * public) {
Private * private = (Private *) public;
// ...
}
这使用标准技巧,指向结构的指针可以与指向结构中第一个元素的指针互换。
如果您希望所有您的字段为私有字段,则更容易:
// lib2.h
typedef struct AllPrivate * Handle;
Handle getHandle();
int function2(Handle handle);
// lib2.c
struct AllPrivate { /* ... */ }
#include
lib2.h不会抱怨的文件,因为我们只使用struct AllPrivate *
,并且所有指针的大小都相同,所以编译器不需要知道{{的内部结构。 1}}。
要做一个受保护的区域,您只需要定义
struct AllPrivate
然后,只需要确保// include/public.h
struct Public { /* ... */ }
struct Public * getPublic();
int somePublicFunction(struct Public *);
// dev/include/protected.h
struct Protected { struct Public public; /* ... */ }
struct Protected * getProtected();
int someProtectedFunction(struct Protected *);
// dev/src/private.c
struct Private { struct Protected protected; /* ... * /}
struct Public * getPublic() { return (struct Public *) getPrivate(); }
struct Public * getProtected() { return (struct Protected *) getPrivate(); }
int somePublicFunction(struct Public * public) {
struct Private private = (struct Private *) public;
// ...
}
int someProtectedFunction(struct Protected * protected) {
struct Private private = (struct Private *) protected;
// ...
}
不被传递。
答案 2 :(得分:2)
对于数据字段 - 只是不要使用它们。你可以做一些技巧,比如给他们疯狂的名字以阻止他们的使用,但这不会阻止人们。唯一真正的方法是创建另一个私有结构,由库函数通过void指针访问。
对于私有函数 - 使用文件static
函数。将所有库函数放在一个C文件中,并声明要将其作为static
私有的函数,并且不要将它们放在任何头文件中。
答案 3 :(得分:1)
通常,按照惯例,私有成员的名称中会有一个额外的下划线,或者附加_pri
之类的内容。或者可能是评论。此技术不执行编译器强制检查以确保没有人不恰当地访问这些字段,但是对于阅读struct
声明内容是实现细节的任何人都是一个警告,他们不应该偷看或戳它们。
另一种常见技术是将结构暴露为不完整类型。例如,在头文件中,您可能有:
struct my_struct;
void some_function(struct my_struct *);
在实现中,或者库的消费者无法访问的某些内部标题,您有:
struct my_struct
{
/* Members of that struct */
};
你也可以使用void指针做类似的技巧,这些技巧会被转换到代码的“私有”部分中的正确位置。这种方法失去了一些灵活性(例如,你不能有一个未定义类型的堆栈分配实例),但这可能是可以接受的。
如果您希望混合使用私有成员和公共成员,则可以执行与上面相同的操作,但将私有结构指针存储为公共成员的成员,并使其在库的公共使用者中保持不完整。
虽然,这引入了一些间接性,这可能会损害性能。有一些(通常是非便携式的,但可以在合理的编译器上工作)类型 - 惩罚技巧你也可以使用:
struct public_struct
{
int public_member;
int public_member2;
/* etc.. */
};
struct private_struct
{
struct public_struct base_members;
int private_member1;
int private_member2;
};
void some_function(struct public_struct *obj)
{
/* Hack alert! */
struct private_struct *private = (struct private_struct*)obj;
}
这也假定您不能将这些对象存储在堆栈或静态存储中,或者在编译时获取大小。
答案 4 :(得分:0)
我为你感到难过,因为将不同的OO概念混合到C中往往就像一个圆孔中的方形钉。话虽如此,GObject
支持公共和私人成员,但它是我在地球上最不喜欢的架构之一。如果你不关心轻微的性能损失,你可以做一个更简单的解决方案 - 有一个充满私有成员的辅助结构,并从主(公共)结构有一个匿名指向该结构。