用C语言单链表实现的一个DotA2英雄管理系统(其实我一直羞于承认这算一个系统。。)

来源:互联网 发布:中国乳业数据 编辑:程序博客网 时间:2024/04/30 01:44
  学校这两周安排的一个小实习,用C语言做一个小的管理系统,很多学校应该都要求做过这个吧。
  我本人是一名降级生,以前也做过,但并不是自己做的,而是把高年级的拿来糊弄过去了-_-
  这次我打算自己写,毕竟以后真的打算当一名好的程序员(我是真的对此感兴趣)。C语言也自学过有与段时间了,这个小系统的其实也没什么难度,然后就动手了,用周末两天的时间写了出来。

  我是用链表实现数据的存储的,这个链表的头文件和实现,我用的是《C primer plus》上的代码,然后做了一点小小的改动,并且加入了删除节点的功能。

  我是用的使VS2015环境,而学校的机房用的是VC++6.0,有些函数不太一样,如scanf()函数在VS2015要改成scanf_s()fscanf()一样也要改成fscanf_s(),输入整数和浮点数数据时,这几个函数基本是一样的,但在输入字符串时有一点区别,scanf_s()和fscanf_s()需要第三个参数来确定缓冲区的大小。如下所示:

scanf("%s", hero_name)scanf_s("%s", hero_name, <span style="color:#ff0000;">_BUFFER_</span>)<span style="color:#ff0000;background-color: rgb(204, 204, 204);">//scanf_s()需要第三个参数确定缓冲区大小,在这里我定义_BUFFER_为20</span>

  除了这个区别外,VS2015还不允许使用fopen()函数,一会我写到文件的读取和写入时再讲。

  下面就先写一下链表的头文件与实现。

  下面是链表的头文件。

  下面是链表的实现。

  接下来就开始写主程序。主函数main()里面使用菜单选项,根据不同的输入进入到不同的函数进行相应的操作,最后退出时将链表中的数据保存到文件中。

  下面展示其中的一个操作,其他的操作都大同小异。

//删除一个英雄void delete_hero(LIST *plist){char hero_name[_BUFFER_];NODE *scan;<span style="color:#ff0000;">int flag = 1;//设置一个标志,用来表示是否找到英雄,初始值为1</span>if (list_is_empty(plist)){printf("\t\t没有英雄数据,请先添加英雄\n");return;}elsescan = *plist;printf("\t\t请输入您要删除的英雄(输入ctrl+z返回):\n\t\t");while (scanf_s("%s", hero_name, _BUFFER_) == 1){while (scan != NULL){if (strcmp(scan->hero.name, hero_name))scan = scan->next;else{if (delete_item(scan->hero, plist)){<span style="color:#ff0000;">save_to_file(plist);</span>printf("\t\t英雄删除成功!\n");<span style="color:#ff0000;">flag = 0;//若找到英雄,将标志值设置为0</span>break;}else{printf("\t\t英雄删除失败!\n");break;}}}<span style="color:#ff0000;">if (flag)//若标志值为1,表示没有找到英雄printf("\t\t没有这个英雄\n");</span><span style="color:#ff0000;">flag = 1;//下一次寻找时,将标志值重置为1</span>printf("\t\t请继续输入您要删除的英雄(输入ctrl+z返回):\n\t\t");scan = *plist;}}

  个人觉得比较巧妙的一个地方在于,使用了一个flag标志来判断是否找到对应的数据,其实这也是很常见的一种办法。还有,在每次应用这个函数后,都会将链表保存到文件一次(其实有点多余了,因为退出程序时也会保存到问价一次,也懒得改了。。)

  下面是如何保存到文件。

//将链表保存到文件中void save_to_file(const LIST *plist){FILE *fp;NODE *scan = *plist;fp = <span style="color:#ff0000;">_fsopen</span>("HERO.txt", "w", <span style="color:#ff0000;">_SH_DENYNO</span>);if (fp == NULL){printf("\t\t打开文件失败!\n");return;}fprintf(fp, "名称        主属性    攻击    护甲    移速    力量    敏捷    智力\n");while (scan){fprintf(fp, "%-10s", scan->hero.name);fprintf(fp, "%8s", scan->hero.main_attribute);fprintf(fp, "%8d", scan->hero.attack);fprintf(fp, "%8.2f", scan->hero.armor);fprintf(fp, "%8d", scan->hero.velocity);fprintf(fp, "%8d", scan->hero.ATTRIBUTE.power);fprintf(fp, "%8d", scan->hero.ATTRIBUTE.agile);fprintf(fp, "%8d\n", scan->hero.ATTRIBUTE.intelligence);scan = scan->next;}fclose(fp);}
  接下来是从文件中读出数据存入一个新的链表,每次运行程序时,都要重新创建链表,并从文件中读入数据。

