首页> 单片机教学(C言语教程)  
第七课 运算符和表达式(3)
作 者: 明浩 出 处: 磁动力工作室 http://www.cdle.net/ mailto:pnzwzw@cdle.net(ID111)
位运算符
   学过汇编的伴侣都晓得汇编对位的处置才能是很强的,可是C言语也能对运算对象进行按位操作,从而使C言语也能具有必然的对硬件间接进行操作的才能。位运算符的传染感动是按位对变量进交运算,可是并不改变参与运算的变量的值。若是要求按位改变变量的值,则要把持响应的赋值运算。又一次有便是位运算符是不能用来对浮点型数据进行操作的。C51中共有6种位运算符。
   位运算一般的表达形式如下:
     变量1 位运算符 变量2
   位运算符也有优先级,从高到低顺次是:"~"(按位取反)→"<<"(左移) →">>"(右移) →"&"(按位与)→"^"(按位异或)→"|"(按位或)
表7-1是位逻辑运算符的真值表,X暗示变量1,Y暗示变量2

X Y ~X ~Y X&Y X|Y X^Y
0 0 1 1 0 0 0
0 1 1 0 0 1 1
1 0 0 1 0 1 1
1 1 0 0 1 1 0

表7-1 按位取反,与,或和异或的逻辑真值表

  把持以前建立起来的测验考试板,咱们来做个测验考试验证一下位运算能否真是不改变参与变量的值,同时进修位运算的表达形式。法式很简单,用P1口做运算变量,P1.0-P1.7对应P1变量的最低位到较高位,经由连接在P1口上的LED咱们便能够直傍观到每个位运算后变量能否有改变或若何改变。法式如下:
#include <at89x51.h>
void main(void)
{
unsigned int a;
unsigned int b;
unsigned char temp; //姑且变量
P1 = 0xAA; //点亮D1,D3,D5,D7 P1口的二进制为10101010,为0时点亮LED
for (a=0;a<1000;a++)
for (b=0;b<1000;b++); //延时
temp = P1 & 0x7; //纯真的写P1|0x7是没成心义的,由于没有变量被影响,不会被编译
//施行P1|0x7后成果存入temp,这时改变的是temp,但P1不会被影响。
//这时LED没有变化,仍然是D1,D3,D5,D7亮
for (a=0;a<1000;a++)
for (b=0;b<1000;b++); //延时
P1 = 0xFF; //熄灭LED
for (a=0;a<1000;a++)
for (b=0;b<1000;b++); //延时
P1 = 0xAA; //点亮D1,D3,D5,D7 P1口的二进制为10101010,为0时点亮LED
for (a=0;a<1000;a++)
for (b=0;b<1000;b++); //延时
P1 = P1 & 0x7; //这时LED会变得只需D2灭
//由于之前P1=0xAA=10101010
//与0x7位与 0x7=00000111
//成果存入P1 P1=00000010 //位为O时点亮LED,电路看第三课
for (a=0;a<1000;a++)
for (b=0;b<1000;b++); //延时
P1 = 0xFF; //熄灭LED
while(1);
//大师能够按照上面的法式去做位或,左移,取反等等。
}

复合赋值运算符

   复合赋值运算符便是在赋值运算符"="的后面加上其他运算符。以下是C言语中的复合赋值运算符:
     += 加法赋值  >>= 右移位赋值
     -= 减法赋值   &= 逻辑与赋值
     *= 乘法赋值   |= 逻辑或赋值
     /= 除法赋值   ^= 逻辑异或赋值
     %= 取模赋值   -= 逻辑非赋值
     <<= 左移位赋值

   复合运算的一般形式为:
     变量 复合赋值运算符 表达式

   其含义便是变量与表达式前辈交运算符所要求的运算,再把运算成果赋值给参与运算的变量。其实这是C言语中一种简化法式的一种方式,凡是二目运算都能够用复合赋值运算符去简化表达。例如:
     a+=56等价于a=a+56
     y/=x+9 等价于 y=y/(x+9)
   很较着采用复合赋值运算符会降低法式的可读性,但多么却能够使法式代码简单化,并能提高编译的效率。对于初学C言语的伴侣在编程时较好仍是按照本人的理解力和习惯去使用法式表达的编制,不要一味追求法式代码的短小。

