(使用柔性数组)动态版通讯录

简介:

@[TOC]

前言:

  • 博主目前实力有限,博文有什么错误,请你斧正!
  • 后面学会EasyX会重新更新本篇博客。0.0.
  • 上一篇博客更新了动态内存管理与柔性数组,因此趁热打铁,本通讯录采用动态函数与柔性数组。
  • 希望本篇博客对你有帮助。

思维导图

动态通讯录(柔性数组版)

思路分析

分析一:

我的思路是来一个元素,增加一次空间(realloc),这样不需要预留空间 了。顺便在添加元素的时侯,排序元素。0.0

分析二

通讯录中包含 很多人,而每个人又有很多类信息。因此我们创建2个结构体(一个也行,建立结构数组):一个是人信息的集合,一个记录有效的人数与人结构柔性数组的通讯录

enum whole//存放全局作用的常量
{
   Name_max=20,
   Age_max=10,
   Sex_max=20,
   Tel_max=20,
   Exit=0,
   Add,
   Del,
   Modify,
   Show,
   Search
};
   
struct peo
{
   char name[Name_max];
   char age[Age_max];
   char sex[Sex_max];
   char tel[Tel_max];
};

struct cont
{
   size_t sz;
   struct peo date[0];//柔性数组,这里用realloc
};

分析三:

在删除,修改,都需要对结构体进行元素 查找(我用的二分查找法,需要数组有 一定顺序),因此需要排序数组,但是什么时候排序呢?我的思路是在 添加元素的时候就 排序数组。而排序我用的是 模拟 qsort的方法。需要2个种子。

分析四:

我们在 函数中动态内存开辟时,实参可能不产生任何影响 (形参只是实参的拷贝,我只是给形式变量传一个数据)。为了避免这种情况,在 某些函数我们通过使用二级指针。可以改变实参的指向。另外

在这个函数中:我们可以先用中间变量去 realloc,产生我们想要作用之后,再用实参指向新的申请空间。

函数分析

初始化函数

  • 通过二级指针的方法,改变实参的指向。
void In_Bein(struct cont** pc)
{
   assert(pc);
   
   struct cont *p = (struct cont*)malloc(sizeof(struct cont));//这里只需要给sz申请空间就行了
                                                               //后面增加元素的时候再未peo开辟空间
   if (NULL == p)
   {
   
       printf("%s\n", strerror(errno));
       exit(1);
   }
   p->sz = 0;
   *pc = p;
   //p是局部变量,因此函数栈帧结束后,就成为野指针,但是那快动态申请的空间没有free,
   // 但是我们通过 *pc=p,这步,在程序结束,最后free就行了。
}

打印函数

void My_Show(struct cont* pc)
{
   assert(pc);//断言防止传入NULL指针
   for (size_t i = 0; i < pc->sz; i++)
   {
       printf("姓名:%s\n", ((pc -> date)+i)->name);
       printf("年龄:%s\n", ((pc-> date)+i)->age);
       printf("性别:%s\n", ((pc->date)+i)->sex);
       printf("电话:%s\n", ((pc->date)+i)->tel);
   }

}

排序函数

  • 复写qsort的使用见我另外一篇博客
  • 在排序的时候,可能会碰到同名的情况,因此我们比较电话号码就行了,毕竟电话号码是唯一的。因此需要2个比较函数(我称谓种子)。(排序是从小到大,想从大到小,改变种子就行)

    int compare1(const void* e1, const void* e2)//种子1 比较名字
                                    //比较结构体2元素中成员 名字
    {
        assert(e1 && e2);//防止传入NULL指针
        return strcmp(((struct peo*)e1)->name, ((struct peo*)e2)->name);
    
    }
    int compare2(const void* e1, const void* e2)//种子2 比较电话
    {
        assert(e1 && e2);
        return strcmp(((struct peo*)e1)->tel, ((struct peo*)e2)->tel);
    
    }
    void swap(void* e1, void* e2, size_t width)//交换2元素所占内存字节中的内容
    {
    
        assert(e1 && e2);
        for (size_t i = 0; i < width; i++)//依次交换e1,e2中的字节内容
        {
            unsigned char tmp = *((unsigned char*)e1 + i);
            *((unsigned char*)e1 + i) = *((unsigned char*)e2 + i);
            *((unsigned char*)e2 + i) = tmp;
        }
    
    }
    void my_qsort(void* base,
        size_t num,size_t width    )
    {
        assert(base);//防止传入NULL指针
        
        for (size_t i = 0; i < num-1; i++)
        {
            size_t min = i;
            for (size_t j = i + 1; j < num; j++)
            { //传入的是date,因此强转(char*)后利用width就行了
                if (0 < compare1( (char *)base + min * width, (char*)base + j * width))
                {
                    min = j;
                }
                else if (0 == compare1((char*)base + min * width, (char*)base + j * width))//一旦重名就检测电话
                {
    
                    if (0 < compare2((char*)base + min * width, (char*)base + j * width))
                    {
                        min = j;
                    }
                }
            }
            if (min != i)
            {
                swap((char *)base + i * width, (char *)base + min * width,width);
            
            }
    
        }
    }
    
    

