如何在链表中使用char *

时间:2018-04-21 18:52:44

标签: c

struct Node 类型中,有一个char *数据。并且每个节点使用链接列表进行组合。

如果“数据”的类型是INT,则可以。 (例如年龄:23​​,45,33 ......)但是当类型变成“char *”时,ex。保存名称:“杰克”,“杰伊”,“詹姆斯”。价值是一样的,后者将涵盖前端。例如: 第一次输入:杰克。列表是杰克 第二次输入:周杰伦。列表是 Jay Jay 第三次输入:Jame。该列表是 Jame Jame Jame

代码如下:

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

typedef struct listNode{
 char *data;
 struct listNode *next;
} *ListNodePtr;


typedef struct list {
  ListNodePtr head;
} List;



List new_list(){
  List temp;
  temp.head = NULL;
  return temp;
}



//Student Courses

void insert_at_front(ListNodePtr* head, char *data){ 
 ListNodePtr new_node = malloc(sizeof(struct listNode));
 new_node->data = data;
 new_node->next = *head;
 *head = new_node;
}

void print_list(List *self)
{
   ListNodePtr current = self->head;
   while(current!=NULL)
   {
       printf("%s \n", current->data);
       current = current->next;
   }

   printf("\n");
}


int main()
{
   char i = 'y';
   char *name;
   List mylist = new_list();
   while(i=='y'){
       printf("your name is :");
       scanf("%s",name);
       insert_at_front(&mylist.head,name);
       print_list(&mylist);
   }

   return 0;
}

1 个答案:

答案 0 :(得分:2)

好消息是你对列表操作的想法并不太远......坏消息是它也不是正确的。您拥有的最大绊脚石只是处理用户输入的基础知识,并确保您为存储的每一位数据存储。

main()声明char *name;时,name 是未初始化的指针。它没有指向任何有效的内存,您可以在其中存储组成name的字符(以及终止 nul-character )。您可以存储在指针中的唯一内容是内存地址 - 并且,有用的是,内存地址必须是有效内存块的开头,其大小足以保存您尝试存储的内容。

在您的代码中,由于name未指向能够在name中存储字符的任何有效内存块,因此您立即使用{{1}调用未定义行为繁荣! - &#34; Game Over&#34;适用于您的计划)。

由于您正在阅读一个名称,但很少超过64个字符,只需使用固定大小的缓冲区来保留scanf("%s",name);即可将其传递给name。不要吝啬缓冲区大小,所以为了确保您可以使用insert_at_front个字节之类的合理内容。不要在代码中使用幻数,所以如果你需要一个常量:

512

现在对于你的结构,你正在使用一个&#34;包装器&#34; 结构,其中包含基本上包装你的列表的#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 512 /* if you need a constant, #define one (or more) */ ... int main (void) { char i = 'y'; char name[MAXN] = ""; /* fixed size buf for reading name input */ ... 。尽管不是必需的,但这样做很好。但是,由于您使用的是自动存储(并将其作为参数传递)或为其动态分配。由于您使用head功能,如果您使用自动存储,则必须在new_list <中将mylist声明为List类型em>将其作为参数传递给main()进行初始化。

为什么呢?您无法new_list()返回temp,因为new_list已在tempnew_list的本地)和new_list返回时,new_list的函数堆栈(包含temp变量的内存)被销毁(释放以供重用)。

如果你想从new_list返回指针,那么你必须(1)将先前声明的new_listtemp传递给main() - 或 - (2)动态分配new_list temp new_list 分配的存储时间,以便包含temp的内存存活到temp在该内存上调用,或者程序结束。正常选项是(2),但如果您充分考虑变量的存储持续时间,则(1)没有任何问题。

进行一些调整,并避免键入一个指针,因为It is NOT a good idea to typedef pointers?,并添加了一个方便的示例,你必须在你的包装器结构中跟踪列表统计信息,你可以做类似于:

free()

为了帮助您在学习过程中使列表更具逻辑性,通常有助于创建一个单独的函数,该函数负责创建添加到列表中的每个节点。这使您可以专注于节点结构中哪些项目需要分配存储空间,并提供一个方便的位置来处理分配(以及验证该分配),以及在一个地方初始化所有值 - 而不会使列表逻辑混乱。您可以实现类似于:

