【零基础学C语言】知识总结十一:动态内存分配!

动态内存分配(动态存储期)

在程序执行并使用该变量的时候分配内存空间,使用完毕立即释放.

动态内存分配就 是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据 程序的需要即时分配,且分配的大小就是程序要求的大小。

当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。

在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道我们要定义的这个数组到底有多大,那么你就要把数组定义得足够大。这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。

即使你知道你想利用的空间大小,但是如果因为某种特殊原因空间利用的大小有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。

我们用动态内存分配就可以解决上面的问题. 所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。

从以上动、静态内存分配比较可以知道动态内存分配相对于静态内存分配的特点:

1、不需要预先分配存储空间;

2、分配的空间可以根据程序的需要扩大或缩小。

常见的动态内存错误:

(1)对NULL指针进行解引用操作

(2)对分配的内存进行操作时越过边界

(3)释放并非动态分配的内存

(4)试图释放一块动态分配的内存的一部分以及一块内存被释放之后被继续使用。

说明:

1、动态分配最常见的错误就是忘记检查所请求的内存是否成功分配。

2、动态内存分配的第二大错误来源是操作内存时超出了分配内存的边界。

当你使用free时,可能出现各种不同的错误:

1、传递给free的指针必须是一个从malloc、calloc或realloc函数返回的指针。

2、传递给free函数一个指针,让它释放一块并非动态分配的内存可能导致程序立即终止或在晚些时候终止。

3、试图释放一块动态分配内存的一部分也有可能引起类似问题。

//实例:动态内存分配实现可变长一维数组

#define  _GRT_SECURE_NO_WARNNGS
#include<stdio.h>
#include<stdlib.h>
#include"array.h"//这个头文件   里边包含一个结构表示数组和下列函数的声明原型

const Block_size = 20;///一次增容20个存储空间

/*
Array array_creat(int ints_size); //创建一个数组
void array_free(Array  *a);//回收空间
int array_size(const Array *a);//查看当前数组大小
int *array_at(Array *a, int index);//访问数组
void array_inlate(Array *a, int more_size);//增容
*/

int main(void) {

	Array a;//表示数组初始值的大小
	int i, j,n,m=0;

	while (1) {
		printf("请输入你需要多大的数组:\n");
		scanf("%d", &n);
		a = array_creat(n);//这个可得到a里边返回的参数
			
		printf("输入数据 \n");

	
		for (i = 0; i < n; i++) {

			scanf("%d", &j);
			*array_at(&a, i) = j;//这个函数相当与是数组   把j的值保存到数组里边的元素中去
		}

		printf("输出数据:\n");
		for (i = 0; i < n; i++) {//遍历输出
			printf("%d ", a.arrray[i]);
			printf("\n");
		}
		printf("\n");
		printf("输入1初始化数组大小,输入其他表示退出程序:\n");
		scanf("%d", &n);

		if (n == 1) {
			m = 0;//清零
			j = 0;
			array_free(&a);//释放之前的内存
		}

		else {
			exit(0);//退出程序
		}

	}

	return 0;
}


Array array_creat(int ints_size) //创建一个数组
{
	Array a;//定义一个数组的结构体
	a.size=ints_size; //表示数组的长度

	a.arrray = (int *)malloc(sizeof(int)*a.size);//前一个int*是强制类型转换,后面的表示一个int 是4个字节 总共就是长度乘以

	return a;//返回的作用是 让主函数调用它时,能够得到它的参数
}

void array_free(Array  *a)//回收空间
{
	free(a->arrray);
	a->arrray = NULL;//让指针指空  不至于成为野指针
	a->size = 0;

}

//封装
int array_size(const Array *a)//查看当前数组大小
{
	return a->size;
}


int *array_at(Array *a, int index)//访问数组
{
	if (index >= a->size) {
		//下面的公式是为了算出Block_size的底在哪
		//比如130,如果直接加20要加两次,但是用公式就一次完成
		array_inlate(a, (index / Block_size + 1)*Block_size - a->size);//在原来的基础上加20个
	}

	//返回指针   加括号是为了保持优先级不出错
	return &(a->arrray[index]);	//如果返回的是值,那将不能被改变,返回指针就可以进行操作了
}

