基于Proteus的AVR单片机C语言程序设计与仿真
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 CodeVisionAVR的C语言基础知识

C语言是国际上广泛流行的计算机高级语言,是一种源于编写UNIX操作系统的语言,也是一种结构化语言,可产生紧凑代码。C语言结构是用括号“{}”而不是用字母和特殊符号表示的语言。在许多硬件平台中可以不使用汇编语言,而采用C语言来编写相关控制代码,以进行硬件系统的控制。由于C语言程序本身并不依赖机器硬件系统,如果在系统中更改单片机的型号或性能,对源程序稍加修改就可根据单片机的不同较快地进行程序移植,而移植程序时也不一定要求用户(程序开发人员)掌握MCU的指令系统,所以现在许多硬件开发人员都使用C语言来进行单片机系统的开发。

有很多第三方厂商为AVR单片机开发了用于程序开发的C编译器,这些C编译器的语法结构基本相同,但在某些细节上还是有所区别的,因此当用户选择了某个C编译器后,应掌握相应的C语言语法。本书将以CodeVisionAVR C编译器为例讲述AVR单片机的开发及应用。

3.1 数据运算

1.C语言程序结构

AVR单片机的C语言程序结构与一般C语言有很大的区别,每个AVR单片机的C语言程序至少有一个main()函数(即主函数)且只能有一个,它是C语言程序的基础,是程序代码执行的起点,而其他的函数都是通过main()函数直接或间接调用的。

AVR单片机的C语言程序结构具有以下特点。

(1)一个C语言源程序由一个或多个源文件组成,主要包括一些C源文件(即后缀名为“.C”的文件)和头文件(即后缀名为“.h”)。对于一些支持C语言的汇编语言混合编程的编译器而言,它还可包括一些汇编源程序(即后缀名为“.asm”)。

(2)每个源文件至少包含一个main()函数,也可以包含一个main()函数和其他多个函数。头文件中声明一些函数、变量或预定义一些特定值,而函数是在C源文件中实现的。(3)一个C语言程序总是从main()函数开始执行的,而不论main()函数在整个程序中的位置如何。

(4)源程序中可以有预处理命令(如include命令),这些命令通常放在源文件或源程序的最前面。

(5)每个声明或语句都以分号结尾,但预处理命令、函数头和花括号“{}”之后不能加分号。

(6)标识符、关键字之间必须加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。

(7)源程序中所用到的变量都必须先声明然后才能使用,否则编译时会报错。

C源程序的书写格式自由度较高,灵活性很强,有较大的任意性,但是这并不表示C源程序可以随意乱写。为了书写清晰,并便于阅读、理解、维护,在书写程序时最好遵循以下规则。

(1)通常情况下,一个声明或一个语句占用一行。在语句的后面可适量添加一些注释,以增强程序的可读性。

(2)不同结构层次的语句,从不同的起始位置开始,即在同一结构层次中的语句,应缩进同样的字数。

(3)用“{}”括起来的部分表示程序的某一层次结构。“{}”通常写在层次结构语句第一字母的下方,与结构化语句对齐,并占用一行。

在此以下面的程序为例,进一步说明AVR单片机C语言的程序结构特点及书写规则。程序清单如下所示。

            /****************************************************       //第1行
             File name:       例1.C                                 //第2行
             Chip type:       ATmega16                              //第3行
             Clock frequency:  8.0MHz                                //第4行
            ****************************************************/       //第5行
            #include<mega16.h>                                      //第6行
            #define uint unsigned int                                   //第7行
            void delay(uint k)                                        //第8行
            {                                                    //第9行
            uint m,n;                                             //第10行
                for(m=0;m<k;m++)                                   //第11行
                {                                               //第12行
                  for(n=0;n<1000;n++);                              //第13行
                }                                               //第14行
            }                                                    //第15行
            void main(void)                                         //第16行
            {                                                    //第17行
              DDRC=0xFF;                                         //第18行
                while(1)                                           //第19行
                {                                                //第20行
            PORTC.0=~PORTC.0;                                    //第21行
                  delay(1000);                                      //第22行
                }                                                //第23行
            }                                                    //第24行

这个小程序的作用是让接在ATmega16的PC0端口上的LED发光二极管进行秒闪显示。下面分析这个C程序源代码。

