【问题标题】:How to use Linked Lists with Functions如何将链表与函数一起使用
【发布时间】:2014-11-15 17:14:34
【问题描述】:

我刚刚开始使用链接列表,我不太确定我是否正确地使用它。我正在尝试初始化一个链接列表并用 .txt 文件中的信息填充它。然后使用打印功能,我只想打印出链接列表中的信息。但是,每次我尝试遍历链表并打印出指针中的内容时,它都会崩溃。任何提示都会有所帮助和赞赏。这是我的代码:

struct employeeData {
   int EMP_ID;
   char name[20];
   int dept;
   int rank;
   double salary;
   };

struct employeeData employee;

struct node {
   struct employeeData employee;
   struct node *next;
   };

struct node *myList = NULL;

struct node initializeList (struct employeeData employee[], struct node myList[]);
void print (struct employeeData employee[], struct node myList[]);

int main () {
    int x;

    initializeList(&employee, &*myList);
    print(&employee, &*myList);

    System("PAUSE");
    return 0;
}

struct node initializeList (struct employeeData employee[], struct node myList[]){
         struct node *newNode = (struct node*)(malloc(sizeof(struct node)));

 FILE *ifp;

 ifp = fopen("empInfo.txt", "r");

 fscanf(ifp, "%d %s %d %d %lf\n", &newNode->employee.EMP_ID, newNode->employee.name, &newNode->employee.dept, &newNode->employee.rank, &newNode->employee.salary);
 //newNode->next = NULL;
 myList = newNode;
 struct node *temptr = myList;

 while (newNode->employee.EMP_ID != 0) {

       fscanf(ifp, "%d %s %d %d %lf\n", &newNode->employee.EMP_ID, newNode->employee.name, &newNode->employee.dept, &newNode->employee.rank, &newNode->employee.salary);

       temptr->next = newNode; 
       newNode->next = NULL;   
       } 

 return *myList;
}

void print (struct employeeData employee[], struct node myList[]){

         struct node *temptr = myList;
                           printf("WOW");

 while(temptr->next!=NULL){
                           printf("WOW");
          printf("%d %s %d %d %lf\n", temptr->employee.EMP_ID, temptr->employee.name, temptr->employee.dept, temptr->employee.rank, temptr->employee.salary);
          temptr = temptr->next;
          }
}

【问题讨论】:

  • 请注意,在调试时,最好通过至少以换行符结束每个printf() 来确保调试输出出现。您甚至可能还需要添加fflush(stdout);。否则,不能保证输出及时出现。 (即使使用换行符,如果输出通过管道传输到诸如less 之类的命令,换行符可能不会将数据强制输出到管道。)

标签: c list function linked-list


【解决方案1】:

您的代码当然是错误的。 我会编写单独的函数 add 将新节点添加到列表中,并编写单独的函数 initialize 使用函数 add 并将文件中的数据附加到列表中。

考虑到函数 add 可以这样写,它要么在列表的开头添加一个新节点,要么在列表的末尾添加一个新节点。如果你用文件中的数据填充列表,那么最好编写函数 add 它将在列表末尾添加一个新节点。

我可以展示一个您可以用于作业的模板。您必须自己编写函数初始化并对函数添加进行细微更改,以便为节点的数据成员员工的所有字段分配值。

这是模板

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

struct employeeData 
{
    int EMP_ID;
    char name[20];
    int dept;
    int rank;
    double salary;
};

struct node 
{
    struct employeeData employee;
    struct node *next;
};

struct node ** add( struct node **list, struct employeeData employee )
{
    struct node *new_node = malloc( sizeof( struct node ) );

    new_node->employee = employee;
    new_node->next = NULL;

    if ( *list == NULL )
    {
        *list = new_node;
        return list;
    }
    else
    {
        ( *list )->next = new_node;
        return &( *list )->next;
    }
}

void clear( struct node **list )
{
    struct node *current = *list;

    while ( current != NULL )
    {
        struct node *tmp = current;
        current = current->next;
        free( tmp );
    }

    *list = NULL;
}

void print( struct node *list )
{
    for ( ; list != NULL; list = list->next )
    {
        printf( "%d\n", list->employee.EMP_ID );
    }
}

int main( void )
{
    struct node *list = NULL;
    struct node **last = &list;
    int i;

    for ( i = 0; i < 10; i++ )
    {
        struct employeeData employee = { i };
        last = add( last, employee );
    }

    print( list );

    clear( &list );

    assert( list == NULL );

    return 0;
}