的create_node函数
typedef struct lnode {  /* don't typedef pointers -- it will confuse you */
    char *data;
    struct lnode *next;
} lnode;

typedef struct list {   /* you pass this list to insert_at_front */
    lnode *head;
    size_t size;        /* you can track any list stats you like */
} list;

/* create a dynamically allocated list struct */
list *new_list (void) 
{
    list *temp = malloc (sizeof *temp);     /* create storage for list  */
    if (!temp) {                            /* validate ALL allocations */
        perror ("malloc-new_list");
        return NULL;
    }
    temp->head = NULL;  /* initialize head NULL */
    temp->size = 0;

    return temp;    /* return pointer to new list */
}

注意:您已为(1)列表,(2)节点和(3)数据分配)

这使得/* create new dynamically allocated node, initialize all values */ lnode *create_new_node (const char *data) { lnode *new_node = NULL; if (!data) { /* validate data not NULL */ fputs ("error: data is NULL in create_new_node.\n", stderr); return NULL; } new_node = malloc (sizeof *new_node); /* allocate/validate node */ if (!new_node) { perror ("malloc-new_node"); return NULL; } /* allocate/validate storage for data */ if (!(new_node->data = malloc (strlen (data) + 1))) { perror ("malloc-new_node->data"); free (new_node); return NULL; } strcpy (new_node->data, data); /* copy data to new_node->data */ new_node->next = NULL; /* set next pointer NULL */ return new_node; /* return pointer to new_node */ } 的逻辑清晰可读。此外,您需要始终对任何列表操作使用正确的insert_at_front,尤其是在涉及任何分配的情况下,这样可以衡量列表操作的成功/失败。通常会在失败时返回指向添加的节点(此处为新type)或head的指针,例如..

NULL

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有2个职责(1) 始终保留指向内存块的起始地址(2),当不再需要时,可以释放。现在养成习惯,自己清理一下 - 当你的程序变得更复杂时,它会带来很大的收益。如果您正在处理列表和节点,则编写一个函数以在完成后释放所有数据,节点和列表。只需要一些简单的东西,例如。

/* insert new node at front, returning pointer to head
 * to guage success/failure of addition.
 */
lnode *insert_at_front (list *mylist, const char *data)
{ 
    lnode *new_node = create_new_node(data);

    if (!new_node) 
        return NULL;

    new_node->next = mylist->head;
    mylist->head = new_node;
    mylist->size++;

    return mylist->head;
}

通过用户输入,您负责验证您获得了良好的输入并且它满足您对输入的任何条件。您还负责确保输入缓冲区的状态为下一个输入操作做好准备。这意味着清除可能留在输入缓冲区中的任何无关字符(例如/* you are responsible for freeing any memory you allocate */ void free_list (list *mylist) { lnode *current = mylist->head; while (current) { lnode *victim = current; current = current->next; free (victim->data); free (victim); } free (mylist); } ),这将导致您下次尝试输入失败。一个简单的帮助函数来清空stdin可以帮助你摆脱困境。

stdin

此外,您将使用的每个输入功能都有返回。您必须验证您使用的任何输入函数的返回,以确定是否(1)读取了有效输入,(2)用户是否通过生成手册/* you are responsible for the state of stdin when doing user input */ void empty_stdin (void) { int c = getchar(); while (c != '\n' && c != EOF) c = getchar(); } 取消了输入,以及(3) )使用EOF时是否发生匹配输入失败。所以经常检查退货!例如:

scanf

在一个简短的例子中,你可以做如下的事情:

    while (i == 'y' || i == '\n') {         /* 'y' or (default '\n') */
        fputs ("\nenter name: ", stdout);
        if (scanf ("%511[^\n]", name) != 1) {   /* ALWAYS CHECK RETURN! */
            fputs ("error: invalid input or user canceled.", stderr);
            return 1;
        }
        empty_stdin();      /* empty any chars that remain in stdin */
        ...

注意:提示继续允许用户只需按 Enter 表示他想继续输入姓名,任何其他字符都会退出。这就是为什么{ {1}}在提示中显示为默认值 - 您能解释为什么以及如何工作?)