第1行至第5行为注释部分。传统的注释定界符使用斜杠-星号(即“/*”)和星号-斜杠(即“*/”)。斜杠-星号用于注释的开始。编译器一旦遇到斜杠-星号(即“/*”),就忽略后面的文本(即使是多行文本),直到遇到星号-斜杠(即“*/”)为止。简言之,在此程序中第1行至第5行的内容不参与编译。在程序中还可使用双斜杠(即“//”)来作为注释定界符。若使用双斜杠(即“//”),则编译器会忽略该行语句中双斜杠(即“//”)后面的一些文本。

第6行和第7行分别是两条不同的预处理命令。在程序中,凡是以“#”开头的均表示这是一条预处理命令语句。第6行为文件包含预处理命令,其意义是把双引号(即“”)或尖括号(<>)内指定的文件包含到本程序中,成为本程序的一部分。第7行为宏定义预处理命令语句,表示uint为无符号整数类型。被包含的文件通常是由系统提供的,也可以由程序员自己编写,其后缀名为“.h”。C语言的头文件中包括了各个标准库函数的函数原型。因此,在程序中调用一个库函数时,都必须包含函数原型所在的头文件。在AVR单片机中,不同的C语言编译器,其头文件也不尽相同。此例中,由于使用的是CodeVisionAVR编译器,所以头文件是“mega16.h”,在这个文件里定义了ATmega16的各个寄存器。若使用ICCAVR编译器,则其头文件应为“iom16v.h”。

第8行定义了一个延时函数,其函数名为“delay”,函数的参数为“uint k”。该函数采用了两个层次结构和两重循环语句。第9行~第15行表示外部层次结构,其中第9行表示延时函数从此处开始执行,第15行表示延时函数的结束。第12~第14行表示内部层次;第10行~第14行为数据说明和执行语句部分。第11行为外循环部分;第13行为内循环部分。

第16行定义了main主函数,函数的参数为“void”,意思是函数的参数为空,即不用传递给函数参数,函数即可运行。同样,该函数也采用了两个层次结构,第17行~第24行为外部层次结构;第20行~第23行为内部层次结构。

2.标识符与关键字

C语言的标识符用来标识源程序中变量、函数、标号和各种用户定义的对象的名字。CodeVisionAVR C中的标识符只能由字母(A~Z、a~z)、数字(0~9)、下画线(_)组成。其中第1个字符必须是字母或下画线,随后只能取字母、数字或下画线。标识符区分大小写,其长度不能超过32个字符。注意,标识符不能使用中文。

关键字是由C语言规定的具有特定意义的特殊标识符,有时又称保留字。关键字应当以小写形式输入。在编写C语言源程序时,用户定义的标识符不能与关键字相同。表3-1列出了CodeVisionAVR C中的一些关键字。

表3-1 CodeVisionAVR C中的一些关键字

3.数据类型

具有一定格式的数字或数值称为数据,数据是计算机操作的对象。数据的不同格式称为数据类型。

CodeVisionAVR C支持的数据类型有:位变量型(bit)、字符型(char)、无符号字符型(unsigned char)、有符号字符型(signed char)、整型(int)、短整型(short int)、无符号整型(unsigned int)、有符号整型(signed int)、长整型(long int)、无符号长整型(unsigned long int)、有符号长整型(signed long int)、单精度浮点型(float)、双精度浮点型(double)等,如图3-1所示。

基本类型就是使用频率最高的数据类型,其值不可以再分解为其他类型。CodeVisionAVR C基本数据类型的长度和范围如表3-2所示。若在CodeVisionAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中将“char is unsigned”复选框选中,或在源程序中使用了“#pragma uchar+”指令时,则char的范围为0~255。

图3-1 CodeVisionAVR C支持的数据类型

表3-2 CodeVisionAVR C基本数据类型的长度和范围

在CodeVisionAVR C中,若一个表达式中有两个操作数的类型不同,则编译器会自动按以下原则将其转换为同一类型的数据。

(1)如果两个数有一个为浮点型(即单精度或双精度浮点型),则另一个操作数将转换成浮点型。

(2)如果两个数有一个是长整型或无符号长整型,则另一个操作数将转换成相同的类型。

(3)如果两个数有一个是整型或无符号整型,则另一个操作数将转换成相同的类型。

(4)字符型和无符号字符型的优先级最低。

4.常量、变量及存储空间

1)常量所谓常量就是指在程序运行过程中,其值不能改变的数据。根据数据类型的不同,常量可分为整型常量、字符常量和实数型常量等。

整型常量可以用二进制、八进制、十进制和十六进制数表示。表示二进制数时,应在数字的前面加上“0b”的标志,其数码取值只能是“0”和“1”,如“0b10110010”表示二进制的“10110010”,其值为十制数的1×27+1×25+1×24+1×21=178;表示八进制数时,在数字的前面应加上“O”的标志,其数码取值只能是“0~7”,如“O517”表示八进制的“517”,其值为十进制数的5×82+1×81+7×80=335;表示十六进制时,应在数字的前面加上“0x“或“0X”的标志,其数码取值是数字“0~9”、字母“a~f”或字母“A~F”,如“0x3a”和“0X3A”均表示相同的十六进制数值,其值为十进制数的3×161+10×160=58。

