独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

[原创] 从业十年,教你单片机入门基础。(连载)

[复制链接]
61#
 楼主| 发表于 2015-12-27 02:04:03 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-12-27 02:07 编辑

第五十二节:从局部变量和全局变量中感悟“栈”为何物。


        52.1  什么是局部变量,什么是全局变量。
        局部变量就是在函数内部定义的变量,全局变量就是在函数外面定义的变量。下面举的例子能很清晰的说明局部变量和全局变量的特点:

  1. unsigned char a;     //在函数外面定义的,所以是全局变量。
  2.      void main()
  3. {
  4.           unsigned char b; //在函数内部定义的,所以是局部变量。
  5. }
复制代码


        52.2  局部变量和全局变量的内存模型。
        我在第三节里讲到,单片机内存包括ROM和RAM两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而RAM存放的是可以被更改的数据,这些可以被程序更改的数据就叫做变量。变量包括局部变量和全局变量,但是局部变量和全局变量在RAM中的数据结构有本质不同。欲知不同在哪,首先要从RAM说起。对于大部分的单片机,RAM都至少分成两部分,一部分是给全局变量,一部分是给局部变量。给全局变量那部分的RAM叫全局数据区,给局部变量那部分的RAM叫做“栈”,因为我后面会用宾馆来比喻“栈”,所以为了方便记忆,可以把“栈”想象成 “客栈”来记忆。 全局数据区就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住,而且是永久的,所以说每个全局变量都有唯一对应的RAM地址,不可能重复的。而“栈”就像宾馆客栈,每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址每天可能住进不同的人,不是唯一的。全局区的全局变量拥有永久产权,“栈”区的局部变量只能临时居住在宾馆客栈,地址不是唯一的,有期限的。全局变量像私人区,局部变量像公共区。


         52.3  借用宾馆客栈比喻局部变量的期限。
  1. void hanshu(void);   //子函数的声明
  2. void hanshu(void)    //子函数的定义
  3. {
  4.      unsigned char a;   //局部变量
  5.      a=1;
  6. }
  7. void main() //主函数
  8. {
  9. hanshu() ;      //子函数的调用
  10. }
复制代码

          请看上面的程序例子,单片机从主函数main往下执行,首先遇到hanshu子函数的调用,所以就跳到hanshu函数的定义那里开始执行,此时的局部变量a开始被分配在RAM的栈区的某个地址,相当于你入住宾馆被分配到某个房间。单片机执行完子函数hanshu后,局部变量a在RAM的栈区所分配的地址被收回,局部变量a消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量,此时相当于你离开宾馆,原来在宾馆居住那个房间被收回,你离开宾馆,从此你跟那个宾馆的房间没有啥关系,它可能会被宾馆老板重新分配给其他的客人入住。结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性不受范围限制的。


          52.4  局部变量和全局变量的优先级。
          上面最后一段话中得到一个结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性并且不受范围限制的。那么问题来,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?这个问题就涉及到优先级。注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。

           52.4.1   请看第一个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. unsigned char a=5;    //全局变量的定义
  3. void main() //主程序
  4. {
  5.     unsigned char a=2;  //局部变量的定义
  6.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2,而不是5

  7. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  8.    while(1)
  9.    {
  10.       initial();
  11.       key_service();
  12.       display_service();
  13.    }

  14. }
