c-如何使用带函数的链表



我刚开始使用链表,不太确定我做得是否正确。我正在尝试初始化一个链表,并用.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 %lfn", &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 %lfn", &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 %lfn", temptr->employee.EMP_ID, temptr->employee.name, temptr->employee.dept, temptr->employee.rank, temptr->employee.salary);
          temptr = temptr->next;
          }
}

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

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'd指针之前,您不需要解除对其的隔离,因为函数调用方可能无法推断出他需要在之后释放它以防止内存泄漏,除非他使用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 %lfn", &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找不到要存储在变量中的格式化文本,它只会将"0"存储为float、int、char甚至字符串。因此,如果fscanf扫描一个空行,它只需在所有变量中存储0。。

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

打印功能

void print (struct employeeData *employee){
    for( struct employeeData *temptr = employee; ( temptr != NULL ); temptr = temptr->next )
        printf("%d %s %d %d %lfn", 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;
}

就是这样。

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

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

我可以展示一个模板,你可以在作业中使用它。您必须自己编写函数initialize,并对函数进行微小的更改,以添加它将为节点的数据成员employee的所有字段赋值的内容。

这是模板

#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( "%dn", 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[] );

相关内容

  • 没有找到相关文章

最新更新