添加元素函数

  • 添加元素,必然需要重新开辟空间,需要realloc。
  • 我们要使实参改变指向,必然二级指针。
void My_Add(struct cont** pc)
{
   assert(pc);//断言防止传入NULL指针
   struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
   if (NULL == p)
   {
       printf("%s\n", strerror(errno));//打印错误信息
       exit(1);
   }
   printf("请输入姓名:");
   scanf("%s", (p->date + p->sz)->name);
   printf("\n");
   printf("请输入性别:");
   scanf("%s", (p->date + p->sz)->sex);
   printf("\n");

   printf("请输入年龄:");
   scanf("%s", (p->date + p->sz)->age);
   printf("\n");

   printf("请输入电话:");
   scanf("%s", (p->date + p->sz)->tel);
   p->sz++;
   *pc = p;
   my_qsort(p->date, p->sz, sizeof(struct peo));//排序新增后的元素
}
void My_Add(struct cont** pc)
{
   assert(pc);//断言防止传入NULL指针
   struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
   if (NULL == p)
   {
       printf("%s\n", strerror(errno));//打印错误信息
       exit(1);
   }
   printf("请输入姓名:");
   scanf("%s", (p->date + p->sz)->name);
   printf("\n");
   printf("请输入性别:");
   scanf("%s", (p->date + p->sz)->sex);
   printf("\n");

   printf("请输入年龄:");
   scanf("%s", (p->date + p->sz)->age);
   printf("\n");

   printf("请输入电话:");
   scanf("%s", (p->date + p->sz)->tel);
   p->sz++;
   *pc = p;
   my_qsort(p->date, p->sz, sizeof(struct peo));
}

查找元素函数

  • 二分查找必然需要数组一定顺序.
  • 返回一个值的原因是为了其它函数的实现。

    int  My_Search(struct cont* pc,const char *str1,const char *str2)//二分查找
    {
        assert(pc && str1 && str2);//断言防止传入NULL指针
        size_t left = 0;
        size_t right = pc->sz - 1;
        while (left <= right)
        {
            size_t mid = (left + right) / 2;
            if ((strcmp((pc->date + mid)->name, str1)==0)&& (strcmp((pc->date + mid)->tel, str2) == 0))
            {
                printf("找到了\n");
                printf("%d\n", mid);
                return mid;
            }
            else if((strcmp((pc->date + mid)->name, str1)<0)||((strcmp((pc->date + mid)->name, str1)==0)  && (strcmp((pc->date + mid)->tel, str2)<0)))
            {
                left = mid + 1;
            
            }
            else if ((strcmp((pc->date + mid)->name, str1) > 0) || ((strcmp((pc->date + mid)->name, str1) == 0 && (strcmp((pc->date + mid)->tel, str2) > 0))))
            {
                right = mid - 1;
            }
        }
    
        printf("查无此人\n");
        return -1;
    }

删除元素函数

  • 删除必然需要查找是否存在。而查找又需要排序。哈哈~~~0.0.

    void My_Del(struct cont** pc)
    {
        assert(pc);//断言防止传入NULL指针
        char You_name[Name_max] = {0};
        char You_tel[Tel_max] = {0};
        printf("请输入要删除的姓名与电话\n");
        scanf("%s", You_name);
        scanf("%s", You_tel);
        int ret = My_Search(*pc,You_name,You_tel);
        if (ret!=-1)
        {
    
            for (size_t i = ret; i < (*pc)->sz-1; i++)
            {
                *((*pc)->date + i) = *((*pc) -> date+i + 1);
            
            }
            struct cont* p = (struct cont*)realloc(*pc, ((*pc)->sz - 1) * sizeof(struct peo));
            if (NULL == p)
            {
                printf("%s\n", strerror(errno));
                exit(1);
            }
            p->sz--;
            *pc = p;
        }
        else
        {
            printf("请重新输入\n");
        }
    }