复制代码

           此时输出显示2。


           52.4.2   请看第二个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void); //函数声明
  3. unsigned char a=5;    //全局变量的定义
  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }
  8. void main() //主程序
  9. {
  10.     unsigned char a=2;   //局部变量的定义
  11.     hanshu();  //子函数被调用
  12.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

           此时输出显示2。
           分析:虽然子函数hanshu内部也有一个a=3,但是当hanshu被调用完结束后,这个a就消失不起作用了,所以此时显示的是main函数内部的局部变量。


           52.4.3   请看第三个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/

  2. void hanshu(void); //函数声明

  3. unsigned char a=5;    //全局变量的定义

  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }


  8. void main() //主程序
  9. {

  10.     hanshu();  //子函数被调用

  11.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是全部变量5

  12.       
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

          此时输出显示5。
          分析:子函数hanshu 里面的a的作用域只是它本身,因此子函数hanshu里面的a没办法影响到其它函数的a。而main函数内部没有了局部变量,那么此时起作用的只能是全局变量,因此显示5。假如此时连全局变量也没有了怎么办?这种情况是语法错误不能编译通过,因为任何变量的调用都必须要被先定义,定义有两种,要么是在函数内被定义成局部变量,要么是在函数外被定义成全局变量。


          52.5   局部变量和全局变量的总结归纳.
         (1)每定义一个新的全局变量,就意味着多开销一个新的RAM内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的“栈”区,此时的局部变量不开销新的RAM内存,因为局部变量是临时借用“栈”区的,使用后就还给“栈”,“栈”是公共区,可以重复利用,可以服务若干个不同的函数内部局部变量。
         (2)单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化,全局变量是一直保存之前最后一次更改的值。


下节预告:空返回空形参的函数。
(未完待续)

乐于分享,勇于质疑!
62#
 楼主| 发表于 2016-1-4 05:05:31 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-10 01:36 编辑

第五十三节:函数的作用和四种常见书写类型。
第五十三节_pdf文件.pdf (109.18 KB, 下载次数: 2464)

【53.1   函数的作用和分类。】

        函数的作用。通常把一些可能反复用到的算法或者过程封装成一个函数,函数就是一个模块,给它输入特定的参数,就可以输出想要的结果,比如一个加法函数,只要输入加数和被加数,然后就会输出相加结果之和,里面具体的算法过程只要写一次就可以重复调用,极大的节省单片机程序容量,也节省程序开发人员的工作量。
        函数的分类。从输入输出的角度来看,有四种常见的书写类型。分别是无输出无输入,无输出有输入,有输出无输入,有输出有输入。输出是看函数名的前缀,前缀如果是void表示无输出,否则就是有输出。输入是看函数名括号里的内容,如果是void或者是空着就表示无输入,否则就是有输入。输出和输入是比较通俗的说法,专业一点的说法是,有输出表示函数有返回,无输出表示函数无返回。有输入表示有形参,无输入表示无形参。下面举一个加法函数的例子,分别用四种不同的函数类型来实现。大家对比一下它们在书写方面有哪些不同,又有哪些规律。

【53.2   无输出无输入的函数。】

  1. unsigned char a;  //此变量用来接收最后相加结果的和。
  2. unsigned char g=2;
  3. unsigned char h=3;
  4. void hanshu(void)  //无输出无输入函数的定义。
  5. {
  6.    a=g+h;
  7. }
  8. main()
  9. {
  10.     hanshu();  //函数调用时的样子。
  11. }
复制代码
分析:
         void hanshu(void),此函数名的前缀是void,括号内也是void,属于无输出无输入函数。这类函数表面看是无输出无输入,其实在实际应用中也可以通过全局变量来输入输出,比如上面的例子就是靠a,g,h这三个全局变量来传递信息,只不过表达方式上像隐藏起来一样没有那么直观。

53.3   无输出有输入的函数。】

  1. unsigned char b;  //此变量用来接收最后相加结果的和。
  2. void hanshu(unsigned char i,unsigned char k)   //无输出有输入函数的定义
  3. {
  4.    b=i+k;
  5. }
  6. main()
  7. {
  8.     hanshu(2,3);  //函数调用时的样子。
  9. }
复制代码
分析:
         void hanshu(unsigned char i,unsigned char k),此函数名的前缀是void,括号内是(unsigned char i,unsigned char k),属于无输出有输入的函数。括号的两个变量ik是函数内的局部变量,也是跟对外的桥梁接口,它们有一个专业的名称叫形参。外部要调用此函数时,只要给括号填入对应的变量或者数值,这些变量和数值就会被复制一份传递给作为函数形参的局部变量,从而外部调用者跟函数内部就发生了数据信息的传递。这种书写方式的特点是把输入接口封装了出来。

53.4   有输出无输入的函数。】

  1. unsigned char c;   //此变量用来接收最后相加结果的和。
  2. unsigned char m=2;
  3. unsigned char n=3;
  4. unsigned char hanshu(void)     //有输出无输入函数的定义。
  5. {
  6. unsigned char p;
  7. p=m+n;
  8. return p;
  9. }
  10. main()
  11. {
  12.     c= hanshu();  //函数调用时的样子。
  13. }
复制代码
分析:
         unsigned char hanshu(void),此函数名的前缀是unsigned char类型,括号内是void,属于有输出无输入的函数。函数前缀的unsigned char表示此函数最后退出时会返回一个unsigned char类型的数据给外部调用者。而且这类函数内部必须有一个return语句配套,表示立即退出当前函数并且返回某个变量或者常量的数值给外部调用者。这种书写方式的特点是把输出接口封装了出来。

53.5   有输出有输入的函数。】

  1. unsigned char d;    //此变量用来接收最后相加结果的和。
  2. unsigned char hanshu(unsigned char r,unsigned char s)     //有输出无输入函数的定义
  3. {
  4. unsigned char t;
  5. t=r+s;
  6. return t;
  7. }
  8. main()
  9. {
  10.     d= hanshu(2,3);  //函数调用时的样子。
  11. }
复制代码
分析:
        unsigned char hanshu(unsigned char r,unsigned char s),此函数名的前缀是unsigned char类型,括号内是(unsigned char r,unsigned char s),属于有输出有输入的函数。输入输出的特点跟前面介绍的函数一样,不多讲。这种书写方式的特点是把输出和输入接口都封装了出来。

53.6   调用函数时特别要注意。】

        注意:在函数调用时,凡是“void”关键词都要省略,否则编译不通过。

53.7   练习程序。】

       现在把上述的4加法函数写成一个程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. //第1种:无输出无输入函数的声明。
  3. void hanshu_1(void);  
  4. //第2种:无输出有输入函数的声明。
  5. void hanshu_2(unsigned char i,unsigned char k);   
  6. //第3种:有输出无输入函数的声明。
  7. unsigned char hanshu_3(void);     
  8. //第4种:有输出无输入函数的声明。
  9. unsigned char hanshu_4(unsigned char r,unsigned char s);  
  10. //第1种:无输出无输入
  11. unsigned char a;  //此变量用来接收最后相加结果的和。
  12. unsigned char g=2;
  13. unsigned char h=3;
  14. //第2种:无输出有输入
  15. unsigned char b;  //此变量用来接收最后相加结果的和。
  16. //第3种:有输出无输入
  17. unsigned char c;   //此变量用来接收最后相加结果的和。
  18. unsigned char m=2;
  19. unsigned char n=3;
  20. //第4种:有输出有输入
  21. unsigned char d;    //此变量用来接收最后相加结果的和。
  22. //第1种:无输出无输入函数的定义。
  23. void hanshu_1(void)  
  24. {
  25.    a=g+h;
  26. }
  27. //第2种:无输出有输入函数的定义
  28. void hanshu_2(unsigned char i,unsigned char k)   
  29. {
  30.    b=i+k;
  31. }
  32. //第3种:有输出无输入函数的定义。
  33. unsigned char hanshu_3(void)   
  34. {
  35. unsigned char p;
  36. p=m+n;
  37. return p;
  38. }
  39. //第4种:有输出无输入函数的定义
  40. unsigned char hanshu_4(unsigned char r,unsigned char s)  
  41. {
  42. unsigned char t;
  43. t=r+s;
  44. return t;
  45. }
  46. void main() //主程序
  47. {
  48.     hanshu_1();       //第1种:无输出无输入函数。
  49.     hanshu_2(2,3);      //第2种:无输出有输入函数。
  50.     c=hanshu_3();      //第3种:有输出无输入函数。
  51.     d=hanshu_4(2,3);     //第4种:有输出无输入函数。
  52.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示。
  53.     GuiWdData1=b;   //把b这个数值放到窗口变量1里面显示。
  54.     GuiWdData2=c;   //把c这个数值放到窗口变量2里面显示。
  55.     GuiWdData3=d;   //把d这个数值放到窗口变量3里面显示。
  56.         
  57. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  58.    while(1)  
  59.    {
  60.       initial();
  61.       key_service();
  62.       display_service();
  63.    }
  64. }
  65.       
复制代码
        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
       变量a为5。
       变量b为5。
       变量c为5。
       变量d为5。

       下节预告:return语句在函数中的作用
(未完待续)



乐于分享,勇于质疑!
63#
 楼主| 发表于 2016-1-10 01:19:32 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-10 01:35 编辑

第五十四节:return语句在函数中的作用以及容易被忽略的四个功能
第五十四节_pdf文件.pdf (117.79 KB, 下载次数: 2383)

【54.1   return深入讲解

       “return”在英语单词中有“返回”的意思,在上一节提到,凡是“有输出函数”,函数内部必须有一个“return+变量或者常量”与之配套,表示返回的结果给外部调用者接收,这个初学者都很容易理解,容易被忽略的是return另外四个功能:
       第一个是return语句隐含了立即退出的功能。退出哪?退出当前函数。只要执行到return语句,就马上退出当前函数。即使return语句身陷多层while或者for的循环中,它也毫不犹豫立即退出当前函数。
       第二个是return语句可以出现在函数内的任何位置。可以出现在第一行代码,也可以出现在中间的某行代码,也可以出现在最后一行的代码,它的位置不受限制。很多初学者有个错觉,以为return只能出现在最后一行,这是错的。
       第三个是return语句不仅仅可以用在“有输出函数”,也可以用在“无输出函数”,也就是可以用在前缀是void的函数里。回顾上一节,在“有输出函数”函数里,return后面紧跟一个变量或者常量,表示返回的数,但是在“无输出函数”里,因为是“无输出”,此时return后面不用跟任何变量或者常量,表示返回的是空的。此时return主要起到立即退出当前函数的作用。
       第四个是return语句可以在一个函数里出现N次,次数不受限制,不一定必须只能一次。不管一个函数内有多少个return语句,只要任何一个return语句被单片机执行到,立即退出当前函数。

【54.2   中途立即退出的功能。】

       下面的书写格式是合法的:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. return; //立即退出当前函数。
  5. 语句2;
  6. return; //立即退出当前函数。
  7. 语句3;
  8. return; //立即退出当前函数。
  9. }