//把文件中的数据导入到链表中LIST file_to_list(void){LIST new_list = (LIST)malloc(sizeof(NODE));FILE *fp;NODE *node_a, *node_b;HERO new_hero;node_a = node_b = new_list;fp = <span style="color:#ff0000;">_fsopen</span>("HERO.txt", "r", <span style="color:#ff0000;">_SH_DENYNO</span>);if (fp == NULL){printf("\t\t打开文件失败!\n");return NULL;}<span style="color:#ff0000;">fseek(fp, 66L, SEEK_SET);//跳过文件的第一行,从第二行开始读取数据到链表</span>if (file_to_hero(fp, &new_hero) != EOF)new_list->hero = new_hero;elsereturn NULL;while (<span style="color:#ff0000;">file_to_hero(fp, &new_hero) != EOF</span>){node_a = (NODE *)malloc(sizeof(NODE));node_a->hero = new_hero;node_b->next = node_a;node_b = node_a;}node_b->next = NULL;fclose(fp);return new_list;}<span style="color:#ff0000;">//从文件中导入一个英雄的数据</span><span style="color:#ff0000;">int file_to_hero(FILE *fp, HERO *hero)</span>{if (fscanf_s(fp, "%s", hero->name, _BUFFER_) == EOF)return EOF;if (fscanf_s(fp, "%s", hero->main_attribute, _BUFFER_) == EOF)return EOF;if (fscanf_s(fp, "%d", &hero->attack) == EOF)return EOF;if (fscanf_s(fp, "%f", &hero->armor) == EOF)return EOF;if (fscanf_s(fp, "%d", &hero->velocity) == EOF)return EOF;if (fscanf_s(fp, "%d", &hero->ATTRIBUTE.power) == EOF)return EOF;if (fscanf_s(fp, "%d", &hero->ATTRIBUTE.agile) == EOF)return EOF;if (fscanf_s(fp, "%d", &hero->ATTRIBUTE.intelligence) == EOF)return EOF;elsereturn 1;}

  我还单独写了一个函数来只读入一个节点的数据,这样就使上面的函数看起来更加整洁。

  因为文件中的第一行不是链表需要的数据,而是标识,所以我用fseek()函数是文件指针指向下一行,从下一行开始读取。


  可以看到我读写文件使用的函数为_fsopen()函数,而不是VC++6.0中的fopen()函数,也是因为被VS2015告知fopen()函数不安全。这里还出现了一点小问题,导致我花费了差不多一个多小时。

  最开始VS2015提醒我使用更安全的fopen_s()函数,我百度了一下发现fopen_s()函数的调用方式和fopen()是不一样的,如下所示:

FILE *fp;int err;fp = fopen("123.txt","r");//fopen()返回一个文件指针,直接赋给fperr = fopen_s(&fp,"123.txt","r");//fopen_s()的第一个参数为指向文件的指针的指针,并返回一个errno_t类型的值
  fopen_s()的第一个参数为一个指向文件的指针的指针,返回的是一个errno_t类型的值,由于我百度出来说errno_t属于一种整型数据,于是我就用int代替了。如果文件打开成功,fopen_s()返回值为0,打开失败为非零,而且不同的错误会返回不同的值。这可能有点反直觉,一般好像都是成功返回1,失败返回0。

  而我的问题也就出在这个函数上,我用fopen_s()函数代替fopen()函数后,发现程序可以将文件中的数据读入链表,退出时却不能将链表中的数据保存到文件。我在这里卡住了很长时间,大约有一个多小时,最后我用一个printf()函数打印出了err的值,发现err的值为13,我用百度查询fopen_s()函数返回值为13属于什么错误时,终于找到了原因。

  原来用fopen_s()函数打开的文件是不能共享的,每次运行程序时都先将数据从文件读入到链表中,所以当我想在存入时,使用的时另外一个函数,这时就无法打开文件了。而这个返回值13就是关于无法共享文件的一个错误代码。

  解决方法就是使用_fsopen()函数,_fsopen()函数的使用方法和fopen()很相似,但是需要第三个参数来确定共享方式。

FILE *fp;fp = fopen("123.txt","r");fp = _fsopen("123.txt","r",_SH_DENYNO);//第三个参数确定共享方式,_SH_DENYNO表示共享读和写
  可以看到_fsopen()使用了第三个参数来确定共享方式,而对应的常量定义在<share.h>头文件中,需要在程序的开头使用#include包含这个头文件。

  而不同的参数代表不同的共享方式。

  以下来自百度百科:

_SH_COMPAT
Sets Compatibility mode for 16-bit applications.
_SH_DENYNO
Permits read and write access.
_SH_DENYRD
Denies read access to the file.
_SH_DENYRW
Denies read and write access to the file.
_SH_DENYWR
Denies write access to the file.
现在回到主程序中来。
接下来就只剩排序功能了,我定义的结构体中每个英雄有六个不同的参数,我可以根据这六个参数来进行不同的排序。我本来只想写一个通用的排序函数,通过传递一个额外的参数来确定应该按那个英雄的参数来进行排序,但是我并没有找到办法来实现,所以我就很笨的写了六个排序函数,每个函数都是基本一样的,只是在比较的时候使用的是结构体中不同的数。
下面是按照攻击力大小排序,其他的类似:
//按攻击力大小排序void sort_as_attack(LIST *plist){NODE *current, *scan;HERO trans;if (list_is_empty(plist))return;current = *plist;scan = current->next;while (current){while (scan){if (<span style="color:#ff0000;">current->hero.attack < scan->hero.attack</span>)<span style="color:#ff0000;">//其他五个排序唯一的不同是在这里使用不用的结构体中的值</span>{trans = current->hero;current->hero = scan->hero;scan->hero = trans;}scan = scan->next;}current = current->next;if (current)scan = current->next;}}


  主要的函数已经写完了,下面贴上完整的代码:
  

总结:
    这次是我第一次独立写这么长的程序,中间出现过许多的问题,也都慢慢解决了,有的看书,有的通过百度,发现自己需要学的实在太多,一定要努力学习。当然我觉得我还是对写代码这件事保留着很大的兴趣,这应该也是唯一的有点了吧。剩下的两周可以轻松一点了,在别人上机的时候可以复习复习,准备期末考试了,加油吧。


  参考资料:

   http://www.cnblogs.com/chenkunyun/archive/2012/04/20/2459767.html












0 0
原创粉丝点击