C语言 - 制作一个可以容纳一千人的本地通讯录

2023-11-16

本章目录


前言

        本期文章教大家制作一个属于自己的本地通讯录,对通讯录的要求有:

        1、可以容纳1000人的信息,储存信息的空间要随着存储数量的增加而增大。

        2、存储的信息可以保存在本地,当程序运行时可以自动读取本地信息文件。

        3、可以对信息进行增、删、改、查、排序、销毁本地信息文件等功能。


一、菜单制作

        首先,我们需要制作一个菜单来方便我们选择功能

enum Option
{
    Exit,
    Add,
    Del,
    Search,
    Modify,
    Sort,
    Print,
    Destroy
};

void Menu()
{
    printf("————————————————————————————————\n");
    printf("————————————————————————————————\n");
    printf("————————1、增加   2、删除———————\n");
    printf("————————3、查找   4、改正———————\n");
    printf("————————5、排序   6、打印———————\n");
    printf("————————7、销毁   0、退出———————\n");
    printf("————————————————————————————————\n");
    printf("————————————————————————————————\n");
}

void Choice()
{
    int input;
    do
    {
        Menu();
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
            case Add:break;
            case Del:break;
            case Search:break;
            case Modify:break;
            case Sort:break;
            case Print:break;
            case Destroy:break;
            case Exit:break;
            default:printf("选择错误, 请重新选择!\n");break;
        }
    } while (input);
}

        此时,利用枚举和Switch就可以制作出一个可以供我们选择的菜单了。

二、创建通讯录

1、创建人员信息结构体

        在创建通讯录之前,我们应该知道我们的通讯录结构。

        通讯录应该包含:1、人员名字    2、性别    3、年龄    4、电话号码    5、地址等类型

struct PeoInfo
{
    char name[10];
    char sex[2];
    int age;
    long long tele[11];
    char addr[50];
} PeoInfo;

         我们这里就很好的创建出了通讯录的结构变量。


        但是问题出现了,如果这样把数组的大小写死了,那么到后期维护时,需要更改大小,那么将很麻烦

        所以我们应该使用 #define 为他们定义标识符

        并且把PeoInfo结构体重新起一个新的名字,这样在后期写代码时就更加方便

#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 11
#define MAX_ADDR 30

typedef struct PeoInfo //对结构体重命名
{
    char name[MAX_NAME];
    char sex[MAX_SEX];
    int age;
    long long tele[MAX_TELE];
    char addr[MAX_ADDR];
} PeoInfo;

2、创建完整通讯录结构体

        当我们创建好上面的结构体之后呢,我们还需要创建一个结构体,用来记录人员信息结构体。

typedef struct Contact
{
    PeoInfo *data;

    int sz;

    int capacity;

} Contact;

        PeoInfo *data  ---   用来指向为通讯录动态申请的空间(后面会讲到)

        int sz   ---   用来记录当前通讯录人员有效信息,下一次人员存放为在sz+1的位置

        int capacity   ---   记录当前空间最多能存储多少信息,因为当空间 sz == capacity时,则当前空间已满。

        接下来,我们就可以在Choice函数中添加一句,来创建通讯录了。

    Contact con; 

 3、初始化通讯录

        因为我们只是在上面创建了通讯录,但是还没初始化,所以接下来我们需要写一个初始化函数。

#define DEFAULT_SZ 3
#define INC_SZ 2

void InitContact(Contact *pc)
{
    pc->data = (PeoInfo *)malloc(DEFAULT_SZ * sizeof(PeoInfo));
    if (pc->data == NULL)
    {
        perror("InitContact");
        return;
    }

    pc->sz = 0;

    pc->capacity = DEFAULT_SZ;

}

        1、需要定义两个标识符, 

                DEFAULT_SZ  ---  为通讯录的初始化大小,我们这里初始化为3

                INC_SZ   ---   作为通讯录空间不够时,为通讯录新开辟的空间大小,每次开辟2个空间

        2、因为我们之前已经创建好了data变量,而我们现在用data维护malloc函数开辟的空间。(malloc首先开辟3个PeoInfo大小的空间,然后强制转换为PeoInfo类型,在交给data指针维护)

        3、当data为NULL时,则开辟失败(有可能为堆空间不足)

        4、将sz初始化为0,因为此时还没有人员信息

        5、将capacity初始化为通讯录初始大小,也就是3个人员的空间大小

         当我们初始化好通讯录之后,就可以开始往里面存入数据了。

