// Implements a list of numbers using a linked list

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

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

node *makelist(int argc, char *argv[]);
int check_spaces(char *input);
int *nodes(char *input, int amount);
node *add(char *input, node *list, int *numbers, int amount);


/*int delete();
int find();*/

int main(int argc, char *argv[])
{
    //Get linked list
    node *list = makelist(argc, argv);
    //Print linked list
    for (node *ptr = list; ptr != NULL; ptr = ptr->next)
    {
        printf("%i ", ptr->number);
    }
    printf("\n");
    // Free memory
    node *ptr = list;
    while (ptr != NULL)
    {
        node *next = ptr->next;
        free(ptr);
        ptr = next;
    }
    char c;
    //Give choices
    printf("Do you want to (d)delete a node, (a)add a node, or (f)find a node? ");
    //Repeat if incorrect input
    do
    {
        scanf("%c", &c);
        if (c == 'd' || c == 'a' || c == 'f')
        {
            break;
        }
        else
        {
            printf("Wrong input, try again. ");

        }
    }
    while (c != 'd' || c != 'a' || c != 'f');

    //If user chose to "add" node(s)
    if (c == 'a')
    {
        char *name = get_string("");
        char *input = get_string("What are the node(s) that you want to add? ");
        int amount = check_spaces(input);
        int *numbers = malloc(amount * 4);
        *numbers = *nodes(input, amount);
        list = add(input, list, numbers, amount);
        for (node *ptr2 = list; ptr2 != NULL; ptr2 = ptr2->next)
        {
            printf("%i ", ptr2->number);
        }
        free(numbers);
        free(list);
    }
    //If user chose to "delete" node(s)
    else if (c == 'd')
    {
        printf("d");
    }
    //If user chose to "find" node(s)
    else
    {
        printf("c");
    }
    return 0;
}
//Make linked list
node *makelist(int argc, char *argv[])
{
    // Memory for numbers
    node *list = NULL;

    // For each command-line argument
    for (int i = 1; i < argc; i++)
    {
        // Convert argument to int
        int number = atoi(argv[i]);

        // Allocate node for number
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return 0;
        }
        n->number = number;
        n->next = NULL;

        // If list is empty
        if (list == NULL)
        {
            // This node is the whole list
            list = n;
        }

        // If list has numbers already
        else
        {
            // Iterate over nodes in list
            for (node *ptr = list; ptr != NULL; ptr = ptr->next)
            {
                // If at end of list
                if (ptr->next == NULL)
                {
                    // Append node
                    ptr->next = n;
                    break;
                }
            }
        }
    }
    return list;
}
//Add function
node *add(char *input, node *list, int *numbers, int amount)
{
    int a = 0;
    while (numbers[a] != '\0')
    {
        a++;
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return 0;
        }
        n->number = numbers[amount-a];
        // Prepend node to list
        n->next = list;
        list = n;
    }
    return list;
}
//Get command line arguments from printf
int *nodes(char *input, int amount)
{
    int *y = malloc(amount * 4);
    char *x = &input[0];
    y[0] = atoi(x);
    int i = 0;
    int j = 1;
    while (input[i] != '\0')
    {
        if (input[i] == ' ')
        {
            x = &input[i+1];
            y[j] = atoi(x);
            j++;
        }
        i++;
    }
    return y;
}
//How many nodes are we messing with
int check_spaces(char *input)
{
    int numbers = 1;
    int i = 0;
    while (input[i] != '\0')
    {
        if (input[i] == ' ')
        {
            numbers++;
        }
        i++;
    }
    return numbers;
}

/*int delete()
{

}
int find()
{

}*/

该问题源于C无法返回array.问题是关于添加函数,因为其他函数是不完整的.

我的整个计划是使用atoi将用户的命令行参数转换为数字,然后反向将它们添加到链表中.如果我的 list 是3,4,5,我加上1,2,它就会变成1,2,3,4,5(理论上).

我的 node 函数执行所有的转换,由于C的规则,我不能返回数组,所以我返回了一个指针.我认为所有的数字都是背靠背的,因为有了Malloc,所以我有一个循环遍历n-&gt;Numbers=Numbers[Amount-a].第一个值是正确的,但然后其他的值只是一堆垃圾值.

我很确定这是因为指针只指向第一个对象,其余的对象都是按添加的顺序排列的.我曾try 过访问其他值,但没有这样的运气.老实说,我觉得我错过了一些重要的东西.