修改元素函数

  • 修改必然查找......(套娃--哈哈 ~ ~~)

    void My_Modify(struct cont* pc)
    {
    
        assert(pc);//断言防止传入NULL指针
        char You_name[Name_max] = {0};//要初始化下,strcpy的原因
        char You_tel[Tel_max] = {0};
        char You_sex[Sex_max] = {0};
        char You_age[Age_max] = {0};
    
        while (1)
        {
            printf("请输入要修改的姓名与电话\n");
            scanf("%s", You_name);
            scanf("%s", You_tel);
            int ret = My_Search(pc, You_name, You_tel);
            if (ret != -1)
            {
                printf("请输入要修改的姓名:");
                scanf("%s", You_name);
                strcpy((pc->date + ret)->name, You_name);
    
                printf("请输入要修改的年龄:");
                scanf("%s", You_age);
                strcpy((pc->date + ret)->age , You_age);
    
                printf("请输入要修改的性别:");
                scanf("%s", You_sex);
                strcpy((pc->date + ret)->sex, You_sex);
    
                printf("请输入要修改的电话:");
                scanf("%s", You_tel);
                strcpy((pc->date + ret)->tel, You_tel);
                printf("修改成功\n");
                return;
            }
            else
            {
                printf("%s\t%s\n", You_name, You_tel);
            
            }
    
        }
    }

全部代码:

text.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

void menu()//提示菜单
{
   printf("****************************************************\n");
   printf("*************动态通讯录*****************************\n");
   printf("****************************************************\n");
   printf("*******1.Add(添加)               2. Del(删除)*******\n");
   printf("****** 3.Modify(纠正)            4.Show(打印)*******\n");
   printf("*******5.Search(寻找)              *******\n");
   printf("*******0.Exit(退出)                     ************\n");
   printf("****************************************************\n");
}

int main ()
{

   int Input = 0;
   struct cont* pc = NULL;//悬挂pc
   In_Bein(&pc);//因为pc NULL的原因,我们必须让它指向一段动态的内存,并初始化。
   do//先打印提示菜单,供玩家判断。
   {
       menu();
       printf("请输入你的选择:\n");
           scanf("%d", &Input);
       switch (Input)
       {
       case Add:My_Add(&pc);
           break;
       case Del:My_Del(&pc);
           break;
       case Modify:My_Modify(pc);
           break;
       case Show:My_Show(pc);
           break;
       case Search:
       {
           char You_name[Name_max] = {0};
           char You_tel[Tel_max] = {0};
           printf("请输入要找的的姓名与电话\n");
           scanf("%s", You_name);
           scanf("%s", You_tel);
           My_Search(pc, You_name, You_tel);
       };
          break;
       case Exit://exit是C语言内置的关键字,因此Exit,而不是 exit.
       {
           system("cls");//清空屏幕
           printf("\t\t~~~~~~~感谢使用博主的动态通许录!!!!~~~~~\n");
       };
          break;
       default:printf("输入错误。请重新输入\n");
           break;
       }

   } while (Input);
   free(pc);
   pc = NULL;
   return 0;
}

contact.h

#pragma once

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
#include <string.h>
enum whole//存放全局作用的常量
{
   Name_max=20,
   Age_max=10,
   Sex_max=20,
   Tel_max=20,
   Exit=0,
   Add,
   Del,
   Modify,
   Show,
   Search
};
   

struct peo
{
   char name[Name_max];
   char age[Age_max];
   char sex[Sex_max];
   char tel[Tel_max];
};

struct cont
{
   size_t sz;
   struct peo date[0];//柔性数组,这里用realloc
};

void In_Bein(struct cont** pc);
void My_Show(struct cont* pc);
void My_Add(struct cont** pc);
void My_Del(struct cont** pc);
int  My_Search(struct cont* pc,const char*str1,const char *str2);
void My_Modify(struct cont* pc);
int compare1(const void* e1, const void* e2);//种子1 比较名字
                               //比较结构体2元素中成员 名字 