无符号整数常量应在一个数字后面加上“u”或“U”,如6325U;长整型整数常量应在一个数字后面加上“l”或“L”,如97L。无符号长整型整数常量应在一个数字后面加上“ul”或“UL”,如25UL;实数型常量应在一个数字后面加上“f”或“F”,如3.146F。字符常量是用单引号将字符括起来表示的,如‘a’;字符串常量是用引号将字符括起来表示的,如“CodeVisionAVR”。

如果将一个字符串作为一个函数的参数来引用,则这个字符串将自动被当做常量使用,并且会被放在FLASH中。例如:

            //这是一个显示RAM中字符串的函数
            void  display_ram(char*s){
            /* …… */
            //这是一个显示FLASH中字符串的函数
            void  display_flash(char flash*s){
            /* …… */
            void main(){
            display_ram(“Hello world”);   //不工作,因为寻址的字符串在FLASH中
            display_flash(“Hello world”);//工作正常

在使用常量过程中需注意:①常量可以被定义成数组,最多为8维;②由于常量存在FLASH中,所以必须使用关键词flash或const;③常量表达式在编译时会自动求解;④常量可以在函数内部声明。以下为一些常量的定义:

            flash  int  integer_constant=2134+87;         //定义一个“2134+87”的整数
            flash  char  char_constant=a;               //定义一个字符‘a’
            flash  long  long_int_constant1=98L;         //定义一个长整数98
            flash  long  long_int_constant2=0x1234;       //定义一个十六进制的长整数0x1234
            flash  long  long_int_constant3=0b10011000;   //定义一个二进制的长整数0b10011000
            flash  int  integer_array1[]={3,2,4};       //定义一个一维数组,其元素分别为3,2,4
            flash  int  integer_array2[8]={3,2};           //定义一个含有8个元素的一维数组,其中前2个
                                                //元素为3,2,其余的元素为0
            flash  int  integer_array3[2,3]={{3,2,1},{4,5,6}}; //定义了一个2行3列共6个元素的
                                                //2维数组
            flash  char  string_constant1[]=This is a string constant;        //定义了一个字符串数组
            const  char  string_constant2[]=This is also a string constant;     //定义了一个字符串数组

2)变量所谓变量就是在程序运行过程中,其值可以改变的数据。

(1)局部变量与全局变量:根据实际程序的需求,变量可被声明为局部变量或全局变量。

局部变量是在创建函数时由函数分配的存储器空间,这些变量只能在所声明的函数内使用,而不能被其他的函数访问。但是在多个函数中可以声明变量名相同的局部变量,而不会引起冲突,这是因为编译器会将这些变量视为每个函数的一部分。

全局变量是由编译器分配的存储器空间,可被程序内所有的函数访问。全局变量能够被任何函数修改,并且会保持全局变量的值,以便由其他函数使用。

如果没有对全局变量或静态局部变量赋初值,则相当于给该变量赋初值0,因为CodeVisionAVR在程序开始时会对全部数据内存清0。在CodeVisionAVR中,若没有对一个局部变量赋初值,则该变量的值是不确定的。

定义局部变量与全局变量的语法格式如下:

            [<存储模式>]<类型定义><标识符>;

例如:

            /*全局变量*/
            char  a1;
            int   a2;
            /*赋初值*/
            long  a3=123456;
            void  main(void){
            /*局部变量*/
            char  a4;
            int   a5;
            /*赋初值*/
            long  a6=21346541;

变量也可以被定义成数组,且最多为8维,第一个数组元素编号为0。如果全局变量数组没有赋初值,则在程序开始时会被自动赋值为0。

例如:

            int  global_array1[32];                //定义了一个含32个元素的整型数组全局变量,所有
                                            //元素自动赋值为0
            int  global_array2[]={2,2,3};         //定义了一个整型数组全局变量,数组变量元素初值
                                            //分别为2,2,3
            int  global_array3[4]={4,2,3,1};     //定义了一个含4个元素的整型数组全局变量,元素
                                            //初值分别为4,2,3,1
            char global_array4[]=This is a string       //定义了一个字符数组全局变量
            int  global_array5[32]={1,2,3};       //定义了一个含32个元素的整型数组全局变量,前3
                                            //个元素赋初值,其余29个自动赋值为0
            int  multidim_array[2,3]={{1,2,3},{4,5,6}};  //定义了一个2行3列共6个元素的2维数
                                                    //组全局变量
            void main(void){
            int  local_array1[10];                 //定义了一个含10个元素的整型数组局部变量,每个
                                            //元素的初值为0
            int  local_array2[3]={11,22,33};      //定义了一个含3个元素的整型数组局部变量,元素
                                            //初值分别为11,22,33
            char local_array3[7]="Hello";            //定义了一个含7个元素的字符串数组局部变量

对一些需要被不同的函数调用,并且必须保存其值的局部变量而言,最好将这些局部变量声明为静态变量static。如果静态变量没有赋初值,在程序开始时会被自动赋值为0。

例如:

            int   alfa(void){
            static  int n=1;    //声明为静态变量
            return  n++;
            void   main(void){
            int  i;
            i=alfa();          //返回值为1
            i=alfa();          //返回值为2

如果变量在其他的文件中声明,则必须使用关键字extern。

例如:

            extern  int  xyz;
            #include<file_xyz.h>//包含声明xyz的文件

如果某个变量需占用寄存器,则必须使用register关键词告诉编译器分配寄存器给该变量。例如:

            register  int  abc;  //不管整型变量abc是否使用,编译器均会分配一个寄存器给变量abc

为了防止把一个变量分配到寄存器,必须使用volatile关键词,并且通知编译器这个变量的赋值受外部变化的支配。所有没被分配到寄存器的全局变量存放在SRAM的全局变量区;所有没被分配到寄存器的局部变量动态地存放在SRAM的数据堆栈区。例如:

            volatile  int  abc;

(2)指定全局变量保存在SRAM的地址单元:在程序中可以使用“@”操作符指定全局变量保存在SRAM的地址单元。

例如:

            int  a@0x80;          //指定整型变量a保存在SRAM的0x80h地址单元
            struct  b{
                    int a;
                    char c;
                    }alfa@0x100;  //结构体alfa 保存在SRAM的0x100h地址单元

(3)位变量:位变量是存储在寄存器R2~R15的特殊全局变量,用关键词bit声明。其语法格式如下:

            bit<标识符>;

例如:

            /*声明和赋初值*/
            bit  alfa=1;    //定义位变量alfa,其初值等于1,存储在R2寄存器的bit0中
            bit  beta;     //定义位变量beta,存储在R2寄存器的bit1中
            void main(void)
            if  (alfa)  beta=!beta;
            /*........*/

根据声明的顺序,位变量的分配从寄存器R2的bit0开始,按升序排列。由于R2~R15每个寄存器均有8位,所以最多可声明112个位变量。

若在CodeVisionAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中设置“bit Variables Size”选项,即可给位变量分配寄存器空间。为了给其他的全局变量分配寄存器,给位变量分配的空间要尽可能地小。

如果位变量没有赋初值,则在程序开始时会自动赋值为0。在表达式赋值中,位变量会自动转变为无符号字符型。

(4)给变量分配寄存器:为了给其他的全局变量分配寄存器,给位变量分配的空间要尽可能地小。若在CodeVisionAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中选择“Automatic Global Register Allocation”复选框,或在程序中使用#pragma regalloc+编译指示,则CodeVisionAVR编译器会自动将位变量未使用完所剩下的R2~R15中相关的寄存器分配给字符型和整形全局变量,直到R15也分配完为止。

如果自动寄存器分配被禁止,则可以用关键字“register”指定将某个全局变量分配到相应寄存器中。例如:

            #pragma  regalloc-      //禁止自动寄存器分配
            register  int  alfa;       //分配变量alfa到一个寄存器中
            register  int  beta@14;   //分配变量beta到寄存器R14和R15中

3)存储空间 AVR单片机采用Harvard(哈佛)结构,CPU中拥有3个独立的空间:Flash存储器、数据存储器和E2PROM存储器。由于C语言是针对冯·诺依曼结构的处理器开发的,并不适合描述单片机这3种不同的存储空间,所以AVR的C编译器均对此做了相应的扩充。CodeVisionAVR编译器对每个空间都做了相应的扩充,并引入了flash和eeprom两个关键词。

(1)Flash存储器:Flash存储器单元的首地址为0x0000,其容量由AVR单片机的型号决定,ATmega16的Flash存储空间为16KB。Flash存储器是非易失性存储器,当电源被切断后,保存在里面的数据也不会消失。

CodeVisionAVR编译器对字符串的处理方式与ICCAVR相同,即用户没有指定只保存在Flash中的字符串,在启动时将字符串从Flash存储器区复制到数据存储区。例如:

            char  *ptr_to_ram=”this string is placed in RAMI”;    //没有指定,字符串只存储在Flash中,因此
                                                    //启动时会将该字符串复制到数据存储空间
            char  flash  *ptr_to_ram=”this string is placed in flash”;//使用了flash关键词,字符串只存储在
                                                    //Flash中

CodeVisionAVR编译器最多支持8个元素的字符串数组,若该字符串数组超过8个元素时,CodeVisionAVR编译器将会报错。注意这个限制仅针对字符串数组而言,普通数组没有这个限制。例如:

            char  flash  *string[8]={“I”,“l”,“o”,“v”,“e”,“y”,“o”,“u”};

(2)数据存储器:AVR单片机的数据存储器一般包括3个相互独立的读/写存储区。全部共有1120个数据存储器地址,前96个地址为32个通用工作寄存器(地址空间为0x0000~0x001F)和64个I/O寄存器(地址空间为0x0020~0x005F),剩下的就是内部SRAM(地址空间为0x0060~0x045F)。

通用工作存储器一般用于存储程序运行过程中的局部变量和其他暂时性的数据,甚至还可以用于存储全局变量。64个I/O寄存器则用做微控制器上I/O设备的外设接口,而内部SRAM则用做通用的变量存储空间,同时也是处理器的堆栈。

(3)E2PROM存储器:存储器中的E2PROM区域是一块可以读/写非易失性存储空间的区域,它常用来存储那些在掉电以后不能丢失的和微控制器要反复运用的数据。E2PROM的首地址为0x000,最大的地址取决于所用的单片机型号。

虽然E2PROM存储器是可读写的,但很少用它来存放一般的变量,这是因为E2PROM的写速度非常慢,它要用一毫秒才能完成一字节数据的写操作。大量使用这类存储器来存放变量会在很大程度上降低处理器的速度。

在CodeVisionAVR中,可以使用“eeprom”关键字将全局变量分配至E2PROM区域。例如:

          /*对芯片编程时将“1”写入 E2PROM*/
          eeprom int alfa=1;
          eeprom char beta;
          eeprom long array1[5];
          /*对芯片编程时将字符串写入E2PROM*/
          eeprom char string[]="Hello";
          void main(void){
          int i;
          int eeprom*ptr_to_eeprom;  //指向E2PROM的指针
          alfa=0x85;             //将0x85直接写入E2PROM
          /*或使用指针间接写入*/
          ptr_to_eeprom=&alfa;
          *ptr_to_eeprom=0x85;
          i=alfa;                //直接从E2PROM中读出/
          /*或使用指针间接读出*/
          i=*ptr_to_eeprom;
          }

5.C语言的运算符及表达式

运算符是告诉编译程序执行特定算术或逻辑操作的符号。C语言的运算符和表达式相当丰富,在高级语言中是少见的。C语言常用的运算符如表3-3所示。

表3-3 C语言常用的运算符

C语言规定了一些运算符的优先级和结合性。优先级是指当运算对象两侧都有运算符时,执行运算的先后次序;结合性是指当一个运算两侧的运算符的优先级别相同时的运算顺序。C语言运算符的优先级及结合性如表3-4所示。

表3-4 C语言运算符的优先级及结合性

1)算术运算符算术运算符可用于各类数值运算,包括加(+)、减(-)、乘(*)、除(/)、求余(又称取模运算,%)、自增(++)和自减(- -)共7种运算。

用算术运算符和括号将运算对象连接起来的式子称为算术表达式,其运算对象包括常量、变量、函数和结构等。例如:

            a+b;
            a+b-c;
            a*(b+c)-(d-e)/f;
                a+b/c-3.6+’b’;

算术运算符的优先级规定为:先乘除求模,反加减,括号最优先。也就是说,在算术运算符中,乘、除、求余运算符的优先级相同,并高于加减运算符。在表达式中若出现括号,括号中的内容的优先级最高。例如:

            a-b/c;        //在这个表达式中,除号的优先级高于减号,因此先运算b/c求得商,再用a减
                        //去该商
            (a+b)*(c-d%e)-f;//在这个表达式中,括号的优先级最高,因此先运算(a+b)和(c-d%e),然后再将这
                        //两者相乘,最后再减去f。注意,执行(c-d%e)时,先将d除以e所得的余数作
                        //为被减数,然后用c减去该被减数即可

算术运算符的结合性规定为自左至右方向,又称“左结合性”,即当一个运算对象两侧的算术运算符优先级别相同时,运算对象与左面的运算符结合。例如:

            a-b+c;   //式中b两侧的“-”、“+”运算符的优先级别相同,则按左结合性,先执行a-b再与c相加
            a*b/c;    //式中b两侧的“*”、“/”运算符的优先级别相同,则按左结合性,先执行a*b再除以c

自增(++)和自减(--)运算符的作用是使变量的值增加或减少1。例如:

            ++a;     //先使a的值加上1,然后再使用a的值
            a++;     //先使用a的当前值进行运算,然后再使a加上1
            --a;     //先使a的值减少1,然后再使用a的值
            a--;     //先使用a的当前值进行运算,然后再使a减少1

2)赋值运算符和赋值表达式

(1)一般赋值运算符:在C语言中,最常见的赋值运算符为“=”,它的作用是计算表达式的值,再将数据赋值给左边的变量。赋值运算符具有右结合性,即当一个运算对象两侧的运算符优先级别相同时,运算对象与右面的运算符结合。

其一般格式为

            变量=表达式

例如:

            x=a+b;           //变量x输出为a加上b
            s=sqrt(a)+sin(b);    //变量s输出的内容为a的平方根加上b的正弦值
            y=i++;           //变量y输出的内容为i,然后i的内容加1
            y=z=x=3;         //可理解为y=(z=(x=3))

如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型转换成左边的类型。具体规定如下:①将实数型转换为整型时,舍去小数部分;②将整型转换为实数型时,数值不变,但将以实数型形式存放,即增加小数部分(小数部分的值为0);③将字符型转换为整型时,由于字符型为一个字节,而整型为两个字节,所以将字符的ASCII码值放到整型量的低8位中,高8位为0;④将整型转换为字符型时,只把低8位转换给字符变量。

(2)复合赋值运算符:在赋值运算符“=”的前面加上其他运算符,就可构成复合赋值运算符。C语言中的复合赋值运算符包括加法赋值运算符(+=)、减法赋值运算符(-=)、乘法赋值运算符(*=)、除法赋值运算符(/=)、求余(取模)赋值运算符(%=)、逻辑“与”赋值运算符(&=)、逻辑“或”赋值运算符(|=)、逻辑“异或”赋值运算符(^=)、逻辑“取反”赋值运算符(~=)、逻辑“左移”赋值运算符(<<=)、逻辑“右移”赋值运算符(>>=)共11种运算。

复合赋值运算首先对变量进行某种运算,然后再将运算的结果赋给该变量。采用复合赋值运算,可以简化程序,同时提高C程序的编译效率。复合赋值运算表达式的一般格式为

              变量  复合赋值运算符   表达式

例如:

            a+=b;                //相当于a=a+b
            a-=b;                //相当于a=a-b
            a*=b;                //相当于a=a*b
            a/=b;                 //相当于a=a/b
            a%=b;                //相当于a=a%b

3)关系运算符在程序中有时需要先对某些量的大小进行比较,然后根据比较的结果进行相应的操作。在C语言中,关系运算符专用于两个量的大小比较,其比较运算的结果只有“真”和“假”两个值。

C语言中的关系运算符包括:大于(>)、小于(<)、等于(==)、大于或等于(>=)、小于或等于(<=)、不等于(!=)共6种。

用关系运算符将两个表达式连接起来的式子称为关系表达式。关系运算符两边的运算对象可以是C语言中任意合法的表达式或者变量。关系表达式的一般格式为

              表达式   关系运算符   表达式

关系运算符的优先级别如下:①大于(>)、小于(<)、大于或等于(>=)、小于或等于(<=)属于同一优先级,等于(==)、不等于(!=)属于同一优先级,其中前4种运算符的优先级高于后2种运算符的优先级;②关系运算符的优先级别低于算术运算符,但高于赋值运算符的优先级。

例如:

              x>y;            //判断x是否大于y
            a+b<c;           //判断a加上b的和是否小于c
            a+b-c==m×n;       //判断a加上b的和再减去c的差值是否等于m乘上n的积

关系运算符的结合性为左结合。C语言不像其他高级语言一样有专门的“逻辑值”,它用整数“0”和“1”来描述关系表达式的运算结果,规定用“0”表示逻辑“假”,即当表达式不成立时,运算结果为“0”;用“1”表示逻辑“真”,即当表达式成立时,运算结果为“1”。例如:

            unsigned  char  x=8,y=9,z=18;//定义无符号字符x、y、z,它们的初始值分别为8、9、18
            x>y;            //x=8,y=9,x小于y,因此表达式不成立,运算结果为“0”
            x+y<z;          //x加y等于17,小于z(z=18),因此表达式成立,运算结果为“1”
            (y=18)==z;        //y重新赋值为18后等于z(z=18),因此表达式成立,运算结果为“1”
            x+6!=z;           //x加6等于14,不等于z(z=18),因此表达式成立,运算结果为“1”
            a==x<y<z       //由于关系运算符的结合性为左结合,所以x<y的值为1,而1<z的值
                            //为1,所以a的值为1

4)逻辑运算符逻辑关系主要包括逻辑“与”、逻辑“或”、逻辑“非”这3种基本运算。在C语言中,用“&&”表示逻辑“与”运算;用“||”表示逻辑“或”运算;用“!”表示逻辑“非”运算。其中,“&&”和“||”是双目运算符,它要求有两个操作数,而“!”是单目运算符,只要求一个操作数即可。注意,“&”和“|”是位运算符,不要将逻辑运算符与位运算符混淆。

用逻辑运算符将关系表达式或逻辑量连接起来的式子称为逻辑表达式。逻辑表达式的一般格式为:

              表达式   逻辑运算符   表达式

逻辑表达式的值是一个逻辑量,为“真”(即“1”)或“假”(即“0”)。对于逻辑“与”运算(&&)而言,参与运算的两个量都为“真”时,结果才为“真”,否则为“假”;对于逻辑“或”运算(||)而言,参与运算的两个量中只要有一个量为“真”时,结果为“真”,否则为“假”;对于逻辑“非”运算(!)而言,参与运算量为“真”时,结果为“假”,参与运算量为“假”时,结果为“真”。

逻辑运算符的优先级别如下:①在3个逻辑运算符中,逻辑“非”运算符(!)的优先级最高,其次是逻辑“与”运算符(&&)的优先级,逻辑“或”运算符(||)的优先级最低。②与算术运算符、关系运算符及赋值运算符的优先级相比,逻辑“非”运算符(!)的优先级高于算术运算符的优先级,算术运算符的优先级高于关系运算符的优先级,关系运算符的优先级高于逻辑“与”运算符(&&)和逻辑“或”运算符(||)的优先级,而赋值运算符的优先级最低。

假如:

            unsigned  char  a=5,b=8,y;   //定义无符号字符a、b、y,a的初始值为5,b的初始值为8
            y=!a;            //y的值为逻辑“假”,因为a=5为逻辑“真”,所以“!a”为逻辑“假”
            y=a||b;           //y的值为逻辑“真”,因为a、b为逻辑“真”,所以“a||b”为逻辑“真”
            y=a&&b;          //y的值为逻辑“真”,因为a、b为逻辑“真”,所以“a&&b”为逻辑“真”
            y=!a&&b;         //y的值为逻辑“假”,因为“!”的优先级高于“&&”,需先执行“!a”,其值为
            //逻辑“假”(即“0”);而“0&&b”的运算为逻辑“假”,所以结果为逻辑“假”

5)位操作运算符能对运算对象进行位操作是C语言的一大特点,正是由于这一特点使C语言具有了汇编语言的一些功能,从而使它能对计算机的硬件直接进行操作。

