如何使用带有函数的链接列表

时间:2014-09-21 17:56:53

标签: c list function linked-list

我刚刚开始使用链接列表,但我不确定我是否正确地执行了此操作。我正在尝试初始化链表并使用.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;
          }
}

2 个答案:

答案 0 :(得分:2)

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

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

就是这样!另外,在函数中你应该知道指针衰减 ..简单来说,当你将一个函数传递给一个数组时,该函数将它作为指针接收(这在技术上是不同的) 。处理此问题的最佳方法是将数组作为struct employeeData ** data_array接收,将指针接收为struct employeeData * data。所以struct employeeData employee[]没有任何意义。另外,这个:&*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->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没有找到要存储在变量中的格式化文本,它只是将&#34; 0&#34 ;,存储为浮点数,一个int ,一个字符串甚至一个字符串。 因此,如果fscanf扫描一个空行,它只会在所有变量中存储0 ..

为防止这种情况发生,您必须确保fscanf仅扫描占用的行,即使它们格式不正确,因为检查fscanf的其他条件是否返回5(如果行没有正确格式化,那么存储所需的变量数量将不正确但是如果该行甚至没有被占用则返回true(这是我在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);
}

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

内存释放

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

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;
}

那就是它。

答案 1 :(得分:1)

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

考虑到函数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可以声明为

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