作为有Java背景的人,如何在C中正确实现数据结构(主要是指针)。例如。正确创建构造函数?

问题描述 投票:0回答:1

假设我在 C 中有一个“类”(结构体),如下所示:

typedef struct{
    int age;
}
Person;

我已经看到了如何实施的各种可能性,例如构造函数(但也链接到其他结构等),我不确定应该使用哪一个以及何时使用。第一个想到的是:

Person newPerson(int age){
    Person person;
    person.age = age;
    return person;
}

Person person = newPerson(4);

这与 Java 中的工作方式相同...但我经常在网上看到不同的东西

Person* /*why?*/ newPerson(int age){
    Person person;
    person.age = age;
    return &person; //why?
}

Person* person = newPerson(4);
//also, why not just: Person person = *newPerson(4);

我知道如何使用它,但我不确定为什么我会返回指向结构的指针而不仅仅是结构本身。有些网站甚至使用 malloc:

Person* newPerson(int age){
    Person* person = (Person*)malloc(sizeof(Person)); //why?
    person->age = age;
    return person;
}

Person* person = newPerson(4);

再说一遍,我仍然知道如何使用所有这些其他方法,我只是不明白为什么我要这样做?第一种方法似乎更容易理解和实现,所以一定还有其他原因为什么我想使用方法2或3(或类似的)来代替?如果是这样的话,有人可以解释其中一些原因吗?

编辑:如果您知道一个网站(或搜索词)可以很好地解释这一点,我也可以使用该网站的链接而不是答案

c oop pointers struct constructor
1个回答
0
投票

为什么我要返回指针而不是结构本身?

不要将 C 结构体与 Java 对象混淆。 Java 对象隐式表示为指针,即使您在代码中看不到这一点,但 C 结构体不是:它是结构体所有字段的全部内容。如果将返回的结构分配给变量,则所有字段都会被复制。如果将该变量分配给相同类型的另一个变量,则所有字段都会被复制,这与 Java 不同,在 Java 中,两个变量都引用相同的内存和相同的字段实例。

正如@wohlstad 所评论的,此选项有效,但会导致 Person 在返回时被复制:

Person newPerson(int age){
    Person person;
    person.age = age;
    return person;
}

Person person = newPerson(4);

是否要复制一个“对象”,在C和Java中是类似的;如果可以复制对象,那么这很好,特别是对于小型结构。但通常,您希望对象的标识在其整个生命周期中保持不变,在这种情况下,您希望传递指向该对象的指针而不是复制该对象。

第二种选择是错误且危险的:

Person* newPerson(int age){
    Person person;
    person.age = age;
    return &person;
}

Person* person1 = newPerson(4);

这是错误的,因为

person
是在堆栈上分配的,它在从
newPerson()
返回时弹出,并且堆栈空间在后续代码中出于不同目的而重用,例如调用其他函数时。但与此同时,变量
person1
仍然指向被重用于其他目的的内存,并且会导致不好的结果。

第三个选项是规范选择:

Person* newPerson(int age){
    Person* person = (Person*)malloc(sizeof(Person)); //why?
    person->age = age;
    return person;
}

Person* person1 = newPerson(4);

malloc()
为新对象分配内存。在 Java 中,您不需要这样做,该语言向您隐藏了该细节,但 C 则不会。这个新分配的内存将保持分配状态,直到您使用
free()
释放它,因此除了
newPerson()
之外,您还需要定义一个
deletePerson()
函数,并且用户代码需要显式调用它。

要编写提供类型安全性并隐藏调用代码细节的结构良好的代码,您可以在一个 C 模块中实现

Person
,并使用定义接口的关联包含文件,并使用 C“不完整类型”:

person.h

struct person_s;  // Note: we could use "Person" instead of "person_s" here and below
typedef Person *person_s;

Person newPerson(int age);
int personAge(Person);
void deletePerson(Person);

person.c

#include "person.h"
struct person_s {
    int age;
}

// hopefully, the rest of the implementation code is obvious ...

用户代码:

#include "person.h"
...
void doPersonStuff() {
    person1 = newPerson(4);
    ... // do stuff with person1
    person2 = newPerson(6);
    ... // etc
    deletePerson(person1);
    deletePerson(person2);
}

请注意,用户代码不知道结构中的内容,并且无法使用

sizeof(person_s)
。所有的细节都被隐藏了。许多程序员使用
void *
来实现此目的,但这是一个坏习惯,因为很容易混淆用于不同目的的不同
void *
变量。例如,如果您有一个
person
API 和一个
group
API,并且都返回
void *
作为类型,则您可能会意外地在需要组的地方传递一个人,而编译器将无法检测到它。使用不完整的类型,编译器会捕获错误。

© www.soinside.com 2019 - 2024. All rights reserved.