我开始使用动态列表并且我不明白为什么即使在main()程序中声明第一个节点时也需要使用malloc函数,下面的代码应该只是打印第一个节点中包含的数据,但如果我没有使用malloc函数初始化节点,它就不起作用了:
struct node{
int data;
struct node* next;
};
void insert(int val, struct node*);
int main() {
struct node* head ;
head->data = 2;
printf("%d \n", head->data);
}
答案 0 :(得分:3)
技术上你没有,但维护具有相同内存模式的所有节点对你来说只是一个优势,没有真正的缺点。
假设所有节点都存储在动态内存中。
你的“插入”程序会更好地命名为“add”或(对于完整的功能上下文)“cons”,它应该返回新节点:
struct node* cons(int val, struct node* next)
{
struct node* this = (struct node*)malloc( sizeof struct node );
if (!this) return next; // or some other error condition!
this->data = val;
this->next = next;
return this;
}
构建列表现在非常简单:
int main()
{
struct node* xs = cons( 2, cons( 3, cons( 5, cons( 7, NULL ) ) ) );
// You now have a list of the first four prime numbers.
很容易处理它们。
// Let’s print them!
{
struct node* p = xs;
while (p)
{
printf( "%d ", p->data );
p = p->next;
}
printf( "\n" );
}
// Let’s get the length!
int length = 0;
{
struct node* p = xs;
while (p)
{
length += 1;
p = p->next;
}
}
printf( "xs is %d elements long.\n", length );
顺便说一下,在命名时应尽量保持一致。您已将节点数据命名为“data”,但构造函数的参数将其称为“val”。你应该选择一个并坚持下去。
此外,通常是:
typedef struct node node;
现在除struct node
定义之外的所有地方都可以使用node
这个词。
哦,我差点忘了:不要忘记用适当的析构函数清理。
node* destroy( node* root )
{
if (!root) return NULL;
destroy( root->next );
free( root );
return NULL;
}
main()
的附录:
int main()
{
node* xs = ...
...
xs = destroy( xs );
}
答案 1 :(得分:3)
当你声明一个变量时,你定义变量的类型,然后它就是 名称,也可以选择声明它的初始值。
每种类型都需要特定数量的内存。例如ul
就是
32位操作系统上32位长,64位上8位长。
函数中声明的变量通常存储在相关的堆栈中 与功能。函数返回时,该函数的堆栈为 不再可用,变量不再存在。
当您需要变量的值/对象时,即使在函数之后也存在
返回,然后你需要在程序的不同部分分配内存,
通常是堆。这正是int
,malloc
和realloc
所做的。
否则
calloc
是错的。您已声明名为struct node* head ;
head->data = 2;
的{{1}}类型的指针,
但你没有给它任何东西。所以它指向一个未指定的
记忆中的位置。 head
尝试将值存储在未指定的位置
位置和程序很可能会因段错误而崩溃。
主要是你可以这样做:
struct node
head->data = 2
将保存在堆栈中,只要int main(void)
{
struct node head;
head.data = 2;
printf("%d \n", head.data);
return 0;
}
没有,它就会一直存在
返回。但这只是一个很小的例子。在一个复杂的程序中你
有更多变量,对象等等,简单地声明所有变量是个坏主意
head
中需要的变量。因此,最好在它们创建对象时创建它们
需要。
例如,你可以有一个创建对象的函数和另一个
调用main
并使用该对象。
main
这里create_node
使用malloc为一个struct node *create_node(int data)
{
struct node *head = malloc(sizeof *head);
if(head == NULL)
return NULL; // no more memory left
head->data = data;
head->next = NULL;
return head;
}
struct node *foo(void)
{
struct node *head = create_node(112);
// do somethig with head
return head;
}
分配内存
object,使用某些值初始化对象并返回指向该内存位置的指针。
create_node
调用struct node
并对其执行某些操作并返回
宾语。如果另一个函数调用foo
,则此函数将获取该对象。
create_node
还有其他原因。请考虑以下代码:
foo
在这种情况下,您知道您将需要4个整数。但有时你需要一个
例如,在运行时期间仅知道元素数量的数组
因为它取决于一些用户输入。为此,您还可以使用malloc
。
void foo(void)
{
int numbers[4] = { 1, 3, 5, 7 };
...
}
当您使用malloc
,void foo(int size)
{
int *numbers = malloc(size * sizeof *numbers);
// now you have "size" elements
...
free(numbers); // freeing memory
}
,malloc
时,您需要释放内存。如果
你的程序不再需要内存了,你必须使用realloc
(比如
最后一个例子。请注意,为简单起见,我省略了calloc
的使用
free
的示例。
答案 2 :(得分:2)
你所调用的是未定义的行为,因为你没有真正的节点,你有一个指针到一个实际上并不指向节点的节点。使用malloc
和朋友创建一个可以驻留实际节点对象的内存区域,以及节点指针可以指向的位置。
在您的代码中,struct node* head
是一个指向无处的指针,并且正如您所做的那样取消引用它是未定义的行为(通常会导致段错误)。您必须先将head
指向有效的struct node
,然后才能安全取消引用它。一种方式是这样的:
int main() {
struct node* head;
struct node myNode;
head = &myNode; // assigning the address of myNode to head, now head points somewhere
head->data = 2; // this is legal
printf("%d \n", head->data); // will print 2
}
但是在上面的例子中,myNode
是一个局部变量,一旦函数存在就会超出范围(在本例中为main
)。正如您在问题中所说,对于链接列表,您通常希望malloc
数据,以便可以在当前范围之外使用。
int main() {
struct node* head = malloc(sizeof struct node);
if (head != NULL)
{
// we received a valid memory block, so we can safely dereference
// you should ALWAYS initialize/assign memory when you allocate it.
// malloc does not do this, but calloc does (initializes it to 0) if you want to use that
// you can use malloc and memset together.. in this case there's just
// two fields, so we can initialize via assignment.
head->data = 2;
head->next = NULL;
printf("%d \n", head->data);
// clean up memory when we're done using it
free(head);
}
else
{
// we were unable to obtain memory
fprintf(stderr, "Unable to allocate memory!\n");
}
return 0;
}
这是一个非常简单的例子。通常对于链接列表,您将具有插入函数(malloc
通常发生并删除函数(free
通常发生的位置。您将在至少有一个head
指针总是指向列表中的第一个项目,对于双链表,你也需要一个tail
指针。还可以有打印函数,{{但是,无论如何,你必须为实际对象分配空间。deleteEntireList
是一种方法,所以内存的有效性在整个程序运行时间内都会持续存在。
编辑:
不正确的。这绝对适用于malloc
和int
,它适用于任何对象和指针。如果你有以下内容:
int*
这是您在OP中所有的未定义行为。指针必须指向有效的内容才能取消引用它。在上面的代码中,int main() {
int* head;
*head = 2; // head uninitialized and unassigned, this is UB
printf("%d\n", *head); // UB again
return 0;
}
未初始化,它没有确定性地指向任何内容,并且只要执行head
(无论是读还是写),就会调用未定义的行为。与您的*head
一样,您必须执行以下操作才能更正:
struct node
或者你可以做
int main() {
int myInt; // creates space for an actual int in automatic storage (most likely the stack)
int* head = &myInt; // now head points to a valid memory location, namely myInt
*head = 2; // now myInt == 2
printf("%d\n", *head); // prints 2
return 0;
}
这些规则适用于您正在处理的任何基本类型或数据结构。指针必须指向某些内容,否则取消引用它们是未定义的行为,并且您希望在发生这种情况时会出现段错误,因此您可以在TA评级之前或客户演示之前跟踪错误。墨菲定律规定UB在呈现时会一直崩溃。
答案 3 :(得分:1)
语句struct node* head;
定义节点对象的指针,但不定义节点对象本身。由于您没有初始化指针(即通过让它指向由malloc
- 语句创建的节点对象),因此与head->data
一样取消引用此指针会产生未定义的行为。< / p>
克服这个问题的两种方法,(1)动态分配内存 - 产生具有动态存储持续时间的对象,或(2)将对象本身定义为具有自动存储持续时间的本地变量:
(1)动态存储时间
int main() {
struct node* head = calloc(1, sizeof(struct node));
if (head) {
head->data = 2;
printf("%d \n", head->data);
free(head);
}
}
(2)自动存储时间
int main() {
struct node head;
head.data = 2;
printf("%d \n", head.data);
}