发新帖本帖赏金 10.00元(功能说明)我要提问
返回列表
打印
[51单片机]

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

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
201
jianhong_wu|  楼主 | 2016-1-4 07:19 | 只看该作者 回帖奖励 |倒序浏览
本帖最后由 jianhong_wu 于 2016-1-4 07:23 编辑

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


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

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


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


unsigned char a;  //此变量用来接收最后相加结果的和。
unsigned char g=2;
unsigned char h=3;
void hanshu(void)  //无输出无输入函数的定义。
{
   a=g+h;
}
main()
{
    hanshu();  //函数调用时的样子。
}


分析:
         void hanshu(void),此函数名的前缀是void,括号内也是void,属于无输出无输入函数。这类函数表面看是无输出无输入,其实在实际应用中也可以通过全局变量来输入输出,比如上面的例子就是靠a,g,h这三个全局变量来传递信息,只不过表达方式上像隐藏起来一样没有那么直观。


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


unsigned char b;  //此变量用来接收最后相加结果的和。
void hanshu(unsigned char i,unsigned char k)   //无输出有输入函数的定义
{
   b=i+k;
}
main()
{
    hanshu(2,3);  //函数调用时的样子。
}


分析:
        void hanshu(unsigned char i,unsigned char k),此函数名的前缀是void,括号内是(unsigned char i,unsigned char k),属于无输出有输入的函数。括号的两个变量ik是函数内的局部变量,也是跟对外的桥梁接口,它们有一个专业的名称叫形参。外部要调用此函数时,只要给括号填入对应的变量或者数值,这些变量和数值就会被复制一份传递给作为函数形参的局部变量,从而外部调用者跟函数内部就发生了数据信息的传递。这种书写方式的特点是把输入接口封装了出来。




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


unsigned char c;   //此变量用来接收最后相加结果的和。
unsigned char m=2;
unsigned char n=3;
unsigned char hanshu(void)     //有输出无输入函数的定义。
{
unsigned char p;
p=m+n;
return p;
}
main()
{
    c= hanshu();  //函数调用时的样子。
}


分析:
        unsigned char hanshu(void),此函数名的前缀是unsigned char类型,括号内是void,属于有输出无输入的函数。函数前缀的unsigned char表示此函数最后退出时会返回一个unsigned char类型的数据给外部调用者。而且这类函数内部必须有一个return语句配套,表示立即退出当前函数并且返回某个变量或者常量的数值给外部调用者。这种书写方式的特点是把输出接口封装了出来。



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


unsigned char d;    //此变量用来接收最后相加结果的和。
unsigned char hanshu(unsigned char r,unsigned char s)     //有输出无输入函数的定义
{
unsigned char t;
t=r+s;
return t;
}
main()
{
    d= hanshu(2,3);  //函数调用时的样子。
}


分析:
         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程序代码如下:


/*---C语言学习区域的开始---------------------------------------------------------------------------*/
//第1种:无输出无输入函数的声明。
void hanshu_1(void);  
//第2种:无输出有输入函数的声明。
void hanshu_2(unsigned char i,unsigned char k);   
//第3种:有输出无输入函数的声明。
unsigned char hanshu_3(void);     
//第4种:有输出无输入函数的声明。
unsigned char hanshu_4(unsigned char r,unsigned char s);  
//第1种:无输出无输入
unsigned char a;  //此变量用来接收最后相加结果的和。
unsigned char g=2;
unsigned char h=3;
//第2种:无输出有输入
unsigned char b;  //此变量用来接收最后相加结果的和。
//第3种:有输出无输入
unsigned char c;   //此变量用来接收最后相加结果的和。
unsigned char m=2;
unsigned char n=3;
//第4种:有输出有输入
unsigned char d;    //此变量用来接收最后相加结果的和。
//第1种:无输出无输入函数的定义。
void hanshu_1(void)  
{
   a=g+h;
}
//第2种:无输出有输入函数的定义
void hanshu_2(unsigned char i,unsigned char k)   
{
   b=i+k;
}
//第3种:有输出无输入函数的定义。
unsigned char hanshu_3(void)   
{
unsigned char p;
p=m+n;
return p;
}
//第4种:有输出无输入函数的定义
unsigned char hanshu_4(unsigned char r,unsigned char s)  
{
unsigned char t;
t=r+s;
return t;
}
void main() //主程序
{
    hanshu_1();       //第1种:无输出无输入函数。
    hanshu_2(2,3);      //第2种:无输出有输入函数。
    c=hanshu_3();      //第3种:有输出无输入函数。
    d=hanshu_4(2,3);     //第4种:有输出无输入函数。
    GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示。
    GuiWdData1=b;   //把b这个数值放到窗口变量1里面显示。
    GuiWdData2=c;   //把c这个数值放到窗口变量2里面显示。
    GuiWdData3=d;   //把d这个数值放到窗口变量3里面显示。
        
/*---C语言学习区域的结束---------------------------------------------------------------------------*/
   while(1)  
   {
      initial();
      key_service();
      display_service();
   }
}

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

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