位操作运算符是指按位对变量进行运算,并不改变参与运算的变量的值。如果希望按位改变运算变量的值,则应利用相应的赋值运算。另外,位运算符只能对整型或字符型数据进行操作,不能用来对浮点型数据进行操作。

C语言中的位操作运算符包括:按位“与”(&)、按位“或”(|)、按位“异或”(^)、按位“取反”(~)、按位“左移”(<<)、按位“右移”(>>),共6种运算。除了按位“取反”运算符外,其余5种位操作运算符都是两目运算符,即要求运算符两侧各有一个运算对象。

(1)按位“与”(&):按位“与”的运算规则是参加运算的两个运算对象,若两者相应的位都为“1”,则该位的结果为“1”,否则为“0”。

例如:若a=0x62=0b01100010,b=0x3c=0b00111100,则表达式c=a&b的值为0x20,即

(2)按位“或”(|):按位“或”的运算规则是参加运算的两个运算对象,若两者相应的位中只要有一位为“1”,则该位的结果为“1”,否则为“0”。

例如:若a=0xa5=0b10100101,b=0x29=0b00101001,则表达式c=a|b的值为0xad,即

(3)按位“异或”(^):按位“异或”的运算规则是参加运算的两个运算对象,若两者相应的位值相同,则该位的结果为“0”;若两者相应的位值相异,则该位的结果为“1”。