一定有什么事情我在这里过分复杂化了.

推荐答案

我相信,就像你说的,你把事情搞得过于复杂了.

该问题源于C语言无法返回数组

嗯,这个问题本身对我来说并不清楚.

  • 这百分百的无能为力是不存在的
  • 为什么要在创建链表的程序中返回数组?你为什么不能这么做呢?

我的整个计划是使用ATOI将用户的命令行参数转换为数字,然后反向将它们添加到链表中.如果我的 list 是3,4,5,我加上1,2,它就会变成1,2,3,4,5(理论上).

这只是意味着您希望将元素始终添加到列表的开头.这不是一种常见的 Select :通常,最好将元素插入到末尾,保持插入的顺序.这样的名单被称为stable.

另一种常见的 Select 是按顺序插入元素,但保留可能重复项的插入顺序.因此,如果添加%1%1%2%3%4,则会在第一个%1之后插入第二个%1

我的 node 函数执行所有的转换,由于C的规则,我不能返回数组,所以我返回了一个指针

这是node函数

// Get command line arguments from printf
int* nodes(char* input, int amount)
{
    int*  y = malloc(amount * 4);
    char* x = &input[0];
    y[0]    = atoi(x);
    int i   = 0;
    int j   = 1;
    while (input[i] != '\0')
    {
        if (input[i] == ' ')
        {
            x    = &input[i + 1];
            y[j] = atoi(x);
            j++;
        }
        i++;
    }
    return y;
}

你想做什么?您说过atoi将用于转换参数,但现在node"完成了所有的转换".怎么会这样呢?

我认为所有的数字都是背靠背的,因为有了Malloc,所以我有一个循环遍历n->;Numbers=Numbers[Amount-a].第一个值是正确的,但然后其他的值只是一堆垃圾值.

我不明白你想说什么.malloc仅根据单个参数(以字节为单位的大小)来分配内存.

我很确定这是因为指针只指向第一个对象,其余的对象都是按添加的顺序排列的.

嗯,这是意料之中的:指针指向某个东西.只对一件事.至于其余的,我看不出是什么.可能是其他因素?还是其他 node ?

我觉得我错过了一些重要的东西.

我也相信你是.请参见下面的示例和讨论.也许它增加了一些东西

restarting things: a C complete EXAMPLE

我将try 向您展示一种可用于每个程序的方法,但使用链接列表的方式是您正在try 构建的.

因为这对学生来说是非常非常常见的作业(job),所以我会留下完整的代码和输出.

关于行参数

在开始时考虑该程序p

#include <stdio.h>
int main(int argc, char* argv[])
{
   if (argc < 2)
   {
       printf("\tNo arguments provided!\n");
       return -1;
   }
   printf(
       "    %d arguments on the command line\n\n", argc - 1);
   for (int i = 1; i < argc; i += 1)
       printf("    #%d    \"%s\"\n", i, argv[i]);
   return 0;
}

提出了这些论点

        p 1 2 3 4 5 5

以下是输出:

    6 arguments on the command line

    #1    "1"
    #2    "2"
    #3    "3"
    #4    "4"
    #5    "5"
    #6    "5"

该数组由操作系统提供.不需要仅仅为了插入到链表中而将其转换为另一个int的array.我们可以只使用这些值,并按此顺序.

链表

这就是你所说的链表:

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

But it is not.看不到这一点会让你在DAA建筑周围的生活变得更加艰难.你有关于这方面的增刊吗?

链表 is a --- possibly empty --- collection of nodes.每个 node 包含或指向一些数据.

A node is not a list. A list is not a node.

重写:一个 node

这是一个 node :

typedef struct s_n
{
    int         number;
    struct s_n* next;
} Node;

请注意,只有一个方向的指针比有next个和prev个指针要复杂得多.单一链表比双向链表更难管理.

为什么?导航.例如,当您需要删除一个 node 时-您将-并且只有next个指针,您必须记住,除非您要删除第一个元素,否则以前的一些 node 将指向您要删除的 node ,但是you do not have its 添加ress...

Supose List是{ v, x, y }.删除x时,如果Node中有prev个指针,那么x->prev指向v,v->next指向x.x->next点,至y点.

要删除x,您必须将v指向y,y在删除x之前指向v.这很简单: x->prev->next = yy->prev = v.那么x就可以删除了.

如果只有next个指针,则需要在代码中保存删除的状态.请参见下面的示例.

和一份名单

