前言
在视频学习时看到一个使用C语言模拟实现通讯录的题目,通讯录包含若干个人的信息——姓名、电话、年龄、地址、性别。且要求这个通讯录能够实现添加联系人、删除联系人、查找联系人、修改联系人、显示所有联系人、排序联系人功能。不难看出最有挑战性的当属排序功能,可以分别实现按姓名、电话、年龄、地址、性别排序,这涉及到不同类型的数据排序,因此需要定义5个比较函数;5个比较函数的调用可以利用函数指针数组,通过函数指针数组下标调用函数无疑使得代码更加简洁易于维护。此外很容易想到qsort库函数,因为qsort函数的一个参数就是函数指针——指向一个比较函数,这里当然可以直接使用qsort库函数进行排序,这样只需提供5个比较函数即可。但是这也太简单了,毫无挑战性,为了给冒泡排序足够的尊重,我决定利用冒泡排序思想自定义一个排序函数,结合回调函数使之可以实现5种排序方法。而且通讯录待排序的都是已知的结构体类型数据和待比较个数,无疑比qsort实现起来更简单,不需要设计void*参数接收数据类型、参数sz接收元素个数、参数len接收数据长度。
显而易见,这是一个宝藏编程题,涉及的知识点有冒泡排序、qsort排序、结构体指针和结构体传参、字符串比较、回调函数(函数指针)、函数指针数组等,其中函数指针、函数指针数组以及冒泡排序、qsort排序的详细总结可移步至C语言指针进阶:数组指针和函数指针-CSDN博客,里面还有利用冒泡排序思想模拟实现qsort排序的例程。这些都是重要知识点,刚好借助这个题目实战演练一下,温故知新。话不多说,开始编程之旅······
一、完整代码
#include<stdio.h>
#include<stdio.h>
typedef struct Peo
{
char name[20];
char sex[5];
int age;
char tel[12];
char addr[30];
}Peo;
typedef struct Contact
{
Peo data[100]; //通讯录人员信息
int num; //通讯录已存放人员个数
}Contact;
void menu()
{
printf("************************************************\n");
printf("**************** 1.按姓名排序 ****************\n");
printf("**************** 2.按性别排序 ****************\n");
printf("**************** 3.按年龄排序 ****************\n");
printf("**************** 4.按电话排序 ****************\n");
printf("**************** 5.按地址排序 ****************\n");
printf("************************************************\n");
}
void print(Contact *p)
{
int i = 0;
for (i = 0; i < p->num; i++)
{
printf("%-10s %-5s %-5d %-12s %-30s\n", //左对齐打印
p->data[i].name,
p->data[i].sex,
p->data[i].age,
p->data[i].tel,
p->data[i].addr);
}
printf("\n");
}
void sort_con(Contact* p, int (*cmp)(const Peo* e1, const Peo* e2)) //参数cmp指向实现两个数排序算法的函数
{
int i = 0;
int j = 0;
for (i = 0; i < p->num-1; i++) //冒牌排序躺数=待比较数据个数-1
{
for (j = 0; j < p->num - 1 - i; j++) //比较相领两个数,每冒泡一趟循环次数-1
{
if (cmp(&(p->data[j]), &(p->data[j + 1])) > 0)//将Peo结构体数组成员传给cmp比较函数,至于怎么排序取决于sort_con调用时指向的函数指针
{
Peo tmp = p->data[j];
p->data[j] = p->data[j + 1];
p->data[j+1] = tmp;
}
}
}
}
//定义按名字比较两个数的函数
int cmp_byname(const Peo* e1, const Peo* e2)
{
return strcmp(e1->name, e2->name);
}
//定义按性别比较两个数的函数
int cmp_bysex(const Peo* e1, const Peo* e2)
{
return strcmp(e1->sex, e2->sex);
}
//定义按年龄比较两个数的函数
int cmp_byage(const Peo* e1, const Peo* e2)
{
return (e1->age) - (e2->age);
}
//定义按电话比较两个数的函数
int cmp_bytel(const Peo* e1, const Peo* e2)
{
return strcmp(e1->tel, e2->tel);
}
//定义按地址比较两个数的函数
int cmp_byaddr(const Peo* e1, const Peo* e2)
{
return strcmp(e1->addr, e2->addr);
}
int main()
{
int input = 0;
Contact con = { {{"abcd","nan",18,"123456789","agdkkjkojo"},
{"jhgg","nan",17,"124356789","agdlkjkojo"},
{"acbd","nv", 26,"122456789","agdekjkojo"},
{"agcd","nv", 24,"121456789","agdfkjkojo"},
{"jhhg","nan",13,"128456789","agdgkjkojo"} ,},5 }; //结构体初始化
int(*cmp_arr[6])(const Peo* e1, const Peo* e2) = { 0,cmp_byname,cmp_bysex,cmp_byage,cmp_bytel,cmp_byaddr };//函数指针数组初始化
print(&con); //打印排序前结果
do
{
menu();
printf("请选择排序方法:>\n");
scanf("%d", &input);
if (input == 0)
printf("退出\n");
else if ((input >= 1) && (input <= 5))
{
sort_con(&con, cmp_arr[input]); //调用排序函数实现排序,cmp_arr为函数指针数组
print(&con); //打印排序后结果
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
二、代码分析
首先搭建main函数主体:
#include<stdio.h>
#include<stdio.h>
typedef struct Peo
{
char name[20];
char sex[5];
int age;
char tel[12];
char addr[30];
}Peo;
typedef struct Contact
{
Peo data[100]; //通讯录人员信息
int num; //通讯录已存放人员个数
}Contact;
void menu()
{
printf("************************************************\n");
printf("**************** 1.按姓名排序 ****************\n");
printf("**************** 2.按性别排序 ****************\n");
printf("**************** 3.按年龄排序 ****************\n");
printf("**************** 4.按电话排序 ****************\n");
printf("**************** 5.按地址排序 ****************\n");
printf("************************************************\n");
}
void print(Contact *p)
{
int i = 0;
for (i = 0; i < p->num; i++)
{
printf("%-10s %-5s %-5d %-12s %-30s\n", //左对齐打印
p->data[i].name,
p->data[i].sex,
p->data[i].age,
p->data[i].tel,
p->data[i].addr);
}
printf("\n");
}
int main()
{
int input = 0;
Contact con = { {{"abcd","nan",18,"123456789","agdkkjkojo"},
{"jhgg","nan",17,"124356789","agdlkjkojo"},
{"acbd","nv", 26,"122456789","agdekjkojo"},
{"agcd","nv", 24,"121456789","agdfkjkojo"},
{"jhhg","nan",13,"128456789","agdgkjkojo"} ,},5 }; //结构体初始化
print(&con); //打印排序前结果
do
{
menu();
printf("请选择排序方法:>\n");
scanf("%d", &input);
if (input == 0)
printf("退出\n");
else if ((input >= 1) && (input <= 5))
{
sort_con(&con, cmp_arr[input]); //调用排序函数实现排序,cmp_arr为函数指针数组
print(&con); //打印排序后结果
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
48行初始化结构体数组,注意数组和结构体都是用花括号括起来,为了美观便于阅读可以换行处理。排序前和排序后各打印一次结构体数组观察排序是否成功,30-43行打印函数print的实现非常简单。使用do···while循环保证至少执行一次循环主体语句,且可以在选择一次排序方法后可以继续选择下一次,利用输入的input作为循环条件,选择0就可以退出排序。根据输入input选择排序方式,可以用switch···case语句,也可以用if···else语句,此处选用if···else语句是为了使用函数指针数组下标调用不同比较函数实现各种排序方法,如64行所示。另外选用if···else语句不仅代码更加简洁而且易于维护,后续如果需要添加新的排序方法直接多定义一个比较函数,函数指针数组添加一个元素即可,而switch···case语句则需要增加case语句。
接下来就是核心——自定义排序函数的实现,此函数设计灵感是来自qsort函数设计思想,通过回调函数(即设计一个指向比较函数的参数)实现根据传入不同函数指针进行不同比较规则的排序。
void sort_con(Contact* p, int (*cmp)(const Peo* e1, const Peo* e2)) //参数cmp指向实现两个数排序算法的函数
{
int i = 0;
int j = 0;
for (i = 0; i < p->num-1; i++) //冒牌排序躺数=待比较数据个数-1
{
for (j = 0; j < p->num - 1 - i; j++) //比较相领两个数,每冒泡一趟循环次数-1
{
if (cmp(&(p->data[j]), &(p->data[j + 1])) > 0)//将Peo结构体数组成员传给cmp比较函数,至于怎么排序取决于sort_con调用时指向的函数指针
{
Peo tmp = p->data[j];
p->data[j] = p->data[j + 1];
p->data[j+1] = tmp;
}
}
}
}
因为待比较的数据类型和数据个数是确定的,此函数实现起来较qsort要简单的多,只需两个参数即可,一个是待比较的结构体指针,一个就是比较函数指针。比较函数的设计也参考qsort函数中比较函数的设计,输入e1、e2两个元素进行比较,e1>e2返回大于0的值,e1=e2返回0,e1<e2返回小于0的值。这里应该想到冒泡排序的思想就是一趟一趟地比较相邻两个数进行排序,所以此处采用冒泡排序,相邻两个元素就是结构体数组元素Peo data[0] ~ Peo data[4],因为比较算法是根据data结构体里面的成员进行比较的,所以此处直接将Peo data[i]和Peo data[i+1]传给比较函数即可,至于调用哪个比较算法就根据调用排序函数时传入的函数指针而定。
接下来就是5个比较函数的实现,实际上就是两种比较算法的实现,因为data结构体成员中就两种数据类型——字符串和整型,巧的是strcmp字符串比较函数的返回值跟qsort函数参数指向的比较函数返回值一样,所以实现起来比较简易:
//定义按名字比较两个数的函数
int cmp_byname(const Peo* e1, const Peo* e2)
{
return strcmp(e1->name, e2->name);
}
//定义按性别比较两个数的函数
int cmp_bysex(const Peo* e1, const Peo* e2)
{
return strcmp(e1->sex, e2->sex);
}
//定义按年龄比较两个数的函数
int cmp_byage(const Peo* e1, const Peo* e2)
{
return (e1->age) - (e2->age);
}
//定义按电话比较两个数的函数
int cmp_bytel(const Peo* e1, const Peo* e2)
{
return strcmp(e1->tel, e2->tel);
}
//定义按地址比较两个数的函数
int cmp_byaddr(const Peo* e1, const Peo* e2)
{
return strcmp(e1->addr, e2->addr);
}
三、测试结果
按姓名排序:
按性别排序:
按年龄排序:
按电话排序:
按地址排序: