第10章单片机的C语言编程.ppt
第10章单片机的C语言编程,单片机原理、接口及应用,内容提要,★C51程序结构★C51的数据类型★数据的存贮类型和存贮模式★C51对SFR、可寻址位、存储器和I/O口的定义★C51的运算符★函数★C语言编程实例★单片机资源的C语言编程实例★汇编语言和C语言的混合编程★C语言函数库的管理与使用★小结,,51系列单片机支持三种高级语言,即PL/M,C和BASIC。8052单片机内固化有解释BASIC语言,BASIC语言适用于简单编程而对编程效率运行速度要求不高的场合;PL/M是一种结构化的语言,很象PASCAL,PL/M编译器好象汇编器一样产生紧凑的机器代码,可以说是高级汇编语言,但它不支持复杂的算术运算,无丰富库函数支持,学习PL/M无异于学习一种新的语言。C语言是一种通用的程序设计语言,其代码率高,数据类型及运算符丰富,并具有良好的程序结构,适用于各种应用的程序设计,是目前使用较广的单片机编程语言。,,,单片机的C语言采用C51编译器简称C51。由C51产生的目标代码短、运行速度高、所需存储空间小、符合C语言的ANSI标准,生成的代码遵循Intel目标文件格式,而且可与A51汇编语言或PL/M51语言目标代码混和使用。,,应用C51编程具有以下优点(1)C51管理内部寄存器和存贮器的分配,编程时,无需考虑不同存储器的寻址和数据类型等细节问题;(2)程序由若干函数组成,具有良好的模块化结构;(3)有丰富的子程序库可直接引用,从而大大减少用户编程的工作量。(4)C语言和汇编语言可以交叉使用.汇编语言程序代码短、运行速度快、但复杂运算编程耗时。如果用汇编语言编写与硬件有关的部分程序,用C语言编写与硬件无关的运算部分程序,充分发挥两种语言的长处,可以提高开发效率。,,10.1C51程序结构,同标准C一样,C51的程序由一个个函数组成,这里的函数和其他语言的“子程序”或“过程”具有相同的意义。其中必须有一个主函数main,程序的执行从main函数开始,调用其他函数后返回主函数main,最后在主函数中结束整个程序而不管函数的排列顺序如何。,,C语言程序的组成结构如下所示,全局变量说明/*可被各函数引用*/main/*主函数*/{局部变量说明/*只在本函数引用*/执行语句包括函数调用语句}fun1形式参数表/*函数1*/形式参数说明,{局部变量说明执行语句包括调用其他函数语句}funn形式参数表/*函数n*/形式参数说明{局部变量说明执行语句},,,C语言的语句规则1.每个变量必须先说明后引用,变量名英文大小写是有差别的。2.C语言程序一行可以书写多条语句,但每个语句必须以“;”结尾,一个语句也可以多行书写为好。3.C语言的注释用/**/表示。4.“{”花括号必须成对,位置随意,可在紧挨函数名后,也可另起一行,多个花括号可以同行书写,也可逐行书写,为层次分明,增加可读性,同一层的“{”花括号对齐,采用逐层缩进方式书写。,,10.2C51的数据类型,C51的数据有常量和变量之分。常量在程序运行中其值不变的量,可以为字符,十进制数或十六进制数用0 x表示。常量分为数值型常量和符号型常量,如果是符号型常量,需用宏定义指令define对其进行定义相当于汇编的“EQU”伪指令如definePI3.1415那么程序中只要出现PI的地方,编译程序都译为3.1415。变量在程序运行中其值可以改变的量。一个变量由变量名和变量值构成,变量名即是存贮单元地址的符号表示,而变量的值就是该单元存放的内容。定义一个变量,编译系统就会自动为它安排一个存贮单元,具体的地址值用户不必在意。,,10.2.1C51变量的数据类型,无论哪种数据都是存放在存贮单元中的,每一个数据究竟要占用几个单元即数据的长度都要提供给编译系统,正如汇编语言中存放数据的单元要用DB或DW伪指令进行定义一样,编译系统以此为根据预留存贮单元,这就是定义数据类型的意义.C51编译器支持数据类型见表10.1。,,表10-1C51的数据类型,,对表10.1作如下说明1.字符型char、整型int和长整型long均有符号型signed和无符号型unsigned两种,如果不是必须,尽可能选择unsigned型,这将会使编译器省却符号位的检测,使生成的程序代码比signed类型短得多。2.程序编译时,C51编译器会自动进行类型转换,例如将一个位变量赋值给一个整型变量时,位型值自动转换为整型值;当运算符两边为不同类型的数据时,编译器先将低级的数据类型转换为较高级的数据类型,运算后,运算结果为高级数据类型。3.51单片机内部数据存贮器的可寻址位20H~2FH定义为bit型,而特殊功能寄存器的可寻址位即地址为X0H和X8H的SFR的各位只能定义为sbit类型。,10.2.2关于指针型数据,1关于指针型变量在汇编语言程序中,要取存贮单元m的内容可用直接寻址方式,也可用寄存器间接寻址方式,如果用R1寄存器指示m的地址,用R1取m单元的内容。相对应的在C语言中用变量名表示取变量的值相当于直接寻址,也可用另一个变量如P存放m的地址,P就相当于R1寄存器。用*P取得m单元的内容相当于汇编的间接寻址方式这里P即为指针型变量。下面表格表示两种语言将m单元的内容送n单元的对照语句。,注上表省略了汇编语言程序中对符号地址n和m用EQU伪指令进行具体地址定义的语句以及C语言对变量n、m和指针变量P进行类型定义的语句,实际程序设计中,此步是不可缺少的。表中相等;不相等优先级前四个高,后二个“”和“”级别低。,,,4.C51的逻辑运算符有三种逻辑表达式和关系表达式的值相同,以0代表假,以1代表真。以上三种运算的优先级见图10.1。5.C51的按位操作的运算符有六种{intc;cab;returnc;}main{intd,0u3,v2;d2*funu,v;}上例被调函数在主调函数前,不用说明。,,intfun1a,b;main{intd,u3,v2;d2*fun1u,v;intfun1a,b;inta,b;{intc;cab;returnc;}上例中被调函数在主调函数后,在前面对被调函数进行说明。,,10.7C语言编程实例,为了使C语言的编程方法和汇编语言的编程方法有一个对比,本节采用3.1节的例题。由于C51编译器是针对单片机的,因此ANSIC中的scanf和printf等对PC电脑的键盘和监视器的输入、输出语句无效。运算的数据可以通过变量置入或取出,这时C51会自动安排使用的存贮单元。当然也可以用户自行通过具体的内存地址置入数据或从特定地址取出数据,这就少不了要会观察具体地址的内容或改变该地址的内容,C语言的编程上机调试见本教材的实验部分。下面通过一个例子说明C语言程序编译后生成的机器代码及对应的反汇编程序,从中引出一些道理。,,10.7.1顺序程序的设计,例10-5完成1980524503的编程分析两个乘数比较大,其积更大,采用unsignedlong类型,设乘积存放在外部数据存贮器0号开始的单元。程序如下main{unsignedlongxdata*p;/*设定指针p指向类型为unsignedlong的外部RAM区*/unsignedlonga19805;/*设置a为unsignedlong类型,并赋初值*/unsignedlongb24503,c;/*设置b和积为unsignedlong类型,并赋初值*/p0;/*设地址指向0号单元*/ca*b;*pc;/*积存入外部RAM0号单元*/},,上机通过WAVE软件仿真调试,在变量观察窗口看到运算结果c48528195,即为乘积的十进制数。观察XDATA区外部RAM的0000H~0003H单元分别为1CECD07B,即存放的为乘积的十六进制数。观察DATA区(内部RAM区)地址0405060708090A0B0C0D0E0F1CECD07B00004D5D00005FB7C变量积a变量b变量可见定义为unsignedlong类型,给每个变量分配四个单元,如果定义类型不对,将得不到正确的结果。对于复杂的运算通常采用查表的方法。如同汇编程序设计一样,在程序存贮器建立一张表,在C语言中表格定义为数组,表内数据元素的偏移量表现为下标。数组的使用如同变量一样,要先进行定义说明数组名、维数、数据类型和存贮类型,在定义数组的同时,还可以给数组各元素赋初值。通过下例说明C51数组的定义方法和用C语言编查表程序的方法。,,,,,例10-6片内RAM20H单元存放着一个0~05H的数,用查表法,求出该数的平方值放入内部RAM21H单元。main{charx,*pcharcodetab[6]{0,1,4,9,16,25};p0 x20;xtab[*p];p;*px;},10.7.2循环程序的设计,C语言的循环语句有以下几种形式1.while表达式{语句;}其中表达式为循环条件,语句为循环体,当表达式值为真值为非0,重复执行“语句”。语句可只一条以“;”结尾;可以多条组成复合语句,复合语句必须用{}括起;也可以没有语句,通常用于等待中断,或查询。2.do{语句;}while表达式表达式为真执行循环体“语句”,直至表达式为假,退出循环执行下一个语句。3.for表达式1;表达式2;表达式3;{语句;}其中语句为循环体。执行过程是执行表达式1后进入循环体,如表达式2为假,按表达式3修改变量,再执行循环体,直到表达式2为真.语句中的表达式可以省其中任一项甚至全部,但二个分号不可省,如for;;{语句;}为无限循环,fori4;;i{语句}i从4开始无限循环,for;i<100;相当于whilei<100,,例10-7whileP1}本程序完成01210的累加,执行后sum55,例10-9将例10-8改用for语句编程main{intsum0,i;fori0;i10;isuni;},10.7.3分支程序的设计,C语言的分支选择语句有以下几种形式1.if表达式{语句;}句中表达式为真执行语句,否则执行下一条语句。当花括号中的语句不只一条,花括号不能省。2.if表达式{语句1;}else{语句2;}句中表达式为真执行语句1,否则执行语句2为了能无论哪种情况,执行完后都执行下一条语句。if语句可以嵌套。3.switch表达式{case常量表达式1{语句1;}break;case常量表达式2{语句2;}break;case常量表达式n{语句n;}break;default{语句n1;},,说明①语句先进行表达式的运算,当表达式的值与某一case后面的常量表达式相等,就执行它后面的语句。②当case语句后有break语句时,执行完这一case语句后,跳出switch语句,当case后面无break语句,程序将执行下一条case语句。③如果case中常量表达式值和表达式的值都不匹配,就执行default后面的语句。如果无default语句就退出switch语句。④default的次序不影响执行的结果,也可无此语句。case语句适于多分支转移的情况下使用。,,例10-10片内RAM的20H单元存放一个有符号数x,函数y与x有如下关系式xx>0y20Hx0 x5x<0设y存放于21H单元,程序如下main{charx,*p,*y;p0 x20;y0 x21;for;;{x*p;ifx>0*yx;ifx<0*yx5;ifx0*y0 x20;}}程序中为观察不同数的执行结果,采用了死循环语句for;;,上机调试时退出死循环可用CtrlC。,,例10-11有两个数a和b,根据R3的内容转向不同的处理子程序r30,执行子程序pr0完成两数相加r31,执行子程序pr1完成两数相减r32,执行子程序pr2完成两数相乘r33,执行子程序pr3完成两数相除分析①C语言中的子程序即为函数,因此需编四个处理的函数,如果主函数在前,主函数要对子函数进行说明;如果子函数在前,主函数无须对子函数说明,但是无论子、主函数的顺序如何,程序总是从主函数开始执行,执行到调用子函数就会转到子函数执行.②在C51编译器中通过头文件reg51.h可以识别特殊功能寄存器,但不能识别R0~R7通用寄存器,因此R0~R7只有通过绝对地址访问识别,程序如下,,includedefiner3DBYTE[0 x03]intc,c1,a,b;pr0{cab;}pr1{ca-b;}pr2{ca*b;}pr3{ca/b;}main{a90;b30;,for;;{switchr3{case0pr0;break;case1pr1;break;case2pr2;break;case3pr3;break;}c156;}},在上述程序中,为便于调试观察,加了C156语句,并使用了死循环语句for;;,用CtrlC可退出死循环。,,10.8单片机资源的C语言编程实例,例10-12在3.1节曾用汇编语言完成了外部RAM的000EH单元和000FH单元的内容交换,现改用C语言编程。C语言对地址的指示方法可以采用指针变量,也可以引用absacc.h头文件作绝对地址访问,下面采用绝对地址访问方法。includemain{charc;for;;{cXBYTE[14];XBYTE[14]XBYTE[15];XBYTE[15]c;}},,程序中为方便反复观察,使用了死循环语句for;;只要用CtrlC即可退出死循环。上面程序通过编译,生成的机器代码和反汇编程序如下,0000020014LJMP0014H000390000EMOVDPTR,000EH0006E0MOVXA,DPTR0007FFMOVR7,A0008A3INCDPTR0009E0MOVXA,DPTR000A90000EMOVDPTR,000EH000DF0MOVXDPTR,A000EA3INCDPTR000FEFMOVA,R70010F0MOVXDPTR,A,001180F0SJMP0003H001322RET0014787FMOVR0,7FH0016E4CLRA0017F6MOVR0,A0018D8FDDJNZR0,0017H001A758107MOVSP,07H001D020003LJMP0003H,,例中可见①一进入C语言程序,首先执行初始化,将内部RAM的0~7FH128个单元清零,然后置SP为07H视变量多少不同,SP置不同值,依程序而定,因此如果要对内部RAM置初值,一定要在执行了一条C语言语句后进行。②C语言程序设定的变量,C51自行安排寄存器或存贮器作参数传递区,通常在R0~R7一组或两组,视参数多少定,因此,如果对具体地址置数据,应避开这些R0~R7的地址。③如果不特别指定变量的存贮类型,通常被安排在内部RAM中。,10.8.2并行口及键盘的C语言编程,例10-13用P1.0输出1KHz和500Hz的音频信号驱动扬声器,作报警信号,要求1KHz信号响100ms,500Hz信号响200ms,交替进行,P1.7接一开关进行控制,当开关合上,响报警信号,当开关断Kk告警信号停止,编出程序.分析500Hz信号周期为2ms,信号电平为每1ms变反一次.1KHz信号周期为1ms,信号电平每500s变反一次。用C语言编程如下,,includesbitP10P1ˆ0;sbitP17P1ˆ7;main{unsignedchari,j;while1{whileP170{fori1;i150;i/*控制音响时间*/{P10~P10;forj0;j50;j;/*延时完成信号gou周期时间*/}fori1;i100;i/*控制音响时间*/{P10~P10;forj0;j100;j;/*延时,完成信号周期时间*/}}}},,例10-14在下图中8XX51接有五个共阴极数码管的动态显示接口电路,开关打向位置“1”时,显示“12345”字样,当开关打向“2”时,显示“HELLO“字样,C语言编程程序清单如下。,,图10-7接五个共阴极数码管的动态显示接口,,用C语言完成上述功能编程includedefineuintunsignedintdeefineucharunsignedcharsbitP17P17;main{ucharcodetab1[5]{0 x86,0 xdb,0 xcf,0 xe6,0 xed};/*“15”的字形码,因P1.7接的开关,最高位送的“1”*/ucharcodetab2[5]{0 xf8,0 xf9,0 xb8,0 xb8,0 x,bf};/*“HELLO”的段码“1”*/uchari;unitj;while1{p30 x011fori0;i{ifp171P1tab1[i];elseP1tab2[i];P31;forj0;j0;i--){}},图10-844矩阵键盘,,ucharkbscanvoid/*键盘扫描函数*/{ucharsccode,recode;P10 xf0;/*P1.0P1.3发全0,P1.4P1.7输入*/ifP1/*无键按下,返回值为0*/},10.8.3C51中断程序的编制,C51使用户能编写高效的中断服务程序,编译器在规定的中断源的矢量地址中放入无条件转移指令,使CPU响应中断后自动地从矢量地址跳转到中断服务程序的实际地址,而无需用户去安排。中断服务程序定义为函数,函数的完整定义如下。返回值函数名[参数][模式][再入]interruptn[usingm]其中必选项interruptn表示将函数声明为中断服务函数,n为中断源编号,可以是0~31间的整数,不允许是带运算符的表达式,n通常取以下值0外部中断0;1定时器/计数器0溢出中断2外部中断1;3定时器/计数器1溢出中断4串行口发送与接收中断5定时器/计数器2中断,,各可选项的意义如下usingm定义函数使用的工作寄存器组,m的取值范围为0~3,可缺省。它对目标代码的影响是函数入口处将当前寄存器保存,使用m指定的寄存器组,函数退出时原寄存器组恢复。选不同的工作寄存器组,可方便实现寄存器组的现场保护。再入属性关键字reentrant将函数定义为再入的,在C51中,普通函数非再入的不能递归调用,只有再入函数才可被递归调用。中断服务函数不允许用于外部函数,它对目标代码影响如下①当调用函数时,SFR中的ACC、B、DPH、DPL和PSW当需要时入栈。②如果不使用寄存器组切换,中断函数所需的所有工作寄存器Rn都入栈。③函数退出前,所有工作寄存器都出栈。④函数由“RETI”指令终止。下面示例说明C语言的编程方法。,,例10-15对10.2.3的例10-4(见图)要求每中断一次,发光二极管显示开关状态用C语言编程includeint0interrupt0/*INT0中断函数*/{P10 x0f;/*输入端先置1,灯灭*/P4;/*读入开关状态,并左移四位,使开关反映在发光二极管上*/}main{EA1;/*开中断总开关*/EX01;/*允许INT0中断*/IT01;/*下降沿产生中断*/while1;/*等待中断*/},,例10-16记录并显示中断次数用C语言编程,可有两种编程方法。法1在主程序中判断中断次数,程序如下includechari;codechartab[16]{0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71};intinterrupt2{i;/*计中断次数*/P1tab[i];/*查表,次数送显示*/}main{EA1;EX11;IT11;ap5P10 x3f/*显示“0”*/fori0;i<16;;/*当i小于16等待中断*/gotoap5;/*当i16重复下一轮16次中断*/},,法2在中断程序中判断中断次数includechari;codechartab[16]{0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71};intinterrupt1{iifi<16P1tab[i];else{i0;P10 x3f;}}main{EA1;EX11;IT11;P10 x3f;while1;/*等待中断*/},10.8.4定时/计数器的C语言编程,例10-17在P1.7端接一个发光二极管LED,要求利用定时控制使LED亮一秒灭一秒周而复始,设fosc6MHz。分析T0定时100ms初值100103/250000,即初值为-50000。T1计数5个脉冲工作于方式2,计数初值为-5,T0和T1均采用中断方式。程序如下include〈reg51.h〉sbitP1_0P10;sbitP1_7P17;timer0interrupt1using1/*T0中断服务程序*/{P1_0P1_0;/*100ms到P1.0反相*/TH0-50000/256;/*重载计数初值*/TL0-50000256;}timerlinterrupt3using2/*T1中断服务程序*/{P1_7P1_7;/*1s到,灯改变状态*/},,main{P1_70;/*置灯初始灭*/P1_01;/*保证第一次反相便开始计数*/TMOD0 x61;/*T0方式1定时,T1方式2计数*/TH0-50000/256;/*预置计数初值*/TL0-50000256;TH1-5;TL1-5;IP0 x08;/*置优先级寄存器*/EA1;ET01;ET11;/*开中断*/TR01;TR11;/*启动定时/计数器*/for;;{}/*等待中断*/},,例10-18在内部数据存贮器20H~3FH单元中共有32个数据,要求采用方式1串行发送出去,传送速率为1200波特,设fosc=12MHZ。方法T1工作于方式2作波特率发生器,取SMOD=0,T1的时间常数计算如下波特率=(2SMOD/32)fosc/(12256-x)1200=(1/32)12106/12256-xx=230=E6H1查询方式编程C语的编程发送程序includemain{unsingnedchari;char*p;TMOD0 x20;TH10 xe6;TL10 xe6;TR11;,SCON0 x40;p0 x20;fori0;i32;i{SBUF*ppwhileTI;TI0;}},,接收程序includemain{unsingnedchari;char*p;TMOD0 x20;TH10 xe6;TL10 xe6;TR11;SCON0 x50;p0 x20;fori0;i32;i{whileRI;RI0;*pSBUF;p}},10.8.6外扩并行I/O口的C语言编程,例10-19用8155作6位共阴极LED显示器接口,PB口经驱动器7407接LED的段选,PA0~PA5位反相驱动器7406接位选,待显示字符依次存于dis-buf数组,从右向左顺序显示。8155命令字03,table为段码表,动态显示6个字符。8155和8XX51的接口见图10.12。,,图10.128155和8XX51单片机的接口电路,,各口的地址A口-7FF1HB口-7FF2HC口-7FF3H命令/状态口-7FF0HC语言程序如下includeincludedefineucharunsignedchardefineCOM8155XBYTE[0 x7ff0]definePA8155XBYTE[0 x7ff1]definePB8155XBYTE[0 x7ff2]definePC8155XBYTE[0 x7ff3]ucharidatadis[6]{2,4,6,8,10,12};/*存放显示字符2、4、6、8、A,C*/ucharcodetable[18]{0 x3f,0 x06,0 x5b,0 x4f,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71,0 x40,0 x00};,,voiddisplayucharidata*p{ucharsel,i,j;COM81550 x03;scl0 x01;/*送命令字,选最右边的LED*/fori0;i<6;i{PB8155table[*p];PA8155sel;/*送段码和位码*/forj400;j>0;j--;/*延时*/p--;/*地址指针下移位*/selsel0;i--/*汇编函数执行完后返回于此*/totalbuf[i-1];/*50个数累加*/totaltotal/50;/*求平均*/dis[0]total10;/*求个位,并存入显示缓冲区*/totaltotal/10;dis[1]total10;/*求十位,并存入显示缓冲区*/dis[2]total/10;/*求百位,并存入显示缓冲区*/P30 x01;/*P3口位选*/form0;m50;m{fori0;i3;i/*显示*/{P1tab[dis[i]];dayl50;/*调汇编函数DAYL,延时*/P3如果库中不存在指定的模块,则出错。示例如下图。,,10.10.3用户库函数的使用,为了使用已经制作好的用户库中的模块,在KeiluVision2集成开发环境中,只需要在其工程文件窗口中将用户自定义好的库文件加入工程即可。库中的函数原型一般单独在一个头文件中声明,余下的工作如同使用C51的标准函数一样简单,此处不再重述。由于库管理程序是以模块为单元来进行管理的。因此,在制作用户函数库的时候,最好是一个函数为一个模块文件。这样制作库时文件虽多,但管理和使用起来灵活方便高效。此外,如果想要改动C51原有的库函数(如_getkey和putchar),其方法同自建用户库完全一样,但需要主要的是它的输入输出参数及该函数所在的库文件(会与存储模式相关)。,,,10.11小结,本章介绍了C51的基本数据类型、存贮类型及对C51对单片机内部部件的定义,并介绍了C语言基础知识,最后通过编程实例介绍了各种结构的程序设计,以上是利用C语言编单片机程序的基础,都应该掌握并灵活应用,只有多编程,多上机才能不断提高编程的能力。如何编写高效的C语言程序,通常应注意以下问题1.定位变量经常访问的数据对象放入在片内数据RAM中,这可在任一种模式COMPACT/LARGE下用输入存贮器类型的方法实现。访问片内RAM要比访问片外RAM快得多。在片内RAM由寄存器组、位数据区、栈和其它由用户用“data”类型定义的变量共享。由于片内RAM容量的限制128~256字节,由使用的处理器决定,必须权衡利弊以解决访问效率和这些对象的数量之间的矛盾。,,,2.尽可能使用最小数据类型MCS-51系列单片机是8位机,因此对具有“char”类型的对象的操作比“int”或“long”类型的对象方便得多。建议编程者只要能满足要求,应尽量使用最小数据类型。C51编译器直接支持所有的字节操作,因而如果不是运算符要求,就不作“int”类型的转换,这可用一个乘积运算来说明,两“char”类型对象的乘积与8XX51操作码“MULAB”刚好相符。如果用整型完成同样的运算,则需调用库函数。3.只要有可能,使用“unsigned”数据类型8XX51单片机的CPU不直接支持有符号数的运算。因而C51编译必须产生与之相关的更多的代码以解决这个问题。如果使用无符号类型,产生的代码要少得多。4.只要有可能,使用局部函数变量编译器总是尝试在寄存器里保持局部变量。这样,将索引变量如FOR和WHILE循环中计数变量声明为局部变量是最好的,这个优化步骤只为局部变量执行。使用“unsignedchar/int”的对象通常能获得最好的结果。,,