例如:若a=0xb6=0b10110110,b=0x58=0b01011000,则表达式c=a^b的值为0xee,即

(4)按位“取反”(~):按位“取反”(~)是单目运算,用来对一个二进制数按位进行“取反”操作,即“0”变“1”,“1”变“0”。

例如:若a=0x72=0b01110010,则表达式a=~a的值为0x8d,即

(5)按位“左移”(<<)、按位“右移”(>>):按位“左移”(<<)用于将一个操作数的各二进制位的全部左移若干位,移位后,空白位补“0”,而溢出的位舍弃。

例如:若a=0x8b=0b10001011,则表达式a=a<<2表示将a值左移2位,其结果为0x2c,即

按位“右移”(>>)用于将一个操作数的各二进制位的全部右移若干位,移位后,空白位补“0”,而溢出的位舍弃。

例如:若a=0x8b=0b10001011,则表达式a=a>>2表示将a值左移2位,其结果为0x2c,即

在ICCAVR中不能直接访问寄存器的某一位,因此对寄存器的位操作时需使用C语言中的位运算功能。在CodeVisionAVR中可以直接或间接寄存器的某一位,它可以直接对位进行置“1”或清“0”操作。

①将寄存器的某一位清零,在ICC AVR中可使用按位“与”(&)来实现;在CodeVisionAVR中除了可以使用按位“与”(&)外,还可以直接将该位赋为“0”。

