基于Linux中的通讯录管理系统(C语言+双向循环链表+MySQL)

    技术2024-07-03  79

    文章目录

    系统功能实现相关软件前提部分重要的功能代码Makefilemenu.cMySQL API函数使用C语言连接MySQL:调用SQL命令 双向循环列表功能 效果实现

    这个通讯录管理系统是我5月份对于C语言和数据结构复习做的一个小项目和总结,后来这几天对数据库起了兴趣自己又重新补上了数据库(MySQL)的部分,让通讯录管理系统真正地可以存储数据。

    基于自己还是大学生的关系,代码的逻辑方面和实现方面可能有所出错,如果有哪里不对的地方请大家在评论写上,我会一一回复。

    系统功能实现

    通讯录联系人信息有:姓名(中文)、公司、职位、电话号码、备注可对通讯录联系人实现增、删、查、改操作支持中文联系人输入可实现电话号码与姓名查找联系人通过姓名拼音进行排序有快速浏览联系人的功能增加、删除、修改联系人的时候,会将数据放到数据库中存储,下次重新打开程序时,会将数据库表中的数据重新加载到链表上

    相关软件前提

    编程语言:C语言,并使用Ubuntu中的gcc编译器进行编译和链接操作系统:Ubuntu18.04版本Ubuntu下要安装MySQL数据库,详细安装步骤可以查看我写的另一篇文章: Ubuntu18.04安装Mysql8.0的详细步骤.注意要提前安装好Linux下的一个库: 安装这个库之后,/usr/include文件夹内才有mysql文件夹,里面带了mysql.h这个含有MySQL官方的C语言API接口函数的头文件。我们要使用这个头文件调用里面的接口从而访问到MySQL数据库。

    部分重要的功能代码

    详细的代码可以参考下面的附录,也可以直接在下面的链接进行下载。 这里只是挑比较重要的代码进行部分讲解。

    Makefile

    使用了Makefile来实现自动化编译。

    -I选项是指定了mysql.h头文件所在的位置 mysql.h是MySQL官方提供的一些C语言函数的库,没有这个头文件我们无法将自己的程序与MySQL连接起来。

    -L是指定库的所在目录,我们要用到MySQL的库,也就是它的某个dll文件

    test:main.o list.o keyboard.o menu.o gcc -o test $^ -I/usr/include/mysql -L/usr/lib/mysql -lmysqlclient %.o:%.c gcc -c -o $@ $< -I/usr/include/mysql -L/usr/lib/mysql -lmysqlclient .PHONY: clean: rm -r *.o *.c *.h

    menu.c

    menu.c上面只有一个menu函数,用来输出总菜单提示用户通过键盘输入菜单值,从而到达不同的菜单里面。 menu.h:

    #ifndef __MENU_H__ #define __MENU_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> //菜单 int menu(void); #endif

    menu.c:

    #include "menu.h" int menu(void) { int menu = 0;//菜单值 printf("------欢迎来到通讯录系统------\n"); printf("------1.增加联系人------\n"); printf("------2.删除联系人------\n"); printf("------3.查询联系人------\n"); printf("------4.修改联系人------\n"); printf("------5.快速浏览联系人------\n"); printf("------6.退出通讯录------\n"); printf("请输入菜单值:"); scanf("%d",&menu); return menu; }

    MySQL API函数使用

    C语言连接MySQL:

    mysql_init函数:用来初始化一个MySQL连接的实例对象

    MYSQL *sql;//创造一个MySQL句柄 sql = mysql_init(NULL);//初始化一个MySQL连接的实例对象 if(sql == NULL)//如果内存不足返回一个NULL { printf("初始化MySQL数据库连接失败!\r\n"); return -1; }

    使用mysql_real_connect函数与MySQL的某个数据库进行连接: 注意:连接之前你要在MySQL的中创建好你要连接的库,否则有可能会连接失败。建库的命令请上网自行搜索(我也是百度来的)。

    /*用MySQL句柄与MySQL数据库发生连接*/ /*MySQL句柄 IP地址 用户名 用户名密码 要连接的数据库 端口 套接字 flag选项*/ sql = mysql_real_connect(sql,"localhost","root","123456","address_list",0,NULL,0); if(sql == NULL)//连接失败返回NULL { printf("连接MySQL数据库失败!失败原因:%s\r\n",mysql_error(sql)); return -1; } else//连接成功 { printf("连接MySQL数据库成功!\r\n"); } //..........

    在主函数最后记得用mysql_close()函数来释放句柄

    mysql_close(sql);//释放一个MySQL句柄

    调用SQL命令

    调用sql命令实际上使用了mysql_query函数:

    int mysql_query(MYSQL* mysql,const char* query)

    query是SQL命令的字符串,我是用sprintf格式化了字符串SQL语句。 如果调用成功,返回0,如果出现错误,返回非0值。

    插入数据到数据库

    /*插入数据到数据库*/ void insert_into_mysql(MYSQL *sql,char *name,char *company,char *work,char *remarks,unsigned long phonenumber) { char qs[1000];//用来存放mysql命令 sprintf(qs,"insert into test values('%s','%s','%s','%s','%ld')",name,company,work,remarks,phonenumber); if(mysql_query(sql,qs)!=0)//返回值不等于0,操作失败 { printf("增加失败!\r\n"); exit(1);//直接退出进程 } else { printf("插入数据到数据库成功!\r\n"); } }

    根据姓名删除数据库表中某一行

    /*根据姓名删除MySQL数据库数据*/ void delete_name_mysql(MYSQL *sql,char *name) { char qs[100];//存放MySQL命令 sprintf(qs,"delete from test where 姓名='%s'",name); if(mysql_query(sql,qs) != 0) { printf("根据姓名删除MySQL数据时失败!\r\n"); exit(1);//退出异常进程 } else { printf("根据姓名删除MySQL数据时成功!\r\n"); } }

    根据电话号码删除数据表中某一行

    /*根据电话号码删除MySQL表中数据*/ void delete_phonenumber_mysql(MYSQL *sql,unsigned long phonenumber) { char qs[100];//存储MySQL命令 sprintf(qs,"delete from test where 电话号码='%ld'",phonenumber); if(mysql_query(sql,qs) != 0) { printf("根据电话号码删除MySQL数据时失败!\r\n"); } else { printf("根据电话号码删除MySQL数据时成功!\r\n"); } }

    修改数据库表中姓名为xx的某一行

    /*根据姓名更新MySQL数据表*/ void update_name_mysql(MYSQL *sql,char *name,char *company,char *work,char *remarks,unsigned long phonenumber) { char qs[1000];//存储的sql语句 sprintf(qs,"update test set 公司='%s',职位='%s',备注='%s',电话号码='%ld' where 姓名='%s'",company,work,remarks,phonenumber,name); if(mysql_query(sql,qs) != 0) { printf("根据姓名更改MySQL信息失败!\r\n"); exit(1); } else { printf("根据姓名更改MySQL信息成功!\r\n"); } }

    修改数据库表中电话号码为xx的某一行

    /*根据电话号码修改MySQL数据表*/ void update_phonenumber_mysql(MYSQL *sql,char *name,char *company,char *work,char *remarks,unsigned long phonenumber) { char qs[1000];//存储的sql语句 sprintf(qs,"update test set 姓名='%s',公司='%s',职位='%s',备注='%s' where 电话号码='%ld'",name,company,work,remarks,phonenumber); if(mysql_query(sql,qs) != 0) { printf("根据电话号码更改MySQL信息失败!\r\n"); exit(1); } else { printf("根据电话号码更改MySQL信息成功!\r\n"); } }

    清空数据库表中所有数据

    /*清空MySQL数据表中的所有数据*/ void delete_all_mysql(MYSQL *sql) { char qs[100];//存储MySQL命令 sprintf(qs,"TRUNCATE TABLE test"); if(mysql_query(sql,qs)!= 0) { printf("删除MySQL表中所有数据失败!\r\n"); exit(1); } else { printf("删除MySQL表中所有数据成功!\r\n"); } }

    查询数据表

    我程序并没有调用这个函数,但功能是可以实现的

    /*查询数据表*/ void select_mysql(MYSQL *sql) { char qs[100];//用来存放mysql命令 MYSQL_RES *result;//用于存放查询的全部结果 MYSQL_ROW row;//用来保存一行中的结果 /*以名字拼字升序查询数据库表test*/ sprintf(qs,"select * from test order by CONVERT(姓名 USING GBK) asc"); if(mysql_query(sql,qs)!= 0)//出现错误,返回非0值 { printf("查询数据表失败!\r\n"); exit(1);//异常退出 } else { if((result = mysql_store_result(sql)) == NULL)//出现错误返回NULL { printf("保存结果集失败!\r\n"); exit(1); } else { printf("\n名字\t公司\t职位\t备注\t电话号码\r\n"); while ((row = mysql_fetch_row(result)) != NULL) { printf("%s\t%s\t%s\t%s\t%s\r\n",row[0],row[1],row[2],row[3],row[4]); } printf("\r\n"); } } memset(qs,'\0',100);//清空qs字符串 mysql_free_result(result);//释放查询的所有结果 }

    读取数据表中数据并将每一行的数据放入链表节点里面 这个函数是在程序运行一开始时使用,来读取MySQL数据库中test表的数据,并将每一行数据放入链表内。

    /*将读取到的内容放入链表的函数*/ void list_add_for_read_mysql(node_pt head,char *name,char *company,char *work,char *marks,char *phonenumber) { unsigned long phone_number; /*初始化节点*/ node_pt new_node = malloc(sizeof(node_st)); if(new_node == NULL) { printf("error!读取MySQL数据时新节点初始化失败!\n"); free(new_node); return ; } node_pt pos = head; /*将读取到的名字、公司、职位、备注、电话号码等信息赋给新节点*/ strcpy(new_node->name,name); strcpy(new_node->company,company); strcpy(new_node->work,work); strcpy(new_node->remarks,marks); phone_number = atol(phonenumber);//将字符串转换为长整型 new_node->phone_number = phone_number; new_node->next = pos->next; new_node->prev = pos->next->prev; pos->next->prev = new_node; pos->next = new_node; } /*从MySQL读取数据到链表*/ void read_for_mysql(MYSQL *sql,node_pt head) { int i = 0; char qs[100];//用来存放mysql命令 MYSQL_RES *result;//用于存放查询的全部结果 MYSQL_ROW row;//用来保存一行中的结果 /*以名字拼字升序查询数据库表test*/ sprintf(qs,"select * from test order by CONVERT(姓名 USING GBK) asc"); if(mysql_query(sql,qs)!= 0)//出现错误,返回非0值 { printf("查询数据表失败!\r\n"); exit(1);//异常退出 } else { if((result = mysql_store_result(sql)) == NULL)//出现错误返回NULL { printf("保存结果集失败!\r\n"); exit(1); } else { while ((row = mysql_fetch_row(result)) != NULL) { //调用这个函数将表中的每一行数据放进链表 list_add_for_read_mysql(head,row[0],row[1],row[2],row[3],row[4]); i++; } printf("\r\n"); } } printf("读出数据库内一共有%d位联系人!\r\n",i); memset(qs,'\0',100);//清空qs字符串 mysql_free_result(result);//释放查询的所有结果 }

    调用MySQL接口函数实际上非常简单,参考着上面的程序基本没有什么问题。

    双向循环列表功能

    初始化头节点:

    /*初始化头节点*/ node_pt list_init(void) { node_pt new_list = malloc(sizeof(node_st));//分配空间 if(new_list == NULL) { printf("error!头节点初始化失败!\n"); free(new_list); return NULL; } else { new_list->next = new_list; new_list->prev = new_list; //printf("头节点初始化成功!\n"); } return new_list; }

    判断节点个数:

    /*判断结点个数*/ int list_sum(node_pt head) { int i = 0; node_pt pos = head->next; while(pos!=head) { i++; pos = pos->next; } return i; }

    头插法插入数据到节点:同时也插入数据到数据库

    /*头插*/ int list_add_head(MYSQL *sql,node_pt head) { int flag; node_pt new_node = malloc(sizeof(node_st)); if(new_node == NULL) { printf("error!新节点初始化失败!\n"); free(new_node); return -1; } node_pt pos = head; flag = get_string(head,new_node->name,new_node->company,new_node->work,new_node->remarks);//获取字符串类型的数据 if(flag == -1) { printf("插入数据失败!\r\n"); return -1; } new_node->phone_number = get_phonenum();//获取电话号码 new_node->next = pos->next; new_node->prev = pos->next->prev; pos->next->prev = new_node; pos->next = new_node; /*插入刚刚的节点数据到MySQL*/ insert_into_mysql(sql,new_node->name,new_node->company,new_node->work,new_node->remarks,new_node->phone_number); return 0; }

    显示所有节点:

    /*遍历*/ void list_show(node_pt head) { node_pt pos = head->next; int i; for(i = 1; pos!= head; pos = pos->next,i++) { printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); } }

    按姓名查找节点数据:

    /*按姓名查找*/ void list_search_for_name(node_pt head) { node_pt pos = head->next; int i = 1; char flag = 0; char name[100]; printf("请输入你要查找的姓名:"); scanf("%s",name); while(pos != head) { if(strcmp(name,pos->name) == 0) { printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); i++; flag = 1; pos = pos->next; continue; } pos = pos->next; i++; } if(flag == 0) { printf("找不到是该姓名的联系人!\n"); } }

    按电话号码查找数据:

    /*按电话号码查找*/ void list_search_for_phonenumber(node_pt head) { node_pt pos = head->next; unsigned long phone_number; int i = 1; char flag = 0; printf("请输入你要查找的电话号码:"); scanf("%ld",&phone_number); while(pos != head) { if(pos->phone_number == phone_number) { printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); i++; flag = 1; pos = pos->next; continue; } pos = pos->next; i++; } if(flag == 0) { printf("找不到是该电话号码的联系人!\n"); } }

    按姓名删除节点数据:同时MySQL数据库根据姓名删除数据库表中某一行

    /*按姓名删除*/ void list_delete_for_name(MYSQL *sql,node_pt head) { node_pt pos; char name[100];//存储输入的名字 char flag; int i = 1; printf("输入你要删除的姓名:"); scanf("%s",name); /*从头结点的下一个结点开始找是否有同名的*/ for(pos = head->next; pos != head; pos = pos->next,i++) { if(strcmp(name,pos->name) == 0)//如果找到同名的,立即退出循环 { break; } if(i > list_sum(head))//如果找的次数比节点个数多 { printf("根据姓名删除节点寻找同名节点时发生错误!\n"); return ; } } if(pos == head)//如果没有找到同名的节点数据 { printf("没有找到该姓名的联系人!\n"); return ; } /*如果找到*/ printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("已找到该姓名%s,该节点位与%d位,是否删除(y/n):",pos->name,i); flag = getchar(); scanf("%c",&flag); if(flag == 'y')//y删除 { if(i == 1)//删除第一个 { head->next=pos->next; pos->next->prev=head; printf("删除姓名为%s的联系人成功!\n",pos->name); delete_name_mysql(sql,pos->name); free(pos); } else//删除除了第一个之外的其它 { pos->prev->next = pos->next; pos->next->prev = pos->prev; printf("删除姓名为%s的联系人成功!\n",pos->name); delete_name_mysql(sql,pos->name); free(pos); } } else if(flag == 'n')//n不删除 { return ; } }

    按电话号码删除节点数据:同时MySQL也根据电话号码删除数据表中某一行

    /*按电话号码删除*/ void list_delete_for_phonenumber(MYSQL *sql,node_pt head) { node_pt pos; unsigned long phone_number;//存储输入的电话号码 char flag; int i = 1;//节点下标 printf("输入你要删除的电话号码的联系人:"); scanf("%ld",&phone_number); /*从头节点的下一个节点进行寻找*/ for(pos = head->next; pos != head; pos = pos->next,i++) { if(pos->phone_number == phone_number)//找到同电话号码的,退出循环 { break; } if(i > list_sum(head))//如果找的次数比节点个数多 { printf("根据电话号码删除节点寻找同电话号码节点时发生错误!\n"); return ; } } if(pos == head)//如果找不到同电话号码的联系人 { printf("没有找到该电话号码的联系人!\n"); return ; } printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("已找到该电话号码%ld,该节点位与%d位,是否删除(y/n):",pos->phone_number,i); flag = getchar(); scanf("%c",&flag); if(flag == 'y')//删除 { if(i == 1)//删除第一个 { head->next=pos->next; pos->next->prev=head; printf("删除电话号码为%ld联系人成功!\n",pos->phone_number); delete_phonenumber_mysql(sql,pos->phone_number); free(pos); } else//删除其它 { pos->prev->next = pos->next; pos->next->prev = pos->prev; printf("删除电话号码为%ld联系人成功!\n",pos->phone_number); delete_phonenumber_mysql(sql,pos->phone_number); free(pos); } } else if(flag == 'n') { return ; } }

    修改链表中姓名为xx的某个节点的数据:同时也修改数据库表中姓名为xx的某一行:

    /*按姓名修改*/ void list_motify_name(MYSQL *sql,node_pt head) { int i = 1; char flag;//存放y or n char name[100];//存放输入的姓名 char company[100]; char work[100]; char remarks[200]; unsigned long phonenumber; node_pt pos; printf("输入你要修改的姓名:"); scanf("%s",name); /*从头结点的下一个结点开始找是否有同名的*/ for(pos = head->next; pos != head; pos = pos->next,i++) { if(strcmp(name,pos->name) == 0)//如果找到同名的,立即退出循环 { break; } if(i > list_sum(head))//如果找的次数比节点个数多 { printf("根据姓名修改节点寻找同名节点时发生错误!\n"); return ; } } if(pos == head)//如果没有找到同名的节点数据 { printf("没有找到该姓名的联系人!\n"); return ; } /*如果找到*/ printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("已找到该姓名%s的联系人节点,该节点位与%d位,是否修改(y/n):",pos->name,i); flag = getchar(); scanf("%c",&flag); if(flag == 'y') { printf("输入公司:"); scanf("%s",company); printf("输入职位:"); scanf("%s",work); printf("输入备注:"); scanf("%s",remarks); printf("输入电话号码:"); scanf("%ld",&phonenumber); strcpy(pos->company,company); strcpy(pos->work,work); strcpy(pos->remarks,remarks); pos->phone_number = phonenumber; update_name_mysql(sql,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("根据姓名修改节点信息成功!\r\n"); } else if(flag == 'n') { printf("不修改节点名字为%s的联系人\r\n",pos->name); return ; } }

    修改链表中电话号码为xx的某个节点的数据:同时也修改数据库表中电话号码为xx的某一行:

    /*按电话号码修改*/ void list_motify_phonenumber(MYSQL *sql,node_pt head) { int i = 1; char flag;//存放y or n char name[100]; char company[100]; char work[100]; char remarks[200]; unsigned long phonenumber;//存放输入的电话号码 node_pt pos; printf("输入你要修改的电话号码的联系人节点:"); scanf("%ld",&phonenumber); /*从头节点的下一个节点进行寻找*/ for(pos = head->next; pos != head; pos = pos->next,i++) { if(pos->phone_number == phonenumber)//找到同电话号码的,退出循环 { break; } if(i > list_sum(head))//如果找的次数比节点个数多 { printf("根据电话号码修改节点寻找同电话号码节点时发生错误!\n"); return ; } } if(pos == head)//如果找不到同电话号码的联系人 { printf("没有找到该电话号码的联系人!\n"); return ; } printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("已找到该电话号码%ld的联系人节点,该节点位与%d位,是否修改(y/n):",pos->phone_number,i); flag = getchar(); scanf("%c",&flag); if(flag == 'y') { printf("输入姓名:"); scanf("%s",name); printf("输入公司:"); scanf("%s",company); printf("输入职位:"); scanf("%s",work); printf("输入备注:"); scanf("%s",remarks); strcpy(pos->name,name); strcpy(pos->company,company); strcpy(pos->work,work); strcpy(pos->remarks,remarks); update_phonenumber_mysql(sql,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); printf("根据电话号码修改联系人节点信息成功!\r\n"); } else if(flag == 'n') { printf("不修改电话号码为%ld的联系人节点\r\n",pos->phone_number); return ; } }

    清空所有节点函数:同时MySQL数据库也清空表中的所有数据

    /*清空所有联系人*/ void list_delete_all(MYSQL *sql,node_pt head) { node_pt pos = head->next; node_pt temp; while(pos!= head) { temp = pos->next; free(pos); pos = temp; } free(head); /*清空MySQL表中所有数据*/ delete_all_mysql(sql); }

    排序函数: 每次进行插入节点、删除节点、修改节点时,最后都会调用这个函数以节点的姓名拼音进行升序排序。

    /*对链表中所有数据根据姓名的首字拼音进行排序*/ void sort(node_pt head) { setlocale(LC_ALL, "");//配置地域信息 使用系统默认设置 int i = 0;//infor数组的下标 int j; node_pt pos = head->next;//指向头节点的下一个节点 while(pos != head) { /*将名字、公司、职位、备注、电话号码全部复制一遍*/ strcpy(infor[i].name,pos->name); strcpy(infor[i].company,pos->company); strcpy(infor[i].work,pos->work); strcpy(infor[i].remarks,pos->remarks); infor[i].phone_number = pos->phone_number; i++;//下标+1 pos = pos->next; } printf("通讯录人数:%d\n",i);//打印出通讯录人数 /*下面三种情况皆可 *通过C语言编译器函数库自带的排序函数qsort对infor数组进行排序 */ //qsort(infor[0].name,i,sizeof(informations),(void *)strcoll); qsort(infor,i,sizeof(informations),(void *)strcoll); //qsort(&infor[0],i,sizeof(informations),(void *)strcoll); pos = head->next;//重新指向头节点 i = 0;//infor数组下标置0 while(pos != head) { /*将节点的所有字符串类型数据清空(名字、公司、职位、注释)*/ memset(pos->name,0,sizeof(pos->name)); memset(pos->company,0,sizeof(pos->company)); memset(pos->work,0,sizeof(pos->work)); memset(pos->remarks,0,sizeof(pos->remarks)); /*将排序好的数据重新赋予给各节点*/ pos->phone_number = infor[i].phone_number; strcpy(pos->name,infor[i].name); strcpy(pos->company,infor[i].company); strcpy(pos->work,infor[i].work); strcpy(pos->remarks,infor[i].remarks); i++; pos = pos->next; } pos = head->next; i = 1; while(pos != head) { printf("%d:姓名:%s\t公司:%s\t职位:%s\t备注:%s\t电话号码:%ld\n",i,pos->name,pos->company,pos->work,pos->remarks,pos->phone_number); pos = pos->next; i++; } }

    效果实现

    进入程序: 增加节点数据: 数据库里面也有数据: 查询节点数据: 之前我已经提前添加节点数据。 查询所有节点: 按照姓名查询单个节点: 按照电话号码查询单个节点: 按姓名删除节点: 数据库内也被删除:

    修改节点: 数据库中也被修改 退出重新进入后,数据依然存在,并重新加载到节点里面: 按电话号码删除节点: 数据库中也被删除:

    其它功能我就不一一验证了,大家可以下载验证(下载不用钱)。

    下载地址:通讯录管理系统.zip(Linux-C语言+数据结构+MySQL). https://download.csdn.net/download/weixin_41363159/12574223

    Processed: 0.014, SQL: 9