4、存放数据

        在存放数据之前,需要大家了解C语言的文件函数的使用和其原理。接下来就是使用文件函数来存放数据和读入数据

void LoadContact(Contact *pc)
{
    //打开文件
    FILE *pf = fopen("contact.dat", "r");
    if (pf == NULL)
    {
        perror("LoadContact:");
        return;
    }

    //读文件
    PeoInfo tmp = {0};
    while (fread(&tmp, sizeof(PeoInfo), 1, pf))
    {
        //考虑是否需要整容
        CheckCapacity(pc);
        pc->data[pc->sz] = tmp;
        pc->sz++;
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
}

        1、利用fopen文件操作函数,可以打开文件(如果文件不存在则返回NULL,这里设置如果为NULL就退出这个函数,所以就不影响后面的操作)

        2、当不等于NUL,那就说明已经成功打开了文件。

        3、我们需要创建一个临时的结构体变量tmp用于存放从文件中读取的信息。

        4、我们直接可以在while循环中加入表达式,从 pf指针 指向的空间中读取一个PeoInfo大小的文件,然后存储在tmp变量中。当fread函数从文件中读取不到信息时,则返回0,循环则结束

        5、在存放到data之前,需要判断当前的空间够不够我们存放(函数在下面)

        6、当存放完成后,我们需要关闭文件,并且将 指针置为NULL,防止内存泄漏

5、判断空间容量

        因为本文章使用动态存储,所以一开始并不可能开辟很大的空间,所以当空间存满之后,必然需要开辟一块新的空间,所以这部分要求大家需要内存函数的基础接下来是教大家写一个函数开辟空间。

void CheckCapacity(Contact *pc)
{
    if (pc->sz == pc->capacity)
    {
        PeoInfo *tmp = (PeoInfo *)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));

        if (tmp != NULL)
        {
            pc->data = tmp;
            pc->capacity += INC_SZ;
            printf("已增加2位空间!\n");
        }
        else
        {
            perror("AddContct");
            printf("增加失败!\n");
            return;
        }
    }
}

        1、在前文我们可以了解到,sz 和 capacity 这两个变量一个是记录我们 通讯录有效信息的,另一个为 记录当前空间最大容量 

        2、所以当两个变量相等时,就意味着当前空间已经存满了,所以我们必须扩容了

        3、首先,先创建一个 PeoInfo *类型的 tmp 变量用于临时存储使用realloc函数开辟的空间地址。

        (这一段的含义为:使用 realloc函数 为pc->data开辟一块 pc->capacity(初始化大小为3) + INC_SZ(定义的为2) 再乘上 PeoInfo 大小的空间 , 所以就意味着每次增加2个PeoInfo大小的空间 然后再转为(PeoInfo *)类型,再将地址给tmp存储)

        4、当空间开辟完成时,如果tmp为NULL,那么就开辟失败

        5、如果不为NULL时,那么就开辟成功,这时就需要将地址再转给 pc->data 管理

        6、最大容量(capacity)也需要增加两个位置

三、各功能的实现

1、增加人员信息的功能

        当我们做好上面的准备工作时,此时就需要开始写功能函数了,首先我们先写增加功能。

void AddContct(Contact *pc) //增加
{
    CheckCapacity(pc);

    //增加一个信息
    printf("\n请输入姓名:> ");
    scanf("%s", pc->data[pc->sz].name);

    printf("请输入性别:> ");
    scanf("%s", pc->data[pc->sz].sex);

    printf("请输入年龄:> ");
    scanf("%d", &pc->data[pc->sz].age);

    printf("请输入手机号:> ");
    scanf("%lld", pc->data[pc->sz].tele);

    printf("请输入地址:> ");
    scanf("%s", pc->data[pc->sz].addr);

    pc->sz++;

    printf("\n增加成功!\n");
}

        1、跟之前一样,增加一个人员信息时,需要先判断空间是否足够大。

        2、然后就使用 scanf函数 将输入的信息存入对应的位置就行

        3、输入完成后 sz++ 下次就可以直接存放到下一个空间了

2、按名字查找的功能(内部使用)

        在写其他的删、改、查、排序等功能时,需要一个前提,在删除某个人员信息时,是需要找到该人员信息的,所以我们需要先写一个查找人员的函数,但是与 查 功能不同,此函数是用于内部查找的,只是给电脑使用的,所以是不需要打印出来的。