例如:将PC2清零,而其他位不变,以下3条指令均可实现该要求。

            PORTC&=0xfb;    //相当于PORTC=PORTC&0xfb,由于0xfb的二进制代码为“11111011”,
                            //所以执行该指令后,PC2被清零了
            PORTC&=~(1<<2);//相当于PORTC=PORTC& ~(1<<2),该指令先执行“(1<<2)”将“1”左
                            //移2位,再执行“~”取“反”操作,然后将取“反”的结果与PORTC
                            //按位“与”操作
            PORTC.2=0;       //此指令只能在CodeVisionAVR中使用,直接将PC2这位清零

②将寄存器的某一位置“1”,在ICC AVR中可使用按位“或”(|)来实现;在CodeVisionAVR中除了可以使用按位“或”(|)外,还可以直接将该位赋为“1”。

例如:将PC4置“1”,而其他位不变,以下3条指令均可实现该要求。

            PORTC|=0x10;     //相当于PORTC=PORTC|0x10,由于0x10的二进制代码为“00010000”,
                            //所以执行该指令后,PC4被置1
            PORTC|=(1<<4);   //相当于PORTC=PORTC&(1<<4),该指令先执行“(1<<4)”将“1”左移4
                            //位,再与PORTC按位“或”操作
            PORTC.4=1;       //此指令只能在CodeVisionAVR中使用,直接将PC4这位置“1”