复制代码
分析:
        当hanshu此函数被调用时,单片机从“语句1”往下执行,当遇到第一个return语句后,马上退出当前函数。在此函数里,后面的“语句2”等代码永远不会被执行到。

【54.3   身陷多层while或者for的循环时的惊人表现。】

       下面的书写格式是合法的:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. while(1)  //第一个循环
  5. {
  6.     while(1)  //第二个循环中的循环
  7.     {
  8.           return; //立即退出当前函数。
  9.     }
  10.     语句2;
  11.     return; //立即退出当前函数。
  12. }
  13. 语句3;
  14.     return; //立即退出当前函数。
  15. }
复制代码
分析:
        当hanshu此函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个return语句,马上退出当前函数。在此函数里,后面的“语句2”等代码也是永远不会被执行到。虽然表面看起来有那么多可怕的循环约束着,但是一旦碰上return语句都是浮云,立刻退出当前函数。

【54.4   “有输出函数”时是什么样子的?】

        把上面例子中“无输出函数”改成“有输出函数”后:
  1. unsigned char hanshu(void)  //有输出/函数的定义。
  2. {
  3. unsigned char a=9;
  4. 语句1;
  5. while(1)  //第一个循环
  6. {
  7.     while(1)  //第二个循环中的循环
  8.     {
  9.           return a; //返回a变量的值,并且立即退出当前函数。
  10.     }
  11.     语句2;
  12.     return a; //返回a变量的值,并且立即退出当前函数。
  13. }
  14. 语句3;
  15. return a; //返回a变量的值,并且立即退出当前函数。
  16. }