typedef struct
{
    unsigned size;
    Node*    head;
    Node*    tail;
} List;

现在我们有了一个列表和一个 node .请注意,在程序中创建的名称保留第一个大写字母的惯例.

正如您所看到的,该列表有两个指针,因为我们希望保持值的插入顺序,而每次仅仅为了获得最后一个 node 的地址而遍历该列表将不是很有效. 而且,由于随时可以方便地知道列表中有多少个 node ,因此我们也有size个 node .我相信这是很容易知道它是如何unsigned的价值.

你当然可以没有size个,但如果它总是在列表中,那么总是知道它的项目数要简单得多.

一些规划

在编写任何程序之前,让我们看看我们将需要的操作以及程序将如何工作.

操作:

  • 删除
  • 添加
  • 发现

在程序启动时,将使用命令行中提供的值创建该列表.如果没有,则列表将被创建为空,因为仍然可以对空列表执行所有操作

一些函数

在Raii in C++的风格中,一开始我们有

List* create();
List* destroy(List* toGo);

使用这种方式使工作更容易:可以在一行中创建List,同时销毁List,我们可以保证列表指针无效:没有机会悬空指针.

请注意,我们还没有一行代码.只是浏览了一下数据.

更多功能

如果我们想要销毁一个列表,很明显,我们需要在删除列表本身之前删除所有 node ,否则就会泄漏内存. 在我们进行测试时,很明显我们还需要一个打印列表内容的选项.因此,我们添加这些函数作为最低要求

int   添加(int data, List* list);
Node* 删除(int one, List* list);
int   display(List* list, const char* title);
int   发现(int one, List* list);

由于我们有一些定义和响应行参数的原始程序,所以我们可以编写第二个程序

第二个项目

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

typedef struct s_n
{
    int         number;
    struct s_n* next;
} Node;

typedef struct
{
    unsigned size;
    Node*    head;
    Node*    tail;
} List;

List* create();
List* destroy(List* toGo);
int 添加(int data, List* list);
int 删除(int one, List* list);
int display(List* list, const char* title);
int 发现(int one, List* list);


int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("\tNo arguments provided!\n");
        return -1;
    }
    printf(
        "    %d arguments on the command line\n\n",
        argc - 1);
    for (int i = 1; i < argc; i += 1)
        printf("    #%d    \"%s\"\n", i, argv[i]);
    return 0;
}

List* create() { return NULL; }
List* destroy(List* toGo) { return NULL; }

int 添加(int data, List* list) { return 0; }
int 删除(int one, List* list);
int display(List* list, const char* title) { return 0; }
int 发现(int one, List* list) { return 0; }

我们甚至可以运行它.

create and destroy list

创建列表只需为其分配内存并将其标记为空

List* create()
{
    List* one = malloc(sizeof(List));
    if (one == NULL) return NULL;
    one->size = 0;
    one->head = NULL;
    one->tail = NULL;
    return one;
}

删除列表也是基本的:我们知道我们有size个 node ,我们知道如何删除每个 node ,因为我们有一个删除列表的函数,所以...

List* destroy(List* gone)
{
    if (gone == NULL) return NULL;
    for (unsigned i = 0; i < gone->size; i += 1)
    {
        Node* p = gone->head->next;
        free(gone->head);
        gone->head = p;
    }
    free(gone);
    return NULL;
};

这是正常的,因为这里的 node 不分配内存.

A third program

现在,我们可以运行此版本的Main:

int main(int argc, char* argv[])
{
    List* my_list = create();
    my_list       = destroy(my_list);
    return 0;
}

请注意,destroy()总是返回NULL,这可能看起来很愚蠢.但目标是能够使列表被销毁的同一行中的List指针无效,并在调用destroy()的代码中:

    my_list       = destroy(my_list);

这不会使悬挂的无效指针在调用代码中保持丢失的机会...

而且它运行得很好!它没有什么用处,但对精神上的支持是有好处的

inserting data: 添加()

int 添加(int data, List* list)
{  // insert at last element
    if (list == NULL) return -1;
    Node* p = malloc(sizeof(Node));
    p->number = data;  // data copied to node
    p->next = NULL;  // inserted at tail
    if (list->size == 0)
    {
        list->head = list->tail = p;
        list->size              = 1;
        return 0;
    }
    list->tail->next = p;
    list->tail       = p;
    list->size += 1;  // count it in
    return 0;
}

