学习笔记之C语言提升篇

 

1.数据结构。分析数据结构,进行框架设计。如果事情的发生具备时间性,则先把它们进行事件判别分类,然后按一定时间顺序进行存储,再依需要依一定的时间顺序进行取出处理,相比于,出现事件,立即判别作处理而无需存储的方式,这种结构设计,更能对事情的属性进行智能化,人性化的管理,如能够有效控制事情发生与作处理的间隔时间,如连续发生重复事件是否要作重复处理。通常有两种时间顺序:FIFO(先进先出),FILO(先进后出),前者可用队列的数据结构,后者则是栈的数据结构。

实例:处理RS232指令时,中断随机的产生事件,且事件有先有后,当收到指定格式的数据包时,实现相应的功能。问题出来了,任何时间内我收完一包数据都要处理吗,什么样的数据算一包。一般规定在指定时间内收完,才算有效数据,为了区分数据包,每包数据规定一个开始字节和结尾字节,即一定长时间内,完成从开始字节开始接收到结尾字节结束的一包数据,再对这包数据进行处理。这里就存在一个等待时间的问题,即要等到一包数据完成再作处理,因此要将接收的数据事先存储起来,再将先存的先取出判别处理,那这些数据存在哪儿呢,也许你会想存在一个限定长的数组里面,存上你的一包数据,当处理完成时,再发来数据,再清空存储新的数据包,这样做有个缺陷:当我的第一包数据还没来得及处理时,第二包数据就已经将数组空间覆盖了,从而导致丢包;也就是说,我们要解决,第二包数据来的时候不会覆盖在原来的空间上,也许你会想,把空间开大点,第二包来的时候,顺着后面的存储空间存,这样同样存在一个问题,那随着我的数据的增多,而且前面的数据包也已相应的做完处理,造成前面空间的浪费,后面空间的不断占用,那何不想个办法,把新来的数据包放在已经处理完的数据包的空间上呢,这就用到了数据结构中的循环队列了。关于栈和队列的应用有个共同点:一是事件有先有后二是通常会随机产生多个事件,造成事件堆积,来不及处理。就像我们日常中银行排队一样,如果操作员的速度比顾客到来的速度快,每来一个人都能及时处理的话,就不会有队列的产生,同样的,如果CPU处理比中断到来的要快(当然不太可能,因为任意小的间隔时间内都可能触发中断)或者我们有这种来一个处理一个的需求的话,就不需要用这两种数据结构了。

二.数据存储方式:顺序,链式。顺序的存储方式具备存储空间连续,使用前需预先开辟空间,便于随机的读取,适合排序等操作;链式相反,读取不方便想读取某一单元,必须先知道它前面的单元位置,适合插入删除结点等操作。链表在内在分配中有被大量使用,因为,我们经常会需要动态地分配内存,频繁地分配和释放,如果使用顺序存储,由于不得插入和删除的操作,但是如果不动态地进入插入和删除势必造成大量的内存碎片。

三.索引,是树性结构的一种。索引表是一个组织关系表,通过此表能对应出所索引内容的组成元素,表通过牵绳一样将组成元素牵出来。每个组成元素有时只是一种对应关系,对应于指定顺序的数据表中相应的位置。(所谓的顺序即各个元素应该按照什么样的顺序排列,各个元素又对应着一个数据表,根据这是第几个元素再去第几个数据表中去找)然后从此数据表相应的位置中取得实现的内容,最后组合成我们所需的目标,形象地比喻就是,第一个数据表就是第一个元素所要查找的范围,第一个元素在索引表的位置即对应第一个元素在数据表里的位置,第二个数据表被第一根绳子牵着,第一个元素所在索引表位置中存放的内容对应的位置即是第二个元素在第二个数据表的实际位置;第三个索引表被第二根绳子牵着,第二个元素所对应索引表的位置所存入的内容即第三个元素在第三个数据表的实际位置。上述关系只相对于,各元素之间彼此独立占用一个数据表,即各元素之间相互独立。FAT就是一个索引表。索引表的优点:使用很灵活,减少重复的单元占用内存空间,又能较高效找出目标组合。还有一种线性表,直接通过输入,牵出连续的一组输出关系,此输入输出关系已经确定,不像索引表还需要根据索引位置去数据表中查找,并组合成目标, 这种情况适合,输入的关系与输出一一对应,并且在一个系统中不会再发生变化且输入的情况范围较小时使用,因此这种线性的结构灵活性较差,如果输入情况过多,会占用大量的系统空间。

2.static的用法。static的用法一言以蔽之即:

(1)使生存期为程序开始运行至结束(如在函数内部变量前加static,改变其生存期)/*变量前次调用的值和后一次调用的值会有联系或要具备某种记忆功能时,会有此用法,如一个函数传入一个值,并与上次函数处理时产生的值进行对比,则可定义一个static变量用于存放此值*/;

