[C Language] Introduction to Simple Address Book 2 and Dynamic Memory Management

Preface

See here for the implementation of static address book[C language] Implementation of simple address book

After we implemented the address book last time, we summarized two major flaws in the address book last time:

  1. Its storage size is limited and space will not be automatically allocated based on demand.
  2. It cannot save data after the program exits, and the data must be re-recorded every time the program is entered.

So this time, we will solve the problem of limited space.

But before that, we need to first understand some functions that help us achieve this function

Introduction to dynamic memory functions

malloc

mallocIt is a dynamic memory allocation function provided by C language, which allows us to apply for a space in the memory in real time to store data. It is defined as follows

void* malloc (size_t size);

When using this function, please pay attention to the following points:

  • sizeIs the size of the memory space to be opened, in bytes
  • If the memory is allocated successfully, the returned pointer is a pointer to the starting point of the allocated memory. The type is a null pointer. Type conversion is required when using it.
  • If the memory allocation fails, a null pointer will be returned.Since the dynamic allocation of memory may fail, we must check it every time we open it to prevent unknown space. Take action
  • If the number of size is 0, the behavior of malloc is not defined in the C language standard. How to operate is determined by the compiler

The following is an example of using the malloc function to open up space

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main() {
    
    
	int* p = (int*)malloc(20);
    //判断是否开辟成功
    //没成功就结束程序
	if (NULL == p) {
    
    
		printf("%s\n", strerror(errno));
        return 1;
	}
    //通过操作数组的方式操作一块动态内存
    for (int i = 0; i < 5; i++) {
    
    
		p[i] = i + 1;
	}
	for (int i = 0; i < 5; i++) {
    
    
		printf("%d", p[i]);
	}
	return 0;
}

At this time, a space of 20 bytes has been successfully opened, and the return pointer type has been set to int*. We can operate these 20 words through this pointer. section space

But there is actually a problem at this time. The dynamic allocation function can dynamically operate memory, which occupies the memory in the heap area. The space in the heap area is limited, and the dynamically allocated memory even if we do not perform any operations on this space. Leave it empty and the program will not clean up automatically unless it is recycled by the operating system after the program is closed. Then when the program is running, when we continue to dynamically allocate memory, sooner or later the heap area will be full, and the program will crash due to insufficient memory.

The above memory operation that dynamically allocates memory without recycling it is called a memory leak.

In order to prevent memory leaks from occurring, we should try our best to manually clean up a piece of dynamically allocated memory after using it. At this time, we will use the function we introduce below.

free

In order to allow the program to clean up dynamic memory that is no longer used, C language provides this function to release and reclaim memory, which is defined as follows

void free (void* ptr);

Theptr pointer should point to dynamically allocated memory. If the memory pointed to byptr is non-dynamic, then the behavior of this function is also undefined. Its behavior is determined by the compiler

Ifptr is a NULL pointer, then thefree function will not perform any operation

Then modify the above code to the normal usage form and it should be as follows

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main() {
    
    
	int* p = (int*)malloc(20);
	if (NULL == p) {
    
    
		printf("%s\n", strerror(errno));
        return 1;
	}
    for (int i = 0; i < 5; i++) {
    
    
		p[i] = i + 1;
	}
	for (int i = 0; i < 5; i++) {
    
    
		printf("%d", p[i]);
	}
	//不仅要将内存空间释放,也要将指针置空
    //因为释放内存后这个指针就变成了野指针
	free(p);
	p = NULL;
	return 0;
}

calloc

This function is very similar tomalloc. It is also used to dynamically allocate a memory. It is defined as follows

void* calloc (size_t num, size_t size);

But there are two differences between it andmalloc

  • callocAllocating space is to open up num a space of size size. The allocation mode is more similar to the memory allocation method of array, and malloc is to directly open up space of size bytes
  • callocAfter the space is allocated, all bytes in the allocated space will be modified to 0

So if we write code and need to initialize the space after opening it, we can directly use the calloc function

realloc

Since the memory is dynamically allocated, we will inevitably need to reallocate the memory to adapt to our needs. Therefore, the C language also provides a function for us to re-adjust the dynamically allocated memory space. The usage format is as follows

void* realloc (void* ptr, size_t size);

ptr points to the starting point of the dynamic memory to be adjusted, size is the size of the space to be reallocated, and the return value is the starting position of the adjusted memory

Some people may be confused, what is returning to the starting position of the adjusted memory, isn't it just changing it in place?

