c-在初始化指向结构的指针时选择正确的模式



我很难确定这两者之间的区别结构初始化。

1.

a_struct_t *create_a_struct(int a, char *b);
void destroy_a_struct(a_struct_t *a_struct);
typedef struct {
int a;
char *b;
} a_struct_t;
int main()
{
a_struct_t *a_struct = create_a_struct(1, "FOO");
destroy_a_struct(a_struct);
return 0;
}
a_struct_t *create_a_struct(int a, char *b)
{
a_struct_t *a_struct = malloc(sizeof(*a_struct));
a_struct->a = a;
a_struct->b = b;
return a_struct;
}
void destroy_a_struct(a_struct_t *a_struct)
{
free(a_struct);
}
void a_struct_init(a_struct_t *a_struct, int a, char *b);
typedef struct {
int a;
char *b;
} a_struct_t;
int main()
{
// from heap
a_struct_t *a_struct = malloc(sizeof(*a_struct));
a_struct_init(a_struct, 1, "FOO");
free(a_struct);
// from main stack
a_struct_t a_struct;
a_struct_init(&a_struct, 1, "FOO");

return 0;
}
void a_struct_init(a_struct_t *a_struct, int a, char *b)
{
a_struct->a = a;
a_struct->b = b;
}

我见过很多c代码同时使用这两种模式。我的问题是什么是最好的模式使用两种模式初始化结构时他们之间有什么优点和缺点吗?

注意:在第二个主函数中,我使用了两个初始化一个来自堆,一个来自堆栈。

第一种方法(返回指向新对象的指针的工厂方法)更符合OO设计,将创建和初始化相结合,这样一个对象在没有正确初始化的情况下就无法创建,并且任何对象都不能初始化两次。

第二种方法比较老派,只是将指针传递给init方法。如果您希望对同一类型的对象使用不同的init方法,或者如果您希望能够重新初始化对象,这将更加灵活。

选择取决于你想要完成什么以及你想要如何传达你的意图。

如果您想将字符串存储在结构中(而不仅仅是对它的引用),我宁愿:

typedef struct 
{
int a;
char b[];
} a_struct_t;

a_struct_t *a_struct_init(a_struct_t *a_struct, int a, char *b)
{
if(!a_struct)
{
a_struct = malloc(sizeof(*a_struct) + strlen(b) + 1);
}
if(a_struct)
{
a_struct->a = a;
strcpy(a_struct->b, b);
}
return a_struct;
}

我很难确定这两种结构初始化之间的区别

最简单地说,(无论如何,在感兴趣的部分中)的区别是,一个构造返回struct *,另一个输出struct *。两者都是有效、有用的方法,如果使用得当,它们都是完美的。返回struct *的方法对于(但不限于)在堆上创建结构的实例非常有用。输出struct *的方法更常用于(但不限于)更新现有struct.的内容(无论是在堆上还是在堆栈上创建)

在您的示例中,所示的两种方法都不能像编写的那样工作。即在两者中的任何一个安全可用之前都需要对两者进行修改。而且,如果修改,两者都可以变得同样有用,这取决于编码器的意图。

还要注意,a_struct_t需要分配内存的唯一部分(至少对于第二种方法)是成员char *b;,但在您的两个示例中都没有分配。因此,初始化任一示例都需要分配char *b

第一个示例要求返回一个指针,为了使该指针在返回之前能够存储值,还要求为其分配内存。然后,与第二示例中一样,成员char *b也需要额外的存储器分配。

以下示例涵盖两个示例。证明每种方法都同样有效(IMO),仅根据您的风格、应用程序需求等确定使用哪种方法:

typedef struct {
int a;
char *b;
} a_struct_t;
//method 1
a_struct_t * a_struct_init_B(int a, char *b);
//method 2
void a_struct_init(a_struct_t *a_struct, int a, char *b);
int main(void )
{
// created on stack
a_struct_t a_struct;    

//method 2
a_struct_init(&a_struct, 1, "FOO");
free( a_struct.b);

//method 1
a_struct_t *pA_struct = a_struct_init_B(2, "Foo2");
if(pA_struct)
{
//use it here, then free
free(pA_struct->b);
free(pA_struct);
}
}
void a_struct_init(a_struct_t *a_struct, int a, char *b)
{
a_struct->a = a;
a_struct->b = malloc(strlen(b)+1);//+1 room for nul terminator
if(a_struct->b)
{
strcpy(a_struct->b, b);
}
}
a_struct_t * a_struct_init_B(int a, char *b)
{
a_struct_t *pA_struct = malloc(sizeof(*pA_struct));
if(pA_struct)
{
pA_struct->a = a;
pA_struct->b = malloc(strlen(b)+1);//+1 room for nul terminator
if(pA_struct->b)
{
strcpy(pA_struct->b, b);
}
}
return pA_struct;
}

如果您只对动态分配的单个结构感兴趣,那么具有组合分配/初始化功能的第一种选择至少具有以下优势:

  1. 使用起来更简单、更清洁
  2. 它可以与不透明类型一起使用

然而,除此之外,第二种选择具有专用的仅初始化功能,具有一些重要优势:

  1. 它可以用于具有自动或静态存储持续时间的对象
  2. 它可以迭代地用于准备结构阵列的元素

此外,请注意两者并不互斥。提供这两个函数是完全合理的,特别是如果分配函数依赖于仅初始化函数来设置对象的内容。即使您提供的是不透明类型,也是如此。

此外,一定要明白,对拆卸函数的需求与您设置实例的方法无关。如果你所需要做的只是释放结构(并且你愿意继续致力于此),那么你就不需要这样的函数。另一方面,如果还需要对成员进行某种清理,那么无论采用哪种方法来设置实例,拆卸函数都是合适的。细节,例如函数是否真的释放了结构,可能会在两种选择之间有所不同,但拆卸函数是否合适是它自己的考虑因素。

您的示例没有提出的一些要点:

如果您想从定义结构的地方返回结构,则不能使用第二个版本,因为返回局部变量的地址是UB。由于在你的例子中,一切都发生在主要的情况下,所以这个问题就不会出现。

如果你可能有一个结构数组,那么虽然你可以在堆上或"堆栈上"分配数组,但你需要以某种方式初始化数组元素,所以我认为最好总是有单独的分配和初始化例程。

如果你的结构本身已经分配了指针,那么你需要有一个例程来释放这些指针。同样,为了处理数组元素,我认为最好有一个"empty_a_struct"例程和一个"destroy_a_struct'"(我称之为"free_a_struct")。

最新更新