static int FindByName(Contact *pc, char name[])
{
    for (size_t i = 0; i < pc->sz; i++)
    {
        if (strcmp(pc->data[i].name, name) == 0)
        {
            return i;
        }
    }
    return -1;
}

        1、因为只是在该文件中调用的,所以可以加上static修饰一下

        2、 这里就是使用最简单的暴力求解了,没有涉及什么算法

        3、当每一次找到名字的时候就使用 strcmp函数 和输入的名字对比一下,如果一样 strcmp函数就会返回0,那代表我们找到了

        4、找到之后就会返回名字对应的下标位置,如果没找到就返回-1

3、删除人员信息的功能

        写完了必要的查找名字功能,这时我们就可以写删除功能了

void DelContct(Contact *pc)
{
    char Name[MAX_NAME] = {0};
    if (pc->sz == 0)
    {
        printf("当前通讯录为空, 无需删除!\n");
        return;
    }

    printf("请输入要删除人的名字:> ");
    scanf("%s", Name);

    // 查找要删除的人
    int pos = FindByName(pc, Name);

    // 没有 - 退出
    if (pos == -1)
    {
        printf("查无此人!\n");
        return;
    }

    // 有 - 删除 - 往前挪
    for (size_t i = pos; i < pc->sz - 1; i++)
    {
        pc->data[i] = pc->data[i + 1];
    }
    pc->sz--;

    printf("删除成功!\n");
}

        1、创建一个 字符数组Name 用于存放scanf输入的名字,以便我们好查找这个名字

        2、当 sz为0时,就代表着当前通讯录是空的所以不需要删除

        3、当Name接收到名字后,此时我们前面的查找名字的函数就起了作用,使用该函数就可以很轻松的得到该名字的下标。

        4、找到之后,我们只需要将后面的数据全部往前挪,把该信息覆盖掉,再将 sz有效信息-- ,就等于删除了这个人员的信息

4、更改人员信息的功能 

        更改人员信息的功能与其他功能大同小异。

void ModifyContct(Contact *pc)
{
    char Name[MAX_NAME] = {0};
    printf("请输入要修改人的名字:> ");
    scanf("%s", Name);
    int pos = FindByName(pc, Name);
    if (pos == -1)
        printf("查无此人!\n");
    else
    {
        printf("\n请输入姓名:> ");
        scanf("%s", pc->data[pos].name);

        printf("请输入性别:> ");
        scanf("%s", pc->data[pos].sex);

        printf("请输入年龄:> ");
        scanf("%d", &pc->data[pos].age);

        printf("请输入手机号:> ");
        scanf("%lld", pc->data[pos].tele);

        printf("请输入地址:> ");
        scanf("%s", pc->data[pos].addr);

        printf("修改成功!\n");
        printf("%-0s\t%7s\t%7s\t%7s\t%15s\t%15s\n", "序号", "姓名", "性别", "年龄", "手机号", "地址");
        printf("%2d\t%5s\t%6s\t%5d\t%13lld\t%15s\n", pos + 1,
               pc->data[pos].name,
               pc->data[pos].sex,
               pc->data[pos].age,
               pc->data[pos].tele[0],
               pc->data[pos].addr);
    }
}

        1、首先对要改的名字进行查找,获取下标

        2、再重新输入信息,覆盖之前的信息

        3、完成后,再打印一遍

5、查找指定联系人

        增删改查中,我们还剩个查的功能,在上面我们已经实现过一遍了,这次我们只需要加入打印功能即可

void SearchContct(Contact *pc)
{
    char Name[MAX_NAME] = {0};
    printf("请输入要查找人的名字:> ");
    scanf("%s", Name);

    int pos = FindByName(pc, Name);
    if (pos == -1)
    {
        printf("查无此人!\n");
    }
    else
    {
        printf("%-0s\t%7s\t%7s\t%7s\t%15s\t%15s\n", "序号", "姓名", "性别", "年龄", "手机号", "地址");

        printf("\n%2d\t%5s\t%6s\t%5d\t%13lld\t%15s\n", pos + 1,
               pc->data[pos].name,
               pc->data[pos].sex,
               pc->data[pos].age,
               pc->data[pos].tele[pos],
               pc->data[pos].addr);
    }
}

        1、因为上面已经写过一个查找名字的功能了,所以我们这只需要调用该函数即可

        2、将获取的名字传入 FindByName函数 中,如果返回-1就代表查找失败

        3、如果不是-1,那么就直接打印出来

6、按照人员姓名排序的功能 

        接下来教大家如何制作排序的功能,我们需要写一个自己的qsort函数,所以要求有qsort函数的基础(在我的前几章博客中有写)