Actuallyrealloc operates as follows:

  1. If the space is reduced, the excess dynamic memory will be released (equivalent tofree)
  2. If the space is to be enlarged, first confirm whether the space is enough. If it is enough, directly open the memory at the rear.
  3. If the space is enlarged and the rear memory is not enough, a new space will be found to open up, and the data of the original memory will be copied over, and the previously occupied memory will be released at the same time.
  4. If the space is enlarged and the rear memory is not enough, and there is not enough space to open up a new space, returnNULL

Therefore, the following operation is not recommended

int main() {
    
    
	int* p = (int*)malloc(100);
	if (ptr == NULL) {
    
    
		printf("%s\n", strerror(errno));
		return 1;
	}
	p = (int*)realloc(p, 1000);
}

If it is case 4, then our p becomes a null pointer at this time, and the space originally opened by malloc has not been modified or released

Since we directly cleared p, we lost the pointer that can find the memory. At this time, the space was completely uncontrollable, and a memory leak occurred. And at this time, our p is a null pointer. If the operation is performed, unknown results will be produced
. Therefore, we should also check before operating on the return pointer of realloc Whether space was allocated successfully

int main() {
    
    
	int* p = (int*)malloc(100);
	if (ptr == NULL) {
    
    
		printf("%s\n", strerror(errno));
		return 1;
	}
	int* ptmp = (int*)realloc(p, 1000);
	if (ptmp == NULL) {
    
    
		printf("%s\n", strerror(errno));
		return 1;
	}
	//如果是情况3的分配成功,但是内存开辟位置改变
	//此时p指向的还是原来的位置,是野指针,因此改变p是必要的
	//但是也要预防上面那个情况的发生
	p = ptmp;
    
    //使用,这里就忽略了
    
    //随时记得使用完要释放置空
    free(p);
    p = NULL;
	return 0;
}

reallocspecial usage

If we provide realloc with a null pointer, then in fact its effect is exactly the same as malloc, which is to open up a dynamic memory space

Implementation of dynamic address book

Then we will borrow the above knowledge and make modifications based on the last address book code.

data storage

First of all, since the memory this time is dynamically allocated, we definitely can't create an array with a size of 100 like last time.


Last timeContactStructure

#define MAX 100
typedef struct Contact 
{
    
    
	//存储有效数据个数
	int dataNum;

	//存贮数据用的数组
	Peopleinfo data[MAX];
}Contact;

So how to save? Based on the above knowledge, we know that the malloc function and the calloc function return pointers after allocating memory. Therefore, here we can put a pointer of type in the Contact structure, which will be used to point to the memory location we opened upPeopleinfo

At the same time, since we will expand the capacity later, if we write a function to determine whether the memory is full, it will obviously be troublesome and inefficient, so we create another capacity variable for to record the maximum capacity. With this, we can directly compare dataNum and capacity to determine whether to perform expansion operations. The modified code is as follows

typedef struct Contact
{
    
    
	//存储有效数据个数
	int dataNum;

	//指向存贮数据的空间
	Peopleinfo* data;

	//记录最大容量
	int capacity;
}Contact;

Address book initialization

We have made sufficient preparations in the previous step, so in the initialization step we can start allocating dynamic memory.

First of all, we must open up a space, so here we stipulate that the default size is3

and change the return value to int. If the memory allocation fails, we will directly return to the main function and end the program directly

#define DEFAULT_SZ 3

int InitContact(Contact* p) {
    
    
    //动态开辟内存
	p->data = (Peopleinfo*)malloc(DEFAULT_SZ * sizeof(Peopleinfo));
    //判断是否成功
	if (p->data == NULL) {
    
    
		printf("通讯录初始化失败:%s", strerror(errno));
		return 1;
	}
    //给dataNum初始化
	p->dataNum = 0;
    //将基础容量赋值给现最大容量
	p->capacity = DEFAULT_SZ;
	return 0;
}

Here we have not initialized the dynamically allocated memory. We also said last time that whether the memory used to store data is initialized does not affect its use. The most important thing isdataNum Must be initialized

But if you want to initialize, it is recommended to usecalloc, then you can replace the code that opens the memory with the following code

p->data = (Peopleinfo*)calloc(DEFAULT_SZ , sizeof(Peopleinfo));

Expansion

When our data increases to the maximum capacity, the maximum capacity should change, then at this time we can userealloc to reallocate memory

At the same time, we set the default number of single expansions to 2

