打印
[51单片机]

从单片机基础到程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
241
一直跟进  楼主辛苦

使用特权

评论回复
242
jianhong_wu|  楼主 | 2017-2-26 12:22 | 只看该作者
第五十八节: const(或code)在定义数据时的作用。
第五十八节_pdf文件.pdf (90.38 KB)
【58.1   const与code的关系。】

       const与code都是语法的修饰关键词,放在所定义的数据前面时有“不可更改”之意。在C语言语法中,const像普通话全国通用,是标准的语言;而code像地方的方言,仅仅适合针对51单片机的C51编译器环境。而其它大多数单片机的C编译器并不支持code,只支持const。比如PIC,stm32等单片机的C编译器都是只认const而不认code的。通常情况下,const定义的数据都是放在ROM的,但是51单片机的C51编译器是例外,它并不把const定义的数据放在ROM区,只有用code关键词时它才会把数据放在ROM区,这一点相对其它大多数的单片机来说是不一样的。因为本教程是用51单片机的C51编译器,所以用code来替代const。本节教程所提到的const,在实际编程时都用code来替代。

【58.2   const(或code)在定义数据时的终极目的。】

       在数据定义分配的应用中,const的终极目的是为了节省RAM的开销。从“读”和“写”的角度分析,数据有两种:“能读能写”和“只能读”这两种。 “能读能写”的数据占用RAM内存,叫变量,C语言语法上定义此类数据时“无”const前缀。 “只能读”的数据占用ROM内存,叫常量, C语言语法上定义此类数据时“有”const前缀。单片机的ROM容量比RAM容量往往大几十倍甚至上百倍,相比之下,RAM的资源显得比较稀缺。因此,把某些只需“读”而不需“写”的数据定义成const放在ROM,就可以节省RAM的开销。

【58.3   const(或code)的应用场合。】

        const可以定义单个常量,也可以定义常量数组。定义单个常量时,通常应用在某个出现在程序多处并且需要经常调整的“阀值”参数,方便“一键更改”的操作。所谓“一键更改”,就是只要改一次const所定义初始化的某个常量,整个程序多次出现的此常量就自动更改了。定义常量数组时,通常应用在某个数据转换表,把某些固定的常量预先放到常量数组,通过数组的下标来“查表”。

【58.4   const(或code)的语法格式。】

        定义单个常量和常量数组时的语法是以下这个样子的:
const unsigned char x=10;  //定义单个常量。加了const。
const unsigned char y[12]={31,28,31,30,31,30,31,31,30,31,30,31}; //定义常量数组。加了const。


【58.5   const(或code)的“能读”和“不可写”概念】

        所谓“读”和“写”的能力,其实就是看某数能在赋值符号“=”的“右边”还是“左边”的能力。普通的变量,既可以在赋值符号“=”的“右边”(能读),也可以在赋值符号“=”的“左边”(能写)。比如,下面的写法是合法的:
unsigned char k=1;  //这是普通的变量,无const前缀。
unsigned char n=2;  //这是普通的变量,无const前缀。
n=k; //k出现在赋值符号“=”的右边,表示能读。合法。
k=n; //k出现在赋值符号“=”的左边,表示能写,可更改之意。合法。


        但是如果一旦在普通的变量前面加了const(或code)关键词,就会发生“化学变化”,原来的“变量”就变成了“常量”,常量只能“读”,不能“写”。比如:
const unsigned char c=1;  //这是常量,有const前缀。
unsigned char n=2;  //这是普通的变量,无const前缀。
n=c; //c是常量,能读,这是合法的。这行代码是语**确的。
c=n; //c是常量,不能写,这是非法的,C编译器不通过。这行代码是语法错误的。


【58.6   const(或code)能在函数内部定义吗?】

       const(或code)能在函数内部定义吗?能。语法是允许的。当在函数内部定义数据成const(或者code),在数据的存储结构上,数据也是放在ROM区的(实际上在51单片机里想把数据放在ROM只能用code而不能用const),把数据定义在函数内部,就只能在这个函数里面用,不能被其它函数调用。在作用域的问题上,const(或者code)的常量数据跟其它变量的数据是一样的。比如:
void HanShu(void)
{
    const unsigned char c=1;  //在函数内部定义的const常量也是放在ROM区存储。
    unsigned char n=2;  
    n=c; //c是常量,在函数内部定义,只能在当前这个HanShu函数里调用。
}


【58.7   例程练习和分析。】

       本教程使用的是51单片机的C51编译器,编写程序时为了让常量数据真正存储在ROM区,因此,本教程的程序例子都是用code替代const。
本例程讲两个例子,一个是单个常量,一个是常量数组。
      (1)单个常量。举的例子是“阀值”的“一键更改”应用。根据考试的分数,分两个等级。凡是大于或者等于90分的就是“优”,串口助手输出显示“1”。凡是小于90分的就是“良”,串口助手输出显示“0”。这里的“90分”就是我所说的“阀值”概念,只要用一个const定义一个常量数据来替代“90”,当需要调整“阀值”时,只要更改一次此定义的常量数值就可以达到“一键更改”之目的。
      (2)常量数组。举的例子是,查询2017年12个月的某个月的总天数,用两种思路实现,一种是switch分支语句来实现,另一种是const常量数组的“查表”思路来实现。通过对比这两种思路,你会发现const常量数组在做“转换表”这类“查表”项目时的强大优越性。

/*---C语言学习区域的开始。-----------------------------------------------*/

//函数的声明。
unsigned char HanShu_switch(unsigned char u8Month);        
unsigned char HanShu_const(unsigned char u8Month);  

//数据的定义。
code unsigned char Cu8Level=90;  //需要调整“阀值”时,只需更改一次这里的“90”这个数值。
code unsigned char Cu8MonthBuffer[12]= //每个月对应的天数。从数组下标0开始,0代表1月...
{31,28,31,30,31,30,31,31,30,31,30,31};


unsigned char a; //用来接收函数返回的结果。
unsigned char b;
unsigned char c;
unsigned char d;

//函数的定义。
unsigned char HanShu_switch(unsigned char u8Month)  //用switch分支来实现。
{
   switch(u8Month)
   {
      case 1:   //1月份的天数
           return 31;
      case 2:   //2月份的天数
           return 28;
      case 3:   //3月份的天数
           return 31;
      case 4:   //4月份的天数
           return 30;
      case 5:   //5月份的天数
           return 31;
      case 6:   //6月份的天数
           return 30;
      case 7:   //7月份的天数
           return 31;
      case 8:   //8月份的天数
           return 31;
      case 9:   //9月份的天数
           return 30;
      case 10:  //10月份的天数
           return 31;
      case 11:  //11月份的天数
           return 30;
      case 12:  //12月份的天数
           return 31;
      default:  //万一输错了其它范围的月份,就默认返回30天。
          return 30;
   }
    }