复制代码
分析:
       因为此函数是“有输出/函数”,所以return语句后面必须配套一个变量或者常量,而执行顺序跟前面54.3的例子是一样的。当hanshu函数被调用时,单片机从“语句1”往下执行,先进入第一个循环,接着进入第二个循环中的循环,然后遇到第一个“return a”语句,马上退出当前函数。在此函数里,后面的“语句2”等代码也是永远不会被执行到的。再一次说明了,return语句不仅有返回某数的功能,还有立即退出的重要功能。

【54.5   项目应用时,中间的return语句往往是跟if语句搭配使用的。】

       前面的例子只是为了解释return语句的执行顺序和功能,实际项目中,如果中间有多个return语句,中间的return语句不可能像前面的例子那样单独使用,它往往是跟if语句一起搭配使用的,否则单独用return就没有什么意义。比如:
  1. void hanshu(void)  //无输出/函数的定义。
  2. {
  3. 语句1;
  4. if(某条件满足)
  5. {
  6.    return; //立即退出当前函数。
  7. }
  8. 语句2;
  9. if(某条件满足)
  10. {
  11.    return; //立即退出当前函数。
  12. }
  13. 语句3;
  14. }
复制代码
分析:
        单片机从“语句1”开始往下执行,至于在哪个“return”语句处退出当前函数,就要看哪个if的条件满不满足了,如果所有的if的条件都不满足,此函数会一直执行完最后的“语句3”才退出当前函数。