③将寄存器的某一位“取反”,在ICC AVR中可使用按位“异或”(^)来实现;在CodeVisionAVR中除了可以使用按位“异或”(^)外,还可以直接将该位“取反”(~)。

例如:将PC6位“取反”,而其他位不变,以下3条指令均可实现该要求。

            PORTC^=0x40;    //相当于PORTC=PORTC^0x40,由于0x40的二进制代码为“01000000”,
                            //所以执行该指令后,PC4被“取反”
            PORTC^=(1<<6);   //相当于PORTC=PORTC^(1<<6),该指令先执行“(1<<6)”将“1”左移
                            //6位,再与PORTC按位“异或”操作
            PORTC.6=~PORTC //此指令只能在CodeVisionAVR中使用,直接将PC6这位“取反”

6)条件运算符条件运算符是C语言中唯一的一个三目运算符,它要求有3个运算对象,用它可以将3个表达式连接起来构成一个条件表达式。条件表达式的一般格式为

            表达式1?  表达式2  :  表达式3

条件表达式的功能是首先计算表达式1的逻辑值,当逻辑值为“真”时,将表达式2的值当做整个条件表达式的值;当逻辑值为“假”时,将表达式3的值当做整个条件表达式的值。例如:

            min=(a<b)?a:b     //当a小于b时,min=a;当a小于b不成立时,min=b

7)逗号运算符逗号运算符又称顺序示值运算符。在C语言中,逗号运算符用于将两个或多个表达式连接起来。逗号表达式的一般格式为

            表达式1,表达式2,…表达式n

逗号表达式的运算过程是,先求解表达式1,再求解表达式2,……依次求解到表达式n。例如:

            a=2+3,a*8            //先求解a=2+3,得a的值为5,然后求解a×8得40,整个逗号表达式
                                //的值为40
            a=4*5,a+10,a/6       //先求解a=4×5,得20,再求解a+10得30,最后求解a/6得5,整个
                                //逗号表达式的值为5

8)求字节运算符在C语言中提供了一种用于求取数据类型、变量及表达式的字节数的运算符sizeof。求字节运算符的一般格式为

            sizeof(表达式) 或 sizeof(数据类型)

注意,sizeof是一种特殊的运算符,而不是一个函数。通常,字节数的计算在程序编译时就完成了,而不是在程序执行的过程中计算出来的。