假设我在 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 结构体与 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 *
作为类型,则您可能会意外地在需要组的地方传递一个人,而编译器将无法检测到它。使用不完整的类型,编译器会捕获错误。