void array_inlate(Array *a, int more_size)//增容
{
	int *p = (int*)malloc(sizeof(int)*(a->size+more_size));//重新申请一块更大的内存  100 +20
	int i;

	for (i = 0; i < a->size; i++) {//把之前数组的内容拷贝到新的数组中去
		p[i] = a->arrray[i];
	}


	free(a->arrray);//把之前的数组释放
	a->arrray = p;//将指针改变指向  重定向
	a->size += more_size;//大小加上新增的
}

/*程序演示:

请输入你需要多大的数组:
5
输入数据
1 2 3 4 5
输出数据:
1
2
3
4
5

输入1初始化数组大小,输入其他表述退出程序:
1
请输入你需要多大的数组:
6
输入数据
1 2 3 4 5 6
输出数据:
1
2
3
4
5
6

输入1初始化数组大小,输入其他表述退出程序:
0

进程1520已退出.返回代码为 0.
按任意键关闭此窗口...
//实例:动态内存分配实现可变长二维数组

#include<stdio.h>
#include<malloc.h>

int main(void)
{

	int n, m;
	scanf("%d %d", &n, &m);//n=5  m=2  按照自己输入 来确定二维数组的大小

	int **p = (int **)malloc(sizeof(int *) * n);//利用二级指针  申请五行元素
			
	//p是一个二级指针  malloc函数返回一个int* 的类型  sizeof(int*)表示乘以的指针类型的大小

		/*、申请m个能够能够存放 int* 类型的空间,并将首地址返回给一个二维指针p;
			内存可能的分布情况:

		int a < -- int *; < -- int **p;
		int b < -- int *;
		int c < -- int *;
		int d < -- int *;

		*/

	// (int **) 一个*表示强制类型转换,另一个表示指针 int *
	//sizeof(int*),不能少*,一个指针的内存大小,每个元素是一个指针。用指针长度乘以数量 (int*)*n
	// 这个p指针的数据类型是个二级指针,它指向的这个空间里放的是些一级指针



	for (int i = 0; i < 5; i++)//每行有两列元素
	{
		p[i] = (int *)malloc(sizeof(int) * m);//每个元素是int大小  4*m  将元素保存到每一行
		//每一个一级指针值的大小     指向一个实际大小的空间
		// *(p+i) = p[i]   每一次移动表示行的移动
	}

	//赋值
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
	{
			p[i][j] =1;
			//*(*(p + i) + j) = p[i][j]
		}
	}


	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)

		{
			//输出数组每个元素值和地址
			printf("%d=%p\t", p[i][j],&p[i][j]);
			
		}
		printf("\n");
	}

	for (int i = 0; i < n; i++) {//按 行 释放指针
		free(p[i]);
	}

	free(p);//释放整体

	return 0;
}