int MyStrcmp(const char *s1, const char *s2) // MyStrcmp
{
    assert(s1 && s2);
    while (*s1 == *s2)
    {
        if (*s1 == '\0')
        {
            return 0;
        }
        s1++;
        s2++;
    }

    return *s1 > *s2 ? 1 : -1;
}

void Swap(char *e1, char *e2, int width) // MyQsort - 冒泡排序
{
    for (int i = 0; i < width; i++)
    {
        char tmp = *e1;
        *e1 = *e2;
        *e2 = tmp;
        e1++;
        e2++;
    }
}

void MyQsort(void *base, int ret, int width, int (*cmp)(const void *e1, const void *e2)) // MyQsort
{
    //趟数
    for (int i = 0; i < ret - 1; i++)
    {
        //一趟的排序
        for (int j = 0; j < ret - 1 - i; j++)
        {
            if (cmp((char *)base + j * (width), (char *)base + (j + 1) * (width)) > 0)
            {
                //升序交换
                Swap((char *)base + j * (width), (char *)base + (j + 1) * (width), width);
            }
        }
    }
}

int SortByname(const void *e1, const void *e2) // MyQsort - 升序
{
    return MyStrcmp(((PeoInfo *)e1)->name, (((PeoInfo *)e2)->name));
}

void SortContct(Contact *pc) //排序功能
{
    //使用qsort函数进行排序
    MyQsort(pc->data, pc->sz, sizeof(pc->data[0]), SortByname);
    
    printf("排序成功!\n");
}

        1、qsort库函数使用的快速排序,我们模拟的MyQsort函数使用的是冒泡排序,也是最简单的

        2、qsort的核心思想是需要我们自己写出一个对比两个数函数,因为qsort不知道我们对比的是什么类型的数据

        3、所以我们需要创建一个对比名字大小的函数,为SortByname,因为名字是字符串,而不可以直接使用大于小于号对比,所以函数内部使用Strcmp函数进行对比,这里我们也写出一个我们自己的MyStrcmp

        4、写好后,我们将 数组的地址、数组的元素个数、每个元素的大小、对比名字大小的函数 都传入进去

        5、当程序走完时,也代表人员已经按照名字排好序了

7、销毁人员信息的功能

        因为按照要求,我们写的是有自动保存的功能,当人员信息多了起来后,一个一个删除太麻烦,所以我们需要一个一键销毁的功能。

void DestroyContct(Contact *pc)
    FILE *pf = fopen("Contact.dat", "w");
    if (pf == NULL)
    {
        perror("SaveContct:");
        return;
    }
    pc->sz = pc->capacity = 0;
    printf("销毁成功!\n");
}

        1、当 fopen函数 使用 ' w ' 形式打开文件时,如果文件里有数据,那么会直接清空,利用这个性质,我们就可以清空文件的人员信息了

        2、当文件信息清除后,sz 和 capacity 变量也需要我们置为0,当下次打开文件后,就是一个全新的文件了

8、退出保存的功能

        接下来就是写退出保存的功能了,需要大家有 文件操作函数 的基础


