一、运算符的应用
C语言中的运算符主要包括:算术运算符、关系运算符、逻辑运算符、赋值运算符等。
【例】用单片机实现乘法“78×18”的运算,并通过P2口的发光二极管分时显示结果的高八位和低八位状态。
#include<reg51.h>
#define uint unsigned int //宏定义
#define uchar unsigned char
delay()
{
uint m,n;
for(m=1000;m>0;m--)
for(n=110;n>0;n--);
}
void main()
{
uint s; //保存乘法结果
uchar i,j; //保存相乘的因数
i=78;
j=18;
s=i*j;
while(1)
{
P2=s/256; //取乘积的高八位送P2口显示
delay();
P2=0xff;
delay();
P2=s%256; //取乘积的低八位送P2口显示
delay();
}
}
这个程序里,我们用到了宏定义,宏定义的格式为:#define 新名称 原内容
#define命令的作用是:用“新名称”代替后面的“原内容”,一般用于“原内容”比较长,又在程序里反复用到的情况。
【例】用十六个发光二极管显示除法运算结果。
芯片74HC595是八位串行输入转并行输出移位寄存器。其中引脚SER(14)是串行移位输入引脚,串行数据从低位到高位在该引脚输入;引脚SRCLK(11)移位时钟输入引脚,该引脚的上升沿可以使14脚的数据输入芯片内,即该引脚的上升沿控制数据串行移入;引脚RCLK(12)并行输出时钟端,通常情况下该引脚保持低电平,当串行数据移入完成时,该引脚产生一个上升沿,将刚才移入的数据在QA—QH端并行输出。
#include<reg51.h>
#define uint unsigned int
#define uchar unsigned char
sbit data1=P3^4; //定义74HC595中用到的几个口
sbit iclk=P3^6;
sbit oclk=P3^5;
uchar i,j,k,m;
void delay()
{
uint a,b;
for(a=10;a>0;a--)
for(b=110;b>0;b--);
}
void xianshi(uchar m) //74HC595显示子程序
{
uchar n;
n=m; //要显示的数存放在n里
oclk=0;
iclk=0;
for(j=8;j>0;j--) //要显示的数左移串行输入
{
n=n<<1;
data1=CY;
iclk=1;
delay();
iclk=0;
}
oclk=1; //移位完成并行输出
delay();
oclk=0;
}
void main()
{
i=10; //被除数和除数赋给i和j
j=6;
P2=i/j;
k=((i%j)*10)/j; //小数位保存在k中
xianshi(k); //调用显示子程序
while(1);
}
【例】用自增、自减运算控制P2口的流水灯。
#include<reg51.h>
void delay() //延时一秒子程序
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
unsigned char m,n;
while(1)
{
for(m=0;m<10;m++) //m从零到九自增一,状态从P2输出
{
P2=m;
delay();
}
for(n=10;n>0;n--) //n从十到一自减一,状态从P2输出
{
P2=n;
delay();
}
}
}
二、C语言的语句
一个完整的C程序是由若干条C语句按一定的方式组合而成的。按C语句执行方式的不同,C程序可分为顺序结构、选择结构和循环结构。
顺序结构:指程序按语句的顺序逐条执行。
选择结构:指程序根据条件选择相应的执行顺序。
循环结构:指程序根据某条件的存在重复执行一段程序,直到这个条件不满足为止。如果这个条件永远存在就会形成死循环。
一般的 C程序都是由上述三种结构混合而成的。但要保证 C程序能够按照预期的意图运行,还要用到以下5类语句来对程序进行控制。
1.控制语句:完成一定的控制功能,C语言中有9种控制语句。
if…else…:条件语句;
for…:循环语句
while…:循环语句
do…while…:循环语句
continue:结束本次循环语句
break:终止执行循环语句
switch:多分支选择语句
goto:跳转语句
return:从函数返回语句
2.函数调用语句:调用已定义过的函数,例如延时函数。
3.表达式语句:由一个表达式和一个分号构成,示例:z=x+y;
4.空语句:空语句什么也不做,常用于消耗若干机器周期,延时等待。
5.复合语句:用“{ }”把一些语句括起来就构成了复合语句。
1.if语句
格式如下:
(1)if(表达式)
(2)if(表达式)
语句1
else
语句2
(3)if(表达式1)
语句1
else
if(表达式2)
语句2
else
if(表达式3)
语句3
……
else
语句n
【例】用if语句控制P2口一个流水灯从低位到高位循环移位点亮。
#include<reg51.h>
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
unsigned char m=0xfe; //赋初值最低位的灯亮
while(1) //无限循环
{
P2=m;
delay();
m=(m<<1)|0x01; //m的值左移一位并且最低位填零
if(m==0xff)m=0xfe; //当点亮的灯移出最高位时,恢复初值0xfe
}
}
2.switch…case多分支选择语句
一般格式如下:
switch(表达式)
{
case 常量表达式1://如果常量表达式1满足,则执行语句1
语句1;
break; //执行语句1后,用此指令跳出switch结构
case 常量表达式2://如果常量表达式2满足,则执行语句2
语句2;
break; //执行语句2后,用此指令跳出switch结构
……
case 常量表达式n:
语句n;
break;
default: //上述表达式都不满足时,执行语句(n+1)
语句n+1;
}
【例】用多分支选择语句switch…case实现P2口流水灯的控制。
#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
void delay()
{
uint i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
char m=3; //m赋初值为3
while(1)
{
switch(m--) //表达式为m--,第一次执行为m的初值
{
case 0: //如果表达式的值为0,执行这句后面的语句
P2=0x01;
break; //执行完前一条指令,跳出switch
case 1: //如果表达式的值为1 ,执行这句后面的语句
P2=0x02;
break;
case 2: //如果表达式的值为2,执行这句后面的语句
P2=0x04;
break;
default: //当表达式的值不等于0-2,执行这句后面的语句
P2=0x08;
}
delay();
if(m<0)m=3; //如果m自减一后的结果小于零,重新赋为初值3
}
}
3.do…while循环语句
该循环语句先执行循环体一次,再判断表达式的值。若为真值,则继续执行循环,否则退出循环。一般格式如下:
do循环体语句
while(表达式);
do…while循环语句的执行过程如下:
先执行一次指定的循环体语句,然后判断表达式;当表达式的值为非零时,返回到第一步重新执行循环体语句;如此反复,直到表达式的值等于0时,循环结束。
使用时要注意while(表达式)后的分号“;”不能丢,它表示整个循环语句的结束。
【例】用do…while循环语句实现流水灯三次左移循环点亮。
#include<reg51.h>
#include<intrins.h> //包含循环左移指令的头文件
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
unsigned char m,n;
n=2;
do //do…while语句先执行循环体,再判断循环条件
{
P2=0xfe; //P2口的初始状态,最低位的灯亮
delay();
for(m=7;m>0;m--) //循环左移七次
{
P2=_crol_(P2,1); //循环左移语句
delay();
}
}while(n--); //n减一不为零时,返回执行循环体
while(1); //n等于零时,跳出循环,原地踏步
}
三、C语言的数组
数组是同类型的一组变量,引用这些变量时可用同一个标志符,借助于下标来区分各个变量。数组中的每一个变量称为数组元素。数组由连续的存储区域组成,最低地址对应于数组的第一个元素,最高地址对应于最后一个元素。数组可以是唯一的,也可以是多维的。
1.一维数组
一维数组的表达式如下:
类型说明符 数组名 [常量];
方括号中的常量称为下标。C语言中,下标是从0开始的。
int a[10]; //定义整型数组a,它有a[0]—a[9]共10个元素,每个元素都是整型变量
一维数组的赋值方法有以下几种。
(1)在数组定义时赋值,示例如下:
int a[10]={0,1,2,3,4,5,6,7,8,9};
数组元素的下标从0开始,赋值后,a[0]=0,a[1]=1,依次类推,直至a[9]=9。
(2)对于一个数组也可以部分赋值,示例如下:
int b[10]={0,1,2,3,4,5};
这里只对前6个元素赋值。对于没有赋值的b[6]—b[9],默认的初始值为0。
(3)如果一个数组的全部元素都已赋值,可以省去方括号中的下标,示例如下:
int a[ ]={0,1,2,3,4,5,6,7,8,9};
数组元素的赋值与普通变量相同。可以把数组元素像普通变量一样使用。
2.二维数组
C语言允许使用多维数组,最简单的多维数组是二维数组。其一般表达式形式如下:
类型说明符 数组名[下标1][下标2];
unsigned char x[3][4]; //定义无符号字符型二维数组,有3×4=12个元素
二维数组以行列矩阵的形式存储。第一个下标代表行,第二个下标代表列。上一数组中各数组元素的顺序排列如下:
x[0][0]、x[0][1]、x[0][2]、x[0][3]
x[1][0]、x[1][1]、x[1][2]、x[1][3]
x[2][0]、x[2][1]、x[2][2]、x[2][3]
二维数组的赋值方法可以采用以下两种方式。
(1)按存储顺序整体赋值,这是一种比较直观的赋值方式,示例如下:
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
如果是全部元素赋值,可以不指定行数,即
int a[ ][4]={0,1,2,3,4,5,6,7,8,9,10,11};
(2)按每行分别赋值。为了能更直观地给二维数组赋值,可以按每行分别赋值,这时要用{ }标明,没有说明的部分默认为0,示例如下:
int a[3][4]={ {0,1,2,3},{4,5,6,7},{8} }; //最后三个元素,没有赋值的被默认为0
3.字符数组
用来存放字符型数据的数组称为字符数组。与整型数组一样,字符数组也可以在定义时进行初始化赋值。
char a[8]={‘B’,’e’,’i’,’-’,’j’,’x’,’l’,’d’};
上述语句定义了字符型数组,它有a[0]—a[7]共8个元素,每个元素都是字符型变量。还可以用字符串的形式来对全体字符数组元素进行赋值。
char str[]={“Now, Temperature is:”};
要特别注意的是:字符串是以’\0’作为结束标志的。所以,当把一个字符串存入数组时,也把结束标志’\0’存入了数组。数组必须先定义,才能使用。
4.数组的应用
流水灯的控制方法有许多种,一种比较常用的方法是:把流水灯的控制代码按顺序存入数组,再依次引用数组元素,并送到发光二极管的接口显示。无论流水灯的控制逻辑多么复杂,用这种方法都可以很容易地通过调用控制代码实现。
【例】用数组控制P2口一个流水灯间隔1s右移点亮。
#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
uchar code tab[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; //定义一个数组
uchar i=0;
void delay()
{
uint m,n;
for(m=1000;m>0;m--)
for(n=110;n>0;n--);
}
void main()
{
for(;i<8;i++) //循环给P2口送数八次
{
P2=tab[i]; //引用数组元素,并送P2口显示
delay(); //调用延时一秒子程序
}
while(1); //显示结束,程序停在这里
}
5.数组作为函数参数
一个数组的名字表示该数组的首地址,所以用数组名作为函数的参数时,被传递的就是数组的首地址,被调用的函数的形式参数必须定义为指针型变量。
用数组名作为函数的参数时,应该在主调函数和被调函数中分别进行数组定义,而不能只在一方定义数组。并且,两个函数中定义的数组类型必须一致。如果不一致,将导致编译出错。实参数组和形参数组的长度可以一致也可以不一致,编译器不检查形参数组的长度,只是将实参数组的首地址传递给形参数组。为保证两者长度一致,最好在定义形参数组时,不指定长度,只在数组名后面跟一个空的方括号[ ]。编译时,系统会根据实参数组的长度为形参数组自动分配长度。
【例】使用数组作参数控制P2口八位流水灯点亮。
#include<reg51.h>
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void xianshi(unsigned char a[]) //定义显示子函数,形参为字符型数组首地址
{
unsigned char m;
for(m=0;m<8;m++)
{
P2=a[m]; //取数组的第m个元素送P2口显示
delay();
}
}
void main()
{
unsigned char code tab[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}; //定义流水灯控制码
while(1)
{
xianshi(tab); //调用显示子函数
}
}
四、C语言的指针
指针是C语言中的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用指针,可以有效地表示复杂的数据结构,动态地分配内存,方便地使用字符串,有效地使用数组。利用指针引用数组元素速度更快,占用内存更少。
1.指针的定义和引用
1.指针的概念
一个数据的“指针”就是它的地址。通过变量的地址能找到该变量在内存中的存储单元,从而能得到它的值。指针具有一般变量的三要素:名字、类型和值。指针与一般变量的区别在于类型和值上。
指针的值:指针存放的是某个变量在内存中的地址值。被定义过的变量都有一个内存地址。
指针的类型:指针的类型就是该指针所指向的变量的类型。例如,一个指针指向int型变量,该指针就是int型指针。
指针的定义格式:指针变量不同于整型或字符型等其他类型的数据,使用前必须将其定义为“指针类型”。指针定义的一般形式如下:
类型说明符 * 指针名字
int i; //定义一个整型变量i
int *pointer; //定义整型指针,名字为pointer
//可以用取地址运算符“&”使一个指针变量指向一个变量,例如:
pointer=&i; //“&i”表示取i的地址,将i的地址存放在指针变量pointer中
2.指针的初始化
在使用指针前必须进行初始化,一般格式如下:
类型说明符 指针变量=初始地址值;
unsigned char *p; //定义无符号字符型指针变量p
unsigned char m; //定义无符号字符型数据m
p=&m; //将m的地址存在p中(指针变量p被初始化了)
3.指针数组
指针可以指向某类变量,也可以指向数组。以指针变量为元素的数组称为指针数组。这些指针变量应具有相同的存储类型,并且指向的数据类型也必须相同。
指针数组定义的一般格式如下:
类型说明符 *指针数组名[元素个数];
int * p[2]; //p[2]是含有p[0]和p[1]两个指针的指针数组,指向int型数据
//指针数组的初始化可以在定义时同时进行
unsigned char a[] = {0,1,2,3};
unsigned char * p[4] = {&a[0],&a[1],&a[2],&a[3]}; //存放的元素必须为地址
4.指向数组的指针
一个变量有地址,一个数组元素也有地址,所以可以用一个指针指向一个数组元素。如果一个指针存放了某数组的第一个元素的地址,就说该指针是指向这一数组的指针。数组的指针即数组的起始地址。
unsigned char a[ ]={0,1,2,3};
unsigned char *p;
p=&a[0]; //将数组a的首地址存放在指针变量p中
经上述定义后,指针p就是数组a的指针。
C语言规定:数组名代表数组的首地址,也就是第一个元素的地址。
p=&a[0] ; 等价于 p=a;
C语言规定:p指向数组a的首地址后,p+1就指向数组的第二个元素a[1],p+2指向a[2],依次类推,p+i指向a[i]。
2.指针的应用
【例】用指针数组控制P2口八位流水灯点亮。
#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
void delay()
{
uint i,j;
for(i=0;i<1000;i++)
for(j=0;j<110;j++);
}
void main()
{
uchar code tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
uchar *p[]={&tab[0],&tab[1],&tab[2],&tab[3],&tab[4],&tab[5],&tab[6],&tab[7]};
uchar m=0;
for(;m<8;m++) //循环控制取数组元素八次
{
P2=*p[m]; //将指针数组中第i个元素送P2口
delay();
}
while(1); //取数完毕程序停在这里
}
【例】用指向数组的指针来控制流水灯
#include<reg51.h>
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
unsigned char code Tab[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
//流水灯控制码数组
unsigned char *p,m; //定义无符号字符型指针和控制循环的变量
p=Tab; //指针指向数组首地址
for(m=0;m<8;m++) //循环显示八个状态
{
P2=*(p+m); //通过指针引用数组元素的组,送到P2显示
delay();
}
while(1); //显示完毕,程序停在这里
}
从以上两个例子,我们可以比较一下两种用法的区别。指针数组需要先将指针数组元素都赋值(初始化)再使用,通过指针取数组元素的方法是“∗p[m]”(其中 m 是数组元素下 标)。而指向数组的指针使用前要先定义一个指针,并将数组的首地址给指针,取数组元素的方法是“∗(p+m)”。所以这两种方法使用时是有一定区别的,一定要注意区分。
3.指针作函数参数的应用
【例】用指针作函数参数控制P2口八位流水灯点亮。
#include<reg51.h>
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void display(unsigned char *p) //显示子程序,形参为无符号字符型指针
{
unsigned char i;
while(1) //无限循环
{
i=0;
while(*(p+i)!='\0') //当数组中元素未取完时,接着取数送显示
{
P2=*(p+i); //取数组中第i+1个元素送P2口显示
delay();
i++; //修改循环计数变量
}
}
}
void main()
{
unsigned char code tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
//定义显示码数组
unsigned char *pin;
pin=tab; //指针指向数组首地址
display(pin); //调用显示子程序
}
4.函数型指针的应用
在C语言中,指针变量除能指向数据对象外,也可以指向函数。一个函数在编译时,分配了一个入口地址,这个入口地址就称为函数的指针。可以用一个指针变量指向函数的入口地址,然后通过该指针变量调用此函数。
定义指向函数的指针变量的一般形式如下:
类型说明符(*指针变量名)(形参列表)
函数的调用可以通过函数名调用,也可以通过函数指针来调用。要通过函数指针调用函数,只要把函数的名字赋给该指针就可以了。
【例】用函数型指针控制P2口八位流水灯点亮
#include<reg51.h>
unsigned char code tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
//定义显示码数组
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void xianshi() //定义显示子程序
{
unsigned char i;
for(i=0;i<8;i++)
{
P2=tab[i]; //取数组中的第i+1个元素送P2口
delay();
}
}
void main()
{
void(*p)(void); //定义函数型指针p
p=xianshi; //将函数入口地址赋给指针p
while(1)(*p)(); //通过指针p调用显示函数,并循环
}
五、C语言的编译预处理
编译预处理是C语言编译器的一个组成部分。编译器在对整个程序进行编译之前,先对程序中的编译控制进行预处理,然后在将预处理的结果与整个C语言源程序一起进行编译,以产生目标代码。常用的预处理命令有宏定义、文件包含和条件命令。为了与一般C语言语句区别,预处理命令由“#”开头。
1.宏定义
C语言允许用一个标志符来表示一个字符串,称为宏。被定义为宏的标志符为宏名。在编译预处理时,程序中的所有宏名都用宏定义中的字符串代替,这个过程称为宏代换。宏定义分为不带参数的宏定义和带参数的宏定义。
不带参数的宏定义的一般形式如下:
#define 标志符 字符串
#define uchar unsigned char
带参数的宏定义不是进行简单的字符串替换,还要进行参数替换,其一般形式如下:
#define 宏名(参数表) 字符串
#define PI 3.1415926
#define S(r) PI*(r)*(r)
main()
{
float a,area;
a = 2.3;
area = S(a);
}
2.文件包含
文件包含是指一个程序将另一个指定的文件的全部内容包含进来。文件包含的命令一般格式如下:
#include<文件名>
文件包含命令的功能是用指定文件的全部内容替换该预处理行。
如果程序中要包含多个文件,则需要使用多个包含命令。当程序中需要调用C51编译器提供的各种库函数时,必须在程序的开头使用#include命令将相应的函数说明文件包含进来。
3.条件编译
一般情况下,对C语言程序进行编译时,所有的程序都参加编译,但有时希望对其中一部分内容只在满足一定条件时才进行编译,这就是所谓的条件编译。C51编译器的预处理提供的条件编译命令可以分为以下三种形式。
1)#ifdef 标志符
程序段1
#else
程序段2
#end if
2)#if 常量表达式
程序段1
#else
程序段2
#endif
如果常量表达式为“真”,那么就编译该语句后的程序段。
3)#ifndef 标志符
程序段1
#else
程序段2
#end if
【例】用带参数的宏定义完成运算a*b/(a+b),将结果送P2口显示。
#include<reg51.h>
#define F(a,b) (a)*(b)/((a)+(b))
void main()
{
int i,j,k;
i=34;
j=45;
k=30;
P2=F(i+j,k);
while(1);
}
【例】使用条件编译控制P2口点亮灯的状态。
#include<reg51.h>
#define max 100
void main()
{
#if max>80 //当max>80时,0xf0送P2口
P2=0xf0;
#else
P2=0x0f; //当max≤80时,0x0f送P2口
#endif
}
(1)用 C51编程实现除法“90÷8”的运算,并通过 P2口的发光二极管分时显示结果的商和余数。
#include<reg51.h> //p2 分时显示除法运算结果的商和余数
#define uchar unsigned char
#define uint unsigned int
delay()
{
uint m,n;
for(m=1000;m>0;m--)
for(n=110;n>0;n--);
}
void main()
{
uchar i,j;
i=90;
j=8;
while(1)
{
P2=i/j;
delay();
P2=0xff;
delay();
P2=i%j;
delay();
}
}
(2)编程实现:设初始状态为P2口8个灯全亮,用if语句控制P2口流水灯从高位到低位顺序熄灭。
#include<reg51.h> //if 用于流水灯控制
void delay()
{
unsigned int i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
unsigned char m=0;
P2=m;
delay();
|1: m=(m>>1)|0x80;
P2=m;
if(m!=0xff)goto |1;
while(1);
}
(3)编程实现:用多分支选择语句switch···case实现P2口流水灯从高位到低位点亮。
#include<reg51.h> //多分支语句在流水灯控制中的应用
#define uchar unsigned char
#define uint unsigned int
void delay()
{
uint i,j;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
char m=8;
while(1)
{
switch(m--)
{
case 8:
P2=0x0ff;
break;
case 7:
P2=0x7f;
break;
case 6:
P2=0x3f;
break;
case 5:
P2=0x1f;
break;
case 4:
P2=0x0f;
break;
case 3:
P2=7;
break;
case 2:
P2=3;
break;
case 1:
P2=1;
break;
case 0:
P2=0;
break;
default:
m=8;
}
}
}