指针指向 NULL 指针

A pointer points to a NULL pointer

cs50哈佛处理链表课程的代码: ---我不明白的问题是,当节点*ptr指向数字时,它是一个空指针,for循环怎么能:(节点*ptr =数字; ptr!= NULL)运行因为 *numbers = NULL?---

完整版代码可在以下位置找到:https://cdn.cs50.net/2017/fall/lectures/5/src5/list2.c

#include <cs50.h>
#include <stdio.h>

typedef struct node
{
    int number;
    struct node *next;
}
node;

int main(void)
{
    // Memory for numbers
    node *numbers = NULL;

    // Prompt for numbers (until EOF)
    while (true)
    {
        // Prompt for number
        int number = get_int("number: ");

        // Check for EOF
        if (number == INT_MAX)
        {
            break;
        }

        // Check whether number is already in list
        bool found = false;
        for (node *ptr = numbers; ptr != NULL; ptr = ptr->next)
        {
            if (ptr->number == number)
            {
                found = true;
                break;
            }


}

如果您检查也在 while 循环中的其余代码,您可以在共享 link.

上看到 numbers 的更改
if (!found)
{
    // Allocate space for number
    node *n = malloc(sizeof(node));
    if (!n)
    {
        return 1;
    }

    // Add number to list
    n->number = number;
    n->next = NULL;
    if (numbers)
    {
        for (node *ptr = numbers; ptr != NULL; ptr = ptr->next)
        {
            if (!ptr->next)
            {
                ptr->next = n;
                break;
            }
        }
    }
    else
    {
        numbers = n;
    }
}

此外,它一开始并没有命中for循环体,所以你的想法是正确的。

该循环用于检查正在构建的列表中是否存在先前存在。如果不存在(found 从未设置为真),则剩余的不方便省略的代码会将其添加到列表中。

初始运行时,numbers链表头指针为空,表示空表。这不会改变 search + if-not-found-insert 的算法。这只是意味着永远不会进入循环,因为 bail-case 立即为真。换句话说,numbers 为 NULL

for (node *ptr = numbers; ptr != NULL; ptr = ptr->next)

继续的条件,ptr != NULL已经为假,所以for-loop的body就直接跳过了。这导致您没有执行实际插入的其余代码 post。插入之后,列表现在有了一些东西,outer-while 循环的下一次迭代最终将在读取下一个预期值后再次扫描列表。这一直持续到不再满足 outer-while 条件。


不同的方法

从来没有喜欢cs50开发策略,以及哈佛教授C语言给entry-level CS学生的技巧。 cs50 header 和 lib 对 real-world 软件工程造成的过渡性混乱超出了人们的想象。下面是读取值链接列表的替代方法,只保留唯一条目。它可能看起来很多,但其中一半是描述正在发生的事情的内嵌注释。其中一些看起来微不足道,但 search-and-insert 方法才是您应该关注的。它使用了您可能不熟悉的 pointer-to-pointer 策略,这是一个很好的曝光。

尽情享受吧。

#include <stdio.h>
#include <stdlib.h>

struct node
{
    int value;
    struct node *next;
};

int main()
{
    struct node *numbers = NULL;
    int value = 0;

    // retrieve list input. stop when we hit
    //  - anything that doesn't parse as an integer
    //  - a value less than zero
    //  - EOF
    while (scanf("%d", &value) == 1 && value >= 0)
    {
        // finds the address-of (not the address-in) the first
        // pointer whose node has a value matching ours, or the
        // last pointer in the list (which points to NULL).
        //
        // note the "last" pointer will be the head pointer if
        // the list is empty.
        struct node **pp = &numbers;
        while (*pp && (*pp)->value != value)
            pp = &(*pp)->next;

        // if we didn't find our value, `pp` holds the address of
        //  the last pointer in the list. Again, not a pointer to the
        //  last "node" in the list; rather the last actual "pointer"
        //  in the list. Think of it as the "next" member of last node,
        //  and in the case of an empty list, it will be the address of
        //  the head pointer. *That* is where we will be hanging our
        //  new node, and since we already know where it goes, there is
        //  no need to rescan the list again.
        if (!*pp)
        {
            *pp = malloc(sizeof **pp);
            if (!*pp)
            {
                perror("Failed to allocate new node");
                exit(EXIT_FAILURE);
            }

            (*pp)->value = value;
            (*pp)->next = NULL;
        }
    }

    // display entire list, single line
    for (struct node const *p = numbers; p; p = p->next)
        printf("%d ", p->value);
    fputc('\n', stdout);

    // free the list
    while (numbers)
    {
        struct node *tmp = numbers;
        numbers = numbers->next;
        free(tmp);
    }

    return EXIT_SUCCESS;
}

这种方法在构建 sorted 列表时特别方便,因为只需进行一些更改即可更改它。