一.枚举类型
1.什么是枚举
枚举就是一一列举,在生活中有些值可以一一列举,例如月份,星期
例子如下
enum Color
{
RED,
GREEN,
BLUE
};
大括号中的内容为枚举常量。
若不进行赋值,默认第一个值为0,依次向下递增1
enum Color
{
RED=5,
GREEN,
BLUE
};
此时RED GREEN BLUE 依次变成5,6,7
enum Color
{
RED,
GREEN=5,
BLUE
};
若这样写,则依次变成0,5,6
2.枚举类型的优点
枚举类型通常用来给常量赋值
a.与#define相比,enum定义的变量具有类型,且方便调试。在预处理阶段,编译器会直接将定义的替换成数字
b.enum增加了程序的可读性,例如在以往的计算器编写时,可在switch选择时给予数字以特殊的意义,使使用者更清楚计算器的功能
c.enum具有作用域,#define的类型是全局变量
二.动态内存管理
1.动态内存管理的必要性
以往我们向内存申请空间,往往直接创建一个变量。
char s[5] = { 0 };
这样创建的变量往往一旦申请,就无法改变其长度和大小。
但在实际情况中,空间申请的不合理可能会造成浪费或者短缺,而动态内存就能很好地解决这个问题。
2.malloc和free
void* malloc(size_t size);
首先是malloc函数,他的功能是向内存申请一块连续可用的空间(size的值传入申请的空间大小),并返回这个空间的起始地址。(返回void*是因为向内存申请的数据类型完全由使用者决定)
a.注意,若malloc开辟成功,则返回空间的起始地址,若开辟失败,则返回NULL,因此malloc返回值一定要做检查,具体使用如下
int main()
{
int* p = (int*)malloc(20);//开辟空间,申请一个20字节的空间
if (p == NULL)//判断malloc函数是否返回正常
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
free(p);//释放空间,p成为野指针
p = NULL;//避免p成为野指针
return 0;
}
b.当参数size为0时,其行为编译器未定义,不一定可以通过编译
c.free函数的参数是需要释放空间的起始地址,并且free无法释放非动态申请的空间
d.free函数的作用是将空间的访问权限还给操作系统,若不把释放空间后的指针置为NULL,会造成野指针的情况
下面有一种情况:
int* p = (int*)malloc(20);//开辟空间,申请一个20字节的空间
if (p == NULL)//判断malloc函数是否返回正常
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p =i+1;
p++;
}
free(p);//释放空间,p成为野指针
p = NULL;//避免p成为野指针
可以这样写吗?
答案是不行,在for循环中我们已经改变了p的位置,导致free函数执行时p已经不指向空间的起始位置
3.calloc和realloc
a.calloc函数
void* calloc (size_t num, size_t size);
其功能为,向内存申请num个每个大小为size的内存,且初始化为0
具体使用,创建五个每个大小为四个字节的空间
int* p2 = (int*)calloc(5, sizeof(int));//calloc可将申请的空间初始化为0
if (p2 == NULL)//判断calloc函数是否返回正常
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p2 + 1));
}
//释放空间,p成为野指针
free(p2);
//避免p成为野指针
p2 = NULL;
b.realloc函数
其功能为将某个已申请的空间进行调整,其参数如下:要修改的空间起始位置ptr,要修改形成的大小size
void* realloc (void* ptr, size_t size);
int* p = (int*)malloc(20);//开辟空间,申请一个20字节的空间
if (p == NULL)//判断malloc函数是否返回正常
{
perror("malloc");
return 1;
}
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
int* ptr = (int*)realloc(p, 40);//用realloc函数将原本申请的空间修改为40个字节
if (ptr == NULL)
{
perror("realloc");
return 1;
}
p = ptr;
//realloc可以实现和malloc类似的功能,只需要传入空指针即可
/*若用局部变量开辟空间,申请空间不释放,会造成内存泄漏*/
realloc函数的调整原理(有两种):
1:要将原本的20字节扩展为40字节,其后的空间恰好未分配,直接续上即可
2:之后的空间已经的分配,不足以直接续上
此时会在空间中找一份40字节的空间,将原数据拷贝到新空间,返回新空间的地址,并释放旧空间
注意:空间调整失败,realloc返回NULL
不可直接将调整后的地址直接覆盖旧地址,若realloc调整失败,旧空间也会出错,正确方法应如上代码所示
realloc若传入NULL,其功能与malloc类似
4.常见的动态内存管理的错误
a.对空指针的解引用操作
void test()
{
int* ps = (int*)malloc(INT_MAX);
*ps = 20;
free(ps);
}
这里没有检查malloc的返回值,而这里的ps为空指针,会造成程序崩溃
b.对动态开辟空间的越界访问
int* p = (int*)malloc(20);//开辟空间,申请一个20字节的空间
if (p == NULL)//判断malloc函数是否返回正常
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i <= 5; i++)
{
*(p + i) = i + 1;
}
这里i=5时会造成越界访问
c.对非动态内存的free释放
d.对动态空间的部分释放
int* p = (int*)malloc(20);//开辟空间,申请一个20字节的空间
if (p == NULL)//判断malloc函数是否返回正常
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p =i+1;
p++;
}
free(p);//释放空间,p成为野指针
p = NULL;//避免p成为野指针
这里的p已经发生位置移动,free只能从空间的起始位置开始释放
e.对同一空间多次释放
f.动态内存忘记释放,造成内存泄漏
若程序忘记回收,在结束后会由操作系统回收,这里较为危险的情况是程序一直运行的情况
谁申请,谁释放(不管是函数,还是在main函数内部)
5.动态内存管理的一些应用
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
注意在函数之间尽量使用传址调用
三.柔性数组
struct s_type
{
int i = 0;
int a[0];
};
柔性数组需建立在结构体内,且为最后一个成员,没有指定大小
1.柔性数组的特点
a.因为未指定数组大小,计算结构体大小时往往不包含数组大小
b.柔性数组往往使用动态内存管理进行分配
c.柔性数组的创建 使用 修改
struct S
{
int i ;
int a[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
return 0;
ps->i = 0;
int j = 0;
for (j = 0; j < 5; j++)
{
ps->a[j]=j;
}
struct S* ptr =(struct S*) realloc(ps, sizeof(struct S) + 10 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
}
free(ps);
ps = NULL;
}