动态分配,结构,联合

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;在成员的后边是一个冒号和一个整数,这个整数

指定该位段所占的位的数目。