/*程序演示:
5 2
1=010F44C0	1=010F44C4
1=010F4378	1=010F433C
1=010F4330	1=010F4374
1=010FAB60	1=010FAB64
1=010FAD98	1=010FAB94
进程8432已退出.返回代码为 0.
按任意键关闭此窗口...

const 函数(补充)

      之前一直把这个关键字漏掉了现在补上,const 限定符,它把一个对象转换成一个常量,C语言中const关键字是constant的缩写,通常翻译为常量、常数等,有些朋友一看到const关键字马上就想到了常量。事实上在C语言中const功能很强大,它可以修饰变量、数组、指针、函数参数等。

1、修饰变量:

在程序中使用const修饰变量,就可以对变量声明为只读特性,并保护变量值以防被修改。如下:

const int i = 5;  变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10;则是错误的。

值得注意的是,定义变量的同时,必须初始化。定义形式也可以写成int const i=5,同样正确。

此外,const修饰变量还起到了节约空间的目的,通常编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。

2、修饰数组

C语言中const还可以修饰数组,举例如下:

const int array[5] = {1,2,3,4,5};

array[0] = array[0]+1; //错误

数组元素与变量类似,具有只读属性,不能被更改;一旦更改,如程序将会报错。

3、修饰函数参数

const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改。所限定的函数参数可以是普通变量,也可以是指针变量。举例如下:

void fun1(const int i)

i++; //对i的值进行了修改,程序报错

void fun2(const int *p)

(*p)++; //对p指向空间的值进行了修改,程序报错

保护数组中的元素:

为了避免函数的意图不是为了修改数组当中的数据内容,那么在函数原始声明定义中时应使用关键字const,如:

int sum(const a[ ],int n);  这段代码告诉编译器,该函数不能修改a所指向的数组中的内容,如果在函数中不小心使用类似a[i]++;的表达式,那么程序将会报错。

要注意的是,这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可修改,这样使用const可以保护数组当中的数据不被修改。

4、修饰指针

C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。举例说明如下:

int i = 5;

int j = 6;

int k = 7;

const int * p1 = &i; //定义1

int * const p2 =&j; //定义2

上面定义了两个指针p1和p2。

在定义1中const限定的是 * p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=20,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。

在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如 * p2=80是没有问题的,程序正常执行。

关于指针赋值和const需要注意一些规则:

1、把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的

2、可以声明并初始化一个不能指向别处的指针,关键是const的位置,这时候,这个指针可以修改它所指向的值,但是只能指向初始化时设置的地址。

3、在创建指针时还可以使用两次const,表示该指针既不能修改它所指向的地址,也不能修改它所指向地址上的值

清单:

int a[10];
const double b[10];

const double *p=a;	//有效
p=b;				//有效
p=&a[3];			//有效

---------------------------
int a[10];
const double b[10];
				//只能将非const数据的地址赋给普通指针  (否则,通过指针就能修改const数组中的值了)
double *p=a //有效
p=b;		//无效*
p=&a[3];	//有效

---------------------------
void sum(const double *a,int n);
		//此函数可以接受普通数组和const数组名作为参数,因为这两种参数都可以用来初始化指向const的指针
int a[10];
const double b[10];
sum(a,5);//合法
sum(b,4);//合法

---------------------------
int a[10];
double *const p=a; //p指向数组的开始
p=&a[0];		   //不允许,因为该指针不能指向别处
*p=9.9;				//可以做,更改a[0]的值

---------------------------
int a[10];
const double *const p=a;
p=&a[0];	//不允许
*p=9.9;		//不允许

块指的是一块数据,是个抽象的概念,和C语言没有关系,这种抽象的东西,别说其他语言也能用,就是日常生活中也会把东西分块管理,C语言中没有对块进行定义,因为这只是个抽象的概念,块可以是内存块,数据块,程序块,哪怕是豆腐块也能是块...  意思就是在管理中被划分为一类的一个基本单位

存储期:

存储期这也是变量的特点,它称为生存期,表示变量在内存中存在的时间的长短。

  1、静态存储期:在程序编译时就分配内存空间并保持不变,程序执行结束后才释放。

  2、线程存储期:thread_local,其声明后会给每个线程分配一个单独的私有备份

  3、自动存储期:局部变量通常都自动为auto 存储期

  4、动态存储期:就是用new 或者malloc分配的内存,如果不主动释放,在整个程序都占有内存

作用域:

这个是表示变量在哪些范围内起作用,由链接点决定。

1、块作用域:用{}括起来的,从声明开始到“}” 结束

    2、函数作用域:goto(标识符) 的作用域为整个函数。

    3、函数原型作用域:函数声明开始,函数声明结束而结束

    4、文件作用域:整个文件或者程序

链接属性:

表示变量能在哪些范围内使用.

1、内部链接 :只能在源文件内部使用.

2、外部链接 : 能在源文件内部和外部文件中使用.

3、空连接 : 只能在代码块内(函数内部)使用.

限定符

  volatile:

  const:

  restrict:

_Atomic:

作者:Mr_Li_

另外的话为了帮助大家,轻松,高效学习C语言/C++,我给大家分享我收集的资源,从最零基础开始的教程到C语言项目案例,帮助大家在学习C语言的道路上披荆斩棘!

编程学习视频分享:

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值