使用特权

评论回复
202
lk040411| | 2016-1-4 22:01 | 只看该作者
楼主讲解很仔细,实在太感谢了。:handshake

使用特权

评论回复
203
xu_jinjing| | 2016-1-7 14:22 | 只看该作者
能不能写的再深一点,不错的

使用特权

评论回复
204
jianhong_wu|  楼主 | 2016-1-10 01:31 | 只看该作者
第五十四节:return语句在函数中的作用以及容易被忽略的四个功能。
第五十四节_pdf文件.pdf (117.79 KB)

【54.1   return深入讲解

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

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

       下面的书写格式是合法的:

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

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

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

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

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

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

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

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

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

54.7   练习程序。】

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

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


使用特权

评论回复
205
Gotham| | 2016-1-10 15:16 | 只看该作者
楼主写的非常好!能不能开辟某个地方,集中在一起看。也方便调整字体大小,方便随时拜读。

使用特权

评论回复
206
952212739| | 2016-1-15 10:15 | 只看该作者
学习了!

使用特权

评论回复
207
jianhong_wu|  楼主 | 2016-1-17 11:24 | 只看该作者
本帖最后由 jianhong_wu 于 2016-1-17 11:41 编辑

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


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


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

【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程序代码如下:
/*---C语言学习区域的开始-----------------------------------------*/
unsigned char hanshu(void);         //函数声明
unsigned char hanshu_static(void);  //函数声明
unsigned char hanshu(void)  //函数定义
{
   unsigned char i=0;   //普通局部变量,每次函数调用都被初始化为0.
   i++;  //i自加1
   return i;
}
unsigned char hanshu_static(void)  //函数定义
{
   static unsigned char i=0;   //静态局部变量,只在上电是此初始化语句才起作用。
   i++;  //i自加1
   return i;
}
void main() //主程序
{
  unsigned char a; //用来接收函数返回的结果。
  unsigned char b;
  unsigned char c;
  unsigned char d; //用来接收函数返回的结果。
  unsigned char e;
  unsigned char f;
  //下面函数内的i是普通局部变量。
  a=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以a等于1。
  b=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以b等于1。
  c=hanshu();  //函数内的i每次重新初始化为0,再自加1,所以c等于1。
  //下面函数内的i是静态局部变量,第一次上电后默认为0,就不会再被初始化,
  d=hanshu_static(); //d由0自加1后等于1。
  e=hanshu_static(); //e由1自加1后等于2。
  f=hanshu_static(); //f由2自加1后等于3。
     GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示,下同。
  GuiWdData1=b;  
  GuiWdData2=c;  
  GuiWdData3=d;
  GuiWdData4=e;  
  GuiWdData5=f;  
        
/*---C语言学习区域的结束----------------------------------------*/
   while(1)  
   {
      initial();
      key_service();
      display_service();
   }
}

        查看运算结果的方法。如何在坚鸿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年左右就可以新书出版了。
    感谢各位朋友的支持。





使用特权

评论回复
208
wrr360661326| | 2016-1-18 14:46 | 只看该作者
写的太好了~接触单片机也有几年了,但总感觉太低端了,总有一天会被淘汰的样纸

使用特权

评论回复
209
乐逍遥6| | 2016-1-19 10:41 | 只看该作者
又来看看 再顶

使用特权

评论回复
210
zhanghoub| | 2016-1-19 21:04 | 只看该作者
market收藏一下!

使用特权

评论回复
211
kn988| | 2016-1-19 21:10 | 只看该作者
已读四节,慢慢消化。
接下来再读。

使用特权

评论回复
212
liuzc879| | 2016-2-12 22:17 | 只看该作者
:handshake:handshake

使用特权

评论回复
213
zhanghoub| | 2016-2-15 15:06 | 只看该作者
能把自己的知识分享出来也是人生的一大成功!

使用特权

评论回复
214
64xiaodian| | 2016-3-7 14:21 | 只看该作者
写的真不错楼主,大致看了一遍

使用特权

评论回复
215
great1949| | 2016-5-1 14:07 | 只看该作者
谢谢楼主分享

使用特权

评论回复
216
21ic小能手| | 2016-5-9 16:27 | 只看该作者
持续关注中~

使用特权

评论回复
217
yddjz| | 2017-4-28 00:32 | 只看该作者
什么时候再写?

使用特权

评论回复
218
新手华仔| | 2017-11-21 17:22 | 只看该作者
shenxfs 发表于 2015-7-7 07:55
初看教程感觉还可以,但仔细阅读还是有些不对,误人子弟之嫌。
首先main函数书写就有问题:void main(),函 ...

你走吧,不欢迎你,乱喷

使用特权

评论回复
219
guaxiaodai11| | 2019-10-8 22:48 | 只看该作者
做个记号,慢慢学习

使用特权

评论回复
220
a540192739| | 2019-11-5 19:48 | 只看该作者
刚开始学还有好多不懂

使用特权

评论回复
发新帖 本帖赏金 10.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则