void SaveContct(Contact *pc) //保存
{
    FILE *pf = fopen("Contact.dat", "w");
    if (pf == NULL)
    {
        perror("SaveContct:");
        return;
    }

    //写文件
    for (int i = 0; i < pc->sz; i++)
    {
        fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
    printf("保存成功,");
}

        1、原理也很简单,文件以 ' w ' 形式打开,如果没有则自己创建一个名字为 " Contact.dat " ,当然也可以想改什么名也行

        2、当 pf变量 不等于NULL时,那么就说明打开了该文件,文明此时就可以往里面写数据了

        3、因为此时我们使用增加功能写的数据还保存在内存的数组中,所以我们需要一个一个的往里面写入数组的数据

        (使用 fwrite文件操作函数 就可以从 ps->data 往 pf指向的文件中 写入 PeoInfo大小的数据 )

        4、写入完成后,我们需要关闭文件,并将pf置为NULL

         当然,保存完后,我们还有一步需要完成,那就是退出功能

void ExitContct(Contact *pc)
{
    free(pc->data);
    pc->data = NULL;

    pc->sz = pc->capacity = 0;
    printf("已退出!\n");
}

        1、这一步也很重要,退出整个工程前,我们需要将 pc->data指向的空间 free,不然会存在内存泄漏,接着将 data置为NULL

        2、sz 和 capacity 变量也需要我们置为0

四、添加功能

        我们制作完功能后,还没有添加到菜单中,此时选择菜单还是没有功能的,所以我们需要添加上去

void Choice()
{
    int input;
    Contact con;
    InitContact(&con);

    do
    {
        Menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch (input)
        {
        case Add:
            AddContct(&con);
            break;
        case Del:
            DelContct(&con);
            break;
        case Search:
            SearchContct(&con);
            break;
        case Modify:
            ModifyContct(&con);
            break;
        case Sort:
            SortContct(&con);
            break;
        case Print:
            PrintContct(&con);
            break;
        case Destroy:
            DestroyContct(&con);
            break;
        case Exit:
            SaveContct(&con);
            ExitContct(&con);
            break;
        default:
            printf("选择错误, 请重新选择!\n");
            break;
        }

    } while (input);
}

         此时,我们就可以在正常的使用整个工程了。

五、完整代码 

        已经合为一个文件了

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#define MAX_NAME 50
#define MAX_SEX 10
#define MAX_TELE 11
#define MAX_ADDR 30

#define DEFAULT_SZ 3 //初始化大小
#define INC_SZ 2     //每次增加大小

typedef struct PeoInfo //对结构体重命名
{
    char name[MAX_NAME];
    char sex[MAX_SEX];
    int age;
    long long tele[MAX_TELE];
    char addr[MAX_ADDR];
} PeoInfo;

typedef struct Contact
{
    // data 指向动态申请的空间,用来存放联系人的信息
    PeoInfo *data;

    //记录人员的数量
    int sz;

    //记录当前通讯录的最大容量
    int capacity;

} Contact;

void CheckCapacity(Contact *pc) // 检测容量
{
    if (pc->sz == pc->capacity) //当两个相等时,就需要增加容量
    {
        PeoInfo *tmp = (PeoInfo *)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));

        if (tmp != NULL)
        {
            pc->data = tmp;
            pc->capacity += INC_SZ;
            printf("已增加2位空间!\n");
        }
        else
        {
            perror("AddContct");
            printf("增加失败!\n");
            return;
        }
    }
}

