目录
开辟动态内存的方式
Malloc
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
free
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数ptr是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中。
calloc
C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
realloc
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 的调整。
函数原型如下: void* realloc (void* ptr, size_t size);
realloc 函数就可以做到对动态开辟内存大小 ptr 是要调整的内存地址 size 调整之后新大小 返回值为调整之后的内存起始位置。 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。 realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间比
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。
具体的四个函数如何使用,如果感兴趣的话可以去找找例子,我们这里主要来谈如何利用动态内存来制作一个通讯录,以及使用的时候需要注意的事项
通讯录的制作
源代码
头文件 contact.h
#pragma once
//写一个通讯录,其中人物信息包含姓名、性别、年龄、电话、地址
//需要通讯录具备:增加、删除、搜索、修改、排序、展示、退出
//采用动态内存的分配方式
#define _CRT_SECURE_NO_WARNINGS
#define Max_name 20
#define Max_sex 10
#define Max_phone_num 20
#define Max_addr 30
#define initi_size 3
#define add_size 2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
typedef struct Pepoinfo
{
char name[Max_name];
char sex[Max_sex];
int age;
char phone_num[Max_phone_num];
char addr[Max_addr];
}Pepoinfo;
typedef struct Con
{
Pepoinfo* data;
int sz;
int capacity;
}Con;
void initi_Con(Con* addr);
void Add_Con(Con* addr);
void Print_Con(Con* addr);
void Del_Con(Con* addr);
void Search_Con(Con* addr);
void Modify_Con(Con* addr);
void Sort_Con(Con* addr);
void Destory_Con(Con* addr);
contact.c文件
#include "contact.h"
void initi_Con(Con*addr)
{
//将数据存储在动态内存中
addr->data=(Pepoinfo*)(malloc(initi_size*sizeof(Pepoinfo)));
//如果为动态内存不足以分配空间既返回空指针,此时结束初始化
if (addr->data == NULL)
{
perror("initi_Con");
return;
}
addr-> sz = 0;
addr-> capacity = initi_size;
}
static int is_Con(Con* addr,char*str)
{
//按名字搜索是否为结构数组内部的元素
//如是返回数组下标
//如不是返回-1
int i;
for (i = 0; i < addr->sz; i++)
{
if (strcmp(addr->data[i].name, str) == 0)
{
return i;
}
}
return -1;
}
void Add_Con(Con* addr)
{
//首先判断是否动态内存需要扩容
if (addr->sz == addr->capacity)
{
Pepoinfo* ret = (Pepoinfo * )(realloc(addr->data, (addr->capacity + add_size)*sizeof(Pepoinfo)));
if (ret != NULL)
{
addr->data = ret;
}
else
{
perror("Add_Con");
return;
}
}
//输入数据信息
printf("请输入需要输入的姓名:\n");
scanf("%s", addr->data[addr->sz].name);
printf("请输入需要输入的性别:\n");
scanf("%s", addr->data[addr->sz].sex);
printf("请输入需要输入的年龄:\n");
scanf("%d", &addr->data[addr->sz].age);
printf("请输入需要输入的手机号:\n");
scanf("%s", addr->data[addr->sz].phone_num);
printf("请输入需要输入的地址:\n");
scanf("%s", addr->data[addr->sz].addr);
addr->sz++;
}
void Print_Con(Con* addr)
{
//按照结构体数组的大小下标依次打印数据
printf("%-20s\t%-10s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "手机号", "地址");
int i;
for (i = 0; i < addr->sz; i++)
{
printf("%-20s\t%-10s\t%-d\t%-20s\t%-30s\n", addr->data[i].name,
addr->data[i].sex,
addr->data[i].age,
addr->data[i].phone_num,
addr->data[i].addr);
}
}
void Del_Con(Con* addr)
{
printf("请输入要删除的人:");
char input_name[Max_name];
scanf("%s", input_name);
//is_Con为判断是否为结构体内的数据
//如是返回下标
//如不是返回-1
int ret = is_Con(addr, input_name);
int i;
if (ret == -1)
{
printf("输入无此人\n");
return;
}
//将结构体的数据往前移既删除了当前下标的值
for (i = ret; i < addr->sz - 1; i++)
{
addr->data[i] = addr->data[i + 1];
}
printf("删除成功\n");
addr->sz--;
}
void Search_Con(Con* addr)
{
printf("请输入要搜索的人:");
char input_name[Max_name];
scanf("%s", input_name);
int ret = is_Con(addr, input_name);
if (ret == -1)
{
printf("输入无此人\n");
return;
}
printf("%-20s\t%-10s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "手机号", "地址");
printf("%-20s\t%-10s\t%-d\t%-20s\t%-30s\n", addr->data[ret].name,
addr->data[ret].sex,
addr->data[ret].age,
addr->data[ret].phone_num,
addr->data[ret].addr);
return;
}
void Modify_Con(Con* addr)
{
printf("请输入要修改的人:");
char input_name[Max_name];
scanf("%s", input_name);
int ret = is_Con(addr, input_name);
if (ret == -1)
{
printf("输入无此人\n");
return;
}
printf("请输入需要输入的姓名:\n");
scanf("%s", addr->data[addr->sz].name);
printf("请输入需要输入的性别:\n");
scanf("%s", addr->data[addr->sz].sex);
printf("请输入需要输入的年龄:\n");
scanf("%d", &addr->data[addr->sz].age);
printf("请输入需要输入的手机号:\n");
scanf("%s", addr->data[addr->sz].phone_num);
printf("请输入需要输入的地址:\n");
scanf("%s", addr->data[addr->sz].addr);
printf("修改成功\n");
}
static int sortbyage(void* struct1, void* struct2)
{
return((((Pepoinfo*)struct1)->age) - (((Pepoinfo*)struct2)->age));
}
static int sortbyname(void* struct1, void* struct2)
{
return strcmp((((Pepoinfo*)struct1)->name) , (((Pepoinfo*)struct2)->name));
}
void Sort_Con(Con* addr)
{
printf("请问需要按照什么进行排序\n");
printf("****1.年龄 2.姓名******\n");
//利用qsort进行排序
int input_sort;
do
{
scanf("%d", &input_sort);
switch (input_sort)
{
case 1:
qsort(addr->data, addr->sz, sizeof(Pepoinfo), sortbyage);
break;
case 2:
qsort(addr->data, addr->sz, sizeof(Pepoinfo), sortbyname);
break;
default:
printf("输入错误请重新输入:\n");
break;
}
} while (input_sort != 1 && input_sort != 2);
printf("排序成功\n");
}
void Destory_Con(Con* addr)
{
//释放内存
free(addr->data);
addr->data = NULL;
addr->sz = 0;
addr->capacity = 0;
printf("结束程序\n");
}
主程序Test.c文件
#include "contact.h"
void menu()
{
printf("************************\n");
printf("* 1.Add 2.Del *\n");
printf("* 3.Search 4.Modify *\n");
printf("* 5.Sort 6.Print *\n");
printf("* 0.Exit *\n");
printf("************************\n");
}
void main()
{
//初始化
Con Contact;
initi_Con(&Contact);
int input;
//将需要进行的操作函数放到一个函数指针数组内
//根据需要的需求排放位置,如输入1为加联系人既吧Add_Con放在下标1处
void (*option_arr[7])(Con * addr) = {
Destory_Con ,
Add_Con,
Del_Con,
Search_Con,
Modify_Con,
Sort_Con,
Print_Con };
do
{
menu();
printf("请输入需要选择的操作:\n");
scanf("%d", &input);
if (0 <= input && input <= 7)
{
//根据下标以及输入的参数选择要进行的操作
option_arr[input](&Contact);
}
else
{
printf("输入错误请重新输入\n");
}
}while (input);
}
代码解读以及注意事项
我们这次的通讯录的任务是
写一个通讯录,其中人物信息包含姓名、性别、年龄、电话、地址
需要通讯录具备:增加、删除、搜索、修改、排序、展示、退出
采用动态内存的分配方式
首先我们要先创建一个人物信息的结构体,以及通讯录的结构体,其中人物信息的补充我们采用分配动态内存空间的方式,如下
其中要注意的是data我们采用动态内存分配,当进行动态内存开辟的时候会返回一个开辟空间的第一个地址的初始指针,我们采用data来进行接受,所以在Contact中初始化data采用Pepoinfo的结构体,但后Con中sz是代表填入了多少个联系人的意思是,capacity是现在data动态内存目前开辟的空间,我们设置初始开辟的空间为3个Peopinfo的大小。
紧接着我们进行第一个关键点的位置就是如何初始化这样的结构体呢?
我们看看我们初始化函数内部的代码
我们来一点点分析这个代码,首先malloc是开辟一个动态内存空间没错,但是要开辟多少呢?我们首先定义初始的内存为三个Pepoinfo的大小,我们定义initi_size的大小为3然后乘上一个sizeof(Pepoinfo)就可以了,我们直到malloc返回的指针类型为空指针,我们需要对其进行类型转换为我们的pepoinfo的结构体,然后再用data进行接收就完成了我们的数据初始化,当然我们要考虑如果不够内存空间分配返回空指针的情况,这个时候我们便结束我们的初始化。
第二个关键点是,对于选择操作,我们建立了一个函数指针数组如下所示
我们定义了销毁(也就是退出)、增加联系人、删除联系人、搜索联系人、修改联系人、排序联系人、以及打印联系人的操作,分别对应了0~6,既输入数字为多少就进行哪个操作。
如下所示
这里我们主要讲讲Add Del 和Sort这三个操作中的重点内容,其他的部分注释的都比较详细
对于增加联系人,我们知道我们动态内存目前之开辟了三个联系人大小的空间,如果我们联系人超出了我们的内存应该怎么样呢?这里我们就可以看看这几行代码
我们先输入一个判断,如果联系人个数sz等于我们的容量了,那我们将realloc我们的空间,返回值我们用结构体指针Pepoinfo修改指针变量为结构体变量,增加大小为我们要设置的add_size的大小,这里我们设置的2就是每次都多增加2个人,我们用ret接收这个指针,接受这个指针是怕传回来的为空,不可以直接用我们之前data指向的地点来接受,不然我们找不到我们原数据的内容。接着如果判断不为空,我们就用原数据data接受这个返回值。这就是Add联系人对于动态指针比较重要的部分
现在我们来看一下Del里面的重点内容
这段代码是找到了我们返回的要删除的数据下标然后进行删除,这里我们发现这段代码的核心思想是下标后的结构体数据往前移覆盖前一个结构体的数据即可完成删除,这里有没有人和我一样有一个疑问,就是结构体里面有char的数组有int类型,我们知道char数组类型如果要进行赋值,得一个个字节的进行赋值,为什么结构体这里可以直接从后往前赋值呢?这样子真的合理吗?
为此我特以写了个程序看看是否可以进行赋值
最终我们看到,结构体是可以直接进行交换的,笔者这里猜想结构体的赋值,应该是地址之间的转换,所以可以直接两个相同结构体赋值即可
第三点就是sort的实现
这里的sort函数我采用了姓名以及年龄两种方式进行排序
其中用到了qsort函数
这里重要的地方就是如果用到qsort对结构体内部某一元素进行整体排序的时候应该如何操作的问题。
和其他的一样前面三个数据分别是,需要排序的地址,需要排序的数组内有几个元素,以及每个原素所占的大小,接着最后一个就是实现这个qsort排序的重点了,我们知道最后一项的返回值是一个int类型的,如果两个数比较,前面比后面的大返回一个大于0的数,后面比前面大返回小于0的数,相等就返回一个等于0的数。那如果我们要按照年龄来排序如何写这个函数呢?我们来看一下下面的代码
因为这里的函数要求我们输入的是void*的指针,我们首先对其转换为Pepoinfo的结构体,然后指向其年龄,然后用第一个减去第二个就可以实现我们的目的了。
用动态内存制作一个通讯录一些重点就在这个地方,其他的地方笔者有注释并且容易看得懂,感谢大家阅读。