int compare2(const void* e1, const void* e2);//种子2 比较学号
void swap(void* e1, void* e2, size_t width);
void my_qsort(void* base,size_t num,size_t width);//有2个种子就不需要 再来个参数了



contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"
void In_Bein(struct cont** pc)
{
   assert(pc);//断言防止传入NULL指针

   struct cont* p = (struct cont*)malloc(sizeof(struct cont));//这里只需要给sz申请空间就行了
                                                               //后面增加元素的时候再未peo开辟空间
   if (NULL == p)
   {

       printf("%s\n", strerror(errno));
       exit(1);
   }
   p->sz = 0;
   *pc = p;
   //p是局部变量,因此函数栈帧结束后,就成为野指针,但是那快动态申请的空间没有free,
   // 但是我们通过 *pc=p,这步,在程序结束,最后free就行了。
}
void My_Show(struct cont* pc)
{
   assert(pc);//断言防止传入NULL指针
   for (size_t i = 0; i < pc->sz; i++)
   {
       printf("姓名:%s\n", ((pc -> date)+i)->name);
       printf("年龄:%s\n", ((pc-> date)+i)->age);
       printf("性别:%s\n", ((pc->date)+i)->sex);
       printf("电话:%s\n", ((pc->date)+i)->tel);
   }

}
void My_Add(struct cont** pc)
{
   assert(pc);//断言防止传入NULL指针
   struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
   if (NULL == p)
   {
       printf("%s\n", strerror(errno));//打印错误信息
       exit(1);
   }
   printf("请输入姓名:");
   scanf("%s", (p->date + p->sz)->name);
   printf("\n");
   printf("请输入性别:");
   scanf("%s", (p->date + p->sz)->sex);
   printf("\n");

   printf("请输入年龄:");
   scanf("%s", (p->date + p->sz)->age);
   printf("\n");

   printf("请输入电话:");
   scanf("%s", (p->date + p->sz)->tel);
   p->sz++;
   *pc = p;
   my_qsort(p->date, p->sz, sizeof(struct peo));//排序新增后的元素
}

int  My_Search(struct cont* pc,const char *str1,const char *str2)//二分查找
{
   assert(pc && str1 && str2);//断言防止传入NULL指针
   size_t left = 0;
   size_t right = pc->sz - 1;
   while (left <= right)
   {
       size_t mid = (left + right) / 2;
       if ((strcmp((pc->date + mid)->name, str1)==0)&& (strcmp((pc->date + mid)->tel, str2) == 0))
       {
           printf("找到了\n");
           printf("%d\n", mid);
           return mid;
       }
       else if((strcmp((pc->date + mid)->name, str1)<0)||((strcmp((pc->date + mid)->name, str1)==0)  && (strcmp((pc->date + mid)->tel, str2)<0)))
       {
           left = mid + 1;
       
       }
       else if ((strcmp((pc->date + mid)->name, str1) > 0) || ((strcmp((pc->date + mid)->name, str1) == 0 && (strcmp((pc->date + mid)->tel, str2) > 0))))
       {
           right = mid - 1;
       }
   }

   printf("查无此人\n");
   return -1;
}

void My_Del(struct cont** pc)
{
   assert(pc);//断言防止传入NULL指针
   char You_name[Name_max] = {0};
   char You_tel[Tel_max] = {0};
   printf("请输入要删除的姓名与电话\n");
   scanf("%s", You_name);
   scanf("%s", You_tel);
   int ret = My_Search(*pc,You_name,You_tel);
   if (ret!=-1)
   {

       for (size_t i = ret; i < (*pc)->sz-1; i++)
       {
           *((*pc)->date + i) = *((*pc) -> date+i + 1);
       
       }
       struct cont* p = (struct cont*)realloc(*pc, ((*pc)->sz - 1) * sizeof(struct peo));
       if (NULL == p)
       {
           printf("%s\n", strerror(errno));
           exit(1);
       }
       p->sz--;
       *pc = p;
   }
   else
   {
       printf("请重新输入\n");
   }
}

