内存简要概述(三)

1、数据结构是什么

数据结构就是研究数据如何组织(在内存中排布),如何加工的学问。

2、最简单的数据结构:数组

(1)为什么要有数组?
  因为程序中有好多个类型相同、意义相关的变量需要管理,这时候如果用单独的变量来做程序看起来比较乱,用数组来管理会更好管理。譬如 int ages[20];
(2)数组的优势和缺陷

优势:数组比较简单,访问用下标,可以随机访问。
缺陷:数组中所有元素类型必须相同;2 数组大小必须定义时给出,而且一旦确定不
能再改。

3、结构体隆重登场

结构体发明出来就是为了解决数组的第一个缺陷:数组中所有元素类型必须相同
我们要管理3个学生的年龄(int类型),怎么办?
第一种解法:用数组		int ages[3];
第二种解法:用结构体	
struct ages
{
	int age1;
	int	age2;
	int age3;
};
struct ages age;
分析总结:在这个示例中,数组要比结构体好。但是不能得出结论说数组就比结构体好,
在包中元素类型不同时就只能用结构体而不能用数组了。
struct people
{
	int age;			// 人的年龄
	char name[20];		// 人的姓名
	int height;			// 人的身高
};
因为people的各个元素类型不完全相同,所以必须用结构体,没法用数组。

4、题外话:结构体内嵌指针实现面向对象

面向过程与面向对象。
	总的来说:C语言是面向过程的,但是C语言写出的linux系统是面向对象的。
	非面向对象的语言,不一定不能实现面向对象的代码。只是说用面向对象的语言来实
现面向对象要更加简单一些、直观一些、无脑一些。
	用C++、Java等面向对象的语言来实现面向对象简单一些,因为语言本身帮我们做了
很多事情;但是用C来实现面向对象很麻烦,看起来也不容易理解,这就是为什么大多数人
学过C语言却看不懂linux内核代码的原因。

struct s
{
	int age;					// 普通变量
	void (*pFunc)(void);		// 函数指针,指向 void func(void)这类的函数
};

	使用这样的结构体就可以实现面向对象。
	这样包含了函数指针的结构体就类似于面向对象中的class,结构体中的变量类
似于class中的成员变量,结构体中的函数指针类似于class中的成员方法。

6、内存管理之栈(stack)

(1)什么是栈
  栈是一种数据结构,C语言中使用栈来保存局部变量。栈是被发明出来管理内存的。
(2)栈管理内存的特点(小内存、自动化)

先进后出 FILO   first in last out	栈
先进先出 FIFO   first in first out  	队列

  栈的特点是入口即出口,只有一个口,另一个口是堵死的。所以先进去的必须后出来。

  队列的特点是入口和出口都有,必须从入口进去,从出口出来,所以先进去的必须先出来,否则就堵住后面的。

(3)栈的应用举例:局部变量
  C语言中的局部变量是用栈来实现的。

  我们在C中定义一个局部变量时(int a),编译器会在栈中分配一段空间(4字节)给这个局部变量用(分配时栈顶指针会移动给出空间,给局部变量a用的意思就是,将这4字节的栈内存的内存地址和我们定义的局部变量名a给关联起来),对应栈的操作是入栈。

  注意:这里栈指针的移动和内存分配是自动的(栈自己完成,不用我们写代码去操作)。

  然后等我们函数退出的时候,局部变量要灭亡。对应栈的操作是弹栈(出栈)。出栈时也是栈顶指针移动将栈空间中与a关联的那4个字节空间释放。这个动作也是自动的,也不用人写代码干预。

  栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。

  分析一个细节:C语言中,定义局部变量时如果未初始化,则值是随机的,为什么?

  定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上,而栈内存是反复使用的(脏的,上次用完没清零的),所以说使用栈来实现的局部变量定义时如果不显式初始化,值就是脏的。如果你显式初始化怎么样?

C语言是通过一个小手段来实现局部变量的初始化的。
int a = 15;		// 局部变量定义时初始化
C语言编译器会自动把这行转成:
int a;			// 局部变量定义
a = 15;			// 普通的赋值语句

(4)栈的约束(预定栈大小不灵活,怕溢出)

  首先,栈是有大小的。所以栈内存大小不好设置。如果太小怕溢出,太大怕浪费内存。(这个缺点有点像数组)

  其次,栈的溢出危害很大,一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时 int a[10000]; 使用递归来解决问题时一定要注意递归收敛)

7、内存管理之堆