54.6   函数和变量的命名规则。】

       函数的名字和变量的名字一样,第一个字符不能是数字,必须是字母或者下划线“_”,后面紧跟的第2个字符开始可以是数字。在C语言中名字是区分大小写的。可以用下划线“_”,但是不可以用横杠“-”。名字不能跟C编译系统已经征用的关键字重名,比如不能用“unsigned ,char,static”等系统关键词,跟古代时不能跟皇帝重名一样,要避尊者讳。

54.7   练习程序。】

        写一个简单的除法函数,在除法运算中,除数不能为0,如果发现除数为0,就立即退出当前函数,并且返回运算结果默认为0最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始---------------------------------------------*/
  2. //函数的声明。
  3. unsigned int chu_fa(unsigned int bei_chu_shu,unsigned int chu_shu);
  4. unsigned int a;//此变量用来接收除法的运算结果。
  5. unsigned int b;//此变量用来接收除法的运算结果。
  6. //函数的定义。
  7. unsigned int chu_fa(unsigned int bei_chu_shu,unsigned int chu_shu)
  8. {
  9.     unsigned int shang;  //返回的除法运算结果:商。
  10.     if(0==chu_shu)   //如果除数等于0,就立即退出当前函数,并返回0
  11.     {
  12.         return 0; // 退出当前函数并且返回0.此时后面的代码不会被执行。
  13.     }
  14.    
  15.     shang=bei_chu_shu/chu_shu;  //除法运算的算法
  16.     return shang;  //返回最后的运算结果:商。并且退出当前函数。
  17. }
  18. void main() //主程序
  19. {
  20.    
  21.     a=chu_fa(128,0);  //函数调用。128除以0,把商返回给a变量。
  22.     b=chu_fa(128,2);  //函数调用。128除以2,把商返回给b变量。
  23.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示。
  24.     GuiWdData1=b;   //把b这个数值放到窗口变量1里面显示。
  25.         
  26. /*---C语言学习区域的结束---------------------------------------------*/
  27.    while(1)  
  28.    {
  29.       initial();
  30.       key_service();
  31.       display_service();
  32.    }
  33. }