void My_Modify(struct cont* pc)
{

   assert(pc);//断言防止传入NULL指针
   char You_name[Name_max] = {0};//要初始化下,strcpy的原因
   char You_tel[Tel_max] = {0};
   char You_sex[Sex_max] = {0};
   char You_age[Age_max] = {0};

   while (1)
   {
       printf("请输入要修改的姓名与电话\n");
       scanf("%s", You_name);
       scanf("%s", You_tel);
       int ret = My_Search(pc, You_name, You_tel);
       if (ret != -1)
       {
           printf("请输入要修改的姓名:");
           scanf("%s", You_name);
           strcpy((pc->date + ret)->name, You_name);

           printf("请输入要修改的年龄:");
           scanf("%s", You_age);
           strcpy((pc->date + ret)->age , You_age);

           printf("请输入要修改的性别:");
           scanf("%s", You_sex);
           strcpy((pc->date + ret)->sex, You_sex);

           printf("请输入要修改的电话:");
           scanf("%s", You_tel);
           strcpy((pc->date + ret)->tel, You_tel);
           printf("修改成功\n");
           return;
       }
       else
       {
           printf("%s\t%s\n", You_name, You_tel);
       
       }

   }
}


int compare1(const void* e1, const void* e2)//种子1 比较名字
                               //比较结构体2元素中成员 名字
{
   assert(e1 && e2);//防止传入NULL指针
   return strcmp(((struct peo*)e1)->name, ((struct peo*)e2)->name);

}
int compare2(const void* e1, const void* e2)//种子2 比较电话
{
   assert(e1 && e2);
   return strcmp(((struct peo*)e1)->tel, ((struct peo*)e2)->tel);

}
void swap(void* e1, void* e2, size_t width)//交换2元素所占内存字节中的内容
{

   assert(e1 && e2);
   for (size_t i = 0; i < width; i++)//依次交换e1,e2中的字节内容
   {
       unsigned char tmp = *((unsigned char*)e1 + i);
       *((unsigned char*)e1 + i) = *((unsigned char*)e2 + i);
       *((unsigned char*)e2 + i) = tmp;
   }

}
void my_qsort(void* base,
   size_t num,size_t width    )
{
   assert(base);//防止传入NULL指针
   
   for (size_t i = 0; i < num-1; i++)
   {
       size_t min = i;
       for (size_t j = i + 1; j < num; j++)
       { //传入的是date,因此强转(char*)后利用width就行了
           if (0 < compare1( (char *)base + min * width, (char*)base + j * width))
           {
               min = j;
           }
           else if (0 == compare1((char*)base + min * width, (char*)base + j * width))//一旦重名就检测电话
           {

               if (0 < compare2((char*)base + min * width, (char*)base + j * width))
               {
                   min = j;
               }
           }
       }
       if (min != i)
       {
           swap((char *)base + i * width, (char *)base + min * width,width);
       
       }

   }
}

效果展示

image-20211007222514767

image-20211007222539589

总结

弄清思路,代码的实现只是时间与思考的问题。
动态函数的语法容易理解,但是实操却会有很多问题坚持,调试,思考,测试。反复这个400行的小程序,你也可以
相关文章
|
1月前
|
存储
基于静态顺序表实现通讯录
基于静态顺序表实现通讯录
|
6月前
【动态通讯录】
【动态通讯录】
20 0
|
6月前
|
存储
【静态通讯录】
【静态通讯录】
12 0
|
8月前
|
存储
通讯录(静态版)
通讯录(静态版)
|
9月前
|
C语言
C/【静态通讯录】
C/【静态通讯录】
|
10月前
|
程序员 编译器 C语言
C语言---认识动态内存管理并实现一个动态通讯录:静态通讯录别来沾边
C语言学习——动态内存管理(上)+优化版通讯录+笔试题
|
10月前
|
C语言
静态通讯录
C语言学习——教你学会静态通讯录的实现(保姆级教程哦~)
|
10月前
|
存储 C语言 C++
动态版内存分配&动态内存通讯录的实现
动态版的通讯录 我们可以先了解一下动态内存分配
48 0
|
10月前
|
C语言
动态版通讯录
来了朋友们,今天给大家分享的是动态版本的通讯录。这个动态版本的通讯录较静态版本的通讯录的好处是,动态版本的通讯录对空间的浪费较少,并且可以随时增加空间,使用更加灵活。其实基本逻辑是跟静态版本的通讯录是一样的。