(1)什么是堆
  堆(heap)是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存,申请或者释放的内存块大小随意)。

  堆这种内存管理方式特点就是自由(随时申请、释放;大小块随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存。

  我们什么时候使用堆内存?需要内存容量比较大时,需要反复使用及释放时,很多数据结构(譬如链表)的实现都要使用堆内存。

(2)堆管理内存的特点(大块内存、手工分配&使用&释放)

  特点一:容量不限(常规使用的需求容量都能满足,但不是真的无限)。

  特点二:申请及释放都需要手工进行,手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free。如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),称为内存泄漏。在C/C++语言中,内存泄漏是最严重的程序bug,这也是别人认为Java/C#等语言C/C++优秀的地方。

(3)C语言操作堆内存的接口(malloc free)

堆内存释放时最简单,直接调用free释放即可。	void free(void *ptr);

堆内存申请时,有3个可选择的类似功能的函数:
void *malloc(size_t size);

void *calloc(size_t nmemb, size_t size);	// nmemb个单元,每个单元size字节

void *realloc(void *ptr, size_t size);		// 改变原来申请的空间的大小的

譬如要申请10int元素的内存:
malloc(40);			malloc(10*sizeof(int));
calloc(10, 4);		calloc(10, sizeof(int));

  数组定义时必须同时给出数组元素个数(数组大小),而且一旦定义再无法更改。在Java等高级语言中,有一些语法技巧可以更改数组大小,但其实这只是一种障眼法。它的工作原理是:先重新创建一个新的数组大小为要更改后的数组,然后将原数组的所有元素复制进新的数组,然后释放掉原数组,最后返回新的数组给用户;

  堆内存申请时必须给定大小,然后一旦申请完成大小不变,如果要变只能通过realloc接口。realloc的实现原理类似于上面说的Java中的可变大小的数组的方式。

(4)堆的优势和劣势(管理大块内存、灵活、容易内存泄漏)

优势:灵活;
劣势:需要程序员去处理各种细节,所以容易出错,严重依赖于程序员的水平。

8、复杂数据结构

(1)链表、哈希表、二叉树、图等

  链表是最重要的,链表在linux内核中使用非常多,驱动、应用编写很多时候都需要使用链表。所以对链表必须掌握,掌握到:会自己定义结构体来实现链表、会写链表的节点插入(前插、后插)、节点删除、节点查找、节点遍历等。(至于像逆序这些很少用,掌握了前面那几个这个也不难)。

  哈希表不是很常用,一般不需要自己写实现,而直接使用别人实现的哈希表比较多。对我们来说最重要的是要明白哈希表的原理、从而知道哈希表的特点,从而知道什么时候该用哈希表,当看到别人用了哈希表的时候要明白别人为什么要用哈希表、合适不合适?有没有更好的选择?

  二叉树、图等。对于这些复杂数据结构,不要太当回事。这些复杂数据结构用到的概率很小(在嵌入式开发中),其实这些数据结构被发明出来就是为了解决特定问题的,你不处理特定问题根本用不到这些,没必要去研究。

(2)为什么需要更复杂的数据结构
  因为现实中的实际问题是多种多样的,问题的复杂度不同,所以需要解决问题的算法和数据结构也不同。所以当你处理什么复杂度的问题,就去研究针对性解决的数据结构和算法;当你没有遇到此类问题(或者你工作的领域根本跟这个就没关系)时就不要去管了。

(3)数据结构和算法的关系

  数据结构的发明都是为了配合一定的算法;算法是为了处理具体问题,算法的实现依赖于相应的数据结构。

  当前我们说的算法和纯数学是不同的(算法是基于数学的,大学计算机系研究生博士生很多本科都是数学相关专业的),因为计算机算法要求以数学算法为指导,并且结合计算机本身的特点来改进,最终实现一个在计算机上可以运行的算法(意思就是用代码可以表示的算法)。

(4)大家应该明白:

	<1> 数据结构和算法是相辅相成的,要一起研究。
	<2> 数据结构和算法对嵌入式来说不全是重点,不要盲目的跑去研究这个。
	<3> 一般在实际应用中,实现数据结构和算法的人和使用数据结构和算法的人是分开
	的。实际中有一部分人的工作就是研究数据结构和算法,并且试图用代码来实现这些
	算法(表现为库);其他做真正工作的人要做的就是理解、明白这些算法和数据结构的
	意义、优劣、特征,然后在合适的时候选择合适的数据结构和算法来解决自己碰到的实
	际问题。

	举个例子:linux内核在字符设备驱动管理时,使用了哈希表(hash table,
	散列表)。所以字符设备驱动的很多特点都和哈希表的特点有关。

注:本文章内容整理于朱老师物联网大讲堂课程,如有侵权,联系删除!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值