逗号运算符
   若是你有编程的经验,那么对逗号的传染感动也不会目生了。如在VB中"Dim a,b,c"的逗号便是把多个变量定义为统一类型的变量,在C也一样,如"int a,b,c",这些例子申明逗号用于分隔表达式用。但在C言语中逗号仍是一种特殊的运算符,也便是逗号运算符,能够用它将两个或多个表达式连接起来,构成逗号表达式。逗号表达式的一般形式为:
     表达式1,表达式2,表达式3……表达式n
   多么用逗号运算符构成的表达式在法式运转时,是从左到右算计出各个表达式的值,而整个用逗号运算符构成的表达式的值等于最右边表达式的值,便是"表达式n"的值。在现实的使用中,大部门环境下,使用逗号表达式的方针只是为了别离获得名个表达式的值,而并不必然要获得和使用整个逗号表达式的值。要留意的又一次有,并不是在法式的任何位置出现的逗号,都能够认为是逗号运算符。如函数中的参数,同类型变量的定义中的逗号只是用来间隔之用而不是逗号运算符。

前提运算符
   上面咱们说过C言语中有一个三目运算符,它便是"?:"前提运算符,它要求有三个运算对象。它能够把三个表达式连接构成一个前提表达式。前提表达式的一般形式如下:
     逻辑表达式? 表达式1 : 表达式2
   前提运算符的传染感动简单来说便是按照逻辑表达式的值选择使用表达式的值。当逻辑表达式的值为真时(非0值)时,整个表达式的值为表达式1的值;当逻辑表达式的值为假(值为0)时,整个表达式的值为表达式2的值。要留意的是前提表达式中逻辑表达式的类型能够与表达式1和表达式2的类型不一样。下面是一个逻辑表达式的例子。

如有a=1,b=2这时咱们要求是取ab两数中的较小的值放入min变量中,也许你会多么写:
if (a<b)
min = a;
else
min = b; //这一段的意义是当a<b时min的值为a的值,不然为b的值。

用前提运算符去构成前提表达式就变得简单了然了:
min = (a<b)?a : b
   很较着它的成果和含意都和上面的一段法式是一样的,可是代码却比上一段法式少良多,编译的效率也相对要高,但有着和复合赋值表达式一样的错误谬误便是可读性相对效差。在现实使用时按照本人要习惯使用,就我本人来说我爱好使用较为好读的编制和加上得当的注释,多么能够有助于法式的调试和编写,也便于日后的编削读写。

指针和地址运算符
   在第四课咱们进修数据类型时,进修过指针类型,晓得它是一种存放指向另一个数据的地址的变量类型。指针是C言语中一个很是次要的概念,也是进修C言语中的一个难点。对于指针将会在第九课中做细致的讲解。在这里咱们先来领会一下C言语中供给的两个特地用于指针和地址的运算符:
     * 取内容
     & 取地址
   取内容和地址的一般形式别离为:
     变量 = * 指针变量
     指针变量 = & 方针变量

   取内容运算是将指针变量所指向的方针变量的值赋给右边的变量;取地址运算是将方针变量的地址赋给右边的变量。要留意的是:指针变量中只能存放地址(也便是指针型数据),一般环境下不要将非指针类型的数据赋值给一个指针变量。
下面来看一个例子,并用一个图表和实例去简单理解指针的用法和含义。

  设有两个unsigned int 变量 ABC处CBA 存放在0x0028,0x002A中

      又一次有一个指针变量 portA 存放在0x002C中

      那么咱们写多么一段法式去看看*,&的运算成果

unsigned int data ABC _at_ 0x0028;