void LoadContact(Contact *pc) //加载文件到通讯录
{
    FILE *pf = fopen("contact.dat", "r");
    if (pf == NULL)
    {
        perror("LoadContact:");
        return;
    }

    //读文件
    PeoInfo tmp = {0};
    while (fread(&tmp, sizeof(PeoInfo), 1, pf)) //当fread读不到时,就会返回0;
    {
        //考虑是否需要增容
        CheckCapacity(pc);
        pc->data[pc->sz] = tmp;
        pc->sz++;
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
}

void InitContact(Contact *pc)
{
    pc->data = (PeoInfo *)malloc(DEFAULT_SZ * sizeof(PeoInfo));
    if (pc->data == NULL)
    {
        perror("InitContact");
        return;
    }

    pc->sz = 0;

    pc->capacity = DEFAULT_SZ;

    //加载文件
    LoadContact(pc);
}

void AddContct(Contact *pc) //增加
{
    CheckCapacity(pc);
    //增加一个信息
    printf("\n请输入姓名:> ");
    scanf("%s", pc->data[pc->sz].name);

    printf("请输入性别:> ");
    scanf("%s", pc->data[pc->sz].sex);

    printf("请输入年龄:> ");
    scanf("%d", &pc->data[pc->sz].age);

    printf("请输入手机号:> ");
    scanf("%lld", pc->data[pc->sz].tele);

    printf("请输入地址:> ");
    scanf("%s", pc->data[pc->sz].addr);

    pc->sz++;

    printf("\n增加成功!\n");
}

static int FindByName(Contact *pc, char name[]) //按照名字查找
{
    for (size_t i = 0; i < pc->sz; i++)
    {
        if (strcmp(pc->data[i].name, name) == 0)
        {
            return i;
        }
    }
    return -1;
}

void DelContct(Contact *pc) //删除功能
{
    char Name[MAX_NAME] = {0};
    if (pc->sz == 0)
    {
        printf("当前通讯录为空, 无需删除!\n");
        return;
    }

    printf("请输入要删除人的名字:> ");
    scanf("%s", Name);

    // 1. 查找要删除的人
    int pos = FindByName(pc, Name);
    // 没有 - 退出
    if (pos == -1)
    {
        printf("查无此人!\n");
        return;
    }

    // 有 - 删除 - 往前挪
    for (size_t i = pos; i < pc->sz - 1; i++)
    {
        pc->data[i] = pc->data[i + 1];
    }
    pc->sz--;

    printf("删除成功!\n");
}

void SearchContct(Contact *pc) //查找指定联系人
{
    char Name[MAX_NAME] = {0};
    printf("请输入要查找人的名字:> ");
    scanf("%s", Name);
    int pos = FindByName(pc, Name);
    if (pos == -1)
    {
        printf("查无此人!\n");
    }
    else
    {
        printf("%-0s\t%7s\t%7s\t%7s\t%15s\t%15s\n", "序号", "姓名", "性别", "年龄", "手机号", "地址");

        printf("\n%2d\t%5s\t%6s\t%5d\t%13lld\t%15s\n", pos + 1,
               pc->data[pos].name,
               pc->data[pos].sex,
               pc->data[pos].age,
               pc->data[pos].tele[pos],
               pc->data[pos].addr);
    }
}

void ModifyContct(Contact *pc) //修改指定联系人
{
    char Name[MAX_NAME] = {0};
    printf("请输入要修改人的名字:> ");
    scanf("%s", Name);
    int pos = FindByName(pc, Name);
    if (pos == -1)
    {
        printf("查无此人!\n");
    }
    else
    {
        printf("\n请输入姓名:> ");
        scanf("%s", pc->data[pos].name);

        printf("请输入性别:> ");
        scanf("%s", pc->data[pos].sex);

        printf("请输入年龄:> ");
        scanf("%d", &pc->data[pos].age);

        printf("请输入手机号:> ");
        scanf("%lld", pc->data[pos].tele);

        printf("请输入地址:> ");
        scanf("%s", pc->data[pos].addr);

        printf("修改成功!\n");

        printf("%-0s\t%7s\t%7s\t%7s\t%15s\t%15s\n", "序号", "姓名", "性别", "年龄", "手机号", "地址");

        printf("%2d\t%5s\t%6s\t%5d\t%13lld\t%15s\n", pos + 1,
               pc->data[pos].name,
               pc->data[pos].sex,
               pc->data[pos].age,
               pc->data[pos].tele[0],
               pc->data[pos].addr);
    }
}

int MyStrcmp(const char *s1, const char *s2) // MyStrcmp
{
    assert(s1 && s2);
    while (*s1 == *s2)
    {
        if (*s1 == '\0')
        {
            return 0;
        }
        s1++;
        s2++;
    }

    return *s1 > *s2 ? 1 : -1;
}

void Swap(char *e1, char *e2, int width) // MyQsort - 冒泡排序
{
    for (int i = 0; i < width; i++)
    {
        char tmp = *e1;
        *e1 = *e2;
        *e2 = tmp;
        e1++;
        e2++;
    }
}

void MyQsort(void *base, int ret, int width, int (*cmp)(const void *e1, const void *e2)) // MyQsort
{
    //趟数
    for (int i = 0; i < ret - 1; i++)
    {
        //一趟的排序
        for (int j = 0; j < ret - 1 - i; j++)
        {
            if (cmp((char *)base + j * (width), (char *)base + (j + 1) * (width)) > 0)
            {
                //升序交换
                Swap((char *)base + j * (width), (char *)base + (j + 1) * (width), width);
            }
        }
    }
}

int Sort_By_name(const void *e1, const void *e2) // MyQsort - 升序
{
    return MyStrcmp(((PeoInfo *)e1)->name, (((PeoInfo *)e2)->name));
}

void SortContct(Contact *pc) //排序功能
{
    //使用qsort函数进行排序
    MyQsort(pc->data, pc->sz, sizeof(pc->data[0]), Sort_By_name);
    // printf("%d", sizeof(pc->data->name[0]));
    printf("排序成功!\n");
}

void PrintContct(const Contact *pc) //打印所有联系人
{
    if (pc->sz == 0)
    {
        printf("抱歉,暂无联系人!\n");
        return;
    }
    printf("%-0s\t%7s\t%7s\t%7s\t%15s\t%15s\n", "序号", "姓名", "性别", "年龄", "手机号", "地址");
    for (size_t i = 0; i < pc->sz; i++)
    {
        printf("\n%2d\t%7s\t%6s\t%5d\t%15lld\t%15s\n", i + 1,
               pc->data[i].name,
               pc->data[i].sex,
               pc->data[i].age,
               pc->data[i].tele[0],
               pc->data[i].addr);
    }
    printf("\n");
}

void SaveContct(Contact *pc) //保存
{
    FILE *pf = fopen("Contact.dat", "w"); //以w打开,数据文件清零,然后因为arr里面本身就有数据,再写入
    if (pf == NULL)
    {
        perror("SaveContct:");
        return;
    }

    //写文件
    for (int i = 0; i < pc->sz; i++)
    {
        fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
    printf("保存成功,");
}

void ExitContct(Contact *pc) //退出
{
    free(pc->data);
    pc->data = NULL;

    pc->sz = 0;
    pc->capacity = 0;
    printf("已退出!\n");
}

void DestroyContct(Contact *pc) //销毁
{
    FILE *pf = fopen("Contact.dat", "w"); // w打开文件会清零
    if (pf == NULL)
    {
        perror("SaveContct:");
        return;
    }
    pc->sz = pc->capacity = 0; //这里将数组的下标置为0之后,退出时就不会把数组内的数据保存进去
    printf("销毁成功!\n");
}

enum Option
{
    Exit,
    Add,
    Del,
    Search,
    Modify,
    Sort,
    Print,
    Destroy
};

void Menu()
{
    printf("————————————————————————————————\n");
    printf("————————————————————————————————\n");
    printf("————————1、增加   2、删除———————\n");
    printf("————————3、查找   4、改正———————\n");
    printf("————————5、排序   6、打印———————\n");
    printf("————————7、销毁   0、退出———————\n");
    printf("————————————————————————————————\n");
    printf("————————————————————————————————\n");
}

void Choice()
{
    int input;

    Contact con; //创建通讯录

    InitContact(&con); //给data在堆上申请一块空间 , sz = 0;capacity = 当前容量

    do
    {
        Menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch (input)
        {
        case Add:
            AddContct(&con);
            break;
        case Del:
            DelContct(&con);
            break;
        case Search:
            SearchContct(&con);
            break;
        case Modify:
            ModifyContct(&con);
            break;
        case Sort:
            SortContct(&con);
            break;
        case Print:
            PrintContct(&con);
            break;
        case Destroy:
            DestroyContct(&con);
            break;
        case Exit:
            //保存
            SaveContct(&con);
            //销毁
            ExitContct(&con);
            break;
        default:
            printf("选择错误, 请重新选择!\n");
            break;
        }

    } while (input);
}

int main()
{

    Choice();
    return 0;
}

六、总结

        本次文章只是一个刚入门不久的新手做的一次总结,还有很多因数没有考虑到,不是特别的全面,如果哪里有问题,欢迎在评论区斧正( ̄▽ ̄)~*

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言 - 制作一个可以容纳一千人的本地通讯录 的相关文章

随机推荐

  • 创建Google play开发者账号,并验证身份通过

    一 注册前准备 最好准备一台没有怎么用过Google的电脑和 准备一个没有注册过Google的手机号 准备一张信用卡或者借记卡 需要支付 25 支持的类型如下图 这里还需注意 最后账号注册成功还需要验证身份也就是实名认证 那么Google去
  • 在Windows Server上使用IIS部署Python-Flask项目

    1 安装Flask与wfastcgi pip install Flask pip install wfastcgi 1 python安装wfastcgi并激活 2 进入python安装目录下的scripts文件夹然后运行wfastcgi e
  • 15黑马笔记之二叉树的递归遍历求叶子节点数

    15黑马笔记之二叉树的递归遍历求叶子节点数 1 思想 对每一个节点遍历其左右孩子 若都为空 则是叶子节点 递归结束条件 传进的节点不为空 代码基本只是在递归遍历的基础上加了统计叶子数的变量 2 具体代码实现 例子 include
  • nginx 反向代理 ssh连接办公室win电脑

    1 生产密钥免密连接服务器 ssh keygen 复制win电脑上的 ssh id rsa pu到服务器 root ssh 目录下 cat id rsa pub gt gt authorized keys sudo chmod 600 au
  • windows11安装wsl2(linux)+VScode+ miniconda+TVM+python环境部署

    在wsl上运行 TVM demo 环境配置 预备加速工具 vpn 清华镜像 第一次安装 失败 1 Windows11 D盘安装wls linux 2 安装vscode并连接到WSL2 3 wsl 安装miniconda 4 wls安装TVM
  • JavaSE基础学习

    JavaSE基础笔记 锦苏的个人笔记 首先声明 这个笔记是根据韩顺平老师 讲的Java课程全程来学习并且做笔记的 如果各位小伙伴们 需要从事Java行业那么从开始决定学习了 就不要放弃 三天打鱼两天晒网这是我的真实写照 我希望不是大家的真实
  • openwrt恢复默认设置

    如果通过无线或者有线口无法连接到router 可以用恢复某些设置重新设置路由器 1 开机 等着一个工作灯亮的时候立即按下rest键2秒 然后就开始拼命闪烁 很好现在进入failsafe模式了 2 设置电脑的ip为静态ip 192 168 1
  • 数据库中的binlog、redolog、undolog的区别

    binlog二进制日志是mysql server层的 主要是做主从复制 时间点恢复使用 redo log重做日志是InnoDB存储引擎层的 用来保证事务安全 undo log回滚日志保存了事务发生之前的数据的一个版本 可以用于回滚 同时可以
  • 线程池之阻塞主线程,等子线程全部跑完之后,执行主线程

    在spark中有异步多线程的需求 需要阻塞主线程 等所有子线程都执行完成后 主线程继续执行 如果是用Thread 不太好实现 用Callable FutrueTask结合线程池 可以快速实现 final AtomicInteger mess
  • 基于Java+SSM项目的计算机毕业设计-毕业生离校管理系统项目实战(附论文+源码)

    大家好 我是职场程序猿 感谢您阅读本文 欢迎一键三连哦 当前专栏 Java毕业设计 精彩专栏推荐 安卓app毕业设计 微信小程序毕业设计 演示视频 ssm147毕业生离校离校管理系统演示 源码下载地址 https download csdn
  • 数论算法:唯一因子分解定理

    这里讲一下算法中常用到的唯一因子分解定理 合数a仅能以一种方式写成如下乘积形式 a p1 e1 p2 e2 pr er 其中pi为素数 p1
  • Vue基本知识1,安装创建以及常用指令

    Vue基本知识1 安装创建以及常用指令 1 Vue的概念 概念 vue是一个渐进式的JavaScript开发框架 基于MVVM实现数据驱动的框架 官网 还可以用来做SPA single page web application 一个网页就是
  • Qt udp数据发送太快,数据丢失

    Qt 在单线程中 如果数据发送太快 应用程序还在处理别的程序 就会触发不了readyRead信号 导致造成数据的丢失 解决方法可以将udp处理类继承于QThread 在多线程入口run函数里通过死循环接收数据 通过信号槽机制通知处理函数进行
  • 微信小程序&PHP 改变小程序码中间logo的方法

    需求 获取小程序码 小程序码中间logo换成用户头像 仔细看了小程序本身的程序 没有发现好的方法 如果有 不吝赐教呀 所以改变方法 把头像传回后台 使用 php gd库在后台操作 然后传回小程序端 初始数据准备 define PATH op
  • Hive的联级(cascade)- 修改分区表的字段类型或者增加新字段

    一 问题描述 踩坑 数仓的分区表 由于需求需要 要把int类型的字段改为bigint 我直接执行的以下语句 alter table table name change column 字段 字段 bigint 出现的问题 之后的分区数据可以正
  • 经济复苏!!! ReportLinker调整2020-2027 IMU市场预测金额至335亿美元

    2022年10月 市场研究机构ReportLinker发布了2020 2027全球IMU市场预测报告 报告预测到2027年全球IMU市场金额将达到236亿美元 2020 2027年期间复合增长率为5 4 新冠过后 经济复苏 ReportLi
  • Python同时显示多张图片在一个画面中(两种方法)

    很多时候需要把很多图片同时显示到一个画面中 现在分享两个方法 这里我恰好拿之前写的爬取网上图片保存到本地的爬虫模型爬一些图片作为素材Python 爬虫批量爬取网页图片保存到本地 得到素材如下所示 现在让这些图片同时显示 方法一 subplo
  • [论文笔记]知识图谱+推荐系统

    仅作个人笔记 2021 3 22 2021 3 29 1 RippleNet Propagating User Preferences on the Knowledge Graph for Recommender Systems 看到一篇翻
  • 深入理解程序设计使用Linux汇编语言

    关于函数 4 1 由系统提供的函数称为原函数 或源语 因为其他一切都是建立在它们之上 在汇编语言中 原语通常就是系统调用 4 3 由于内存的结构 栈是从内存顶部开始向下增长的 当我们提到 栈顶 请记住这是栈内存的底部 movl esp ea
  • C语言 - 制作一个可以容纳一千人的本地通讯录

    本章目录 前言 一 菜单制作 二 创建通讯录 1 创建人员信息结构体 2 创建完整通讯录结构体 3 初始化通讯录 4 存放数据 5 判断空间容量 三 各功能的实现 1 增加人员信息的功能 2 按名字查找的功能 内部使用 3 删除人员信息的功