C指针,将元素插入链表的HEAD

时间:2013-10-17 01:37:19

标签: c pointers

我正在研究K& R书中的一个问题(#6.3),用户输入一系列单词,你必须创建这些单词的列表以及每个单词出现的行。它应该涉及结构,所以这些是我现在拥有的:

struct entry {
    int line; 
    int count; 
    struct entry *next; 
};

struct word {
    char *str;  
    struct entry *lines; 
    struct word *next; 
}; 

static struct word *wordlist = NULL;    // GLOBAL WORDLIST

但是,当我输入内容并且程序尝试向结构添加新条目(有点像链接列表)时,存在问题并且程序终止而没有错误消息。代码:

void add_entry(char *word, int line)
{
    if (word == NULL || line <= 0 || is_blocked_word(word))
        return;

    struct word *w; 
    for (w = wordlist; w != NULL && w->next != NULL && !strcmp(w->str, word); w = w->next); 

    // If word is found in the wordlist, then update the entry
    if (w != NULL) {
        struct entry *v; 
        for (v = w->lines; v != NULL && v->next != NULL && v->line != line; v = v->next); 

        if (v == NULL) {
            struct entry *new = (struct entry*) malloc(sizeof(struct entry)); 
            new->line = line;
            new->count = 1;
            new->next = NULL; 

            if (w->lines == NULL)
                w->lines = new; 
            else
                v->next = new; 
        }
        else v->count++; 
    }

    // If word is not found in the word list, then create a new entry for it
    else {
        struct word *new = (struct word*) malloc(sizeof(struct word)); 
        new->lines = (struct entry*) malloc(sizeof(struct entry)); 
        new->next = NULL; 
        new->str = (char*) malloc(sizeof(char) * strlen(word)); 
        new->lines->line = line; 
        new->lines->count = 1; 
        new->lines->next = NULL; 
        strcpy(new->str, word); 

        // If the word list is empty, then populate head first before populating the "next" entry
        if (wordlist == NULL) 
            wordlist = new; 
        else 
            w->next = new;
    }
}

即使在向wordlist添加第一个单词后,程序也会终止。这就是if (wordlist == NULL) wordlist = new;所在的行,其中new包含指向我malloc的有效结构的指针。这怎么可能?

据我所知,这是我的指针使用问题,但我不确定它到底在哪里。有人可以帮忙吗?

1 个答案:

答案 0 :(得分:1)

一些相当明显的,一些不那么明显的事情。

w的for循环限制会停止一次

for (w = wordlist; w != NULL && w->next != NULL && !strcmp(w->str, word); w = w->next);

这将从第一个开始,然后继续直到

  1. 我们已经用完了节点
  2. 我们几乎(一个简称)用完节点。
  3. 当前节点中的单词不匹配
  4. 几乎相同的问题,不同的for-loop

    for (v = w->lines; v != NULL && v->next != NULL && v->line != line; v = v->next); 
    

    如上所述,它具有相似的属性(但不是第三个选项,因为正确继续只要行号不匹配。先前的循环在 any < / em>单词不匹配。

    这就是这个功能的前十行。

    字符串分配大小无法考虑nulchar终结符

    这不符合零终止字符串所需的分配大小的一个字符:

    malloc(sizeof(char) * strlen(word))
    

    终结者总是需要空间。最简单的方法是考虑零长度C字符串需要多少个字符?答案:一,因为终结者需要去某个地方。之后只需length+1


    一种可行的方法是通过指向指针的方法,如下所示:

    void add_entry(const char *word, int line)
    {
        if (word == NULL || line <= 0 || is_blocked_word(word))
            return;
    
        struct word **pp = &wordlist;
        for (; *pp && strcmp((*pp)->str, word); pp = &(*pp)->next);
        if (*pp)
        {
            // search for matching line number
            struct entry **vv = &(*pp)->lines;
            for (; *vv && (*vv)->line != line; vv = &(*vv)->next);
            if (!*vv)
            {
                *vv = malloc(sizeof(**vv));
                if (!*vv)
                {
                    perror("Failed to allocate line entry.");
                    exit(EXIT_FAILURE);
                }
                (*vv)->count = 1;
                (*vv)->line = line;
                (*vv)->next = NULL;
            }
            else
            {   // found an entry. increment count.
                (*vv)->count++;
            }
        }
        else
        {   // no matching word. create a new word with a new line entry
            size_t len = strlen(word);
            *pp = malloc(sizeof(**pp));
            if (!*pp)
            {
                perror("Failed to allocate word entry.");
                exit(EXIT_FAILURE);
            }
    
            (*pp)->lines = malloc(sizeof(*(*pp)->lines));
            if (!(*pp)->lines)
            {
                perror("Failed to allocate line count entry.");
                exit(EXIT_FAILURE);
            }
    
            (*pp)->str = malloc(len + 1);
            if (!(*pp)->str)
            {
                perror("Failed to allocate word string entry.");
                exit(EXIT_FAILURE);
            }
    
            (*pp)->lines->count = 1;
            (*pp)->lines->line = line;
            (*pp)->lines->next = NULL;
            (*pp)->next = NULL;
            memcpy((*pp)->str, word, len+1);
        }
    }
    

    如何运作

    在这两种情况下,我们都使用指向指针的指针。当希望在链表上执行尾端插入而不必保留“单后”或“前一个”指针时,它们是最常用的构造。就像任何指针一样,它们都有一个地址。与常规指针指向某事物不同,指向指针的指针保存另一指针的地址。通过它,我们可以通过最初将其设置为头指针的地址来“循环”,进入搜索。

    struct word **pp = &wordlist;
    for (; *pp && strcmp((*pp)->str, word); pp = &(*pp)->next);
    

    这里我们从头指针的地址开始。如果pp 中保存的地址处的指针为NULL,或者该字实际匹配,则循环将终止。否则,它会设置当前节点的next指针的地址 (而不是中的地址)。如果我们用完了单词而从未找到匹配项循环将中断,但有一个最方便的结果:pp包含我们需要设置为新分配的指针的地址。如果列表最初为空,它包含头指针的地址。

    有了这个,我们就可以这样做:

    if (*pp)
    {
        // search for matching line number
        struct entry **vv = &(*pp)->lines;
        for (; *vv && (*vv)->line != line; vv = &(*vv)->next);
    

    请注意,我们在行条目列表中使用相同的想法。要么我们要找到一个条目,要么循环将退出,*vvNULL,而vv包含我们要设置为next指针的地址我们的新分配。

    强烈敦促您逐行逐步调试此代码,并了解其工作原理。利用这种技术有许多赎回的特质,其中包括在O(n)复杂度中填充前向链表的令人难以置信的简短方法,无需检查头指针或每个插入的列表行走保留原始订单(而不是将订单转换为类似堆栈的解决方案):

    struct node *head = NULL;
    
    struct node **pp = &head;
    while (get-data-for-our-list)
    {
        *pp = malloc(sizeof(**pp));
        // TODO: populate (*pp)->members here
        pp = &(*pp)->next;
    }
    *pp = NULL;