复制代码
        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
       变量a为0。
       变量b为64。

       下节预告:static静态局部变量在函数中的重要作用。
(未完待续)


乐于分享,勇于质疑!
64#
 楼主| 发表于 2016-1-17 01:58:36 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-17 11:40 编辑

第五十五节:static静态局部变量在函数中的重要作用。
第五十五节_pdf文件.pdf (144.04 KB, 下载次数: 2405)

【55.1   变量前加入static关键词后发生“化学反应”】


          前面介绍了两类变量,一类全局变量,一类局部变量。定义时,在任何一类变量前面加入static关键词,变量原有的特性都会发生某些变化,此时static像化学里的催化剂,具有神奇的功能。加static关键词的书写格式如下:
  1.      static unsigned char a;  //全局变量前加的static关键词
  2.      void hanshu(void)
  3.      {
  4.          static unsigned char i;   //局部变量前加的static关键词
  5.      }
复制代码


【55.2   在全局变量前加static】


          static读作“静态”,全局变量前加static,称为静态全局变量。静态全局变量和普通全局变量的功能大体相同,仅在有效范围(作用域)方面有差异。假设整个工程有多个文件组成,普通全局变量的有效范围能覆盖全部文件,在任何一个文件里都畅通无阻。而静态全局变量只能在当前定义的那个文件里起作用,活动范围完全被限定在一个文件,彷佛被加了紧箍咒,由不得你任性。这部分的内容有个大致印象就可以,暂时不用深入研究,等以后学到多文件编程时再关注,因为我当前的程序例子只有一个源文件,还没涉及多文件编程。

【55.3   在局部变量前加static】

         这是本节重点。我常把局部变量比喻宾馆的客房,客人入住时被分配在哪间客房是随机临时安排的,第二天退房时宾馆会把客房收回继续分配给下一位其他的客人,是临时公共区。而加入static后的局部变量,发生了哪些变化?加入static后的局部变量,称为静态局部变量。静态局部变量就像宾馆的VIP客户,VIP客户财大气粗,把宾馆分配的客房永远包了下来,永远不许再给其它客人入住。总结了静态局部变量的两个重要特性:
        第一个,静态局部变量不会在函数调用时被初始化,它只在单片机刚上电时被编译器初始化了一次,因为它的内存模型不是被分配在“栈”,而是在全局变量同一类数据区,拥有自己唯一的地址。但是跟全局变量又有差别,全局变量的有效范围(作用域)是整个工程,而静态局部变量毕竟是“局部”,仅局限于当前函数。而普通局部变量,众所周知,每次被函数调用时,都会被重新初始化。
        第二个,每次函数调用时,静态局部变量比普通局部变量少开销一条潜在的“初始化语句”,原因是普通局部变量每次被函数调用时都要重新初始化,而静态局部变量不用进行这个操作。也就是说,静态局部变量比普通局部变量的效率高一点,虽然这个“点”的时间开销微不足道,但是不留意这“点”,写程序时容易出现瑕疵。


【55.4   静态局部变量的应用场合】


        静态局部变量适用在那些频繁调用的函数,比如main函数主循环while(1)里直接调用的所有函数,还有以后讲到的定时器中断函数,等等。因为静态局部变量每次被调用都不会被重新初始化,用在这类函数时就省去了每次初始化语句的时间。还有就是那些规定不能被函数初始化的场合,比如在很多用switch搭建的程序函数里,这类switch程序俗称为状态机思路。


