最近一直在打Python,就拿了一天的时间来复习一下C语言,我发现Python有一个很方便使用者的地方是Python里无论是列表还是字典都是可以自由地增加和删除,而C语言里的数组却要先指定大小,那么有没有什么办法来用C语言来实现Python列表或字典这样的功能呢?在C语言中,动态内存就可以让我们创建类似Python这样的功能。
今天来就来创建一个通讯录,我们并不能预知通讯录的使用者到底要使用这个通讯录来存储多少联系人,如果我们仍像以前那样创建的话可能会出现内存不足和内存过剩的现象,我们是否可以根据使用者的输入来动态地改变这个内存呢?先慢慢看。
首先我们先创建两个结构体:
struct Inf
{
char name[20];
char phone_num[12];
};
struct Point_inf
{
int num; //存放的数目
int max_num; //记录已经创建的空间
struct Inf* inf;
};
可能有人会说为什么要有两个结构体呢?这里的Inf主要用于存储联系人的信息,而Point_inf用于存储所有联系人,Point_inf中有一个整形num用于记录当前存放了多少个联系人,这个可以对后面记录删除或寻找有很大的帮助,这在后面遍历等功能可以发挥很大的作用,而max_num用于记录目前已经创建的空间,用于在删除后判断是否要增加空间,而inf是指向struct Inf的指针,我们正是用这个指针来开辟动态内存空间,实现自由存储的功能。
这里有个知识点就是结构体的内存对齐原则,结构体内的数据是根据他的字节大小的整数倍位置存放的,如果第一个类型是char第二个类型是int,它的内存不是5而是8,因为int占4个字节,它不会紧跟放在char的后面,而是浪费三个字节放在第4个字节的位置(类型所占字节数的整数倍),也就是说char占1个字节,浪费3个字节,然后int在占4个字节,所以我们在书写结构体的时候应该吧相同的类型放在一起,防止内存的浪费。当然在这里字节数都是相同的,怎么放都无所谓了。
接下来是主函数
int main()
{
struct Point_inf inf = {0, 0, NULL};
int function_number;
int flat = 1;
while (flat)
{
menu();
printf("input the function you want to use:");
scanf("%d", &function_number);
switch (function_number)
{
case 1:add(&inf); break; //由于要对inf做出改动,所有传参传inf的地址
case 2:my_remove(&inf); break;
case 3:put(&inf); break;
case 4:print_find(&inf);break;
case 0:flat = 0; free(inf.inf);inf.inf = NULL;break;
default:printf("the function is not in the menu\n"); break;
}
}
return 0;
}
这个主函数看起来十分简洁,因为我们将功能都封装在函数中了,用户只需要输入其想实现的功能就会调用函数去完成用户的需求,也可以从这里看出可以实现的功能有添加,删除,遍历和查找。
接下来就是函数部分了:
这个是用于打印菜单的函数,没什么技术含量
void menu()
{
printf("***1.添加***2.删除***\n");
printf("***3.打印***4.查找***0.退出***\n");
}
截下来是寻找函数:
int find(struct Point_inf* p, char* name)
{
int n;
for (n = 0; n < (p->num); n++) //解应用的级别比指向的级别低
{
if (strcmp(((p->inf)+n)->name, name) == 0)
{
return n;
}
}
return -1;
}
由于在删除和查找中都需要用到查找到具体联系人的num,因此将查找封装成一个函数,如果找到了则返回找到的位置的num,便于后面打印或删除。
这里有一个需要注意的地方是判断字符串是否相同不能直接的用等号,要调用strcmp来实现这个比较,当然strcmp这个函数想自己实现也很简单,有兴趣可以自己去实现这个函数。
接下来就是那几个功能函数了:
首先是添加函数:
void* add(struct Point_inf* p)
{
struct Inf* p2;
if (p->num >= p->max_num)
{
p2 = (struct Inf*)realloc(p->inf, (p->max_num+1) * sizeof(struct Inf)); //先开辟动态内存空间
if (p2 != NULL)
{
p->max_num++;
p->inf = p2;
}
else
{
printf("open space error\n");
return p2;
}
}
int num = p->num;
struct Inf* inf = p->inf;
printf("input the name:"); //存储名字和电话
scanf("%s", (inf+num)->name);
printf("input the phone number:");
scanf("%s", (inf+num)->phone_num);
(p->num)++; //给每个标号,num也表示了有多少个号码
}
添加函数是这里面最复杂的了,首先我们要先判断num和max_num的关系,如果这两者相等那么就意味着指针指向的空间满了,那么我们就用realloc重新开辟一个新的空间,这里每次只新增一个struct Inf的空间(p->max_num+1),在这之后判断是否开辟成功,如果成功就将这个指针给回给p,然后max_num加一表示可用的内存增加了一个,不成功就return结束这个函数,也没有后面打印的内容了,成功之后,就将用户输入的姓名和电话存入到新开辟的空间里,然后在吧num加1,可能在这里会有疑问说num和max_num都加一了那不就一直相等了吗,其实这个是为了删除后的空间利用做准备的。
这里要注意的是不要直接地就把realloc的返回值赋给原来的指针,因为当其开辟不成功后返回的是空指针,这样有可能导致原来的数据丢失;还有一点就是这个指向(->)的优先级是很高的,所以我们在指针加num中要加括号,让指针先相加再指向,例如(inf+num)->name。
接下来是删除函数:
void my_remove(struct Point_inf* p)
{
char name[20];
printf("input the name you want to remove:");
scanf("%s", name);
int rect = find(p, name);
if (rect != -1 && rect != p->num - 1)
{
for (;rect < p->num; rect++)
{
p->inf[rect] = p->inf[rect+1];
}
p->num--;
printf("remove successfully\n");
}
else if(rect != -1 && rect == p->num - 1)
{
p->num--;
printf("remove it successfully\n");
}
else
{
printf("the name not in the book\n");
}
}
在删除函数中,让使用者输入姓名,调用查找函数查找对应的编号,放回这个编号,根据这个编号来查找对应的人所在的位置,如果不是最后一位,那么就将后面的往前挪来覆盖那个人的信息,如果是最后一个,那么就直接吧num的位置前移一位,虽然这个人的信息依然在,但由于num的限制,我们无论查找还是打印都无法显示这个人的信息,在上面的添加函数中也会因为num小于max_num不会接着创建空间,而当用户输入下一个想要存的人时会利用删除的那块空间,如果是最后一个会直接把他覆盖掉(这样做事因为我们在最后一个后面是没有空间的,如果还从后面调用的话会导致非法访问的出现)
接下来是遍历函数了:
void put(struct Point_inf* p)
{
int n;
for (n = 0; n < p->num; n++)
{
printf("name:%s\n", p->inf[n].name);
printf("phone:%s\n", p->inf[n].phone_num);
}
}
这个函数是最简单的了,在这里就回顾一下指针的写法就过了
例如一个指针inf,如果想访问后面的几位inf[n]和inf+n是等价的,都是表示访问的是指针后的第n位
接下来是寻找函数:
void print_find(struct Point_inf* p)
{
char name[20];
printf("input the name you want to find:");
scanf("%s", name);
int ret = find(p, name);
if (ret != -1)
{
struct Inf* inf = p->inf;
printf("name:%s\n", (inf+ret)->name);
printf("phone:%s\n", (inf+ret)->phone_num);
}
else
{
printf("the guy you want to find not in your book\n");
}
}
这里也调用了之前写的find函数,这里的作用就是将find函数返回的下标来找到对应的名字和电话,可以说将寻找单独写成一个函数是一个很明智的选择,因为我们对这个函数有多次的使用,这里就简单多了,没什么技术含量,还是要注意一下优先级的问题,我也是在写这个程序的时候才发现这个优先级的问题。
最后的代码:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct Inf
{
char name[20];
char phone_num[12];
};
struct Point_inf
{
int num; //存放的数目
int max_num; //记录已经创建的空间
struct Inf* inf;
};
void menu()
{
printf("***1.添加***2.删除***\n");
printf("***3.打印***4.查找***0.退出***\n");
}
int find(struct Point_inf* p, char* name)
{
int n;
for (n = 0; n < (p->num); n++) //解应用的级别比指向的级别低
{
if (strcmp(((p->inf)+n)->name, name) == 0)
{
return n;
}
}
return -1;
}
void* add(struct Point_inf* p)
{
struct Inf* p2;
if (p->num >= p->max_num)
{
p2 = (struct Inf*)realloc(p->inf, (p->max_num+1) * sizeof(struct Inf)); //先开辟动态内存空间
if (p2 != NULL)
{
p->max_num++;
p->inf = p2;
}
else
{
printf("open space error\n");
return p2;
}
}
int num = p->num;
struct Inf* inf = p->inf;
printf("input the name:"); //存储名字和电话
scanf("%s", (inf+num)->name);
printf("input the phone number:");
scanf("%s", (inf+num)->phone_num);
(p->num)++; //给每个标号,num也表示了有多少个号码
}
void put(struct Point_inf* p)
{
int n;
for (n = 0; n < p->num; n++)
{
printf("name:%s\n", p->inf[n].name);
printf("phone:%s\n", p->inf[n].phone_num);
}
}
void my_remove(struct Point_inf* p)
{
char name[20];
printf("input the name you want to remove:");
scanf("%s", name);
int rect = find(p, name);
if (rect != -1 && rect != p->num - 1)
{
for (;rect < p->num; rect++)
{
p->inf[rect] = p->inf[rect+1];
}
p->num--;
printf("remove successfully\n");
}
else if(rect != -1 && rect == p->num - 1)
{
p->num--;
printf("remove it successfully\n");
}
else
{
printf("the name not in the book\n");
}
}
void print_find(struct Point_inf* p)
{
char name[20];
printf("input the name you want to find:");
scanf("%s", name);
int ret = find(p, name);
if (ret != -1)
{
struct Inf* inf = p->inf;
printf("name:%s\n", (inf+ret)->name);
printf("phone:%s\n", (inf+ret)->phone_num);
}
else
{
printf("the guy you want to find not in your book\n");
}
}
int main()
{
struct Point_inf inf = {0, 0, NULL};
int function_number;
int flat = 1;
while (flat)
{
menu();
printf("input the function you want to use:");
scanf("%d", &function_number);
switch (function_number)
{
case 1:add(&inf); break; //由于要对inf做出改动,所有传参传inf的地址
case 2:my_remove(&inf); break;
case 3:put(&inf); break;
case 4:print_find(&inf);break;
case 0:flat = 0; free(inf.inf);inf.inf = NULL;break;
default:printf("the function is not in the menu\n"); break;
}
}
return 0;
}
当然由于水平有限,肯定还有值得改进的地方,通过写这个程序也只是起到了一个温故而知新的作用。