目录
1. 为什么存在动态内存分配
2. 动态内存函数的介绍
2.1.malloc函数和free函数
2.2.calloc函数
2.3.realloc函数
3.常见的动态内存错误
3.1.对NULL指针的解引用操作
3.2.对动态开辟空间的越界访问
3.3.对非动态开辟内存使用free释放
3.4.使用free释放一块动态开辟内存的一部分
3.5.对同一块动态内存多次释放
3.6.动态开辟内存忘记释放(内存泄漏)
4.几个经典的笔试题
5. C/C++程序的内存开辟
6.柔性数组
6.1.柔性数组的基本概念
6.2.柔性数组的使用与特点
6.3.柔性数组的优势
6.4.柔性数组的拓展阅读
7.通讯录(改进版)(动态内存增长版本)
我们已经掌握的内存开辟方式有:
int val = 20 ; // 在栈空间上开辟四个字节char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间但是上述的开辟空间的方式有两个特点:1. 空间开辟大小是固定的。2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态内存开辟了
我们知道,内存分为:栈区、堆区、静态区。栈区用来存局部变量和形式参数;静态区用来存静态变量和全局变量;堆区则用来动态内存分配,其涉及的函数为malloc、calloc、realloc、free。如下图所示:(栈区里的变量系统自动回收;堆区中的变量需要使用free函数手动回收,否则程序运行完系统回收;静态区中的变量程序运行完系统回收)
参数:
size_t size:需要开辟空间的大小,单位是字节
返回:
void*:如果开辟空间成功,返回一个指向这个空间的指针;如果开辟空间失败(没有足够的空间开辟),返回空指针
功能:
申请一个内存空间(以字节为单位),返回一个指针,指向这个空间的起始位置
注:
1.使用malloc需要包含stdlib.h头文件
2.语法上malloc可以申请0字节空间,但是没有意义
参数:
void* ptr:ptr指针指向的的空间为malloc、calloc、realloc函数开辟的空间
返回:
void:无返回
功能:
释放所开辟的空间(参数中的指针指向的地址不变,仅释放空间)
注:
1.使用free函数释放空间之后,参数中的指针就变成了野指针,一般使用free函数释放后将参数中指针置为空指针
2.申请了内存空间使用完后就要释放,如果不释放只有等程序运行完该申请空间才会释放,如果程序运行时停在某一步(比如getchar等待输入或死循环),该申请的内存空间就会一直被占着,造成内存泄漏的问题
代码1:
#include
#include
#include
#include
int main()
{
//开辟10个整型的空间
int* p = (int*)malloc(4000000000);
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果1:
代码2:
#include
#include
#include
#include
int main()
{
//开辟10个整型的空间
int* p = (int*)malloc(40);
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果2:
参数:
size_t num:元素的个数
size_t size:每一个元素的大小
返回:
void*:如果开辟空间成功,返回一个指向这个空间的指针;如果开辟空间失败(没有足够的空间开辟),返回空指针
功能:
申请一个内存空间(以字节为单位),返回一个指针,指向这个空间的起始位置,并且将开辟的空间数据全部初始化为0
注:
1.使用calloc需要包含stdlib.h头文件
2.calloc会将开辟好的空间数据初始化为全0
代码:
#include
#include
#include
#include
int main()
{
//开辟10个整型的空间
int* p = (int*)calloc(10, sizeof(int));
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果:
参数:
void* ptr:ptr指针为指向malloc、calloc、realloc函数开辟空间的起始地址,当给ptr传一个空指针,其功能与malloc相同
size_t size:重新指定的空间大小(单位为字节)
返回:
void*:如果调整空间成功,返回调整后之后内存空间的起始地址;如果调整空间失败,返回空指针
情况1:若调整前申请空间后面有空余空间,则会在后面补足,直到补满要调整的空间数,返回空间首地址(与调整前申请的空间首地址相同)
情况2:若调整前申请空间后面没有足够的空余空间,则会重新找有足够空间的内存块,将调整前申请空间复制过来(里面数据完全相同),再在后面补足,直到补满要调整的空间数,返回重新找到的空间首地址,并且最后会自动释放调整前的内存空间(与调整前申请的空间首地址不同)
功能:
可以开辟空间(第一个参数为空指针时),也可以调整空间(第一个参数不为空指针时)
注:
1.使用realloc需要包含stdlib.h头文件
2.代码:p = (int*)realloc(p, 80),不要这么去增容,因为如果增容失败返回一个空指针,这样p接收到的一个空指针,不仅无法增容,连增容前的空间也无法找到了,正确使用形式如下面代码所示
代码1:
#include
#include
#include
#include
int main()
{
//开辟10个整型的空间
int* p = (int*)calloc(10, sizeof(int));
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//需要增容 - 80
int*ptr = (int*)realloc(p, 80);
if (NULL != ptr)
{
p = ptr;
ptr = NULL;
}
//继续使用了
//释放
free(p);
p = NULL;
return 0;
}
运行调试结果1:(调整前后申请内存空间首地址相同的情况)
运行调试结果2:(调整前后申请内存空间首地址不同的情况)
代码2:(该代码realloc与malloc函数功能完全相同)
#include
#include
#include
#include
int main()
{
//开辟10个整型的空间
int* p = (int*)realloc(NULL,40);
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果2:
对NULL指针的解引用操作代码:
#include
#include
int main()
{
int* p = (int*)malloc(INT_MAX);
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
return 0;
}
运行调试代码:
注:
1.INT_MAX为整型的最大值,具体数值转定义如下图所示
2.由于INT_MAX过大,系统无法帮我们开辟这么大的空间,因此返回一个空指针,对空指针加上一个偏移量进行解引用依然是无法正常访问的,强行运行系统会崩溃
3.对该代码进行修改,应该对接收的指针p进行判断是否是空指针,如果是空指针就return 0不要往后走了,如下代码所示
对动态开辟空间的越界访问代码:
#include
#include
#include
#include
#include
int main()
{
char* p = (char*)malloc(10 * sizeof(char));
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p + i) = 'a'+i;
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果:(结果出错)
注:
1.malloc函数申请了10个字节的内存,访问修改时要修改第11个字节的内存空间,越界访问,会报如上错误
对非动态开辟内存使用free释放代码:
int main()
{
int a = 10;
int*p = &a;
//写代码
free(p);
p = NULL;
return 0;
}
运行结果:(结果出错)
注:
1.局部变量a是分配在栈上的,而不是动态开辟出来的,栈上的空间是由系统自动释放,不能free手动释放
使用free释放一块动态开辟内存的一部分代码:
#include
#include
#include
#include
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用内存
int i = 0;
//1~5
for (i = 0; i < 5; i++)
{
*p = i+1;
p++;
}
//释放
free(p);
p = NULL;
return 0;
}
运行结果:(结果出错)
注:
1.动态开辟的内存空间进行释放是无法释放其中一部分的,强行释放系统报错,如上图所示,只能使用realloc函数对其进行空间调整
2.一定要有指针变量记住开辟空间的起始位置,否则无法释放,存在内存泄漏的风险
对同一块动态内存多次释放代码:
#include
#include
#include
#include
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//使用内存
int i = 0;
//1~5
for (i = 0; i < 5; i++)
{
*(p+i) = i + 1;
}
//释放
free(p);
free(p);
return 0;
}
运行结果:(结果出错)
注:
1.对已经释放的内存块进行二次释放是错误的,如上图所示。如果我们释放完空间后对存储空间首地址的指针变量置为空指针的话,再进行释放是没有问题的,如下图所示,也就是free空指针是可以的,如果free空指针那么这一次free什么也不会发生,如下图所示
2.我们每次释放完内存空间后,要对存储内存首地址的指针变量置为空指针
动态开辟内存忘记释放(内存泄漏)代码:
#include
#include
#include
#include
#include
void test()
{
int* p=(int*)malloc(100);
if(p==NULL)
{
return 0;
}
}
int main()
{
test();
return 0;
}
注:
1.动态开辟的空间一定要释放,并且正确释放 ,如果忘记释放不再使用的动态开辟的空间,就会出现内存泄漏问题。
2.该代码不能在主函数里释放函数中申请的内存空间,因为出了函数,存储内存空间首地址的指针变量已经被释放了。要么在函数中返回存储内存空间首地址的指针变量,在主函数中接收然后进行释放,要么就在函数中直接进行释放,如下图所示总之一定要有效释放
题目一:
下面代码运行会有什么结果
代码:
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
运行结果:(程序崩溃,什么输出也没有)
注:
1.GetMemory函数中的指针变量p是Test函数中指针变量str的一份临时拷贝,因为str里面存的是空指针,所以p也拷贝空指针。随后p = (char*)malloc(100),系统在堆区申请100字节空间,将空间首地址赋值给p,然而GetMemory函数运行完后,指针变量p所占内存被释放,但malloc开辟的100字节空间没有被释放,并且没有指针变量记住空间首地址无法再找到这100字节的空间,造成内存泄漏
2.strcpy(str, "hello world"):指针变量p的改变不会影响指针变量str的值,str里面存的仍是空指针,将"hello world"字符串拷贝到空指针处,此时strcpy函数第二个参数为整个字符串,传参其实是传字符串首字符的地址(与char* p="hello world"用法相似),因为空指针不能被访问,此时非法访问内存,程序会崩溃
3.printf(str):这个代码是正确的。我们先看printf("hello world\n")代码,该代码其实是先将字符串"hello world\n"放在只读数据区,将字符串"hello world\n"的首元素地址传给printf函数,printf函数进行字符串打印。与该代码类似,本代码意图(实现是错误的)是经过拷贝指针变量str内存储的是字符串"helloworld"首元素地址,printf(str)进行打印,没有问题,只不过这里是拷贝失败无法进行打印
4.修改该代码,实现其想要功能如下图所示(修改:1.传址调用或传值调用加上返回值 2.用完申请的空间后要及时释放)
题目二:
下面代码运行会有什么结果
代码:
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
运行结果:
注:
1.Test函数中指针变量str能够正常接收到GetMemory函数返回的字符串"hello word"的首元素地址,但是调用完GetMemory函数,里面的局部变量数组p所占空间就会被回收,此时该空间已经没有使用权了,但是该空间地址返回给指针变量str,也就是记住了该空间的地址,str就变成了野指针,因为该空间已经没有使用权了,空间内数据可能已经被改变了,所以打印出来的结果与预期不符,如上图所示
2.该代码是返回栈空间地址的问题,因此写代码时不要返回栈空间的地址,因为栈空间系统会自动回收,返回地址没有意义,接收的指针也会变成野指针(用static进行修饰,将存储在栈空间的数据存在静态区内,再返回该空间地址是可行的,因为出了函数该空间数据不会被释放)
3.类似的,下面第一个代码是正确的(返回的是栈空间里的变量),第二个代码是错误的(返回了栈空间地址),在验证时第二个代码打印出来的可能还是10,这是因为此处栈区里面的数据没有被修改,如果在打印*p之前进行一次函数调用,此处栈区的数据就会被修改,再进行打印就不再是10,如第三个图所示。
4.每一次函数调用都会在栈中申请空间,先申请main函数空间,然后压栈创建test函数空间,main函数空间中指针变量p存储了test函数中变量a的地址,如下图所示。test函数执行完,空间被释放后,如果没有其他函数创建,变量a地址处数据没有被修改,对p解引用进行打印有可能还为10(如图2所示)。当调用printf("hehe\n")时,压栈在main函数上面申请一块空间给printf,此时printf会对a地址处的空间数据进行修改,再通过对p解引用得到的数就不再为10了
题目三:
下面代码运行会有什么结果
代码:
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
运行结果:
注:
1.该代码唯一的问题就是忘记释放在堆区申请的空间,造成了内存泄漏,修改后代码如下图所示
题目四:
下面代码运行会有什么结果
代码:
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
运行结果:(运行结果没有问题,但是会非法访问内存)
注:
1.该代码申请了100字节的空间将该空间地址赋值给指针变量str,将字符串"hello"拷贝在这块空间中,然后将这块空间进行释放,此时str不为空指针,将字符串"world"拷贝给这块空间,因为之前str指向的空间已经释放,不能再次使用,非法访问内存。
2.因此在释放完申请的内存空间后,应该及时对相应的指针变量置为空指针,并且申请完空间后应该判断相应指针变量是否为空指针,如下图所示
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时由OS(操作系统)回收 。分配方式类似于链表。3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段(只读数据区):存放函数体(类成员函数和全局函数)的二进制代码。
注:
1.上图中的数据段就是我们前面说的静态区
上图中的代码段就是我们前面说的只读数据区
2.全局变量和static修饰的局部变量都会放在静态区(上图中的数据段)
局部变量放在栈区中
初始化数组或指针变量时,字符串是存放在只读数据区(代码段)中的
malloc等函数开辟的动态内存空间是放在堆区的
3.实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁所以生命周期变长。
C99 中,结构中的最后一个元素成员允许是未知大小的数组,这就叫做『柔性数组』成员
代码1:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
代码2:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
注:
1.代码1中的 int a[0]和代码2中的 int a[ ]都是创建柔性数组,有些编译器代码1的创建可能会报错,那就使用代码2的创建方法
柔性数组的特点:结构中的柔性数组成员前面必须至少一个其他成员,保证结构体大小不是0,可以为该结构体开辟空间sizeof 返回的这种结构大小不包括柔性数组的内存(如下面代码1所示 )包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
代码1:
struct S1
{
int n;
int arr[0];
};
typedef struct st_type
{
int i;
int a[0];
}type_a;
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(type_a));
return 0;
}
运行结果1:
代码2:
struct S2
{
int n;
int arr[];//
};
int main()
{
struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);
ps->n = 100;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
//增容
struct S2* ptr = (struct S2*)realloc(ps, sizeof(struct S2)+80);
if (ptr == NULL)
{
return 0;
}
else
{
ps = ptr;
}
for (i = 10; i < 20; i++)
{
ps->arr[i] = i;
}
//释放
free(ps);
ps = NULL;
return 0;
}
运行调试结果2:
注:
1.malloc(sizeof(struct S2) + 40):sizeof(struct S2)计算出来的大小是给第一个结构体成员n,后面40是再开辟40个字节的空间给柔性数组arr的。如下图所示。此时去访问,当访问n访问的是前4个字节,访问arr访问的是后40个字节。因为整块空间是动态开辟出来的,想让柔性数组增大或减少可用realloc函数进行调整即可
下面的代码可以模拟柔性数组的功能,那么柔性数组与下面代码相比哪个更好呢
模拟柔性数组代码:
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
ps->n = 100;
ps->arr = (int*)malloc(40);
//使用
//增容
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
模拟柔性数组代码的关系图:
柔性数组和上面模拟柔性数组代码作比较,柔性数组更好一些
第一个好处:柔性数组只需要一次malloc申请空间一次free释放空间,模拟柔性数组代码需要申请、释放各两次,申请和释放空间次数过多容易出错
第二个好处:模拟柔性数组代码由于多次申请内存空间容易形成内存碎片,柔性数组是连续的内存,相比于模拟柔性数组代码少申请一次
第三个好处: 柔性数组是连续的内存,连续的内存有益于提高访问速度
C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell
通讯录:
1.可以存放1000个人的信息
2.人的信息:名字、年龄、性别、电话、住址
功能:
1.增加联系人(完成)
2.删除联系人(完成)
3.查找联系人(先不完成)
4.修改联系人(先不完成)
5.排序(名字/年龄)(先不完成)
6.展示所有人信息(完成)
0.退出(完成)
test.c代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
enum Oprion
{
EXIT,//0
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
SHOW
};
void menu()
{
printf("***************************************\n");
printf("******** 1.add 2.del *****\n");
printf("******** 3.search 4.modify *****\n");
printf("******** 5.sort 6.show *****\n");
printf("******** 0.exit *****\n");
printf("***************************************\n");
}
int main()
{
int input = 0;
Contact con = { 0 };//通讯录
//初始化通讯录
InitContact(&con);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&con);
break;
case DEL:
DeleteContact(&con);
break;
case SEARCH:
break;
case MODIFY:
break;
case SORT:
break;
case SHOW:
ShowContact(&con);
break;
case EXIT:
DestroyContact(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
contact.h代码:
#pragma once
#include
#include
#include
#include
#include
#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 30
#define TELE_MAX 12
#define DEFAULT_SZ 3
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char addr[ADDR_MAX];
char tele[TELE_MAX];
}PeoInfo;
//通讯录的结构体
typedef struct Contact
{
//PeoInfo data[MAX];
PeoInfo* data;//存放数据
int sz;//通讯录中有效信息的个数
int capacity;//记录当前通讯录的最大容量
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//销毁通讯录
void DestroyContact(Contact* pc);
//增加联系人到通讯录
void AddContact(Contact* pc);
//打印通讯录中的信息
void ShowContact(const Contact* pc);
void DeleteContact(Contact* pc);
contact.c代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
PeoInfo* tmp = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (tmp != NULL)
{
pc->data = tmp;
}
else
{
printf("InitContact()::%s\n", strerror(errno));
return;
}
pc->capacity = DEFAULT_SZ;
}
void DestroyContact(Contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
void check_capacity(Contact* pc)
{
assert(pc);
if (pc->sz == pc->capacity)
{
//增加容量
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (tmp != NULL)
{
pc->data = tmp;
pc->capacity += 2;
printf("增容成功\n");
}
else
{
printf("check_capacity()::%s\n", strerror(errno));
}
}
}
void AddContact(Contact* pc)
{
assert(pc);
check_capacity(pc);
//输入联系人
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("增加联系人成功\n");
}
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-5s\t%-5s\t%-13s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-5d\t%-5s\t%-13s\t%-20s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
}
int FindByName(const Contact* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;//找不到
}
void DeleteContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空,无法删除\n");
return;
}
printf("请输入要删除人的名字:>");
scanf("%s", name);
//查找指定联系人
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
else
{
//删除
int j = 0;
for (j = pos; j < pc->sz - 1; j++)
{
pc->data[j] = pc->data[j + 1];
}
pc->sz--;
printf("删除指定联系人成功\n");
}
}