大家好啊,我是Happy__pomelo,可以叫我小柚子,最近开始重新学习c,今天第一次发布文章,也是想通过这种方式来记录自己的成长!!!
你们的通讯录里面一定有许多的亲朋好友的联系方式,看到这里不妨给许久没有联系的亲朋好友打个电话说说你此刻正想念他们!!!
好了,咱们进入正题,今天以通讯录来带大家一起理解动态内存函数的逻辑,同时也加深自己对动态内存函数的理解。
先来建立一个快速原型模型:
#define _CRT_SECURE_NO_WARNINGS 1
enum Option
{
EXIT,//0
ADD,//1
DEL,//2
MODIFY,
SEARCH,
SHOW,
SORT
}; //此处为枚举类型,在下方Switch使用到的时候出现的就不是数字
//而是一个看上去有意义的符号,而不用每次去查找,方便后期维护
#include <stdio.h>
void menu()
{
printf("__________________________________________________\n");
printf("|************************************************|好友信息:\n");
printf("|******** 欢迎使用柚子牌通讯录 ********|姓名:XXX\n");
printf("|******** 1.增加好友信息 2.删除好友信息 ********|电话:XXX\n");
printf("|******** 3.修改好友信息 4.查找好友信息 ********|性别:XXX\n");
printf("|******** 5.显示好友信息 6.好友信息排序 ******|住址:XXX\n");
printf("|************** 0.退出程序 **************|年龄:XXX\n");
printf("|************************************************|\n");
printf("|________________________________________________|\n");
printf(" Please select a function\n");
}
int main()
{
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case ADD:
break;
case DEL:
break;
case MODIFY:
break;
case SEARCH:
break;
case SHOW:
break;
case SORT:
break;
case EXIT:
break;
default:
break;
}
} while (input);
return 0;
}
/*
* 通讯录模块
* 1.每个好友都包含以下属性(属性不一定全有)
* 姓名
* 电话
* 性别
* 住址
* 年龄
* 2.具有增删改查四种功能
* 3.打印好友信息
* 4.对好友信息进行排序
* ……
*
*/
那么这里也是用过一个快速还原模型让大家能够简单的了解通讯录的功能
整体思想:通过动态指针的形式来创建通讯录,为什么选择动态通讯录呢?通讯录里面的人数不是固定不变的,如果初始化一个极大的空间,然而只存储几个人,那么多余的数据必将导致浪费,那么有什么方法可以将空间的浪费降至最低呢?然而初始化的空间是有限的,而要存储的联系人是无限的,那么要怎么做才能保证可以随时根据需要动态的增加空间呢?
PS:在得出答案之前先做一个情景模拟,把通讯录比作一个盒子,而这个盒子放在一个房间内部的任意一处,这个盒子的大小是可以伸缩的,以保证可以装得下物品又不会浪费太多的房子空间,而通讯录也是同样的思想,讲到这里,相比大家对动态空间就有了一个初步的认识了,那么接下来就说几个动态内存函数:malloc, free, realloc, calloc(需要使用到stdlib.h头文件)
以下是函数原型:
void *malloc( size_t size );//创建一个size大小的空间,并返回首地址
void *realloc( void *memblock, size_t size );//创建size个大小的空间,每一个空间的大小对应memblock的大小
void free( void *memblock );//将memblock该指针指向的首地址释放掉,但此时该指针仍然记忆着原地址,因此该语句后面常常会重新赋予其一个NULL空指针
void *calloc( size_t num, size_t size );//创建num个空间,每一个空间的大小为size,并且会初始化内容为0
接下来开始梳理思路
需要用到一个People对象用来描述每个联系人的属性,那么就将其创建为一个结构体
创建一个动态数组用来保存people结构体,以及一个size标识符用来表示该通讯录的最大人数,flag用来表示当前已经有几个人了
c. 需要一个枚举类型,方便维护以及提高代码的可读性
d. 需要使用到几个主要的函数:初始化,增删改查对应函数,打印函数,排序函数……
万事俱备,那么开始编写代码,首先是头文件的编写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//在这里用到了大量的宏定义的原因:当需要修改一个参数的时候能够更加方便的修改,而不用挨个找,提高可维护性
#define INITSIZE 2//当容量不够的时候,每次增加的个数
#define MAX_NAME 20
#define MAX_TELE 12
#define MAX_SEX 5
#define MAX_ADDR 20
enum Option
{
EXIT,//0
ADD,//1
DEL,//2
MODIFY,
SEARCH,
SHOW,
SORT
}; //此处为枚举类型,在下方Switch使用到的时候出现的就不是数字
//而是一个看上去有意义的符号,而不用每次去查找,方便后期维护
typedef struct People
{
char name[MAX_NAME];
char tele[MAX_TELE];
char sex[MAX_SEX];
char addr[MAX_ADDR];
int age;
}People;
typedef struct Contact
{
int size;//该通讯录当前科容纳多少个联系人
int flag;//当前有几个联系人
People* data;
//****************这里要说一个重点,这个结构体为可更改空间的结构体,原因就在于最后一个指针
// 这个指针就是用于动态扩容的
}Contact;
void menu();//菜单显示
void InitContact(Contact* con);//初始化数据
void show(const Contact* con);//打印通讯录
void add_contact(Contact* con);//增加通讯录成员
void del_contact(Contact* con);//删除一个通讯录成员
void modify_contact(Contact* con);//修改一个通讯录成员
void search_contact(Contact* con);//搜索一个通讯录成员
void sortByName_contact(Contact* con);//以姓名的形式排序通讯录成员
void release(Contact* con);//释放空间
接下来是函数的实现部分
menu函数较为简单,就不用过多篇幅解释了
void menu()
{
printf("__________________________________________________\n");
printf("|************************************************|好友信息:\n");
printf("|******** 欢迎使用冰冰牌通讯录 ********|姓名:XXX\n");
printf("|******** 1.增加好友信息 2.删除好友信息 ********|电话:XXX\n");
printf("|******** 3.修改好友信息 4.查找好友信息 ********|性别:XXX\n");
printf("|******** 5.显示好友信息 6.好友信息排序 ******|住址:XXX\n");
printf("|************** 0.退出程序 **************|年龄:XXX\n");
printf("|************************************************|\n");
printf("|________________________________________________|\n");
printf(" Please select a function\n");
}
初始化函数
void InitContact(Contact* con)//初始化数据
这个函数里面涉及了malloc,可以看到,用了一个con->data来接收这个函数的返回值,因为是要对People这个数据结构进行扩容而不是对Contact这个数据结构扩容,之前的结构体的妙用也在此得到了体现,data是People类型,而malloc的返回值为一个指针类型,因此要进行类型强制转换,他的参数即为每一个的大小,用一张图来表示这个函数给我们做了什么事
如果malloc函数返回了一个空指针就代表没有多余的内存空间了,即创建失败,同时也返回一个空,结束该函数,若创建成功就对size,flag赋予初值
void InitContact(Contact* con)//初始化数据
{
con->data = (People*)malloc(INITSIZE * sizeof(People));
if (con->data == NULL)
{
perror("con->data::");
return ;
}
con->size = INITSIZE;
con->flag = 0;
}
接下来是void show(const Contact* con)//打印通讯录
指针引用结构体参数,用->,若不是则是“ . ”操作符
void show(const Contact* con)//打印通讯录
{
int i = 0;
printf("当前有%d个联系人\n", con->flag);
if (con->flag == 0)
{
printf("%-10s\t%-15s\t%-5s\t%-20s\t%-5s\t\n", "姓名", "电话", "性别", "地址", "年龄");
}
else
{
printf("%-10s\t%-15s\t%-5s\t%-20s\t%-5s\t\n", "姓名", "电话", "性别", "地址", "年龄");
for (i = 0; i < con->flag; i++)
{
printf("%-10s\t%-15s\t%-5s\t%-20s\t%-5d\t\n",
con->data[i].name,
con->data[i].tele,
con->data[i].sex,
con->data[i].addr,
con->data[i].age);
}
}
}
void add_contact(Contact* con)//增加通讯录成员
此函数用到了realloc函数,这个函数挺有趣的,我把他理解成malloc函数的进化体,因为这个函数可以用来扩建,而malloc是用来初始化的时候创建的。realloc函数的工作原理是先看需要多少个字节空间,然后再看原先的连续空间后面是否可以容的小扩充的空间,如果容不下,则会自动找一片可以容纳原先字节大小+扩充字节大小总和的一片空间。如图
该函数的返回值也是一个指针,若创建失败则返回空指针NULL,创建成功则返回该指针的首地址,函数有两个参数,前者是是类型,后者是大小(个数*类型的大小)
int expansion(Contact* con)//扩容函数,并且返回1(成功) / 0(失败)
{
if (con->flag >= con->size)
{
con->data = (People*)realloc(con->data, (con->size + 1) * sizeof(People));
con->size++;
if (!con->data)
{
printf("扩容失败\n");
perror("con->data::");
return 0;
}
else
{
printf("扩容成功\n");
return 1;
}
}
else
{
return 1;
}
}
void add_contact(Contact* con)//增加通讯录成员
{
char name[MAX_NAME] = { 0 };
char tele[MAX_TELE] = { 0 };
char sex[MAX_SEX] = { 0 };
char addr[MAX_ADDR] = { 0 };
int age = 0;
if (expansion(con))
{
printf("请输入姓名:>");
scanf("%s", &con->data[con->flag].name);
printf("请输入电话:>");
scanf("%s", &con->data[con->flag].tele);
printf("请输入性别:>");
scanf("%s", &con->data[con->flag].sex);
printf("请输入住址:>");
scanf("%s", &con->data[con->flag].addr);
printf("请输入年龄:>");
scanf("%d", &con->data[con->flag].age);
con->flag++;
}
}
int find_People(Contact* con, char name[MAX_NAME])//查找联系人是否存在于通讯录中,字符串比较使用strcmp函数,如果两字符串相等则返回0,如果前者比后者大则返回一个大于0的数,在VS中返回1,GCC环境中是返回ASCII码的差值,如果前者比后者小则返回一个小于0的数,VS于GCC同理。
在该函数只会出现两类返回值,一类是>=0 且小于con->flag的数,一类是-1,前者代表能查找到此联系人,如果整个for循环都循环完了,都没查找到,则会返回一个-1代表无此联系人
int find_People(Contact* con, char name[MAX_NAME])
{
int i = 0;
for (i; i <= con->flag; i++)
{
if (0 == strcmp(name, con->data[i].name))
{
return i;
}
}
return -1;
}
void del_contact(Contact* con)//删除一个通讯录成员该代码逻辑较为简单,会运用到上面的find_People函数,删除一个联系人有两种方式,1、直接将最后一个人替换掉待删除者,另一种方式则是从待删除者这个位置开始
另一种方式后面的一个一个往前面替换,这里采用的是第二种方法
void del_contact(Contact* con)//删除一个通讯录成员
{
char name[MAX_NAME] = { 0 };
int i = 0;
printf("请输入要删除者姓名:>");
scanf("%s", name);
i = find_People(con, name);
if (i < con->flag) //通过i来接收返回值
{
for (i; i < con->flag - 1; i++)//从返回值开始替换
{ //con->flag - 1的原因是替换的值是以i为起始值到最后一个,如果这个1不减去,就将会替换到通讯录意外的内容导致数据异常
con->data[i] = con->data[i + 1];
}
con->flag--;//替换完了需要减一
}
if (i == -1)
{
printf("没有此人\n");
}
}
以下两个函数上一个函数的逻辑大同小异,就不赘述了
void modify_contact(Contact* con)//修改一个通讯录成员
void search_contact(Contact* con)//搜索一个通讯录成员
void modify_contact(Contact* con)//修改一个通讯录成员
{
char name_find[MAX_NAME] = { 0 };
char name[MAX_NAME];
char tele[MAX_TELE];
char sex[MAX_SEX];
char addr[MAX_ADDR];
int i = 0;
printf("请输入要修改者姓名:>");
scanf("%s", name_find);
i = find_People(con, name_find);
if (i < con->flag)
{
printf("请输入姓名:>");
scanf("%s", &name);
strcpy(con->data[i].name, name);
printf("请输入电话:>");
scanf("%s", &tele);
strcpy(con->data[i].tele, tele);
printf("请输入性别:>");
scanf("%s", &sex);
strcpy(con->data[i].sex, sex);
printf("请输入住址:>");
scanf("%s", &addr);
strcpy(con->data[i].addr, addr);
printf("请输入年龄:>");
scanf("%d", &con->data[i].age);
}
if (i == -1)
{
printf("没有此人\n");
}
}
void search_contact(Contact* con)//搜索一个通讯录成员
{
char name[MAX_NAME] = { 0 };
int i = 0;
printf("请输入要删除者姓名:>");
scanf("%s", name);
i = find_People(con, name);
if (i < con->flag)
{
printf("此人已查到:>\n");
printf("%-10s\t%-15s\t%-5s\t%-20s\t%-5s\t\n", "姓名", "电话", "性别", "地址", "年龄");
printf("%-10s\t%-15s\t%-5s\t%-20s\t%-5d\t\n",
con->data[i].name,
con->data[i].tele,
con->data[i].sex,
con->data[i].addr,
con->data[i].age);
}
if (i == -1)
{
printf("没有此人,请检查输入是否正确\n");
}
}
这里运用了冒泡排序,来对联系人进行了一个排序,在后面自己写了一个全类型的数据互换函数,冒泡函数为两层for循环结构,第一层为要进行比较的趟数,第二层为要进行比较的次数
做个类推:1 2 两个数要进行比较需要一趟;3 2 1三个数进行比较需要两趟,即3与2进行比较,选最大的数3再与1进行比较,第一趟得到最大数3,依次类推得到全新数组1 2 3
void sortByName_contact(Contact* con)//以姓名的形式排序通讯录成员
{
int i = 0, j = 0;
//趟数
for (i; i < con->flag - 1; i++)
{
//比较次数
for (j = 0; j < con->flag - i - 1; j++)
{
if (con->data[j].age > con->data[j + 1].age)
{
//万能调换函数
change((char* )&con->data[j], (char* )&con->data[j + 1], sizeof(People));
}
}
}
}
void change(char* data1, char* data2, int Psz)
data1:交换数1
data2:交换数2(两个都是相同类型)
Psz:该类型的大小
之所以选择char* 的原因是该指针每次移动的时候都是1个字节,刚好可以将各种类型的数据结构类型的内容按字节遍历完不会多也不会少,易于操作
void change(char* data1, char* data2, int Psz)
{
char buf = '\0';
int i = 0;
for (i; i < Psz; i++)
{
buf = *data1;
*data1 = *data2;
*data2 = buf;
data1++;//两个对象的指针也要往后边移动一个
data2++;
}
}
void release(Contact* con)//释放空间
这个函数是与malloc calloc realloc联系起来成对使用的
解释:电脑的内存空间是有限的,如果资源被不停的调用,不断占用有限的内存空间,那么必定有一时刻会用完,从而导致程序崩溃,而赋予空指针的原因则是,当对空间进行了释放之后,其指针仍然指向原先位置,对于原先内存来说这是不安全的,被人搞了后门,所以需要对其赋予一个空指针,让他没有指向,也使得原数据具有更好的安全性。
void release(Contact* con)//释放空间
{
free(con->data);
con = NULL;
}
以上为个人学习之中的体会,如有疑问请留下你的足迹让我看见你