unsigned int data CBA _at_ 0x002A;

unsigned int data *Port _at_ 0x002C;

 

#include <at89x51.h>

#include <stdio.h>

 

void main(void)

{

    SCON = 0x50; //串口编制1,答应领受

    TMOD = 0x20; //按时器1按时编制2

    TH1 = 0xE8; //11.0592MHz 1200波特率

    TL1 = 0xE8;

    TI = 1;

    TR1 = 1; //启动按时器

 

    ABC = 10; //设初值

    CBA = 20;

 

    Port = &CBA; //取CBA的地址放到指针变量Port

    *Port = 100; //更改指针变量Port所指向的地址的内容

 

    printf("1: CBA=%d\n",CBA); //显示此时CBA的值

 

    Port = &ABC; //取ABC的地址放到指针变量Port

    CBA = *Port; //把当前Port所指的地址的内容赋给变量CBA

 

    printf("2: CBA=%d\n",CBA); //显示此时CBA的值

    printf("   ABC=%d\n",ABC); //显示ABC的值

}

法式初始时

地址

申明

0x00

0x002DH

 

0x00

0x002CH

 

0x00

0x002BH

 

0x00

0x002AH

 

0x0A

0x0029H

 

0x00

0x0028H

 

 

施行ABC = 10;向ABC所指的地址0x28H写入10(0xA),因ABC是int类型要占用0x28H和0x29H两个字节的内存空间,低位字节会放入高地址中,所以0x28H中放入0x00,0x29H中放入0x0A

地址

申明

0x00

0x002DH

 

0x00

0x002CH

 

0x00

0x002BH

 

0x00

0x002AH

 

0x0A

0x0029H

ABC为int类型占用两字节

0x00

0x0028H

 

 

施行CBA = 20;道理和上一句一样

地址

申明

0x00

0x002DH

 

0x00

0x002CH

 

0x14

0x002BH

CBA为int类型占用两字节

0x00

0x002AH

 

0x0A

0x0029H

ABC为int类型占用两字节

0x00

0x0028H

 

 

施行Port = &CBA; 取CBA的首地址放到指针变量Port

地址

申明

0x00

0x002DH

 

0x2A

0x002CH

CBA的首地址存入Port

0x14

0x002BH

 

0x00

0x002AH

 

0x0A

0x0029H

 

0x00

0x0028H

 

 

*Port = 100; 更改指针变量Port所指向的地址的内容

地址

申明

0x00

0x002DH

 

0x2A

0x002CH

 

0x64

0x002BH

Port指向了CBA地址地址2AH

0x00

0x002AH

并存入100

0x0A

0x0029H

 

0x00

0x0028H

 

其它的语句也是一样的事理,大师能够用Keil的单步施行和打开存储器查看器一看,多么就更容易理解了。

7-6 存储器查看窗
 
7-7 在串行调试窗口的最终成果

sizeof运算符
   看上去这确实是个奇异的运算符,有点像函数,却又不是。大师看到size该当就猜到是和大小相关的吧?是的,sizeof是用来求数据类型、变量或是表达式的字节数的一个运算符,但它并不像"="之类运算符那样在法式施行后才能算计出成果,它是间接在编译时发生成果的。它的语法如下:
     sizeof (数据类型)
     sizeof (表达式)
   下面是两句使用例句,法式大师能够试着编写一下。

    printf("char是多少个字节? %bd 字节\n",sizeof(char));
     printf("long是多少个字节? %bd 字节\n",sizeof(long));

  成果是:
char是多少个字节? 1字节
long是多少个字节? 4字节

强制类型转换运算符
   不知你们能否有本人去试着编一些法式,从中能否有碰到一些问题?初学时我就碰到过多么一个问题:两个不合数据类型的数在相互赋值时会出现不对的值。如下面的一段小法式:
void main(void)
{
unsigned char a;
unsigned int b;

b=100*4;
a=b;
while(1);
}
这段小法式并没有什么现实的使企图义,若是你是细心的伴侣定会发觉a的值是不会等于100*4的。是的a和b一个是char类型一个是int类型,从以前的进修可知char只占一个字节值较大只能是255。但编译时为何不犯错呢?先来看看这法式的运转环境:


图7-8 小法式的运转环境

  b=100*4就能够得知b=0x190,这时咱们能够在Watches查看a的值,对于watches窗口咱们在第5课时简单进修过,在这个窗口Locals页里能够查看法式运转中的变量的值,也能够在watch页中输入所要查看的变量名对它的值进行查看。做法是按图中1的watch#1(或watch#2),然后光标移到图中的2按F2键,多么就能够输入变量名了。在这里咱们能够查看到a的值为0x90,也便是b的低8位。这是由于施行了数据类型的隐式转换。隐式转换是在法式进行编译时由编译器主动去向理完成的。所以有需方式会隐式转换的法例:
   1.变量赋值时发生的隐式转换,"="号右边的表达式的数据类型转换成右边变量的数据类型。就如上面例子中的把INT赋值给CHAR字符型变量,获得的CHAR将会是INT的低8位。如把浮点数赋值给整形变量,小数部门将丢失。
   2.所有char型的操作数转换成int型。
   3.两个具有不合数据类型的操作数用运算符连接时,隐式转换会按以下次序进行:如有一操作数是float类型,则另一个操作数也会转换成float类型;若是一个操作数为long类型,另一个也转换成long;若是一个操作数是unsigned类型,则另一个操作会被转换成unsigned类型。
   从上面的法例能够大体晓得有那几种数据类型是能够进行隐式转换的。是的,在C51中只需char,int,long及float这几种根底的数据类型能够被隐式转换。而其它的数据类型就只能用到显示转换。要使用强制转换运算符应遵照以下的表达形式:

   (类型) 表达式

   用显示类型转换来处置不合类型的数据间运算和赋值是很是便当和便当的,出格对指针变量赋值是很有用的。看一面一段小法式:

#include <at89x51.h>
#include <stdio.h>

void main(void)
{
char xdata * XROM;
char a;
int Aa = 0xFB1C;
long Ba = 0x893B7832;
float Ca = 3.4534;
SCON = 0x50; //串口编制1,答应领受
TMOD = 0x20; //按时器1按时编制2
TH1 = 0xE8; //11.0592MHz 1200波特率
TL1 = 0xE8;
TI = 1;
TR1 = 1; //启动按时器
XROM=(char xdata *) 0xB012; //给指针变量赋XROM初值
*XROM = 'R'; //给XROM指向的无效地址赋值
a = *((char xdata *) 0xB012); //等同于a = *XROM
printf ("%bx %x %d %c \n",(char) Aa, (int) Ba,(int)Ca, a);//转换类型并输出
while(1);
}

法式运转成果:1c 7832 3 R

   在上面这段法式中,能够很清晰到到各类类型进行强制类型转换的根底用法,法式中先在外部数据存储器XDATA中定义了一个字符型指针变量XROM,当用XROM=(char xdata *) 0xB012这一语句时,便把0xB012这个地址指针赋于了XROM,如你用XROM则会长短法的,这类方式出格适合于用标识符来存取无效地址,如在法式前用#define ROM 0xB012多么的语句,在法式中就能够用上面的方式用ROM对无效地址0xB012进行存取操作了。

   在附录三中运算符的优先级申明。

   在这课的完结后,C言语中一些数据类型和运算规律已根底进修完了,下一课会起头讲述语法,函数等。

联系地址:浙江省杭州市西湖科技园西园七路3号4层 邮政编码:310011 Email:hificat@163.com xu169@sina.com
德律:0571-87615070 传真:0571-87615070 手机:13185018567 QQ:420951892 MSN:hificat@hotmail.com
杭州电子&算计机工作室 版权所有 COPYRIGHT2003——2007 HangZhou Electron&Computer Studio. All rights reserved