(2)使可见性局限在指定范围内(如全局变量或函数前加static,改变它的可见性为仅此文件可见,/*变量或函数仅在一个文件中会使用到,就需这样设置,一是可以增加可读性,二是可以限制其它文件的访问,如由于编写不够谨慎不小心定义了一个同名的函数和变量,想用作另一种用途,但是其中只要有一个是static的,就不会因为同名而影响到彼此的功能,因为它们虽然同名,但由于static变量对于其它文件不可见,所以互相之间没有任何关联*/.

(3)将存储位置改为静态存储区。如局部变量前加static,原来存储为栈(内存的动态存储区)改为静态存储区。

   可以说static用在不同的地方,作用不同。

3.函数指针的用法。指向代码区的一段,函数名本身就是一个地址,定义此类型及变量的方法。

void handle_key(void)

{

/*此处省略一段代码,你懂的~!~ */

}

 

变量:

int (*proc_key)(void);

类型:

typedef int (*proc_key_typ)(int);

proc_key_typ proc_key;

/**************************

处理如下

 ***************************/

 proc_key = handle_key;

 proc_key(); //等于handle_key();

/**************************

作为结构体成员的用法

 ***************************/

typedef struct a{

int key;

int (*proc_key)(int);

}key_type;

key_type A = {LEFT, handle_key, };
A.proc_key(); //等于handle_key();

4.volatile的用法

(1)状态寄存器存放的值。(可能随时会被硬件改动到)因此需将这些寄存器地址用(volatile unsigned int *)进行类型转换后再使用。

(2)中断程序中会被改变的变量。(一旦跳入中断可能会变动)

(3)多线程之间公用的变量。(这样的变量在线程切换时可能会被改变,而volatile保证了当前线程对变量的改变对其它线程可见,但是它不能保证原子性,要实现原子操作必须还得对特殊指令加锁)

5.内存对齐

内存对齐的产生为了提高CPU访问内存的速率,按n字节对齐则CPU一次访问n个字节(内存访问粒度)。

对齐分两种:

1.结构体各成员的对齐;

方法:(1)先确定结构体内当前成员的对齐字节数,若成员为结构体,(则结构体的对齐字节数为结构体成员中数据类型占字节最多的成员所占字节数)/*与默认对齐字节数(#pragma pack(n))n比较取较小值,则为对齐字节数*/

             (2)确定当前成员起存地址,/*在满足大于上一成员的结尾地址并能整除已确定的对齐字节数这一条件的值中取最小值即为起存地址,起存地址与上一成员结尾地址之间的地址自动填充cc*/   

            (3)确定当前成员的尾地址,/*用确定好的起存地址加上此成员的数据类型本身占用的字节数即为结尾地址*/        

2.结构体本身的对齐。

6.连接符##

连接符作用是将两个字符通过##连接成一个字符,

如#define PIN(x) GPIO_PIN_##x

当使用PIN(1)时,直接连接成GPIO_PIN_1

如果x为宏,

#define ADC_PIN 1

当使用PIN(ADC_PIN)时,则变成了GPIO_PIN_ADC_PIN

也就是说,这里只直接替换,不会对宏进行进一步的翻译,要想实现GPIO_PIN_

方法:

1.确定整体对齐字节数,/*取默认对齐字节数与成员中数据类型占用字节最大的字节数比较,取较小值便是*/

2.确定整个结构体占用的字节数,/*结构体最后成员的结尾地址减去第一成员的起始地址便是*/

3.用此整个结构体字节数除以确定的对齐字节数,若不能整除,则在最后成员的后面填充cc直到能整除为止(即所谓的圆整过程)。

注意:只要是未分配的内存地址,均会自动填充为CC,如果以字节为单位存储,在大端模式,高字节放置在低地址处,小端模式,高字节放置于高地址处;如果以位块存储,那大端模式,高位块放置于低地址处,小端模式,高位块置于高地址处。

这里讲到了位,以上的对齐都是针对非位域的情况,下面介绍位域的对齐方式:

 方法:1.判断相邻成员的类型是否一致,若一致则,判断相邻成员位宽相加是否大于此数据类型的位宽,若小于,按指定位宽连续分配,不够处填充CC;若大于,则重新分配一个存储单元。

6.巧妙应用(大量的概念化的东西其实都能具体化到程序的执行)

1)所谓的注册,挂载(参比文件系统),打开,都是为需要操作的对象申请工作区,一个公共区域(其实是全局内存区域),当使用完成后,再将此公共区域释放掉,以供其它的对象使用,申请工作区的作用,使此对象为当前的操作对象,以便让此对象对所有的处理机制(可称为函数)可见,一旦可见,便可对对象进行各种处理。

2)内核动态加载,模块的加载和卸载这是我所知除main函数外唯一一个可以作为执行入口的函数,目前仅被使用于linux操作系统中,方便内核在运行时,能够驱动未被预先加入的设备。(有对函数指针的灵活运用,函数指针将处理的动作抽象成一个对象,可以由用户灵活决定选用哪种处理方式)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值