略多于10行.请注意,我们使用tail指针在结尾处插入并保持 node 的插入顺序.无论如何,在开头插入或head是相同的,只需使用head指针,并使您的node->next首先指向head,然后再重新分配head.

To test this we need display()

很明显,我们可以在一个空的列表上调用display(),也可以在一个包含几十个项目的列表上调用display().或者甚至使用无效指针,所以让我们这样做吧. 标题的使用在这里非常方便,所以不需要在测试非常非常常见的printf之前...

int display(List* list, const char* title)
{
    if (title != NULL) printf("%s", title);
    if (list == NULL)
    {
        printf("\nList does not exist\n");
        return 0;
    }
    printf("\nList size: %u\n  [ ", list->size);
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        printf("%d ", p->number);
        p = p->next;
    }
    printf("]\n\n");
    return 0;
};

display()只是跟着指针走.这份名单实际上是极简主义的,只有一个数字.

第4个节目现在甚至可以显示一些数据

请考虑以下几点:

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("\tNo arguments provided!\n");
        return -1;
    }
    printf(
        "    %d arguments on the command line\n", argc - 1);

    List* my_list = create();
    display(my_list, "list empty");
    for (int i = 1; i < argc; i += 1)
        添加(atoi(argv[i]), my_list);
    display(my_list, "after insertions");
    my_list = destroy(my_list);
    display(my_list, "afer destroy()");
    return 0;
}

和的输出

    p 1 2 3 4 5
     5 arguments on the command line
list empty
List size: 0
  [ ]

after insertions
List size: 5
  [ 1 2 3 4 5 ]

afer destroy()
List does not exist

到目前为止,没有什么令人惊讶的:在第一次运行时就像预期的那样工作,因为我们一步一步地围绕数据编写

删除() and 发现() simple implementations

它们类似于display(),因为它是导航整个列表的问题,但是

  • 删除() return 0 when element is found and 删除d
  • 发现() returns the Node 添加ress when found, or NULL

这是一个可能的删除():

int 删除(int data, List* list)
{  // 删除 the 1st node that contains 'data'
    if (list == NULL) return -1;
    Node* p    = list->head;
    Node* prev = NULL;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data)
        {  // this is the one
            if (p == list->head)
                list->head = p->next;
            else
            {
                prev->next = p->next;
                if (p == list->tail) list->tail = prev;
            }
            list->size -= 1;
            free(p);
            return 0;
        }
        prev = p;        // save state: no back pointer
        p    = p->next;  // go ahead
    }
    return -2;  // not found
};

由于缺少后向指针,大约有15行

以及可能的发现()分:

Node* 发现(int data, List* list)
{  // return 添加ress of the first node with 'data'
    // or NULL if not found. Do not consider the
    // 'list` as sorted
    if (list == NULL)
    {
        fprintf(stderr, "\n发现(): List does not exist\n");
        return NULL;
    }
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data) return p;
        p = p->next;
    }
    return NULL;
};

第五期节目时间到了

插入命令行参数(如果有)

现在是恢复第一个程序并插入命令行中的参数的好时机.

我们已经有添加()个了,这不会很难的.参数是字符串,但我们也有atoi()个so:

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("\tNo arguments provided!\n");
        return -1;
    }
    printf(
        "    %d arguments on the command line\n", argc - 1);

    List* my_list = create();
    display(my_list, "list empty");
    for (int i = 1; i < argc; i += 1)
        添加(atoi(argv[i]), my_list);
    display(my_list, "after insertions");
    //
    删除 (1, my_list);
    删除 (3, my_list);
    删除 (5, my_list);
    display(my_list, "after 删除 1 3 & 5");
    //
    my_list = destroy(my_list);
    display(my_list, "afer destroy()");
    return 0;
}

插入了3个删除第一条、最后一条和中间记录的调用以进行测试...

这句话是唯一的 fresh 事:

    for (int i = 1; i < argc; i += 1) 添加(atoi(argv[i]), my_list);

在列表中插入参数之前只需调用atoi()即可.

我们知道添加()个有效,我们知道atoi()个有效,其余的我们已经测试过了,所以这只是一个复制和粘贴的问题.

输出

    5 arguments on the command line
list empty
List size: 0
  [ ]

after insertions
List size: 5
  [ 1 2 3 4 5 ]

after 删除 1 3 & 5
List size: 2
  [ 2 4 ]

afer destroy()
List does not exist

到目前为止的完整例子

C code

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

typedef struct s_n
{
    int         number;
    struct s_n* next;
} Node;