unsigned char HanShu_const(unsigned char u8Month) //用const常量数组的“查表”来实现。
{
   unsigned char u8GetDays;
   u8Month=u8Month-1;  //因为数组下标是从0开始,0代表1月份,1代表2月份。所以减去1。
  u8GetDays=Cu8MonthBuffer[u8Month]; //这就是查表,马上获取常量数组表格里固定对应的天数。
  return u8GetDays;
    }

    void main() //主函数
{
    //第(1)个例子
    if(89>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       a=1;
    }
    else  //否则输出0。
    {
       a=0;
    }

    if(95>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       b=1;
    }
    else  //否则输出0。
    {
       b=0;
    }

    //第(2)个例子
    c=HanShu_switch(2);  //用switch分支的函数获取2月份的总天数。
    d=HanShu_const(2);   //用const常量数组“查表”的函数获取2月份的总天数。

    View(a);  //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);  //把第2个数b发送到电脑端的串口助手软件上观察。
    View(c);  //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);  //把第4个数d发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:0
十六进制:0
二进制:0

第2个数
十进制:1
十六进制:1
二进制:1

第3个数
十进制:28
十六进制:1C
二进制:11100

第4个数
十进制:28
十六进制:1C
二进制:11100


分析:
       a为0。
       b为1。
       c为28。
       d为28。

【58.8   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
评论
zhangtao3b608 2021-5-15 16:47 回复TA
总算搞明白了,谢谢 
评分
参与人数 1威望 +2 收起 理由
zhangtao3b608 + 2 很给力!
243
penghan| | 2017-2-27 22:23 | 只看该作者
谢谢楼主,收益很多。

使用特权

评论回复
244
srxz| | 2017-3-1 14:34 | 只看该作者
楼主 你的博客还更新吗  一定别删掉了 我才跟着学习到第七节 收货很多谢谢

使用特权

评论回复
245
jianhong_wu|  楼主 | 2017-3-5 13:25 | 只看该作者
第五十九节: 全局“一键替换”功能的#define。
第五十九节_pdf文件.pdf (93.7 KB)
【59.1   #define作用和书写格式。】

       上一节讲const的时候,讲到了当某个常量在程序中是属于需要频繁更改的“阀值”的时候,用const就可以提供“一键更改”的快捷服务。本节的#define也具有此功能,而且功能比const更加强大灵活,它除了可以应用在常量,还可以应用在运算式以及函数的“一键更改”中。所谓“一键更改”,其实是说,#define内含了“替换”的功能,此“替换”跟word办公软件的“替换”功能几乎是一模一样的。#define的“替换”功能,除了在某些场合起到“一键更改”的作用,还可以在某些场合,把一些在字符命名上不方便阅读理解的常量、运算式或函数先“替换”成容易理解的字符串,让程序阅读起来更加清晰更加方便维护。#define的常见三种书写格式如下:

#define  字符串  常量     //注意,这里后面没有分号“;”
#define  字符串  运算式   //注意,这里后面没有分号“;”
#define  字符串  函数     //注意,这里后面没有分号“;”


        具体一点如下:

#define  AA  1        //常量
#define  BB  (a+b+c)  //运算式
#define  C   add()    //函数


        需要注意的时候,#define后面没有分号“;”,因为它是C语言中的“预处理”的语句,不是单片机运行的程序指令语句。

【59.2   #define的编译机制。】

        #define是属于“预编译”的指令,所谓“预编译”就是在“编译”之前就开始的准备工作。编译器在正式编译某个源代码的时候,先进行“预编译”的准备工作,对于#define语句,编译器是直接把#define要替换的内容先在“编辑层面”进行机械化替换,这个“机械化替换”纯粹是字符串的替换,可以理解成word办公软件的“替换”编辑功能。比如以下程序:

#define A 3
#define B (2+6)   //有括号
#define C  2+6     //无括号
unsigned long x=3;
unsigned long a;
unsigned long b;
unsigned long c;
void main() //主函数
{
    a=x*A;
    b=x*B;
    c=x*C;
    while(1)  
    {
    }
}


        经过编译器“预编译”的“机械化替换”后,等效于以下代码:

unsigned long x=3;
unsigned long a;
unsigned long b;
unsigned long c;
void main() //主函数
{
    a=x*3;
    b=x*(2+6);
    c=x*2+6;
    while(1)  
    {
    }
}


【59.3   #define在常量上的“一键替换”功能。】

        上一节讲const(或code)的时候,举了一个“阀值”常量的例子,这个例子可以用#define来替换等效。比如,原来const(或code)的例子如下:


code unsigned char Cu8Level=90;  //需要调整“阀值”时,只需更改一次这里的“90”这个数值。

unsigned char a;
unsigned char b;
    void main() //主函数
{
    if(89>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       a=1;
    }
    else  //否则输出0。
    {
       a=0;
    }
    if(95>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       b=1;
    }
    else  //否则输出0。
    {
       b=0;
    }
    while(1)  
    {
    }
}


       上述程序现在用#define来替换,等效如下:


#define Cu8Level 90  //需要调整“阀值”时,只需更改一次这里的“90”这个数值。

unsigned char a;
unsigned char b;
    void main() //主函数
{
    if(89>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       a=1;
    }
    else  //否则输出0。
    {
       a=0;
    }
    if(95>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       b=1;
    }
    else  //否则输出0。
    {
       b=0;
    }
    while(1)  
    {
    }
}


【59.4   #define在运算式上的“一键替换”功能。】

       #define在运算式上应用的时候,有一个地方要特别注意,就是必须加小括号“()”,否则容易出错。因为#define的替换是很“机械呆板”的,它只管“字符编辑层面”的机械化替换,举一个例子如下:

#define B (2+6)   //有括号
#define C  2+6     //无括号
unsigned long x=3;
unsigned long b;
unsigned long c;
void main() //主函数
{
    b=x*B;  //等效于b=x*(2+6),最终运算结果b等于24。因为3乘以8(2加上6等于8)。
    c=x*C;  //等效于c=x*2+6,  最终运算结果c等于12。因为3乘以2等于6,6再加6等于12。
    while(1)  
    {
    }
}


        上述例子中,“有括号”与“没括号”的运算结果差别很大,第一个是24,第二个是12。具体的分析已经在源代码的注释了。

【59.5   #define在函数上的“一键替换”功能。】

        #define的应用很广,也可以应用在函数的“替换”上。例子如下:


void add(void);  //函数的声明。
void add(void)   //函数的定义。
{
   a++;
}

#define a_zi_jia  add()  //用字符串a_zi_jia来替代函数add()。

    unsigned long a=1;
void main() //主函数
{
    a_zi_jia;  //这里相当于调用函数add()。
    while(1)  
    {
    }
}



【59.6   #define在常量后面添加U或者L的特殊写法。】

       有些初学者今后可能在工作中遇到#define以下这种写法:

#define  字符串  常量U
#define  字符串  常量L


        具体一点如下:

#define  AA  6U
#define  BB  6L


        常量加后缀“U”或者“L”有什么含义呢?字面上理解,U表示该常量是无符号整型unsigned int;L表示该常量是长整型long。但是在实际应用中这样“多此一举”地去强调某个常量的数据类型有什么意义呢?我自己私下也做了一些测试,目前我本人暂时还没有发现这个秘密的答案。所以对于这个问题,初学者现在只要知道这种写法在语法上是合法的就可以,至于它背后有什么玄机,有待大家今后更深的发掘。

【59.7   #define省略常量的特殊写法。】

        有些初学者今后在多文件编程中,在某些头文件.h中,会经常遇到以下这类代码:
#ifndef _AAA_
#define _AAA_
#endif

        其中第2行代码“#define _AAA_”后面居然没有常量,这样子的写法也行,到底是什么意思?在这类写法中,当字符串“_AAA_”后面省略了常量的时候,编译器默认会给_AAA_添加一个“非0”的常量,也许是1或者其它“非0”的值,多说一句,所谓“非0”值就是“肯定不是0”。上述代码等效于:
#ifndef _AAA_
#define _AAA_ 1  //编译器会在这类默认添加一个1或者其它“非0”的常量
#endif


        这个知识点大家只要先有一个感性的认识即可,暂时不用深入了解。

【59.8   例程练习和分析。】

        现在编一个练习程序来熟悉#define的用法。

/*---C语言学习区域的开始。-----------------------------------------------*/

    //第1个:常量的例子
    #define Cu8Level 90  //需要调整“阀值”时,只需更改一次这里的“90”这个数值。
    unsigned char a;
    unsigned char b;

    //第2个:运算式的例子
        #define C (2+6)   //有括号
        #define D  2+6    //无括号
    unsigned char x=3;
    unsigned char c;
    unsigned char d;

    //第3个:函数的例子
    unsigned char e=1;
    void add(void);
    void add(void)   
    {
       e++;
    }
    #define a_zi_jia  add()  //用字符串a_zi_jia来替代函数add()。


void main() //主函数
{

    //第1个:常量的例子
    if(89>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       a=1;
    }
    else  //否则输出0。
    {
       a=0;
    }
    if(95>=Cu8Level)  //大于或者等于阀值,就输出1。
    {
       b=1;
    }
    else  //否则输出0。
    {
       b=0;
    }


    //第2个:运算式的例子
    c=x*C;  //等效于c=x*(2+6),最终运算结果c等于24。因为3乘以8(2加上6等于8)。
    d=x*D;  //等效于d=x*2+6,  最终运算结果d等于12。因为3乘以2等于6,6再加6等于12。


    //第3个:函数的例子
    a_zi_jia;  //这里相当于调用函数add()。e从1自加到2。
    a_zi_jia;  //这里相当于调用函数add()。e从2自加到3。


    View(a);  //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);  //把第2个数b发送到电脑端的串口助手软件上观察。
    View(c);  //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);  //把第4个数d发送到电脑端的串口助手软件上观察。
    View(e);  //把第5个数e发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:0
十六进制:0
二进制:0

第2个数
十进制:1
十六进制:1
二进制:1

第3个数
十进制:24
十六进制:18
二进制:11000

第4个数
十进制:12
十六进制:C
二进制:1100

第5个数
十进制:3
十六进制:3
二进制:11


分析:
         a为0。
         b为1。
         c为24。
         d为12。
         e为3。

【59.9   如何在单片机上练习本章节C语言程序?】

         直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
246
jianhong_wu|  楼主 | 2017-3-12 12:48 | 只看该作者
第六十节: 指针在变量(或常量)中的基础知识。
第六十节_pdf文件.pdf (92.17 KB)
【60.1   指针与普通变量的对比。】

       普通变量和指针都是变量,都要占用RAM资源。普通变量的unsigned char类型占用1个字节,unsigned  int类型占用2个字节,unsigned long类型占用4个字节。但是指针不一样,指针是一种特殊的变量,unsigned char*,unsigned int*,unsigned long*这三类指针都是一样占用4个字节。指针是普通变量的载体,平时我们处理普通变量,都是可以“直接”操作普通变量本身。而学了指针之后,我们就多一种选择,可以通过指针这个载体来“间接”操作某个普通变量。“直接”不是比“间接”更好更高效吗?为什么要用“间接”?其实在某些场合,指针的“间接”操作更加灵活更加高效,这个要看具体的应用。
指针既然是普通变量的“载体”,那么普通变量就是“物”。“载体”与“物”之间可以存在一对多的关系。也就是说,一个篮子(载体),可以盛放**蛋(物),也可以盛放青菜(物),也可以盛放水果(物)。
但是,在这里,一个篮子在一个时间段内,只能承载一种物品,如果想承载其它物品,必须先把当前物品“卸”下来,然后再“装”其它物品”。这里有两个关键动作“装”和“卸”,就是指针在处理普通变量时的“绑定”,某个指针与某个变量发生“绑定”,就已经包含了先“卸”后“装”这两个动作在其中。
        题外话多说一句,刚才提到,unsigned  int类型占用2个字节,这个是在C51编译器下的情况。如果是在stm32单片机的编译器下,unsigned  int类型是占用4个字节。而“凡是指针都是4个字节”,这个描述仅仅适用于32位以下的单片机编译器(包括8位的单片机),而在某些64位的PC机,指针可能是8个字节,这些内容大家只要有个大概的了解即可。

【60.2   指针的定义。】

        跟普通变量一样,指针也必须先定义再使用。为了与普通变量区分开来,指针在定义的时候多加了一个星号“*”,例子如下:

unsigned char* pu8;   //针对unsigned char类型变量的指针。凡是指针都是占4个字节!
unsigned int* pu16;   //针对unsigned int类型变量的指针。凡是指针都是占4个字节!
unsigned long* pu32;  //针对unsigned long类型变量的指针。凡是指针都是占4个字节!


        既然指针都是4个字节,为什么还要区分unsigned char*,unsigned int* pu16,unsigned long* pu32这三种类型?因为指针是为普通变量(或常量)而生,所以要根据普通变量(或常量)的类型定义对应的指针。

【60.3   指针与普通变量是如何关联和操作的?】

        指针在操作某个变量的时候,必须先跟某个变量关联起来,这里的关联就是“绑定”。“绑定”后,才可以通过指针这个“载体”来“间接”操作变量。指针与普通变量在“绑定”的时候,需要用到“&”这个符号。例子如下:

unsigned char* pu8;   //针对unsigned char类型变量的指针。凡是指针都是占4个字节!
unsigned char a=0;    //普通的变量。
pu8=&a;  //指针与普通变量发生关联(或者说绑定)。
*pu8=2;  //通过指针这个载体来处理a这个变量,此时a从原来的0变成了2。


【60.4   指针处理“批量数据”的基础知识。】

       之所以有通过载体来“间接”操作普通变量的存在价值,其中很重要的原因是指针在处理“批量数据”时特别给力,这里的“批量数据”是有条件的,要求这些数据的地址必须挨家挨户连起来的,不能是零零散散的“散户”,比如说,数组就是由一堆在RAM空间里地址连续的变量组合而成,指针在很多时候就是为数组而生的。先看一个例子如下:

unsigned char* pu8;   //针对unsigned char类型变量的指针。凡是指针都是占4个字节!
unsigned char Buffer[3];    //普通的数组,内含3个变量,它们地址是相连的。

pu8=&Buffer[0];  //指针与普通变量Buffer[0]发生关联(或者说绑定)。
*pu8=1;          //通过指针这个载体来处理Buffer[0]这个变量,此时Buffer[0]变成了1。

pu8=&Buffer[1];  //指针与普通变量Buffer[1]发生关联(或者说绑定)。
*pu8=2;          //通过指针这个载体来处理Buffer[1]这个变量,此时Buffer[1]变成了2。

pu8=&Buffer[2];  //指针与普通变量Buffer[2]发生关联(或者说绑定)。
*pu8=3;          //通过指针这个载体来处理Buffer[2]这个变量,此时Buffer[2]变成了3。



       分析:上述例子中,并没有体现出指针的优越性,因为数组有3个元素,居然要绑定了3次,如果数组有1000个元素,难道要绑定1000次?显然这样是繁琐低效不可取的。而要发挥指针的优越性,我们现在必须深入了解一下指针的本质是什么,指针跟普通变量发生“绑定”的本质是什么。普通变量由“地址”和“地址所装的数据”构成,指针是特殊的变量,它是由什么构成呢?其实,指针是由“地址”和“地址所装的变量(或常量)的地址”组成。很明显,一个重要的区别是,普通变量装的数据,而指针装的是地址。正因为指针装的是地址,所以指针可以有两种选择,第一种可以处理“装的地址”,第二种可以处理“装的地址的所在数据”,这两种能力,就是指针的精华和本质所在,也是跟普通变量的区别所在。那么指针处理“装的地址”的语法是什么样子的?请看例子如下:

unsigned char* pu8;   //针对unsigned char类型变量的指针。凡是指针都是占4个字节!
unsigned char Buffer[3];   //普通的数组,内含3个变量,它们地址是相连的。

pu8=&Buffer[0];  //处理“装的地址”。把 Buffer[0]变量的地址装在指针这个载体里。
*pu8=1;     //处理“装的地址的所在数据”。此时Buffer[0]变成了1。

pu8++;      //处理“装的地址”。这里是“地址”自加1,相当于指针此时装的是Buffer[1]的地址。
*pu8=2;     //处理“装的地址的所在数据”。此时Buffer[1]变成了2。

pu8++;      //处理“装的地址”。这里是“地址”自加1,相当于指针此时装的是Buffer[2]的地址。
*pu8=3;     //处理“装的地址的所在数据”。此时Buffer[2]变成了3。


      上述例子中,利用“地址”自加1的操作,省去了2条赋值式的“绑定”操作(比如像pu8=&Buffer[0]这类语句),因此“绑定”本质其实就是更改指针所装的“变量(或常量)的地址”的操作。此例子中虽然还没体现了出指针在数组处理时的优越性,但是利用指针处理“装的地址”这项功能,在实际项目中很容易发现它的好处。

【60.5   指针与数组关联(绑定)时省略“&和下标[0]”的写法。】

        指针与数组关联的时候,通常是跟数组的第0个元素的地址关联,此时,可以把数组的“&和下标[0]”省略,比如:
unsigned char* pu8;  
unsigned char Buffer[3];   
pu8=Buffer;     //此行代码省略了“&和下标[0]”,等效于pu8=&Buffer[0];


【60.6   带const关键字的常量指针。】

        指针也可以跟常量关联起来,处理常量,但是常量只能“读”不能“写”,所以通过指针操作常量的时候也是只能“读”不能“写”。操作常量的指针用const关键词修饰,强调此指针只有“读”的操作。例子如下:
const unsigned char* pCu8;   //常量指针
code char Cu8Buffer[3]={5,6,7};   //常量数组
unsigned char b;
unsigned char c;
unsigned char d;

pCu8=Cu8Buffer; //此行代码省略了“&和下标[0]”,等效于pCu8=&Cu8Buffer[0];
b=*pCu8;        //读“装的地址的所在数据”。b等于5。

pCu8++;         //所装的地址自加1,跟Cu8Buffer[1]关联
c=*pCu8;        //读“装的地址的所在数据”。c等于6。

pCu8++;         //所装的地址自加1,跟Cu8Buffer[2]关联
d=*pCu8;        //读“装的地址的所在数据”。d等于7。




【60.7   例程练习和分析。】

       现在编一个练习程序来熟悉指针的基础知识。

/*---C语言学习区域的开始。-----------------------------------------------*/

    unsigned char* pu8;        //针对unsigned char类型变量的指针。凡是指针都是占4个字节!
    unsigned char a=0;         //普通的变量。
    unsigned char Buffer[3];   //普通的数组,内含3个变量,它们地址是相连的。

const unsigned char* pCu8;   //常量指针
code char Cu8Buffer[3]={5,6,7};   //常量数组
unsigned char b;
unsigned char c;
unsigned char d;


void main() //主函数
{

    pu8=&a;  //指针与普通变量发生关联(或者说绑定)。
    *pu8=2;  //通过指针这个载体来处理a这个变量,此时a从原来的0变成了2。


    pu8=&Buffer[0];  //处理“装的地址”。把 Buffer[0]变量的地址装在指针这个载体里。
    *pu8=1;     //处理“装的地址的所在数据”。此时Buffer[0]变成了1。

    pu8++;      //处理“装的地址”。这里是“地址”自加1,相当于指针此时装的是Buffer[1]的地址。
    *pu8=2;     //处理“装的地址的所在数据”。此时Buffer[1]变成了2。

    pu8++;      //处理“装的地址”。这里是“地址”自加1,相当于指针此时装的是Buffer[2]的地址。
    *pu8=3;     //处理“装的地址的所在数据”。此时Buffer[2]变成了3。

pCu8=Cu8Buffer; //此行代码省略了“&和下标[0]”,等效于pCu8=&Cu8Buffer[0];
b=*pCu8;        //读“装的地址的所在数据”。b等于5。

pCu8++;         //所装的地址自加1,跟Cu8Buffer[1]关联
c=*pCu8;        //读“装的地址的所在数据”。c等于6。

pCu8++;         //所装的地址自加1,跟Cu8Buffer[2]关联
d=*pCu8;        //读“装的地址的所在数据”。d等于7。

View(a);          //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);          //把第2个数b发送到电脑端的串口助手软件上观察。
    View(c);          //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);          //把第4个数d发送到电脑端的串口助手软件上观察。
    View(Buffer[0]);  //把第5个数Buffer[0]发送到电脑端的串口助手软件上观察。
    View(Buffer[1]);  //把第6个数Buffer[1]发送到电脑端的串口助手软件上观察。
    View(Buffer[2]);  //把第7个数Buffer[2]发送到电脑端的串口助手软件上观察。


    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:5
十六进制:5
二进制:101

第3个数
十进制:6
十六进制:6
二进制:110

第4个数
十进制:7
十六进制:7
二进制:111

第5个数
十进制:1
十六进制:1
二进制:1

第6个数
十进制:2
十六进制:2
二进制:10

第7个数
十进制:3
十六进制:3
二进制:11


分析:
        a为2。
        b为5。
        c为6。
        d为7。
        Buffer[0]为1。
        Buffer[1]为2。
        Buffer[2]为3。

【60.8   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
247
renxiaolin| | 2017-3-13 14:48 | 只看该作者
我也要支持你

使用特权

评论回复
248
renxiaolin| | 2017-3-13 14:49 | 只看该作者
我也要支持你

使用特权

评论回复
249
trackerlin| | 2017-3-17 14:09 | 只看该作者
吴老师,向你好好学习

使用特权

评论回复
250
jianhong_wu|  楼主 | 2017-3-20 10:41 | 只看该作者
第六十一节: 指针的中转站作用,地址自加法,地址偏移法。
第六十一节_pdf文件.pdf (87.5 KB)
【61.1   指针与批量数组的关系。】

       指针和批量数据的关系,更像领导和团队的关系,领导是团队的代表,所以当需要描述某个团队的时候,为了表述方便,可以把由N个人组成的团队简化成该团队的一个领导,用一个领导来代表整个团队,此时,领导就是团队,团队就是领导。指针也一样,指针一旦跟某堆数据“绑定”了,那么指针就是这堆数据,这堆数据就是该指针,所以在很多PC上位机的项目中,往往也把指针称呼为“句柄”,字面上理解,就是一句话由N个文字组成,而“句柄”就是这句话的代表,实际上“句柄”往往是某一堆资源的代表。不管是把指针比喻成“领导”、“代表”还是“句柄”,指针在这里都有“中间站”这一层含义。

【61.2   指针在批量数据的“中转站”作用。】

       指针在批量数据处理中,主要是能节省代码容量,而且是非常直观的节省代码容量。为什么能节省代码容量?是因为可以把某些重复性的具体实现的功能封装成指针来操作,请看下面的例子:

       程序要求:根据一个选择变量Gu8Sec的值,要从三堆数据中选择对应的一堆数据放到数组Gu8Buffer里。当Gu8Sec等于1的时候选择第1堆,等于2的时候选择第2堆,等于3的时候选择第3堆。也就是“三选一”。

       第1种实现的方法:没有用指针,最原始的处理方式。如下:
code unsigned char Cu8Memory_1[3]={1,2,3};  //第1堆数据
code unsigned char Cu8Memory_2[3]={4,5,6};  //第2堆数据
code unsigned char Cu8Memory_3[3]={7,8,9};  //第3堆数据

unsigned char Gu8Sec=2;  //选择的变量
unsigned char Gu8Buffer[3];  //根据变量来存放对应的某堆数据的数组
unsigned char i; //for循环用到的变量i

switch(Gu8Sec)  //根据此选择变量来切换到对应的操作上
{
    case 1:  //第1堆
          for(i=0;i<3;i++)   //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
          {
             Gu8Buffer[i]=Cu8Memory_1[i];  
          }
          break;

    case 2:  //第2堆
          for(i=0;i<3;i++)  //第2次出现for循环,用来实现“赋值”的“搬运数据”的动作。
          {
             Gu8Buffer[i]=Cu8Memory_2[i];
          }
          break;

    case 3:  //第3堆
          for(i=0;i<3;i++)   //第3次出现for循环,用来实现“赋值”的“搬运数据”的动作。
          {
             Gu8Buffer[i]=Cu8Memory_3[i];
          }
          break;

}


       分析:上述程序中,没有用到指针,出现了3次for循环的“赋值”的“搬运数据”的动作。

        第2种实现的方法:用指针作为“中间站”。如下:
code unsigned char Cu8Memory_1[3]={1,2,3};  //第1堆数据
code unsigned char Cu8Memory_2[3]={4,5,6};  //第2堆数据
code unsigned char Cu8Memory_3[3]={7,8,9};  //第3堆数据

unsigned char Gu8Sec=2;  //选择的变量
unsigned char Gu8Buffer[3];  //根据变量来存放对应的某堆数据的数组
unsigned char i; //for循环用到的变量i
const unsigned char *pCu8; //引入一个指针作为“中间站”

switch(Gu8Sec)  //根据此选择变量来切换到对应的操作上
{
    case 1:  //第1堆
          pCu8=&Cu8Memory_1[0];  //跟第1堆数据“绑定”起来。
          break;

    case 2:  //第2堆
          pCu8=&Cu8Memory_2[0];  //跟第2堆数据“绑定”起来。
          break;

    case 3:  //第3堆
          pCu8=&Cu8Memory_3[0];  //跟第3堆数据“绑定”起来。
          break;
}

for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
{
     Gu8Buffer[i]=*pCu8;  //把“指针所存的地址的数据”赋值给数组
     pCu8++;  //“指针所存的地址”自加1,为下一个数据的“赋值”的“搬运”作准备。
}


      分析:上述程序中,用到了指针作为中间站,只出现了1次for循环的“赋值”的“搬运数据”的动作。对比之前第1种方法,在本例子中,用了指针之后,程序代码看起来更加高效简洁清爽省容量。在实际项目中,数据量越大的时候,指针这种“优越性”就越明显。

【61.3   指针在书写上另外两种常用写法。】

       刚才61.2处第2个例子中,有一段代码如下:
 for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
{
     Gu8Buffer[i]=*pCu8;  //把“指针所存的地址的数据”赋值给数组
     pCu8++;  //“指针所存的地址”自加1,为下一个数据的“赋值”的“搬运”作准备。
}


       很多高手,喜欢把上面for循环内部的那两行代码简化成一行代码,如下:


for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
{
     Gu8Buffer[i]=*pCu8++;  //先把“数据”赋值给数组,然后“指针所存的地址”再自加1。
}


        上面这种写法也是合法的,而且在高手的代码中常见,据说也是最高效的写法。还有一种是利用“指针的偏移地址”的写法,我常用这种写法,因为感觉这种写法比较直观,而且跟数组的书写很像。如下:

 for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
{
     Gu8Buffer[i]=pCu8[i];  //这类是“偏移地址”的写法,i在这里相当于指针的偏移地址。
}


        这种写法也是跟前面那两种写法在程序实现的功能上是一样的,是等效的,我常用这种写法。

【61.4   指针的“地址自加法”和“地址偏移法”的差别。】

       刚才61.3处讲了3个例子,其中前面的两个例子都是属于“地址自加法”,而最后的那一个是属于“地址偏移法”。它们的根本差别是:“地址自加法”的时候,“指针所存的地址”是变动的;而“地址偏移法”的时候,“指针所存的地址”是不变的,“指针所存的地址”的“不变”的属性,就像某个原点,原点再加上偏移,就可以寻址到某个新的RAM地址所存的数据。例子如下:

       第1种:“地址自加法”:

 pCu8=&Cu8Memory_2[0];  //假设赋值后,此时“指针所存的地址”是RAM的地址4。
for(i=0;i<3;i++)  
{
     Gu8Buffer[i]=*pCu8++;  //先把“数据”赋值给数组,然后“指针所存的地址”再自加1。
}


       分析:上述代码,等程序执行完for循环后,指针所存的地址还是RAM地址4吗?不是。因为它是变动的,经过for循环,“指针所存的地址”自加3次后,此时“所存的RAM地址”从原来的4变成了7。

       第2种:“地址偏移法”:

 pCu8=&Cu8Memory_2[0];  //假设赋值后,此时“指针所存的地址”是RAM的地址4。
for(i=0;i<3;i++)  
{
    Gu8Buffer[i]=pCu8[i];  //这类是“偏移地址”的写法,i在这里相当于指针的偏移地址。
}


       分析:上述代码,等程序执行完for循环后,指针所存的地址还是RAM地址4吗?是的。因为它存的地址是不变的,变的只是偏移地址i。此时“指针所存的地址”就像“原点”一样具有“绝对地址”的“参考点”的属性。

【61.5   例程练习和分析。】

       现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

code unsigned char Cu8Memory_1[3]={1,2,3};  //第1堆数据
code unsigned char Cu8Memory_2[3]={4,5,6};  //第2堆数据
code unsigned char Cu8Memory_3[3]={7,8,9};  //第3堆数据

unsigned char Gu8Sec=2;  //选择的变量
unsigned char Gu8Buffer[3];  //根据变量来存放对应的某堆数据的数组
unsigned char i; //for循环用到的变量i
const unsigned char *pCu8; //引入一个指针作为“中间站”

void main() //主函数
{

switch(Gu8Sec)  //根据此选择变量来切换到对应的操作上
{
    case 1:  //第1堆
          pCu8=&Cu8Memory_1[0];  //跟第1堆数据“绑定”起来。
          break;

    case 2:  //第2堆
          pCu8=&Cu8Memory_2[0];  //跟第2堆数据“绑定”起来。
          break;

    case 3:  //第3堆
          pCu8=&Cu8Memory_3[0];  //跟第3堆数据“绑定”起来。
          break;
}

// for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
// {
//     Gu8Buffer[i]=*pCu8++; //先把“数据”赋值给数组,然后“指针所存的地址”再自加1。
// }

    for(i=0;i<3;i++)  //第1次出现for循环,用来实现“赋值”的“搬运数据”的动作。
{
    Gu8Buffer[i]=pCu8[i];  //这类是“偏移地址”的写法,i在这里相当于指针的偏移地址。
}

    View(Gu8Buffer[0]);  //把第1个数Gu8Buffer[0]发送到电脑端的串口助手软件上观察。
    View(Gu8Buffer[1]);  //把第2个数Gu8Buffer[1]发送到电脑端的串口助手软件上观察。
    View(Gu8Buffer[2]);  //把第3个数Gu8Buffer[2]发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:4
十六进制:4
二进制:100

第2个数
十进制:5
十六进制:5
二进制:101

第3个数
十进制:6
十六进制:6
二进制:110


分析:
        Gu8Buffer[0]为4。
        Gu8Buffer[1]为5。
        Gu8Buffer[2]为6。

【61.6   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
251
jianhong_wu|  楼主 | 2017-3-26 10:52 | 只看该作者
第六十二节: 指针,大小端,化整为零,化零为整。
第六十二节_pdf文件.pdf (82.29 KB)
【62.1   内存的大小端。】

       C51编译器的unsigned int占2字节RAM(也称为内存),unsigned long占4字节RAM,这两种数据类型所占的字节数都超过了1个字节,而RAM内存是每一个地址对应一个字节的RAM内存,那么问题就来了,比如像unsigned long这种占4个字节RAM的数据变量,它这4个字节在RAM中的地址是“连续”的“挨家挨户”的“连号”的,这4个字节所存的一个数据,它的数据高低位在地址的排列上,到底是从低到高还是从高到低,到底是“正向”的还是“反向”?这两种不同的排列顺序,在C语言里用“大端”和“小端”这两个专业术语来描述。“大端”的方式是将高位存放在低地址,“小端”的方式是将低位存放在低地址。比如:

       假设有一个unsigned long变量a等于0x12345678,是存放在RAM内存中的第4,5,6,7这四个“连号”的地址里,现在看看它在“大端”和“小端”的存储方式里的差别。如下:

      (1)在“大端”的方式里,将高位存放在低地址。
       0x12存在第4个地址,0x34存在第5个地址,0x56存在第6个地址,0x78存在第7个地址。

      (2)在“小端”的方式里,将低位存放在低地址。
       0x78存在第4个地址,0x56存在第5个地址,0x34存在第6个地址,0x12存在第7个地址。

       问题来了,在单片机里,内存到底是“大端”方式还是“小端”方式?答:这个跟C编译器有关。比如,在51单片机的C51编译环境里是“大端”方式,而在stm32单片机的ARM_MDK编译环境里则是“小端”方式。那么问题又来了?如何知道一个C编译器是“大端”还是“小端”?答:有两种方式,一种是看C编译器的说明书,另一种是自己编写一个小程序测试一下就知道了(这种方法最简单可靠)。那么问题又来了?讲这个 “大小端”有什么用?答:这个跟指针的使用密切相关。

【62.2   化整为零。】

       在数据的存储和通信中,往往要先把数据转换成以字节为单位的数组,才能进行数据存储和通信。比如unsigned long这种类型的数据,就要先转换成4个字节,这种把某个变量转换成N个字节的过程,就是“化整为零”。“化整为零”的过程,在代码上,有两种常见的方式,一种是原始的“移位法”,另一种是极具优越性的“指针法”。比如,现在以“大端”方式为例(因为本教程是用C51编译器,C51编译器是“大端”方式),有一个unsigned long变量a等于0x12345678,要把这个变量分解成4个字节存放在一个数组Gu8BufferA中,现在跟大家分享和对比一下这两种方法。

      (1)原始的“移位法”。
unsigned long a=0x12345678;
unsigned char Gu8BufferA[4];

Gu8BufferA[0]=a>>24;  
Gu8BufferA[1]=a>>16;
Gu8BufferA[2]=a>>8;
Gu8BufferA[3]=a;


      (2)极具优越性的“指针法”。
unsigned long a=0x12345678;
unsigned char Gu8BufferA[4];
unsigned long *pu32;   //引入一个指针变量,注意,这里是unsigned long类型的指针。

pu32=(unsigned long *)&Gu8BufferA[0];  //指针跟数组“绑定”(也称为“关联”)起来。
*pu32=a;  //这里仅仅1行代码就等效于上述(1)“移位”例子中的4行代码,所以极具优越性。


        多说一句,“pu32=(unsigned long *)&Gu8BufferA[0]”这行代码中,其中小括号“(unsigned long *)”是表示数据的强制类型转换,这里表示强制转换成unsigned long的指针方式,以后这类代码写多了,就会发现这种书写方法的规律。作为语言来解读先熟悉一下它的表达方式就可以了,暂时不用深究它的含义。

【62.3   化零为整。】

        从数据存储中提取数据出来,从通讯端接收到一堆数据,这里的“提取”和“接收”都是以字节为单位的数据,所以为了“还原”成原来的类型变量,就涉及“化零为整”的过程。在代码上,有两种常见的方式,一种是原始的“移位法”,另一种是极具优越性的“指针法”。比如,现在以“大端”方式为例(因为本教程是用C51编译器,C51编译器是“大端”方式),有一个数组Gu8BufferB存放了4个字节数据分别是:0x12,0x34,0x56,0x78。现在要把这4个字节数据“合并”成一个unsigned long类型的变量b,这个变量b等于0x12345678。现在跟大家分享和对比一下这两种方法。

       (1)原始的“移位法”。
unsigned char Gu8BufferB[4]={0x12,0x34,0x56,0x78};
unsigned long b;

b=Gu8BufferB[0];
b=b<<8;
b=b+Gu8BufferB[1];
b=b<<8;
b=b+Gu8BufferB[2];
b=b<<8;
b=b+Gu8BufferB[3];


       (2)极具优越性的“指针法”。
unsigned char Gu8BufferB[4]={0x12,0x34,0x56,0x78};
unsigned long b;
unsigned long *pu32;   //引入一个指针变量,注意,这里是unsigned long类型的指针。

pu32=(unsigned long *)&Gu8BufferB[0];  //指针跟数组“绑定”(也称为“关联”)起来。
b=*pu32;  //这里仅仅1行代码就等效于上述(1)“移位”例子中的7行代码,所以极具优越性。


【62.4   “指针法”要注意的问题。】

       “化整为零”和“化零为整”其实是一个“互逆”的过程,在使用“指针法”的时候,一定要注意“大小端”的问题。“化整为零”和“化零为整”这两个“互逆”过程要么同时为“大端”,要么同时为“小端”,否则会因字节的排列顺序问题而引起数据的严重错误。

【62.5   例程练习和分析。】

        现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned long a=0x12345678;
unsigned char Gu8BufferA[4];

unsigned char Gu8BufferB[4]={0x12,0x34,0x56,0x78};
unsigned long b;

unsigned long *pu32;   //引入一个指针变量,注意,这里是unsigned long类型的指针。

void main() //主函数
{
pu32=(unsigned long *)&Gu8BufferA[0];  //指针跟数组“绑定”(也称为“关联”)起来。
*pu32=a;  //化整为零

pu32=(unsigned long *)&Gu8BufferB[0];  //指针跟数组“绑定”(也称为“关联”)起来。
b=*pu32;  //化零为整


    View(Gu8BufferA[0]);  //把第1个数Gu8BufferA[0]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferA[1]);  //把第2个数Gu8BufferA[1]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferA[2]);  //把第3个数Gu8BufferA[2]发送到电脑端的串口助手软件上观察。
View(Gu8BufferA[3]);  //把第4个数Gu8BufferA[3]发送到电脑端的串口助手软件上观察。

View(b);              //把第5个数b发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:18
十六进制:12
二进制:10010

第2个数
十进制:52
十六进制:34
二进制:110100

第3个数
十进制:86
十六进制:56
二进制:1010110

第4个数
十进制:120
十六进制:78
二进制:1111000

第5个数
十进制:305419896
十六进制:12345678
二进制:10010001101000101011001111000


分析:
         Gu8BufferA[0]为0x12。
         Gu8BufferA[1]为0x34。
         Gu8BufferA[2]为0x56。
         Gu8BufferA[3]为0x78。
         b为0x12345678。

【62.6   如何在单片机上练习本章节C语言程序?】

         直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
评论
zhangtao3b608 2021-5-31 09:11 回复TA
学习啦 
252
jianhong_wu|  楼主 | 2017-4-2 10:29 | 只看该作者
第六十三节: 指针“化整为零”和“化零为整”的“灵活”应用。
第六十三节_pdf文件.pdf (70.92 KB)
【63.1   化整为零的“灵活”应用。】

       上一节讲“化整为零”的例子,指针是跟数组的首地址(下标是0)“绑定”的,这样,很多初学者就误以为指针跟数组“绑定”时,只能跟数组的“首地址”关联。其实,指针是可以跟数组的任何一个成员的地址“绑定”(只要不超过数组的长度导致越界),它不仅仅局限于首地址,指针的这个特征就是本节标题所说的“灵活”。请看下面这个例子:

       有3个变量,分别是单字节unsigned char a,双字节unsigned int b,四字节unsigned long c,它们加起来一共有7个字节,要把这7个字节放到一个7字节容量的数组里。除了用传统的“移位法”,还有一种更加便捷的“指针法”,代码如下:

unsigned char a=0x01;
unsigned int b=0x0203;
unsigned long c=0x04050607;

unsigned char Gu8BufferABC[7]; //存放3个不同长度变量的数组

unsigned char *pu8;   //引入的unsigned char 类型指针
unsigned int *pu16;   //引入的unsigned int 类型指针
unsigned long *pu32;  //引入的unsigned long 类型指针

pu8=&Gu8BufferABC[0]; //指针跟数组的第0个位置“绑定”起来。
*pu8=a; //把a的1个字节放在数组第0个位置。

pu16=(unsigned int *)&Gu8BufferABC[1]; //指针跟数组的第1个位置“绑定”起来。
*pu16=b;            //把b的2个字节放在数组第1、2这两个位置。

pu32=(unsigned long *)&Gu8BufferABC[3]; //指针跟数组的第3个位置“绑定”起来。
*pu32=c;            //把c的4个字节放在数组第3、4、5、6这四个位置。


【63.2   化零为整的“灵活”应用。】

       刚才讲的是“化整为零”,现在讲的是“化零为整”。刚才讲的是“分解”,现在讲的是“合成”。请看下面这个例子:

       有一个容量为7字节数组,第0字节存放的是unsigned char d变量,第1、2字节存放的是unsigned int e变量,第3、4、5、6字节存放的是unsigned long f变量,现在要从数组中“零散”的字节里提取并且合成为“完整”的3个变量。代码如下:

    unsigned char Gu8BufferDEF[7]={0x01,0x02,0x03,0x04,0x05,0x06,0x07}; //注意大小端的问题

unsigned char d;
unsigned int e;
unsigned long f;

unsigned char *pu8;   //引入的unsigned char 类型指针
unsigned int *pu16;   //引入的unsigned int 类型指针
unsigned long *pu32;  //引入的unsigned long 类型指针

pu8=&Gu8BufferDEF[0]; //指针跟数组的第0个位置“绑定”起来。
d=*pu8;          //从数组第0位置提取单字节完整的d变量。

pu16=(unsigned int *)&Gu8BufferDEF[1]; //指针跟数组的第1个位置“绑定”起来。
e=*pu16;        //从数组第1,2位置提取双字节完整的e变量。

pu32=(unsigned long *)&Gu8BufferDEF[3]; //指针跟数组的第3个位置“绑定”起来。
f=*pu32;        //从数组第3,4,5,6位置提取四字节完整的f变量。




【63.3   例程练习和分析。】

        现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

unsigned char a=0x01;
unsigned int b=0x0203;
unsigned long c=0x04050607;
unsigned char Gu8BufferABC[7]; //存放3个不同长度变量的数组

    unsigned char Gu8BufferDEF[7]={0x01,0x02,0x03,0x04,0x05,0x06,0x07}; //注意大小端的问题
unsigned char d;
unsigned int e;
unsigned long f;

unsigned char *pu8;   //引入的unsigned char 类型指针
unsigned int *pu16;   //引入的unsigned int 类型指针
unsigned long *pu32;  //引入的unsigned long 类型指针


void main() //主函数
{
    //第1类例子:化整为零。
pu8=&Gu8BufferABC[0]; //指针跟数组的第0个位置“绑定”起来。
*pu8=a; //把a的1个字节放在数组第0个位置。

pu16=(unsigned int *)&Gu8BufferABC[1]; //指针跟数组的第1个位置“绑定”起来。
*pu16=b;            //把b的2个字节放在数组第1、2这两个位置。

pu32=(unsigned long *)&Gu8BufferABC[3]; //指针跟数组的第3个位置“绑定”起来。
*pu32=c;            //把c的4个字节放在数组第3、4、5、6这四个位置。

    //第2类例子:化零为整。
pu8=&Gu8BufferDEF[0]; //指针跟数组的第0个位置“绑定”起来。
d=*pu8;          //从数组第0位置提取单字节完整的d变量。

pu16=(unsigned int *)&Gu8BufferDEF[1]; //指针跟数组的第1个位置“绑定”起来。
e=*pu16;        //从数组第1,2位置提取双字节完整的e变量。

pu32=(unsigned long *)&Gu8BufferDEF[3]; //指针跟数组的第3个位置“绑定”起来。
f=*pu32;        //从数组第3,4,5,6位置提取四字节完整的f变量。


    View(Gu8BufferABC[0]);  //把第1个数Gu8BufferABC[0]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferABC[1]);  //把第2个数Gu8BufferABC[1]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferABC[2]);  //把第3个数Gu8BufferABC[2]发送到电脑端的串口助手软件上观察。
View(Gu8BufferABC[3]);  //把第4个数Gu8BufferABC[3]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferABC[4]);  //把第5个数Gu8BufferABC[4]发送到电脑端的串口助手软件上观察。
    View(Gu8BufferABC[5]);  //把第6个数Gu8BufferABC[5]发送到电脑端的串口助手软件上观察。
View(Gu8BufferABC[6]);  //把第7个数Gu8BufferABC[6]发送到电脑端的串口助手软件上观察。

    View(d);               //把第8个数d发送到电脑端的串口助手软件上观察。
    View(e);               //把第9个数e发送到电脑端的串口助手软件上观察。
View(f);               //把第10个数f发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

第1个数
十进制:1
十六进制:1
二进制:1

第2个数
十进制:2
十六进制:2
二进制:10

第3个数
十进制:3
十六进制:3
二进制:11

第4个数
十进制:4
十六进制:4
二进制:100

第5个数
十进制:5
十六进制:5
二进制:101

第6个数
十进制:6
十六进制:6
二进制:110

第7个数
十进制:7
十六进制:7
二进制:111

第8个数
十进制:1
十六进制:1
二进制:1

第9个数
十进制:515
十六进制:203
二进制:1000000011

第:个数(这里是第10个数。本模块程序只支持显示第1到第9个,所以这里没有显示“10”)
十进制:67438087
十六进制:4050607
二进制:100000001010000011000000111



分析:
        Gu8BufferABC[0]为0x01。
        Gu8BufferABC[1]为0x02。
        Gu8BufferABC[2]为0x03。
        Gu8BufferABC[3]为0x04。
        Gu8BufferABC[4]为0x05。
        Gu8BufferABC[5]为0x06。
        Gu8BufferABC[6]为0x07。
        d为0x01。
        e为0x0203。
        f为0x04050607。

【63.4   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
253
liuxianbing| | 2017-4-3 20:51 | 只看该作者
对楼主真是钦佩之至!

使用特权

评论回复
254
MCU89c52| | 2017-4-6 18:55 | 只看该作者
一直跟着老师学习,增长知识了,

使用特权

评论回复
255
jianhong_wu|  楼主 | 2017-4-9 18:11 | 只看该作者
第六十四节: 指针让函数具备了多个相当于return的输出口。
第六十四节_pdf文件.pdf (68.56 KB)
【64.1   函数的三类输出渠道。】

       函数是模块,模块必须具备输入和输出的接口,从输入和输出的角度分析,函数对外部调用者传递信息主要有三类渠道,第一类是全局变量,第二类是return返回值,第三类是用指针。全局变量太隐蔽,没有那么直观,可读性稍差。return可读性强,缺点是一个函数只能有一个return,如果一个函数要输出多个结果,return就力不从心。指针作为函数的输出接口,就能随心所欲了,不但可读性强,而且输出的接口数量不受限制。

【64.2   只有一个输出接口的时候。】

       现在举一个例子,要用函数实现一个加法运算,输出“一个”加法运算的和,求3加上5等于8。下面三个例子中分别使用“全局变量,return,指针”这三类输出接口。

       第一类:全局变量。
unsigned char DiaoYongZhe;  //调用者
unsigned char BeiJiaShu;    //被加数
unsigned char JiaShu;       //加数
unsigned char He;           //输出的接口,加法运算的"和"。

void JiaFa(void)   
{
   He=BeiJiaShu+JiaShu;  
}

void main()
{
    BeiJiaShu=3;          //填入被加数3
    JiaShu=5;             //填入加数5
    JiaFa();              //调用一次加法运算的函数
    DiaoYongZhe=He;       //把加法运算的“和”赋值给调用者。
}


       第二类:return。
unsigned char DiaoYongZhe;    //调用者

unsigned char JiaFa(unsigned char BeiJiaShu,unsigned char JiaShu)   
{
     unsigned char He;
     He=BeiJiaShu+JiaShu;  
     return He;
}

void main()
{
    DiaoYongZhe=JiaFa(3,5);   //把加法运算的“和”赋值给调用者,一气呵成。
}


       第三类:指针。
unsigned char DiaoYongZhe;    //调用者

void char JiaFa(unsigned char BeiJiaShu,unsigned char JiaShu,unsigned char *pu8He)   
{
    *pu8He=BeiJiaShu+JiaShu;  
}

void main()
{
    JiaFa(3,5,&DiaoYongZhe);  //通过指针这个输出渠道,把加法运算的“和”赋值给调用者,一气呵成。
}


【64.3   有多个输出接口的时候。】

       现在举一个例子,要用函数实现一个除法运算,分别输出除法运算的商和余数这“两个”数,求5除以3等于1余2。因为return只能输出一个结果,所以这里不列举return的例子,只使用“全局变量”和“指针”这两类输出接口。

       第一类:全局变量。
unsigned char DiaoYongZhe_Shang; //调用者的商
unsigned char DiaoYongZhe_Yu;    //调用者的余数

unsigned char BeiChuShu;         //被除数
unsigned char ChuShu;            //除数
unsigned char Shang;             //输出的接口,除法运算的"商"。
unsigned char Yu;                //输出的接口,除法运算的"余"。

void ChuFa(void)   
{
     Shang=BeiChuShu/ChuShu;     //求商。假设除数不会为0的情况。
     Yu=BeiChuShu%ChuShu;        //求余数。假设除数不会为0的情况。
}


void main()
{
    BeiChuShu=5;              //填入被除数5
    ChuShu=3;                 //填入除数3
    ChuFa();                  //调用一次除法运算的函数
    DiaoYongZhe_Shang=Shang;  //把除法运算的“商”赋值给调用者的商。
    DiaoYongZhe_Yu=Yu;        //把除法运算的“余数”赋值给调用者的余数。
}


       第二类:return。
       return只能输出一个结果,力不从心,所以这里不列举return的例子。

       第三类:指针。
unsigned char DiaoYongZhe_Shang;   //调用者的商
unsigned char DiaoYongZhe_Yu;      //调用者的余数

void ChuFa(unsigned char BeiChuShu,
           unsigned char ChuShu,
           unsigned char *pu8Shang,
           unsigned char *pu8Yu)   
{
      *pu8Shang=BeiChuShu/ChuShu;   //求商。假设除数不会为0的情况。
      *pu8Yu=BeiChuShu%ChuShu;      //求余数。假设除数不会为0的情况。
}

void main()
{
    ChuFa(5,3,&DiaoYongZhe_Shang,&DiaoYongZhe_Yu);//通过两个指针的输出接口,一气呵成。
}


【64.4   例程练习和分析。】

       现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/
void ChuFa(unsigned char BeiChuShu,
           unsigned char ChuShu,
           unsigned char *pu8Shang,
           unsigned char *pu8Yu);   //函数声明   

unsigned char DiaoYongZhe_Shang;    //调用者的商
unsigned char DiaoYongZhe_Yu;       //调用者的余数

void ChuFa(unsigned char BeiChuShu,
           unsigned char ChuShu,
           unsigned char *pu8Shang,
           unsigned char *pu8Yu)    //函数定义  
{
      *pu8Shang=BeiChuShu/ChuShu;   //求商。假设除数不会为0的情况。
      *pu8Yu=BeiChuShu%ChuShu;      //求余数。假设除数不会为0的情况。
}

void main() //主函数
{
ChuFa(5,3,&DiaoYongZhe_Shang,&DiaoYongZhe_Yu);//函数调用。通过两个指针的输出接口,一气呵成。

    View(DiaoYongZhe_Shang); //把第1个数DiaoYongZhe_Shang发送到电脑端的串口助手软件上观察。
    View(DiaoYongZhe_Yu);    //把第2个数DiaoYongZhe_Yu发送到电脑端的串口助手软件上观察。
    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:1
十六进制:1
二进制:1

第2个数
十进制:2
十六进制:2
二进制:10


分析:
        DiaoYongZhe_Shang为1。
        DiaoYongZhe_Yu为2。

【64.5   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
256
mojingjian| | 2017-4-9 22:38 | 只看该作者
表示支持!!!

使用特权

评论回复
257
dudu_q| | 2017-4-11 16:58 | 只看该作者
MARK

使用特权

评论回复
258
jianhong_wu|  楼主 | 2017-4-16 12:02 | 只看该作者
第六十五节: 指针作为数组在函数中的入口作用。
第六十五节_pdf文件.pdf (72.58 KB)
【65.1   函数的参数入口。】

        要往函数内部传递信息,主要有两类渠道。第一类是全局变量。第二类是函数的参数入口,而参数入口可以分为“普通局部变量”和“指针”这两类。“普通局部变量”的参数入口一次只能传一个数据,如果一个数组有几十个甚至上百个数据,此时“普通局部变量”就无能为力,这时不可能也写几十个甚至上百个入口参数吧(这会累坏程序员),针对这种需要输入批量数据的场合,“指针”的参数入口就因此而生,完美解决了此问题,仅用一个“指针”参数入口就能解决一个数组N个数据的入口问题。那么,什么是函数的参数入口?例子如下:


//函数声明
unsigned long PinJunZhi(unsigned char a,unsigned char b,unsigned char c,unsigned char d);

//变量定义
unsigned char Gu8Buffer[4]={2,6,8,4};  //4个变量分别是2,6,8,4。
unsigned long Gu32PinJunZhi;  //求平均值的结果

//函数定义
unsigned long PinJunZhi(unsigned char a,unsigned char b,unsigned char c,unsigned char d)
{
     unsigned long u32PinJunZhi;
     u32PinJunZhi=(a+b+c+d)/4;   
     return u32PinJunZhi;
}

    void main() //主函数
{
    //函数调用
    Gu32PinJunZhi=PinJunZhi(Gu8Buffer[0],Gu8Buffer[1],Gu8Buffer[2],Gu8Buffer[3]);

}


       上面是一个求4个数据平均值的函数,在这个函数中,函数小括号的(unsigned char a,unsigned char b,unsigned char c,unsigned char d)就是4个变量的“普通局部变量”参数入口,刚才说到,如果一个数组有上百个变量,这种书写方式是很累的。如果改用“指针”入口参数的方式,例子如下:

//函数声明
unsigned long PinJunZhi(unsigned char *pu8Buffer);

//变量定义
unsigned char Gu8Buffer[4]={2,6,8,4};  //4个变量分别是2,6,8,4。
unsigned long Gu32PinJunZhi;  //求平均值的结果

//函数定义
unsigned long PinJunZhi(unsigned char *pu8Buffer)
{
     unsigned long u32PinJunZhi;
     u32PinJunZhi=(pu8Buffer[0]+pu8Buffer[1]+pu8Buffer[2]+pu8Buffer[3])/4;   
     return u32PinJunZhi;
}

    void main() //主函数
{
    //函数调用
    Gu32PinJunZhi=PinJunZhi(&Gu8Buffer[0]);//也等效于Gu32PinJunZhi=PinJunZhi(Gu8Buffer)
}


        上面例子中,仅用一个(unsigned char *pu8Buffer)指针入口参数,就可以达到输入4个变量的目的(这4个变量要求是同在一个数组内)。

【65.2   const在指针参数“入口”中的作用。】

        指针在函数的参数入口中,既可以做“入口”,也可以做“出口”,而C语言为了区分这两种情况,提供了const这个关键字来限定权限。如果指针加了const前缀,就为指针的权限加了紧箍咒,限定了此指针只能作为“入口”,而不能作为“出口”。如果没有加了const前缀,就像本节的函数例子,此时指针参数既可以作为“入口”,也可以作为“出口”。加const关键字有两个意义,一方面是方便阅读,通过const就知道此接口的“入口”和“出口”属性,另一方面,是为了代码的安全,对于只能作为“入口”的指针参数一旦加了const限定,万一我们不小心在函数内部对const限定的指针所关联的数据进行了更改(“更改”就意味着“出口”),C编译器在编译的时候就会有提醒或者报错,及时让我们发现程序的bug(程序的漏洞)。这部分的内容后续章节会讲到,大家先有个大概的了解,本节暂时不深入讲。

【65.3   例程练习和分析。】

        现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

//函数声明
unsigned long PinJunZhi(unsigned char *pu8Buffer);

//变量定义
unsigned char Gu8Buffer[4]={2,6,8,4};  //4个变量分别是2,6,8,4。
unsigned long Gu32PinJunZhi;  //求平均值的结果

//函数定义
unsigned long PinJunZhi(unsigned char *pu8Buffer)
{
     unsigned long u32PinJunZhi;
     u32PinJunZhi=(pu8Buffer[0]+pu8Buffer[1]+pu8Buffer[2]+pu8Buffer[3])/4;   
     return u32PinJunZhi;
}

void main() //主函数
{
//函数调用
Gu32PinJunZhi=PinJunZhi(&Gu8Buffer[0]);//也等效于Gu32PinJunZhi=PinJunZhi(Gu8Buffer)
    View(Gu32PinJunZhi); //把第1个数Gu32PinJunZhi发送到电脑端的串口助手软件上观察。
    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:5
十六进制:5
二进制:101


分析:
         平均值变量Gu32PinJunZhi为5。

【65.4   如何在单片机上练习本章节C语言程序?】

         直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
259
jianhong_wu|  楼主 | 2017-4-23 09:58 | 只看该作者
第六十六节: 指针作为数组在函数中的出口作用。
第六十六节_pdf文件.pdf (61.73 KB)
【66.1   指针作为数组在函数中的出口。】

       函数对外部调用者传递信息主要有三类渠道,第一类是全局变量,第二类是return返回值,第三类是指针。之前讲指针对外传递信息的时候,只讲了单个变量的情况,现在重点讲讲数组的情况。要把一个四位数的个,十,百,千位分别提取出来成为4个数,依次存放在一个包含4个字节的数组里,代码如下:

void TiQu(unsigned int u16Data,unsigned char *pu8Buffer) //“提取”函数
{
     unsigned char u8Ge; //个位
     unsigned char u8Shi; //十位
     unsigned char u8Bai; //百位
     unsigned char u8Qian; //千位

     u8Ge=u16Data/1%10;       //提取个位
     u8Shi=u16Data/10%10;     //提取十位
     u8Bai=u16Data/100%10;    //提取百位
     u8Qian=u16Data/1000%10;  //提取千位

     //最后,把所提取的数分别传输到“指针”这个“出口通道”
     pu8Buffer[0]=u8Ge;
     pu8Buffer[1]=u8Shi;
     pu8Buffer[2]=u8Bai;
     pu8Buffer[3]=u8Qian;

}


       上述代码,为了突出“出口通道”,我刻意多增加了u8Ge、u8Shi、u8Bai、u8Qian这4个局部变量,其实,这4个局部变量还可以省略的,此函数简化后的等效代码如下:

void TiQu(unsigned int u16Data,unsigned char *pu8Buffer) //“提取”函数
{

     pu8Buffer[0]=u16Data/1%10;       //提取个位
     pu8Buffer[1]=u16Data/10%10;      //提取十位
     pu8Buffer[2]=u16Data/100%10;     //提取百位
     pu8Buffer[3]=u16Data/1000%10;    //提取千位
}


【66.2   例程练习和分析。】

        现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

//函数声明
void TiQu(unsigned int u16Data,unsigned char *pu8Buffer);

//全局变量定义
unsigned char Gu8Buffer[4];  //存放提取结果的数组

//函数定义
void TiQu(unsigned int u16Data,unsigned char *pu8Buffer) //“提取”函数
{

     pu8Buffer[0]=u16Data/1%10;       //提取个位
     pu8Buffer[1]=u16Data/10%10;      //提取十位
     pu8Buffer[2]=u16Data/100%10;     //提取百位
     pu8Buffer[3]=u16Data/1000%10;    //提取千位
}


void main() //主函数
{
TiQu(9876,&Gu8Buffer[0]);  //把9876这个四位数分别提取6、7、8、9存放在数组Gu8Buffer里

View(Gu8Buffer[0]); //把第1个数Gu8Buffer[0])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[1]); //把第2个数Gu8Buffer[1])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[2]); //把第3个数Gu8Buffer[2])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[3]); //把第4个数Gu8Buffer[3])发送到电脑端的串口助手软件上观察

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:6
十六进制:6
二进制:110

第2个数
十进制:7
十六进制:7
二进制:111

第3个数
十进制:8
十六进制:8
二进制:1000

第4个数
十进制:9
十六进制:9
二进制:1001


分析:
        Gu8Buffer[0]为6。
        Gu8Buffer[1]为7。
        Gu8Buffer[2]为8。
        Gu8Buffer[3]为9。

【66.3   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
260
jianhong_wu|  楼主 | 2017-4-30 08:54 | 只看该作者
第六十七节: 指针作为数组在函数中既“入口”又“出口”的作用。
第六十七节_pdf文件.pdf (63.51 KB)
【67.1   指针作为数组在函数中的“入口”和“出口”。】

       前面分别讲了指针的入口和出口,很多初学者误以为指针是一个“单向”的通道,其实,如果指针前面没有加const这个“紧箍咒”限定它的属性,指针是“双向”的,不是“单向”的,也就是说,指针是可以同时具备“入口”和“出口”这两种属性的。现在讲一个程序例子,求一个数组(内含4元素)的每个元素变量的整数倍的一半,所谓整数倍的一半,就是除以2,但是不带小数点,比如4的整数倍的一半是2,7的整数倍的一半是3(不是3.5),代码如下:

void Half(unsigned char *pu8Buffer) //“求一半”的函数
{
     unsigned char u8Data_0; //临时中间变量
     unsigned char u8Data_1; //临时中间变量
     unsigned char u8Data_2; //临时中间变量
     unsigned char u8Data_3; //临时中间变量

     //从指针这个“入口”里获取需要“被除以2”的数据。
     u8Data_0=pu8Buffer[0];
     u8Data_1=pu8Buffer[1];
     u8Data_2=pu8Buffer[2];
     u8Data_3=pu8Buffer[3];

     //求数据的整数倍的一半的算法
     u8Data_0=u8Data_0/2;
     u8Data_1=u8Data_1/2;
     u8Data_2=u8Data_2/2;
     u8Data_3=u8Data_3/2;

     //最后,把计算所得的结果分别传输到指针这个“出口”
     pu8Buffer[0]=u8Data_0;
     pu8Buffer[1]=u8Data_1;
     pu8Buffer[2]=u8Data_2;
     pu8Buffer[3]=u8Data_3;

}


       上述代码,为了突出“入口”和“出口”,我刻意多增加了u8Data_0,u8Data_1,u8Data_2,u8Data_3这4个临时中间变量,其实,这4个临时中间变量还可以省略的,此函数简化后的等效代码如下:

void Half(unsigned char *pu8Buffer) //“求一半”的函数
{
     pu8Buffer[0]=pu8Buffer[0]/2;
     pu8Buffer[1]=pu8Buffer[1]/2;
     pu8Buffer[2]=pu8Buffer[2]/2;
     pu8Buffer[3]=pu8Buffer[3]/2;
}


【67.2   例程练习和分析。】

        现在编一个练习程序。

/*---C语言学习区域的开始。-----------------------------------------------*/

//函数声明
void Half(unsigned char *pu8Buffer);

//全局变量定义
unsigned char Gu8Buffer[4]={4,7,16,25};  //需要“被除以2”的数组

//函数定义
void Half(unsigned char *pu8Buffer) //“求一半”的函数
{
     pu8Buffer[0]=pu8Buffer[0]/2;
     pu8Buffer[1]=pu8Buffer[1]/2;
     pu8Buffer[2]=pu8Buffer[2]/2;
     pu8Buffer[3]=pu8Buffer[3]/2;
}

void main() //主函数
{
    Half(&Gu8Buffer[0]); //计算数组的整数倍的一半。这里的“入口”和“出口”是“同一个通道”。

View(Gu8Buffer[0]); //把第1个数Gu8Buffer[0])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[1]); //把第2个数Gu8Buffer[1])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[2]); //把第3个数Gu8Buffer[2])发送到电脑端的串口助手软件上观察
View(Gu8Buffer[3]); //把第4个数Gu8Buffer[3])发送到电脑端的串口助手软件上观察

    while(1)  
    {
    }
}
/*---C语言学习区域的结束。-----------------------------------------------*/


        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:2
十六进制:2
二进制:10

第2个数
十进制:3
十六进制:3
二进制:11

第3个数
十进制:8
十六进制:8
二进制:1000

第4个数
十进制:12
十六进制:C
二进制:1100


分析:
        Gu8Buffer[0]为2。
        Gu8Buffer[1]为3。
        Gu8Buffer[2]为8。
        Gu8Buffer[3]为12。

【67.3   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

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

本版积分规则