【55.5   能用全局变量替代静态局部变量吗】


         能用全局变量替代静态局部变量吗?能。哪怕在整个程序里全部用全局变量都可以。全局变量是一把牛刀,什么场合都用牛刀虽然也能解决问题,但是显得鲁莽没有条理。尽量把全局变量,普通局部变量,静态局部变量各自优势充分发挥出来才是编程之道。能用局部变量的尽量用局部变量,这样可以减少全局变量的使用。当局部变量帮分担一部分工作时,最后全局变量只起到一个作用,那就是在各函数之间传递信息。局部变量与全局变量的分工定位明确了,程序代码阅读起来就没有那么凌乱,思路也清晰很多。


【55.6   程序分析】

         编写一个程序来学习刚才讲到的内容,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,添加和修改main程序代码如下:
  1. /*---C语言学习区域的开始-----------------------------------------*/
  2. unsigned char hanshu(void);         //函数声明
  3. unsigned char hanshu_static(void);  //函数声明
  4. unsigned char hanshu(void)  //函数定义
  5. {
  6.    unsigned char i=0;   //普通局部变量,每次函数调用都被初始化为0.
  7.    i++;  //i自加1
  8.    return i;
  9. }
  10. unsigned char hanshu_static(void)  //函数定义
  11. {
  12.    static unsigned char i=0;   //静态局部变量,只在上电是此初始化语句才起作用。
  13.    i++;  //i自加1
  14.    return i;
  15. }
  16. void main() //主程序
  17. {
  18.   unsigned char a; //用来接收函数返回的结果。
  19.   unsigned char b;
  20.   unsigned char c;
  21.   unsigned char d; //用来接收函数返回的结果。
  22.   unsigned char e;
  23.   unsigned char f;
  24.   //下面函数内的i是普通局部变量。
  25.   a=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以a等于1。
  26.   b=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以b等于1。
  27.   c=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以c等于1。
  28.   //下面函数内的i是静态局部变量,第一次上电后默认为0,就不会再被初始化,
  29.   d=hanshu_static(); //d由0自加1后等于1。
  30.   e=hanshu_static(); //e由1自加1后等于2。
  31.   f=hanshu_static(); //f由2自加1后等于3。
  32.      GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示,下同。
  33.   GuiWdData1=b;  
  34.   GuiWdData2=c;  
  35.   GuiWdData3=d;
  36.   GuiWdData4=e;  
  37.   GuiWdData5=f;  
  38.         
  39. /*---C语言学习区域的结束----------------------------------------*/
  40.    while(1)  
  41.    {
  42.       initial();
  43.       key_service();
  44.       display_service();
  45.    }
  46. }
  47.       
复制代码


         查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0上坚鸿51学习板观察程序执行的结果如下:
         变量a为1。
         变量b为1。
         变量c为1。

         变量d为1。
         变量e为2。
         变量f 为3。

【55.7   为什么中止此连载帖的更新了】

    我以前一直想写两本书,一本讲单片机入门基础,一本讲单片机程序框架。现在发现,单片机基础和程序框架并没有明显的分水岭,基础中有框架,框架中有基础,应该合二为一,读起来才会连贯舒畅。所以我决定中止当前已写到55节的《从业十年,教你单片机入门基础》连载帖,另外新开一个连载帖叫《从单片机基础到程序框架(连载)》,希望大家关注我的新连载帖。
    再提一下我2014年写的《从业将近十年,手把手教你单片机程序框架》,一方面受到很多网友的好评,另一方面也有一些热心网友提出了宝贵的意见,我今天看来,确实还有一些可待改进的地方。本来计划在2017年重写《……单片机程序框架》那个老帖,现在看来不用那么折腾了,只要把《……单片机程序框架》的内容也整合到新开的帖子里就可以了,这样对我也比较省事。我的时间计划是,先花4年时间写一
个初稿,然后再花2年时间重写一次,最后再花1年时间整理成书,整个过程大概7年时间左右,今年是2016年,估计到2023年左右就可以新书出版了。
    感谢各位朋友的支持。






乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-5-5 23:00 , Processed in 0.215719 second(s), 24 queries .

快速回复 返回顶部 返回列表