typedef struct
{
    unsigned size;
    Node*    head;
    Node*    tail;
} List;

List* create();
List* destroy(List* toGo);
int   添加(int data, List* list);
int   删除(int one, List* list);
int   display(List* list, const char* title);
Node* 发现(int one, List* list);

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("\tNo arguments provided!\n");
        return -1;
    }
    printf(
        "    %d arguments on the command line\n", argc - 1);

    List* my_list = create();
    display(my_list, "list empty");
    for (int i = 1; i < argc; i += 1)
        添加(atoi(argv[i]), my_list);
    display(my_list, "after insertions");
    //
    删除 (1, my_list);
    删除 (3, my_list);
    删除 (5, my_list);
    display(my_list, "after 删除 1 3 & 5");
    //
    my_list = destroy(my_list);
    display(my_list, "afer destroy()");
    return 0;
}

List* create()
{
    List* one = malloc(sizeof(List));
    if (one == NULL) return NULL;
    one->size = 0;
    one->head = NULL;
    one->tail = NULL;
    return one;
}

List* destroy(List* gone)
{
    if (gone == NULL) return NULL;
    for (unsigned i = 0; i < gone->size; i += 1)
    {
        Node* p = gone->head->next;
        free(gone->head);
        gone->head = p;
    }
    free(gone);
    return NULL;
};

int 添加(int data, List* list)
{  // insert at last element
    if (list == NULL) return -1;
    Node* p   = malloc(sizeof(Node));
    p->number = data;  // data copied to node
    p->next   = NULL;  // inserted at tail
    if (list->size == 0)
    {
        list->head = list->tail = p;
        list->size              = 1;
        return 0;
    }
    list->tail->next = p;
    list->tail       = p;
    list->size += 1;  // count it in
    return 0;
}

int 删除(int data, List* list)
{  // 删除 the 1st node that contains 'data'
    if (list == NULL) return -1;
    Node* p    = list->head;
    Node* prev = NULL;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data)
        {  // this is the one
            if (p == list->head)
                list->head = p->next;
            else
            {
                prev->next = p->next;
                if (p == list->tail) list->tail = prev;
            }
            list->size -= 1;
            free(p);
            return 0;
        }
        prev = p;        // save state: no back pointer
        p    = p->next;  // go ahead
    }
    return -2;  // not found
};

int display(List* list, const char* title)
{
    if (title != NULL) printf("%s", title);
    if (list == NULL)
    {
        printf("\nList does not exist\n");
        return 0;
    }
    printf("\nList size: %u\n  [ ", list->size);
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        printf("%d ", p->number);
        p = p->next;
    }
    printf("]\n\n");
    return 0;
};

Node* 发现(int data, List* list)
{  // return 添加ress of the first node with 'data'
    // or NULL if not found. Do not consider the
    // 'list` as sorted
    if (list == NULL)
    {
        fprintf(stderr, "\n发现(): List does not exist\n");
        return NULL;
    }
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data) return p;
        p = p->next;
    }
    return NULL;
};

我只是想展示一种写这些东西的方法.

发现()没有考试,但它和删除()一样.请让我知道这是否有帮助,如果有什么我可以补充的.

C++相关问答推荐

理解C中的指针定义

gcc已编译的可执行文件TSB是否同时暗示最低有效字节和最低有效位?

是否定义了数组指针类型转换为指针类型?""""

malloc实现:判断正确的分配对齐

核心转储文件中出现奇怪的大小变化

C由四个8位整数组成无符号32位整数

将常量转换为指针会增加.数据大小增加1000字节

进程已完成,退出代码为138 Clion

使用nmake for程序比Hello World稍微复杂一些

将 struct 数组写入二进制文件时发生Valgrind错误

Dlsym()的手册页解决方法仍然容易出错?

Tcl_GetDoubleFromObj在列表的迭代中是一个缺点

STM32:代码的执行似乎取决于它在闪存中的位置

在C中交换字符串和数组的通用交换函数

如何在Rust中处理C的longjmp情况?

为什么INT_MIN是在c语言的头文件limits.h中定义的(-INT_MAX-1)而不是直接使用-2147483648

使用 GCC 将一个函数中初始化的 struct 体实例通过指针传递到 C 中的另一个函数会产生不同的结果

运行以下 C 程序时出现分段错误

定义 int a = 0, b = a++, c = a++;在 C 中定义了行为吗?

如何在C中以0x格式打印十六进制值