首先,为了使用“免费”功能,我们还需要包含 stdlib.h:
#include <stdlib.h>
当您将结构作为参数传递给函数时,您不需要指定该结构中的元素。当您在顶部将结构定义为:
struct student
{
char * name;
char * course;
};
这是你告诉编译器它需要知道的关于该结构的一切。所以在你的 getStudentInfo 原型中,你只需要这样做:
void getStudentInfo(struct student *pStu);
现在,让我们开始你的主要功能。
目前,您没有在堆上分配学生对象。
struct student springClass[5];
此代码在堆栈上为数组中的 5 个学生结构腾出空间。如果您希望这些对象位于堆中,则必须使用 malloc 动态分配它们,就像您对用户输入所做的那样:
struct student *springClass = malloc ( 5 * sizeof(struct student) );
如果您事先知道会有多少学生,最好还是坚持目前的方法。但是,动态分配通常适用于您不知道具体数量的情况。
注意:“动态分配”是指在堆中“动态”分配空间,或者在程序运行时分配空间。它是动态的,因为它可以随时更改。
无论如何,在将数据放入所有 5 个结构的循环中,我们将 getStudentInfo 函数重新格式化为仅接受“结构学生指针”作为参数。这是我们当前要添加的学生所在的内存位置。在 C 语言中,您可以通过在其前面加上“&”或 & 符号来检索某物的地址。我喜欢通过认为“地址”以 A 开头来记住这一点,就像“与号”一样。这不是一个很强的比较,但它是我所拥有的最好的:)
因为我们想要一个指向当前学生地址的指针,所以我们传入“&springClass[i]”。另外,因为我们已经告诉编译器 getStudentInfo 接收什么,我们不需要再告诉它,我们可以简单地给它一个值。总而言之,在循环内部,我们这样调用:
getStudentInfo( &springClass[i] );
如果您想了解有关指针以及 * 和 & 符号的更多信息,this site 提供了很好的解释。
现在,在循环之后我们还调用了 printInfo。与前面的 getStudentInfo 调用类似,这里我们不需要告诉函数我们要给它什么样的东西,所以不需要指定“struct student”:
printInfo(springClass);
最后,释放我们之前分配的内存。请注意,如果您不使用“malloc”函数分配内存,而是使用“int array[10];”,例如,此内存将在堆栈上。当你退出一个函数时,堆栈上的内存会被自动释放,所以你不必担心显式释放它。
注意:如果您尝试在该函数之外使用它,自动释放的内存也会导致问题。例如,如果您在堆栈上分配一些内存,然后返回指向该内存的指针,您将得到分段错误。这样做的原因是当您返回时您的内存被自动释放(这称为“超出范围”)。因此,该指针现在将指向不再有效的内存。
然而,如果你决定在堆上分配你的 springClass 列表,你将需要使用“free”函数。但是,我们不能告诉它释放“p1”,因为这是我们在 getStudentInfo 函数中定义的。内存以及变量名都受它们在内部定义的“范围”的约束。这意味着我们可以在 GetStudentInfo 中使用变量“p1”,但是如果我们尝试在其他任何地方使用它,编译器将不知道我们在说什么。相反,我们需要释放整个数组,例如:
free( springClass );
但是等等,还有更多!
由于我们 malloc 了这个数组中每个结构的所有元素,我们还需要进入其中并单独释放所有这些元素。
重要提示:free() 不是递归的!
所以让我们再次遍历列表,这次释放东西,直到最终释放整个列表:
for ( int i = 0; i < 5; i++ )
{
free( springClass[i].name );
free( springClass[i].course );
}
free( springClass );
唷,好吧,进入下一个功能。但是,我想快速指出,在编程中,孤立地查看函数通常是一种好习惯。
“这个函数的参数是什么?”
“这个函数需要返回什么?”
“我们如何使用给定的参数做到这一点?”
如果您单独查看每个功能,它可以帮助您避免不知所措。这也是为什么人们喜欢提倡让你的函数保持较小的部分原因:长函数不仅难以阅读,而且会让人感到困惑,并且在以后调试时变得很痛苦。这也是为什么我一次看完了你的整个 main 函数,而不是在你调用它们时转到其他函数。
无论如何,我们差不多完成了。最后两个功能只需要最少的修复。首先,这个开始类似于我们如何调整原型:
void getStudentInfo(struct student *pStu) {
...
}
而且,由于我们已经分配了这个学生对象,无论是在堆栈中还是在堆中都无关紧要,我们只需通过给定的指针访问它的数据成员。但是,我们仍然需要为用户将要输入的字符串分配空间。所以,我们这样做:
pStu->name = malloc( 31 * sizeof( char ) );
pStu->class = malloc( 31 * sizeof( char ) );
您可以同时分配两者,但我不建议这样做。一旦你学会了如何实际使用指针和动态内存,你就可以学习如何优化它的使用。
最终函数变为:
void getStudentInfo(struct student *pStu)
{
// Allocate memory on the stack
pStu->name = malloc( 31 * sizeof(char) );
pStu->class = malloc( 31 * sizeof(char) );
// Get input from user
printf("Enter name: ");
fgets( pStu->name, 31, stdin);
printf("Enter course: ");
fgets( pStu->course, 31, stdin);
}
除了一个小错字(使用“int i=;”而不是“int i = 0;”),我认为您的最终功能没有任何问题。虽然有更好的写法,但我觉得入门并不重要,因为现在你应该专注于理解你写的所有内容。
这是我的完整代码。请仅将其用作参考,重要的是自己动手并了解发生了什么。
#include <stdio.h>
#include <stdlib.h>
struct student
{
char * name;
char * course;
};
//function prototypes
void getStudentInfo(struct student *pStu);
void printInfo(struct student springClass[]);
int main(){
//create an array of 5 students
struct student* springClass = malloc(5 * sizeof(struct student));
for(int i=0; i < 5; i++)
{
getStudentInfo(&springClass[i]);
}
printInfo(springClass);
//free memory
for ( int i = 0; i < 5; i++ )
{
free( springClass[i].name );
free( springClass[i].course );
}
free( springClass );
return 0;
}
// function that gets input from the user and saves the input in the elements of the struct on the heap
void getStudentInfo(struct student *pStu)
{
// Allocate memory on the stack
pStu->name = malloc( 31 * sizeof(char) );
pStu->course = malloc( 31 * sizeof(char) );
// Get input from user
printf("Enter name: ");
fgets( pStu->name, 31, stdin);
printf("Enter course: ");
fgets( pStu->course, 31, stdin);
}
void printInfo(struct student springClass[])
{
for(int i=0 ; i < 5; i++)
{
printf("Student #%d Name: %s\n", i+1, springClass[i].name);
printf("Student #%d course: %s\n", i+1, springClass[i].course);
printf("==========================================\n");
}
}
肖恩和我的答案之间存在一些风格差异,但两者都应该有效。对于非常冗长的答案,我深表歉意,但由于您是编程新手,我觉得比通常需要的更多细节可能很有用。希望对您有所帮助!
编辑:根据 Sean 的建议,在 main 末尾添加了免费的 student->name 和 student->class。