2019-08-22内存这个大话题

作者: 嵌入式Linux小白 | 来源:发表于2019-08-23 22:29 被阅读1次

    1.程序运行为什么需要内存

    1.1计算机程序运行的目的

    计算机为什么需要编程?编程已经变了很多年,已经写了很多程序,为什么还需要另外写程序?计算机有这个新的程序到底是为了什么?(类比美食)
    程序的目的是为了去运行,程序运行是为了得到一定的结果。计算机就是用来计算的,所有的计算机程序其实都是在做计算。计算就是在计算数据。所以计算机程序中很重要的部分就是数据。
    计算机程序=代码+数据
    代码+数据→运行=结果
    从宏观上来理解,代码就是动作,就是加工数据的动作,数据就是数字,就是被代码所加工的东西。那么可以得出结论:程序运行的目的不外乎2个:结果、过程
    用函数来类比,函数的形参就是待加工的数据(函数内还需要一些临时数据,就是局部变量),函数本体就是代码,函数的返回值就是结果,函数体的执行过程就是过程。

    int  add(int a, int  b)
    {
        return  a+b;    //这个函数的执行就是为了得到结果
    }
    
    void  add(int  a,  int  b)
    {
        int  c;
        c  =  a  +  b;
        printf("c=%d.\n",  c);
        //这个函数的执行重在过程。返回值不需要
    }
    
    int  add(int  a,  int  b)
    {
        int  c;
        c  =  a  +  b;
        printf("c=%d.\n",  c);
        return  c;
        //这个函数既重结果又重过程
    }
    

    1.2计算机程序运行的过程

    计算机程序运行过程,其实就是程序中很多个函数相继运行的过程。程序是由很多个函数组成的,程序的本质就是函数,函数的本质就是加工数据的动作。

    1.3冯诺依曼结构和哈佛结构

    冯诺依曼结构是:数据和代码放在一起
    哈佛结构是:数据和代码分开存放
    什么是代码:函数
    什么是数据:全局变量、局部变量
    在S5PV210中运行的Linux系统上,运行应用程序时,这时候所以的应用程序的代码和数据都在DRAM,所以这种结构就是冯诺依曼结构;在单片机中,我们把程序少写到flash(NorFlash)中,然后程序在Flash中原地运行,程序中涉及到的数据(全局变量、局部变量)不能放在Flash中,必须放在RAM(SRAM)中。这种就叫哈佛结构。

    1.4动态内存DRAM和静态内存SRAM

    https://www.jianshu.com/p/6a115fc9f269

    1.5总结:为什么需要内存呢?

    内存是用来存储可变数据的,数据在程序中表现为全局变量、局部变量等(在gcc中,其实常量也是存储在内存中的)(大部分单片机中,常量是存储在flash中的,也就是在代码段),对我们写程序来说非常重要,对程序运行更是本质相关。
    所以内存对程序来说几乎是本质需求。越简单的程序需要越少的内存,而越庞大越复杂的程序需要更多的内存。内存管理是我们写程序时很重要的话题。我们以前学过的了解过的很多编程的关键其实都是为了内存。比如说数据结构(数据结构是研究数据如何组织的,数据是放在内存中的)和算法(算法是为了用更优秀的方法来加工数据,既然跟数据有关就离不开内存。)

    1.6深入思考:如何管理内存(无OS时,有OS时)

    对于计算机来说,内存容量越大则可能性越大,所以大家都希望自己的电脑内存更大,我们写程序时如何管理内存就成了很大的问题。如果管理不善,可能会造成程序运行消耗过多的内存,这样迟早内存都被你这个程序吃光了,当没有内存可用时程序就会崩溃。所以内存对程序来说是一种资源,所以管理内存对程序来说是一个重要技术和话题。
    (1)先从操作系统的角度来讲:操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分成1个1个的页面(其实就是一块,一般是4kb),然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说,其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。比如在C语言中使用malloc、free这些接口来管理内存。
    (2)没有操作系统时:在没有操作系统(其实就是裸机程序)中,程序需要直接操作内存,编程者需要自己计算内存的使用和安排。如果编程者不小心把内存用错了,错误结果需要自己承担。
    (3)再从语言角度来说:不同的语言提供了不同的操作内存的接口。
    ①比如汇编:根本没有任何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(0xd0020010),非常麻烦;
    ②比如C语言:C语言中编译器帮我们管理直接内存地址,我们都是通过编辑器提供的变量名等来访问内存的,操作系统下如果需要大块内存,可以通过API(malloc、free)来访问系统内存。裸机程序中需要大块的内存,需要自己来定义数组等来解决。
    ③比如C++语言:C++语言对内存的使用进一步封装。我们可以用new来创建对象(其实就是为对象分配内存),然后使用完了用delete删除对象(其实就是释放内存)。所以C++对内存的管理要比C高级一些,但是C++中内存的管理还是靠程序员自己来做。如果程序员new了一个对象,但是用完了忘记delete就会造成这个对象占用的内存不能释放,这就是内存泄漏。
    ④比如JAVA、C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理,来帮我们处理内存的释放工作。如果我的程序申请了内存,使用完成之后忘记释放,则虚拟机会帮助我们释放掉这些内存。听起来似乎JAVA、C#等语言比C、C++有优势,但是其实他这个虚拟机回收内存是需要付出一定代价的,所以说语言没有好坏之分,只有适应不适应。
    当我们对程序性能非常在乎的时候(如操作系统内核)就会用C、C++语言;当我们对开发程序的速度非常在乎的时候,就会用JAVA、C#等语言

    2.位、字节、半字、字的概念和内存位宽

    2.1什么是内存?(硬件和逻辑两个角度)

    (1)从硬件角度:内存实际上是电脑的一个配件(一般叫内存条)。根据不同的硬件实现原理还可以把内存分成SRAM(不需初始化)和DRAM(需初始化)(DRAM又有好多代,比如最早的SDRAM,后来的DDR1、DDR2,…,LPDDR低功耗手机用)。
    (2)从逻辑角度:内存是这样一种东西,它可以随机访问(随机访问的意思是只要给一个地址,就可以访问这个内存地址)、并且可以读写(当然了逻辑上也可以限制其为只读或只写);内存在编程中天然是用来存放变量的(就是因为有了内存,所以C语言才能定义变量,C语言中的一个变量实际就对应内存中的一个单元)。

    2.2内存的逻辑抽象图(内存的编程模型)

    (1)从逻辑角度讲,内存实际上是由无限多个内存单元格组成的,每个单元格有一个固定的地址叫内存地址,这个内存地址和这个内存单元格唯一对应且永久绑定。
    以大楼来类比内存最合适的。逻辑上的内存就好像是一栋无限大的大楼,内存的单元格就好像大楼中的一个个小房间。每个内存单元格的地址就好像每个小房间的房间号。内存中存储的内容就好像住在房间中的人一样。
    (2)从逻辑上来说,内存可以有无限大(因为数学上编号永远可以增加,无尽头)。但是现实中实际的内存大小是有限制的,比如32位的系统(32位系统指的是32位数据线,但是一般地址线也是32位的,这个地址线32位决定了内存地址只能有32位二进制,所以逻辑上的大小为2的32次方)内存限制就为4G(4GB=4x8x2^32位二进制)。实际上32位的系统中可用的内存是小于或等于4G的(如我32位的CPU装32位Windows,但实际电脑只有512M内存)。

    2.3位和字节

    内存的单元大小单位是4个:位(1bit)、字节(8bit)
    在所有的计算机、所有的机器中(不管是32位系统还是16位系统还是以后的64位系统),位永远都是1bit,字永远都是8bit。

    2.4字和半字

    半字(一般是16bit)、字(一般是32bit)
    历史上曾经出现过16位系统、32位系统、64位系统三种,而且操作系统还有Windows、Linux、iOS等很多,所以很多的概念在历史上曾经被混乱定义过。建议大家对字、半字、双字这些概念不要详细区分,只要知道这些单位具体有多少位是依赖于平台的。实际工作中在每种平台上先去搞清楚这个平台的定义(字是多少位、半字永远是字的一半、双字永远是字的2倍大小)。编程时一般根本用不到字这个概念,那我们区分这个概念主要是因为有些文档中会用到这些概念,如果不加以区别可能会造成你对程序的误解。
    在Linux+ARM这个软硬件平台上(我们嵌入式核心课的所有课程中),字是32位的。

    2.5内存位宽(硬件和逻辑两个角度)

    (1)从硬件角度讲:硬件内存的实现本身是有宽度的,也就是说有些内存条就是8位的,而有些就是16位的。那么需要强调的是内存芯片之间是可以并联的,通过并联后即使8位的内存芯片也可以做出16位或32位的硬件内存。
    (2)从逻辑角度讲:内存位宽在逻辑上是任意的,甚至逻辑上存在内存位宽是24位的内存(但实际上这种硬件是买不到的,也没有实际意义)。从逻辑角度来讲不管内存位宽是多少,我就直接操作即可,对我的操作不构成影响。但是因为你的操作不是纯逻辑的而是需要硬件去执行,所以不能为所欲为,所以我们实际的很多操作都是受限于硬件的特性的。比如24位的内存逻辑上和32位的内存没有任何区别,但实际硬件都是32位的,都需要按照32硬件的特性和限制来干活。

    3.内存编址和寻址、内存对齐

    3.1内存编址方法

    内存在逻辑上就是一个一个的格子,这些格子可以用来装东西(里面装的东西就是内存中存储的数),每个格子有一个编号,这个编号就是内存地址,这个内存地址(一个数字)和这个格子的空间(实质是一个空间)是一一对应且永久绑定的,这就是内存的编址方法。
    在程序运行时,计算机中CPU实际只认识内存地址,而不关心这个地址所代表的的空间在哪里,怎么分配这些实体问题。因为硬件设计保证了按照这地址就一定能找到这个格子,所以说内存单元的2个概念:地址和空间是内存单元的两个方面(类比酒店房卡和房间)。

    3.2关键:内存编址是以字节为单位的

    重点提示:一个地址=一个字节
    我随便给个数字(如7),然后说这个数字是一个内存地址,然后问你这个内存地址对应的空间多大?这个大小是固定的,就是一个字节(8bit)。
    如果把内存比喻为一栋大楼,那么这个楼里面的一个一个房间就是一个个内存格子,这个格子的大小是固定的8bit,就好像这个大楼里面所有的房间户型是一样的。

    3.3内存和数据类型的关系

    C语言中的基本数据类型有:char、short、int、long、float、double
    重点讲解:int
    整型(整数类型,这个“整”体现在它和CPU本身的数据位宽是一样的)比如32位的CPU,整型就是32位,int就是32位。
    (1)数据类型和内存的关系就在于:数据类型是用来定义变量的,而这些变量需要存储、运算在内存找那个。所以数据类型必须和内存相匹配才能获得最好的性能,否则可能不工作或者效率低下。
    (2)在32位系统中定义变量最好用int,因为这样效率高。原因就在于32位的系统本身配合内存等也是32位的,这样的硬件配置天生适合定义32位的int类型变量,效率最高。也能定义8位的char类型变量或者16位的short类型变量,但是实际访问效率不高。
    (3)在很多32位环境下,我们实现定义bool类型变量(实际只需要1bit就够了)都是用int来实现bool的。也就是说我们定义一个bool b1;时,编译器实际帮我们分配了32位的内存来存储这个bool变量b1.编译器这么做实际上浪费了31位的内存,但是好处是效率高。
    (4)问题:实际编程时要以省内存为大还是要以运行效率为重?
    答案是不一定的,看具体情况。很多年前内存很贵,机器上内存都很少,那时候写代码以省内存为主,现在随着半导体技术的发展,内存变得很便宜了,现在的机器都是高配,不在乎一点内存,而效率和用户体验变成了关键。所以现在写程序大部分都是以效率为重。

    3.4内存对齐

    我们在C中int a;定义一个int类型变量,在内存中就必须分配4个字节来存储这个a。有这么2种不同内存分配思路和策略:
    第一种:0 1 2 3 (对齐方式)


    对齐访问

    第二种:1 2 3 4 或 2 3 4 5 或 3 4 5 6


    非对齐访问
    内存的对齐方式不是逻辑的问题,是硬件的问题。从硬件角度来说,32位内存它0123四个单元本身逻辑就有相关性,这4个字节组合起来当做一个int,硬件上就是合适的,效率就高。
    对齐访问很配合硬件所以效率很高;非对齐访问因为和硬件本身不搭,所以效率不高。(因为兼容性的问题,一般硬件都提供非对齐访问,但是效率要低很多。)

    3.5从内存编址看数组的意义

    数组在内存的表示

    a表示数组的首元素的首地址,即是内存0的位置。

    4.C语言如何操作内存

    4.1C语言对内存地址的封装

    用变量名来访问内存、数据类型的含义、函数名的含义
    (1)比如在C语言中,int a;a = 5;a += 4; //a等于9
    结合内存来解析C语言语句的本质:
    ①int a;
    编译器帮我们申请了1个int类型的内存格子(长度是4字节,地址是确定的,但是只有编译器直到,我们是不知道的,也不需要知道)并且把符号a和这个格子绑定。
    ②a = 5;
    编译器发现我们要给a赋值,就会把这个5丢到符号a绑定的那个内存格子中。
    ③a += 4;
    编译器发现我们要给a加值,a += 等效于a = a+4;编译器会先把a原来的值读出来,然后给这个值加4,再把加之后的和写到a里面去。
    (2)C语言中数据类型的本质含义是:表示一个内存格子的长度和解析方法。
    ①数据类型决定长度的含义:我们一个内存地址(0x30000000)本来这个内存地址只代表1个字节的长度,但是实际上我们可以通过给它一个类型(int)让它有了长度(4字节),这样这个代表内存地址的数字(0x30000000)就能表示从这个数字(0x30000000)开始的连续的n(4)个字节的内存格子了(0x30000000+0x30000001+0x30000002+0x30000003)。
    ②数据类型决定解析方法的含义:比如我有一个内存地址(0x30000000),我们可以通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法。比如我(int)0x30000000,含义就是(0x30000000+0x30000001+0x30000002+0x30000003)这4个字节连起来共同存储的是一个int型数据;那么我(float)0x30000000,含义就是(0x30000000+0x30000001+0x30000002+0x30000003)这4个字节连起来共同存储的是一个float型数据。
    (之前讲过一个很重要的概念:内存单元格子的编址单位都是字节。)
    (3)C语言中,函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名的本质也是一个内存地址。

    4.2用指针来间接访问内存

    (1)关于类型(不管是普通变量类型int float等,还是指针类型int * 、float *等),只要记住:类型只是对后面数字或者符号(代表的是内存地址)所表征的内存的一种长度规定和解析方法规定而已。(好比男女之间的感情,人家对你是友情,你对人家是爱情,往往解析方法就不对,就会造成误会,造成痛苦。)
    (2)C语言中的指针,全名叫指针变量,指针变量和普通变量其实没有任何区别。比如int a和int *p其实没有任何区别,a和p都代表一个内存地址(比如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a是int型所以a的长度是4字节,解析方法是按照int的规定来的;p是int *类型,所以长度是4字节,解析方法是按照int *的规定来的(0x20000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int型的数)。

    4.3指针类型的含义

    int  a;    //int  a;编译器会自动给a分配一个内存地址,比如说0x12345678
    (int  *)a;      //等价于(int  *)0x12345678,符号可以看成一个数,它代表地址
    

    4.4用数组来管理内存

    (1)数组管理内存和变量管理内存其实没有本质区别,只是符号的解析方法不同。(普通变量、数组、指针变量其实都没有本质差别,都是对内存地址的解析,只是解析方法不一样)

    int  a;    //编译器分配4个字节长度给a,并且把首地址和符号绑定起来
    int  b[3];    //编译器分配12个字节长度给b,并且把数组首元素的首地址和符号b绑定起来
    
    数组与普通变量内存存储

    数组中第一个元素(b[0])就称为首元素,每一个元素类型都是int,所以长度都是4,其中第一个字节的地址就称为首地址,首元素a[0]的首地址就称为首元素首地址,即红色区域。

    5.内存管理之结构体

    5.1数据结构这门学问的意义

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

    5.2最简单的数据结构:数组

    为什么需要数组?因为程序中有好多个类型相同、意义相关的变量需要管理,这时候如果用单独的变量来做程序看起来比较乱,用数组来管理会更好管理。比如int ages[20]来管理一个班级学生年龄。

    5.3数组的优势和缺陷

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

    5.4结构体隆重登场

    结构体发明出来就是为了解决数组的第一个缺陷——数组中所有元素类型必须相同。
    我们要管理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的各个元素类型不完全相同,所以必须用结构体,没法用数组。

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

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

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

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

    6.内存管理之栈

    6.1什么是栈?

    栈是一种数据结构,C语言中使用栈来保存局部变量。栈是被发明出来管理内存的。

    6.2栈管理内存的特点(小内存、自动化)

    先进后出(FILO)first in last out 栈
    先进先出 (FIFO)first in first out 队列
    栈的特点是入口即出口,只有一个口,另一个口试堵死的,所以先进去的必须后出来。
    队列的特点是入口和出口都有,必须从入口进去,从出口出来,所以先进去的必须先出来,否则就堵住后面的。

    6.3栈的应用举例:局部变量

    (1)C语言中的局部变量是用栈来实现的。
    ①我们再C语言中定义局部变量时(int a),编译器会自动在栈中分配一段空间(4字节)给这个局部变量用(分配时栈顶指针会移动给出空间,给局部变量a用的意思就是,将这4字节的栈内存的内存地址和我们定义的局部变量名a给关联起来),对应栈的操作就是入栈。
    注意:这里的栈指针移动和内存分配时自动的。栈自己完成,不用我们写代码操作。
    ②然后等我们函数退出时,局部变量要灭亡,对应栈的操作是弹栈(出栈)。出栈时也是栈顶指针移动将栈空间中与a关联的那4个字节空间释放。这个动作也是自动的,也不用人写代码干预。
    (2)栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。
    (3)分析一个细节:
    ①C语言中,定义局部变量时如果未初始化,则值是随机的,为什么?
    定义局部变量,其实就是在栈中通过移动栈指针来给程序员提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上,而栈内存是反复使用的(脏的,上次用完没有清零的),所以说使用栈来实现的局部变量定义时如果不显式初始化,值是脏的。
    ②如果你显式初始化怎么样?
    C语言是通过一个小手段来实现局部变量初始化的。
    int a = 15; //局部变量定义时初始化
    C语言编译器会自动把这行转成:
    int a;a = 15;

    6.4栈的约束

    首先,栈是有大小的。所以栈内存不好设置。如果太小怕溢出,太大怕浪费内存。(这个缺点有点像数组)其次,栈的溢出危害很大(因为入侵者可以利用栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。),一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大(比如不能定义局部变量时int a[10000];使用递归来解决问题时一定要注意递归收敛)。

    7.内存管理之堆

    7.1什么是堆?

    (1)堆(heap)是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着几十、几百、几千个进程所示都会申请或者释放内存,申请或释放的内存块大小随意)。
    (2)堆这种内存管理方式就是自由(随时申请、释放、大小块随意)。堆内存是操作系统划归堆管理起(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc、free)来使用堆内存。
    (3)我们什么时候使用堆内存?需要内存容量比较大时,需要反复使用及释放,很多数据结构(如链表)的实现都要使用堆内存。

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

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

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

    (1)堆内存释放时最简单,直接调用free释放即可。
    void free(void *ptr)
    堆内存申请时,有3个可选择的类似功能的函数
    malloc、calloc、realloc
    void *malloc(size_t size);
    void *calloc(size_t nmemb, size_t size); \\nmemb个单元,每个单元size字节
    void *realloc(void *ptr, size_t size);
    比如要申请10个int元素的内存:

    简单 通用性强
    malloc(40); malloc(10*sizeof(int));
    calloc(10, 4); calloc(10, sizeof(int));

    (2)数组定义时必须同时给出数组元素的个数(数组大小),而且一旦定义无法更改。在JAVA等高级语言中,有一些语法技巧可以更改数组大小,但其实只是一种障眼法。它的工作原理是:
    ①先重新创建一个新的数组大小为要更改后的数组;
    ②然后将原数组的所有元素复制进新的数组;
    ③然后释放掉原数组;
    ④最后返回新的数组给用户。
    (3)堆内存申请时必须给定大小,然后一旦申请完成,大小不变,如果邀标只能通过realloc接口。realloc的实现原理类似于上面说的JAVA中的可变大小的数组的方式。

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

    优势:灵活
    劣势:需要程序员去处理各种细节,所以容易出错,严重依赖于程序员的水平。
    (记住:世界上任何事物都是有优势与劣势,但劣势可以变成优势,优势也可能成为劣势。)

    8.复杂数据结构

    8.1链表、哈希表、二叉树、图等

    (1)链表是最重要的链表在Linux内核中使用非常多,驱动、应用编写很多时候都需要使用链表。所以对链表必须掌握,掌握到的程度:会自己定义结构体来实现链表、会写链表的结点插入(头插法、尾插法)、结点删除、结点查找、结点遍历等。(至于像逆序这些很少用,掌握了前面那些几个这个也不难。)
    (2)哈希表不是很常用,一般不需要自己写实现,而是直接使用别人实现的哈希表非常多。对我们来说最重要的就是要明白哈希表的原理,从而知道哈希表的特点,从而知道什么时候该用哈希表。当看到别人用了哈希表的时候要明白别人为什么使用哈希表,合不合适?有没有更好的选择?
    (3)二叉树、图等。对于这些复杂数据结构,不要太当回事。这些复杂数据结构用到的概率很小(在嵌入式开发中),其实这些数据结构被发明出来就是为了解决特定问题的,你不处理特定问题根本用不到这些,没必要去研究。

    8.2为什么需要更复杂的数据结构?

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

    8.3数据结构和算法的关系

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

    8.4应该怎么学习数据结构和算法这部分?

    从上面表述大家应该明白以下事实:
    (1)数据结构和算法是相辅相成的,要一起研究;
    (2)数据结构和算法对嵌入式来说不全是重点,不要盲目跑去研究这个;
    (3)一般在实际应用中,实现数据结构和算法的人和使用数据结构和算法的人是分开的。实际中有一部分人的工作就是研究数据结构和算法,并且试图用代码来实现这些算法(表现为库);其他真正工作的人要做的就是理解、明白这些数据结构和算法的意义、优劣、特征,然后在合适的时候选择合适的数据结构和算法来解决自己碰到的实际问题。
    举个例子:
    Linux内核在字符设备驱动管理时,使用了哈希表(hash table,散列表)。所以字符设备驱动的很多特点都和哈希表的特点有关。

    相关文章

      网友评论

        本文标题:2019-08-22内存这个大话题

        本文链接:https://www.haomeiwen.com/subject/moynsctx.html