示例使用/输出

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

#define MAXN 512    /* if you need a constant, #define one (or more) */

typedef struct lnode {  /* don't typedef pointers -- it will confuse you */
    char *data;
    struct lnode *next;
} lnode;

typedef struct list {   /* you pass this list to insert_at_front */
    lnode *head;
    size_t size;        /* you can track any list stats you like */
} list;

/* create a dynamically allocated list struct */
list *new_list (void) 
{
    list *temp = malloc (sizeof *temp);     /* create storage for list  */
    if (!temp) {                            /* validate ALL allocations */
        perror ("malloc-new_list");
        return NULL;
    }
    temp->head = NULL;  /* initialize head NULL */
    temp->size = 0;

    return temp;    /* return pointer to new list */
}

/* create new dynamically allocated node, initialize all values */
lnode *create_new_node (const char *data)
{
    lnode *new_node = NULL;

    if (!data) {    /* validate data not NULL */
        fputs ("error: data is NULL in create_new_node.\n", stderr);
        return NULL;
    }

    new_node = malloc (sizeof *new_node);   /* allocate/validate node */
    if (!new_node) {
        perror ("malloc-new_node");
        return NULL;
    }
    /* allocate/validate storage for data */
    if (!(new_node->data = malloc (strlen (data) + 1))) {
        perror ("malloc-new_node->data");
        free (new_node);
        return NULL;
    }
    strcpy (new_node->data, data);  /* copy data to new_node->data */
    new_node->next = NULL;          /* set next pointer NULL */

    return new_node;    /* return pointer to new_node */
}

/* insert new node at front, returning pointer to head
 * to guage success/failure of addition.
 */
lnode *insert_at_front (list *mylist, const char *data)
{ 
    lnode *new_node = create_new_node(data);

    if (!new_node) 
        return NULL;

    new_node->next = mylist->head;
    mylist->head = new_node;
    mylist->size++;

    return mylist->head;
}

/* print_list - tweaked for formatted output */
void print_list (list *self)
{
    lnode *current = self->head;

    while (current != NULL)
    {
        if (current == self->head)
            printf (" %s", current->data);
        else
            printf (", %s", current->data);
        current = current->next;
    }

    putchar ('\n');
}

/* you are responsible for freeing any memory you allocate */
void free_list (list *mylist)
{
    lnode *current = mylist->head;
    while (current) {
        lnode *victim = current;
        current = current->next;
        free (victim->data);
        free (victim);
    }
    free (mylist);
}

/* you are responsible for the state of stdin when doing user input */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int main (void) {

    char i = 'y';
    char name[MAXN] = "";   /* fixed size buf for reading name input */

    list *mylist = new_list();

    while (i == 'y' || i == '\n') {         /* 'y' or (default '\n') */
        fputs ("\nenter name: ", stdout);
        if (scanf ("%511[^\n]", name) != 1) {   /* ALWAYS CHECK RETURN! */
            fputs ("error: invalid input or user canceled.", stderr);
            return 1;
        }
        empty_stdin();      /* empty any chars that remain in stdin */

        insert_at_front (mylist, name);         /* insert name */
        fputs ("continue (y)/n: ", stdout);     /* prompt to continue */
        scanf ("%c", &i);   /* read answer (or '\n' from pressing Enter) */
    }

    printf ("\nfinal list (%zu nodes):", mylist->size);
    print_list (mylist);

    free_list (mylist);     /* don't forget to free memory you allocate */

    return 0;
}

内存使用/错误检查

必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出已分配块的范围,尝试读取或基于未初始化值的条件跳转,最后,确认您释放了所有已分配的内存。

对于Linux (y)是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

$ ./bin/llinshead

enter name: Mickey Mouse
continue (y)/n:

enter name: Donald Duck
continue (y)/n:

enter name: Pluto (the dog)
continue (y)/n:

enter name: Minnie Mouse
continue (y)/n: n

final list (4 nodes): Minnie Mouse, Pluto (the dog), Donald Duck, Mickey Mouse

始终确认已释放已分配的所有内存并且没有内存错误。

仔细看看,如果您有任何其他问题,请告诉我。