static int IncreaseContact(Contact* p) {
    
    
    //先做暂存,防止开辟失败造成内存泄露
	Peopleinfo* ptr = (Peopleinfo*)realloc(p->data, (p->capacity + INCREACE_SZ) * sizeof(Peopleinfo));
	if (ptr == NULL) {
    
    
		printf("通讯录扩容失败:%s", strerror(errno));
		return 1;
	}
    //开辟成功后将指针赋值 最大容量计数增加
	p->capacity += INCREACE_SZ;
	p->data = ptr;
	return 0;
}

But where should this function be placed? In fact, the only thing that will increase the amount of our data is adding data. Then we can set a judgment statement in it to expand the capacity when the capacity is equal to the amount of data.

void AddContact(Contact* p) {
    
    
	//判断
	if (p->dataNum == p->capacity) {
    
    
        //如果扩容失败直接返回
		if (IncreaseContact(p)) {
    
    
			printf("数据添加失败\n");		
			return;
		}
	}
	printf("接下来请按照指示输入各项数据\n");
	printf("请输入姓名\n");
	scanf("%s", (p->data)[p->dataNum].name);
	printf("请输入性别\n");
	scanf("%s", (p->data)[p->dataNum].gender);
	printf("请输入年龄\n");
	scanf("%d", &((p->data)[p->dataNum].age));
	printf("请输入电话\n");
	scanf("%s", (p->data)[p->dataNum].tele);
	printf("请输入地址\n"); 
	scanf("%s", (p->data)[p->dataNum].addr);
	p->dataNum++;
	printf("添加成功\n\n\n");
}

memory release

When we exit the address book, we should also release the memory we dynamically allocated and initialize all data (the focus is still on releasing the memory and setting the null pointer)

void DestroyContact(Contact* p) {
    
    
	free(p->data);
	p->dataNum = 0;
	p->capacity = 0;
	p->data = NULL;
}

Summarize

This time we used the knowledge of dynamic memory management to transform the address book that could only store a fixed amount of data into an address book that dynamically opens up memory.

Then next time we will continue to make the last modification to this address book, that is, it can store data in files and inherit the data from the previous time each time.

Code overview

contact.h

#define _CRT_SECURE_NO_WARNINGS 1

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

#define MAX_NAME 20
#define MAX_GENDER 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
#define INCREACE_SZ 2
 

typedef struct Peopleinfo
{
    
    
	char name[MAX_NAME];                    
    char gender[MAX_GENDER];
	int age;
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}Peopleinfo;

typedef struct Contact
{
    
    
	//存储有效数据个数
	int dataNum;

	//指向存贮数据的空间
	Peopleinfo* data;

	//记录最大容量
	int capacity;
}Contact;

//初始化通讯录
int InitContact(Contact* p);

//添加功能
void AddContact(Contact* p);

//展示通讯录
void ShowContact(const Contact* p);

//查找功能
void SearchContact(const Contact* p);

//删除功能
void DelContact(Contact* p);

//编辑通讯录
void ModifyContact(Contact* p);

//排序通讯录
void SortContact(Contact* p);

//销毁通讯率
void DestroyContact(Contact* p);

contact.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"

int InitContact(Contact* p) {
    
    
	p->data = (Peopleinfo*)malloc(DEFAULT_SZ * sizeof(Peopleinfo));
	if (p->data == NULL) {
    
    
		printf("通讯录初始化失败:%s", strerror(errno));
		return 1;
	}
	p->dataNum = 0;
	p->capacity = DEFAULT_SZ;
	return 0;
}

static int IncreaseContact(Contact* p) {
    
    
	Peopleinfo* ptr = (Peopleinfo*)realloc(p->data, (p->capacity + INCREACE_SZ) * sizeof(Peopleinfo));
	if (ptr == NULL) {
    
    
		printf("通讯录扩容失败:%s", strerror(errno));
		return 1;
	}
	p->capacity += INCREACE_SZ;
	p->data = ptr;
	return 0;
}

void AddContact(Contact* p) {
    
    

	if (p->dataNum == p->capacity) {
    
    
		if (IncreaseContact(p)) {
    
    
			printf("数据添加失败\n");
			return;
		}
	}
	printf("接下来请按照指示输入各项数据\n");
	printf("请输入姓名\n");
	scanf("%s", (p->data)[p->dataNum].name);
	printf("请输入性别\n");
	scanf("%s", (p->data)[p->dataNum].gender);
	printf("请输入年龄\n");
	scanf("%d", &((p->data)[p->dataNum].age));
	printf("请输入电话\n");
	scanf("%s", (p->data)[p->dataNum].tele);
	printf("请输入地址\n"); 
	scanf("%s", (p->data)[p->dataNum].addr);
	p->dataNum++;
	printf("添加成功\n\n\n");
}

