动态分配,结构,联合
Posted peiyao456
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态分配,结构,联合相关的知识,希望对你有一定的参考价值。
(一)动态内存分配:
1.为什么要动态内存分配呢?比如,我要做一个学生成绩管理系统,这里可能需要存储每个班级所有学生的信息,但
是,我们到底要分配多大的空间呢??每个班的人数有可能并不相等,按多分配 ,那样多浪费;按少分配,不够。所以
动态内存分配就有自己的作用了~~
2.动态内存分配函数:
(1)void *malloc(unsigned int size);-------size是需要分配的字节数。
(2)void *calloc(unsigned int num_elements,unsigned int elements_size);----num_elements是分配的元素个
数,elements_size是每个元素占的字节数。
(3)void *realloc(void *p,unsigned int new_size);----new_size是修改后的字节数,p是原先的内存首地址。
由于这些函数都是在堆(heap)里开辟的空间,使用完后需要释放~所以free就来了~~
(4)void free(void *p);-----p是需要释放的空间的首地址。
乍一看,malloc和calloc好像是一样的,其实并不是。calloc在返回指向内存的指针之前就将这块内存初始化为0.所以
我们可以认为calloc的工作相当于malloc加memset。所以calloc在分配过程中可能就要慢一点~~
前边3个函数都需要free,如果不free就可能造成内存泄漏~关于这个问题在这篇文章点击打开链接中有详细解释~
free释放完成后需要置为NULL。我们在使用指针之前必须要有一个原则----那就是使用之前先判断是否为空、使用之
后需要置为NULL。看下边一段代码:
void Test()
{
char *p = (char *)malloc(20*sizeof(char));
if (p == NULL)
{
printf("out of memory");
exit(1);
}
strcpy(p,"hello");
printf(p);
free(p);
if (p != NULL)
{
strcpy(p,"yaoyao");
printf(p);
}
//free(p);
}
程序究竟会输出什么呢??没错,是helloyaoyao,内存已经释放,为什么还能拷贝呢??是因为释放后并没有置为
NULL。虽然p已经跟那块内存断了关系,可以p里边还存着人家的地址,所以~~~
realloc函数到底是用来干什么的?分配内存,对,还可以扩容,缩容(下边的提到的扩容就是指扩容或者缩容)。
下边我来详细剖析这个函数:
函数原型是void *realloc(void *p,unsigned int new_size);当p是NULL时,函数的作用是开辟空间,当p不为NULL,
函数的作用就是扩容。结合下边的代码,我来详细分析:
void Test()
{
char *p = (char *)malloc(10 * sizeof(char));
char *preal = NULL;
if (p == NULL)
{
printf("out of memory");
exit(1);
}//如果此时我发现10个空间根本不够,所以需要扩容
preal = (char *)realloc((void *)p,12);
if (preal != NULL)
{
p = preal;
}
else
printf("out of memory");
free(p);
}
这段程序,乍一看,貌似preal这个变量是不需要的,其实是完全需要。当我们扩容时,有时会扩容不成功(缩容不存
在这个问题),此时realloc就会返回NULL,如果我们直接将realloc函数的返回值赋值给p,此时p就变成了NULL,之
前的数据也就找不到了(这样划得来吗??)如果分配成功,新的地址赋给p,分配不成功,就直接报错离开,最后
直接释放p就可以了。如果你仔细看,你会发现realloc的参数1我强制类型转换了,其实不转化在我的编译器下也是可
以的。在你的编译器下我就不知道了~~~
上边程序中多次用到exit(),下边我来给出exit函数的整理:
exit()函数:
所在头文件:stdlib.h
功能:关闭所有文件,终止正在执行的进程。
exit(1)表示异常退出,这个1是返回给操作系统的。
exit(x)(x != 0)都表示异常退出。
exit(0)表示正常退出。
exit的参数会传给操作系统的。
exit和return的区别:
按照ANSI C,在最初调用的main()中使用return和exit的效果一样。
如果main函数在一个递归程序中,使用exit仍然会终止程序,但return会将控制权移交给递归的前一级,此时return才
会终止程序。
另一个区别:即使在除main函数之外的函数中调用exit,它也将终止程序。
_exit和exit的区别:
_exit所在头文件:unistd.h
作用:直接使进程停止运行,清除其所用的内存空间,并销毁其在内核中的各种数据结构。
exit:作用 在基础上做了一些包装,在执行退出前做了若干道工序。
最大的区别:exie函数在调用exit系统调用之前要检查文件的的打开情况,把文件缓冲区的内容写回文件。
3.常见的内存分配错误:对NULL 指针进行解引用(直接原因就是没有检测是否分配成功),对分配的内存进行操作时
越过边界,释放并非动态分配的内存,释放动态分配内存的一部分,内存释放后继续使用。
这几个错误都比较好理解,这里就不给出例子了。但是,我还是想给出防止第一个错误发生的办法~~
下边给出一种方法:
#define malloc
#define MALLOC(num,type) (type *)alloc((num )*sizeof(type))
extern void *alloc(unsigned int size)
MALLOC宏接收元素的数目以及元素的类型,计算总共需要的字节数,并调用alloc获得内存,alloc调用malloc并进行
检查,确保返回的指针不是NULL。
4.其实动态内存分配并不是你想的那样,当你申请空间时,系统其实在这块空间之前会预留一块空间,用来存放这个
内存的大小吧。(或者是所放元素的个数)
给一个3行4列的数组动态分配空间:
错误方法1:
int **a = (int **)malloc(3*4*sizeof(int));
这是自己骗自己的方法。二维数组不等同于二级指针,之前的文章中有重点提到过。
错误方法2:
void test()
{
int i = 0;
int *p = NULL;
for (i = 0;i < 3;i++)
{
p = (int *)malloc(4*sizeof(int));
}
//释放省略
}
这个会存在被覆盖的问题。
下边给出正确的方法:
1.
void test()
{
int i = 0;
int *p[3] = { NULL };
for (i = 0;i < 3;i++)
{
p[i] = (int *)malloc(4*sizeof(int));
}
for (i = 0;i < 3;i++)
{
free(p[i]);
}
}
2.
void test()
{
int(*p)[4] = (int(*)[4])malloc(4*3*sizeof(int));
free(p);
}
上边的代码没有实现任何功能,只是给出了申请空间~~
(二)结构体:
1.定义:
struct Stu
{
int age;
char name[12];
}student;
以上给出了一各简单结构体的定义,struct Stu 是类型名,类比于int,double之类的,student就相当于一个结构体变
量,记住,类型名是struct Stu ,不是Stu ,Stu 是标签,所以有时写起来比较麻烦,我们可以用typedef来解决。
typedef struct Stu
{
int age;
char name[12];
}stu;
之后你如果需要定义这个结构体类型的变量直接用stu这个类型名。
typedef struct Stu
{
int age;
char name[12];
struct Stu *next;
}stu;
如果在结构体内部要定义在结构体指针,不能用”新名字“,原因就是新名字还没有出现~~~
<pre name="code" class="cpp">
struct
{
int age;
char name[12];
}s1,s2;
这是一个没有标签的结构体的定义,定义了两个结构体变量,然而你,之后就不能定义别的变量~~因为没有变量名~
先有鸡还是先有蛋???结构体里的鸡与蛋问题:
struct B;
struct A
{
int age;
char name[12];
struct B b;
};
struct B
{
int age;
char name[12];
struct A a;
};
B里有A的变量,A里有B的变量,所以只能牺牲一个~~
2.访问:有两种方式,直接访问和间接访问。
比如:
struct Stu
{
int age;
char name[12];
}s,*ps;
s是结构体变量,ps是指向结构体的指针,如果要访问结构体的age成员,我们可以这样访问:
s.age
ps->age
(*ps).age
第一种是直接引用,后边两种是间接引用~~
3.当结构体作为参数需要传递给另一个函数时,可以传结构体,也可以传结构体的地址,不过我们一般传地址呀,因
为地址只占4字节(32位系统下),节省空间,但是传地址过去就比较自由了,函数里想改就改,如果不希望被修
改,那你就用const修饰吧~~
4.位段:位段的声明和结构体类似,但它的成员是1个或多个位段,这些不同长度放的字段实际上存储在一个或多个
整形变量中。位段的成员必须声明为int、unsigned int、signed int;在成员的后边是一个冒号和一个整数,这个整数
指定该位段所占的位的数目。
struct A
{
int a:10;
int b:2;
int c:15;
};
sizeof(struct A) = 4;
以上是关于动态分配,结构,联合的主要内容,如果未能解决你的问题,请参考以下文章