【专题】【重点】
动态内存分配【重点 难点】
传统数组的缺点:
1. 数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a[5]; //ok
int len =5; int a[len]; //error 不能是变量
2. 传统形式的定义的数组,该数组的内存程序员无法手动释放它,是由系统自动释放的
在一个函数运行期间,系统为该函数中数组所分配的存储空间会一直存在,
直到该函数运行完毕时,数组的空间才会被系统释放。
3. 数组的长度一旦定义,其长度就不能再更改 a[5] 其中5就表示数组长度
数组的长度不能在函数运行的过程总动态的扩充或缩小
4. A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,
A函数中的数组将无法再被其他函数使用
传统方式定义的数组,不能跨函数使用
为什么需要动态分配内存
动态数组很好的解决了传统数组的四个缺陷
传统数组也叫静态数组
动态内存分配的举例_动态数组的构造
malloc函数的使用:malloc 是分配内存的意思
动态内存分配【重点 难点】
传统数组的缺点:
1. 数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a[5]; //ok
int len =5; int a[len]; //error 不能是变量
2. 传统形式的定义的数组,该数组的内存程序员无法手动释放它,是由系统自动释放的
在一个函数运行期间,系统为该函数中数组所分配的存储空间会一直存在,
直到该函数运行完毕时,数组的空间才会被系统释放。
3. 数组的长度一旦定义,其长度就不能再更改 a[5] 其中5就表示数组长度
数组的长度不能在函数运行的过程总动态的扩充或缩小
4. A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,
A函数中的数组将无法再被其他函数使用
传统方式定义的数组,不能跨函数使用
为什么需要动态分配内存
动态数组很好的解决了传统数组的四个缺陷
传统数组也叫静态数组
动态内存分配的举例_动态数组的构造
malloc函数的使用:malloc 是分配内存的意思
malloc是memory(内存) allocate(分配)的缩写
#include <stdio.h>
#include <malloc.h> //不能省
int main(void)
{
int i = 5; //静态分配了 4个字节 //5行
int *p = (int *)malloc(4); //6行
/*
1.要使用malloc函数,必须添加malloc.h的头文件
2.malloc函数只有一个形参,并且形参是整型
3.形参 (4) 表示请求系统为本程序分配4个字节
4.malloc函数只能返回第一个字节的地址
5.6行最终分配了8个字节,P变量占了4个字节,p所指向的内存也占了4个字节
6.p 本身所占 的4个内存是静态分配的,p 所指向的 4个内存是动态分配的
*/
*p = 5; //*p 代表的就是一个int变量,只不过*p这个整型变量的内存分配方式和5行的分配方式不同
free(p); //表示把p所指向的内存给释放掉,p本身的内存是静态的,不能由程序员手动释放,
//p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
printf("hello!\n");
return 0;
}
解析:
(int *)是将malloc请求的字节强制转换为整型变量,按整型变量所占字节数来划分地址。
(第一个字节地址只有值,而没有类型,只有强制转换后才会给加上所属转换的类型。)
malloc后面的括号中必须是一个值,而这个值必须为整数,无论请求的字节数是多少,比如 malloc(200)
但是最终返回的都是第一个字节的地址,如果只知道第一个字节地址,但是并不知道第一个字节地址指向的变量
最终会占用几个字节,所以需要在前面进行强制的变量类型转换,告诉程序最后按什么标准来划分地址。以便明
白第一个字节地址到底指向的是一个什么类型的变量。最终表示请求来的字节数按第一个字节地址所指向的变量
类型所占的字节数来划分,比如(int *)是4个字节的整型变量类型,那么最后就会按4个字节来进行划分。
如果按 malloc(200) 来看:
char * 占1字节 200个变量
int * 占4字节 50个变量
double * 占8字节 25个变量
int *p = (int *)malloc(4)
完了之后,请求来的4个字节的首地址就会给p 而通过强制类型转换,也知道了转换后的类型。
#include <stdio.h>
#include <malloc.h>
void f(int *q)
{
//*p = 200; //error
//q = 200; //error
//**q = 200; //error 因为*q已经是个整型变量了,所以前面不能再加*号
* q =200;
// free(q); //是把q所指向的内存释放掉,必须注释掉,否则会导致第17行代码会出错
}
int main(void)
{
int *p = (int *)malloc(sizeof(int)); //动态分配int类型的字节数4个字节,sizeof(int)的返回值是int
*p = 10;
printf("%d\n",*p); //10
f(p); //p是int *类型
printf("%d\n",*p); //200 //第17行
return 0;
}
解析:
因为f(p)调用上面的f函数,而这个括号内p存放的是动态内存分配而来的第一个字节的地址,那么将这个地址传递给q,那么
void f(int *q)中q的值就是这个地址,跟p等同,而*q=200 是给*q这个整型变量赋值200,即改变了*q 的值,也相当于改变了
*p的值为200,所以最终*p的输出值也为200
而如果free(q);生效,则因为最终q指向的是sizeof申请来的整型的4个字节,所以如果生效,则会释放掉申请来4字节的内存。
从而导致出错。
动态一维数组的构造:
-------------------------------
#include <stdio.h>
#include <malloc.h>
int main(void)
{
int a[5]; //如果int占4个字节的话,则本数组总共包含有20个字节,每个4个字节为一个元素,从a[0]开始,后面依次类推。
int len;
int * pArr;
int i;
printf("请输入你要存放的元素的个数:\n");
scanf("%d",&len);
pArr = (int *)malloc(4 * len); //动态的构造了一个数组,因为是整型指针变量,一个元素占4个字节,而len是元素个数
for (i=0;i<len;++i) //对动态一维数组进行操作
scanf("%d",&pArr[i]); //对动态一维数组进行赋值
printf("一维数组的内容是:\n");
for (i=0;i<len;++i) //对动态一维数组进行循环输出
printf("%d\n",pArr[i]);
free(pArr); //释放掉pArr所指向的动态数组分配的内存
return 0;
}
解析:
动态数组在使用上是跟静态数组的使用方式是一样的,只是构造的时候不一样而已。
因为pArr是整型指针变量占4个字节,所以动态内存分配后,指向的是内存分配的20个字节中的前4个字节,如果pArr+1 则表示
指向的是第二个4个字节(即第5个字节到第8个字节),而不是在前面4字节的基础上+1成为5个字节。因为每4个字节为一部分。
由几个字节为一部分,需要由pArr的指针变量类型来决定。
pArr = (int *)malloc(4 * len);等同于int pArr[0]、int pArr[1]、int pArr[2]、int pArr[3]、int pArr[4]
即int pArr[5]
动态的构造了一个一维数组,该数组的数组名是pArr,数组长度是len,单个元素如上所示。
上面静态的a[5]有静态的20字节的内存
下面pArr动态申请的有20个字节的内存
由上可知:
a[0]是第一个元素,占用了20个字节的前4个字节
那么:pArr[0]同样也是占用了动态分配的20字节的前4个字节
又pArr[0]等同于 *(pArr+0)
所以例如:a[2]就等同于pArr[2]
那么如何动态的增加数组的长度呢:
可以使用realloc函数:realloc(aArr,100) 括号里面,前面表示数组名,后面表示要扩充的字节数容量上限。
如果pArr当初指向的是50个字节的内存,那么运行的时候就会扩充到100.前50个字节的数据会保留。
如果pArr当初指向的是150个字节的内存,那么运行的时候就缩小到100,后50个字节的数据会丢失。
#include <stdio.h>
#include <malloc.h>
void f(int *q)
{
*q = 10;
}
//void g(int **p)
//{
//}
int main(void)
{
int *p = (int *)malloc(4); //动态请求4字节 p指向的是(int*)
printf("*p = %d\n",*p); //输出的是垃圾数字
f(p);
//g(&p); //p是int*,&p是int**
printf("*p = %d\n",*p);
return 0;
}
程序运行的结果为:*p = 10
---------------------------
#include <stdio.h>
#include <malloc.h>
void f(int **q)
{
**q = **q + 30;
}
int main(void)
{
int i;
int *p;
printf("请输入一个数字:\n");
scanf("%d",&i);
p = (int *)malloc(4); //动态请求4个字节的内存 //第13行
p = &i;
f(&p);
printf("i = %d\n",i);
// free(p); error,此处不能添加,否则会有内存报错。
return 0;
}
程序运行的结果为:i = 输入的任何数+30后的结果
如果将第13行取消,程序依然可以运行,只是却变成了由系统自行分配的静态内存。
--------------------------------------------------------------------------
静态内存和动态内存的区别比较:
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈中分配的
动态内存是由程序员手动分配,手动释放,如果忘了释放,就会出现内存泄漏,内存越用越少,最后就会死机。
动态内存是在堆分配的 堆就是堆排序
-----------------------------------------------
跨函数使用内存的问题:
静态变量不能跨函数使用内存:
#include <stdio.h>
void f(int **q)
{
int i = 5;
//**q等价于*p *q等价于p q和**q都不等价于p
//*q = i; error,因为*q = i; 等价于 p = i 这样写是错误的,而p中存放的是地址不是变量
*q = &i; // p = &i
}
int main(void)
{
int *p;
f(&p); //第12行
printf("%d\n",*p); //本句语法没有问题,但逻辑上有问题。
//不能读取不属于自己的内存,在规则上讲 //第13行
return 0;
}
程序的运行结果为:5 .
解析:
此程序在语法上是没有问题的,但是在现实中逻辑上是有问题的,不能这样去写程序。
程序在运行到第12行的时候,实际上f函数的静态内存已经由系统自动释放掉了,
所以这个时候第13行的p指向的i变量就不存在了,换句话说就是无法访问了。
由此可见静态变量当被终止的时候就不能够被其他函数使用了。
----------------------------------------------------------------------------------
动态内存可以跨函数使用:
#include <stdio.h>
#include <malloc.h>
void f(int **q)
{
*q = (int *)malloc(sizeof(int)); //sizeof(数据类型) 返回值是该数据类型所占的字节数
// 等价于 p = (int *)malloc(sizeof(int));
// q = 5; error
// *q = 5; //p=5; error
**q = 5; //*p = 5;
}
int main(void)
{
int *p;
f(&p);
printf("%d\n",*p); // 第15行
return 0;
}
运行结果为:5
解析:
因为是动态分配的内存 而p指向的是malloc(sizeof(int))中的int的4个字节的内存,而最终此程序并没有手动释放内存的
free(q),且动态内存是在堆里面进行分配的不存在出栈,所以在最后第15行,仍然可以访问之前动态分配的内存。
----------------------------------------------------------------------------------------------------------------