输出是

0
1
2
3
4
5
6
7
8
9

函数初始化可以声明为

initialize( struct node **list, const char filename[] );

【讨论】:

    【解决方案2】:

    首先,您不必创建两个单独的结构来创建一个链表..
    你可以这样做:

    struct list {
        /* struct members */
        struct list *next;
    }
    

    就这样!此外,在函数中,您应该注意 pointer decay .. 简单来说,就是当您将数组传递给函数时,函数将其作为指针接收(这在技术上是不同的)。处理这个问题的最好方法是接收一个struct employeeData ** data_array 的数组和一个struct employeeData * data 的指针。所以struct employeeData employee[]没有任何意义。另外,这个:&amp;*employee 和这个:employee 完全一样,只是第二个效率更高一些,因为在第一个中你获取了变量的地址然后取消引用它,基本上什么都不做。

    在结构定义中..

    没有必要定义一个名为 node 的结构,因为这只会使程序复杂化并使您感到困惑。链表的实际实现方式是添加一个与其定义的结构相同类型的成员,正如我之前解释的那样。

    这是改进的版本:

    struct employeeData {
       int EMP_ID;
       char name[20];
       int dept;
       int rank;
       double salary;
       struct employeeData *next;
    };
    

    initializeList function ..

    如果您只是要在函数中复制第二个参数,则不需要第二个参数.. 此外,您不需要在返回之前取消对 malloc 的指针的引用,因为函数调用者可能无法推断出他需要在之后释放它以防止内存泄漏,除非他使用类似的工具valgrind ..

    您也不需要调用fscanf 两次。 我还将函数重命名为:getEmployeeData,因为我认为这样更有意义。

    这是改进后的函数的最终形式:

    struct employeeData *getEmployeeData (const char * filename) {
        FILE *ifp = fopen(filename, "r");
        if ( ifp == NULL )
            return NULL;
    
        struct employeeData *employee = malloc(sizeof(struct employeeData));
        struct employeeData *temptr = employee;
    
        int num = (int)getNumberOfLines(filename);
    
        for (int line = 1;
            (fscanf(ifp, "%d %s %d %d %lf\n", &temptr->EMP_ID, temptr->name, &temptr->dept, &temptr->rank, &temptr->salary) == 5)
                && line < num; ++line) {
    
            temptr->next = malloc(sizeof(struct employeeData));
            temptr = temptr->next;
        }
    
        fclose(ifp); /* fopen uses malloc internally */
        return employee;
    }
    

    您可能会注意到(与您的函数版本不同)我这样做:temptr-&gt;next = malloc(sizeof(struct employeeData))。这肯定是您的程序崩溃的原因,这是因为您仅 malloc 节点的第一个元素并尝试对甚至未分配在内存中的结构成员使用 fscanf。这就是为什么你必须在使用它之前分配它。请记住,节点中的每个元素(大部分)都是相互独立的,即使在内存分配中也是如此。

    这个功能更简单,效率更高。您可能还会注意到,我调用了另一个名为 getNumberOfLines 的函数来获取文件中的行数。

    这里是:

    size_t getNumberOfLines(const char * filename) {
        FILE *stream = fopen(filename, "r");
        if ( stream == NULL )
            return EOF;
    
        size_t lines = 0;
        char c;
        while (( c = getc(stream))  != EOF )
            if ( c == '\n' )
                lines++;
        fclose(stream);
        return lines;
    }
    

    我这样做的原因是,如果fscanf 没有找到要存储在变量中的格式化文本,它只会将“0”存储为浮点数、整数、字符甚至字符串。 因此,如果fscanf 扫描了一个空行,它只会在所有变量中存储 0 ..

    为防止这种情况,您必须确保 fscanf 仅扫描占用的行,即使它们的格式不正确,因为检查 fscanf 是否返回 5 的另一个条件(存储所需的变量数in) 如果该行的格式不正确,则不会为真,但如果该行甚至没有被占用,则将返回真(这是我在 gcc 实现中所经历的,如果您不需要它,请将其删除)。

    打印功能

    void print (struct employeeData *employee){
        for( struct employeeData *temptr = employee; ( temptr != NULL ); temptr = temptr->next )
            printf("%d %s %d %d %lf\n", temptr->EMP_ID, temptr->name, temptr->dept, temptr->rank, temptr->salary);
    }
    

    我想我解释了这里应用的所有想法。让我们继续..

    内存释放

    我们需要释放动态内存以防止内存泄漏,而当您尝试释放链表时,这会变得更加棘手!如果您尝试按顺序释放它们,那肯定是行不通的,除非在您的程序运行时发生了一些普遍的巧合!原因很简单。这是因为您可以链接到下一个列表的唯一方法是通过手头结构的成员。显然,如果您从内存中删除所有结构的成员,那么您不知道在哪里寻找下一个列表!一种解决方案是通过递归,如下所示:

    void releaseData(struct employeeData *data) {
    
        /* freeing nodes in reverse because nodes are connected in memory, if you free the first, you lose grasp on the second and the third, resulting in a memory leak .. */
        if (data->next)
            releaseData2(data->next);
        free(data);
    }
    

    但我不喜欢这种方法,因为它会触发为函数及其参数分配内存,然后释放它们,并跟踪调用函数,实际上这完全取决于操作系统和运行内核来确定。如您所见,这种方法大多是可以避免的,并且仅在没有其他方法时使用,这就是我创建此方法的原因:

    void releaseData(struct employeeData *data) {
    
        /* freeing nodes in reverse because nodes are connected in memory, if you free the first, you lose grasp on the second and the third, resulting in a memory leak .. */
    
        struct employeeData * temptr = data;
        int num = 0, first = 1;
    
        while ( temptr != NULL ) {
            if ( temptr->next != NULL ) {
                if (first) {
                    while ( temptr->next != NULL ) {
                        temptr = temptr->next;
                        num++;
                    }
                    first = 0;
                } else {
                    for(int i = 0; i < num - 1; ++i)
                        temptr = temptr->next;
                    num--;
                }
            }
            free(temptr);
            temptr = (num == 0) ? NULL : data;
        }
    
        /* We could have used recursion, but that come with unnecessary overhead and less memory efficiency */
    
    }
    

    如您所见,这个要复杂得多,但效率也要高得多。
    我们使用两个变量来跟踪循环:numfirst

    num 用于计算有多少嵌套节点要通过,因为当我 free 一个指针时,它肯定不是 NULL 所以循环将是无限的,因为它只是检查一个值在那里..

    first 用于指示是否是第一次运行循环,因为如果是,我们肯定不知道其中有多少个节点。

    我认为该函数的其余部分是不言自明的,所以我将其留给您自己弄清楚。

    主要功能

    int main () {
        /* Never dereference a malloc'd pointer before freeing it, so we'll get the pointer returned from the function as is */
        struct employeeData *employee = getEmployeeData("empInfo.txt"); /* Getting employee data in the struct */
        print(employee); /* Printing them out */
        releaseData(employee); /* Freeing Dynamic Memory */
        return 0;
    }
    

    就是这样。

    【讨论】:

    • 谢谢!这真的很有帮助!一个问题,如果我确实将它保存在两个结构中,就像我对列表一样,我是否将正确的参数传递给我的打印函数?
    • @user3281576:如果你真的想使用两个结构来实现像链表这样简单的功能,那么我建议你保留指向另一个结构的指针(在这种情况下为employeeData)而不是像您在 node 结构的初始版本中所做的那样需要一个副本。如果您想从代码中未定义的结构(例如 FILE 结构(如果您想在文件之间链接))创建链接列表,那么创建包装节点结构当然很有用。
    • 关于传递参数的问题,您应该只传递节点结构,因为它充当另一个之上的包装器。
    • 我吃得听起来很固执,只是指导方针说我必须有两个结构。我想我正确地更新了我的两个函数,但我没有放入 freeMemory 函数。我认为程序正确地将数据读入链表,因为如果我在 Initilize 函数中打印数据,它会正确打印,但是当我尝试在打印函数中打印它时它不起作用并崩溃。另外,如果我将打印语句放在您创建的 for 循环中,它甚至不会运行一次 for 循环。
    • 这里是打印功能:void print (struct node *myList) { for( struct node *temptr = myList; ( temptr != NULL ); temptr = temptr-&gt;next ){ printf("WOW"); printf("%d\t%s\t%d\t%d\t%lf\n", temptr-&gt;employee.EMP_ID, temptr-&gt;employee.name, temptr-&gt;employee.dept, temptr-&gt;employee.rank, temptr-&gt;employee.salary); }
    猜你喜欢
    • 2019-08-20
    • 1970-01-01
    • 2020-04-24
    • 2022-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多