static void printContact(const Contact* p, int num) {
    
    
	printf("%-10s %-5s %-4d %-12s %-30s\n",
		(p->data)[num].name
		, (p->data)[num].gender
		, (p->data)[num].age
		, (p->data)[num].tele
		, (p->data)[num].addr);
}

void ShowContact(const Contact* p) {
    
    
	if (p->dataNum) {
    
    
		printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
		for (int i = 0; i < p->dataNum; i++) {
    
    
			printContact(p, i);
		}
	}
	else {
    
    
		printf("数据为空,请先存放一些数据在进行该操作\n");
	}
	printf("\n\n\n");
}

static FindByName(const Contact* p, char name[MAX_NAME]) {
    
    
	for (int i = 0; i < p->dataNum; i++) {
    
    
		if (!strcmp(name, (p->data)[i].name)) {
    
    
			return i;
		}
	}
	return -1;
}

void SearchContact(const Contact* p) {
    
    
	if (p->dataNum) {
    
    
		char name[MAX_NAME] = {
    
     0 };
		printf("请输入要查找人的名字\n");
		scanf("%s", name);
		int i = FindByName(p, name);
		if (i != -1) {
    
    
			printf("查找成功\n");
			printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
			printContact(p, i);
		}
		else {
    
    
			printf("未查询到目标\n");
		}
	}
	else {
    
    
		printf("数据为空,请先存放一些数据在进行该操作\n");
	}
	printf("\n\n\n");
}


void DelContact(Contact* p) {
    
    
	if (p->dataNum != 0) {
    
    
		char name[MAX_NAME] = {
    
     0 };
		printf("请输入要删除人的名字\n");
		scanf("%s", name);
		int pos = FindByName(p, name);
		if (pos != -1) {
    
    
			while (pos < p->capacity - 1) {
    
    
				p->data[pos] = p->data[pos + 1];
				pos++;
			}
			memset(&(p->data[pos]), 0, sizeof(p->data[pos]));
			p->dataNum--;
			printf("删除成功\n");
		}
		else {
    
    
			printf("要找的人不存在\n");
		}
	}
	else {
    
    
		printf("数据为空,请先存放一些数据在进行该操作\n");
	}
	printf("\n\n\n");
}


void ModifyContact(Contact* p) {
    
    
	if (p->dataNum) {
    
    
		char name[MAX_NAME] = {
    
     0 };
		printf("请输入要修改数据的人名\n");
		scanf("%s", name);
		int i = FindByName(p, name);
		if (i != -1) {
    
    
			printf("接下来请按照指示输入各项数据\n");
			printf("请输入姓名\n");
			scanf("%s", (p->data)[i].name);
			printf("请输入性别\n");
			scanf("%s", (p->data)[i].gender);
			printf("请输入年龄\n");
			scanf("%d", &((p->data)[i].age));
			printf("请输入电话\n");
			scanf("%s", (p->data)[i].tele);
			printf("请输入地址\n");
			scanf("%s", (p->data)[i].addr);
			printf("修改成功\n\n\n");
		}
		else {
    
    
			printf("未查询到目标\n");
		}
	}
	else {
    
    
		printf("数据为空,请先存放一些数据在进行该操作\n");
	}
	printf("\n\n\n");
}

static int SortByName(void* p1, void* p2) {
    
    
	return strcmp((char*)p1, (char*)p2);
}

void SortContact(Contact* p) {
    
    
	if (p->dataNum) {
    
    
		qsort(p->data, p->dataNum, sizeof(p->data[0]), SortByName);
		printf("排序成功\n\n");
		ShowContact(p);
	}
	else {
    
    
		printf("数据为空,请先存放一些数据在进行该操作\n");
		printf("\n\n\n");
	}
}

void DestroyContact(Contact* p) {
    
    
	free(p->data);
	p->dataNum = 0;
	p->capacity = 0;
	p->data = NULL;
}

main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"

void menu() {
    
    
	printf("********************************\n");
	printf("*****  1.ADD     2.DEL     *****\n");
	printf("*****  3.SEARCH  4.MODIFY  *****\n");
	printf("*****  5.SHOW    6.SORT    *****\n");
	printf("*****       0.EXIT         *****\n");
	printf("********************************\n");
}

enum Option
{
    
    
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

int main() {
    
    
	Contact con;
	if (InitContact(&con)) {
    
    
		return 0;
	}
	int input = 0;
	do {
    
    
	
		printf("请选择操作\n");
		menu();
		scanf("%d", &input);
		switch (input)
		{
    
    
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_42150700/article/details/129977045