4

我有一个关于在 C 中封装我的模块(一个 .c/.h 文件对)的问题。

人们通常使用 getTime(char * time) 之类的访问函数来访问模块的内部变量,而不会提供干扰它们的能力。

现在,我正在使用指向常量数据的常量指针,指向内部结构来做同样的事情。我认为这很有效,因为其他模块可以查看结构中的数据而无法更改它,并且它节省了函数调用的开销。(这是用于嵌入的东西,所以函数调用是“昂贵的”)。

我只是想对这是否是有效的封装等提出一些意见。我想他们可以设置一个指向它的指针,它不是常量,然后他们可能会弄乱数据?一个例子:blah.h

typedef struct {
    unsigned char data;
    unsigned int intdata;
} myStruct;

extern const myStruct * const ptrConstMyStruct;

废话.c

static myStruct thisIsMyModulesData;
const myStruct * const ptrConstMyStruct = &thisIsMyModulesData;

另一个文件.c

variable = ptrConstMyStruct->data;
ptrConstMyStruct->data = variable; //compile error!
4

4 回答 4

2

使用不完整类型并在头文件中前向声明结构是首选方式。

就封装而言,常量更多是一种声明特定函数不会改变给定对象的方式。在您的解决方案中,您将不得不抛弃似乎违反直觉的 constness。或者你永远不会使用 const 指针作为变异函数的参数?

此外,封装是关于隐藏实现和细节,如果您公开作为实现的结构,则无法做到这一点。

编辑以解释前向声明:

我的代码.h:

struct my_struct;

我的代码.c:

struct my_struct { .... };

以上意味着 MyCode 的用户将能够使用指向 my_struct 的指针,但不能检查其成员。

于 2012-05-15T16:55:21.340 回答
1

封装允许您更改内部机制,并防止用户更改您的内部数据。一种方法是使用 getter 和 setter 函数,它们既封装又允许你做你的 const 事情。

于 2012-05-15T17:28:50.087 回答
1

您的问题中有一些陈述可能表明问题的真正根源。

我认为这很有效,因为其他模块可以查看数据......

如果其他模块可以查看数据,则不进行封装。如果其他模块需要查看(原始)数据,那么您为什么要尝试将其设为私有?这暗示了程序设计中的一些基本缺陷。

外部

您永远不需要在 C 语言中使用全局变量(也许 MCU 硬件外围寄存器除外)。在过去的 10 年里,我没有使用过全局变量,而且我几乎只使用嵌入式实时系统。同样,这表明程序设计中有问题。

...它节省了函数调用的开销。(这是用于嵌入的东西,所以函数调用是“昂贵的”)。

不,不是。C 语言支持函数内联已经 13 年了,即使你有一个旧的 C90 编译器,我敢打赌它也有一个内联选项,#pragma inline或者类似的。我还没有看到缺少内联的嵌入式编译器。最重要的是,如果您只启用优化,即使是旧的编译器也可以在没有程序员任何明确提示的情况下执行内联。

此外,函数调用开销是否是程序中的瓶颈,这是您通过使用示波器进行基准测试/测量发现的?如果不是,那你为什么要用全局变量和暴露私有数据的奇怪指针来混淆你的代码?那是过早的优化。

于 2012-05-15T19:49:54.887 回答
0

除非您能够在结构的“用户”读取它的位置和数据所在的位置之间注入代码,否则它不是封装。这样的垫片允许您在不改变结构的外部使用的情况下更改结构的内部结构。

虽然您的解决方案是对通常在 C 中完成的方式的改进,但为了封装它,您需要能够将字段从单个值更改为构造值,而无需更改任何“外部”代码来证明它是真的封装了。

在 C 中,这通常是通过将数据隐藏在 void 指针后面,或者在封装代码的外部使用声明的(但未定义的)结构来执行的。

废话

struct myStruct_t;
typedef struct myStruct_t myStruct;

extern myStruct * const ptrConstMyStruct;

// if you want a constructor, you need to declare the
// method here, because only blah.c will know the struct
// size with this solution.
myStruct * new_myStruct();

// methods
myStruct_setData(myStruct* obj, char c);
char myStruct_getData(myStruct* obj);

废话.c

#include "blah.h"

struct myStruct_t {
    unsigned char data;
    unsigned int intdata;
};

static myStruct thisIsMyModulesData;

// no need to make the struct const, just the pointer
// otherwise, we would have to break the const just to change
// data, which now will only be done within this file.
myStruct * const ptrConstMyStruct = &thisIsMyModulesData;

另一个文件.c

#include "blah.h"
// anotherFile "knows" of a struct called myStruct, but
// it doesn't know what it contains, or even it's size.

// this is no longer possible
// now "data" is encapsulated, and can be changed
// by reimplementing `myStruct_getData(...)`
// (as long as we don't change the method signature).
variable = ptrConstMyStruct->data;

// this is the "new" way
variable = myStruct_getData(ptrConstmyStruct);

// with the old, compiler error because of 
// assigning a value to a const value.
ptrConstMyStruct->data = variable; //compile error!
                       ^
              (error occurs here)

// with the new, compiler error because of
// dereferencing a pointer to a unknown / incomplete type.
ptrConstMyStruct->data = variable; // compile error!
                ^
        (error occurs here)

如您所见,错误的位置决定了封装与否。ptrConstMyStruct->data如果您在分配而不是取消引用时检查错误,则无法更改指针和数据之间的关系。

于 2012-05-15T17:08:29.347 回答