在C中,如果结构变量内部有指针,它是不是需要指针?

Posted

技术标签:

【中文标题】在C中,如果结构变量内部有指针,它是不是需要指针?【英文标题】:In C, does struct variable needs to be pointer if there is a pointer inside it?在C中,如果结构变量内部有指针,它是否需要指针? 【发布时间】:2022-01-11 03:36:45 【问题描述】:

在我的代码中我有这个(由练习给出):

typedef struct student
    int id;
    float *grades;
    char name[100];
Student;

Student* fillStudent();

我的问题是:为什么函数“fillStudent()”返回一个Student类型的指针?是不是因为里面有等级指针?最初我认为这是因为您应该返回一个包含各种学生的向量,但这没有意义,因为另一个结构:

typedef struct subject
    Student *V;
    float average[5];
    int nStudents; 
Subject;

该练习要求您调用函数“fillStudent()”nStudents 次来填充向量 V,因此在一次调用中返回所有学生是没有意义的。 那么为什么 fillStudent() 需要返回一个指针呢?难道它不能简单地是一个 Student 类型的变量,并在成绩上做一个 malloc 吗?如果是这样,那么变量到底会被认为是指针吗?

【问题讨论】:

这个练习似乎是由不太熟悉 C 编程的人创建的,因此很难说出他们的想法。 没有技术原因返回一个指针。也许吧,但这只是一个猜测,它旨在提醒用户该结构是动态分配的,使用后需要free()d。但是,仅从函数签名中无法判断,也无法判断需要如何处理 V 指针。查看函数文档。如果没有,代码不好,不应该作为学习的例子。 是的,这个练习看起来很奇怪,还有很多其他原因。谢谢你们俩! 在 1970 年代和 1980 年代初期,一些编译器不支持按值返回结构;所以返回指针的版本是标准做法。而在 C 语言中,你会发现旧编码器和旧代码存在很多“惯性”;许多人仍然使用 malloc(另一个 ANSI 之前的习惯),并且许多人不知道 22 年前添加的数组类型的更改等。 【参考方案1】:

我想首先解决您在推测推理时提出的一些子问题。

问题一,函数fillStudent()返回一个Student类型的指针是因为里面有一个成绩指针吗?

答:不。一个结构[通常]可以在其字段中包含任何类型的变量,而不必位于指针变量本身中,并且是正确的 C 代码。

Q2:为什么fillStudent()需要返回一个指针?难道不能简单地是一个Student类型的变量,并在成绩上调用malloc吗?

它绝对可以返回一个Student 结构,它的grades 字段用malloc 初始化。 (尽管正如@Zeppe 指出的那样,如果您不知道数组有多长,这样做可能会出现问题)。这看起来像这样,

#include <string.h>

Student createStudent(char* name, id, int num_of_grades) 
    Student s;
    s.id = id;
    //since s.name is a fixed length array in the struct, memory was reserved
    //for it when we declared the variable s. 
    strcpy(s.name, name)
    //s.grades is a pointer to memory on the heap, so we have to 
    //allocate with something like malloc or calloc
    s.grades = malloc(num_of_grades * sizeof(float));
    
    return s; //perfectly okay to return a struct from a function.

Q3:Q2中函数返回的变量算指针吗?

答:没有。我们返回了一个结构而不是一个指针。返回指针的函数看起来像这样,

Student * foo() 
    Student * s = malloc(sizeof(Student));

    //code to initialize/do something with s
   
    //s is a pointer to a Student structure, so by returning s, we are returning
    //a pointer to a Student structure
    return s;

总而言之,鉴于您描述的设置,函数 必须 签名返回一个指针,这甚至可能是一个错误!但是按照他们的方式执行它并没有错,并且根据其余代码的外观,可能会有一些优势。

第一个是平凡的。如果您的所有其余代码都在处理Student*而不是Student,那么让您用来构造它们的函数为您提供Student*而不是Student可能会感觉更方便的。你可以对这是否是一个很好的理由有意见,但我猜测你的情况最有可能是这样写的。

下一个原因有更明显的优势。如果创建Student 可能会失败(比如他们可能没有注册足够的课程或其他东西),那么您希望能够向程序的其余部分表明这一点。如果你只是返回一个结构,那么你通常无法从返回值中知道它是否有效。你可以这样做

#include <stdbool.h>
//value of is_error gets set to true if there was an error, and false otherwise
Student createsStudent(char * name, /* other fields */, bool * is_error)

可以像这样使用

bool is_error;
Student s = createsStudent(/* ... */, &is_error)
if(is_error) 
    //respond to error


这很好用,但你最终会得到很多额外的 is_error 变量,这可能会有点烦人/分散注意力。相反,如果创建Student 的方法返回一个指向Student 的指针,那么您可以检查它是否为空,并使用它来判断是否有错误。

最后,如果您有一堆修改Student 的函数,您可能希望在同一个变量上一个接一个地调用它们。但如果它们是这样写的,

Student createStudent(/*...*?);
void foo(Student* s);
void bar(Student* s);
void baz(Student* s);

你必须这样称呼他们

Student s = createStudent(/*...*/);
foo(&s);
bar(&s);
baz(&s);

但是如果你写这个,函数返回指向其参数的指针(它们只是返回 s),

Student * createStudent(/*...*/?);
//returns s
Student * foo(Student* s);
//returns s
Student * bar(Student* s);
//returns s
Student * baz(Student* s);

你可以这样做,

Student * s = baz( bar( foo( createStudent(/*...*/) ) ) );

这是一件小事,但可以非常好。

总而言之,这两种方法都没有对错。

【讨论】:

很棒的答案,你和 Zeppe 让我对这个概念有了更多的理解。但老实说,我只是认为这是练习的错误。过去有一些像这样奇怪的东西的练习(我在大学上 C 课),所以我想这只是其中的另一个......感谢您花时间解释它:) 您可能是对的,如果这是一个错误,我不会感到惊讶。但是有正当理由返回指针而不是返回普通结构,我想说明这些。特别是,标准库大量使用了错误返回空值和函数链接模式,很高兴知道为什么要这样写。没问题:)【参考方案2】:

我只能告诉你,你的推理是有道理的。首先,一个名为fillStudent 的函数应该“填充”已经存在的东西。因此,可能的签名是:

void fillStudent(Student *dest);

这将是理想的,因为您会预先分配 nStudents,然后调用函数 fillStudent nStudents 次。

另外,结构 Student 不是很有用,因为你有一个指针 grades 但你没有变量来知道那里存储了多少成绩(所以我猜你不能循环遍历它们某些类型的约定,例如“最后一个年级是-1”,这是一种不当行为。

对于您的问题,C 中按值返回 struct 没有问题,即使它们里面有一个向量。

【讨论】:

以上是关于在C中,如果结构变量内部有指针,它是不是需要指针?的主要内容,如果未能解决你的问题,请参考以下文章

C结构指针从结构到结构数组

混合 C/C++ 代码中的结构指针

c语音指针问题:Pnumn、*Pnum、num分别指啥?

C 语言结构体 ( 结构体深拷贝 )

C 语言结构体 ( 结构体浅拷贝 )

C语言中如何交换两个指针变量的的值